* Merge software and hardware barcodes to one method * Fix wrong sw barcode heigh/width * Add missing param to _sw_barcode call * Make barcode() smarter, improvements and clean up * Use param font_size in sw_barcode() * Update docstrings * Update barcode examples and docs * Add --force_software option to CLI * Attempt to match the sw and hw barcode sizes * Better approximation to native font size * Fix docs build * Update tests at test_function_softbarcode * Fix exception * Move image dpi setting to writter_options * Fix _sw_barcode() docstring param * Fix wrong default param in docstring * improve linkage in documentation --------- Co-authored-by: Patrick Kanzler <4189642+patkan@users.noreply.github.com> Co-authored-by: Patrick Kanzler <dev@pkanzler.de>
This commit is contained in:
parent
676d2840de
commit
3c11c1b9ab
|
@ -1,17 +1,18 @@
|
|||
Printing Barcodes
|
||||
-----------------
|
||||
:Last Reviewed: 2016-07-31
|
||||
:Last Reviewed: 2023-05-16
|
||||
|
||||
Most ESC/POS-printers implement barcode-printing.
|
||||
The barcode-commandset is implemented in the barcode-method.
|
||||
For a list of compatible barcodes you should check the manual of your printer.
|
||||
As a rule of thumb: even older Epson-models support most 1D-barcodes.
|
||||
To be sure just try some implementations and have a look at the notices below.
|
||||
Many printers implement barcode printing natively.
|
||||
This hardware renderered barcodes are fast but the supported formats are limited by the printer itself and different between models.
|
||||
However, almost all printers support printing images, so barcode renderization can be performed externally by software and then sent to the printer as an image.
|
||||
As a drawback, this operation is much slower and the user needs to know and choose the image implementation method supported by the printer's commandset.
|
||||
|
||||
barcode-method
|
||||
~~~~~~~~~~~~~~
|
||||
The barcode-method is rather low-level and orients itself on the implementation of ESC/POS.
|
||||
In the future this class could be supplemented by a high-level class that helps the user generating the payload.
|
||||
Since version 3.0, the ``barcode`` method unifies the previous ``barcode`` (hardware) and ``soft_barcode`` (software) methods.
|
||||
It is able to choose automatically the best printer implementation for barcode printing based on the capabilities of the printer and the type of barcode desired.
|
||||
To achieve this, it relies on the information contained in the escpos-printer-db profiles.
|
||||
The chosen profile needs to match the capabilities of the printer as closely as possible.
|
||||
|
||||
.. py:currentmodule:: escpos.escpos
|
||||
|
||||
|
|
|
@ -2,10 +2,10 @@ from escpos.printer import Usb
|
|||
|
||||
|
||||
# Adapt to your needs
|
||||
p = Usb(0x0416, 0x5011, profile="POS-5890")
|
||||
p = Usb(0x0416, 0x5011, profile="TM-T88II")
|
||||
|
||||
# Print software and then hardware barcode with the same content
|
||||
p.soft_barcode("code39", "123456")
|
||||
p.barcode("123456", "CODE39", width=2, force_software=True)
|
||||
p.text("\n")
|
||||
p.text("\n")
|
||||
p.barcode("123456", "CODE39")
|
||||
|
|
|
@ -5,5 +5,5 @@ from escpos.printer import Usb
|
|||
p = Usb(0x0416, 0x5011, profile="POS-5890")
|
||||
|
||||
# Some software barcodes
|
||||
p.soft_barcode("code128", "Hello")
|
||||
p.soft_barcode("code39", "1234")
|
||||
p.barcode("Hello", "code128", width=2, force_software="bitImageRaster")
|
||||
p.barcode("1234", "code39", width=2, force_software=True)
|
||||
|
|
|
@ -161,6 +161,11 @@ ESCPOS_COMMANDS = [
|
|||
"help": "ESCPOS function type",
|
||||
"choices": ["A", "B"],
|
||||
},
|
||||
{
|
||||
"option_strings": ("--force_software",),
|
||||
"help": "Force render and print barcode as an image",
|
||||
"choices": ["graphics", "bitImageColumn", "bitImageRaster"],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
@ -27,12 +27,12 @@ FS = b"\x1c"
|
|||
GS = b"\x1d"
|
||||
|
||||
# Feed control sequences
|
||||
CTL_LF = b"\n" # Print and line feed
|
||||
CTL_FF = b"\f" # Form feed
|
||||
CTL_CR = b"\r" # Carriage return
|
||||
CTL_HT = b"\t" # Horizontal tab
|
||||
CTL_SET_HT = ESC + b"\x44" # Set horizontal tab positions
|
||||
CTL_VT = b"\v" # Vertical tab
|
||||
CTL_LF = b"\n" #: Print and line feed
|
||||
CTL_FF = b"\f" #: Form feed
|
||||
CTL_CR = b"\r" #: Carriage return
|
||||
CTL_HT = b"\t" #: Horizontal tab
|
||||
CTL_SET_HT = ESC + b"\x44" #: Set horizontal tab positions
|
||||
CTL_VT = b"\v" #: Vertical tab
|
||||
|
||||
# Printer hardware
|
||||
HW_INIT = ESC + b"@" # Clear data in buffer and reset modes
|
||||
|
@ -57,8 +57,8 @@ CD_KICK_5 = _CASH_DRAWER(b"\x01", 50, 50) # Sends a pulse to pin 5 []
|
|||
|
||||
# Paper Cutter
|
||||
_CUT_PAPER = lambda m: GS + b"V" + m
|
||||
PAPER_FULL_CUT = _CUT_PAPER(b"\x00") # Full cut paper
|
||||
PAPER_PART_CUT = _CUT_PAPER(b"\x01") # Partial cut paper
|
||||
PAPER_FULL_CUT = _CUT_PAPER(b"\x00") #: Full cut paper
|
||||
PAPER_PART_CUT = _CUT_PAPER(b"\x01") #: Partial cut paper
|
||||
|
||||
# Beep (please note that the actual beep sequence may differ between devices)
|
||||
BEEP = b"\x07"
|
||||
|
@ -168,8 +168,8 @@ TXT_STYLE = {
|
|||
|
||||
# Fonts
|
||||
SET_FONT = lambda n: ESC + b"\x4d" + n
|
||||
TXT_FONT_A = SET_FONT(b"\x00") # Font type A
|
||||
TXT_FONT_B = SET_FONT(b"\x01") # Font type B
|
||||
TXT_FONT_A = SET_FONT(b"\x00") #: Font type A
|
||||
TXT_FONT_B = SET_FONT(b"\x01") #: Font type B
|
||||
|
||||
# Spacing
|
||||
LINESPACING_RESET = ESC + b"2"
|
||||
|
@ -179,23 +179,23 @@ LINESPACING_FUNCS = {
|
|||
180: ESC + b"3", # line_spacing/180 of an inch, 0 <= line_spacing <= 255
|
||||
}
|
||||
|
||||
# Prefix to change the codepage. You need to attach a byte to indicate
|
||||
# the codepage to use. We use escpos-printer-db as the data source.
|
||||
#: Prefix to change the codepage. You need to attach a byte to indicate
|
||||
#: the codepage to use. We use escpos-printer-db as the data source.
|
||||
CODEPAGE_CHANGE = ESC + b"\x74"
|
||||
|
||||
# Barcode format
|
||||
_SET_BARCODE_TXT_POS = lambda n: GS + b"H" + n
|
||||
BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS(b"\x00") # HRI barcode chars OFF
|
||||
BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS(b"\x01") # HRI barcode chars above
|
||||
BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS(b"\x02") # HRI barcode chars below
|
||||
BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b"\x03") # HRI both above and below
|
||||
BARCODE_TXT_OFF = _SET_BARCODE_TXT_POS(b"\x00") #: HRI barcode chars OFF
|
||||
BARCODE_TXT_ABV = _SET_BARCODE_TXT_POS(b"\x01") #: HRI barcode chars above
|
||||
BARCODE_TXT_BLW = _SET_BARCODE_TXT_POS(b"\x02") #: HRI barcode chars below
|
||||
BARCODE_TXT_BTH = _SET_BARCODE_TXT_POS(b"\x03") #: HRI both above and below
|
||||
|
||||
_SET_HRI_FONT = lambda n: GS + b"f" + n
|
||||
BARCODE_FONT_A = _SET_HRI_FONT(b"\x00") # Font type A for HRI barcode chars
|
||||
BARCODE_FONT_B = _SET_HRI_FONT(b"\x01") # Font type B for HRI barcode chars
|
||||
BARCODE_FONT_A = _SET_HRI_FONT(b"\x00") #: Font type A for HRI barcode chars
|
||||
BARCODE_FONT_B = _SET_HRI_FONT(b"\x01") #: Font type B for HRI barcode chars
|
||||
|
||||
BARCODE_HEIGHT = GS + b"h" # Barcode Height [1-255]
|
||||
BARCODE_WIDTH = GS + b"w" # Barcode Width [2-6]
|
||||
BARCODE_HEIGHT = GS + b"h" #: Barcode Height [1-255]
|
||||
BARCODE_WIDTH = GS + b"w" #: Barcode Width [2-6]
|
||||
|
||||
# NOTE: This isn't actually an ESC/POS command. It's the common prefix to the
|
||||
# two "print bar code" commands:
|
||||
|
@ -204,7 +204,7 @@ BARCODE_WIDTH = GS + b"w" # Barcode Width [2-6]
|
|||
# The latter command supports more barcode types
|
||||
_SET_BARCODE_TYPE = lambda m: GS + b"k" + six.int2byte(m)
|
||||
|
||||
# Barcodes for printing function type A
|
||||
#: Barcodes for printing function type A
|
||||
BARCODE_TYPE_A = {
|
||||
"UPC-A": _SET_BARCODE_TYPE(0),
|
||||
"UPC-E": _SET_BARCODE_TYPE(1),
|
||||
|
@ -216,8 +216,8 @@ BARCODE_TYPE_A = {
|
|||
"CODABAR": _SET_BARCODE_TYPE(6), # Same as NW7
|
||||
}
|
||||
|
||||
# Barcodes for printing function type B
|
||||
# The first 8 are the same barcodes as type A
|
||||
#: Barcodes for printing function type B
|
||||
#: The first 8 are the same barcodes as type A
|
||||
BARCODE_TYPE_B = {
|
||||
"UPC-A": _SET_BARCODE_TYPE(65),
|
||||
"UPC-E": _SET_BARCODE_TYPE(66),
|
||||
|
@ -236,6 +236,7 @@ BARCODE_TYPE_B = {
|
|||
"GS1 DATABAR EXPANDED": _SET_BARCODE_TYPE(78),
|
||||
}
|
||||
|
||||
#: supported barcode formats
|
||||
BARCODE_FORMATS = {
|
||||
"UPC-A": ([(11, 12)], "^[0-9]{11,12}$"),
|
||||
"UPC-E": ([(7, 8), (11, 12)], "^([0-9]{7,8}|[0-9]{11,12})$"),
|
||||
|
|
|
@ -87,6 +87,19 @@ from escpos.image import EscposImage
|
|||
from escpos.capabilities import get_profile, BARCODE_B
|
||||
|
||||
|
||||
# Remove special characters and whitespaces of the supported barcode names,
|
||||
# convert to uppercase and map them to their original names.
|
||||
HW_BARCODE_NAMES = {
|
||||
"".join([char for char in name.upper() if char.isalnum()]): name
|
||||
for bc_type in BARCODE_TYPES.values()
|
||||
for name in bc_type
|
||||
}
|
||||
SW_BARCODE_NAMES = {
|
||||
"".join([char for char in name.upper() if char.isalnum()]): name
|
||||
for name in barcode.PROVIDED_BARCODES
|
||||
}
|
||||
|
||||
|
||||
@six.add_metaclass(ABCMeta)
|
||||
class Escpos(object):
|
||||
"""ESC/POS Printer object
|
||||
|
@ -411,6 +424,24 @@ class Escpos(object):
|
|||
regex, code
|
||||
)
|
||||
|
||||
def _dpi(self) -> int:
|
||||
"""Printer's DPI resolution."""
|
||||
try:
|
||||
dpi = int(self.profile.profile_data["media"]["dpi"])
|
||||
except (KeyError, TypeError):
|
||||
# Calculate the printer's DPI from the width info of the profile.
|
||||
try:
|
||||
px = self.profile.profile_data["media"]["width"]["pixels"]
|
||||
mm = self.profile.profile_data["media"]["width"]["mm"]
|
||||
mm -= 10 # paper width minus margin =~ printable area
|
||||
dpi = int(px / (mm / 25.4))
|
||||
except (KeyError, TypeError, ZeroDivisionError):
|
||||
# Value on error.
|
||||
dpi = 180
|
||||
print(f"No printer's DPI info was found: Defaulting to {dpi}.")
|
||||
self.profile.profile_data["media"]["dpi"] = dpi
|
||||
return dpi
|
||||
|
||||
def barcode(
|
||||
self,
|
||||
code,
|
||||
|
@ -422,6 +453,128 @@ class Escpos(object):
|
|||
align_ct=True,
|
||||
function_type=None,
|
||||
check=True,
|
||||
force_software=False,
|
||||
):
|
||||
"""Print barcode.
|
||||
|
||||
Automatic hardware|software barcode renderer according to the printer capabilities.
|
||||
|
||||
Defaults to hardware barcode and its format types if supported.
|
||||
Automatically switches to software barcode renderer if hardware does not
|
||||
support a barcode type that is supported by software. (e.g. JAN, ISSN, etc.).
|
||||
|
||||
Set force_software=True to force the software renderer according to the profile.
|
||||
Set force_software=graphics|bitImageColumn|bitImageRaster to specify a renderer.
|
||||
|
||||
Ignores caps, special chars and whitespaces in barcode type names.
|
||||
So "EAN13", "ean-13", "Ean_13", "EAN 13" are all accepted.
|
||||
|
||||
:param code: alphanumeric data to be printed as bar code (payload).
|
||||
|
||||
:param bc: barcode format type (EAN13, CODE128, JAN, etc.).
|
||||
|
||||
:param height: barcode module height (in printer dots), has to be between 1 and 255.
|
||||
*default*: 64
|
||||
:type height: int
|
||||
|
||||
:param width: barcode module width (in printer dots), has to be between 2 and 6.
|
||||
*default*: 3
|
||||
:type width: int
|
||||
|
||||
:param pos: text position (ABOVE, BELOW, BOTH, OFF) relative to the barcode
|
||||
(ignored in software renderer).
|
||||
*default*: BELOW
|
||||
|
||||
:param font: select font A or B (ignored in software renderer).
|
||||
*default*: A
|
||||
|
||||
:param align_ct: If *True*, center the barcode.
|
||||
*default*: True
|
||||
:type align_ct: bool
|
||||
|
||||
:param function_type: ESCPOS function type A or B. None to guess it from profile
|
||||
(ignored in software renderer).
|
||||
*default*: None
|
||||
|
||||
:param check: If *True*, checks that the code meets the requirements of the barcode type.
|
||||
*default*: True
|
||||
:type check: bool
|
||||
|
||||
:param force_software: If *True*, force the use of software barcode renderer from profile.
|
||||
If *"graphics", "bitImageColumn" or "bitImageRaster"*, force the use of specific renderer.
|
||||
:type force_software: bool | str
|
||||
|
||||
:raises: :py:exc:`~escpos.exceptions.BarcodeCodeError`,
|
||||
:py:exc:`~escpos.exceptions.BarcodeTypeError`
|
||||
|
||||
.. note::
|
||||
Get all supported formats at:
|
||||
- Hardware: :py:const:`~escpos.constants.BARCODE_FORMATS`
|
||||
- Software: `Python barcode documentation <https://python-barcode.readthedocs.io/en/stable/supported-formats.html>`_
|
||||
"""
|
||||
hw_modes = ["barcodeA", "barcodeB"]
|
||||
sw_modes = ["graphics", "bitImageColumn", "bitImageRaster"]
|
||||
capable = {
|
||||
"hw": [mode for mode in hw_modes if self.profile.supports(mode)] or None,
|
||||
"sw": [mode for mode in sw_modes if self.profile.supports(mode)] or None,
|
||||
}
|
||||
if (not capable["hw"] and not capable["sw"]) or (
|
||||
not capable["sw"] and force_software
|
||||
):
|
||||
raise BarcodeTypeError(
|
||||
f"""Profile {
|
||||
self.profile.profile_data['name']
|
||||
} - hw barcode: {capable['hw']}, sw barcode: {capable['sw']}"""
|
||||
)
|
||||
|
||||
bc_alnum = "".join([char for char in bc.upper() if char.isalnum()])
|
||||
capable_bc = {
|
||||
"hw": HW_BARCODE_NAMES.get(bc_alnum),
|
||||
"sw": SW_BARCODE_NAMES.get(bc_alnum),
|
||||
}
|
||||
if not any([*capable_bc.values()]):
|
||||
raise BarcodeTypeError(f"Not supported or wrong barcode name {bc}.")
|
||||
|
||||
if force_software or not capable["hw"] or not capable_bc["hw"]:
|
||||
# Select the best possible capable render mode
|
||||
impl = capable["sw"][0]
|
||||
if force_software in capable["sw"]:
|
||||
# Force to a specific mode
|
||||
impl = force_software
|
||||
print(f"Using {impl} software barcode renderer")
|
||||
# Set barcode type
|
||||
bc = capable_bc["sw"] or bc
|
||||
# Get mm per point of the printer
|
||||
mmxpt = 25.4 / self._dpi()
|
||||
self._sw_barcode(
|
||||
bc,
|
||||
code,
|
||||
impl=impl,
|
||||
module_height=height * mmxpt,
|
||||
module_width=width * mmxpt,
|
||||
text_distance=3, # TODO: _hw_barcode() size equivalence
|
||||
font_size=9, # TODO: _hw_barcode() size equivalence
|
||||
center=align_ct,
|
||||
)
|
||||
return
|
||||
|
||||
print("Using hardware barcode renderer")
|
||||
bc = capable_bc["hw"] or bc
|
||||
self._hw_barcode(
|
||||
code, bc, height, width, pos, font, align_ct, function_type, check
|
||||
)
|
||||
|
||||
def _hw_barcode(
|
||||
self,
|
||||
code,
|
||||
bc,
|
||||
height=64,
|
||||
width=3,
|
||||
pos="BELOW",
|
||||
font="A",
|
||||
align_ct=True,
|
||||
function_type=None,
|
||||
check=True,
|
||||
):
|
||||
"""Print Barcode
|
||||
|
||||
|
@ -444,9 +597,6 @@ class Escpos(object):
|
|||
automatic centering. Please note that when you use center alignment, then the alignment of text will be changed
|
||||
automatically to centered. You have to manually restore the alignment if necessary.
|
||||
|
||||
.. todo:: If further barcode-types are needed they could be rendered transparently as an image. (This could also
|
||||
be of help if the printer does not support types that others do.)
|
||||
|
||||
:param code: alphanumeric data to be printed as bar code
|
||||
:param bc: barcode format, possible values are for type A are:
|
||||
|
||||
|
@ -506,27 +656,12 @@ class Escpos(object):
|
|||
:py:exc:`~escpos.exceptions.BarcodeTypeError`,
|
||||
:py:exc:`~escpos.exceptions.BarcodeCodeError`
|
||||
"""
|
||||
if function_type is None:
|
||||
# Choose the function type automatically.
|
||||
if bc in BARCODE_TYPES["A"]:
|
||||
function_type = "A"
|
||||
else:
|
||||
if bc in BARCODE_TYPES["B"]:
|
||||
if not self.profile.supports(BARCODE_B):
|
||||
raise BarcodeTypeError(
|
||||
(
|
||||
"Barcode type '{bc} not supported for "
|
||||
"the current printer profile"
|
||||
).format(bc=bc)
|
||||
)
|
||||
function_type = "B"
|
||||
else:
|
||||
raise BarcodeTypeError(
|
||||
("Barcode type '{bc} is not valid").format(bc=bc)
|
||||
)
|
||||
# If function_type is specified, otherwise use guessing.
|
||||
ft_guess = [ft for ft in ["A", "B"] if bc in BARCODE_TYPES.get(ft)]
|
||||
ft_guess = ft_guess or [None]
|
||||
function_type = function_type or ft_guess[0]
|
||||
|
||||
bc_types = BARCODE_TYPES[function_type.upper()]
|
||||
if bc.upper() not in bc_types.keys():
|
||||
if not function_type or not BARCODE_TYPES.get(function_type.upper()):
|
||||
raise BarcodeTypeError(
|
||||
(
|
||||
"Barcode '{bc}' not valid for barcode function type "
|
||||
|
@ -536,6 +671,7 @@ class Escpos(object):
|
|||
function_type=function_type,
|
||||
)
|
||||
)
|
||||
bc_types = BARCODE_TYPES[function_type.upper()]
|
||||
|
||||
if check and not self.check_barcode(bc, code):
|
||||
raise BarcodeCodeError(
|
||||
|
@ -587,16 +723,71 @@ class Escpos(object):
|
|||
if function_type.upper() == "A":
|
||||
self._raw(NUL)
|
||||
|
||||
def soft_barcode(
|
||||
def _sw_barcode(
|
||||
self,
|
||||
barcode_type,
|
||||
data,
|
||||
impl="bitImageColumn",
|
||||
module_height=5,
|
||||
module_width=0.2,
|
||||
text_distance=1,
|
||||
text_distance=5,
|
||||
font_size=10,
|
||||
center=True,
|
||||
):
|
||||
"""Print Barcode
|
||||
|
||||
This method allows to print barcodes. The rendering of the barcode is done by
|
||||
the `barcode` library and sent to the printer as image through one of the
|
||||
printer's supported implementations: graphics, bitImageColumn or bitImageRaster.
|
||||
|
||||
:param barcode_type: barcode format, possible values are:
|
||||
* ean8
|
||||
* ean8-guard
|
||||
* ean13
|
||||
* ean13-guard
|
||||
* ean
|
||||
* gtin
|
||||
* ean14
|
||||
* jan
|
||||
* upc
|
||||
* upca
|
||||
* isbn
|
||||
* isbn13
|
||||
* gs1
|
||||
* isbn10
|
||||
* issn
|
||||
* code39
|
||||
* pzn
|
||||
* code128
|
||||
* itf
|
||||
* gs1_128
|
||||
* codabar
|
||||
* nw-7
|
||||
:type data: str
|
||||
|
||||
:param data: alphanumeric data to be printed as bar code (payload).
|
||||
:type data: str
|
||||
|
||||
:param impl: image printing mode:
|
||||
* graphics
|
||||
* bitImageColumn
|
||||
* bitImageRaster
|
||||
|
||||
:param module_height: barcode module height (in mm).
|
||||
:type module_height: int | float
|
||||
|
||||
:param module_width: barcode module width (in mm).
|
||||
:type module_width: int | float
|
||||
|
||||
:param text_distance: distance from the barcode to the code text (in mm).
|
||||
:type text_distance: int | float
|
||||
|
||||
:param font_size: font size of the code text (in dots).
|
||||
:type font_size: int
|
||||
|
||||
:param center: center the barcode.
|
||||
:type center: bool
|
||||
"""
|
||||
image_writer = ImageWriter()
|
||||
|
||||
# Check if barcode type exists
|
||||
|
@ -610,11 +801,15 @@ class Escpos(object):
|
|||
# Render the barcode
|
||||
barcode_class = barcode.get_barcode_class(barcode_type)
|
||||
my_code = barcode_class(data, writer=image_writer)
|
||||
|
||||
my_code.render(
|
||||
writer_options={
|
||||
"module_height": module_height,
|
||||
"module_width": module_width,
|
||||
"quiet_zone": 0, # horizontal padding
|
||||
"text_distance": text_distance,
|
||||
"font_size": font_size,
|
||||
"dpi": self._dpi(), # Image dpi has to match the printer's dpi
|
||||
}
|
||||
)
|
||||
|
||||
|
|
|
@ -13,13 +13,13 @@ def instance():
|
|||
def test_soft_barcode_ean8_invalid(instance):
|
||||
"""test with an invalid barcode"""
|
||||
with pytest.raises(barcode.errors.BarcodeError):
|
||||
instance.soft_barcode("ean8", "1234")
|
||||
instance.barcode("1234", "ean8", force_software=True)
|
||||
|
||||
|
||||
def test_soft_barcode_ean8(instance):
|
||||
"""test with a valid ean8 barcode"""
|
||||
instance.soft_barcode("ean8", "1234567")
|
||||
instance.barcode("1234567", "ean8", force_software=True)
|
||||
|
||||
|
||||
def test_soft_barcode_ean8_nocenter(instance):
|
||||
instance.soft_barcode("ean8", "1234567", center=False)
|
||||
instance.barcode("1234567", "ean8", align_ct=False, force_software=True)
|
||||
|
|
Loading…
Reference in New Issue