Compare commits
10 Commits
0a3af68eaa
...
56cf4394ad
| Author | SHA1 | Date | |
|---|---|---|---|
| 56cf4394ad | |||
| 638b365d45 | |||
| 05516a7692 | |||
| b551b1fc94 | |||
| 5c2b72b18b | |||
| 2eeac7a4b6 | |||
| 99c35993c8 | |||
| ca4ac0544c | |||
| 1cfc7e7302 | |||
| df31020d4d |
@@ -1,4 +1,6 @@
|
|||||||
|
|
||||||
|
from builtins import bytes
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ Install via `pip install pyusb`
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from builtins import str
|
from builtins import str, bytes
|
||||||
|
|
||||||
import time
|
import time
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,8 @@ from PIL import Image
|
|||||||
import PIL.ImageOps, PIL.ImageChops
|
import PIL.ImageOps, PIL.ImageChops
|
||||||
|
|
||||||
from brother_ql.raster import BrotherQLRaster
|
from brother_ql.raster import BrotherQLRaster
|
||||||
from brother_ql.devicedependent import label_type_specs, ENDLESS_LABEL, DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL, right_margin_addition
|
from brother_ql.devicedependent import ENDLESS_LABEL, DIE_CUT_LABEL, ROUND_DIE_CUT_LABEL, PTOUCH_ENDLESS_LABEL
|
||||||
|
from brother_ql.devicedependent import label_type_specs, right_margin_addition
|
||||||
from brother_ql import BrotherQLUnsupportedCmd
|
from brother_ql import BrotherQLUnsupportedCmd
|
||||||
from brother_ql.image_trafos import filtered_hsv
|
from brother_ql.image_trafos import filtered_hsv
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ def convert(qlr, images, label, **kwargs):
|
|||||||
else:
|
else:
|
||||||
dots_expected = dots_printable
|
dots_expected = dots_printable
|
||||||
|
|
||||||
if label_specs['kind'] == ENDLESS_LABEL:
|
if label_specs['kind'] in (ENDLESS_LABEL, PTOUCH_ENDLESS_LABEL):
|
||||||
if rotate not in ('auto', 0):
|
if rotate not in ('auto', 0):
|
||||||
im = im.rotate(rotate, expand=True)
|
im = im.rotate(rotate, expand=True)
|
||||||
if dpi_600:
|
if dpi_600:
|
||||||
@@ -161,10 +162,14 @@ def convert(qlr, images, label, **kwargs):
|
|||||||
qlr.mtype = 0x0B
|
qlr.mtype = 0x0B
|
||||||
qlr.mwidth = tape_size[0]
|
qlr.mwidth = tape_size[0]
|
||||||
qlr.mlength = tape_size[1]
|
qlr.mlength = tape_size[1]
|
||||||
else:
|
elif label_specs['kind'] in (ENDLESS_LABEL, ):
|
||||||
qlr.mtype = 0x0A
|
qlr.mtype = 0x0A
|
||||||
qlr.mwidth = tape_size[0]
|
qlr.mwidth = tape_size[0]
|
||||||
qlr.mlength = 0
|
qlr.mlength = 0
|
||||||
|
elif label_specs['kind'] in (PTOUCH_ENDLESS_LABEL, ):
|
||||||
|
qlr.mtype = 0x00
|
||||||
|
qlr.mwidth = tape_size[0]
|
||||||
|
qlr.mlength = 0
|
||||||
qlr.pquality = int(hq)
|
qlr.pquality = int(hq)
|
||||||
qlr.add_media_and_quality(im.size[1])
|
qlr.add_media_and_quality(im.size[1])
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -16,13 +16,12 @@ import logging
|
|||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
logger.warn("deprecation warning: brother_ql.devicedependent is deprecated and will be removed in a future release")
|
|
||||||
|
|
||||||
## These module level variables were available here before.
|
## These module level variables were available here before.
|
||||||
# Concerning labels
|
# Concerning labels
|
||||||
DIE_CUT_LABEL = None
|
DIE_CUT_LABEL = None
|
||||||
ENDLESS_LABEL = None
|
ENDLESS_LABEL = None
|
||||||
ROUND_DIE_CUT_LABEL = None
|
ROUND_DIE_CUT_LABEL = None
|
||||||
|
PTOUCH_ENDLESS_LABEL = None
|
||||||
label_type_specs = {}
|
label_type_specs = {}
|
||||||
label_sizes = []
|
label_sizes = []
|
||||||
# And concerning printer models
|
# And concerning printer models
|
||||||
@@ -63,13 +62,14 @@ def _populate_label_legacy_structures():
|
|||||||
We contain this code inside a function so that the imports
|
We contain this code inside a function so that the imports
|
||||||
we do in here are not visible at the module level.
|
we do in here are not visible at the module level.
|
||||||
"""
|
"""
|
||||||
global DIE_CUT_LABEL, ENDLESS_LABEL, ROUND_DIE_CUT_LABEL
|
global DIE_CUT_LABEL, ENDLESS_LABEL, ROUND_DIE_CUT_LABEL, PTOUCH_ENDLESS_LABEL
|
||||||
global label_sizes, label_type_specs
|
global label_sizes, label_type_specs
|
||||||
|
|
||||||
from brother_ql.labels import FormFactor
|
from brother_ql.labels import FormFactor
|
||||||
DIE_CUT_LABEL = FormFactor.DIE_CUT
|
DIE_CUT_LABEL = FormFactor.DIE_CUT
|
||||||
ENDLESS_LABEL = FormFactor.ENDLESS
|
ENDLESS_LABEL = FormFactor.ENDLESS
|
||||||
ROUND_DIE_CUT_LABEL = FormFactor.ROUND_DIE_CUT
|
ROUND_DIE_CUT_LABEL = FormFactor.ROUND_DIE_CUT
|
||||||
|
PTOUCH_ENDLESS_LABEL =FormFactor.PTOUCH_ENDLESS
|
||||||
|
|
||||||
from brother_ql.labels import LabelsManager
|
from brother_ql.labels import LabelsManager
|
||||||
lm = LabelsManager()
|
lm = LabelsManager()
|
||||||
|
|||||||
+20
-17
@@ -1,13 +1,13 @@
|
|||||||
|
|
||||||
from dataclasses import dataclass, field
|
from attr import attrs, attrib
|
||||||
from typing import List, Tuple
|
from typing import List, Tuple
|
||||||
from enum import Enum
|
from enum import IntEnum
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from brother_ql.helpers import ElementsManager
|
from brother_ql.helpers import ElementsManager
|
||||||
|
|
||||||
class FormFactor(Enum):
|
class FormFactor(IntEnum):
|
||||||
"""
|
"""
|
||||||
Enumeration representing the form factor of a label.
|
Enumeration representing the form factor of a label.
|
||||||
The labels for the Brother QL series are supplied either as die-cut (pre-sized), or for more flexibility the
|
The labels for the Brother QL series are supplied either as die-cut (pre-sized), or for more flexibility the
|
||||||
@@ -19,8 +19,10 @@ class FormFactor(Enum):
|
|||||||
ENDLESS = 2
|
ENDLESS = 2
|
||||||
#: round die-cut labels
|
#: round die-cut labels
|
||||||
ROUND_DIE_CUT = 3
|
ROUND_DIE_CUT = 3
|
||||||
|
#: endless P-touch labels
|
||||||
|
PTOUCH_ENDLESS = 4
|
||||||
|
|
||||||
class Color(Enum):
|
class Color(IntEnum):
|
||||||
"""
|
"""
|
||||||
Enumeration representing the colors to be printed on a label. Most labels only support printing black on white.
|
Enumeration representing the colors to be printed on a label. Most labels only support printing black on white.
|
||||||
Some newer ones can also print in black and red on white.
|
Some newer ones can also print in black and red on white.
|
||||||
@@ -30,35 +32,35 @@ class Color(Enum):
|
|||||||
#: The label can be printed in black, white & red.
|
#: The label can be printed in black, white & red.
|
||||||
BLACK_RED_WHITE = 1
|
BLACK_RED_WHITE = 1
|
||||||
|
|
||||||
@dataclass
|
@attrs
|
||||||
class Label:
|
class Label(object):
|
||||||
"""
|
"""
|
||||||
This class represents a label. All specifics of a certain label
|
This class represents a label. All specifics of a certain label
|
||||||
and what the rasterizer needs to take care of depending on the
|
and what the rasterizer needs to take care of depending on the
|
||||||
label choosen, should be contained in this class.
|
label choosen, should be contained in this class.
|
||||||
"""
|
"""
|
||||||
#: A string identifier given to each label that can be selected. Eg. '29'.
|
#: A string identifier given to each label that can be selected. Eg. '29'.
|
||||||
identifier: str
|
identifier = attrib(type=str)
|
||||||
#: The tape size of a single label (width, lenght) in mm. For endless labels, the length is 0 by definition.
|
#: The tape size of a single label (width, lenght) in mm. For endless labels, the length is 0 by definition.
|
||||||
tape_size: Tuple[int, int]
|
tape_size = attrib(type=Tuple[int, int])
|
||||||
#: The type of label
|
#: The type of label
|
||||||
form_factor: FormFactor
|
form_factor = attrib(type=FormFactor)
|
||||||
#: The total area (width, length) of the label in dots (@300dpi).
|
#: The total area (width, length) of the label in dots (@300dpi).
|
||||||
dots_total: Tuple[int, int]
|
dots_total = attrib(type=Tuple[int, int])
|
||||||
#: The printable area (width, length) of the label in dots (@300dpi).
|
#: The printable area (width, length) of the label in dots (@300dpi).
|
||||||
dots_printable: Tuple[int, int]
|
dots_printable = attrib(type=Tuple[int, int])
|
||||||
#: The required offset from the right side of the label in dots to obtain a centered printout.
|
#: The required offset from the right side of the label in dots to obtain a centered printout.
|
||||||
offset_r: int
|
offset_r = attrib(type=int)
|
||||||
#: An additional amount of feeding when printing the label.
|
#: An additional amount of feeding when printing the label.
|
||||||
#: This is non-zero for some smaller label sizes and for endless labels.
|
#: This is non-zero for some smaller label sizes and for endless labels.
|
||||||
feed_margin: int = 0
|
feed_margin = attrib(type=int, default=0)
|
||||||
#: If a label can only be printed with certain label printers, this member variable lists the allowed ones.
|
#: If a label can only be printed with certain label printers, this member variable lists the allowed ones.
|
||||||
#: Otherwise it's an empty list.
|
#: Otherwise it's an empty list.
|
||||||
restricted_to_models: List[str] = field(default_factory=list)
|
restricted_to_models = attrib(type=List[str], factory=list)
|
||||||
#: Some labels allow printing in red, most don't.
|
#: Some labels allow printing in red, most don't.
|
||||||
color: Color = Color.BLACK_WHITE
|
color = attrib(type=Color, default=Color.BLACK_WHITE)
|
||||||
|
|
||||||
def works_with_model(self, model) -> bool:
|
def works_with_model(self, model): # type: bool
|
||||||
"""
|
"""
|
||||||
Method to determine if certain label can be printed by the specified printer model.
|
Method to determine if certain label can be printed by the specified printer model.
|
||||||
"""
|
"""
|
||||||
@@ -66,7 +68,7 @@ class Label:
|
|||||||
else: return True
|
else: return True
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self): # type: str
|
||||||
out = ""
|
out = ""
|
||||||
if 'x' in self.identifier:
|
if 'x' in self.identifier:
|
||||||
out = '{0}mm x {1}mm die-cut'.format(*self.tape_size)
|
out = '{0}mm x {1}mm die-cut'.format(*self.tape_size)
|
||||||
@@ -102,6 +104,7 @@ ALL_LABELS = (
|
|||||||
Label("d12", ( 12, 12), FormFactor.ROUND_DIE_CUT, ( 142, 142), ( 94, 94), 113 , feed_margin=35),
|
Label("d12", ( 12, 12), FormFactor.ROUND_DIE_CUT, ( 142, 142), ( 94, 94), 113 , feed_margin=35),
|
||||||
Label("d24", ( 24, 24), FormFactor.ROUND_DIE_CUT, ( 284, 284), ( 236, 236), 42 ),
|
Label("d24", ( 24, 24), FormFactor.ROUND_DIE_CUT, ( 284, 284), ( 236, 236), 42 ),
|
||||||
Label("d58", ( 58, 58), FormFactor.ROUND_DIE_CUT, ( 688, 688), ( 618, 618), 51 ),
|
Label("d58", ( 58, 58), FormFactor.ROUND_DIE_CUT, ( 688, 688), ( 618, 618), 51 ),
|
||||||
|
Label("pt24", ( 24, 0), FormFactor.PTOUCH_ENDLESS,( 128, 0), ( 128, 0), 0, feed_margin=14),
|
||||||
)
|
)
|
||||||
|
|
||||||
class LabelsManager(ElementsManager):
|
class LabelsManager(ElementsManager):
|
||||||
|
|||||||
+16
-14
@@ -1,43 +1,43 @@
|
|||||||
from dataclasses import dataclass
|
from attr import attrs, attrib
|
||||||
from typing import Tuple
|
from typing import Tuple
|
||||||
|
|
||||||
import copy
|
import copy
|
||||||
|
|
||||||
from brother_ql.helpers import ElementsManager
|
from brother_ql.helpers import ElementsManager
|
||||||
|
|
||||||
@dataclass
|
@attrs
|
||||||
class Model:
|
class Model(object):
|
||||||
"""
|
"""
|
||||||
This class represents a printer model. All specifics of a certain model
|
This class represents a printer model. All specifics of a certain model
|
||||||
and the opcodes it supports should be contained in this class.
|
and the opcodes it supports should be contained in this class.
|
||||||
"""
|
"""
|
||||||
#: A string identifier given to each model implemented. Eg. 'QL-500'.
|
#: A string identifier given to each model implemented. Eg. 'QL-500'.
|
||||||
identifier: str
|
identifier = attrib(type=str)
|
||||||
#: Minimum and maximum number of rows or 'dots' that can be printed.
|
#: Minimum and maximum number of rows or 'dots' that can be printed.
|
||||||
#: Together with the dpi this gives the minimum and maximum length
|
#: Together with the dpi this gives the minimum and maximum length
|
||||||
#: for continuous tape printing.
|
#: for continuous tape printing.
|
||||||
min_max_length_dots: Tuple[int, int]
|
min_max_length_dots = attrib(type=Tuple[int, int])
|
||||||
#: The minimum and maximum amount of feeding a label
|
#: The minimum and maximum amount of feeding a label
|
||||||
min_max_feed: Tuple[int, int] = (35, 1500)
|
min_max_feed = attrib(type=Tuple[int, int], default=(35, 1500))
|
||||||
number_bytes_per_row: int = 90
|
number_bytes_per_row = attrib(type=int, default=90)
|
||||||
#: The required additional offset from the right side
|
#: The required additional offset from the right side
|
||||||
additional_offset_r: int = 0
|
additional_offset_r = attrib(type=int, default=0)
|
||||||
#: Support for the 'mode setting' opcode
|
#: Support for the 'mode setting' opcode
|
||||||
mode_setting: bool = True
|
mode_setting = attrib(type=bool, default=True)
|
||||||
#: Model has a cutting blade to automatically cut labels
|
#: Model has a cutting blade to automatically cut labels
|
||||||
cutting: bool = True
|
cutting = attrib(type=bool, default=True)
|
||||||
#: Model has support for the 'expanded mode' opcode.
|
#: Model has support for the 'expanded mode' opcode.
|
||||||
#: (So far, all models that have cutting support do).
|
#: (So far, all models that have cutting support do).
|
||||||
expanded_mode: bool = True
|
expanded_mode = attrib(type=bool, default=True)
|
||||||
#: Model has support for compressing the transmitted raster data.
|
#: Model has support for compressing the transmitted raster data.
|
||||||
#: Some models with only USB connectivity don't support compression.
|
#: Some models with only USB connectivity don't support compression.
|
||||||
compression: bool = True
|
compression = attrib(type=bool, default=True)
|
||||||
#: Support for two color printing (black/red/white)
|
#: Support for two color printing (black/red/white)
|
||||||
#: available only on some newer models.
|
#: available only on some newer models.
|
||||||
two_color: bool = False
|
two_color = attrib(type=bool, default=False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self) -> str:
|
def name(self):
|
||||||
return self.identifier
|
return self.identifier
|
||||||
|
|
||||||
ALL_MODELS = [
|
ALL_MODELS = [
|
||||||
@@ -55,6 +55,8 @@ ALL_MODELS = [
|
|||||||
Model('QL-820NWB',(150, 11811), two_color=True),
|
Model('QL-820NWB',(150, 11811), two_color=True),
|
||||||
Model('QL-1050', (295, 35433), number_bytes_per_row=162, additional_offset_r=44),
|
Model('QL-1050', (295, 35433), number_bytes_per_row=162, additional_offset_r=44),
|
||||||
Model('QL-1060N', (295, 35433), number_bytes_per_row=162, additional_offset_r=44),
|
Model('QL-1060N', (295, 35433), number_bytes_per_row=162, additional_offset_r=44),
|
||||||
|
Model('PT-P750W', (31, 14172), number_bytes_per_row=16),
|
||||||
|
Model('PT-P900W', (57, 28346), number_bytes_per_row=70),
|
||||||
]
|
]
|
||||||
|
|
||||||
class ModelsManager(ElementsManager):
|
class ModelsManager(ElementsManager):
|
||||||
|
|||||||
+54
-12
@@ -1,3 +1,11 @@
|
|||||||
|
"""
|
||||||
|
This module contains the implementation of the raster language
|
||||||
|
of the Brother QL-series label printers according to their
|
||||||
|
documentation and to reverse engineering efforts.
|
||||||
|
|
||||||
|
The central piece of code in this module is the class
|
||||||
|
:py:class:`BrotherQLRaster`.
|
||||||
|
"""
|
||||||
|
|
||||||
from builtins import bytes
|
from builtins import bytes
|
||||||
|
|
||||||
@@ -12,7 +20,6 @@ from .devicedependent import models, \
|
|||||||
min_max_feed, \
|
min_max_feed, \
|
||||||
min_max_length_dots, \
|
min_max_length_dots, \
|
||||||
number_bytes_per_row, \
|
number_bytes_per_row, \
|
||||||
right_margin_addition, \
|
|
||||||
compressionsupport, \
|
compressionsupport, \
|
||||||
cuttingsupport, \
|
cuttingsupport, \
|
||||||
expandedmode, \
|
expandedmode, \
|
||||||
@@ -30,6 +37,21 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class BrotherQLRaster(object):
|
class BrotherQLRaster(object):
|
||||||
|
|
||||||
|
"""
|
||||||
|
This class facilitates the creation of a complete set
|
||||||
|
of raster instructions by adding them one after the other
|
||||||
|
using the methods of the class. Each method call is adding
|
||||||
|
instructions to the member variable :py:attr:`data`.
|
||||||
|
|
||||||
|
Instatiate the class by providing the printer
|
||||||
|
model as argument.
|
||||||
|
|
||||||
|
:param str model: Choose from the list of available models.
|
||||||
|
|
||||||
|
:ivar bytes data: The resulting bytecode with all instructions.
|
||||||
|
:ivar bool exception_on_warning: If set to True, an exception is raised if trying to add instruction which are not supported on the selected model. If set to False, the instruction is simply ignored and a warning sent to logging/stderr.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, model='QL-500'):
|
def __init__(self, model='QL-500'):
|
||||||
if model not in models:
|
if model not in models:
|
||||||
raise BrotherQLUnknownModel()
|
raise BrotherQLUnknownModel()
|
||||||
@@ -57,7 +79,7 @@ class BrotherQLRaster(object):
|
|||||||
else:
|
else:
|
||||||
logger.warning(problem)
|
logger.warning(problem)
|
||||||
|
|
||||||
def unsupported(self, problem):
|
def _unsupported(self, problem):
|
||||||
"""
|
"""
|
||||||
Raises BrotherQLUnsupportedCmd if
|
Raises BrotherQLUnsupportedCmd if
|
||||||
exception_on_warning is set to True.
|
exception_on_warning is set to True.
|
||||||
@@ -86,7 +108,7 @@ class BrotherQLRaster(object):
|
|||||||
the mode change (others are in raster mode already).
|
the mode change (others are in raster mode already).
|
||||||
"""
|
"""
|
||||||
if self.model not in modesetting:
|
if self.model not in modesetting:
|
||||||
self.unsupported("Trying to switch the operating mode on a printer that doesn't support the command.")
|
self._unsupported("Trying to switch the operating mode on a printer that doesn't support the command.")
|
||||||
return
|
return
|
||||||
self.data += b'\x1B\x69\x61\x01' # ESC i a
|
self.data += b'\x1B\x69\x61\x01' # ESC i a
|
||||||
|
|
||||||
@@ -139,24 +161,24 @@ class BrotherQLRaster(object):
|
|||||||
|
|
||||||
def add_autocut(self, autocut = False):
|
def add_autocut(self, autocut = False):
|
||||||
if self.model not in cuttingsupport:
|
if self.model not in cuttingsupport:
|
||||||
self.unsupported("Trying to call add_autocut with a printer that doesn't support it")
|
self._unsupported("Trying to call add_autocut with a printer that doesn't support it")
|
||||||
return
|
return
|
||||||
self.data += b'\x1B\x69\x4D' # ESC i M
|
self.data += b'\x1B\x69\x4D' # ESC i M
|
||||||
self.data += bytes([autocut << 6])
|
self.data += bytes([autocut << 6])
|
||||||
|
|
||||||
def add_cut_every(self, n=1):
|
def add_cut_every(self, n=1):
|
||||||
if self.model not in cuttingsupport:
|
if self.model not in cuttingsupport:
|
||||||
self.unsupported("Trying to call add_cut_every with a printer that doesn't support it")
|
self._unsupported("Trying to call add_cut_every with a printer that doesn't support it")
|
||||||
return
|
return
|
||||||
self.data += b'\x1B\x69\x41' # ESC i A
|
self.data += b'\x1B\x69\x41' # ESC i A
|
||||||
self.data += bytes([n & 0xFF])
|
self.data += bytes([n & 0xFF])
|
||||||
|
|
||||||
def add_expanded_mode(self):
|
def add_expanded_mode(self):
|
||||||
if self.model not in expandedmode:
|
if self.model not in expandedmode:
|
||||||
self.unsupported("Trying to set expanded mode (dpi/cutting at end) on a printer that doesn't support it")
|
self._unsupported("Trying to set expanded mode (dpi/cutting at end) on a printer that doesn't support it")
|
||||||
return
|
return
|
||||||
if self.two_color_printing and not self.two_color_support:
|
if self.two_color_printing and not self.two_color_support:
|
||||||
self.unsupported("Trying to set two_color_printing in expanded mode on a printer that doesn't support it.")
|
self._unsupported("Trying to set two_color_printing in expanded mode on a printer that doesn't support it.")
|
||||||
return
|
return
|
||||||
self.data += b'\x1B\x69\x4B' # ESC i K
|
self.data += b'\x1B\x69\x4B' # ESC i K
|
||||||
flags = 0x00
|
flags = 0x00
|
||||||
@@ -170,8 +192,16 @@ class BrotherQLRaster(object):
|
|||||||
self.data += struct.pack('<H', dots)
|
self.data += struct.pack('<H', dots)
|
||||||
|
|
||||||
def add_compression(self, compression=True):
|
def add_compression(self, compression=True):
|
||||||
|
"""
|
||||||
|
Add an instruction enabling or disabling compression for the transmitted raster image lines.
|
||||||
|
Not all models support compression. If the specific model doesn't support it but this method
|
||||||
|
is called trying to enable it, either a warning is set or an exception is raised depending on
|
||||||
|
the value of :py:attr:`exception_on_warning`
|
||||||
|
|
||||||
|
:param bool compression: Whether compression should be on or off
|
||||||
|
"""
|
||||||
if self.model not in compressionsupport:
|
if self.model not in compressionsupport:
|
||||||
self.unsupported("Trying to set compression on a printer that doesn't support it")
|
self._unsupported("Trying to set compression on a printer that doesn't support it")
|
||||||
return
|
return
|
||||||
self._compression = compression
|
self._compression = compression
|
||||||
self.data += b'\x4D' # M
|
self.data += b'\x4D' # M
|
||||||
@@ -185,7 +215,14 @@ class BrotherQLRaster(object):
|
|||||||
return nbpr*8
|
return nbpr*8
|
||||||
|
|
||||||
def add_raster_data(self, image, second_image=None):
|
def add_raster_data(self, image, second_image=None):
|
||||||
""" image: Pillow Image() """
|
"""
|
||||||
|
Add the image data to the instructions.
|
||||||
|
The provided image has to be binary (every pixel
|
||||||
|
is either black or white).
|
||||||
|
|
||||||
|
:param PIL.Image.Image image: The image to be converted and added to the raster instructions
|
||||||
|
:param PIL.Image.Image second_image: A second image with a separate color layer (red layer for the QL-800 series)
|
||||||
|
"""
|
||||||
logger.debug("raster_image_size: {0}x{1}".format(*image.size))
|
logger.debug("raster_image_size: {0}x{1}".format(*image.size))
|
||||||
if image.size[0] != self.get_pixel_width():
|
if image.size[0] != self.get_pixel_width():
|
||||||
fmt = 'Wrong pixel width: {}, expected {}'
|
fmt = 'Wrong pixel width: {}, expected {}'
|
||||||
@@ -208,13 +245,18 @@ class BrotherQLRaster(object):
|
|||||||
while start + row_len <= frame_len:
|
while start + row_len <= frame_len:
|
||||||
for i, frame in enumerate(frames):
|
for i, frame in enumerate(frames):
|
||||||
row = frame[start:start+row_len]
|
row = frame[start:start+row_len]
|
||||||
|
if self._compression:
|
||||||
|
row = packbits.encode(row)
|
||||||
|
translen = len(row) # number of bytes to be transmitted
|
||||||
|
if self.model.startswith('PT'):
|
||||||
|
file_str.write(b'\x47')
|
||||||
|
file_str.write(bytes([translen%256, translen//256]))
|
||||||
|
else:
|
||||||
if second_image:
|
if second_image:
|
||||||
file_str.write(b'\x77\x01' if i == 0 else b'\x77\x02')
|
file_str.write(b'\x77\x01' if i == 0 else b'\x77\x02')
|
||||||
else:
|
else:
|
||||||
file_str.write(b'\x67\x00')
|
file_str.write(b'\x67\x00')
|
||||||
if self._compression:
|
file_str.write(bytes([translen]))
|
||||||
row = packbits.encode(row)
|
|
||||||
file_str.write(bytes([len(row)]))
|
|
||||||
file_str.write(row)
|
file_str.write(row)
|
||||||
start += row_len
|
start += row_len
|
||||||
self.data += file_str.getvalue()
|
self.data += file_str.getvalue()
|
||||||
|
|||||||
+27
-9
@@ -16,8 +16,10 @@ OPCODES = {
|
|||||||
# signature name following bytes description
|
# signature name following bytes description
|
||||||
b'\x00': ("preamble", -1, "Preamble, 200-300x 0x00 to clear comamnd buffer"),
|
b'\x00': ("preamble", -1, "Preamble, 200-300x 0x00 to clear comamnd buffer"),
|
||||||
b'\x4D': ("compression", 1, ""),
|
b'\x4D': ("compression", 1, ""),
|
||||||
b'\x67': ("raster", -1, ""),
|
b'\x67': ("raster QL", -1, ""),
|
||||||
b'\x77': ("2-color raster", -1, ""),
|
b'\x47': ("raster P-touch", -1, ""),
|
||||||
|
b'\x77': ("2-color raster QL", -1, ""),
|
||||||
|
b'\x5a': ("zero raster", 0, "empty raster line"),
|
||||||
b'\x0C': ("print", 0, "print intermediate page"),
|
b'\x0C': ("print", 0, "print intermediate page"),
|
||||||
b'\x1A': ("print", 0, "print final page"),
|
b'\x1A': ("print", 0, "print final page"),
|
||||||
b'\x1b\x40': ("init", 0, "initialization"),
|
b'\x1b\x40': ("init", 0, "initialization"),
|
||||||
@@ -139,12 +141,15 @@ def chunker(data, raise_exception=False):
|
|||||||
opcode_def = OPCODES[opcode]
|
opcode_def = OPCODES[opcode]
|
||||||
num_bytes = len(opcode)
|
num_bytes = len(opcode)
|
||||||
if opcode_def[1] > 0: num_bytes += opcode_def[1]
|
if opcode_def[1] > 0: num_bytes += opcode_def[1]
|
||||||
if 'raster' in opcode_def[0]:
|
elif opcode_def[0] in ('raster QL', '2-color raster QL'):
|
||||||
num_bytes += data[2] + 2
|
num_bytes += data[2] + 2
|
||||||
|
elif opcode_def[0] in ('raster P-touch',):
|
||||||
|
num_bytes += data[1] + data[2]*256 + 2
|
||||||
#payload = data[len(opcode):num_bytes]
|
#payload = data[len(opcode):num_bytes]
|
||||||
instructions.append(data[:num_bytes])
|
instructions.append(data[:num_bytes])
|
||||||
|
yield instructions[-1]
|
||||||
data = data[num_bytes:]
|
data = data[num_bytes:]
|
||||||
return instructions
|
#return instructions
|
||||||
|
|
||||||
def match_opcode(data):
|
def match_opcode(data):
|
||||||
matching_opcodes = [opcode for opcode in OPCODES.keys() if data.startswith(opcode)]
|
matching_opcodes = [opcode for opcode in OPCODES.keys() if data.startswith(opcode)]
|
||||||
@@ -262,7 +267,11 @@ class BrotherQLReader(object):
|
|||||||
logger.info(" {} ({}) --> found! (payload: {})".format(opcode_def[0], hex_format(opcode), hex_format(payload)))
|
logger.info(" {} ({}) --> found! (payload: {})".format(opcode_def[0], hex_format(opcode), hex_format(payload)))
|
||||||
if opcode_def[0] == 'compression':
|
if opcode_def[0] == 'compression':
|
||||||
self.compression = payload[0] == 0x02
|
self.compression = payload[0] == 0x02
|
||||||
if 'raster' in opcode_def[0]:
|
if opcode_def[0] == 'zero raster':
|
||||||
|
self.black_rows.append(bytes())
|
||||||
|
if self.two_color_printing:
|
||||||
|
self.red_rows.append(bytes())
|
||||||
|
if opcode_def[0] in ('raster QL', '2-color raster QL', 'raster P-touch'):
|
||||||
rpl = bytes(payload[2:]) # raster payload
|
rpl = bytes(payload[2:]) # raster payload
|
||||||
if self.compression:
|
if self.compression:
|
||||||
row = bytes()
|
row = bytes()
|
||||||
@@ -282,7 +291,7 @@ class BrotherQLReader(object):
|
|||||||
if index >= len(rpl): break
|
if index >= len(rpl): break
|
||||||
else:
|
else:
|
||||||
row = rpl
|
row = rpl
|
||||||
if opcode_def[0] == 'raster':
|
if opcode_def[0] in ('raster QL', 'raster P-touch'):
|
||||||
self.black_rows.append(row)
|
self.black_rows.append(row)
|
||||||
else: # 2-color
|
else: # 2-color
|
||||||
if payload[0] == 0x01:
|
if payload[0] == 0x01:
|
||||||
@@ -306,15 +315,24 @@ class BrotherQLReader(object):
|
|||||||
logger.info("Len of red rows: %d", len(self.red_rows))
|
logger.info("Len of red rows: %d", len(self.red_rows))
|
||||||
def get_im(rows):
|
def get_im(rows):
|
||||||
if not len(rows): return None
|
if not len(rows): return None
|
||||||
size = (len(rows[0])*8, len(rows))
|
width_dots = max(len(row) for row in rows)
|
||||||
data = bytes(b''.join(rows))
|
height_dots = len(rows)
|
||||||
|
size = (width_dots*8, height_dots)
|
||||||
|
expanded_rows = []
|
||||||
|
for row in rows:
|
||||||
|
if len(row) == 0:
|
||||||
|
expanded_rows.append(b'\x00'*width_dots)
|
||||||
|
else:
|
||||||
|
expanded_rows.append(row)
|
||||||
|
data = bytes(b''.join(expanded_rows))
|
||||||
data = bytes([2**8 + ~byte for byte in data]) # invert b/w
|
data = bytes([2**8 + ~byte for byte in data]) # invert b/w
|
||||||
im = Image.frombytes("1", size, data, decoder_name='raw')
|
im = Image.frombytes("1", size, data, decoder_name='raw')
|
||||||
return im
|
return im
|
||||||
im_black, im_red = (get_im(rows) for rows in (self.black_rows, self.red_rows))
|
|
||||||
if not self.two_color_printing:
|
if not self.two_color_printing:
|
||||||
|
im_black = get_im(self.black_rows)
|
||||||
im = im_black
|
im = im_black
|
||||||
else:
|
else:
|
||||||
|
im_black, im_red = (get_im(rows) for rows in (self.black_rows, self.red_rows))
|
||||||
im_black = im_black.convert("RGBA")
|
im_black = im_black.convert("RGBA")
|
||||||
im_red = im_red.convert("L")
|
im_red = im_red.convert("L")
|
||||||
im_red = colorize(im_red, (255, 0, 0), (255, 255, 255))
|
im_red = colorize(im_red, (255, 0, 0), (255, 255, 255))
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ setup(name='brother_ql',
|
|||||||
"packbits",
|
"packbits",
|
||||||
"pillow>=3.3.0",
|
"pillow>=3.3.0",
|
||||||
"pyusb",
|
"pyusb",
|
||||||
'dataclasses;python_version<"3.7"',
|
'attrs',
|
||||||
'typing;python_version<"3.5"',
|
'typing;python_version<"3.5"',
|
||||||
'enum34;python_version<"3.4"',
|
'enum34;python_version<"3.4"',
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user