# Copyright (C) 2018 Florent de Labarre () # License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl). import base64 import binascii import io import logging import re from PIL import Image, ImageOps from odoo import _, fields, models from ..models import zpl2 _logger = logging.getLogger(__name__) def _compute_arg(data, arg): vals = {} for i, d in enumerate(data.split(",")): vals[arg[i]] = d return vals def _field_origin(data): if data[:2] == "FO": position = data[2:] vals = _compute_arg(position, ["origin_x", "origin_y"]) return vals return {} def _font_format(data): if data[:1] == "A": data = data.split(",") vals = {} if len(data[0]) > 1: vals[zpl2.ARG_FONT] = data[0][1] if len(data[0]) > 2: vals[zpl2.ARG_ORIENTATION] = data[0][2] if len(data) > 1: vals[zpl2.ARG_HEIGHT] = data[1] if len(data) > 2: vals[zpl2.ARG_WIDTH] = data[2] return vals return {} def _default_font_format(data): if data[:2] == "CF": args = [zpl2.ARG_FONT, zpl2.ARG_HEIGHT, zpl2.ARG_WIDTH] vals = _compute_arg(data[2:], args) if vals.get(zpl2.ARG_HEIGHT, False) and not vals.get(zpl2.ARG_WIDTH, False): vals.update({zpl2.ARG_WIDTH: vals.get(zpl2.ARG_HEIGHT)}) else: vals.update({zpl2.ARG_HEIGHT: 10, zpl2.ARG_HEIGHT: 10}) return vals return {} def _field_block(data): if data[:2] == "FB": vals = {zpl2.ARG_IN_BLOCK: True} args = [ zpl2.ARG_BLOCK_WIDTH, zpl2.ARG_BLOCK_LINES, zpl2.ARG_BLOCK_SPACES, zpl2.ARG_BLOCK_JUSTIFY, zpl2.ARG_BLOCK_LEFT_MARGIN, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _code11(data): if data[:2] == "B1": vals = {"component_type": zpl2.BARCODE_CODE_11} args = [ zpl2.ARG_ORIENTATION, zpl2.ARG_CHECK_DIGITS, zpl2.ARG_HEIGHT, zpl2.ARG_INTERPRETATION_LINE, zpl2.ARG_INTERPRETATION_LINE_ABOVE, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _interleaved2of5(data): if data[:2] == "B2": vals = {"component_type": zpl2.BARCODE_INTERLEAVED_2_OF_5} args = [ zpl2.ARG_ORIENTATION, zpl2.ARG_HEIGHT, zpl2.ARG_INTERPRETATION_LINE, zpl2.ARG_INTERPRETATION_LINE_ABOVE, zpl2.ARG_CHECK_DIGITS, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _code39(data): if data[:2] == "B3": vals = {"component_type": zpl2.BARCODE_CODE_39} args = [ zpl2.ARG_ORIENTATION, zpl2.ARG_CHECK_DIGITS, zpl2.ARG_HEIGHT, zpl2.ARG_INTERPRETATION_LINE, zpl2.ARG_INTERPRETATION_LINE_ABOVE, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _code49(data): if data[:2] == "B4": vals = {"component_type": zpl2.BARCODE_CODE_49} args = [ zpl2.ARG_ORIENTATION, zpl2.ARG_HEIGHT, zpl2.ARG_INTERPRETATION_LINE, zpl2.ARG_STARTING_MODE, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _pdf417(data): if data[:2] == "B7": vals = {"component_type": zpl2.BARCODE_PDF417} args = [ zpl2.ARG_ORIENTATION, zpl2.ARG_HEIGHT, zpl2.ARG_SECURITY_LEVEL, zpl2.ARG_COLUMNS_COUNT, zpl2.ARG_ROWS_COUNT, zpl2.ARG_TRUNCATE, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _ean8(data): if data[:2] == "B8": vals = {"component_type": zpl2.BARCODE_EAN_8} args = [ zpl2.ARG_ORIENTATION, zpl2.ARG_HEIGHT, zpl2.ARG_INTERPRETATION_LINE, zpl2.ARG_INTERPRETATION_LINE_ABOVE, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _upce(data): if data[:2] == "B9": vals = {"component_type": zpl2.BARCODE_UPC_E} args = [ zpl2.ARG_ORIENTATION, zpl2.ARG_HEIGHT, zpl2.ARG_INTERPRETATION_LINE, zpl2.ARG_INTERPRETATION_LINE_ABOVE, zpl2.ARG_CHECK_DIGITS, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _code128(data): if data[:2] == "BC": vals = {"component_type": zpl2.BARCODE_CODE_128} args = [ zpl2.ARG_ORIENTATION, zpl2.ARG_HEIGHT, zpl2.ARG_INTERPRETATION_LINE, zpl2.ARG_INTERPRETATION_LINE_ABOVE, zpl2.ARG_CHECK_DIGITS, zpl2.ARG_MODE, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _ean13(data): if data[:2] == "BE": vals = {"component_type": zpl2.BARCODE_EAN_13} args = [ zpl2.ARG_ORIENTATION, zpl2.ARG_HEIGHT, zpl2.ARG_INTERPRETATION_LINE, zpl2.ARG_INTERPRETATION_LINE_ABOVE, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _qrcode(data): if data[:2] == "BQ": vals = {"component_type": zpl2.BARCODE_QR_CODE} args = [ zpl2.ARG_ORIENTATION, zpl2.ARG_MODEL, zpl2.ARG_MAGNIFICATION_FACTOR, zpl2.ARG_ERROR_CORRECTION, zpl2.ARG_MASK_VALUE, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _default_barcode_field(data): if data[:2] == "BY": args = [zpl2.ARG_MODULE_WIDTH, zpl2.ARG_BAR_WIDTH_RATIO, zpl2.ARG_HEIGHT] return _compute_arg(data[2:], args) return {} def _field_reverse_print(data): if data[:2] == "FR": return {zpl2.ARG_REVERSE_PRINT: True} return {} def _graphic_box(data): if data[:2] == "GB": vals = {"component_type": "rectangle"} args = [ zpl2.ARG_WIDTH, zpl2.ARG_HEIGHT, zpl2.ARG_THICKNESS, zpl2.ARG_COLOR, zpl2.ARG_ROUNDING, ] vals.update(_compute_arg(data[2:], args)) return vals return {} def _graphic_circle(data): if data[:2] == "GC": vals = {"component_type": "circle"} args = [zpl2.ARG_WIDTH, zpl2.ARG_THICKNESS, zpl2.ARG_COLOR] vals.update(_compute_arg(data[2:], args)) return vals return {} def _graphic_field(data): if data[:3] == "GFA": vals = {} args = [ "compression", "total_bytes", "total_bytes", "bytes_per_row", "ascii_data", ] vals.update(_compute_arg(data[3:], args)) # Image rawData = re.sub("[^A-F0-9]+", "", vals["ascii_data"]) rawData = binascii.unhexlify(rawData) width = int(float(vals["bytes_per_row"]) * 8) height = int(float(vals["total_bytes"]) / width) * 8 img = Image.frombytes("1", (width, height), rawData, "raw").convert("L") img = ImageOps.invert(img) imgByteArr = io.BytesIO() img.save(imgByteArr, format="PNG") image = base64.b64encode(imgByteArr.getvalue()) return { "component_type": "graphic", "graphic_image": image, zpl2.ARG_WIDTH: width, zpl2.ARG_HEIGHT: height, } return {} def _get_data(data): if data[:2] == "FD": return {"data": '"%s"' % data[2:]} return {} SUPPORTED_CODE = { "FO": {"method": _field_origin}, "FD": {"method": _get_data}, "A": {"method": _font_format}, "FB": {"method": _field_block}, "B1": {"method": _code11}, "B2": {"method": _interleaved2of5}, "B3": {"method": _code39}, "B4": {"method": _code49}, "B7": {"method": _pdf417}, "B8": {"method": _ean8}, "B9": {"method": _upce}, "BC": {"method": _code128}, "BE": {"method": _ean13}, "BQ": {"method": _qrcode}, "BY": { "method": _default_barcode_field, "default": [ zpl2.BARCODE_CODE_11, zpl2.BARCODE_INTERLEAVED_2_OF_5, zpl2.BARCODE_CODE_39, zpl2.BARCODE_CODE_49, zpl2.BARCODE_PDF417, zpl2.BARCODE_EAN_8, zpl2.BARCODE_UPC_E, zpl2.BARCODE_CODE_128, zpl2.BARCODE_EAN_13, zpl2.BARCODE_QR_CODE, ], }, "CF": {"method": _default_font_format, "default": ["text"]}, "FR": {"method": _field_reverse_print}, "GB": {"method": _graphic_box}, "GC": {"method": _graphic_circle}, "GFA": {"method": _graphic_field}, } class WizardImportZPl2(models.TransientModel): _name = "wizard.import.zpl2" _description = "Import ZPL2" label_id = fields.Many2one( comodel_name="printing.label.zpl2", string="Label", required=True, readonly=True ) data = fields.Text(required=True, help="Printer used to print the labels.") delete_component = fields.Boolean( string="Delete existing components", default=False ) def _start_sequence(self): sequences = self.mapped("label_id.component_ids.sequence") if sequences: return max(sequences) + 1 return 0 def import_zpl2(self): self.ensure_one() Zpl2Component = self.env["printing.label.zpl2.component"] if self.delete_component: self.mapped("label_id.component_ids").unlink() sequence = self._start_sequence() default = {} for i, line in enumerate(self.data.split("\n")): vals = {} args = line.split("^") for arg in args: for _key, code in SUPPORTED_CODE.items(): component_arg = code["method"](arg) if component_arg: if code.get("default", False): for deft in code.get("default"): default.update({deft: component_arg}) else: vals.update(component_arg) break if vals: if "component_type" not in vals.keys(): vals.update({"component_type": "text"}) if vals["component_type"] in default.keys(): vals.update(default[vals["component_type"]]) vals = self._update_vals(vals) seq = sequence + i * 10 vals.update( { "name": _("Import %s") % seq, "sequence": seq, "model": str(zpl2.MODEL_ENHANCED), "label_id": self.label_id.id, } ) Zpl2Component.create(vals) def _update_vals(self, vals): if "orientation" in vals.keys() and vals["orientation"] == "": vals["orientation"] = "N" # Field Zpl2Component = self.env["printing.label.zpl2.component"] model_fields = Zpl2Component.fields_get() component = {} for field, value in vals.items(): if field in model_fields.keys(): field_type = model_fields[field].get("type", False) if field_type == "boolean": if not value or value == zpl2.BOOL_NO: value = False else: value = True if field_type in ("integer", "float"): value = float(value) if field == "model": value = int(float(value)) component.update({field: value}) return component