feat: initial commit
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
__pycache__
|
||||
126
AveryLabels.py
Normal file
126
AveryLabels.py
Normal file
@@ -0,0 +1,126 @@
|
||||
import os
|
||||
from collections.abc import Iterator
|
||||
from reportlab.pdfgen import canvas
|
||||
from reportlab.lib.pagesizes import LETTER, landscape
|
||||
from reportlab.lib.units import inch, mm, cm
|
||||
from reportlab.pdfbase import pdfmetrics
|
||||
from reportlab.pdfbase.ttfonts import TTFont
|
||||
|
||||
# Usage:
|
||||
# label = AveryLabels.AveryLabel(5160)
|
||||
# label.open( "labels5160.pdf" )
|
||||
# label.render( RenderAddress, 30 )
|
||||
# label.close()
|
||||
#
|
||||
# 'render' can either pass a callable, which receives the canvas object
|
||||
# (with X,Y=0,0 at the lower right) or a string "form" name of a form
|
||||
# previously created with canv.beginForm().
|
||||
|
||||
|
||||
# labels across
|
||||
# labels down
|
||||
# label size w/h
|
||||
# label gutter across/down
|
||||
# page margins left/top
|
||||
|
||||
labelInfo = {
|
||||
# 2.6 x 1 address labels
|
||||
5160: ( 3, 10, (187, 72), (11, 0), (14, 36)),
|
||||
5161: ( 2, 10, (288, 72), (0, 0), (18, 36)),
|
||||
# 4 x 2 address labels
|
||||
5163: ( 2, 5, (288, 144), (0, 0), (18, 36)),
|
||||
# 1.75 x 0.5 return address labels
|
||||
5167: ( 4, 20, (126, 36), (0, 0), (54, 36)),
|
||||
# 3.5 x 2 business cards
|
||||
5371: ( 2, 5, (252, 144), (0, 0), (54, 36)),
|
||||
|
||||
# 48x 45.7x21.2mm
|
||||
4778: (4, 12, (45.7*mm, 21.2*mm), (0.25*cm, 0), (1.1*cm, 2*cm) ),
|
||||
}
|
||||
|
||||
class AveryLabel:
|
||||
|
||||
def __init__(self, label, **kwargs):
|
||||
data = labelInfo[label]
|
||||
self.across = data[0]
|
||||
self.down = data[1]
|
||||
self.size = data[2]
|
||||
self.labelsep = self.size[0]+data[3][0], self.size[1]+data[3][1]
|
||||
self.margins = data[4]
|
||||
self.topDown = True
|
||||
self.debug = False
|
||||
self.pagesize = LETTER
|
||||
self.position = 0
|
||||
self.__dict__.update(kwargs)
|
||||
|
||||
def open(self, filename):
|
||||
self.canvas = canvas.Canvas( filename, pagesize=self.pagesize )
|
||||
if self.debug:
|
||||
self.canvas.setPageCompression( 0 )
|
||||
self.canvas.setLineJoin(1)
|
||||
self.canvas.setLineCap(1)
|
||||
|
||||
def topLeft(self, x=None, y=None):
|
||||
if x == None:
|
||||
x = self.position
|
||||
if y == None:
|
||||
if self.topDown:
|
||||
x,y = divmod(x, self.down)
|
||||
else:
|
||||
y,x = divmod(x, self.across)
|
||||
|
||||
return (
|
||||
self.margins[0]+x*self.labelsep[0],
|
||||
self.pagesize[1] - self.margins[1] - (y+1)*self.labelsep[1]
|
||||
)
|
||||
|
||||
def advance(self):
|
||||
self.position += 1
|
||||
if self.position == self.across * self.down:
|
||||
self.canvas.showPage()
|
||||
self.position = 0
|
||||
|
||||
def close(self):
|
||||
if self.position:
|
||||
self.canvas.showPage()
|
||||
self.canvas.save()
|
||||
self.canvas = None
|
||||
|
||||
# To render, you can either create a template and tell me
|
||||
# "go draw N of these templates" or provide a callback.
|
||||
# Callback receives canvas, width, height.
|
||||
#
|
||||
# Or, pass a callable and an iterator. We'll do one label
|
||||
# per iteration of the iterator.
|
||||
|
||||
def render( self, thing, count, offset=0, *args ):
|
||||
assert callable(thing) or isinstance(thing, str)
|
||||
if isinstance(count, Iterator):
|
||||
return self.render_iterator( thing, count )
|
||||
|
||||
canv = self.canvas
|
||||
for i in range(offset+count):
|
||||
if i >= offset:
|
||||
canv.saveState()
|
||||
canv.translate( *self.topLeft() )
|
||||
if self.debug:
|
||||
canv.setLineWidth( 0.25 )
|
||||
canv.rect( 0, 0, self.size[0], self.size[1] )
|
||||
if callable(thing):
|
||||
thing( canv, self.size[0], self.size[1], *args )
|
||||
elif isinstance(thing, str):
|
||||
canv.doForm(thing)
|
||||
canv.restoreState()
|
||||
self.advance()
|
||||
|
||||
def render_iterator( self, func, iterator ):
|
||||
canv = self.canvas
|
||||
for chunk in iterator:
|
||||
canv.saveState()
|
||||
canv.translate( *self.topLeft() )
|
||||
if self.debug:
|
||||
canv.setLineWidth( 0.25 )
|
||||
canv.rect( 0, 0, self.size[0], self.size[1] )
|
||||
func( canv, self.size[0], self.size[1], chunk )
|
||||
canv.restoreState()
|
||||
self.advance()
|
||||
121
LICENSE
Normal file
121
LICENSE
Normal file
@@ -0,0 +1,121 @@
|
||||
Creative Commons Legal Code
|
||||
|
||||
CC0 1.0 Universal
|
||||
|
||||
CREATIVE COMMONS CORPORATION IS NOT A LAW FIRM AND DOES NOT PROVIDE
|
||||
LEGAL SERVICES. DISTRIBUTION OF THIS DOCUMENT DOES NOT CREATE AN
|
||||
ATTORNEY-CLIENT RELATIONSHIP. CREATIVE COMMONS PROVIDES THIS
|
||||
INFORMATION ON AN "AS-IS" BASIS. CREATIVE COMMONS MAKES NO WARRANTIES
|
||||
REGARDING THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS
|
||||
PROVIDED HEREUNDER, AND DISCLAIMS LIABILITY FOR DAMAGES RESULTING FROM
|
||||
THE USE OF THIS DOCUMENT OR THE INFORMATION OR WORKS PROVIDED
|
||||
HEREUNDER.
|
||||
|
||||
Statement of Purpose
|
||||
|
||||
The laws of most jurisdictions throughout the world automatically confer
|
||||
exclusive Copyright and Related Rights (defined below) upon the creator
|
||||
and subsequent owner(s) (each and all, an "owner") of an original work of
|
||||
authorship and/or a database (each, a "Work").
|
||||
|
||||
Certain owners wish to permanently relinquish those rights to a Work for
|
||||
the purpose of contributing to a commons of creative, cultural and
|
||||
scientific works ("Commons") that the public can reliably and without fear
|
||||
of later claims of infringement build upon, modify, incorporate in other
|
||||
works, reuse and redistribute as freely as possible in any form whatsoever
|
||||
and for any purposes, including without limitation commercial purposes.
|
||||
These owners may contribute to the Commons to promote the ideal of a free
|
||||
culture and the further production of creative, cultural and scientific
|
||||
works, or to gain reputation or greater distribution for their Work in
|
||||
part through the use and efforts of others.
|
||||
|
||||
For these and/or other purposes and motivations, and without any
|
||||
expectation of additional consideration or compensation, the person
|
||||
associating CC0 with a Work (the "Affirmer"), to the extent that he or she
|
||||
is an owner of Copyright and Related Rights in the Work, voluntarily
|
||||
elects to apply CC0 to the Work and publicly distribute the Work under its
|
||||
terms, with knowledge of his or her Copyright and Related Rights in the
|
||||
Work and the meaning and intended legal effect of CC0 on those rights.
|
||||
|
||||
1. Copyright and Related Rights. A Work made available under CC0 may be
|
||||
protected by copyright and related or neighboring rights ("Copyright and
|
||||
Related Rights"). Copyright and Related Rights include, but are not
|
||||
limited to, the following:
|
||||
|
||||
i. the right to reproduce, adapt, distribute, perform, display,
|
||||
communicate, and translate a Work;
|
||||
ii. moral rights retained by the original author(s) and/or performer(s);
|
||||
iii. publicity and privacy rights pertaining to a person's image or
|
||||
likeness depicted in a Work;
|
||||
iv. rights protecting against unfair competition in regards to a Work,
|
||||
subject to the limitations in paragraph 4(a), below;
|
||||
v. rights protecting the extraction, dissemination, use and reuse of data
|
||||
in a Work;
|
||||
vi. database rights (such as those arising under Directive 96/9/EC of the
|
||||
European Parliament and of the Council of 11 March 1996 on the legal
|
||||
protection of databases, and under any national implementation
|
||||
thereof, including any amended or successor version of such
|
||||
directive); and
|
||||
vii. other similar, equivalent or corresponding rights throughout the
|
||||
world based on applicable law or treaty, and any national
|
||||
implementations thereof.
|
||||
|
||||
2. Waiver. To the greatest extent permitted by, but not in contravention
|
||||
of, applicable law, Affirmer hereby overtly, fully, permanently,
|
||||
irrevocably and unconditionally waives, abandons, and surrenders all of
|
||||
Affirmer's Copyright and Related Rights and associated claims and causes
|
||||
of action, whether now known or unknown (including existing as well as
|
||||
future claims and causes of action), in the Work (i) in all territories
|
||||
worldwide, (ii) for the maximum duration provided by applicable law or
|
||||
treaty (including future time extensions), (iii) in any current or future
|
||||
medium and for any number of copies, and (iv) for any purpose whatsoever,
|
||||
including without limitation commercial, advertising or promotional
|
||||
purposes (the "Waiver"). Affirmer makes the Waiver for the benefit of each
|
||||
member of the public at large and to the detriment of Affirmer's heirs and
|
||||
successors, fully intending that such Waiver shall not be subject to
|
||||
revocation, rescission, cancellation, termination, or any other legal or
|
||||
equitable action to disrupt the quiet enjoyment of the Work by the public
|
||||
as contemplated by Affirmer's express Statement of Purpose.
|
||||
|
||||
3. Public License Fallback. Should any part of the Waiver for any reason
|
||||
be judged legally invalid or ineffective under applicable law, then the
|
||||
Waiver shall be preserved to the maximum extent permitted taking into
|
||||
account Affirmer's express Statement of Purpose. In addition, to the
|
||||
extent the Waiver is so judged Affirmer hereby grants to each affected
|
||||
person a royalty-free, non transferable, non sublicensable, non exclusive,
|
||||
irrevocable and unconditional license to exercise Affirmer's Copyright and
|
||||
Related Rights in the Work (i) in all territories worldwide, (ii) for the
|
||||
maximum duration provided by applicable law or treaty (including future
|
||||
time extensions), (iii) in any current or future medium and for any number
|
||||
of copies, and (iv) for any purpose whatsoever, including without
|
||||
limitation commercial, advertising or promotional purposes (the
|
||||
"License"). The License shall be deemed effective as of the date CC0 was
|
||||
applied by Affirmer to the Work. Should any part of the License for any
|
||||
reason be judged legally invalid or ineffective under applicable law, such
|
||||
partial invalidity or ineffectiveness shall not invalidate the remainder
|
||||
of the License, and in such case Affirmer hereby affirms that he or she
|
||||
will not (i) exercise any of his or her remaining Copyright and Related
|
||||
Rights in the Work or (ii) assert any associated claims and causes of
|
||||
action with respect to the Work, in either case contrary to Affirmer's
|
||||
express Statement of Purpose.
|
||||
|
||||
4. Limitations and Disclaimers.
|
||||
|
||||
a. No trademark or patent rights held by Affirmer are waived, abandoned,
|
||||
surrendered, licensed or otherwise affected by this document.
|
||||
b. Affirmer offers the Work as-is and makes no representations or
|
||||
warranties of any kind concerning the Work, express, implied,
|
||||
statutory or otherwise, including without limitation warranties of
|
||||
title, merchantability, fitness for a particular purpose, non
|
||||
infringement, or the absence of latent or other defects, accuracy, or
|
||||
the present or absence of errors, whether or not discoverable, all to
|
||||
the greatest extent permissible under applicable law.
|
||||
c. Affirmer disclaims responsibility for clearing rights of other persons
|
||||
that may apply to the Work or any use thereof, including without
|
||||
limitation any person's Copyright and Related Rights in the Work.
|
||||
Further, Affirmer disclaims responsibility for obtaining any necessary
|
||||
consents, permissions or other rights required for any use of the
|
||||
Work.
|
||||
d. Affirmer understands and acknowledges that Creative Commons is not a
|
||||
party to this document and has no duty or obligation with respect to
|
||||
this CC0 or use of the Work.
|
||||
22
README.md
Normal file
22
README.md
Normal file
@@ -0,0 +1,22 @@
|
||||
# ASN labels for Paperless-ngx on Avery labels
|
||||
|
||||
The [recommended workflow](https://docs.paperless-ngx.com/usage/#usage-recommended-workflow) of [Paperless-ngx](https://docs.paperless-ngx.com/) uses QR codes for ASN (archive serial number) labels. This script helps creating them using Python. It outputs a PDF for printing on the label sheets. Make sure to set print size to 100%, not _fit to page_ or similar.
|
||||
|
||||
Other Avery (or competitor's) label sizes can be added to `labelInfo` in `AveryLabels.py`. All other settings are configured at the top part of `main.py`.
|
||||
|
||||
Use these settings for an initial position test to align your printer:
|
||||
|
||||
```python
|
||||
mode = "text"
|
||||
debug = True
|
||||
|
||||
labelsAlreadyPrinted = 0
|
||||
labelsCorrupted = 0
|
||||
labelsToPrint = 1
|
||||
|
||||
positionHelper = True
|
||||
```
|
||||
|
||||
# Credits
|
||||
|
||||
This is based on the [work from timrprobocom](https://gist.github.com/timrprobocom/3946aca8ab75df8267bbf892a427a1b7)
|
||||
108
main.py
Normal file
108
main.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import AveryLabels
|
||||
from reportlab.lib.units import mm, cm
|
||||
from reportlab_qrcode import QRCodeImage
|
||||
from reportlab.pdfgen import canvas
|
||||
|
||||
### config ###
|
||||
labelForm = 4778
|
||||
|
||||
# mode "qr" prints a QR code and an ASN (archive serial number) text
|
||||
mode = "qr"
|
||||
subLabelsX = 2
|
||||
subLabelsY = 2
|
||||
|
||||
# mode text prints a free text
|
||||
#mode = "text"
|
||||
#text="6y"
|
||||
#subLabelsX = 5
|
||||
#subLabelsY = 3
|
||||
|
||||
# what was the first ASN number printed on this sheet
|
||||
firstASNOnSheet = 42
|
||||
# how many labels have already been printed on this sheet successfully
|
||||
labelsAlreadyPrinted = 20
|
||||
# how many labels have been corrupted on this sheet because of misprints
|
||||
labelsCorrupted = 4
|
||||
# how many labels should be printed now
|
||||
labelsToPrint = 18
|
||||
|
||||
fontSize = 2*mm
|
||||
qrSize = 0.9
|
||||
qrMargin = 1*mm
|
||||
|
||||
debug = False
|
||||
positionHelper = True
|
||||
|
||||
### pre-calculation ###
|
||||
asnsAlreadyPrinted = (labelsAlreadyPrinted-labelsCorrupted)*subLabelsX*subLabelsY
|
||||
startASN = firstASNOnSheet + asnsAlreadyPrinted
|
||||
offsetLabels = labelsAlreadyPrinted+labelsCorrupted
|
||||
|
||||
### globals ###
|
||||
currentASN = startASN
|
||||
|
||||
# debug
|
||||
count = 0
|
||||
|
||||
|
||||
def render(c: canvas.Canvas, width: float, height: float):
|
||||
global currentASN
|
||||
global subLabelsX
|
||||
global subLabelsY
|
||||
|
||||
subLabelWidth = width/subLabelsX
|
||||
subLabelHeight = height/subLabelsY
|
||||
|
||||
for i in range(subLabelsX):
|
||||
for j in range(subLabelsY-1, -1, -1): # no idea why inverted...
|
||||
subX = subLabelWidth*i
|
||||
subY = subLabelHeight*j
|
||||
|
||||
c.saveState()
|
||||
c.translate(subX, subY)
|
||||
|
||||
if mode == "qr":
|
||||
barcode_value = f"ASN{currentASN:05d}"
|
||||
currentASN = currentASN + 1
|
||||
|
||||
qr = QRCodeImage(barcode_value, size=subLabelHeight*qrSize)
|
||||
qr.drawOn(c, x=qrMargin, y=subLabelHeight*((1-qrSize)/2))
|
||||
c.setFont("Helvetica", size=fontSize)
|
||||
c.drawString(x=subLabelHeight, y=(
|
||||
subLabelHeight-fontSize)/2, text=barcode_value)
|
||||
|
||||
elif mode == "text":
|
||||
if debug:
|
||||
global count
|
||||
count = count + 1
|
||||
|
||||
c.drawString(
|
||||
x=(subLabelWidth-2*fontSize)/2, y=(subLabelHeight-fontSize)/2,
|
||||
text=text if not debug else str(count)
|
||||
)
|
||||
|
||||
if positionHelper:
|
||||
r = 0.1
|
||||
d = 0
|
||||
if debug:
|
||||
r = 0.5
|
||||
d = r
|
||||
c.circle(x_cen=0+d, y_cen=0+d, r=r, stroke=1)
|
||||
c.circle(x_cen=subLabelWidth-d, y_cen=0+d, r=r, stroke=1)
|
||||
c.circle(x_cen=0+d, y_cen=subLabelHeight-d, r=r, stroke=1)
|
||||
c.circle(x_cen=subLabelWidth-d,
|
||||
y_cen=subLabelHeight-d, r=r, stroke=1)
|
||||
|
||||
c.restoreState()
|
||||
|
||||
|
||||
fileName = "out/labels-" + str(labelForm) + "-" + str(mode) + ".pdf"
|
||||
|
||||
label = AveryLabels.AveryLabel(labelForm)
|
||||
label.debug = debug
|
||||
label.open(fileName)
|
||||
label.render(render, count=labelsToPrint, offset=offsetLabels)
|
||||
label.close()
|
||||
|
||||
print
|
||||
print(fileName)
|
||||
2
requirements.txt
Normal file
2
requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
reportlab
|
||||
reportlab_qrcode
|
||||
Reference in New Issue
Block a user