mirror of
https://github.com/OCA/report-print-send.git
synced 2025-02-16 07:11:31 +02:00
428 lines
12 KiB
Python
428 lines
12 KiB
Python
# Copyright (C) 2018 Florent de Labarre (<https://github.com/fmdl>)
|
|
# 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})
|
|
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
|