From 3c61bf71182390b52ff37df41da08082cb49640d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Heuer?= Date: Sun, 13 Feb 2022 22:22:35 +0100 Subject: [PATCH 1/6] Added support of Code128 --- brother_ql_web.py | 76 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 75 insertions(+), 1 deletion(-) diff --git a/brother_ql_web.py b/brother_ql_web.py index dcdeaaf..ee8c706 100755 --- a/brother_ql_web.py +++ b/brother_ql_web.py @@ -148,6 +148,74 @@ def create_label_im(text, **kwargs): draw.multiline_text(offset, text, kwargs['fill_color'], font=im_font, align=kwargs['align']) return im +def create_label_grocy_1d(text, **kwargs): + try: + product = kwargs['product'] + duedate = kwargs['duedate'] + grocycode = kwargs['grocycode'] + + product_font_size = 80 + duedate_font_size = 40 + barcode_height = 120 + + barcode = Code128(grocycode, writer=ImageWriter()) + barcode.save( + '/tmp/dmtx', {"module_height": 5.0, "quiet_zone": 0.5, "write_text": False}) + + product_font = ImageFont.truetype(kwargs['font_path'], product_font_size) + duedate_font = ImageFont.truetype(kwargs['font_path'], duedate_font_size) + width = kwargs['width'] + + if kwargs['orientation'] == "standard": + margin_left = kwargs['margin_left'] + margin_right = kwargs['margin_right'] + margin_top = kwargs['margin_top'] + margin_bottom = kwargs['margin_bottom'] + width = kwargs['width'] + else: + margin_left = kwargs['margin_bottom'] + margin_right = kwargs['margin_top'] + margin_top = kwargs['margin_left'] + margin_bottom = kwargs['margin_right'] + width = 700 + + + height = margin_top + margin_bottom + barcode_height + int(product_font_size * 1.3) - 30 + if duedate: + height += int(duedate_font_size * 1.3) + + im = Image.new('RGB', (width, height), 'white') + draw = ImageDraw.Draw(im) + + barcode = Image.open('/tmp/dmtx.png').resize((width-margin_left-margin_right,barcode_height)) + + vertical_offset = margin_top + horizontal_offset = margin_left + + textoffset = horizontal_offset, vertical_offset + draw.text(textoffset, product, kwargs['fill_color'], font=product_font) + + vertical_offset += product_font_size + + im.paste(barcode, (horizontal_offset, vertical_offset, width - margin_right, vertical_offset + barcode_height)) + + if duedate is not None: + vertical_offset += barcode_height + horizontal_offset = margin_left + textoffset = horizontal_offset, vertical_offset + draw.text(textoffset, duedate, + kwargs['fill_color'], font=duedate_font) + + if kwargs['orientation'] == "rotated": + im = im.transpose(Image.ROTATE_90) + + return im + except Exception as e: + exc_type, exc_obj, exc_tb = sys.exc_info() + logger.error('Exception happened: %s, Line: %s', + exc_type, exc_tb.tb_lineno) + return + def create_label_grocy(text, **kwargs): product = kwargs['product'] duedate = kwargs['duedate'] @@ -246,7 +314,13 @@ def print_grocy(): return_dict['error'] = 'Please provide the product for the label' return return_dict - im = create_label_grocy(**context) + code_1d = False + + im = None + if code_1d: + im = create_label_grocy_1d(**context) + else: + im = create_label_grocy(**context) if DEBUG: im.save('sample-out.png') if context['kind'] == ENDLESS_LABEL: From cabafc59eabc1ffed541d7c0cdbfc5052ad9b546 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Heuer?= Date: Sun, 13 Feb 2022 23:02:26 +0100 Subject: [PATCH 2/6] Added environment variable for 1d code support --- brother_ql_web.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/brother_ql_web.py b/brother_ql_web.py index ee8c706..7d99b79 100755 --- a/brother_ql_web.py +++ b/brother_ql_web.py @@ -157,7 +157,10 @@ def create_label_grocy_1d(text, **kwargs): product_font_size = 80 duedate_font_size = 40 barcode_height = 120 - + + from barcode.codex import Code128 + from barcode.writer import ImageWriter + barcode = Code128(grocycode, writer=ImageWriter()) barcode.save( '/tmp/dmtx', {"module_height": 5.0, "quiet_zone": 0.5, "write_text": False}) @@ -313,8 +316,11 @@ def print_grocy(): if context['product'] is None: return_dict['error'] = 'Please provide the product for the label' return return_dict - + + import os code_1d = False + if os.environ.get('Code128') and os.environ.get('Code128') == "1": + code_1d = True im = None if code_1d: @@ -483,4 +489,4 @@ def main(): run(host=CONFIG['SERVER']['HOST'], port=PORT, debug=DEBUG) if __name__ == "__main__": - main() + main() \ No newline at end of file From 1e619c35cff4edc2ffb569e72deadaf08e62b489 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Heuer?= Date: Tue, 15 Feb 2022 21:34:08 +0100 Subject: [PATCH 3/6] Reformatted with black --- font_helpers.py | 29 +++++++++++++++++------------ 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/font_helpers.py b/font_helpers.py index beb4567..61c57ea 100755 --- a/font_helpers.py +++ b/font_helpers.py @@ -4,6 +4,7 @@ logger = logging.getLogger(__name__) + def get_fonts(folder=None): """ Scan a folder (or the system) for .ttf / .otf fonts and @@ -11,22 +12,24 @@ def get_fonts(folder=None): """ fonts = {} if folder: - cmd = ['fc-scan', '--format', '%{file}:%{family}:style=%{style}\n', folder] + cmd = ["fc-scan", "--format", "%{file}:%{family}:style=%{style}\n", folder] else: - cmd = ['fc-list', ':', 'file', 'family', 'style'] - for line in subprocess.check_output(cmd).decode('utf-8').split("\n"): + cmd = ["fc-list", ":", "file", "family", "style"] + for line in subprocess.check_output(cmd).decode("utf-8").split("\n"): logger.debug(line) line.strip() - if not line: continue - if 'otf' not in line and 'ttf' not in line: continue - parts = line.split(':') - if 'style=' not in line or len(parts) < 3: + if not line: + continue + if "otf" not in line and "ttf" not in line: + continue + parts = line.split(":") + if "style=" not in line or len(parts) < 3: # fc-list didn't output all desired properties - logger.warn('skipping invalid font %s', line) + logger.warn("skipping invalid font %s", line) continue path = parts[0] - families = parts[1].strip().split(',') - styles = parts[2].split('=')[1].split(',') + families = parts[1].strip().split(",") + styles = parts[2].split("=")[1].split(",") if len(families) == 1 and len(styles) > 1: families = [families[0]] * len(styles) elif len(families) > 1 and len(styles) == 1: @@ -35,8 +38,10 @@ def get_fonts(folder=None): logger.debug("Problem with this font: " + line) continue for i in range(len(families)): - try: fonts[families[i]] - except: fonts[families[i]] = dict() + try: + fonts[families[i]] + except: + fonts[families[i]] = dict() fonts[families[i]][styles[i]] = path logger.debug("Added this font: " + str((families[i], styles[i], path))) return fonts From ede12cc3e1ebe486c6f558b307446b83e257607d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Heuer?= Date: Tue, 15 Feb 2022 21:35:36 +0100 Subject: [PATCH 4/6] Reformatted using black --- brother_ql_web.py | 549 ++++++++++++++++++++++++++++------------------ 1 file changed, 330 insertions(+), 219 deletions(-) diff --git a/brother_ql_web.py b/brother_ql_web.py index 7d99b79..e245c41 100755 --- a/brother_ql_web.py +++ b/brother_ql_web.py @@ -8,7 +8,17 @@ import sys, logging, random, json, argparse from io import BytesIO -from bottle import run, route, get, post, response, request, jinja2_view as view, static_file, redirect +from bottle import ( + run, + route, + get, + post, + response, + request, + jinja2_view as view, + static_file, + redirect, +) from PIL import Image, ImageDraw, ImageFont from brother_ql.devicedependent import models, label_type_specs, label_sizes @@ -20,284 +30,325 @@ logger = logging.getLogger(__name__) -LABEL_SIZES = [ (name, label_type_specs[name]['name']) for name in label_sizes] +LABEL_SIZES = [(name, label_type_specs[name]["name"]) for name in label_sizes] try: - with open('config.json', encoding='utf-8') as fh: + with open("config.json", encoding="utf-8") as fh: CONFIG = json.load(fh) except FileNotFoundError as e: - with open('config.example.json', encoding='utf-8') as fh: + with open("config.example.json", encoding="utf-8") as fh: CONFIG = json.load(fh) -@route('/') +@route("/") def index(): - redirect('/labeldesigner') + redirect("/labeldesigner") -@route('/static/') + +@route("/static/") def serve_static(filename): - return static_file(filename, root='./static') + return static_file(filename, root="./static") + -@route('/labeldesigner') -@view('labeldesigner.jinja2') +@route("/labeldesigner") +@view("labeldesigner.jinja2") def labeldesigner(): font_family_names = sorted(list(FONTS.keys())) - return {'font_family_names': font_family_names, - 'fonts': FONTS, - 'label_sizes': LABEL_SIZES, - 'website': CONFIG['WEBSITE'], - 'label': CONFIG['LABEL']} + return { + "font_family_names": font_family_names, + "fonts": FONTS, + "label_sizes": LABEL_SIZES, + "website": CONFIG["WEBSITE"], + "label": CONFIG["LABEL"], + } + def get_label_context(request): - """ might raise LookupError() """ + """might raise LookupError()""" - d = request.params.decode() # UTF-8 decoded form data + d = request.params.decode() # UTF-8 decoded form data - font_family = d.get('font_family').rpartition('(')[0].strip() - font_style = d.get('font_family').rpartition('(')[2].rstrip(')') + font_family = d.get("font_family").rpartition("(")[0].strip() + font_style = d.get("font_family").rpartition("(")[2].rstrip(")") context = { - 'text': d.get('text', None), - 'font_size': int(d.get('font_size', 100)), - 'font_family': font_family, - 'font_style': font_style, - 'label_size': d.get('label_size', "62"), - 'kind': label_type_specs[d.get('label_size', "62")]['kind'], - 'margin': int(d.get('margin', 10)), - 'threshold': int(d.get('threshold', 70)), - 'align': d.get('align', 'center'), - 'orientation': d.get('orientation', 'standard'), - 'margin_top': float(d.get('margin_top', 24))/100., - 'margin_bottom': float(d.get('margin_bottom', 45))/100., - 'margin_left': float(d.get('margin_left', 35))/100., - 'margin_right': float(d.get('margin_right', 35))/100., - 'grocycode': d.get('grocycode', None), - 'product': d.get('product', None), - 'duedate': d.get('duedate', None) + "text": d.get("text", None), + "font_size": int(d.get("font_size", 100)), + "font_family": font_family, + "font_style": font_style, + "label_size": d.get("label_size", "62"), + "kind": label_type_specs[d.get("label_size", "62")]["kind"], + "margin": int(d.get("margin", 10)), + "threshold": int(d.get("threshold", 70)), + "align": d.get("align", "center"), + "orientation": d.get("orientation", "standard"), + "margin_top": float(d.get("margin_top", 24)) / 100.0, + "margin_bottom": float(d.get("margin_bottom", 45)) / 100.0, + "margin_left": float(d.get("margin_left", 35)) / 100.0, + "margin_right": float(d.get("margin_right", 35)) / 100.0, + "grocycode": d.get("grocycode", None), + "product": d.get("product", None), + "duedate": d.get("duedate", None), } - context['margin_top'] = int(context['font_size']*context['margin_top']) - context['margin_bottom'] = int(context['font_size']*context['margin_bottom']) - context['margin_left'] = int(context['font_size']*context['margin_left']) - context['margin_right'] = int(context['font_size']*context['margin_right']) + context["margin_top"] = int(context["font_size"] * context["margin_top"]) + context["margin_bottom"] = int(context["font_size"] * context["margin_bottom"]) + context["margin_left"] = int(context["font_size"] * context["margin_left"]) + context["margin_right"] = int(context["font_size"] * context["margin_right"]) - context['fill_color'] = (255, 0, 0) if 'red' in context['label_size'] else (0, 0, 0) + context["fill_color"] = (255, 0, 0) if "red" in context["label_size"] else (0, 0, 0) def get_font_path(font_family_name, font_style_name): try: if font_family_name is None or font_style_name is None: - font_family_name = CONFIG['LABEL']['DEFAULT_FONTS']['family'] - font_style_name = CONFIG['LABEL']['DEFAULT_FONTS']['style'] + font_family_name = CONFIG["LABEL"]["DEFAULT_FONTS"]["family"] + font_style_name = CONFIG["LABEL"]["DEFAULT_FONTS"]["style"] font_path = FONTS[font_family_name][font_style_name] except KeyError: raise LookupError("Couln't find the font & style") return font_path - context['font_path'] = get_font_path(context['font_family'], context['font_style']) + context["font_path"] = get_font_path(context["font_family"], context["font_style"]) def get_label_dimensions(label_size): try: - ls = label_type_specs[context['label_size']] + ls = label_type_specs[context["label_size"]] except KeyError: raise LookupError("Unknown label_size") - return ls['dots_printable'] + return ls["dots_printable"] - width, height = get_label_dimensions(context['label_size']) - if height > width: width, height = height, width - if context['orientation'] == 'rotated': height, width = width, height - context['width'], context['height'] = width, height + width, height = get_label_dimensions(context["label_size"]) + if height > width: + width, height = height, width + if context["orientation"] == "rotated": + height, width = width, height + context["width"], context["height"] = width, height return context + def create_label_im(text, **kwargs): - label_type = kwargs['kind'] - im_font = ImageFont.truetype(kwargs['font_path'], kwargs['font_size']) - im = Image.new('L', (20, 20), 'white') + label_type = kwargs["kind"] + im_font = ImageFont.truetype(kwargs["font_path"], kwargs["font_size"]) + im = Image.new("L", (20, 20), "white") draw = ImageDraw.Draw(im) # workaround for a bug in multiline_textsize() # when there are empty lines in the text: lines = [] - for line in text.split('\n'): - if line == '': line = ' ' + for line in text.split("\n"): + if line == "": + line = " " lines.append(line) - text = '\n'.join(lines) + text = "\n".join(lines) linesize = im_font.getsize(text) textsize = draw.multiline_textsize(text, font=im_font) - width, height = kwargs['width'], kwargs['height'] - if kwargs['orientation'] == 'standard': + width, height = kwargs["width"], kwargs["height"] + if kwargs["orientation"] == "standard": if label_type in (ENDLESS_LABEL,): - height = textsize[1] + kwargs['margin_top'] + kwargs['margin_bottom'] - elif kwargs['orientation'] == 'rotated': + height = textsize[1] + kwargs["margin_top"] + kwargs["margin_bottom"] + elif kwargs["orientation"] == "rotated": if label_type in (ENDLESS_LABEL,): - width = textsize[0] + kwargs['margin_left'] + kwargs['margin_right'] - im = Image.new('RGB', (width, height), 'white') + width = textsize[0] + kwargs["margin_left"] + kwargs["margin_right"] + im = Image.new("RGB", (width, height), "white") draw = ImageDraw.Draw(im) - if kwargs['orientation'] == 'standard': + if kwargs["orientation"] == "standard": if label_type in (DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL): - vertical_offset = (height - textsize[1])//2 - vertical_offset += (kwargs['margin_top'] - kwargs['margin_bottom'])//2 + vertical_offset = (height - textsize[1]) // 2 + vertical_offset += (kwargs["margin_top"] - kwargs["margin_bottom"]) // 2 else: - vertical_offset = kwargs['margin_top'] - horizontal_offset = max((width - textsize[0])//2, 0) - elif kwargs['orientation'] == 'rotated': - vertical_offset = (height - textsize[1])//2 - vertical_offset += (kwargs['margin_top'] - kwargs['margin_bottom'])//2 + vertical_offset = kwargs["margin_top"] + horizontal_offset = max((width - textsize[0]) // 2, 0) + elif kwargs["orientation"] == "rotated": + vertical_offset = (height - textsize[1]) // 2 + vertical_offset += (kwargs["margin_top"] - kwargs["margin_bottom"]) // 2 if label_type in (DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL): - horizontal_offset = max((width - textsize[0])//2, 0) + horizontal_offset = max((width - textsize[0]) // 2, 0) else: - horizontal_offset = kwargs['margin_left'] + horizontal_offset = kwargs["margin_left"] offset = horizontal_offset, vertical_offset - draw.multiline_text(offset, text, kwargs['fill_color'], font=im_font, align=kwargs['align']) + draw.multiline_text( + offset, text, kwargs["fill_color"], font=im_font, align=kwargs["align"] + ) return im + def create_label_grocy_1d(text, **kwargs): try: - product = kwargs['product'] - duedate = kwargs['duedate'] - grocycode = kwargs['grocycode'] + product = kwargs["product"] + duedate = kwargs["duedate"] + grocycode = kwargs["grocycode"] product_font_size = 80 duedate_font_size = 40 barcode_height = 120 - + from barcode.codex import Code128 from barcode.writer import ImageWriter - + barcode = Code128(grocycode, writer=ImageWriter()) barcode.save( - '/tmp/dmtx', {"module_height": 5.0, "quiet_zone": 0.5, "write_text": False}) - - product_font = ImageFont.truetype(kwargs['font_path'], product_font_size) - duedate_font = ImageFont.truetype(kwargs['font_path'], duedate_font_size) - width = kwargs['width'] - - if kwargs['orientation'] == "standard": - margin_left = kwargs['margin_left'] - margin_right = kwargs['margin_right'] - margin_top = kwargs['margin_top'] - margin_bottom = kwargs['margin_bottom'] - width = kwargs['width'] + "/tmp/dmtx", {"module_height": 5.0, "quiet_zone": 0.5, "write_text": False} + ) + + product_font = ImageFont.truetype(kwargs["font_path"], product_font_size) + duedate_font = ImageFont.truetype(kwargs["font_path"], duedate_font_size) + width = kwargs["width"] + + if kwargs["orientation"] == "standard": + margin_left = kwargs["margin_left"] + margin_right = kwargs["margin_right"] + margin_top = kwargs["margin_top"] + margin_bottom = kwargs["margin_bottom"] + width = kwargs["width"] else: - margin_left = kwargs['margin_bottom'] - margin_right = kwargs['margin_top'] - margin_top = kwargs['margin_left'] - margin_bottom = kwargs['margin_right'] + margin_left = kwargs["margin_bottom"] + margin_right = kwargs["margin_top"] + margin_top = kwargs["margin_left"] + margin_bottom = kwargs["margin_right"] width = 700 - - - height = margin_top + margin_bottom + barcode_height + int(product_font_size * 1.3) - 30 + + height = ( + margin_top + + margin_bottom + + barcode_height + + int(product_font_size * 1.3) + - 30 + ) if duedate: height += int(duedate_font_size * 1.3) - - im = Image.new('RGB', (width, height), 'white') + + im = Image.new("RGB", (width, height), "white") draw = ImageDraw.Draw(im) - barcode = Image.open('/tmp/dmtx.png').resize((width-margin_left-margin_right,barcode_height)) + barcode = Image.open("/tmp/dmtx.png").resize( + (width - margin_left - margin_right, barcode_height) + ) vertical_offset = margin_top horizontal_offset = margin_left textoffset = horizontal_offset, vertical_offset - draw.text(textoffset, product, kwargs['fill_color'], font=product_font) + draw.text(textoffset, product, kwargs["fill_color"], font=product_font) vertical_offset += product_font_size - im.paste(barcode, (horizontal_offset, vertical_offset, width - margin_right, vertical_offset + barcode_height)) - + im.paste( + barcode, + ( + horizontal_offset, + vertical_offset, + width - margin_right, + vertical_offset + barcode_height, + ), + ) + if duedate is not None: vertical_offset += barcode_height horizontal_offset = margin_left textoffset = horizontal_offset, vertical_offset - draw.text(textoffset, duedate, - kwargs['fill_color'], font=duedate_font) - - if kwargs['orientation'] == "rotated": + draw.text(textoffset, duedate, kwargs["fill_color"], font=duedate_font) + + if kwargs["orientation"] == "rotated": im = im.transpose(Image.ROTATE_90) return im except Exception as e: exc_type, exc_obj, exc_tb = sys.exc_info() - logger.error('Exception happened: %s, Line: %s', - exc_type, exc_tb.tb_lineno) + logger.error("Exception happened: %s, Line: %s", exc_type, exc_tb.tb_lineno) return -def create_label_grocy(text, **kwargs): - product = kwargs['product'] - duedate = kwargs['duedate'] - grocycode = kwargs['grocycode'] +def create_label_grocy(text, **kwargs): + product = kwargs["product"] + duedate = kwargs["duedate"] + grocycode = kwargs["grocycode"] # prepare grocycode datamatrix from pylibdmtx.pylibdmtx import encode - encoded = encode(grocycode.encode('utf8'), size="SquareAuto") # adjusted for 300x300 dpi - results in DM code roughly 5x5mm - datamatrix = Image.frombytes('RGB', (encoded.width, encoded.height), encoded.pixels) - datamatrix.save('/tmp/dmtx.png') - product_font = ImageFont.truetype(kwargs['font_path'], 100) - duedate_font = ImageFont.truetype(kwargs['font_path'], 60) - width = kwargs['width'] + encoded = encode( + grocycode.encode("utf8"), size="SquareAuto" + ) # adjusted for 300x300 dpi - results in DM code roughly 5x5mm + datamatrix = Image.frombytes("RGB", (encoded.width, encoded.height), encoded.pixels) + datamatrix.save("/tmp/dmtx.png") + + product_font = ImageFont.truetype(kwargs["font_path"], 100) + duedate_font = ImageFont.truetype(kwargs["font_path"], 60) + width = kwargs["width"] height = 200 - if kwargs['orientation'] == 'rotated': + if kwargs["orientation"] == "rotated": tw = width width = height height = tw - im = Image.new('RGB', (width, height), 'white') + im = Image.new("RGB", (width, height), "white") draw = ImageDraw.Draw(im) - if kwargs['orientation'] == 'standard': - vertical_offset = kwargs['margin_top'] - horizontal_offset = kwargs['margin_left'] - elif kwargs['orientation'] == 'rotated': - vertical_offset = kwargs['margin_top'] - horizontal_offset = kwargs['margin_left'] + if kwargs["orientation"] == "standard": + vertical_offset = kwargs["margin_top"] + horizontal_offset = kwargs["margin_left"] + elif kwargs["orientation"] == "rotated": + vertical_offset = kwargs["margin_top"] + horizontal_offset = kwargs["margin_left"] datamatrix.transpose(Image.ROTATE_270) - im.paste(datamatrix, (horizontal_offset, vertical_offset, horizontal_offset + encoded.width, vertical_offset + encoded.height)) - - if kwargs['orientation'] == 'standard': + im.paste( + datamatrix, + ( + horizontal_offset, + vertical_offset, + horizontal_offset + encoded.width, + vertical_offset + encoded.height, + ), + ) + + if kwargs["orientation"] == "standard": vertical_offset += -10 horizontal_offset = encoded.width + 40 - elif kwargs['orientation'] == 'rotated': + elif kwargs["orientation"] == "rotated": vertical_offset += encoded.width + 40 horizontal_offset += -10 textoffset = horizontal_offset, vertical_offset - draw.text(textoffset, product, kwargs['fill_color'], font=product_font) + draw.text(textoffset, product, kwargs["fill_color"], font=product_font) if duedate is not None: - if kwargs['orientation'] == 'standard': + if kwargs["orientation"] == "standard": vertical_offset += 110 - horizontal_offset = kwargs['margin_left'] - elif kwargs['orientation'] == 'rotated': - vertical_offset = kwargs['margin_left'] + horizontal_offset = kwargs["margin_left"] + elif kwargs["orientation"] == "rotated": + vertical_offset = kwargs["margin_left"] horizontal_offset += 110 textoffset = horizontal_offset, vertical_offset - draw.text(textoffset, duedate, kwargs['fill_color'], font=duedate_font) + draw.text(textoffset, duedate, kwargs["fill_color"], font=duedate_font) return im -@get('/api/preview/text') -@post('/api/preview/text') + +@get("/api/preview/text") +@post("/api/preview/text") def get_preview_image(): context = get_label_context(request) im = create_label_im(**context) - return_format = request.query.get('return_format', 'png') - if return_format == 'base64': + return_format = request.query.get("return_format", "png") + if return_format == "base64": import base64 - response.set_header('Content-type', 'text/plain') + + response.set_header("Content-type", "text/plain") return base64.b64encode(image_to_png_bytes(im)) else: - response.set_header('Content-type', 'image/png') + response.set_header("Content-type", "image/png") return image_to_png_bytes(im) + def image_to_png_bytes(im): image_buffer = BytesIO() im.save(image_buffer, format="PNG") image_buffer.seek(0) return image_buffer.read() -@post('/api/print/grocy') -@get('/api/print/grocy') + +@post("/api/print/grocy") +@get("/api/print/grocy") def print_grocy(): """ API endpoint to consume the grocy label webhook. @@ -305,58 +356,70 @@ def print_grocy(): returns; JSON """ - return_dict = {'success' : False } + return_dict = {"success": False} try: context = get_label_context(request) except LookupError as e: - return_dict['error'] = e.msg + return_dict["error"] = e.msg return return_dict - if context['product'] is None: - return_dict['error'] = 'Please provide the product for the label' + if context["product"] is None: + return_dict["error"] = "Please provide the product for the label" return return_dict - + import os + code_1d = False - if os.environ.get('Code128') and os.environ.get('Code128') == "1": - code_1d = True - + if os.environ.get("Code128") and os.environ.get("Code128") == "1": + code_1d = True + im = None if code_1d: im = create_label_grocy_1d(**context) else: im = create_label_grocy(**context) - if DEBUG: im.save('sample-out.png') + if DEBUG: + im.save("sample-out.png") - if context['kind'] == ENDLESS_LABEL: - rotate = 0 if context['orientation'] == 'standard' else 90 - elif context['kind'] in (ROUND_DIE_CUT_LABEL, DIE_CUT_LABEL): - rotate = 'auto' + if context["kind"] == ENDLESS_LABEL: + rotate = 0 if context["orientation"] == "standard" else 90 + elif context["kind"] in (ROUND_DIE_CUT_LABEL, DIE_CUT_LABEL): + rotate = "auto" - qlr = BrotherQLRaster(CONFIG['PRINTER']['MODEL']) + qlr = BrotherQLRaster(CONFIG["PRINTER"]["MODEL"]) red = False - if 'red' in context['label_size']: + if "red" in context["label_size"]: red = True - create_label(qlr, im, context['label_size'], red=red, threshold=context['threshold'], cut=True, rotate=rotate) + create_label( + qlr, + im, + context["label_size"], + red=red, + threshold=context["threshold"], + cut=True, + rotate=rotate, + ) if not DEBUG: try: - be = BACKEND_CLASS(CONFIG['PRINTER']['PRINTER']) + be = BACKEND_CLASS(CONFIG["PRINTER"]["PRINTER"]) be.write(qlr.data) be.dispose() del be except Exception as e: - return_dict['message'] = str(e) - logger.warning('Exception happened: %s', e) + return_dict["message"] = str(e) + logger.warning("Exception happened: %s", e) return return_dict - return_dict['success'] = True - if DEBUG: return_dict['data'] = str(qlr.data) + return_dict["success"] = True + if DEBUG: + return_dict["data"] = str(qlr.data) return return_dict -@post('/api/print/text') -@get('/api/print/text') + +@post("/api/print/text") +@get("/api/print/text") def print_text(): """ API to print a label @@ -367,126 +430,174 @@ def print_text(): - alignment """ - return_dict = {'success': False} + return_dict = {"success": False} try: context = get_label_context(request) except LookupError as e: - return_dict['error'] = e.msg + return_dict["error"] = e.msg return return_dict - if context['text'] is None: - return_dict['error'] = 'Please provide the text for the label' + if context["text"] is None: + return_dict["error"] = "Please provide the text for the label" return return_dict im = create_label_im(**context) - if DEBUG: im.save('sample-out.png') + if DEBUG: + im.save("sample-out.png") - if context['kind'] == ENDLESS_LABEL: - rotate = 0 if context['orientation'] == 'standard' else 90 - elif context['kind'] in (ROUND_DIE_CUT_LABEL, DIE_CUT_LABEL): - rotate = 'auto' + if context["kind"] == ENDLESS_LABEL: + rotate = 0 if context["orientation"] == "standard" else 90 + elif context["kind"] in (ROUND_DIE_CUT_LABEL, DIE_CUT_LABEL): + rotate = "auto" - qlr = BrotherQLRaster(CONFIG['PRINTER']['MODEL']) + qlr = BrotherQLRaster(CONFIG["PRINTER"]["MODEL"]) red = False - if 'red' in context['label_size']: + if "red" in context["label_size"]: red = True - create_label(qlr, im, context['label_size'], red=red, threshold=context['threshold'], cut=True, rotate=rotate) + create_label( + qlr, + im, + context["label_size"], + red=red, + threshold=context["threshold"], + cut=True, + rotate=rotate, + ) if not DEBUG: try: - be = BACKEND_CLASS(CONFIG['PRINTER']['PRINTER']) + be = BACKEND_CLASS(CONFIG["PRINTER"]["PRINTER"]) be.write(qlr.data) be.dispose() del be except Exception as e: - return_dict['message'] = str(e) - logger.warning('Exception happened: %s', e) + return_dict["message"] = str(e) + logger.warning("Exception happened: %s", e) return return_dict - return_dict['success'] = True - if DEBUG: return_dict['data'] = str(qlr.data) + return_dict["success"] = True + if DEBUG: + return_dict["data"] = str(qlr.data) return return_dict + def main(): global DEBUG, FONTS, BACKEND_CLASS, CONFIG parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument('--port', default=False) - parser.add_argument('--loglevel', type=lambda x: getattr(logging, x.upper()), default=False) - parser.add_argument('--font-folder', default=False, help='folder for additional .ttf/.otf fonts') - parser.add_argument('--default-label-size', default=False, help='Label size inserted in your printer. Defaults to 62.') - parser.add_argument('--default-orientation', default=False, choices=('standard', 'rotated'), help='Label orientation, defaults to "standard". To turn your text by 90°, state "rotated".') - parser.add_argument('--model', default=False, choices=models, help='The model of your printer (default: QL-500)') - parser.add_argument('printer', nargs='?', default=False, help='String descriptor for the printer to use (like tcp://192.168.0.23:9100 or file:///dev/usb/lp0)') + parser.add_argument("--port", default=False) + parser.add_argument( + "--loglevel", type=lambda x: getattr(logging, x.upper()), default=False + ) + parser.add_argument( + "--font-folder", default=False, help="folder for additional .ttf/.otf fonts" + ) + parser.add_argument( + "--default-label-size", + default=False, + help="Label size inserted in your printer. Defaults to 62.", + ) + parser.add_argument( + "--default-orientation", + default=False, + choices=("standard", "rotated"), + help='Label orientation, defaults to "standard". To turn your text by 90°, state "rotated".', + ) + parser.add_argument( + "--model", + default=False, + choices=models, + help="The model of your printer (default: QL-500)", + ) + parser.add_argument( + "printer", + nargs="?", + default=False, + help="String descriptor for the printer to use (like tcp://192.168.0.23:9100 or file:///dev/usb/lp0)", + ) args = parser.parse_args() if args.printer: - CONFIG['PRINTER']['PRINTER'] = args.printer + CONFIG["PRINTER"]["PRINTER"] = args.printer if args.port: PORT = args.port else: - PORT = CONFIG['SERVER']['PORT'] + PORT = CONFIG["SERVER"]["PORT"] if args.loglevel: LOGLEVEL = args.loglevel else: - LOGLEVEL = CONFIG['SERVER']['LOGLEVEL'] + LOGLEVEL = CONFIG["SERVER"]["LOGLEVEL"] - if LOGLEVEL == 'DEBUG': + if LOGLEVEL == "DEBUG": DEBUG = True else: DEBUG = False if args.model: - CONFIG['PRINTER']['MODEL'] = args.model + CONFIG["PRINTER"]["MODEL"] = args.model if args.default_label_size: - CONFIG['LABEL']['DEFAULT_SIZE'] = args.default_label_size + CONFIG["LABEL"]["DEFAULT_SIZE"] = args.default_label_size if args.default_orientation: - CONFIG['LABEL']['DEFAULT_ORIENTATION'] = args.default_orientation + CONFIG["LABEL"]["DEFAULT_ORIENTATION"] = args.default_orientation if args.font_folder: ADDITIONAL_FONT_FOLDER = args.font_folder else: - ADDITIONAL_FONT_FOLDER = CONFIG['SERVER']['ADDITIONAL_FONT_FOLDER'] - + ADDITIONAL_FONT_FOLDER = CONFIG["SERVER"]["ADDITIONAL_FONT_FOLDER"] logging.basicConfig(level=LOGLEVEL) try: - selected_backend = guess_backend(CONFIG['PRINTER']['PRINTER']) + selected_backend = guess_backend(CONFIG["PRINTER"]["PRINTER"]) except ValueError: - parser.error("Couln't guess the backend to use from the printer string descriptor") - BACKEND_CLASS = backend_factory(selected_backend)['backend_class'] + parser.error( + "Couln't guess the backend to use from the printer string descriptor" + ) + BACKEND_CLASS = backend_factory(selected_backend)["backend_class"] - if CONFIG['LABEL']['DEFAULT_SIZE'] not in label_sizes: - parser.error("Invalid --default-label-size. Please choose on of the following:\n:" + " ".join(label_sizes)) + if CONFIG["LABEL"]["DEFAULT_SIZE"] not in label_sizes: + parser.error( + "Invalid --default-label-size. Please choose on of the following:\n:" + + " ".join(label_sizes) + ) FONTS = get_fonts() if ADDITIONAL_FONT_FOLDER: FONTS.update(get_fonts(ADDITIONAL_FONT_FOLDER)) if not FONTS: - sys.stderr.write("Not a single font was found on your system. Please install some or use the \"--font-folder\" argument.\n") + sys.stderr.write( + 'Not a single font was found on your system. Please install some or use the "--font-folder" argument.\n' + ) sys.exit(2) - for font in CONFIG['LABEL']['DEFAULT_FONTS']: + for font in CONFIG["LABEL"]["DEFAULT_FONTS"]: try: - FONTS[font['family']][font['style']] - CONFIG['LABEL']['DEFAULT_FONTS'] = font + FONTS[font["family"]][font["style"]] + CONFIG["LABEL"]["DEFAULT_FONTS"] = font logger.debug("Selected the following default font: {}".format(font)) break - except: pass - if CONFIG['LABEL']['DEFAULT_FONTS'] is None: - sys.stderr.write('Could not find any of the default fonts. Choosing a random one.\n') - family = random.choice(list(FONTS.keys())) - style = random.choice(list(FONTS[family].keys())) - CONFIG['LABEL']['DEFAULT_FONTS'] = {'family': family, 'style': style} - sys.stderr.write('The default font is now set to: {family} ({style})\n'.format(**CONFIG['LABEL']['DEFAULT_FONTS'])) + except: + pass + if CONFIG["LABEL"]["DEFAULT_FONTS"] is None: + sys.stderr.write( + "Could not find any of the default fonts. Choosing a random one.\n" + ) + family = random.choice(list(FONTS.keys())) + style = random.choice(list(FONTS[family].keys())) + CONFIG["LABEL"]["DEFAULT_FONTS"] = {"family": family, "style": style} + sys.stderr.write( + "The default font is now set to: {family} ({style})\n".format( + **CONFIG["LABEL"]["DEFAULT_FONTS"] + ) + ) + + run(host=CONFIG["SERVER"]["HOST"], port=PORT, debug=DEBUG) - run(host=CONFIG['SERVER']['HOST'], port=PORT, debug=DEBUG) if __name__ == "__main__": - main() \ No newline at end of file + main() From bc7726beb8b8569640cd50c7457922eab00b72de Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Heuer?= Date: Tue, 15 Feb 2022 21:36:30 +0100 Subject: [PATCH 5/6] Added support for chores and battery labels --- brother_ql_web.py | 45 +++++++++++++++++++++++++++++++-------------- 1 file changed, 31 insertions(+), 14 deletions(-) diff --git a/brother_ql_web.py b/brother_ql_web.py index e245c41..0356a94 100755 --- a/brother_ql_web.py +++ b/brother_ql_web.py @@ -88,6 +88,8 @@ def get_label_context(request): "grocycode": d.get("grocycode", None), "product": d.get("product", None), "duedate": d.get("duedate", None), + "battery": d.get("battery", None), + "chore": d.get("chore", None), } context["margin_top"] = int(context["font_size"] * context["margin_top"]) context["margin_bottom"] = int(context["font_size"] * context["margin_bottom"]) @@ -173,10 +175,20 @@ def create_label_im(text, **kwargs): def create_label_grocy_1d(text, **kwargs): try: product = kwargs["product"] + chore = kwargs["chore"] + battery = kwargs["battery"] duedate = kwargs["duedate"] grocycode = kwargs["grocycode"] - product_font_size = 80 + text = None + if product: + text = product + elif chore: + text = chore + else: + text = battery + + text_font_size = 80 duedate_font_size = 40 barcode_height = 120 @@ -188,7 +200,7 @@ def create_label_grocy_1d(text, **kwargs): "/tmp/dmtx", {"module_height": 5.0, "quiet_zone": 0.5, "write_text": False} ) - product_font = ImageFont.truetype(kwargs["font_path"], product_font_size) + text_font = ImageFont.truetype(kwargs["font_path"], text_font_size) duedate_font = ImageFont.truetype(kwargs["font_path"], duedate_font_size) width = kwargs["width"] @@ -206,11 +218,7 @@ def create_label_grocy_1d(text, **kwargs): width = 700 height = ( - margin_top - + margin_bottom - + barcode_height - + int(product_font_size * 1.3) - - 30 + margin_top + margin_bottom + barcode_height + int(text_font_size * 1.3) - 30 ) if duedate: height += int(duedate_font_size * 1.3) @@ -226,9 +234,9 @@ def create_label_grocy_1d(text, **kwargs): horizontal_offset = margin_left textoffset = horizontal_offset, vertical_offset - draw.text(textoffset, product, kwargs["fill_color"], font=product_font) + draw.text(textoffset, text, kwargs["fill_color"], font=text_font) - vertical_offset += product_font_size + vertical_offset += text_font_size im.paste( barcode, @@ -258,9 +266,19 @@ def create_label_grocy_1d(text, **kwargs): def create_label_grocy(text, **kwargs): product = kwargs["product"] + chore = kwargs["chore"] + battery = kwargs["battery"] duedate = kwargs["duedate"] grocycode = kwargs["grocycode"] + text = None + if product: + text = product + elif chore: + text = chore + else: + text = battery + # prepare grocycode datamatrix from pylibdmtx.pylibdmtx import encode @@ -270,7 +288,7 @@ def create_label_grocy(text, **kwargs): datamatrix = Image.frombytes("RGB", (encoded.width, encoded.height), encoded.pixels) datamatrix.save("/tmp/dmtx.png") - product_font = ImageFont.truetype(kwargs["font_path"], 100) + text_font = ImageFont.truetype(kwargs["font_path"], 100) duedate_font = ImageFont.truetype(kwargs["font_path"], 60) width = kwargs["width"] height = 200 @@ -308,7 +326,7 @@ def create_label_grocy(text, **kwargs): textoffset = horizontal_offset, vertical_offset - draw.text(textoffset, product, kwargs["fill_color"], font=product_font) + draw.text(textoffset, text, kwargs["fill_color"], font=text_font) if duedate is not None: if kwargs["orientation"] == "standard": @@ -364,8 +382,8 @@ def print_grocy(): return_dict["error"] = e.msg return return_dict - if context["product"] is None: - return_dict["error"] = "Please provide the product for the label" + if context["product"] is None and context["battery"] and context["chore"]: + return_dict["error"] = "Please provide the product/battery/chore for the label" return return_dict import os @@ -595,7 +613,6 @@ def main(): **CONFIG["LABEL"]["DEFAULT_FONTS"] ) ) - run(host=CONFIG["SERVER"]["HOST"], port=PORT, debug=DEBUG) From 2be273343ee769253f93f33257e32007a0a53cb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andr=C3=A9=20Heuer?= Date: Tue, 15 Feb 2022 21:48:48 +0100 Subject: [PATCH 6/6] Fix debug log level for bottle --- brother_ql_web.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/brother_ql_web.py b/brother_ql_web.py index 0356a94..33b5535 100755 --- a/brother_ql_web.py +++ b/brother_ql_web.py @@ -548,7 +548,7 @@ def main(): else: LOGLEVEL = CONFIG["SERVER"]["LOGLEVEL"] - if LOGLEVEL == "DEBUG": + if LOGLEVEL == 10: DEBUG = True else: DEBUG = False