diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 60a7139..7290950 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -2,7 +2,7 @@ name: Dockerhub Publish on: push: - branches: [ "main" ] + branches: [ "main", "develop" ] tags: [ "v*" ] pull_request: branches: [ "main" ] diff --git a/CHANGELOG.md b/CHANGELOG.md index a901456..1bfd3a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.4.0 + +- Added support for printing endless labels + # 0.3.0 - Added support for using QR codes instead of Datamatrix diff --git a/README.md b/README.md index 3ef1347..5fa1ec4 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,15 @@ The label size and printer are configured via environmental variables. You can a | NAME_MAX_LINES | 4 | The maximum number of lines to use for the name | | DUE_DATE_FONT | NotoSerif-Regular.ttf | The file name of the font in the fonts directory | | DUE_DATE_FONT_SIZE | 30 | The size of that font | +| ENDLESS_MARGIN | 10 | The top & bottom margin to add when using endless labels | Included fonts are `NotoSans-Regular.ttf` and `NotoSerif-Regular.ttf` +## Endless Labels + +These are supported, for example the `62` label size. The length of the label will be big enough to accommodate the max number of lines including a margin. +You may want to experiment with font sizes and line count to get the most out of it. + ## Endpoints Two endpoints are available `/print` and `/image` both accept the same parameters. `/image` will return the rendered image as a PNG instead of sending to the printer. @@ -75,7 +81,7 @@ Its advisable to run and install in a [venv](https://docs.python.org/3/library/v python -m venv .venv source ./.venv/bin/activate # Install packages - python -m pip install -U -r requirements + python -m pip install -U -r requirements.txt # exit with ./.venv/bin/deactivate ``` @@ -84,7 +90,6 @@ For development you can use `flask run --debug` to run the service on port 5000. ## TODO -- Endless Labels - Some more formatting options ### Docker @@ -101,4 +106,4 @@ An example `docker-compose.yml` file can be found [here](docker-compose.yml). I'll try to keep on top of bugs but feature requests may go unfulfilled. Please use the issue tracking in Github. -PRs welcome! \ No newline at end of file +PRs are welcome! diff --git a/app/__init__.py b/app/__init__.py index 424b33e..1848dba 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -19,6 +19,7 @@ NAME_MAX_LINES = int(getenv("NAME_MAX_LINES", "4")) DUE_DATE_FONT = getenv("NAME_FONT", "NotoSerif-Regular.ttf") DUE_DATE_FONT_SIZE = int(getenv("DUE_DATE_FONT_SIZE", "30")) +ENDLESS_MARGIN = int(getenv("ENDLESS_MARGIN", "10")) selected_backend = guess_backend(PRINTER_PATH) BACKEND_CLASS = backend_factory(selected_backend)['backend_class'] @@ -33,7 +34,7 @@ @app.route("/") def home_route(): - return "Label %s, Size %ix%i"%(label_spec.identifier, label_spec.dots_printable[0], label_spec.dots_printable[1]) + return "Label %s, %s"%(label_spec.identifier, label_spec.name) def get_params(): source = request.form if request.method == "POST" else request.args @@ -57,7 +58,7 @@ def get_params(): def print_route(): (name, barcode, dueDate) = get_params(); - label = createLabelImage(label_spec.dots_printable, name, nameFont, NAME_MAX_LINES, createBarcode(barcode, BARCODE_FORMAT), dueDate, ddFont) + label = createLabelImage(label_spec.dots_printable, ENDLESS_MARGIN, name, nameFont, NAME_FONT_SIZE, NAME_MAX_LINES, createBarcode(barcode, BARCODE_FORMAT), dueDate, ddFont) buf = BytesIO() label.save(buf, format="PNG") @@ -70,7 +71,7 @@ def print_route(): def test(): (name, barcode, dueDate) = get_params(); - img = createLabelImage(label_spec.dots_printable, name, nameFont, NAME_MAX_LINES, createBarcode(barcode, BARCODE_FORMAT), dueDate, ddFont) + img = createLabelImage(label_spec.dots_printable, ENDLESS_MARGIN, name, nameFont, NAME_FONT_SIZE, NAME_MAX_LINES, createBarcode(barcode, BARCODE_FORMAT), dueDate, ddFont) buf = BytesIO() img.save(buf, format="PNG") buf.seek(0) @@ -89,4 +90,3 @@ def sendToPrinter(image : Image): be = BACKEND_CLASS(PRINTER_PATH) be.write(bql.data) del be - diff --git a/app/imaging.py b/app/imaging.py index 67a371b..6db38ba 100644 --- a/app/imaging.py +++ b/app/imaging.py @@ -19,36 +19,62 @@ def createBarcode(text: str, type: str): case _: return createDatamatrix(text) -def createLabelImage(labelSize : tuple, text : str, textFont : ImageFont, textMaxLines : int, barcode : Image, dueDate : str, dueDateFont : ImageFont): +def createLabelImage(labelSize : tuple, endlessMargin : int, text : str, textFont : ImageFont, textFontSize : int, textMaxLines : int, barcode : Image, dueDate : str, dueDateFont : ImageFont): + (width, height) = labelSize + # default line spacing used by multiline_text, doesn't seem to have an effect if changed though but we need to take into account + lineSpacing = 4 + # margin to use for label + marginTop = 0 + marginBottom = 0 + + # for endless labels with a height of zero + if height == 0: + # height should be text size + spacing x max lines + margin x 2 + height = (textFontSize + lineSpacing) * textMaxLines + endlessMargin * 2 + # negate the empty space above the text + (_, tTop, _, _) = textFont.getbbox("testing") + marginTop = endlessMargin - tTop + # regular bottom margin + marginBottom = endlessMargin + # make space for the due date + if dueDate: + (_, _, _, ddBottom) = dueDateFont.getbbox(dueDate) + height += ddBottom + # increase the size of the barcode if space permits - if (barcode.size[1] * 4) < labelSize[1]: + if (barcode.size[1] * 8) < height: + barcode = barcode.resize((barcode.size[0] * 8, barcode.size[1] * 8), Image.Resampling.NEAREST) + if (barcode.size[1] * 6) < height: + barcode = barcode.resize((barcode.size[0] * 6, barcode.size[1] * 6), Image.Resampling.NEAREST) + if (barcode.size[1] * 4) < height: barcode = barcode.resize((barcode.size[0] * 4, barcode.size[1] * 4), Image.Resampling.NEAREST) - if (barcode.size[1] * 2) < labelSize[1]: + if (barcode.size[1] * 2) < height: barcode = barcode.resize((barcode.size[0] * 2, barcode.size[1] * 2), Image.Resampling.NEAREST) - label = Image.new("RGB", labelSize, ImageColor.getrgb("#FFF")) - # vertically align barcode + label = Image.new("RGB", (width, height), ImageColor.getrgb("#FFF")) + # vertically align barcode (ignoring margin) barcode_padding = [0, (int)((label.size[1] / 2) - (barcode.size[1] / 2))] label.paste(barcode, barcode_padding) draw = ImageDraw.Draw(label) - (nameText, nameTextWidth) = wrapText(text, textFont, label.size[0] - barcode.size[0], textMaxLines) - nameMaxWidth = label.size[0] - barcode.size[0] + (nameText, nameTextWidth) = wrapText(text, textFont, width - barcode.size[0], textMaxLines) + nameMaxWidth = width - barcode.size[0] nameLeftMargin = (nameMaxWidth - nameTextWidth) / 2 draw.multiline_text( - [barcode.size[0] + nameLeftMargin, 0], + [barcode.size[0] + nameLeftMargin, marginTop], nameText, fill = ImageColor.getrgb("#000"), font = textFont, - align = "center" + align = "center", + spacing = lineSpacing ) if dueDate: (_, _, ddRight, ddBottom) = dueDateFont.getbbox(dueDate) draw.text( - [label.size[0] - ddRight, label.size[1] - ddBottom], + [label.size[0] - ddRight, label.size[1] - ddBottom - marginBottom], dueDate, fill = ImageColor.getrgb("#000"), font = dueDateFont @@ -100,5 +126,8 @@ def wrapText(text : str, font : ImageFont, maxWidth : int, maxLines : int): if len(lines) > maxLines: lines = lines[0:maxLines] lines[-1] += '...' + lineLength = font.getlength(lines[-1]) + if lineLength > longestLine: + longestLine = lineLength - return ('\n'.join(lines), longestLine) \ No newline at end of file + return ('\n'.join(lines), longestLine)