/
kuzant24
/
libre-macros
Обзор
Документация
Войти
/
kuzant24
/
libre-macros
Код
Запросы
1
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
new_source_processing
macro-lib/pythonpath/segno_bundled.py
6 392 строки
264 KB
direct-dev.ru
fixed for home work
04 авг 2026, 16:06
04 авг 2026, 16:06
eb53100
Код
Авторство
О чём код?
# -*- coding: utf-8 -*- """ segno (QR-коды) в одном файле для LibreOffice pythonpath. Версии: segno 1.6.6 Сборка: python macro-lib/bundle_segno.py Перед import целевых пакетов выполните: import segno_bundled """ from __future__ import annotations MACRO_VERSION = "3.10.359" import importlib.abc import importlib.util import sys _FINDER = None _BUNDLE_TAG = "segno_bundled" _PREFIXES = ('segno',) _SOURCES = { 'segno': { 'is_package': True, 'source': r''' # # Copyright (c) 2016 - 2024 -- Lars Heuer # All rights reserved. # # License: BSD License # """\ QR Code and Micro QR Code implementation. "QR Code" and "Micro QR Code" are registered trademarks of DENSO WAVE INCORPORATED. """ import sys import io from . import encoder from .encoder import DataOverflowError from . import writers, utils __version__ = '1.6.6' __all__ = ('make', 'make_qr', 'make_micro', 'make_sequence', 'QRCode', 'QRCodeSequence', 'DataOverflowError') def make(content, error=None, version=None, mode=None, mask=None, encoding=None, eci=False, micro=None, boost_error=True): """\ Creates a (Micro) QR Code. This is main entry point to create QR Codes and Micro QR Codes. Aside from `content`, all parameters are optional and an optimal (minimal) (Micro) QR code with a maximal error correction level is generated. :param content: The data to encode. Either a Unicode string, an integer or bytes. If bytes are provided, the `encoding` parameter should be used to specify the used encoding. :type content: str, int, bytes :param error: Error correction level. If ``None`` (default), error correction level ``L`` is used (note: Micro QR Code version M1 does not support any error correction. If an explicit error correction level is used, a M1 QR code won't be generated). Valid values: ``None`` (allowing generation of M1 codes or use error correction level "L" or better see :paramref:`boost_error <segno.make.boost_error>`), "L", "M", "Q", "H" (error correction level "H" isn't available for Micro QR Codes). ===================================== =========================== Error correction level Error correction capability ===================================== =========================== L (Segno's default unless version M1) recovers 7% of data M recovers 15% of data Q recovers 25% of data H (not available for Micro QR Codes) recovers 30% of data ===================================== =========================== Higher error levels may require larger QR codes (see also :paramref:`version <segno.make.version>` parameter). The `error` parameter is case insensitive. See also the :paramref:`boost_error <segno.make.boost_error>` parameter. :type error: str or None :param version: QR Code version. If the value is ``None`` (default), the minimal version which fits for the input data will be used. Valid values: "M1", "M2", "M3", "M4" (for Micro QR codes) or an integer between 1 and 40 (for QR codes). The `version` parameter is case insensitive. :type version: int, str or None :param mode: "numeric", "alphanumeric", "byte", "kanji" or "hanzi". If the value is ``None`` (default) the appropriate mode will automatically be determined. If `version` refers to a Micro QR code, this function may raise a :py:exc:`ValueError` if the provided `mode` is not supported. The `mode` parameter is case insensitive. ============ ======================= Mode (Micro) QR Code Version ============ ======================= numeric 1 - 40, M1, M2, M3, M4 alphanumeric 1 - 40, M2, M3, M4 byte 1 - 40, M3, M4 kanji 1 - 40, M3, M4 hanzi 1 - 40 ============ ======================= .. note:: The Hanzi mode may not be supported by all QR code readers since it is not part of ISO/IEC 18004:2015(E). For this reason, this mode must be specified explicitly by the user:: import segno qrcode = segno.make('书读百遍其义自现', mode='hanzi') :type mode: str or None :param mask: Data mask. If the value is ``None`` (default), the appropriate data mask is chosen automatically. If the `mask` parameter is provided, this function may raise a :py:exc:`ValueError` if the mask is invalid. :type mask: int or None :param encoding: Indicates the encoding in mode "byte". By default (`encoding` is ``None``) the implementation tries to use the standard conform ISO/IEC 8859-1 encoding and if it does not fit, it will use UTF-8. Note that no ECI mode indicator is inserted by default (see :paramref:`eci <segno.make.eci>`). The `encoding` parameter is case insensitive. :type encoding: str or None :param bool eci: Indicates if binary data which does not use the default encoding (ISO/IEC 8859-1) should enforce the ECI mode. Since a lot of QR code readers do not support the ECI mode, this feature is disabled by default and the data is encoded in the provided `encoding` using the usual "byte" mode. Set `eci` to ``True`` if an ECI header should be inserted into the QR Code. Note that the implementation may not know the ECI designator for the provided `encoding` and may raise an exception if the ECI designator cannot be found. The ECI mode is not supported by Micro QR Codes. :param micro: If :paramref:`version <segno.make.version>` is ``None`` (default) this parameter can be used to allow the creation of a Micro QR code. If set to ``False``, a QR code is generated. If set to ``None`` (default) a Micro QR code may be generated if applicable. If set to ``True`` the algorithm generates a Micro QR Code or raises an exception if the `mode` is not compatible or the `content` is too large for Micro QR codes. :type micro: bool or None :param bool boost_error: Indicates if the error correction level may be increased if it does not affect the version (default: ``True``). If set to ``True``, the :paramref:`error <segno.make.error>` parameter is interpreted as minimum error level. If set to ``False``, the resulting (Micro) QR code uses the provided `error` level (or the default error correction level, if error is ``None``) :raises: :py:exc:`ValueError` or :py:exc:`DataOverflowError`: In case the data does not fit into a (Micro) QR Code or it does not fit into the provided :paramref:`version`. :rtype: QRCode """ return QRCode(encoder.encode(content, error, version, mode, mask, encoding, eci, micro, boost_error=boost_error)) def make_qr(content, error=None, version=None, mode=None, mask=None, encoding=None, eci=False, boost_error=True): """\ Creates a QR code (never a Micro QR code). See :py:func:`make` for a description of the parameters. :rtype: QRCode """ return make(content, error=error, version=version, mode=mode, mask=mask, encoding=encoding, eci=eci, micro=False, boost_error=boost_error) def make_micro(content, error=None, version=None, mode=None, mask=None, encoding=None, boost_error=True): """\ Creates a Micro QR code. See :py:func:`make` for a description of the parameters. Note: Error correction level "H" isn't available for Micro QR codes. If used, this function raises a :py:class:`segno.ErrorLevelError`. :rtype: QRCode """ return make(content, error=error, version=version, mode=mode, mask=mask, encoding=encoding, micro=True, boost_error=boost_error) def make_sequence(content, error=None, version=None, mode=None, mask=None, encoding=None, boost_error=True, symbol_count=None): """\ Creates a sequence of QR codes using the Structured Append mode. If the content fits into one QR code and neither ``version`` nor ``symbol_count`` is provided, this function may return a sequence with one QR Code which does not use the Structured Append mode. Otherwise a sequence of 2 .. n (max. n = 16) QR codes is returned which use the Structured Append mode. The Structured Append mode allows to split the content over a number (max. 16) QR Codes. The Structured Append mode isn't available for Micro QR Codes, therefor the returned sequence contains QR codes, only. Since this function returns an iterable object, it may be used as follows: .. code-block:: python for i, qrcode in enumerate(segno.make_sequence(data, symbol_count=2)): qrcode.save('seq-%d.svg' % i, scale=10, color='darkblue') The number of QR codes is determined by the `version` or `symbol_count` parameter. See :py:func:`make` for a description of the other parameters. :param int symbol_count: Number of symbols. :rtype: QRCodeSequence """ return QRCodeSequence(map(QRCode, encoder.encode_sequence(content, error=error, version=version, mode=mode, mask=mask, encoding=encoding, boost_error=boost_error, symbol_count=symbol_count))) class QRCode: """\ Represents a (Micro) QR Code. """ __slots__ = ('_error', '_matrix_size', '_mode', '_version', 'mask', 'matrix') def __init__(self, code): """\ Initializes the QR Code object. :param code: An object with a ``matrix``, ``version``, ``error``, ``mask`` and ``segments`` attribute. """ matrix = code.matrix self.matrix = matrix """Returns the matrix. :rtype: tuple of :py:class:`bytearray` instances. """ self.mask = code.mask """Returns the data mask pattern reference :rtype: int """ self._matrix_size = len(matrix[0]), len(matrix) self._version = code.version self._error = code.error self._mode = code.segments[0].mode if len(code.segments) == 1 else None @property def version(self): """\ (Micro) QR Code version. Either a string ("M1", "M2", "M3", "M4") or an integer in the range of 1 .. 40. :rtype: str or int """ return encoder.get_version_name(self._version) @property def error(self): """\ Error correction level; either a string ("L", "M", "Q", "H") or ``None`` if the QR code provides no error correction (Micro QR Code version M1) :rtype: str """ if self._error is None: return None return encoder.get_error_name(self._error) @property def mode(self): """\ String indicating the mode ("numeric", "alphanumeric", "byte", "kanji", or "hanzi"). May be ``None`` if multiple modes are used. :rtype: str or None """ if self._mode is not None: return encoder.get_mode_name(self._mode) return None @property def designator(self): """\ Returns the version and error correction level as string `V-E` where `V` represents the version number and `E` the error level. :rtype: str """ version = str(self.version) return '-'.join((version, self.error) if self.error else (version,)) @property def default_border_size(self): """\ Indicates the default border size aka quiet zone. QR Codes have a quiet zone of four light modules, while Micro QR Codes have a quiet zone of two light modules. :rtype: int """ return utils.get_default_border_size(self._matrix_size) @property def is_micro(self): """\ Indicates if this QR code is a Micro QR code :rtype: bool """ return self._version < 1 def __eq__(self, other): return self.__class__ == other.__class__ and self.matrix == other.matrix __hash__ = None def symbol_size(self, scale=1, border=None): """\ Returns the symbol size (width x height) with the provided border and scaling factor. :param scale: Indicates the size of a single module (default: 1). The size of a module depends on the used output format; i.e. in a PNG context, a scaling factor of 2 indicates that a module has a size of 2 x 2 pixel. Some outputs (i.e. SVG) accept floating point values. :type scale: int or float :param int border: The border size or ``None`` to specify the default quiet zone (4 for QR Codes, 2 for Micro QR Codes). :rtype: tuple (width, height) """ return utils.get_symbol_size(self._matrix_size, scale=scale, border=border) def matrix_iter(self, scale=1, border=None, verbose=False): """\ Returns an iterator over the matrix which includes the border. The border is returned as sequence of light modules. Dark modules are reported as ``0x1``, light modules have the value ``0x0``. The following example converts the QR code matrix into a list of lists which use boolean values for the modules (True = dark module, False = light module):: >>> import segno >>> qrcode = segno.make('The Beatles') >>> width, height = qrcode.symbol_size(scale=2) >>> res = [] >>> # Scaling factor 2, default border >>> for row in qrcode.matrix_iter(scale=2): >>> res.append([col == 0x1 for col in row]) >>> width == len(res[0]) True >>> height == len(res) True If `verbose` is ``True``, the iterator returns integer constants which indicate the type of the module, i.e. ``segno.consts.TYPE_FINDER_PATTERN_DARK``, ``segno.consts.TYPE_FINDER_PATTERN_LIGHT``, ``segno.consts.TYPE_QUIET_ZONE`` etc. To check if the returned module type is dark or light, use:: if mt >> 8: print('dark module') if not mt >> 8: print('light module') :param int scale: The scaling factor (default: ``1``). :param int border: The size of border / quiet zone or ``None`` to indicate the default border. :param bool verbose: Indicates if the type of the module should be returned instead of ``0x1`` and ``0x0`` values. See :py:mod:`segno.consts` for the return values. This feature is currently in EXPERIMENTAL state. :raises: :py:exc:`ValueError` if the scaling factor or the border is invalid (i.e. negative). """ iterfn = utils.matrix_iter_verbose if verbose else utils.matrix_iter return iterfn(self.matrix, self._matrix_size, scale, border) def show(self, delete_after=20, scale=10, border=None, dark='#000', light='#fff'): # pragma: no cover """\ Displays this QR code. This method is mainly intended for debugging purposes. This method saves the QR code as an image (by default with a scaling factor of 10) to a temporary file and opens it with the standard PNG viewer application or within the standard webbrowser. The temporary file is deleted afterwards (unless :paramref:`delete_after <segno.QRCode.show.delete_after>` is set to ``None``). If this method does not show any result, try to increase the :paramref:`delete_after <segno.QRCode.show.delete_after>` value or set it to ``None`` :param delete_after: Time in seconds to wait till the temporary file is deleted. :type delete_after: int or None :param int scale: Integer indicating the size of a single module. :param border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used. :type border: int or None :param dark: The color of the dark modules (default: black). :param light: The color of the light modules (default: white). """ import os import time import tempfile import webbrowser import threading from urllib.parse import urljoin from urllib.request import pathname2url def delete_file(name): time.sleep(delete_after) try: os.unlink(name) except OSError: pass f = tempfile.NamedTemporaryFile('wb', suffix='.png', delete=False) try: self.save(f, scale=scale, dark=dark, light=light, border=border) except: f.close() os.unlink(f.name) raise f.close() webbrowser.open_new_tab(urljoin('file:', pathname2url(f.name))) if delete_after is not None: t = threading.Thread(target=delete_file, args=(f.name,)) t.start() def svg_data_uri(self, xmldecl=False, encode_minimal=False, omit_charset=False, nl=False, **kw): """\ Converts the QR code into an SVG data URI. The XML declaration is omitted by default (set :paramref:`xmldecl <segno.QRCode.svg_data_uri.xmldecl>` to ``True`` to enable it), further the newline is omitted by default (set ``nl`` to ``True`` to enable it). Aside from the missing `out` parameter, the different `xmldecl` and `nl` default values, and the additional parameters :paramref:`encode_minimal <segno.QRCode.svg_data_uri.encode_minimal>` and :paramref:`omit_charset <segno.QRCode.svg_data_uri.omit_charset>`, this method uses the same parameters as the usual SVG serializer, see :py:func:`save` and the available `SVG parameters <#svg>`_ .. note:: In order to embed a SVG image in HTML without generating a file, the :py:func:`svg_inline` method could serve better results, as it usually produces a smaller output. :param bool xmldecl: Indicates if the XML declaration should be serialized (default: ``False``) :param bool encode_minimal: Indicates if the resulting data URI should use minimal percent encoding (disabled by default). :param bool omit_charset: Indicates if the ``;charset=...`` should be omitted (disabled by default) :param bool nl: Indicates if the document should have a trailing newline (default: ``False``) :rtype: str """ return writers.as_svg_data_uri(self.matrix, self._matrix_size, xmldecl=xmldecl, nl=nl, encode_minimal=encode_minimal, omit_charset=omit_charset, **kw) def svg_inline(self, **kw): """\ Returns an SVG representation which is embeddable into HTML5 contexts. Due to the fact that HTML5 directly supports SVG, various elements of an SVG document can or should be suppressed (i.e. the XML declaration and the SVG namespace). This method returns a string that can be used in an HTML context. This method uses the same parameters as the usual SVG serializer, see :py:func:`save` and the available `SVG parameters <#svg>`_ (the ``out`` and ``kind`` parameters are not supported). The returned string can be used directly in `Jinja <https://jinja.palletsprojects.com/>`_ and `Django <https://www.djangoproject.com/>`_ templates, provided the ``safe`` filter is used which marks a string as not requiring further HTML escaping prior to output. :: <div>{{ qr.svg_inline(dark='#228b22', scale=3) | safe }}</div> :rtype: str """ buff = io.BytesIO() self.save(buff, kind='svg', xmldecl=False, svgns=False, nl=False, **kw) return buff.getvalue().decode(kw.get('encoding', 'utf-8')) def png_data_uri(self, **kw): """\ Converts the QR code into a PNG data URI. Uses the same keyword parameters as the usual PNG serializer, see :py:func:`save` and the available `PNG parameters <#png>`_ :rtype: str """ return writers.as_png_data_uri(self.matrix, self._matrix_size, **kw) def terminal(self, out=None, border=None, compact=False): """\ Serializes the matrix as ANSI escape code or Unicode Block Elements (if ``compact`` is ``True``). Under Windows, no ANSI escape sequence is generated but the Windows API is used *unless* :paramref:`out <segno.QRCode.terminal.out>` is a writable object or using WinAPI fails or if ``compact`` is ``True``. :param out: Filename or a file-like object supporting to write text. If ``None`` (default), the matrix is written to :py:class:`sys.stdout`. :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). :param bool compact: Indicates if a more compact QR code should be shown (default: ``False``). """ if compact: writers.write_terminal_compact(self.matrix, self._matrix_size, out or sys.stdout, border) elif out is None and sys.platform == 'win32': # pragma: no cover # Windows < 10 does not support ANSI escape sequences, try to # call the a Windows specific terminal output which uses the # Windows API. try: writers.write_terminal_win(self.matrix, self._matrix_size, border) except OSError: # Use the standard output even if it may print garbage writers.write_terminal(self.matrix, self._matrix_size, sys.stdout, border) else: writers.write_terminal(self.matrix, self._matrix_size, out or sys.stdout, border) def save(self, out, kind=None, **kw): """\ Serializes the QR code in one of the supported formats. The serialization format depends on the filename extension. .. _common_keywords: **Common keywords** ========== ============================================================== Name Description ========== ============================================================== scale Integer or float indicating the size of a single module. Default: 1. The interpretation of the scaling factor depends on the serializer. For pixel-based output (like :ref:`PNG <png>`) the scaling factor is interpreted as pixel-size (1 = 1 pixel). :ref:`EPS <eps>` interprets ``1`` as 1 point (1/72 inch) per module. Some serializers (like :ref:`SVG <svg>`) accept float values. If the serializer does not accept float values, the value will be converted to an integer value (note: int(1.6) == 1). border Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR codes, ``2`` for a Micro QR codes). A value of ``0`` indicates that border should be omitted. dark A string or tuple representing a color value for the dark modules. The default value is "black". The color can be provided as ``(R, G, B)`` tuple, as web color name (like "red") or in hexadecimal format (``#RGB`` or ``#RRGGBB``). Some serializers (i.e. :ref:`SVG <svg>` and :ref:`PNG <png>`) accept an alpha transparency value like ``#RRGGBBAA``. light A string or tuple representing a color for the light modules. See `dark` for valid values. The default value depends on the serializer. :ref:`SVG <svg>` uses no color (``None``) for light modules by default, other serializers, like :ref:`PNG <png>`, use "white" as default light color. ========== ============================================================== .. _module_colors: **Module Colors** =============== ======================================================= Name Description =============== ======================================================= finder_dark Color of the dark modules of the finder patterns Default: undefined, use value of "dark" finder_light Color of the light modules of the finder patterns Default: undefined, use value of "light" data_dark Color of the dark data modules Default: undefined, use value of "dark" data_light Color of the light data modules. Default: undefined, use value of "light". version_dark Color of the dark modules of the version information. Default: undefined, use value of "dark". version_light Color of the light modules of the version information, Default: undefined, use value of "light". format_dark Color of the dark modules of the format information. Default: undefined, use value of "dark". format_light Color of the light modules of the format information. Default: undefined, use value of "light". alignment_dark Color of the dark modules of the alignment patterns. Default: undefined, use value of "dark". alignment_light Color of the light modules of the alignment patterns. Default: undefined, use value of "light". timing_dark Color of the dark modules of the timing patterns. Default: undefined, use value of "dark". timing_light Color of the light modules of the timing patterns. Default: undefined, use value of "light". separator Color of the separator. Default: undefined, use value of "light". dark_module Color of the dark module (a single dark module which occurs in all QR Codes but not in Micro QR Codes. Default: undefined, use value of "dark". quiet_zone Color of the quiet zone / border. Default: undefined, use value of "light". =============== ======================================================= .. _svg: **Scalable Vector Graphics (SVG)** All :ref:`common keywords <common_keywords>` and :ref:`module colors <module_colors>` are supported. ================ ============================================================== Name Description ================ ============================================================== out Filename or :py:class:`io.BytesIO` kind "svg" or "svgz" (to create a gzip compressed SVG) scale integer or float dark Default: "#000" (black) ``None`` is a valid value. If set to ``None``, the resulting path won't have a "stroke" attribute. The "stroke" attribute may be defined via CSS (external). If an alpha channel is defined, the output depends of the used SVG version. For SVG versions >= 2.0, the "stroke" attribute will have a value like "rgba(R, G, B, A)", otherwise the path gets another attribute "stroke-opacity" to emulate the alpha channel. To minimize the document size, the SVG serializer uses automatically the shortest color representation: If a value like "#000000" is provided, the resulting document will have a color value of "#000". If the color is "#FF0000", the resulting color is not "#F00", but the web color name "red". light Default value ``None``. If this parameter is set to another value, the resulting image will have another path which is used to define the color of the light modules. If an alpha channel is used, the resulting path may have a "fill-opacity" attribute (for SVG version < 2.0) or the "fill" attribute has a "rgba(R, G, B, A)" value. xmldecl Boolean value (default: ``True``) indicating whether the document should have an XML declaration header. Set to ``False`` to omit the header. svgns Boolean value (default: ``True``) indicating whether the document should have an explicit SVG namespace declaration. Set to ``False`` to omit the namespace declaration. The latter might be useful if the document should be embedded into a HTML 5 document where the SVG namespace is implicitly defined. title String (default: ``None``) Optional title of the generated SVG document. desc String (default: ``None``) Optional description of the generated SVG document. svgid A string indicating the ID of the SVG document (if set to ``None`` (default), the SVG element won't have an ID). svgclass Default: "segno". The CSS class of the SVG document (if set to ``None``, the SVG element won't have a class). lineclass Default: "qrline". The CSS class of the path element (which draws the dark modules (if set to ``None``, the path won't have a class). omitsize Indicates if width and height attributes should be omitted (default: ``False``). If these attributes are omitted, a ``viewBox`` attribute will be added to the document. unit Default: ``None`` Indicates the unit for width / height and other coordinates. By default, the unit is unspecified and all values are in the user space. Valid values: em, ex, px, pt, pc, cm, mm, in, and percentages (any string is accepted, this parameter is not validated by the serializer) encoding Encoding of the XML document. "utf-8" by default. svgversion SVG version (default: ``None``). If specified (a float), the resulting document has an explicit "version" attribute. If set to ``None``, the document won't have a "version" attribute. This parameter is not validated. compresslevel Default: 9. This parameter is only valid, if a compressed SVG document should be created (file extension "svgz"). 1 is fastest and produces the least compression, 9 is slowest and produces the most. 0 is no compression. draw_transparent Indicates if transparent SVG paths should be added to the graphic (default: ``False``) nl Indicates if the document should have a trailing newline (default: ``True``) ================ ============================================================== .. _png: **Portable Network Graphics (PNG)** This writes either a grayscale (maybe with transparency) PNG (color type 0) or a palette-based (maybe with transparency) image (color type 3). If the dark / light values are ``None``, white or black, the serializer chooses the more compact grayscale mode, in all other cases a palette-based image is written. All :ref:`common keywords <common_keywords>` and :ref:`module colors <module_colors>` are supported. =============== ============================================================== Name Description =============== ============================================================== out Filename or :py:class:`io.BytesIO` kind "png" scale integer dark Default: "#000" (black) ``None`` is a valid value iff light is not ``None``. If set to ``None``, the dark modules become transparent. light Default value "#fff" (white) See keyword "dark" for further details. compresslevel Default: 9. Integer indicating the compression level for the ``IDAT`` (data) chunk. 1 is fastest and produces the least compression, 9 is slowest and produces the most. 0 is no compression. dpi Default: ``None``. Specifies the DPI value for the image. By default, the DPI value is unspecified. Please note that the DPI value is converted into meters (maybe with rounding errors) since PNG does not support the unit "dots per inch". =============== ============================================================== .. _eps: **Encapsulated PostScript (EPS)** All :ref:`common keywords <common_keywords>` are supported. ============= ============================================================== Name Description ============= ============================================================== out Filename or :py:class:`io.StringIO` kind "eps" scale integer or float dark Default: "#000" (black) light Default value: ``None`` (transparent light modules) ============= ============================================================== .. _pdf: **Portable Document Format (PDF)** All :ref:`common keywords <common_keywords>` are supported. ============= ============================================================== Name Description ============= ============================================================== out Filename or :py:class:`io.BytesIO` kind "pdf" scale integer or float dark Default: "#000" (black) light Default value: ``None`` (transparent light modules) compresslevel Default: 9. Integer indicating the compression level. 1 is fastest and produces the least compression, 9 is slowest and produces the most. 0 is no compression. ============= ============================================================== .. _txt: **Text (TXT)** Aside of "scale", all :ref:`common keywords <common_keywords>` are supported. ============= ============================================================== Name Description ============= ============================================================== out Filename or :py:class:`io.StringIO` kind "txt" dark Default: "1" light Default: "0" ============= ============================================================== .. _ansi: **ANSI escape code** Supports the "border" keyword, only! ============= ============================================================== Name Description ============= ============================================================== out Filename or :py:class:`io.StringIO` kind "ans" ============= ============================================================== .. _pbm: **Portable Bitmap (PBM)** All :ref:`common keywords <common_keywords>` are supported. ============= ============================================================== Name Description ============= ============================================================== out Filename or :py:class:`io.BytesIO` kind "pbm" scale integer plain Default: False. Boolean to switch between the P4 and P1 format. If set to ``True``, the (outdated) P1 serialization format is used. ============= ============================================================== .. _pam: **Portable Arbitrary Map (PAM)** All :ref:`common keywords <common_keywords>` are supported. ============= ============================================================== Name Description ============= ============================================================== out Filename or :py:class:`io.BytesIO` kind "pam" scale integer dark Default: "#000" (black). light Default value "#fff" (white). Use ``None`` for transparent light modules. ============= ============================================================== .. _ppm: **Portable Pixmap (PPM)** All :ref:`common keywords <common_keywords>` and :ref:`module colors <module_colors>` are supported. ============= ============================================================== Name Description ============= ============================================================== out Filename or :py:class:`io.BytesIO` kind "ppm" scale integer dark Default: "#000" (black). light Default value "#fff" (white). ============= ============================================================== .. _latex: **LaTeX / PGF/TikZ** To use the output of this serializer, the ``PGF/TikZ`` (and optionally ``hyperref``) package is required in the LaTeX environment. The serializer itself does not depend on any external packages. All :ref:`common keywords <common_keywords>` are supported. ============= ============================================================== Name Description ============= ============================================================== out Filename or :py:class:`io.StringIO` kind "tex" scale integer or float dark LaTeX color name (default: "black"). The color is written "at it is", please ensure that the color is a standard color or it has been defined in the enclosing LaTeX document. url Default: ``None``. Optional URL where the QR code should point to. Requires the ``hyperref`` package in the LaTeX environment. ============= ============================================================== .. _xbm: **X BitMap (XBM)** All :ref:`common keywords <common_keywords>` are supported. ============= ============================================================== Name Description ============= ============================================================== out Filename or :py:class:`io.StringIO` kind "xbm" scale integer name Name of the variable (default: "img") ============= ============================================================== .. _xpm: **X PixMap (XPM)** All :ref:`common keywords <common_keywords>` are supported. ============= ============================================================== Name Description ============= ============================================================== out Filename or :py:class:`io.StringIO` kind "xpm" scale integer dark Default: "#000" (black). ``None`` indicates transparent dark modules. light Default value "#fff" (white) ``None`` indicates transparent light modules. name Name of the variable (default: "img") ============= ============================================================== :param out: A filename or a writable file-like object with a ``name`` attribute. Use the :paramref:`kind <segno.QRCode.save.kind>` parameter if `out` is a :py:class:`io.BytesIO` or :py:class:`io.StringIO` stream which don't have a ``name`` attribute. :param str kind: Default ``None``. If the desired output format cannot be determined from the :paramref:`out <segno.QRCode.save.out>` parameter, this parameter can be used to indicate the serialization format (i.e. "svg" to enforce SVG output). The value is case insensitive. :param kw: Any of the supported keywords by the specific serializer. """ writers.save(self.matrix, self._matrix_size, out, kind, **kw) def __getattr__(self, name): """\ This is used to plug-in external serializers. When a "to_<name>" method is invoked, this method tries to find a ``segno.plugin.converter`` plugin with the provided ``<name>``. If such a plugin exists, a callable function is returned. The result of invoking the function depends on the plugin. """ if name.startswith('to_'): try: # Try to use the 3rd party lib first. This is required for # Python versions < 3.10 import importlib_metadata as metadata except ImportError: from importlib import metadata from functools import partial for ep in metadata.entry_points(group='segno.plugin.converter', name=name[3:]): plugin = ep.load() return partial(plugin, self) raise AttributeError(f'{self.__class__} object has no attribute {name}') class QRCodeSequence(tuple): """\ Represents a sequence of 1 .. n (max. n = 16) :py:class:`QRCode` instances. Iff this sequence contains only one item, it behaves like :py:class:`QRCode`. """ __slots__ = () def __new__(cls, qrcodes): return super().__new__(cls, qrcodes) def terminal(self, out=None, border=None, compact=False): """\ Serializes the sequence of QR codes as ANSI escape code. See :py:meth:`QRCode.terminal()` for details. """ for qrcode in self: qrcode.terminal(out=out, border=border, compact=compact) def save(self, out, kind=None, **kw): """\ Saves the sequence of QR codes to `out`. If `out` is a filename, this method modifies the filename and adds ``<Number of QR codes>-<Current QR code>`` to it. ``structured-append.svg`` becomes (if the sequence contains two QR codes): ``structured-append-02-01.svg`` and ``structured-append-02-02.svg`` Please note that using a file or file-like object may result into an invalid serialization format since all QR codes are written to the same output. See :py:meth:`QRCode.save()` for a detailed enumeration of options. """ filename = lambda o, n: o # noqa: E731 m = len(self) if m > 1 and isinstance(out, str): dot_idx = out.rfind('.') if dot_idx > -1: out = out[:dot_idx] + '-{0:02d}-{1:02d}' + out[dot_idx:] filename = lambda o, n: o.format(m, n) # noqa: E731 for n, qrcode in enumerate(self, start=1): qrcode.save(filename(out, n), kind=kind, **kw) def __getattr__(self, item): """\ Behaves like :py:class:`QRCode` iff this sequence contains a single item. """ if len(self) == 1: return getattr(self[0], item) raise AttributeError(f"{self.__class__} object has no attribute '{item}'") ''', }, 'segno.cli': { 'is_package': False, 'source': r''' #!/usr/bin/env python # # Copyright (c) 2016 - 2024 -- Lars Heuer # All rights reserved. # # License: BSD License # # type: ignore """\ Command line script to generate (Micro) QR codes with Segno. "QR Code" and "Micro QR Code" are registered trademarks of DENSO WAVE INCORPORATED. """ import os import sys import argparse import segno from segno import writers # file extension to supported keywords mapping _EXT_TO_KW_MAPPING = {} def _get_args(func): func_code = func.__code__ args = func_code.co_varnames[:func_code.co_argcount] return args[-len(func.__defaults__):] for ext, func in writers._VALID_SERIALIZERS.items(): kws = set(_get_args(func)) try: kws.update(_get_args(func.__wrapped__)) except AttributeError: pass _EXT_TO_KW_MAPPING[ext] = frozenset(kws) del writers def make_parser(): """\ Returns the command line parser. """ def _convert_scale(val): val = float(val) return val if val != int(val) else int(val) parser = argparse.ArgumentParser(prog='segno', description=f'Segno QR Code and Micro QR Code generator version {segno.__version__}') # noqa: E501 parser.add_argument('--version', '-v', help='(Micro) QR Code version: 1 .. 40 or "M1", "M2", "M3", "M4"', required=False,) parser.add_argument('--error', '-e', help='Error correction level: "L": 7%% (default), "M": 15%%, "Q": 25%%, ' '"H": 30%%, "-": no error correction (used for M1 symbols)', choices=('L', 'M', 'Q', 'H', '-'), default=None, type=lambda x: x.upper()) parser.add_argument('--mode', '-m', help='Mode. If unspecified (default), an optimal mode is chosen for the given ' 'input.', choices=('numeric', 'alphanumeric', 'byte', 'kanji', 'hanzi'), default=None, type=lambda x: x.lower()) parser.add_argument('--encoding', help='Sets the encoding of the input. ' 'If not set (default), a minimal encoding is chosen.', default=None) parser.add_argument('--micro', help='Allow the creation of Micro QR Codes', dest='micro', action='store_true') parser.add_argument('--no-micro', help='Disallow creation of Micro QR Codes (default)', dest='micro', action='store_false') parser.add_argument('--pattern', '-p', help='Mask pattern to use. ' 'If unspecified (default), an optimal mask pattern is used. ' 'Valid values for QR Codes: 0 .. 7. ' 'Valid values for Micro QR Codes: 0 .. 3', required=False, default=None, type=int) parser.add_argument('--no-error-boost', help='Disables the automatic error correction level incrementation. ' 'By default, the maximal error correction level is used ' '(without changing the version).', dest='boost_error', action='store_false') parser.add_argument('--seq', help='Creates a sequence of QR Codes (Structured Append mode). ' 'Version or symbol count must be provided', dest='seq', action='store_true') parser.add_argument('--symbol-count', '-sc', help='Number of symbols to create', default=None, type=int) parser.add_argument('--border', '-b', help='Size of the border / quiet zone of the output. ' 'By default, the standard border (4 modules for QR Codes, ' '2 modules for Micro QR Codes) will be used. ' 'A value of 0 omits the border', default=None, type=int) parser.add_argument('--scale', '-s', help='Scaling factor. By default, a scaling factor of 1 is used. ' 'That may lead into too small images. ' 'Some output formats, i.e. SVG, accept a decimal value.', default=1, type=_convert_scale) parser.add_argument('--output', '-o', help='Output file. If not specified, the QR Code is printed to the terminal', required=False) color_group = parser.add_argument_group('Module Colors', 'Arguments to specify the module colors. ' 'Multiple colors are supported for SVG and PNG. ' 'The module color support varies between the ' 'serialization formats. ' 'Most serializers support at least "--dark" and "--light". ' # noqa: E501 'Unsupported arguments are ignored.') color_group.add_argument('--dark', help='Color of the dark modules. ' 'The color may be specified as web color name, i.e. "red" or ' 'as hexadecimal value, i.e. "#0033cc". ' 'Some serializers, i.e. SVG and PNG, support alpha channels ' '(8-digit hexadecimal value) and some support "transparent" / "trans" as ' 'color value for alpha transparency. ' 'The standard color is black.') color_group.add_argument('--light', help='Color of the light modules. ' 'See "dark" for a description of possible values. ' 'The standard light color is white.') color_group.add_argument('--finder-dark', help='Sets the color of the dark finder modules') color_group.add_argument('--finder-light', help='Sets the color of the light finder modules') color_group.add_argument('--separator', help='Sets the color of the separator modules') color_group.add_argument('--data-dark', help='Sets the color of the dark data modules') color_group.add_argument('--data-light', help='Sets the color of the light data modules') color_group.add_argument('--timing-dark', help='Sets the color of the dark timing modules') color_group.add_argument('--timing-light', help='Sets the color of the light timing modules') color_group.add_argument('--align-dark', help='Sets the color of the dark alignment modules', dest='alignment_dark', ) color_group.add_argument('--align-light', help='Sets the color of the light alignment modules', dest='alignment_light', ) color_group.add_argument('--quiet-zone', help='Sets the color of the quiet zone (border)') color_group.add_argument('--dark-module', help='Sets the color of the dark module') color_group.add_argument('--format-dark', help='Sets the color of the dark format information modules') color_group.add_argument('--format-light', help='Sets the color of the light format information modules') color_group.add_argument('--version-dark', help='Sets the color of the dark version information modules') color_group.add_argument('--version-light', help='Sets the color of the light version information modules') # SVG svg_group = parser.add_argument_group('SVG', 'SVG specific options') svg_group.add_argument('--no-classes', help='Omits the (default) SVG classes', action='store_true') svg_group.add_argument('--no-xmldecl', help='Omits the XML declaration header', dest='xmldecl', action='store_false') svg_group.add_argument('--no-namespace', help='Indicates that the SVG document should have no SVG namespace ' 'declaration', dest='svgns', action='store_false') svg_group.add_argument('--no-newline', help='Indicates that the SVG document should have no trailing newline', dest='nl', action='store_false') svg_group.add_argument('--title', help='Specifies the title of the SVG document') svg_group.add_argument('--desc', help='Specifies the description of the SVG document') svg_group.add_argument('--svgid', help='Indicates the ID of the <svg/> element') svg_group.add_argument('--svgclass', help='Indicates the CSS class of the <svg/> element. ' 'An empty string omits the attribute.') svg_group.add_argument('--lineclass', help='Indicates the CSS class of the <path/> elements. ' 'An empty string omits the attribute.') svg_group.add_argument('--no-size', help='Indicates that the SVG document should not have "width" and "height" ' 'attributes', dest='omitsize', action='store_true') svg_group.add_argument('--unit', help='Indicates SVG coordinate system unit') svg_group.add_argument('--svgversion', help='Indicates the SVG version', type=float) svg_group.add_argument('--svgencoding', help='Specifies the encoding of the document', default='utf-8') svg_group.add_argument('--draw-transparent', help='Indicates that transparent paths should be drawn', action='store_true') # PNG png_group = parser.add_argument_group('PNG', 'PNG specific options') png_group.add_argument('--dpi', help='Sets the DPI value of the PNG file', type=int) # Terminal terminal_group = parser.add_argument_group('Terminal', 'Terminal specific options') terminal_group.add_argument('--compact', help='Indicates that the QR code should be printed in a more compact manner', # noqa: E501 action='store_true') # Show Segno's version --version and -v are taken by QR Code version parser.add_mutually_exclusive_group().add_argument('--ver', '-V', help="Shows Segno's version", action='version', version=f'Segno {segno.__version__}') parser.add_argument('content', nargs='+', help='The content to encode') return parser def parse(args): """\ Parses the arguments and returns the result. """ parser = make_parser() if not len(args): parser.print_help() sys.exit(1) parsed_args = parser.parse_args(args) if parsed_args.error == '-': parsed_args.error = None # 'micro' is False by default. If version is set to a Micro QR Code version, # encoder.encode raises a VersionError. # Small problem: --version=M4 --no-micro is allowed version = parsed_args.version if version is not None: version = str(version).upper() if not parsed_args.micro and version in ('M1', 'M2', 'M3', 'M4'): parsed_args.micro = None return _AttrDict(vars(parsed_args)) def build_config(config, filename=None): """\ Builds a configuration and returns it. The config contains only keywords which are supported by the serializer. Unsupported values are removed. :param dict config: The configuration / dict returned by the :py:func:`parse` function. :param filename: Optional filename. If not ``None`` (default), the `filename` must provide a supported extension to identify the serializer. :return: A (maybe) modified configuration. """ # Done here since it seems not to be possible to detect if an argument # was supplied by the user or if it's the default argument. # If using type=lambda v: None if v in ('transparent', 'trans') else v # we cannot detect if "None" comes from "transparent" or the default value for clr in ('dark', 'light', 'finder_dark', 'finder_light', 'format_dark', 'format_light', 'alignment_dark', 'alignment_light', 'timing_dark', 'timing_light', 'data_dark', 'data_light', 'version_dark', 'version_light', 'quiet_zone', 'dark_module', 'separator'): val = config.pop(clr, None) if val in ('transparent', 'trans'): config[clr] = None elif val: config[clr] = val # SVG for name in ('svgid', 'svgclass', 'lineclass'): if config.get(name, None) is None: config.pop(name, None) if config.pop('no_classes', False): config['svgclass'] = None config['lineclass'] = None # encoding is used to provide the encoding to *create* a QR code config['encoding'] = config.pop('svgencoding', 'utf-8') if filename is not None: ext = filename[filename.rfind('.') + 1:].lower() if ext == 'svgz': # There is no svgz serializer, use same config as svg ext = 'svg' supported_args = _EXT_TO_KW_MAPPING.get(ext, ()) # Drop unsupported arguments from config rather than getting a # "unsupported keyword" exception config = {k: config[k] for k in config if k in supported_args} return config def make_code(config): """\ Creates the (Micro) QR Code (Sequence). Configuration parameters used for creating the Micro QR Code, QR Code or QR Code Sequence are removed from the configuration. :param config: Configuration, see :py:func:`build_config` :return: :py:class:`segno.QRCode` or :py:class:`segno.QRCodeSequence`. """ make = segno.make kw = dict(mode=config.pop('mode'), error=config.pop('error'), version=config.pop('version'), mask=config.pop('pattern'), encoding=config.pop('encoding'), boost_error=config.pop('boost_error')) if config.pop('seq'): make = segno.make_sequence kw['symbol_count'] = config.pop('symbol_count') else: kw['micro'] = config.pop('micro') return make(' '.join(config.pop('content')), **kw) def main(args=sys.argv[1:]): config = parse(args) try: qr = make_code(config) except ValueError as ex: sys.stderr.writelines([str(ex), os.linesep]) return sys.exit(1) output = config.pop('output') if output is None: qr.terminal(border=config['border'], compact=config.get('compact', False)) else: qr.save(output, **build_config(config, filename=output)) return 0 class _AttrDict(dict): """\ Internal helper class. """ def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) self.__dict__ = self if __name__ == '__main__': main() ''', }, 'segno.consts': { 'is_package': False, 'source': r''' # # Copyright (c) 2016 - 2024 -- Lars Heuer # All rights reserved. # # License: BSD License # """\ Constants. Internal module. May change without further warning. """ from collections import namedtuple ALPHANUMERIC_CHARS = br'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:' # ISO/IEC 18004:2015(E) -- Table 2 — Mode indicators for QR Code (page 23) MODE_NUMERIC = 0x1 MODE_ALPHANUMERIC = 0x2 MODE_STRUCTURED_APPEND = 0x3 MODE_BYTE = 0x4 MODE_ECI = 0x7 MODE_KANJI = 0x8 # Hanzi is not part of ISO/IEC 18004 and must be enabled by the user explicitly MODE_HANZI = 0xD # Micro QR Code uses different mode indicators # ISO/IEC 18004:2015(E) -- Table 2 — Mode indicators for QR Code (page 23) MODE_TO_MICRO_MODE_MAPPING = { MODE_NUMERIC: 0x0, MODE_ALPHANUMERIC: 0x1, MODE_BYTE: 0x2, MODE_KANJI: 0x3, } # Rectangular Micro QR Code mode indicators # ISO/IEC 23941:2022(E) -- Table 2 - Mode and Mode indicators for rMQR (page 17) MODE_TO_RECT_MICRO_MODE_MAPPING = { MODE_NUMERIC: 0x1, MODE_ALPHANUMERIC: 0x2, MODE_BYTE: 0x3, MODE_KANJI: 0x64 } # Note: These versions must be comparable: Version 1 > M4 > M3 > M2 > M1 VERSION_M4 = 0 VERSION_M3 = -1 VERSION_M2 = -2 VERSION_M1 = -3 MICRO_VERSION_MAPPING = { 'M1': VERSION_M1, 'M2': VERSION_M2, 'M3': VERSION_M3, 'M4': VERSION_M4, } MICRO_VERSIONS = tuple(sorted(MICRO_VERSION_MAPPING.values())) # ISO/IEC 18004:2015(E) # Table 12 — Error correction level indicators for QR Code symbols (page 55) ERROR_LEVEL_L = 1 ERROR_LEVEL_M = 0 ERROR_LEVEL_Q = 3 ERROR_LEVEL_H = 2 ERROR_LEVEL_TO_MICRO_MAPPING = { VERSION_M1: {None: 0}, VERSION_M2: {ERROR_LEVEL_L: 1, ERROR_LEVEL_M: 2}, VERSION_M3: {ERROR_LEVEL_L: 3, ERROR_LEVEL_M: 4}, VERSION_M4: {ERROR_LEVEL_L: 5, ERROR_LEVEL_M: 6, ERROR_LEVEL_Q: 7}, } DEFAULT_BYTE_ENCODING = 'iso-8859-1' KANJI_ENCODING = 'shift_jis' HANZI_ENCODING = 'gb2312' MODE_MAPPING = { 'numeric': MODE_NUMERIC, 'alphanumeric': MODE_ALPHANUMERIC, 'byte': MODE_BYTE, 'kanji': MODE_KANJI, 'hanzi': MODE_HANZI, } ERROR_MAPPING = { 'L': ERROR_LEVEL_L, 'M': ERROR_LEVEL_M, 'Q': ERROR_LEVEL_Q, 'H': ERROR_LEVEL_H, } # # ISO/IEC 18004:2015(E) -- 7.3.2 Extended Channel Interpretation (ECI) mode (page 20) # # <https://strokescribe.com/en/ECI.html> # ECI Reference # ------ --------- # 000000 Represents the default encodation scheme # 000001 Represents the GLI encodation scheme of a number of symbologies # with characters 0 to 127 being identical to those of # ISO/IEC 646 : 1991 IRV (equivalent to ANSI X3.4) and characters # 128 to 255 being identical to those values of ISO 8859-1 # 000002 An equivalent code table to ECI 000000, without the return-to-GLI 0 # logic. It is the default encodation scheme for encoders fully # compliant with this standard. # 000003 ISO/IEC 8859-1 Latin alphabet No. 1 # 000004 ISO/IEC 8859-2 Latin alphabet No. 2 # 000005 ISO/IEC 8859-3 Latin alphabet No. 3 # 000006 ISO/IEC 8859-4 Latin alphabet No. 4 # 000007 ISO/IEC 8859-5 Latin/Cyrillic alphabet # 000008 ISO/IEC 8859-6 Latin/Arabic alphabet # 000009 ISO/IEC 8859-7 Latin/Greek alphabet # 000010 ISO/IEC 8859-8 Latin/Hebrew alphabet # 000011 ISO/IEC 8859-9 Latin alphabet No. 5 # 000012 ISO/IEC 8859-10 Latin alphabet No. 6 # 000013 ISO/IEC 8859-11 Latin/Thai alphabet # 000014 Reserved # 000015 ISO/IEC 8859-13 Latin alphabet No. 7 (Baltic Rim) # 000016 ISO/IEC 8859-14 Latin alphabet No. 8 (Celtic) # 000017 ISO/IEC 8859-15 Latin alphabet No. 9 # 000018 ISO/IEC 8859-16 Latin alphabet No. 10 # 000019 Reserved # 000020 Shift JIS (JIS X 0208 Annex 1 + JIS X 0201) # 000021 Windows 1250 Latin 2 (Central Europe) # 000022 Windows 1251 Cyrillic # 000023 Windows 1252 Latin 1 # 000024 Windows 1256 Arabic # 000025 ISO/IEC 10646 UCS-2 (High order byte first) # 000026 ISO/IEC 10646 UTF-8 (See information above) # 000027 ISO/IEC 646:1991 International Reference Version of ISO 7-bit # coded character set # 000028 Big 5 (Taiwan) Chinese Character Set # 000029 GB (PRC) Chinese Character Set # 000030 Korean Character Set ECI_ASSIGNMENT_NUM = { # Codecs name (``codecs.lookup(some-charset).name``) -> ECI designator 'cp437': 1, 'iso8859-1': 3, 'iso8859-2': 4, 'iso8859-3': 5, 'iso8859-4': 6, 'iso8859-5': 7, 'iso8859-6': 8, 'iso8859-7': 9, 'iso8859-8': 10, 'iso8859-9': 11, 'iso8859-10': 12, 'iso8859-11': 13, 'iso8859-13': 15, 'iso8859-14': 16, 'iso8859-15': 17, 'iso8859-16': 18, 'shift_jis': 20, 'cp1250': 21, 'cp1251': 22, 'cp1252': 23, 'cp1256': 24, 'utf-16-be': 25, 'utf-8': 26, 'ascii': 27, 'big5': 28, 'gb18030': 29, 'gbk': 29, # GBK is treated as GB-18030 'euc_kr': 30, } # ISO/IEC 18004:2015(E) -- Table 2 — Mode indicators for QR Code (page 23) SUPPORTED_MODES = { MODE_NUMERIC: (None, VERSION_M1, VERSION_M2, VERSION_M3, VERSION_M4), MODE_ALPHANUMERIC: (None, VERSION_M2, VERSION_M3, VERSION_M4), MODE_BYTE: (None, VERSION_M3, VERSION_M4), MODE_ECI: (None,), MODE_KANJI: (None, VERSION_M3, VERSION_M4), MODE_HANZI: (None,), } # ISO/IEC 18004:2015(E) -- Table 2 — Mode indicators for QR Code (page 23) TERMINATOR_LENGTH = { None: 4, # QR Codes, all versions VERSION_M1: 3, VERSION_M2: 5, VERSION_M3: 7, VERSION_M4: 9 } VERSION_RANGE_01_09 = 1 # Version 1 .. 9 VERSION_RANGE_10_26 = 2 # Version 10 .. 26 VERSION_RANGE_27_40 = 3 # Version 27 .. 40 # ISO/IEC 18004:2015(E) # Table 3 — Number of bits in character count indicator for QR Code (page 23) CHAR_COUNT_INDICATOR_LENGTH = { MODE_NUMERIC: { VERSION_RANGE_01_09: 10, VERSION_RANGE_10_26: 12, VERSION_RANGE_27_40: 14, VERSION_M1: 3, VERSION_M2: 4, VERSION_M3: 5, VERSION_M4: 6}, MODE_ALPHANUMERIC: { VERSION_RANGE_01_09: 9, VERSION_RANGE_10_26: 11, VERSION_RANGE_27_40: 13, VERSION_M2: 3, VERSION_M3: 4, VERSION_M4: 5}, MODE_BYTE: { VERSION_RANGE_01_09: 8, VERSION_RANGE_10_26: 16, VERSION_RANGE_27_40: 16, VERSION_M3: 4, VERSION_M4: 5}, MODE_KANJI: { VERSION_RANGE_01_09: 8, VERSION_RANGE_10_26: 10, VERSION_RANGE_27_40: 12, VERSION_M3: 3, VERSION_M4: 4}, MODE_HANZI: { VERSION_RANGE_01_09: 8, VERSION_RANGE_10_26: 10, VERSION_RANGE_27_40: 12}, } # ISO/IEC 18004:2015(E) - 6.4.10 Bit stream to codeword conversion (page 33) # Table 7 — Number of symbol characters and input data capacity for QR Code SYMBOL_CAPACITY = { VERSION_M1: { None: 20}, VERSION_M2: { ERROR_LEVEL_L: 40, ERROR_LEVEL_M: 32}, VERSION_M3: { ERROR_LEVEL_L: 84, ERROR_LEVEL_M: 68}, VERSION_M4: { ERROR_LEVEL_L: 128, ERROR_LEVEL_M: 112, ERROR_LEVEL_Q: 80}, 1: {ERROR_LEVEL_L: 152, ERROR_LEVEL_M: 128, ERROR_LEVEL_Q: 104, ERROR_LEVEL_H: 72}, 2: {ERROR_LEVEL_L: 272, ERROR_LEVEL_M: 224, ERROR_LEVEL_Q: 176, ERROR_LEVEL_H: 128}, 3: {ERROR_LEVEL_L: 440, ERROR_LEVEL_M: 352, ERROR_LEVEL_Q: 272, ERROR_LEVEL_H: 208}, 4: {ERROR_LEVEL_L: 640, ERROR_LEVEL_M: 512, ERROR_LEVEL_Q: 384, ERROR_LEVEL_H: 288}, 5: {ERROR_LEVEL_L: 864, ERROR_LEVEL_M: 688, ERROR_LEVEL_Q: 496, ERROR_LEVEL_H: 368}, 6: {ERROR_LEVEL_L: 1088, ERROR_LEVEL_M: 864, ERROR_LEVEL_Q: 608, ERROR_LEVEL_H: 480}, 7: {ERROR_LEVEL_L: 1248, ERROR_LEVEL_M: 992, ERROR_LEVEL_Q: 704, ERROR_LEVEL_H: 528}, 8: {ERROR_LEVEL_L: 1552, ERROR_LEVEL_M: 1232, ERROR_LEVEL_Q: 880, ERROR_LEVEL_H: 688}, 9: {ERROR_LEVEL_L: 1856, ERROR_LEVEL_M: 1456, ERROR_LEVEL_Q: 1056, ERROR_LEVEL_H: 800}, 10: {ERROR_LEVEL_L: 2192, ERROR_LEVEL_M: 1728, ERROR_LEVEL_Q: 1232, ERROR_LEVEL_H: 976}, 11: {ERROR_LEVEL_L: 2592, ERROR_LEVEL_M: 2032, ERROR_LEVEL_Q: 1440, ERROR_LEVEL_H: 1120}, 12: {ERROR_LEVEL_L: 2960, ERROR_LEVEL_M: 2320, ERROR_LEVEL_Q: 1648, ERROR_LEVEL_H: 1264}, 13: {ERROR_LEVEL_L: 3424, ERROR_LEVEL_M: 2672, ERROR_LEVEL_Q: 1952, ERROR_LEVEL_H: 1440}, 14: {ERROR_LEVEL_L: 3688, ERROR_LEVEL_M: 2920, ERROR_LEVEL_Q: 2088, ERROR_LEVEL_H: 1576}, 15: {ERROR_LEVEL_L: 4184, ERROR_LEVEL_M: 3320, ERROR_LEVEL_Q: 2360, ERROR_LEVEL_H: 1784}, 16: {ERROR_LEVEL_L: 4712, ERROR_LEVEL_M: 3624, ERROR_LEVEL_Q: 2600, ERROR_LEVEL_H: 2024}, 17: {ERROR_LEVEL_L: 5176, ERROR_LEVEL_M: 4056, ERROR_LEVEL_Q: 2936, ERROR_LEVEL_H: 2264}, 18: {ERROR_LEVEL_L: 5768, ERROR_LEVEL_M: 4504, ERROR_LEVEL_Q: 3176, ERROR_LEVEL_H: 2504}, 19: {ERROR_LEVEL_L: 6360, ERROR_LEVEL_M: 5016, ERROR_LEVEL_Q: 3560, ERROR_LEVEL_H: 2728}, 20: {ERROR_LEVEL_L: 6888, ERROR_LEVEL_M: 5352, ERROR_LEVEL_Q: 3880, ERROR_LEVEL_H: 3080}, 21: {ERROR_LEVEL_L: 7456, ERROR_LEVEL_M: 5712, ERROR_LEVEL_Q: 4096, ERROR_LEVEL_H: 3248}, 22: {ERROR_LEVEL_L: 8048, ERROR_LEVEL_M: 6256, ERROR_LEVEL_Q: 4544, ERROR_LEVEL_H: 3536}, 23: {ERROR_LEVEL_L: 8752, ERROR_LEVEL_M: 6880, ERROR_LEVEL_Q: 4912, ERROR_LEVEL_H: 3712}, 24: {ERROR_LEVEL_L: 9392, ERROR_LEVEL_M: 7312, ERROR_LEVEL_Q: 5312, ERROR_LEVEL_H: 4112}, 25: {ERROR_LEVEL_L: 10208, ERROR_LEVEL_M: 8000, ERROR_LEVEL_Q: 5744, ERROR_LEVEL_H: 4304}, 26: {ERROR_LEVEL_L: 10960, ERROR_LEVEL_M: 8496, ERROR_LEVEL_Q: 6032, ERROR_LEVEL_H: 4768}, 27: {ERROR_LEVEL_L: 11744, ERROR_LEVEL_M: 9024, ERROR_LEVEL_Q: 6464, ERROR_LEVEL_H: 5024}, 28: {ERROR_LEVEL_L: 12248, ERROR_LEVEL_M: 9544, ERROR_LEVEL_Q: 6968, ERROR_LEVEL_H: 5288}, 29: {ERROR_LEVEL_L: 13048, ERROR_LEVEL_M: 10136, ERROR_LEVEL_Q: 7288, ERROR_LEVEL_H: 5608}, 30: {ERROR_LEVEL_L: 13880, ERROR_LEVEL_M: 10984, ERROR_LEVEL_Q: 7880, ERROR_LEVEL_H: 5960}, 31: {ERROR_LEVEL_L: 14744, ERROR_LEVEL_M: 11640, ERROR_LEVEL_Q: 8264, ERROR_LEVEL_H: 6344}, 32: {ERROR_LEVEL_L: 15640, ERROR_LEVEL_M: 12328, ERROR_LEVEL_Q: 8920, ERROR_LEVEL_H: 6760}, 33: {ERROR_LEVEL_L: 16568, ERROR_LEVEL_M: 13048, ERROR_LEVEL_Q: 9368, ERROR_LEVEL_H: 7208}, 34: {ERROR_LEVEL_L: 17528, ERROR_LEVEL_M: 13800, ERROR_LEVEL_Q: 9848, ERROR_LEVEL_H: 7688}, 35: {ERROR_LEVEL_L: 18448, ERROR_LEVEL_M: 14496, ERROR_LEVEL_Q: 10288, ERROR_LEVEL_H: 7888}, 36: {ERROR_LEVEL_L: 19472, ERROR_LEVEL_M: 15312, ERROR_LEVEL_Q: 10832, ERROR_LEVEL_H: 8432}, 37: {ERROR_LEVEL_L: 20528, ERROR_LEVEL_M: 15936, ERROR_LEVEL_Q: 11408, ERROR_LEVEL_H: 8768}, 38: {ERROR_LEVEL_L: 21616, ERROR_LEVEL_M: 16816, ERROR_LEVEL_Q: 12016, ERROR_LEVEL_H: 9136}, 39: {ERROR_LEVEL_L: 22496, ERROR_LEVEL_M: 17728, ERROR_LEVEL_Q: 12656, ERROR_LEVEL_H: 9776}, 40: {ERROR_LEVEL_L: 23648, ERROR_LEVEL_M: 18672, ERROR_LEVEL_Q: 13328, ERROR_LEVEL_H: 10208} } # ISO/IEC 23941:2022(E) - 7.4.10 Bit stream to codeword conversion (page 25) # Table 6 — Number of symbol characters and input data capacity for rMQR RSYMBOL_CAPACITY = { 'R7x43': {ERROR_LEVEL_M: 48, ERROR_LEVEL_H: 24}, 'R7x59': {ERROR_LEVEL_M: 96, ERROR_LEVEL_H: 56}, 'R7x77': {ERROR_LEVEL_M: 160, ERROR_LEVEL_H: 80}, 'R7x99': {ERROR_LEVEL_M: 224, ERROR_LEVEL_H: 112}, 'R7x139': {ERROR_LEVEL_M: 352, ERROR_LEVEL_H: 192}, 'R9x43': {ERROR_LEVEL_M: 96, ERROR_LEVEL_H: 56}, 'R9x59': {ERROR_LEVEL_M: 168, ERROR_LEVEL_H: 88}, 'R9x77': {ERROR_LEVEL_M: 248, ERROR_LEVEL_H: 136}, 'R9x99': {ERROR_LEVEL_M: 336, ERROR_LEVEL_H: 176}, 'R9x139': {ERROR_LEVEL_M: 504, ERROR_LEVEL_H: 264}, 'R11x27': {ERROR_LEVEL_M: 56, ERROR_LEVEL_H: 40}, 'R11x43': {ERROR_LEVEL_M: 152, ERROR_LEVEL_H: 88}, 'R11x59': {ERROR_LEVEL_M: 248, ERROR_LEVEL_H: 120}, 'R11x77': {ERROR_LEVEL_M: 344, ERROR_LEVEL_H: 184}, 'R11x99': {ERROR_LEVEL_M: 456, ERROR_LEVEL_H: 232}, 'R11x139': {ERROR_LEVEL_M: 672, ERROR_LEVEL_H: 336}, 'R13x27': {ERROR_LEVEL_M: 96, ERROR_LEVEL_H: 56}, 'R13x43': {ERROR_LEVEL_M: 216, ERROR_LEVEL_H: 104}, 'R13x59': {ERROR_LEVEL_M: 304, ERROR_LEVEL_H: 160}, 'R13x77': {ERROR_LEVEL_M: 424, ERROR_LEVEL_H: 232}, 'R13x99': {ERROR_LEVEL_M: 584, ERROR_LEVEL_H: 280}, 'R13x139': {ERROR_LEVEL_M: 848, ERROR_LEVEL_H: 432}, 'R15x43': {ERROR_LEVEL_M: 264, ERROR_LEVEL_H: 120}, 'R15x59': {ERROR_LEVEL_M: 384, ERROR_LEVEL_H: 208}, 'R15x77': {ERROR_LEVEL_M: 536, ERROR_LEVEL_H: 248}, 'R15x99': {ERROR_LEVEL_M: 704, ERROR_LEVEL_H: 384}, 'R15x139': {ERROR_LEVEL_M: 1016, ERROR_LEVEL_H: 552}, 'R17x43': {ERROR_LEVEL_M: 312, ERROR_LEVEL_H: 168}, 'R17x59': {ERROR_LEVEL_M: 448, ERROR_LEVEL_H: 224}, 'R17x77': {ERROR_LEVEL_M: 624, ERROR_LEVEL_H: 304}, 'R17x99': {ERROR_LEVEL_M: 800, ERROR_LEVEL_H: 448}, 'R17x139': {ERROR_LEVEL_M: 1216, ERROR_LEVEL_H: 608}, } # ISO/IEC 18004:2015(E) -- Table 9 — Error correction characteristics for QR Code (page 38) # ISO/IEC 23941:2022(E) -- Table 8 — Error correction characteristics for rMQR (page 29) EC = namedtuple('EC', 'num_blocks num_total num_data') ECC = { VERSION_M1: {None: (EC(1, 5, 3),)}, VERSION_M2: {ERROR_LEVEL_L: (EC(1, 10, 5),), ERROR_LEVEL_M: (EC(1, 10, 4),)}, VERSION_M3: {ERROR_LEVEL_L: (EC(1, 17, 11),), ERROR_LEVEL_M: (EC(1, 17, 9),)}, VERSION_M4: {ERROR_LEVEL_L: (EC(1, 24, 16),), ERROR_LEVEL_M: (EC(1, 24, 14),), ERROR_LEVEL_Q: (EC(1, 24, 10),)}, 1: { ERROR_LEVEL_L: (EC(1, 26, 19),), ERROR_LEVEL_M: (EC(1, 26, 16),), ERROR_LEVEL_Q: (EC(1, 26, 13),), ERROR_LEVEL_H: (EC(1, 26, 9),)}, 2: { ERROR_LEVEL_L: (EC(1, 44, 34),), ERROR_LEVEL_M: (EC(1, 44, 28),), ERROR_LEVEL_Q: (EC(1, 44, 22),), ERROR_LEVEL_H: (EC(1, 44, 16),)}, 3: { ERROR_LEVEL_L: (EC(1, 70, 55),), ERROR_LEVEL_M: (EC(1, 70, 44),), ERROR_LEVEL_Q: (EC(2, 35, 17),), ERROR_LEVEL_H: (EC(2, 35, 13),)}, 4: { ERROR_LEVEL_L: (EC(1, 100, 80),), ERROR_LEVEL_M: (EC(2, 50, 32),), ERROR_LEVEL_Q: (EC(2, 50, 24),), ERROR_LEVEL_H: (EC(4, 25, 9),)}, 5: { ERROR_LEVEL_L: (EC(1, 134, 108),), ERROR_LEVEL_M: (EC(2, 67, 43),), ERROR_LEVEL_Q: (EC(2, 33, 15), EC(2, 34, 16)), ERROR_LEVEL_H: (EC(2, 33, 11), EC(2, 34, 12))}, 6: { ERROR_LEVEL_L: (EC(2, 86, 68),), ERROR_LEVEL_M: (EC(4, 43, 27),), ERROR_LEVEL_Q: (EC(4, 43, 19),), ERROR_LEVEL_H: (EC(4, 43, 15),)}, 7: { ERROR_LEVEL_L: (EC(2, 98, 78),), ERROR_LEVEL_M: (EC(4, 49, 31),), ERROR_LEVEL_Q: (EC(2, 32, 14), EC(4, 33, 15)), ERROR_LEVEL_H: (EC(4, 39, 13), EC(1, 40, 14))}, 8: { ERROR_LEVEL_L: (EC(2, 121, 97),), ERROR_LEVEL_M: (EC(2, 60, 38), EC(2, 61, 39)), ERROR_LEVEL_Q: (EC(4, 40, 18), EC(2, 41, 19)), ERROR_LEVEL_H: (EC(4, 40, 14), EC(2, 41, 15))}, 9: { ERROR_LEVEL_L: (EC(2, 146, 116),), ERROR_LEVEL_M: (EC(3, 58, 36), EC(2, 59, 37)), ERROR_LEVEL_Q: (EC(4, 36, 16), EC(4, 37, 17)), ERROR_LEVEL_H: (EC(4, 36, 12), EC(4, 37, 13))}, 10: { ERROR_LEVEL_L: (EC(2, 86, 68), EC(2, 87, 69)), ERROR_LEVEL_M: (EC(4, 69, 43), EC(1, 70, 44)), ERROR_LEVEL_Q: (EC(6, 43, 19), EC(2, 44, 20)), ERROR_LEVEL_H: (EC(6, 43, 15), EC(2, 44, 16))}, 11: { ERROR_LEVEL_L: (EC(4, 101, 81),), ERROR_LEVEL_M: (EC(1, 80, 50), EC(4, 81, 51)), ERROR_LEVEL_Q: (EC(4, 50, 22), EC(4, 51, 23)), ERROR_LEVEL_H: (EC(3, 36, 12), EC(8, 37, 13))}, 12: { ERROR_LEVEL_L: (EC(2, 116, 92), EC(2, 117, 93)), ERROR_LEVEL_M: (EC(6, 58, 36), EC(2, 59, 37)), ERROR_LEVEL_Q: (EC(4, 46, 20), EC(6, 47, 21)), ERROR_LEVEL_H: (EC(7, 42, 14), EC(4, 43, 15))}, 13: { ERROR_LEVEL_L: (EC(4, 133, 107),), ERROR_LEVEL_M: (EC(8, 59, 37), EC(1, 60, 38)), ERROR_LEVEL_Q: (EC(8, 44, 20), EC(4, 45, 21)), ERROR_LEVEL_H: (EC(12, 33, 11), EC(4, 34, 12))}, 14: { ERROR_LEVEL_L: (EC(3, 145, 115), EC(1, 146, 116)), ERROR_LEVEL_M: (EC(4, 64, 40), EC(5, 65, 41)), ERROR_LEVEL_Q: (EC(11, 36, 16), EC(5, 37, 17)), ERROR_LEVEL_H: (EC(11, 36, 12), EC(5, 37, 13))}, 15: { ERROR_LEVEL_L: (EC(5, 109, 87), EC(1, 110, 88)), ERROR_LEVEL_M: (EC(5, 65, 41), EC(5, 66, 42)), ERROR_LEVEL_Q: (EC(5, 54, 24), EC(7, 55, 25)), ERROR_LEVEL_H: (EC(11, 36, 12), EC(7, 37, 13))}, 16: { ERROR_LEVEL_L: (EC(5, 122, 98), EC(1, 123, 99)), ERROR_LEVEL_M: (EC(7, 73, 45), EC(3, 74, 46)), ERROR_LEVEL_Q: (EC(15, 43, 19), EC(2, 44, 20)), ERROR_LEVEL_H: (EC(3, 45, 15), EC(13, 46, 16))}, 17: { ERROR_LEVEL_L: (EC(1, 135, 107), EC(5, 136, 108)), ERROR_LEVEL_M: (EC(10, 74, 46), EC(1, 75, 47)), ERROR_LEVEL_Q: (EC(1, 50, 22), EC(15, 51, 23)), ERROR_LEVEL_H: (EC(2, 42, 14), EC(17, 43, 15))}, 18: { ERROR_LEVEL_L: (EC(5, 150, 120), EC(1, 151, 121)), ERROR_LEVEL_M: (EC(9, 69, 43), EC(4, 70, 44)), ERROR_LEVEL_Q: (EC(17, 50, 22), EC(1, 51, 23)), ERROR_LEVEL_H: (EC(2, 42, 14), EC(19, 43, 15))}, 19: { ERROR_LEVEL_L: (EC(3, 141, 113), EC(4, 142, 114)), ERROR_LEVEL_M: (EC(3, 70, 44), EC(11, 71, 45)), ERROR_LEVEL_Q: (EC(17, 47, 21), EC(4, 48, 22)), ERROR_LEVEL_H: (EC(9, 39, 13), EC(16, 40, 14))}, 20: { ERROR_LEVEL_L: (EC(3, 135, 107), EC(5, 136, 108)), ERROR_LEVEL_M: (EC(3, 67, 41), EC(13, 68, 42)), ERROR_LEVEL_Q: (EC(15, 54, 24), EC(5, 55, 25)), ERROR_LEVEL_H: (EC(15, 43, 15), EC(10, 44, 16))}, 21: { ERROR_LEVEL_L: (EC(4, 144, 116), EC(4, 145, 117)), ERROR_LEVEL_M: (EC(17, 68, 42),), ERROR_LEVEL_Q: (EC(17, 50, 22), EC(6, 51, 23)), ERROR_LEVEL_H: (EC(19, 46, 16), EC(6, 47, 17))}, 22: { ERROR_LEVEL_L: (EC(2, 139, 111), EC(7, 140, 112)), ERROR_LEVEL_M: (EC(17, 74, 46),), ERROR_LEVEL_Q: (EC(7, 54, 24), EC(16, 55, 25)), ERROR_LEVEL_H: (EC(34, 37, 13),)}, 23: { ERROR_LEVEL_L: (EC(4, 151, 121), EC(5, 152, 122)), ERROR_LEVEL_M: (EC(4, 75, 47), EC(14, 76, 48)), ERROR_LEVEL_Q: (EC(11, 54, 24), EC(14, 55, 25)), ERROR_LEVEL_H: (EC(16, 45, 15), EC(14, 46, 16))}, 24: { ERROR_LEVEL_L: (EC(6, 147, 117), EC(4, 148, 118)), ERROR_LEVEL_M: (EC(6, 73, 45), EC(14, 74, 46)), ERROR_LEVEL_Q: (EC(11, 54, 24), EC(16, 55, 25)), ERROR_LEVEL_H: (EC(30, 46, 16), EC(2, 47, 17))}, 25: { ERROR_LEVEL_L: (EC(8, 132, 106), EC(4, 133, 107)), ERROR_LEVEL_M: (EC(8, 75, 47), EC(13, 76, 48)), ERROR_LEVEL_Q: (EC(7, 54, 24), EC(22, 55, 25)), ERROR_LEVEL_H: (EC(22, 45, 15), EC(13, 46, 16))}, 26: { ERROR_LEVEL_L: (EC(10, 142, 114), EC(2, 143, 115)), ERROR_LEVEL_M: (EC(19, 74, 46), EC(4, 75, 47)), ERROR_LEVEL_Q: (EC(28, 50, 22), EC(6, 51, 23)), ERROR_LEVEL_H: (EC(33, 46, 16), EC(4, 47, 17))}, 27: { ERROR_LEVEL_L: (EC(8, 152, 122), EC(4, 153, 123)), ERROR_LEVEL_M: (EC(22, 73, 45), EC(3, 74, 46)), ERROR_LEVEL_Q: (EC(8, 53, 23), EC(26, 54, 24)), ERROR_LEVEL_H: (EC(12, 45, 15), EC(28, 46, 16))}, 28: { ERROR_LEVEL_L: (EC(3, 147, 117), EC(10, 148, 118)), ERROR_LEVEL_M: (EC(3, 73, 45), EC(23, 74, 46)), ERROR_LEVEL_Q: (EC(4, 54, 24), EC(31, 55, 25)), ERROR_LEVEL_H: (EC(11, 45, 15), EC(31, 46, 16))}, 29: { ERROR_LEVEL_L: (EC(7, 146, 116), EC(7, 147, 117)), ERROR_LEVEL_M: (EC(21, 73, 45), EC(7, 74, 46)), ERROR_LEVEL_Q: (EC(1, 53, 23), EC(37, 54, 24)), ERROR_LEVEL_H: (EC(19, 45, 15), EC(26, 46, 16))}, 30: { ERROR_LEVEL_L: (EC(5, 145, 115), EC(10, 146, 116)), ERROR_LEVEL_M: (EC(19, 75, 47), EC(10, 76, 48)), ERROR_LEVEL_Q: (EC(15, 54, 24), EC(25, 55, 25)), ERROR_LEVEL_H: (EC(23, 45, 15), EC(25, 46, 16))}, 31: { ERROR_LEVEL_L: (EC(13, 145, 115), EC(3, 146, 116)), ERROR_LEVEL_M: (EC(2, 74, 46), EC(29, 75, 47)), ERROR_LEVEL_Q: (EC(42, 54, 24), EC(1, 55, 25)), ERROR_LEVEL_H: (EC(23, 45, 15), EC(28, 46, 16))}, 32: { ERROR_LEVEL_L: (EC(17, 145, 115),), ERROR_LEVEL_M: (EC(10, 74, 46), EC(23, 75, 47)), ERROR_LEVEL_Q: (EC(10, 54, 24), EC(35, 55, 25)), ERROR_LEVEL_H: (EC(19, 45, 15), EC(35, 46, 16))}, 33: { ERROR_LEVEL_L: (EC(17, 145, 115), EC(1, 146, 116)), ERROR_LEVEL_M: (EC(14, 74, 46), EC(21, 75, 47)), ERROR_LEVEL_Q: (EC(29, 54, 24), EC(19, 55, 25)), ERROR_LEVEL_H: (EC(11, 45, 15), EC(46, 46, 16))}, 34: { ERROR_LEVEL_L: (EC(13, 145, 115), EC(6, 146, 116)), ERROR_LEVEL_M: (EC(14, 74, 46), EC(23, 75, 47)), ERROR_LEVEL_Q: (EC(44, 54, 24), EC(7, 55, 25)), ERROR_LEVEL_H: (EC(59, 46, 16), EC(1, 47, 17))}, 35: { ERROR_LEVEL_L: (EC(12, 151, 121), EC(7, 152, 122)), ERROR_LEVEL_M: (EC(12, 75, 47), EC(26, 76, 48)), ERROR_LEVEL_Q: (EC(39, 54, 24), EC(14, 55, 25)), ERROR_LEVEL_H: (EC(22, 45, 15), EC(41, 46, 16))}, 36: { ERROR_LEVEL_L: (EC(6, 151, 121), EC(14, 152, 122)), ERROR_LEVEL_M: (EC(6, 75, 47), EC(34, 76, 48)), ERROR_LEVEL_Q: (EC(46, 54, 24), EC(10, 55, 25)), ERROR_LEVEL_H: (EC(2, 45, 15), EC(64, 46, 16))}, 37: { ERROR_LEVEL_L: (EC(17, 152, 122), EC(4, 153, 123)), ERROR_LEVEL_M: (EC(29, 74, 46), EC(14, 75, 47)), ERROR_LEVEL_Q: (EC(49, 54, 24), EC(10, 55, 25)), ERROR_LEVEL_H: (EC(24, 45, 15), EC(46, 46, 16))}, 38: { ERROR_LEVEL_L: (EC(4, 152, 122), EC(18, 153, 123)), ERROR_LEVEL_M: (EC(13, 74, 46), EC(32, 75, 47)), ERROR_LEVEL_Q: (EC(48, 54, 24), EC(14, 55, 25)), ERROR_LEVEL_H: (EC(42, 45, 15), EC(32, 46, 16))}, 39: { ERROR_LEVEL_L: (EC(20, 147, 117), EC(4, 148, 118)), ERROR_LEVEL_M: (EC(40, 75, 47), EC(7, 76, 48)), ERROR_LEVEL_Q: (EC(43, 54, 24), EC(22, 55, 25)), ERROR_LEVEL_H: (EC(10, 45, 15), EC(67, 46, 16))}, 40: { ERROR_LEVEL_L: (EC(19, 148, 118), EC(6, 149, 119)), ERROR_LEVEL_M: (EC(18, 75, 47), EC(31, 76, 48)), ERROR_LEVEL_Q: (EC(34, 54, 24), EC(34, 55, 25)), ERROR_LEVEL_H: (EC(20, 45, 15), EC(61, 46, 16))}, 'R7x43': {ERROR_LEVEL_M: 48, ERROR_LEVEL_H: 24}, 'R7x59': {ERROR_LEVEL_M: 96, ERROR_LEVEL_H: 56}, 'R7x77': {ERROR_LEVEL_M: 160, ERROR_LEVEL_H: 80}, 'R7x99': {ERROR_LEVEL_M: 224, ERROR_LEVEL_H: 112}, 'R7x139': {ERROR_LEVEL_M: 352, ERROR_LEVEL_H: 192}, 'R9x43': {ERROR_LEVEL_M: 96, ERROR_LEVEL_H: 56}, 'R9x59': {ERROR_LEVEL_M: 168, ERROR_LEVEL_H: 88}, 'R9x77': {ERROR_LEVEL_M: 248, ERROR_LEVEL_H: 136}, 'R9x99': {ERROR_LEVEL_M: 336, ERROR_LEVEL_H: 176}, 'R9x139': {ERROR_LEVEL_M: 504, ERROR_LEVEL_H: 264}, 'R11x27': {ERROR_LEVEL_M: 56, ERROR_LEVEL_H: 40}, 'R11x43': {ERROR_LEVEL_M: 152, ERROR_LEVEL_H: 88}, 'R11x59': {ERROR_LEVEL_M: 248, ERROR_LEVEL_H: 120}, 'R11x77': {ERROR_LEVEL_M: 344, ERROR_LEVEL_H: 184}, 'R11x99': {ERROR_LEVEL_M: 456, ERROR_LEVEL_H: 232}, 'R11x139': {ERROR_LEVEL_M: 672, ERROR_LEVEL_H: 336}, 'R13x27': {ERROR_LEVEL_M: 96, ERROR_LEVEL_H: 56}, 'R13x43': {ERROR_LEVEL_M: 216, ERROR_LEVEL_H: 104}, 'R13x59': {ERROR_LEVEL_M: 304, ERROR_LEVEL_H: 160}, 'R13x77': {ERROR_LEVEL_M: 424, ERROR_LEVEL_H: 232}, 'R13x99': {ERROR_LEVEL_M: 584, ERROR_LEVEL_H: 280}, 'R13x139': {ERROR_LEVEL_M: 848, ERROR_LEVEL_H: 432}, 'R15x43': {ERROR_LEVEL_M: 264, ERROR_LEVEL_H: 120}, 'R15x59': {ERROR_LEVEL_M: 384, ERROR_LEVEL_H: 208}, 'R15x77': {ERROR_LEVEL_M: 536, ERROR_LEVEL_H: 248}, 'R15x99': {ERROR_LEVEL_M: 704, ERROR_LEVEL_H: 384}, 'R15x139': {ERROR_LEVEL_M: 1016, ERROR_LEVEL_H: 552}, 'R17x43': {ERROR_LEVEL_M: 312, ERROR_LEVEL_H: 168}, 'R17x59': {ERROR_LEVEL_M: 448, ERROR_LEVEL_H: 224}, 'R17x77': {ERROR_LEVEL_M: 624, ERROR_LEVEL_H: 304}, 'R17x99': {ERROR_LEVEL_M: 800, ERROR_LEVEL_H: 448}, 'R17x139': {ERROR_LEVEL_M: 1216, ERROR_LEVEL_H: 608}, } # ISO/IEC 18004:2015 -- Annex C - D.1 Error correction bit calculation # Table C.1 — Valid format information bit sequences (page 80) FORMAT_INFO = ( # M: mask 0, mask 1 .. 7 0x5412, 0x5125, 0x5e7c, 0x5b4b, 0x45f9, 0x40ce, 0x4f97, 0x4aa0, # L 0x77c4, 0x72f3, 0x7daa, 0x789d, 0x662f, 0x6318, 0x6c41, 0x6976, # H 0x1689, 0x13be, 0x1ce7, 0x19d0, 0x0762, 0x0255, 0x0d0c, 0x083b, # Q 0x355f, 0x3068, 0x3f31, 0x3a06, 0x24b4, 0x2183, 0x2eda, 0x2bed, ) FORMAT_INFO_MICRO = ( 0x4445, 0x4172, 0x4e2b, 0x4b1c, 0x55ae, 0x5099, 0x5fc0, 0x5af7, 0x6793, 0x62a4, 0x6dfd, 0x68ca, 0x7678, 0x734f, 0x7c16, 0x7921, 0x06de, 0x03e9, 0x0cb0, 0x0987, 0x1735, 0x1202, 0x1d5b, 0x186c, 0x2508, 0x203f, 0x2f66, 0x2a51, 0x34e3, 0x31d4, 0x3e8d, 0x3bba, ) FORMAT_INFO_RECT_MICRO_LEFT = ( 0x1faB2, 0x1e597, 0x1dBdd, 0x1c4f8, 0x1B86c, 0x1a749, 0x19903, 0x18626, 0x17f0e, 0x1602B, 0x15e61, 0x14144, 0x13dd0, 0x122f5, 0x11cBf, 0x1039a, 0x0f1ca, 0x0eeef, 0x0d0a5, 0x0cf80, 0x0B314, 0x0ac31, 0x0927B, 0x08d5e, 0x07476, 0x06B53, 0x05519, 0x04a3c, 0x036a8, 0x0298d, 0x017c7, 0x008e2, 0x3f367, 0x3ec42, 0x3d208, 0x3cd2d, 0x3B1B9, 0x3ae9c, 0x390d6, 0x38ff3, 0x376dB, 0x369fe, 0x357B4, 0x34891, 0x33405, 0x32B20, 0x3156a, 0x30a4f, 0x2f81f, 0x2e73a, 0x2d970, 0x2c655, 0x2Bac1, 0x2a5e4, 0x29Bae, 0x2848B, 0x27da3, 0x26286, 0x25ccc, 0x243e9, 0x23f7d, 0x22058, 0x21e12, 0x20137 ) FORMAT_INFO_RECT_MICRO_RIGHT = ( 0x20a7b, 0x2155e, 0x22b14, 0x23431, 0x248a5, 0x25780, 0x269ca, 0x276ef, 0x28fc7, 0x290e2, 0x2aea8, 0x2b18d, 0x2cd19, 0x2d23c, 0x2ec76, 0x2f353, 0x30103, 0x31e26, 0x3206c, 0x33f49, 0x343dd, 0x35cf8, 0x362b2, 0x37d97, 0x384bf, 0x39b9a, 0x3a5d0, 0x3baf5, 0x3c661, 0x3d944, 0x3e70e, 0x3f82b, 0x003ae, 0x01c8b, 0x022c1, 0x03de4, 0x04170, 0x05e55, 0x0601f, 0x07f3a, 0x08612, 0x09937, 0x0a77d, 0x0b858, 0x0c4cc, 0x0dbe9, 0x0e5a3, 0x0fa86, 0x108d6, 0x117f3, 0x129b9, 0x1369c, 0x14a08, 0x1552d, 0x16b67, 0x17442, 0x18d6a, 0x1924f, 0x1ac05, 0x1b320, 0x1cfb4, 0x1d091, 0x1eedb, 0x1f1fe ) # ISO/IEC 18004:2015 -- Annex D - D.1 Error correction bit calculation # Table D.1 — Version information bit stream for each version (page 82) VERSION_INFO = ( # Version 7, 8, 9 .. 40 0x07c94, 0x085bc, 0x09a99, 0x0a4d3, 0x0bbf6, 0x0c762, 0x0d847, 0x0e60d, 0x0f928, 0x10b78, 0x1145d, 0x12a17, 0x13532, 0x149a6, 0x15683, 0x168c9, 0x177ec, 0x18ec4, 0x191e1, 0x1afab, 0x1b08e, 0x1cc1a, 0x1d33f, 0x1ed75, 0x1f250, 0x209d5, 0x216f0, 0x228ba, 0x2379f, 0x24b0b, 0x2542e, 0x26a64, 0x27541, 0x28c69, ) # ISO/IEC 18004:2015 -- Annex E - Position of alignment patterns # Table E.1 — Row/column coordinates of center module of alignment patterns (page 83) ALIGNMENT_POS = ( (6, 18), # Version 2 (version 1 has no additional alignment patterns) (6, 22), # Version 3 (6, 26), # .. (6, 30), (6, 34), (6, 22, 38), # Version 7 (6, 24, 42), (6, 26, 46), (6, 28, 50), (6, 30, 54), (6, 32, 58), (6, 34, 62), (6, 26, 46, 66), # Version 14 (6, 26, 48, 70), (6, 26, 50, 74), (6, 30, 54, 78), (6, 30, 56, 82), (6, 30, 58, 86), (6, 34, 62, 90), (6, 28, 50, 72, 94), # Version 21 (6, 26, 50, 74, 98), (6, 30, 54, 78, 102), (6, 28, 54, 80, 106), (6, 32, 58, 84, 110), (6, 30, 58, 86, 114), (6, 34, 62, 90, 118), (6, 26, 50, 74, 98, 122), # Version 28 (6, 30, 54, 78, 102, 126), (6, 26, 52, 78, 104, 130), (6, 30, 56, 82, 108, 134), (6, 34, 60, 86, 112, 138), (6, 30, 58, 86, 114, 142), (6, 34, 62, 90, 118, 146), (6, 30, 54, 78, 102, 126, 150), # Version 35 (6, 24, 50, 76, 102, 128, 154), (6, 28, 54, 80, 106, 132, 158), (6, 32, 58, 84, 110, 136, 162), (6, 26, 54, 82, 110, 138, 166), (6, 30, 58, 86, 114, 142, 170), # Version 40 ) # ISO/IEC 23941:2022(E) -- Annex D - Position of alignment patterns # Table D.1 — Column coordinates of centre module of alignment patterns (page 61) RECT_MICRO_ALIGNMENT_POS = { 43: (21,), 59: (19, 39), 77: (25, 51), 99: (23, 49, 75), 139: (27, 55, 83, 111), } # ISO/IEC 18004:2015 -- Annex A - Error detection and correction generator polynomials # Table A.1 — Generator polynomials for Reed-Solomon error correction codewords (page 73) GEN_POLY = { 2: (25, 1), 5: (113, 164, 166, 119, 10), 6: (166, 0, 134, 5, 176, 15), 7: (87, 229, 146, 149, 238, 102, 21), 8: (175, 238, 208, 249, 215, 252, 196, 28), 10: (251, 67, 46, 61, 118, 70, 64, 94, 32, 45), 13: (74, 152, 176, 100, 86, 100, 106, 104, 130, 218, 206, 140, 78), 14: (199, 249, 155, 48, 190, 124, 218, 137, 216, 87, 207, 59, 22, 91), 15: (8, 183, 61, 91, 202, 37, 51, 58, 58, 237, 140, 124, 5, 99, 105), 16: (120, 104, 107, 109, 102, 161, 76, 3, 91, 191, 147, 169, 182, 194, 225, 120), 17: (43, 139, 206, 78, 43, 239, 123, 206, 214, 147, 24, 99, 150, 39, 243, 163, 136), 18: (215, 234, 158, 94, 184, 97, 118, 170, 79, 187, 152, 148, 252, 179, 5, 98, 96, 153), 20: (17, 60, 79, 50, 61, 163, 26, 187, 202, 180, 221, 225, 83, 239, 156, 164, 212, 212, 188, 190), 22: (210, 171, 247, 242, 93, 230, 14, 109, 221, 53, 200, 74, 8, 172, 98, 80, 219, 134, 160, 105, 165, 231), 24: (229, 121, 135, 48, 211, 117, 251, 126, 159, 180, 169, 152, 192, 226, 228, 218, 111, 0, 117, 232, 87, 96, 227, 21), # noqa: E501 26: (173, 125, 158, 2, 103, 182, 118, 17, 145, 201, 111, 28, 165, 53, 161, 21, 245, 142, 13, 102, 48, 227, 153, 145, 218, 70), # noqa: E501 28: (168, 223, 200, 104, 224, 234, 108, 180, 110, 190, 195, 147, 205, 27, 232, 201, 21, 43, 245, 87, 42, 195, 212, 119, 242, 37, 9, 123), # noqa: E501 30: (41, 173, 145, 152, 216, 31, 179, 182, 50, 48, 110, 86, 239, 96, 222, 125, 42, 173, 226, 193, 224, 130, 156, 37, 251, 216, 238, 40, 192, 180) # noqa: E501 } # Precomputed Galios Log tables # # prime polynomial: 0x11d (285) / generator: 2 # GF(256) log GALIOS_LOG = ( 0, 0, 1, 25, 2, 50, 26, 198, 3, 223, 51, 238, 27, 104, 199, 75, 4, 100, 224, 14, 52, 141, 239, 129, 28, 193, 105, 248, 200, 8, 76, 113, 5, 138, 101, 47, 225, 36, 15, 33, 53, 147, 142, 218, 240, 18, 130, 69, 29, 181, 194, 125, 106, 39, 249, 185, 201, 154, 9, 120, 77, 228, 114, 166, 6, 191, 139, 98, 102, 221, 48, 253, 226, 152, 37, 179, 16, 145, 34, 136, 54, 208, 148, 206, 143, 150, 219, 189, 241, 210, 19, 92, 131, 56, 70, 64, 30, 66, 182, 163, 195, 72, 126, 110, 107, 58, 40, 84, 250, 133, 186, 61, 202, 94, 155, 159, 10, 21, 121, 43, 78, 212, 229, 172, 115, 243, 167, 87, 7, 112, 192, 247, 140, 128, 99, 13, 103, 74, 222, 237, 49, 197, 254, 24, 227, 165, 153, 119, 38, 184, 180, 124, 17, 68, 146, 217, 35, 32, 137, 46, 55, 63, 209, 91, 149, 188, 207, 205, 144, 135, 151, 178, 220, 252, 190, 97, 242, 86, 211, 171, 20, 42, 93, 158, 132, 60, 57, 83, 71, 109, 65, 162, 31, 45, 67, 216, 183, 123, 164, 118, 196, 23, 73, 236, 127, 12, 111, 246, 108, 161, 59, 82, 41, 157, 85, 170, 251, 96, 134, 177, 187, 204, 62, 90, 203, 89, 95, 176, 156, 169, 160, 81, 11, 245, 22, 235, 122, 117, 44, 215, 79, 174, 213, 233, 230, 231, 173, 232, 116, 214, 244, 234, 168, 80, 88, 175 ) # GF(256) antilog # Inverse of the logarithm table. Maps integer logarithms to members # of the field. GALIOS_EXP = ([ 1, 2, 4, 8, 16, 32, 64, 128, 29, 58, 116, 232, 205, 135, 19, 38, 76, 152, 45, 90, 180, 117, 234, 201, 143, 3, 6, 12, 24, 48, 96, 192, 157, 39, 78, 156, 37, 74, 148, 53, 106, 212, 181, 119, 238, 193, 159, 35, 70, 140, 5, 10, 20, 40, 80, 160, 93, 186, 105, 210, 185, 111, 222, 161, 95, 190, 97, 194, 153, 47, 94, 188, 101, 202, 137, 15, 30, 60, 120, 240, 253, 231, 211, 187, 107, 214, 177, 127, 254, 225, 223, 163, 91, 182, 113, 226, 217, 175, 67, 134, 17, 34, 68, 136, 13, 26, 52, 104, 208, 189, 103, 206, 129, 31, 62, 124, 248, 237, 199, 147, 59, 118, 236, 197, 151, 51, 102, 204, 133, 23, 46, 92, 184, 109, 218, 169, 79, 158, 33, 66, 132, 21, 42, 84, 168, 77, 154, 41, 82, 164, 85, 170, 73, 146, 57, 114, 228, 213, 183, 115, 230, 209, 191, 99, 198, 145, 63, 126, 252, 229, 215, 179, 123, 246, 241, 255, 227, 219, 171, 75, 150, 49, 98, 196, 149, 55, 110, 220, 165, 87, 174, 65, 130, 25, 50, 100, 200, 141, 7, 14, 28, 56, 112, 224, 221, 167, 83, 166, 81, 162, 89, 178, 121, 242, 249, 239, 195, 155, 43, 86, 172, 69, 138, 9, 18, 36, 72, 144, 61, 122, 244, 245, 247, 243, 251, 235, 203, 139, 11, 22, 44, 88, 176, 125, 250, 233, 207, 131, 27, 54, 108, 216, 173, 71, 142] * 2 ) # Constants for module types TYPE_FINDER_PATTERN_LIGHT = 6 """\ Light finder module """ TYPE_FINDER_PATTERN_DARK = TYPE_FINDER_PATTERN_LIGHT << 8 """\ Dark finder module. """ TYPE_SEPARATOR = 8 """\ Separator around the finder patterns (light module) """ TYPE_ALIGNMENT_PATTERN_LIGHT = 10 """\ Light alignment pattern module. """ TYPE_ALIGNMENT_PATTERN_DARK = TYPE_ALIGNMENT_PATTERN_LIGHT << 8 """\ Dark alignment pattern module. """ TYPE_TIMING_LIGHT = 12 """\ Light timing pattern module. """ TYPE_TIMING_DARK = TYPE_TIMING_LIGHT << 8 """\ Dark timing patten module. """ TYPE_FORMAT_LIGHT = 14 """\ Light format information module. """ TYPE_FORMAT_DARK = TYPE_FORMAT_LIGHT << 8 """\ Dark format information module. """ TYPE_VERSION_LIGHT = 16 """\ Light version information module. """ TYPE_VERSION_DARK = TYPE_VERSION_LIGHT << 8 """\ Dark version information module. """ TYPE_DARKMODULE = 512 """\ A single dark module which occurs in QR Codes (but not in Micro QR Codes). """ TYPE_DATA_LIGHT = 4 """\ Light module in the encoding area (either a data module or an error correction module). """ TYPE_DATA_DARK = TYPE_DATA_LIGHT << 8 """\ Dark module in the encoding area (either a data module or an error correction module). """ TYPE_QUIET_ZONE = 18 """\ Border of light modules. """ ''', }, 'segno.encoder': { 'is_package': False, 'source': r''' # # Copyright (c) 2016 - 2024 -- Lars Heuer # All rights reserved. # # License: BSD License # """\ QR Code and Micro QR Code encoder. DOES NOT belong to the public API. "QR Code" and "Micro QR Code" are registered trademarks of DENSO WAVE INCORPORATED. """ from operator import itemgetter, gt, lt, xor from functools import partial, reduce from itertools import islice, chain, product import re import math import codecs from collections import namedtuple from . import consts from itertools import zip_longest import sys _MAX_PENALTY_SCORE = sys.maxsize del sys __all__ = ('encode', 'encode_sequence', 'DataOverflowError') class DataOverflowError(ValueError): """\ Indicates a problem that the provided data does not fit into the provided QR Code version or the data is too large in general. This exception is inherited from :py:exc:`ValueError` and is only raised if the data does not fit into the provided (Micro) QR Code version. Basically it is sufficient to catch a :py:exc:`ValueError`. """ Code = namedtuple('Code', 'matrix version error mask segments') def encode(content, error=None, version=None, mode=None, mask=None, encoding=None, eci=False, micro=None, boost_error=True): """\ Creates a (Micro) QR code. See :py:func:`segno.make` for a detailed description of the parameters. Contrary to ``make`` this function returns a named tuple: ``(matrix, version, error, mask, segments)`` Note that ``version`` is always an integer referring to the values of the :py:mod:`segno.consts` constants. ``error`` is ``None`` iff a M1 QR Code was generated, otherwise it is always an integer. :rtype: namedtuple """ version = normalize_version(version) if not micro and micro is not None and version in consts.MICRO_VERSIONS: raise ValueError(f'A Micro QR Code version ("{get_version_name(version)}") ' 'is provided but parameter "micro" is False') if micro and version is not None and version not in consts.MICRO_VERSIONS: raise ValueError(f'Illegal Micro QR Code version "{get_version_name(version)}"') error = normalize_errorlevel(error, accept_none=True) mode = normalize_mode(mode) if mode is not None and version is not None \ and not is_mode_supported(mode, version): raise ValueError(f'Mode "{get_mode_name(mode)}" is not available in version "{get_version_name(version)}"') if error == consts.ERROR_LEVEL_H and (micro or version in consts.MICRO_VERSIONS): raise ValueError('Error correction level "H" is not available for Micro QR Codes') if eci and (micro or version in consts.MICRO_VERSIONS): raise ValueError('The ECI mode is not available for Micro QR Codes') segments = prepare_data(content, mode, encoding) guessed_version = find_version(segments, error, eci=eci, micro=micro) if version is None: version = guessed_version elif guessed_version > version: raise DataOverflowError(f'The provided data does not fit into version "{get_version_name(version)}"' f'Proposal: version {get_version_name(guessed_version)}') if error is None and version != consts.VERSION_M1: error = consts.ERROR_LEVEL_L is_micro = version < 1 mask = normalize_mask(mask, is_micro) return _encode(segments, error, version, mask, eci, boost_error) def encode_sequence(content, error=None, version=None, mode=None, mask=None, encoding=None, eci=False, boost_error=True, symbol_count=None): """\ EXPERIMENTAL: Creates a sequence of QR codes in Structured Append mode. :return: Iterable of named tuples, see :py:func:`encode` for details. """ def one_item_segments(chunk, mode): """\ Creates a Segments sequence with one item. """ segs = Segments() segs.add_segment(make_segment(chunk, mode=mode, encoding=encoding)) return segs def divide_into_chunks(data, num): k, m = divmod(len(data), num) return [data[i * k + min(i, m):(i + 1) * k + min(i + 1, m)] for i in range(num)] def calc_qrcode_bit_length(char_count, ver_range, mode, encoding=None, is_eci=False, is_sa=False): overhead = 4 # Mode indicator for QR Codes, only # Number of bits in character count indicator overhead += consts.CHAR_COUNT_INDICATOR_LENGTH[mode][ver_range] if is_eci and mode == consts.MODE_BYTE and encoding != consts.DEFAULT_BYTE_ENCODING: overhead += 4 # ECI indicator overhead += 8 # ECI assignment no if is_sa: # 4 bit for mode, 4 bit for the position, 4 bit for total number of symbols # 8 bit for parity data overhead += 5 * 4 bits = 0 if mode == consts.MODE_NUMERIC: num, remainder = divmod(char_count, 3) bits += num * 10 + (4 if remainder == 1 else 7) elif mode == consts.MODE_ALPHANUMERIC: num, remainder = divmod(char_count, 2) bits += num * 11 + (6 if remainder else 0) elif mode == consts.MODE_BYTE: bits += char_count * 8 elif mode in (consts.MODE_KANJI, consts.MODE_HANZI): bits += char_count * 13 return overhead + bits def number_of_symbols_by_version(content, version, error, mode): """\ Returns the number of symbols for the provided version. """ length = len(content) ver_range = version_range(version) bit_length = calc_qrcode_bit_length(length, ver_range, mode, encoding, is_eci=eci, is_sa=True) capacity = consts.SYMBOL_CAPACITY[version][error] # Initial result does not contain the overhead of SA mode for all QR Codes cnt = int(math.ceil(bit_length / capacity)) # Overhead of SA mode for all QR Codes bit_length += 5 * 4 * (cnt - 1) + (12 * (cnt - 1) if eci else 0) return int(math.ceil(bit_length / capacity)) version = normalize_version(version) if version is not None: if version < 1: raise ValueError('This function does not accept Micro QR Code versions. ' f'Provided: "{get_version_name(version)}"') elif symbol_count is None: raise ValueError('Please provide either a QR Code version or the symbol count') if symbol_count is not None and not 1 <= symbol_count <= 16: raise ValueError('The symbol count must be in range 1 .. 16') error = normalize_errorlevel(error, accept_none=True) if error is None: error = consts.ERROR_LEVEL_L mode = normalize_mode(mode) mask = normalize_mask(mask, is_micro=False) segments = prepare_data(content, mode, encoding) guessed_version = None if symbol_count is None: try: # Try to find a version which fits without using Structured Append guessed_version = find_version(segments, error, eci=eci, micro=False) except DataOverflowError: # Data does fit into a usual QR Code but ignore the error silently, # guessed_version is None pass if guessed_version and guessed_version <= (version or guessed_version): # Return iterable of size 1 return [_encode(segments, error=error, version=(version or guessed_version), mask=mask, eci=eci, boost_error=boost_error)] if len(segments.modes) > 1: raise ValueError('This function cannot handle more than one mode (yet). Sorry.') mode = segments.modes[0] # CHANGE iff more than one mode is supported! # Creating one QR code failed or max_no is not None if mode == consts.MODE_NUMERIC: content = str(content) if symbol_count is not None and len(content) < symbol_count: raise ValueError(f'The content is not long enough to be divided into {symbol_count} symbols') sa_parity_data = calc_structured_append_parity(content) num_symbols = symbol_count or 16 if version is not None: num_symbols = number_of_symbols_by_version(content, version, error, mode) if num_symbols > 16: raise DataOverflowError(f'The data does not fit into Structured Append version {version}') chunks = divide_into_chunks(content, num_symbols) if symbol_count is not None: segments = one_item_segments(max(chunks, key=len), mode) version = find_version(segments, error, eci=eci, micro=False, is_sa=True) sa_info = partial(_StructuredAppendInfo, total=len(chunks) - 1, parity=sa_parity_data) return [_encode(one_item_segments(chunk, mode), error=error, version=version, mask=mask, eci=eci, boost_error=boost_error, sa_info=sa_info(i)) for i, chunk in enumerate(chunks)] def _encode(segments, error, version, mask, eci, boost_error, sa_info=None): """\ Creates a (Micro) QR code. NOTE: This function does not check if the input is valid and does not belong to the public API. """ is_micro = version < 1 sa_mode = sa_info is not None buff = Buffer() ver = version ver_range = version if not is_micro: ver = None ver_range = version_range(version) if boost_error: error = boost_error_level(version, error, segments, eci, is_sa=sa_mode) if sa_mode: # ISO/IEC 18004:2015(E) -- 8 Structured Append (page 59) for i in sa_info[:3]: buff.append_bits(i, 4) buff.append_bits(sa_info.parity, 8) # ISO/IEC 18004:2015(E) -- 7.4 Data encoding (page 22) for segment in segments: write_segment(buff, segment, ver, ver_range, eci) capacity = consts.SYMBOL_CAPACITY[version][error] # ISO/IEC 18004:2015(E) -- 7.4.9 Terminator (page 32) write_terminator(buff, capacity, ver, len(buff)) # ISO/IEC 18004:2015(E) -- 7.4.10 Bit stream to codeword conversion (page 34) write_padding_bits(buff, version, len(buff)) # ISO/IEC 18004:2015(E) -- 7.4.10 Bit stream to codeword conversion (page 34) write_pad_codewords(buff, version, capacity, len(buff)) # ISO/IEC 18004:2015(E) -- 7.6 Constructing the final message codeword sequence (page 45) buff = make_final_message(version, error, buff) # Matrix with timing pattern and reserved format / version regions width = calc_matrix_size(version) height = width matrix = make_matrix(width, height) # ISO/IEC 18004:2015 -- 6.3.3 Finder pattern (page 16) add_finder_patterns(matrix, width, height) # ISO/IEC 18004:2015 -- 6.3.6 Alignment patterns (page 17) add_alignment_patterns(matrix, width, height) # ISO/IEC 18004:2015 -- 7.7 Codeword placement in matrix (page 46) add_codewords(matrix, buff, version) # ISO/IEC 18004:2015(E) -- 7.8.2 Data mask patterns (page 50) # ISO/IEC 18004:2015(E) -- 7.8.3 Evaluation of data masking results (page 53) mask, matrix = find_and_apply_best_mask(matrix, width, height, mask) # ISO/IEC 18004:2015(E) -- 7.9 Format information (page 55) add_format_info(matrix, version, error, mask) # ISO/IEC 18004:2015(E) -- 7.10 Version information (page 58) add_version_info(matrix, version) return Code(matrix, version, error, mask, segments) def boost_error_level(version, error, segments, eci, is_sa=False): """\ Increases the error correction level if possible. Returns either the provided or a better error correction level which works while keeping the (Micro) QR Code version. :param int version: Version constant. :param int|None error: Error level constant or ``None`` :param Segments segments: Instance of :py:class:`Segments` :param bool eci: Indicates if ECI designator should be written. :param bool is_sa: Indicates if Structured Append mode is used. """ if error not in (consts.ERROR_LEVEL_H, None) and len(segments) == 1: levels = [consts.ERROR_LEVEL_L, consts.ERROR_LEVEL_M, consts.ERROR_LEVEL_Q, consts.ERROR_LEVEL_H] if version < 1: levels.pop() # H isn't support by Micro QR Codes if version < consts.VERSION_M4: levels.pop() # Error level Q isn't supported by M2 and M3 data_length = segments.bit_length_with_overhead(version, eci, is_sa=is_sa) for error_level in levels[levels.index(error) + 1:]: if consts.SYMBOL_CAPACITY[version][error_level] >= data_length: error = error_level else: break return error def write_segment(buff, segment, ver, ver_range, eci=False): """\ Writes a segment. :param buff: The byte buffer. :param _Segment segment: The segment to serialize. :param ver: ``None`` if a QR Code is written, "M1", "M2", "M3", or "M4" if a Micro QR Code is written. :param ver_range: "M1", "M2", "M3", or "M4" if a Micro QR Code is written, otherwise a constant representing a range of QR Code versions. """ mode = segment.mode append_bits = buff.append_bits # Write ECI header if requested if eci and mode == consts.MODE_BYTE \ and segment.encoding != consts.DEFAULT_BYTE_ENCODING: append_bits(consts.MODE_ECI, 4) append_bits(get_eci_assignment_number(segment.encoding), 8) if ver is None: # QR Code append_bits(mode, 4) if mode == consts.MODE_HANZI: subset = 1 # Indicator for GB2312 subset append_bits(subset, 4) elif ver > consts.VERSION_M1: # Micro QR Code (M1 has no mode indicator) append_bits(consts.MODE_TO_MICRO_MODE_MAPPING[mode], ver + 3) # Character count indicator append_bits(segment.char_count, consts.CHAR_COUNT_INDICATOR_LENGTH[mode][ver_range]) buff.extend(segment.bits) def write_terminator(buff, capacity, ver, length): """\ Writes the terminator. :param buff: The byte buffer. :param capacity: Symbol capacity. :param ver: ``None`` if a QR Code is written, "M1", "M2", "M3", or "M4" if a Micro QR Code is written. :param length: Length of the data bit stream. """ # ISO/IEC 18004:2015 -- 7.4.9 Terminator (page 32) buff.extend([0] * min(capacity - length, consts.TERMINATOR_LENGTH[ver])) def write_padding_bits(buff, version, length): """\ Writes padding bits if the data stream does not meet the codeword boundary. :param buff: The byte buffer. :param int length: Data stream length. """ # ISO/IEC 18004:2015(E) - 7.4.10 Bit stream to codeword conversion -- page 32 # [...] # All codewords are 8 bits in length, except for the final data symbol # character in Micro QR Code versions M1 and M3 symbols, which is 4 bits # in length. If the bit stream length is such that it does not end at a # codeword boundary, padding bits with binary value 0 shall be added after # the final bit (least significant bit) of the data stream to extend it # to the codeword boundary. [...] if version not in (consts.VERSION_M1, consts.VERSION_M3): buff.extend([0] * (8 - (length % 8))) def write_pad_codewords(buff, version, capacity, length): """\ Writes the pad codewords iff the data does not fill the capacity of the symbol. :param buff: The byte buffer. :param int version: The (Micro) QR Code version. :param int capacity: The total capacity of the symbol (incl. error correction) :param int length: Length of the data bit stream. """ # ISO/IEC 18004:2015(E) -- 7.4.10 Bit stream to codeword conversion (page 32) # The message bit stream shall then be extended to fill the data capacity # of the symbol corresponding to the Version and Error Correction Level, as # defined in Table 8, by adding the Pad Codewords 11101100 and 00010001 # alternately. For Micro QR Code versions M1 and M3 symbols, the final data # codeword is 4 bits long. The Pad Codeword used in the final data symbol # character position in Micro QR Code versions M1 and M3 symbols shall be # represented as 0000. write = buff.extend if version in (consts.VERSION_M1, consts.VERSION_M3): write([0] * (capacity - length)) else: pad_codewords = ((1, 1, 1, 0, 1, 1, 0, 0), (0, 0, 0, 1, 0, 0, 0, 1)) for i in range(capacity // 8 - length // 8): write(pad_codewords[i % 2]) # Finder pattern (includes separator around each side!) _FINDER_PATTERN = ((0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0), (0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0), (0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0), (0x0, 0x1, 0x0, 0x1, 0x1, 0x1, 0x0, 0x1, 0x0), (0x0, 0x1, 0x0, 0x1, 0x1, 0x1, 0x0, 0x1, 0x0), (0x0, 0x1, 0x0, 0x1, 0x1, 0x1, 0x0, 0x1, 0x0), (0x0, 0x1, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x0), (0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0), (0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)) def add_finder_patterns(matrix, width, height): """\ Adds the finder pattern(s) with the separators to the matrix. QR Codes get three finder patterns, Micro QR Codes have just one finder pattern. ISO/IEC 18004:2015(E) -- 6.3.3 Finder pattern (page 16) ISO/IEC 18004:2015(E) -- 6.3.4 Separator (page 17) :param matrix: The matrix. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. """ is_square = width == height corners = ((0, 0), (0, len(matrix) - 8), (-8, 0)) # Upper left, upper right, bottom left if is_square and width < 21: corners = ((0, 0),) finder_range = range(8) for i, j in corners: offset = 1 if i == 0 else 0 sepoffset = 0 if j != 0 else 1 for r in finder_range: matrix[i + r][j:j + 8] = _FINDER_PATTERN[offset + r][sepoffset:sepoffset + 8] def add_timing_pattern(matrix, is_micro): """\ Adds the (horizontal and vertical) timinig pattern to the provided `matrix`. ISO/IEC 18004:2015(E) -- 6.3.5 Timing pattern (page 17) :param matrix: Matrix to add the timing pattern into. :param bool is_micro: Indicates if the timing pattern for a Micro QR Code should be added. """ j, stop = (0, len(matrix)) if is_micro else (6, len(matrix) - 8) col = matrix[j] bit = 0x1 for i in range(8, stop): matrix[i][j] = bit col[i] = bit bit ^= 0x1 def add_alignment_patterns(matrix, width, height): """\ Adds the adjustment patterns to the matrix. For versions < 2 this is a no-op. ISO/IEC 18004:2015(E) -- 6.3.6 Alignment patterns (page 17) ISO/IEC 18004:2015(E) -- Annex E Position of alignment patterns (page 83) :param matrix: An iterable of bytearrays. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. """ is_square = width == height version = (width - 17) // 4 # QR Codes: version * 4 + 17 == width / height of the matrix w/o border if is_square and version < 2: # QR Codes version < 2 don't have alignment patterns return pattern = (0x1, 0x1, 0x1, 0x1, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x0, 0x1, 0x0, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1, 0x1, 0x1, 0x1, 0x1, 0x1) positions = consts.ALIGNMENT_POS[version - 2] alignment_range = range(5) min_pos = positions[0] max_pos = positions[-1] finder_positions = ((min_pos, min_pos), (min_pos, max_pos), (max_pos, min_pos)) for x, y in product(positions, repeat=2): if (x, y) in finder_positions: continue # The x and y values represent the center of the alignment pattern i, j = x - 2, y - 2 for r in alignment_range: matrix[i + r][j:j + 5] = pattern[r * 5:r * 5 + 5] def add_codewords(matrix, codewords, version): """\ Adds the codewords (data and error correction) to the provided matrix. ISO/IEC 18004:2015(E) -- 7.7.3 Symbol character placement (page 46) :param matrix: The matrix to add the codewords into. :param codewords: Sequence of bits :param int version: The (Micro) QR Code version constant. """ matrix_size = len(matrix) is_micro = version < 1 # Necessary for M1 and M3: The algorithm would start at the upper right # corner, see <https://github.com/heuer/segno/issues/36> inc = 0 if version not in (consts.VERSION_M1, consts.VERSION_M3) else 2 idx = 0 # Pointer to the current codeword # ISO/IEC 18004:2015(E) - page 48 # [...] An alternative method for placement in the symbol [...] is to regard # the interleaved codeword sequence as a single bit stream, which is placed # (starting with the most significant bit) in the two-module wide columns # alternately upwards and downwards from the right to left of the symbol. # [...] codeword_length = len(codewords) range_two = range(2) for right in range(matrix_size - 1, 0, -2): if not is_micro and right <= 6: right -= 1 for vertical in range(matrix_size): for z in range_two: j = right - z upwards = ((right + inc) & 2) == 0 if not is_micro: upwards ^= j < 6 i = (matrix_size - 1 - vertical) if upwards else vertical row = matrix[i] if row[j] == 0x2 and idx < codeword_length: row[j] = codewords[idx] idx += 1 if idx != len(codewords): # pragma: no cover raise ValueError('Internal error: Adding codewords to matrix failed. ' f'Added {idx} of {len(codewords)} codewords') def make_final_message(version, error, buff): """\ Constructs the final message (codewords incl. error correction). ISO/IEC 18004:2015(E) -- 7.6 Constructing the final message codeword sequence (page 45) :param int version: (Micro) QR Code version constant. :param int error: Error level constant. :param buff: Byte buffer. :return: Byte buffer representing the final message. """ def to_binary(val, length=8): return ((val >> i) & 1 for i in reversed(range(length))) ec_infos = consts.ECC[version][error] data_blocks, error_blocks = make_blocks(ec_infos, buff) cw_four = None if version in (consts.VERSION_M1, consts.VERSION_M3): # All codewords are 8 bit by default, M1 and M3 symbols use 4 bits # to represent the last codeword. # datablocks[0] is save since Micro QR Codes use just one datablock and # one error block cw_four = to_binary(data_blocks[0].pop(-1) >> 4, 4) res = Buffer() # Write codewords res.extend(chain(*map(to_binary, (x for x in chain.from_iterable(zip_longest(*data_blocks)) if x is not None)))) if cw_four is not None: res.extend(cw_four) # Write error codewords res.extend(chain(*map(to_binary, (x for x in chain.from_iterable(zip_longest(*error_blocks)) if x is not None)))) # ISO/IEC 18004:2015(E) -- 7.6 Constructing the final message codeword sequence # [...] In certain QR Code versions, however, where the number of modules # available for data and error correction codewords is not an exact multiple # of 8, there may be a need for 3, 4 or 7 Remainder Bits to be appended to # the final message bit stream in order to fill exactly the number of # modules in the encoding region remainder = 0 if version in (2, 3, 4, 5, 6): remainder = 7 elif version in (14, 15, 16, 17, 18, 19, 20, 28, 29, 30, 31, 32, 33, 34): remainder = 3 elif version in (21, 22, 23, 24, 25, 26, 27): remainder = 4 res.extend(b'\0' * remainder) return res def make_blocks(ec_infos, buff): """\ Returns the data and error blocks. :param ec_infos: Iterable of ECC information :param buff: Byte buffer. """ codewords = buff.toints() data_blocks, error_blocks = [], [] append_data_block = data_blocks.append append_error_block = error_blocks.append gen_log = consts.GALIOS_LOG gen_exp = consts.GALIOS_EXP for ec_info in ec_infos: num_error_words = ec_info.num_total - ec_info.num_data gen = consts.GEN_POLY[num_error_words] range_error_words = range(num_error_words) for i in range(ec_info.num_blocks): block = bytearray(islice(codewords, ec_info.num_data)) append_data_block(block) len_data = len(block) error_block = bytearray(block) error_block.extend([0] * num_error_words) # Extended synthetic division, see http://research.swtch.com/field for k in range(len_data): coef = error_block[k] if coef != 0: # log(0) is undefined lcoef = gen_log[coef] for n in range_error_words: error_block[k + n + 1] ^= gen_exp[lcoef + gen[n]] append_error_block(error_block[len_data:]) return data_blocks, error_blocks def find_and_apply_best_mask(matrix, width, height, proposed_mask=None): """\ Applies all mask patterns against the provided QR Code matrix and returns the best matrix and best pattern. ISO/IEC 18004:2015(E) -- 7.8.2 Data mask patterns (page 50) ISO/IEC 18004:2015(E) -- 7.8.3 Evaluation of data masking results (page 53) ISO/IEC 18004:2015(E) -- 7.8.3.1 Evaluation of QR Code symbols (page 53/54) ISO/IEC 18004:2015(E) -- 7.8.3.2 Evaluation of Micro QR Code symbols (page 54/55) :param matrix: A matrix. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param proposed_mask: Optional int to indicate the preferred mask. :rtype: tuple :return: A tuple of the best matrix and best data mask pattern index. """ # ISO/IEC 18004:2015 -- 7.8.3.1 Evaluation of QR Code symbols (page 53/54) # The data mask pattern which results in the lowest penalty score shall # be selected for the symbol. is_better = lt best_score = _MAX_PENALTY_SCORE eval_mask = evaluate_mask is_micro = width == height and width < 21 if is_micro: # ISO/IEC 18004:2015(E) - 7.8.3.2 Evaluation of Micro QR Code symbols (page 54/55) # The data mask pattern which results in the highest score shall be # selected for the symbol. is_better = gt best_score = -1 eval_mask = evaluate_micro_mask # Matrix to check if a module belongs to the encoding region # or to the function patterns function_matrix = make_matrix(width, height) add_finder_patterns(function_matrix, width, height) add_alignment_patterns(function_matrix, width, height) if not is_micro: function_matrix[-8][8] = 0x1 def is_encoding_region(i, j): return function_matrix[i][j] > 0x1 mask_patterns = get_data_mask_functions(is_micro) # If the user supplied a mask pattern, the evaluation step is skipped if proposed_mask is not None: apply_mask(matrix, mask_patterns[proposed_mask], width, height, is_encoding_region) return proposed_mask, matrix best_matrix = None for mask_number, mask_pattern in enumerate(mask_patterns): m = [ba[:] for ba in matrix] apply_mask(m, mask_pattern, width, height, is_encoding_region) # NOTE: DO NOT add format / version info in advance of evaluation # See ISO/IEC 18004:2015(E) -- 7.8. Data masking (page 50) score = eval_mask(m, width, height) if is_better(score, best_score): best_score = score best_pattern = mask_number best_matrix = tuple(m) return best_pattern, best_matrix def apply_mask(matrix, mask_pattern, width, height, is_encoding_region): """\ Applies the provided mask pattern on the `matrix`. ISO/IEC 18004:2015(E) -- 7.8.2 Data mask patterns (page 50) :param tuple matrix: A tuple of bytearrays :param mask_pattern: A mask pattern (a function) :param int matrix_size: width or height of the matrix :param is_encoding_region: A function which returns ``True`` iff the row index / col index belongs to the data region. """ width_range = range(width) for i in range(height): row = matrix[i] for j in width_range: if is_encoding_region(i, j): row[j] ^= mask_pattern(i, j) def evaluate_mask(matrix, width, height): """\ Evaluates the provided `matrix` of a QR code. ISO/IEC 18004:2015(E) -- 7.8.3 Evaluation of data masking results (page 53) :param matrix: The matrix to evaluate :param matrix_size: The width (or height) of the matrix. :return int: The penalty score of the matrix. """ return sum(mask_scores(matrix, width, height)) def mask_scores(matrix, width, height): """\ Returns the penalty score features of the matrix. The returned value is a tuple of all penalty scores (N1, N2, N3, N4). Use :py:func:`evaluate_mask` for a single value (sum of all scores). ISO/IEC 18004:2015(E) -- 7.8.3 Evaluation of data masking results - Table 11 (page 54) ============================================ ==================================== =============== Feature Evaluation condition Points ============================================ ==================================== =============== Adjacent modules in row/column in same color No. of modules = (5 + i) N1 + i Block of modules in same color Block size = m × n N2 ×(m-1)×(n-1) 1 : 1 : 3 : 1 : 1 ratio Existence of the pattern N3 (dark:light:dark:light:dark) pattern in row/column, preceded or followed by light area 4 modules wide Proportion of dark modules in entire symbol 50 × (5 × k)% to 50 × (5 × (k + 1))% N4 × k ============================================ ==================================== =============== N1 = 3 N2 = 3 N3 = 40 N4 = 10 :param matrix: The matrix to evaluate :param matrix_size: The width (or height) of the matrix. :return tuple: A tuple of penalty scores (ints): ``(n1, n2, n3, n4)``. """ # noqa: RUF002 n3_pattern = bytearray((0x1, 0x0, 0x1, 0x1, 0x1, 0x0, 0x1)) def n3_pattern_occurrences(seq): count = 0 idx = seq.find(n3_pattern) while idx != -1: offset = idx + 7 if idx in (0, qr_size - 7) \ or not any(seq[max(idx - 4, 0):min(idx, qr_size)]) \ or not any(seq[max(offset, 0):min(offset + 4, qr_size)]): count += 40 # N3 = 40 else: # Found no / not enough light modules, start at next possible # match: # v # dark light dark dark dark light dark # ^ offset = idx + 4 idx = seq.find(n3_pattern, offset) return count score_n1 = 0 score_n2 = 0 score_n3 = 0 assert width == height qr_size = width qr_module_range = range(qr_size) dark_module_counter = 0 last_row = None # Collects the bytes column-wise (required to calculate score N3) n3_column = bytearray(qr_size) for i in qr_module_range: row = matrix[i] row_prev_bit = -1 col_prev_bit = -1 # N1 n1_row_counter = 0 n1_col_counter = 0 for j in qr_module_range: row_current_bit = row[j] col_current_bit = matrix[j][i] n3_column[j] = col_current_bit dark_module_counter += row_current_bit # N1 -- row-wise if row_current_bit == row_prev_bit: n1_row_counter += 1 else: if n1_row_counter >= 5: score_n1 += n1_row_counter - 2 n1_row_counter = 1 # N1 -- col-wise if col_current_bit == col_prev_bit: n1_col_counter += 1 else: if n1_col_counter >= 5: score_n1 += n1_col_counter - 2 n1_col_counter = 1 # N2 if last_row and j and row_current_bit == row_prev_bit == last_row[j] == last_row[j - 1]: score_n2 += 3 row_prev_bit = row_current_bit col_prev_bit = col_current_bit last_row = row # N3 score_n3 += n3_pattern_occurrences(row) score_n3 += n3_pattern_occurrences(n3_column) # N1 if n1_row_counter >= 5: score_n1 += n1_row_counter - 2 if n1_col_counter >= 5: score_n1 += n1_col_counter - 2 # N4 percent = float(dark_module_counter) / (qr_size ** 2) score_n4 = 10 * int(abs(percent * 100 - 50) / 5) # N4 = 10 return score_n1, score_n2, score_n3, score_n4 def evaluate_micro_mask(matrix, width, height): """\ Evaluates the provided `matrix` of a Micro QR code. ISO/IEC 18004:2015(E) -- 7.8.3.2 Evaluation of Micro QR Code symbols (page 54) :param matrix: The matrix to evaluate :param matrix_size: The width (or height) of the matrix. :return int: The penalty score of the matrix. """ module_range = range(1, width) last_row = matrix[-1] sum1 = sum(matrix[i][-1] for i in module_range) sum2 = sum(last_row[i] for i in module_range) return sum1 * 16 + sum2 if sum1 <= sum2 else sum2 * 16 + sum1 def calc_format_info(version, error, mask_pattern): """\ Returns the format information for the provided error level and mask patttern. ISO/IEC 18004:2015(E) -- 7.9 Format information (page 55) ISO/IEC 18004:2015(E) -- Table C.1 — Valid format information bit sequences (page 80) :param int version: Version constant :param int error: Error level constant. :param int mask_pattern: Mask pattern number. """ fmt = mask_pattern if version > 0: if error == consts.ERROR_LEVEL_L: fmt += 0x08 elif error == consts.ERROR_LEVEL_H: fmt += 0x10 elif error == consts.ERROR_LEVEL_Q: fmt += 0x18 format_info = consts.FORMAT_INFO[fmt] else: fmt += consts.ERROR_LEVEL_TO_MICRO_MAPPING[version][error] << 2 format_info = consts.FORMAT_INFO_MICRO[fmt] return format_info def add_format_info(matrix, version, error, mask_pattern): """\ Adds the format information into the provided matrix. ISO/IEC 18004:2015(E) -- 7.9 Format information (page 55) ISO/IEC 18004:2015(E) -- 7.9.1 QR Code symbols ISO/IEC 18004:2015(E) -- 7.9.2 Micro QR Code symbols :param matrix: The matrix. :param int version: Version constant :param int error: Error level constant. :param int mask_pattern: Mask pattern number. """ # 14: most significant bit # 0: least significant bit # # QR Code format info: Micro QR format info # col 0 col 7 col matrix[-1] col 1 # 0 | | [ ] # 1 0 # 2 1 # 3 2 # 4 3 # 5 4 # [ ] 5 # 6 6 # 14 13 12 11 10 9 [ ] 8 7 ... 7 6 5 4 3 2 1 0 14 13 12 11 10 9 8 7 # # ... # [ ] (dark module) # 8 # 9 # 10 # 11 # 12 # 13 # 14 is_micro = version < 1 format_info = calc_format_info(version, error, mask_pattern) voffset = int(is_micro) hoffset = voffset row_eight = matrix[8] for i in range(8): vbit = (format_info >> i) & 0x01 hbit = (format_info >> (14 - i)) & 0x01 if i == 6 and not is_micro: # Timing pattern voffset += 1 hoffset = 1 # vertical row, upper left corner matrix[i + voffset][8] = vbit # horizontal row, upper left corner row_eight[i + hoffset] = hbit if not is_micro: # horizontal row, upper right corner row_eight[-1 - i] = vbit # vertical row, bottom left corner matrix[-1 - i][8] = hbit if not is_micro: # Dark module matrix[-8][8] = 0x1 def add_version_info(matrix, version): """\ Adds the version information to the matrix, for versions < 7 this is a no-op. ISO/IEC 18004:2015(E) -- 7.10 Version information (page 58) """ # # module 0 = least significant bit # module 17 = most significant bit # # Figure 27 — Version information positioning (page 58) # # Lower left Upper right # ---------- ----------- # 0 3 6 9 12 15 0 1 2 # 1 4 7 10 13 16 3 4 5 # 2 5 8 11 14 17 6 7 8 # 9 10 11 # 12 13 14 # 15 16 17 # if version < 7: return version_info = consts.VERSION_INFO[version - 7] for i in range(6): bit1 = (version_info >> (i * 3)) & 0x01 bit2 = (version_info >> ((i * 3) + 1)) & 0x01 bit3 = (version_info >> ((i * 3) + 2)) & 0x01 # Lower left matrix[-11][i] = bit1 matrix[-10][i] = bit2 matrix[-9][i] = bit3 # Upper right row = matrix[i] row[-11] = bit1 row[-10] = bit2 row[-9] = bit3 def prepare_data(content, mode, encoding): """\ Returns an iterable of `Segment` instances. If `content` is a string, an integer, or bytes, the returned tuple will have a single item. If `content` is a list or a tuple, a tuple of `Segment` instances of the same length is returned. :param content: Either a string, bytes, an integer or an iterable. :type content: str, bytes, int, tuple, list or any iterable :param mode: The global mode. If `content` is list/tuple, the `Segment` instances may have a different mode. :param encoding: The global encoding. If `content` is a list or a tuple, the `Segment` instances may have a different encoding. :rtype: Segments """ segments = Segments() add_segment = segments.add_segment if isinstance(content, (str, bytes, int)): add_segment(make_segment(content, mode, encoding)) return segments for item in content: seg_content, seg_mode, seg_encoding = item, mode, encoding if isinstance(item, tuple): seg_content = item[0] if len(item) > 1: seg_mode = item[1] or mode # item[1] could be None if len(item) > 2: seg_encoding = item[2] or encoding # item[2] may be None add_segment(make_segment(seg_content, seg_mode, seg_encoding)) return segments def data_to_bytes(data, encoding): """\ Converts the provided data into bytes. If the data is already a byte sequence, it will be left unchanged. This function tries to use the provided `encoding` (if not ``None``) or the default encoding (ISO/IEC 8859-1). It uses UTF-8 as fallback. Returns the (byte) data, the length of the data and the encoding of the data. :param data: The data to encode :type data: str or bytes :param encoding: str or ``None`` :rtype: tuple: data, data length, encoding """ if isinstance(data, bytes): return data, len(data), encoding or consts.DEFAULT_BYTE_ENCODING data = str(data) if encoding is not None: # Use the provided encoding; could raise an exception by intention data = data.encode(encoding) else: try: # Try to use the default byte encoding encoding = consts.DEFAULT_BYTE_ENCODING data = data.encode(encoding) except UnicodeError: try: # Try Kanji / Shift_JIS encoding = consts.KANJI_ENCODING data = data.encode(encoding) except UnicodeError: # Use UTF-8 encoding = 'utf-8' data = data.encode(encoding) return data, len(data), encoding def make_segment(data, mode, encoding=None): """\ Creates a :py:class:`Segment`. :param data: The segment data :param mode: The mode. :param encoding: The encoding. :rtype: _Segment """ if mode == consts.MODE_HANZI: encoding = consts.HANZI_ENCODING segment_data, segment_length, segment_encoding = data_to_bytes(data, encoding) segment_mode = mode # If the user prefers BYTE, use BYTE and do not try to find a better mode # Necessary since BYTE < KANJI and find_mode may return KANJI as (more) # appropriate mode and the encoder throws an exception # if "user provided mode" < "found mode" guessed_mode = find_mode(segment_data) if segment_mode != consts.MODE_BYTE else consts.MODE_BYTE if segment_mode is not None: # Check if user provided mode is applicable for the given segment_data if segment_mode < guessed_mode: raise ValueError(f'The provided mode "{get_mode_name(segment_mode)}" ' f'is not applicable for {segment_data!r}. ' f'Proposal: {get_mode_name(guessed_mode)}') else: segment_mode = guessed_mode if segment_mode != consts.MODE_BYTE: segment_encoding = None char_count = segment_length if segment_mode not in (consts.MODE_KANJI, consts.MODE_HANZI) else segment_length // 2 buff = Buffer() append_bits = buff.append_bits if segment_mode == consts.MODE_NUMERIC: # ISO/IEC 18004:2015(E) -- 7.4.3 Numeric mode (page 25) # The input data string is divided into groups of three digits, and # each group is converted to its 10-bit binary equivalent. If the number # of input digits is not an exact multiple of three, the final one or # two digits are converted to 4 or 7 bits respectively. for i in range(0, segment_length, 3): chunk = segment_data[i:i + 3] append_bits(int(chunk), len(chunk) * 3 + 1) elif segment_mode == consts.MODE_ALPHANUMERIC: # ISO/IEC 18004:2015(E) -- 7.4.4 Alphanumeric mode (page 26) to_byte = consts.ALPHANUMERIC_CHARS.find for i in range(0, segment_length, 2): chunk = segment_data[i:i + 2] # Input data characters are divided into groups of two characters # which are encoded as 11-bit binary codes. The character value of # the first character is multiplied by 45 and the character value # of the second digit is added to the product. The sum is then # converted to an 11-bit binary number. if len(chunk) > 1: append_bits(to_byte(chunk[0]) * 45 + to_byte(chunk[1]), 11) else: # If the number of input data characters is not a multiple of # two, the character value of the final character is encoded # as a 6-bit binary number. append_bits(to_byte(chunk), 6) elif segment_mode == consts.MODE_BYTE: # ISO/IEC 18004:2015(E) -- 7.4.5 Byte mode (page 27) for b in segment_data: append_bits(b, 8) elif segment_mode == consts.MODE_HANZI: # GBT 18284-2000 -- 6.4.5 Hanzi mode (page 18) # Note: len(segment.data)! segment.data_length = len(segment.data) / 2!! for i in range(0, segment_length, 2): code = (segment_data[i] << 8) | segment_data[i + 1] if 0xa1a1 <= code <= 0xaafe: # For characters with GB2312 values from A1A1HEX to AAFEHEX: # a) Subtract A1A1HEX from GB2312 value; diff = code - 0xa1a1 elif 0xb0a1 <= code <= 0xfafe: # For characters with GB2312 values from B0A1HEX to FAFEHEX: # a) Subtract A6A1HEX from GB2312 value; diff = code - 0xa6a1 else: # pragma: no cover raise ValueError(f'Invalid Hanzi bytes: {code}') # b) Multiply most significant byte of result by 60HEX; # c) Add least significant byte to product from b); # d) Convert result to a 13-bit binary string. append_bits(((diff >> 8) * 0x60) + (diff & 0xff), 13) else: # ISO/IEC 18004:2015(E) -- 7.4.6 Kanji mode (page 29) for i in range(0, segment_length, 2): code = (segment_data[i] << 8) | segment_data[i + 1] if 0x8140 <= code <= 0x9ffc: # 1. a) For characters with Shift JIS values from 8140HEX to 9FFCHEX: # Subtract 8140HEX from Shift JIS value; diff = code - 0x8140 elif 0xe040 <= code <= 0xebbf: # 2. a) For characters with Shift JIS values from E040HEX to EBBFHEX: # Subtract C140HEX from Shift JIS value; diff = code - 0xc140 else: # pragma: no cover raise ValueError(f'Invalid Kanji bytes: {code}') # b) Multiply most significant byte of result by C0HEX; # c) Add least significant byte to product from b); # d) Convert result to a 13-bit binary string. append_bits(((diff >> 8) * 0xc0) + (diff & 0xff), 13) return _Segment(buff.getbits(), char_count, segment_mode, segment_encoding) def make_matrix(width, height, reserve_regions=True, add_timing=True): """\ Creates a matrix of the provided `size` (w x h) initialized with the (illegal) value 0x2. The "timing pattern" is already added to the matrix and the version and format areas are initialized with 0x0. :param int width: Matrix width :param int height: Matrix height. :rtype: tuple of bytearrays """ is_square = width == height is_micro = is_square and width < 21 row = [0x2] * width matrix = tuple(bytearray(row) for i in range(height)) if reserve_regions: if is_square and width > 41: # QR Codes < version 7 don't have a version pattern # Reserve version pattern areas for i in range(6): row = matrix[i] # Upper right row[-11] = 0x0 row[-10] = 0x0 row[-9] = 0x0 # Lower left matrix[-11][i] = 0x0 matrix[-10][i] = 0x0 matrix[-9][i] = 0x0 # Reserve format pattern areas row_eight = matrix[8] for i in range(9): matrix[i][8] = 0x0 # Upper left row_eight[i] = 0x0 # Upper bottom if not is_micro: matrix[-i][8] = 0x0 # Bottom left row_eight[-i] = 0x0 # Upper right if add_timing: # ISO/IEC 18004:2015 -- 6.3.5 Timing pattern (page 17) add_timing_pattern(matrix, is_micro) return matrix def normalize_version(version): """\ Canonicalization of the provided `version`. If the `version` is ``None``, this function returns ``None``. Otherwise this function checks if `version` is an integer or a Micro QR Code version. In case the string represents a Micro QR Code version, an uppercased string identifier is returned. If the `version` does not represent a valid version identifier (aside of ``None``) a :py:exc:`ValueError` is raised. :param version: An integer, a string or ``None``. :raises: :py:exc:`ValueError`: In case the version is not ``None`` and does not represent a valid (Micro) QR Code version. :rtype: int, str or ``None`` """ if version is None: return None error = False try: version = int(version) # Don't want Micro QR Code constants as input error = version < 1 except (ValueError, TypeError): try: version = consts.MICRO_VERSION_MAPPING[version.upper()] except (KeyError, AttributeError): error = True if error or (not 0 < version < 41 and version not in consts.MICRO_VERSIONS): raise ValueError(f'Unsupported version "{version}". ' f'Supported: {", ".join(sorted(consts.MICRO_VERSION_MAPPING.keys()))} and 1 .. 40') return version def normalize_mode(mode): """\ Returns a (Micro) QR Code mode constant which is equivalent to the provided `mode`. In case the provided `mode` is ``None``, this function returns ``None``. Otherwise a mode constant is returned unless the provided parameter cannot be mapped to a valid mode. In the latter case, a :py:exc:`ValueError` is raised. :param mode: An integer or string or ``None``. :raises: :py:exc:`ValueError` In case the provided `mode` does not represent a valid QR Code mode. :rtype: int or None """ if mode is None or mode in consts.MODE_MAPPING.values(): return mode try: return consts.MODE_MAPPING[mode.lower()] except (KeyError, AttributeError): raise ValueError(f'Illegal mode "{mode}". ' f'Supported values: {", ".join(sorted(consts.MODE_MAPPING.keys()))}') def normalize_mask(mask, is_micro): """\ Normalizes the (user specified) mask. :param mask: A mask constant :type mask: int or None :param bool is_micro: Indicates if the mask is meant to be used for a Micro QR Code. :raises: :py:exc:`ValueError` in case of an invalid mask. :rtype: int """ if mask is None: return None try: mask = int(mask) except ValueError: raise ValueError(f'Invalid data mask "{mask}". ' 'Must be an integer or a string which represents an integer value.') if is_micro: if not 0 <= mask < 4: raise ValueError(f'Invalid data mask "{mask}" for Micro QR Code. Must be in range 0 .. 3') else: if not 0 <= mask < 8: raise ValueError(f'Invalid data mask "{mask}". Must be in range 0 .. 7') return mask def normalize_errorlevel(error, accept_none=False): """\ Returns a constant for the provided error level. This function returns ``None`` if the provided parameter is ``None`` and `accept_none` is set to ``True`` (default: ``False``). If `error` is ``None`` and `accept_none` is ``False`` or if the provided parameter cannot be mapped to a valid QR Code error level, a :py:exc:`ValueError` is raised. :param error: String or ``None``. :param bool accept_none: Indicates if ``None`` is accepted as error level. :raises: :py:exc:`ValueError` in case of an invalid mode. :rtype: int """ if error is None: if not accept_none: raise ValueError('The error level must be provided') return error try: return consts.ERROR_MAPPING[error.upper()] except (KeyError, AttributeError): if error in consts.ERROR_MAPPING.values(): return error raise ValueError(f'Illegal error correction level: "{error}". Supported levels: L, M, Q, H') def get_mode_name(mode_const): """\ Returns the mode name for the provided mode constant. :param int mode_const: The mode constant (see :py:module:`segno.consts`) :raises: :py:exc:`ValueError` in case of an unknown mode constant. :rtype: str """ for name, val in consts.MODE_MAPPING.items(): if val == mode_const: return name raise ValueError(f'Unknown mode "{mode_const}"') def get_error_name(error_const): """\ Returns the error name for the provided error constant. :param int error_const: The error constant (see :py:module:`segno.consts`) :raises: :py:exc:`ValueError` in case of an unknown error correction level. :rtype: str """ for name, val in consts.ERROR_MAPPING.items(): if val == error_const: return name raise ValueError(f'Unknown error level "{error_const}"') def get_version_name(version_const): """\ Returns the version name. For version 1 .. 40 it returns the version as integer, for Micro QR Codes it returns a string like ``M1`` etc. :raises: :py:exc:`VersionError`: In case the `version_constant` is unknown. :rtype: str or int """ if 0 < version_const < 41: return version_const for name, v in consts.MICRO_VERSION_MAPPING.items(): if v == version_const: return name raise ValueError(f'Unknown version constant "{version_const}"') _ALPHANUMERIC_PATTERN = re.compile(br'^[' + re.escape(consts.ALPHANUMERIC_CHARS) + br']+\Z') def is_alphanumeric(data): """\ Returns if the provided `data` can be encoded in "alphanumeric" mode. :param bytes data: The data to check. :rtype: bool """ return _ALPHANUMERIC_PATTERN.match(data) def is_kanji(data): """\ Returns if the `data` can be encoded in "kanji" mode. :param bytes data: The data to check. :rtype: bool """ data_len = len(data) if not data_len or data_len % 2: return False data_iter = iter(data) for i in range(0, data_len, 2): code = (next(data_iter) << 8) | next(data_iter) if not (0x8140 <= code <= 0x9ffc or 0xe040 <= code <= 0xebbf): return False return True def find_mode(data): """\ Returns the appropriate QR Code mode (an integer constant) for the provided `data`. :param bytes data: Data to check. :rtype: int """ if data.isdigit(): return consts.MODE_NUMERIC if is_alphanumeric(data): return consts.MODE_ALPHANUMERIC if is_kanji(data): return consts.MODE_KANJI return consts.MODE_BYTE def find_version(segments, error, eci, micro, is_sa=False): """\ Returns the minimal (Micro) QR Code version constant for the provided input. :param segments: Iterable of Segment instances. :param error: The error correction level constant. :type error: int or None :param bool eci: Indicates if the ECI mode should be used. :param micro: Boolean value if a Micro QR Code should be created or ``None`` :type micro: bool or None :param bool is_sa: Indicator if Structured Append is used. :raises: :py:exc:`ValueError` if the content does not fit into a QR Code. :rtype: int """ assert not (eci and micro) micro_allowed = micro or micro is None min_version = consts.VERSION_M1 if micro_allowed else 1 max_version = consts.VERSION_M4 if micro else 40 if min_version < 1: min_version = max([find_minimum_version_for_mode(mode) for mode in segments.modes]) if error is not None and micro_allowed: min_version = consts.VERSION_M2 for version in range(min_version, max_version + 1): if error is None and version != consts.VERSION_M1: error = consts.ERROR_LEVEL_L try: if consts.SYMBOL_CAPACITY[version][error] >= segments.bit_length_with_overhead(version, eci, is_sa): return version except KeyError: pass help_txt = '' if micro is None: help_txt = '(Micro) ' elif micro: help_txt = 'Micro ' raise DataOverflowError(f'Data too large. No {help_txt}QR Code can handle the provided data') def calc_matrix_size(ver): """\ Returns the matrix size according to the provided `version`. Note: This function does not check if `version` is actually a valid (Micro) QR Code version. Invalid versions like ``41`` may return a size as well. :param int ver: (Micro) QR Code version constant. :rtype: int """ return ver * 4 + 17 if ver > 0 else (ver + 4) * 2 + 9 def calc_structured_append_parity(content): """\ Calculates the parity data for the Structured Append mode. :param str content: The content. :rtype: int """ if not isinstance(content, str): content = str(content) try: data = content.encode('iso-8859-1') except UnicodeError: try: data = content.encode('shift-jis') except (LookupError, UnicodeError): data = content.encode('utf-8') return reduce(xor, data) def is_mode_supported(mode, ver): """\ Returns if `mode` is supported by `version`. Note: This function does not check if `version` is actually a valid (Micro) QR Code version. Invalid versions like ``41`` may return an illegal value. :param int mode: Canonicalized mode. :param int or None ver: (Micro) QR Code version constant. :rtype: bool """ ver = None if ver > 0 else ver try: return ver in consts.SUPPORTED_MODES[mode] except KeyError: raise ValueError(f'Unknown mode "{mode}"') def find_minimum_version_for_mode(mode): """\ Returns the minimum Micro QR Code version which supports the provided mode. :param int mode: Canonicalized mode. :rtype: int """ for v in consts.MICRO_VERSIONS: if is_mode_supported(mode, v): return v return 1 def version_range(version): """\ Returns the version range for the provided version. This applies to QR Code versions, only. :param int version: The QR Code version (1 .. 40) :rtype: int """ # ISO/IEC 18004:2015(E) # Table 3 — Number of bits in character count indicator for QR Code (page 23) if 0 < version < 10: return consts.VERSION_RANGE_01_09 elif 9 < version < 27: return consts.VERSION_RANGE_10_26 elif 26 < version < 41: return consts.VERSION_RANGE_27_40 raise ValueError(f'Unknown version "{version}"') def get_eci_assignment_number(encoding): """\ Returns the ECI number for the provided encoding. :param str encoding: A encoding name :return str: The ECI number. """ try: return consts.ECI_ASSIGNMENT_NUM[codecs.lookup(encoding).name] except KeyError: raise ValueError(f'Unknown ECI assignment number for encoding "{encoding}".') def get_data_mask_functions(is_micro): """ Returns the data mask functions. ISO/IEC 18004:2015(E) -- 7.8.2 Data mask patterns (page 50) Table 10 — Data mask pattern generation conditions (page 50) =============== ===================== ===================================== QR Code Pattern Micro QR Code Pattern Condition =============== ===================== ===================================== 000 (i + j) mod 2 = 0 001 00 i mod 2 = 0 010 j mod 3 = 0 011 (i + j) mod 3 = 0 100 01 ((i div 2) + (j div 3)) mod 2 = 0 101 (i j) mod 2 + (i j) mod 3 = 0 110 10 ((i j) mod 2 + (i j) mod 3) mod 2 = 0 111 11 ((i+j) mod 2 + (i j) mod 3) mod 2 = 0 =============== ===================== ===================================== :param is_micro: Indicates if data mask functions for a Micro QR Code should be returned :return: A tuple of functions """ # i = row position, j = col position; (i, j) = (0, 0) = upper left corner def fn0(i, j): return (i + j) & 0x1 == 0 def fn1(i, j): return i & 0x1 == 0 def fn2(i, j): return j % 3 == 0 def fn3(i, j): return (i + j) % 3 == 0 def fn4(i, j): return (i // 2 + j // 3) & 0x1 == 0 def fn5(i, j): tmp = i * j return (tmp & 0x1) + (tmp % 3) == 0 def fn6(i, j): tmp = i * j return ((tmp & 0x1) + (tmp % 3)) & 0x1 == 0 def fn7(i, j): return (((i + j) & 0x1) + (i * j) % 3) & 0x1 == 0 if is_micro: return fn1, fn4, fn6, fn7 return fn0, fn1, fn2, fn3, fn4, fn5, fn6, fn7 class Segments: """\ Represents a sequence of `Segment` instances. Note: len(segments) returns the number of Segments and not the data length; use segments.data_length """ __slots__ = ('bit_length', 'modes', 'segments') def __init__(self): self.segments = [] self.bit_length = 0 self.modes = [] def add_segment(self, segment): """\ :param _Segment segment: Segment to add. """ if self.segments: prev_seg = self.segments[-1] if prev_seg.mode == segment.mode and prev_seg.encoding == segment.encoding: # Merge segment with previous segment segment = _Segment(prev_seg.bits + segment.bits, prev_seg.char_count + segment.char_count, segment.mode, segment.encoding) self.bit_length -= len(prev_seg.bits) del self.segments[-1] del self.modes[-1] self.segments.append(segment) self.bit_length += len(segment.bits) self.modes.append(segment.mode) def __len__(self): return len(self.segments) def __getitem__(self, item): return self.segments[item] def __iter__(self): return iter(self.segments) def bit_length_with_overhead(self, version, eci, is_sa=False): overhead = 0 # ECI overhead if eci: no_eci_indicators = sum(1 for segment in self.segments if segment.mode == consts.MODE_BYTE and segment.encoding != consts.DEFAULT_BYTE_ENCODING) overhead += no_eci_indicators * 4 # ECI indicator overhead += no_eci_indicators * 8 # ECI assignment no if is_sa: # 4 bit for mode, 4 bit for the position, 4 bit for total number of symbols # 8 bit for parity data overhead += 5 * 4 # Mode indicator overhead if version > 0: # QR Code overhead += len(self.modes) * 4 elif version > consts.VERSION_M1: # Micro QR Code (M1 has no mode indicator) overhead += len(self.modes) * (version + 3) # Char count indicator overhead ver_range = version_range(version) if version > 0 else version overhead += sum(consts.CHAR_COUNT_INDICATOR_LENGTH[mode][ver_range] for mode in self.modes) return overhead + self.bit_length class _Segment(tuple): """\ Represents a data segment. A segment provides the (encoding specific) byte data, the data length, the QR Code mode, and the used encoding. The latter is ``None`` iff mode is not "byte". Note that `data_length` may not be equal to len(data)! See also ISO/IEC 18004:2015(E) - 7.4.7 Mixing modes (page 30) """ __slots__ = () def __new__(cls, bits, char_count, mode, encoding=None): return tuple.__new__(cls, (bits, char_count, mode, encoding)) bits = property(itemgetter(0)) char_count = property(itemgetter(1)) mode = property(itemgetter(2)) encoding = property(itemgetter(3)) class Buffer: """\ Wraps a :cls:`bytearray` and provides some useful methods to add bits. """ __slots__ = ['_data'] def __init__(self, iterable=()): self._data = bytearray(iterable) def extend(self, iterable): self._data.extend(iterable) def append_bits(self, val, length): self._data.extend((val >> i) & 1 for i in reversed(range(length))) def getbits(self): return self._data def toints(self): """\ Returns an iterable of integers interpreting the content of `seq` as sequence of binary numbers of length 8. """ return (int(''.join(map(str, g)), 2) for g in zip_longest(*[iter(self._data)] * 8, fillvalue=0)) def __len__(self): return len(self._data) def __getitem__(self, item): return self._data[item] class _StructuredAppendInfo(tuple): """\ Represents Structured Append information. Note: This class provides the Structured Append header information in correct order (incl. Structured Append mode indicator); cf. ISO/IEC 18004:2015(E) -- 8 Structured Append (page 59). """ __slots__ = () def __new__(cls, number, total, parity): """\ :param int number: Symbol number ``[0 .. 15]`` :param int total: Total symbol count ``[2 .. 15]`` :param int parity: Parity data. """ return super().__new__(cls, (consts.MODE_STRUCTURED_APPEND, number, total, parity)) mode = property(itemgetter(0)) number = property(itemgetter(1)) total = property(itemgetter(2)) parity = property(itemgetter(3)) ''', }, 'segno.helpers': { 'is_package': False, 'source': r''' # # Copyright (c) 2016 - 2024 -- Lars Heuer # All rights reserved. # # License: BSD License # """\ Additional factory functions for common QR codes. Aside from :py:func:`make_epc_qr`, the factory functions return a QR code with the minimum error correction level "L" (or better). To create a (Micro) QR code which should use a specific error correction level or version etc., use the "_data" factory functions which return a string which can be used as input for :py:func:`segno.make()`. """ import re import decimal import segno from urllib.parse import quote _MECARD_ESCAPE = { ord('\\'): "\\\\", ord(';'): "\\;", ord(':'): "\\:", ord('"'): '\\"', } _VCARD_ESCAPE = { ord(','): '\\,', ord(';'): '\\;', } def _escape_mecard(s): """\ Escapes ``\\``, ``;``, ``"`` and ``:`` in the provided string. :param str s: The string to escape. :rtype str """ return str(s).translate(_MECARD_ESCAPE) def _escape_vcard(s): """\ Escapes ``\\``, ``;``, ``"`` and ``:`` in the provided string. :param str s: The string to escape. :rtype str """ return str(s).translate(_VCARD_ESCAPE) def make_wifi_data(ssid, password=None, security=None, hidden=False): """\ Creates WIFI configuration string. :param str ssid: The SSID of the network. :param password: The password. :type password: str or None :param security: Authentication type; the value should be "WEP" or "WPA". Set to ``None`` to omit the value. "nopass" is equivalent to setting the value to ``None`` but in the former case, the value is not omitted. :type security: str or None :param bool hidden: Indicates if the network is hidden (default: ``False``) :rtype: str """ escape = _escape_mecard data = 'WIFI:' if security: data += f'T:{security.upper() if security != "nopass" else security};' data += f'S:{escape(ssid)};' if password is not None: data += f'P:{escape(password)};' data += 'H:true;' if hidden else ';' return data def make_wifi(ssid, password=None, security=None, hidden=False): """\ Creates a WIFI configuration QR code. :param str ssid: The SSID of the network. :param password: The password. :type password: str or None :param security: Authentication type; the value should be "WEP" or "WPA". Set to ``None`` to omit the value. "nopass" is equivalent to setting the value to ``None`` but in the former case, the value is not omitted. :type security: str or None :param bool hidden: Indicates if the network is hidden (default: ``False``) :rtype: segno.QRCode """ return segno.make_qr(make_wifi_data(ssid, password, security, hidden)) def make_mecard_data(name, reading=None, email=None, phone=None, videophone=None, memo=None, nickname=None, birthday=None, url=None, pobox=None, roomno=None, houseno=None, city=None, prefecture=None, zipcode=None, country=None): """\ Creates a string encoding the contact information as MeCard. :param str name: Name. If it contains a comma, the first part is treated as lastname and the second part is treated as forename. :param reading: Designates a text string to be set as the kana name in the phonebook :type reading: str or None :param email: E-mail address. Multiple values are allowed. :type email: str, iterable of strings, or None :param phone: Phone number. Multiple values are allowed. :type phone: str, iterable of strings, or None :param videophone: Phone number for video calls. Multiple values are allowed. :type videophone: str, iterable of strings, or None :param memo: A notice for the contact. :type memo: str or None :param nickname: Nickname. :type nickname: str or None :param birthday: Birthday. If a string is provided, it should encode the date as YYYYMMDD value. :type birthday: str, datetime.date or None :param url: Homepage. Multiple values are allowed. :type url: str, iterable of strings, or None :param pobox: P.O. box (address information). :type pobox: str or None :param roomno: Room number (address information). :type roomno: str or None :param houseno: House number (address information). :type houseno: str or None :param city: City (address information). :type city: str or None :param prefecture: Prefecture (address information). :type prefecture: str or None :param zipcode: Zip code (address information). :type zipcode: str or None :param country: Country (address information). :type country: str or None :rtype: str """ def make_multifield(name, val): if not val: return () if isinstance(val, str): val = (val,) return [f'{name}:{escape(i)};' for i in val] escape = _escape_mecard data = [f'MECARD:N:{escape(name)};'] if reading: data.append(f'SOUND:{escape(reading)};') data.extend(make_multifield('TEL', phone)) data.extend(make_multifield('TELAV', videophone)) data.extend(make_multifield('EMAIL', email)) if nickname: data.append(f'NICKNAME:{escape(nickname)};') if birthday: try: birthday = birthday.strftime('%Y%m%d') except AttributeError: pass data.append(f'BDAY:{birthday};') data.extend(make_multifield('URL', url)) adr_properties = (pobox, roomno, houseno, city, prefecture, zipcode, country) if any(adr_properties): adr_data = [escape(i or '') for i in adr_properties] data.append('ADR:{0},{1},{2},{3},{4},{5},{6};'.format(*adr_data)) # noqa UP030 if memo: data.append(f'MEMO:{escape(memo)};') data.append(';') return ''.join(data) def make_mecard(name, reading=None, email=None, phone=None, videophone=None, memo=None, nickname=None, birthday=None, url=None, pobox=None, roomno=None, houseno=None, city=None, prefecture=None, zipcode=None, country=None): """\ Returns a QR code which encodes a `MeCard <https://en.wikipedia.org/wiki/MeCard>`_ :param str name: Name. If it contains a comma, the first part is treated as lastname and the second part is treated as forename. :param reading: Designates a text string to be set as the kana name in the phonebook :type reading: str or None :param email: E-mail address. Multiple values are allowed. :type email: str, iterable of strings, or None :param phone: Phone number. Multiple values are allowed. :type phone: str, iterable of strings, or None :param videophone: Phone number for video calls. Multiple values are allowed. :type videophone: str, iterable of strings, or None :param memo: A notice for the contact. :type memo: str or None :param nickname: Nickname. :type nickname: str or None :param birthday: Birthday. If a string is provided, it should encode the date as YYYYMMDD value. :type birthday: str, datetime.date or None :param url: Homepage. Multiple values are allowed. :type url: str, iterable of strings, or None :param pobox: P.O. box (address information). :type pobox: str or None :param roomno: Room number (address information). :type roomno: str or None :param houseno: House number (address information). :type houseno: str or None :param city: City (address information). :type city: str or None :param prefecture: Prefecture (address information). :type prefecture: str or None :param zipcode: Zip code (address information). :type zipcode: str or None :param country: Country (address information). :type country: str or None :rtype: segno.QRCode """ return segno.make_qr(make_mecard_data(name=name, reading=reading, email=email, phone=phone, videophone=videophone, memo=memo, nickname=nickname, birthday=birthday, url=url, pobox=pobox, roomno=roomno, houseno=houseno, city=city, prefecture=prefecture, zipcode=zipcode, country=country)) _looks_like_datetime = re.compile(r'^\d{4}-\d{2}-\d{2}(?:T\d{2}:\d{2}:\d{2}(?:(?:-?\d{2}:\d{2})|Z)?)?$').match def make_vcard_data(name, displayname, email=None, phone=None, fax=None, videophone=None, memo=None, nickname=None, birthday=None, url=None, pobox=None, street=None, city=None, region=None, zipcode=None, country=None, org=None, lat=None, lng=None, source=None, rev=None, title=None, photo_uri=None, cellphone=None, homephone=None, workphone=None): """\ Creates a string encoding the contact information as vCard 3.0. Only a subset of available `vCard 3.0 properties <https://tools.ietf.org/html/rfc2426>` is supported. :param str name: The name. If it contains a semicolon, , the first part is treated as lastname and the second part is treated as forename. :param str displayname: Common name. :param email: E-mail address. Multiple values are allowed. :type email: str, iterable of strings, or None :param phone: Phone number. Multiple values are allowed. :type phone: str, iterable of strings, or None :param fax: Fax number. Multiple values are allowed. :type fax: str, iterable of strings, or None :param videophone: Phone number for video calls. Multiple values are allowed. :type videophone: str, iterable of strings, or None :param memo: A notice for the contact. :type memo: str or None :param nickname: Nickname. :type nickname: str or None :param birthday: Birthday. If a string is provided, it should encode the date as ``YYYY-MM-DD`` value. :type birthday: str, datetime.date or None :param url: Homepage. Multiple values are allowed. :type url: str, iterable of strings, or None :param pobox: P.O. box (address information). :type pobox: str or None :param street: Street address. :type street: str or None :param city: City (address information). :type city: str or None :param region: Region (address information). :type region: str or None :param zipcode: Zip code (address information). :type zipcode: str or None :param country: Country (address information). :type country: str or None :param org: Company / organization name. :type org: str or None :param lat: Latitude. :type lat: float or None :param lng: Longitude. :type lng: float or None :param source: URL where to obtain the vCard. :type source: str or None :param rev: Revision of the vCard / last modification date. :type rev: str, datetime.date or None :param title: Job Title. Multiple values are allowed. :type title: str, iterable of strings, or None :param photo_uri: Photo URI. Multiple values are allowed. :type photo_uri: str, iterable of strings, or None :param cellphone: Cell phone number. Multiple values are allowed. :type cellphone: str, iterable of strings, or None :param homephone: Home phone number. Multiple values are allowed. :type homephone: str, iterable of strings, or None :param workphone: Work phone number. Multiple values are allowed. :type workphone: str, iterable of strings, or None :rtype: str """ def make_multifield(name, val): if not val: return () if isinstance(val, str): val = (val,) return [f'{name}:{escape(i)}' for i in val] escape = _escape_vcard data = ['BEGIN:VCARD', 'VERSION:3.0', f'N:{name}', f'FN:{escape(displayname)}'] if org: data.append(f'ORG:{escape(org)}') data.extend(make_multifield('EMAIL', email)) data.extend(make_multifield('TEL', phone)) data.extend(make_multifield('TEL;TYPE=FAX', fax)) data.extend(make_multifield('TEL;TYPE=VIDEO', videophone)) data.extend(make_multifield('TEL;TYPE=CELL', cellphone)) data.extend(make_multifield('TEL;TYPE=HOME', homephone)) data.extend(make_multifield('TEL;TYPE=WORK', workphone)) data.extend(make_multifield('URL', url)) data.extend(make_multifield('TITLE', title)) data.extend(make_multifield('PHOTO;VALUE=uri', photo_uri)) if nickname: data.append(f'NICKNAME:{escape(nickname)}') adr_properties = (pobox, street, city, region, zipcode, country) if any(adr_properties): adr_data = [escape(i or '') for i in adr_properties] data.append('ADR:{0};;{1};{2};{3};{4};{5}'.format(*adr_data)) # noqa UP030 if birthday: try: birthday = birthday.strftime('%Y-%m-%d') except AttributeError: pass if not isinstance(birthday, str) or not _looks_like_datetime(birthday): raise ValueError('"birthday" does not seem to be a valid date or date/time representation') data.append(f'BDAY:{birthday}') if (lat and not lng) or (lng and not lat): raise ValueError('Incomplete geo information, please specify latitude and longitude.') if lat and lng: data.append(f'GEO:{lat};{lng}') if source: data.append(f'SOURCE:{escape(source)}') if memo: data.append(f'NOTE:{escape(memo)}') if rev: try: rev = rev.strftime('%Y-%m-%d') except AttributeError: pass if not isinstance(rev, str) or not _looks_like_datetime(rev): raise ValueError('"rev" does not seem to be a valid date or date/time representation') data.append(f'REV:{rev}') data.append('END:VCARD') data.append('') return '\r\n'.join(data) def make_vcard(name, displayname, email=None, phone=None, fax=None, videophone=None, memo=None, nickname=None, birthday=None, url=None, pobox=None, street=None, city=None, region=None, zipcode=None, country=None, org=None, lat=None, lng=None, source=None, rev=None, title=None, photo_uri=None, cellphone=None, homephone=None, workphone=None): """\ Creates a QR code which encodes a `vCard <https://en.wikipedia.org/wiki/VCard>`_ version 3.0. Only a subset of available `vCard 3.0 properties <https://tools.ietf.org/html/rfc2426>` is supported. :param str name: The name. If it contains a semicolon, , the first part is treated as lastname and the second part is treated as forename. :param str displayname: Common name. :param email: E-mail address. Multiple values are allowed. :type email: str, iterable of strings, or None :param phone: Phone number. Multiple values are allowed. :type phone: str, iterable of strings, or None :param fax: Fax number. Multiple values are allowed. :type fax: str, iterable of strings, or None :param videophone: Phone number for video calls. Multiple values are allowed. :type videophone: str, iterable of strings, or None :param memo: A notice for the contact. :type memo: str or None :param nickname: Nickname. :type nickname: str or None :param birthday: Birthday. If a string is provided, it should encode the date as ``YYYY-MM-DD`` value. :type birthday: str, datetime.date or None :param url: Homepage. Multiple values are allowed. :type url: str, iterable of strings, or None :param pobox: P.O. box (address information). :type pobox: str or None :param street: Street address. :type street: str or None :param city: City (address information). :type city: str or None :param region: Region (address information). :type region: str or None :param zipcode: Zip code (address information). :type zipcode: str or None :param country: Country (address information). :type country: str or None :param org: Company / organization name. :type org: str or None :param lat: Latitude. :type lat: float or None :param lng: Longitude. :type lng: float or None :param source: URL where to obtain the vCard. :type source: str or None :param rev: Revision of the vCard / last modification date. :type rev: str, datetime.date or None :param title: Job Title. Multiple values are allowed. :type title: str, iterable of strings, or None :param photo_uri: Photo URI. Multiple values are allowed. :type photo_uri: str, iterable of strings, or None :param cellphone: Cell phone number. Multiple values are allowed. :type cellphone: str, iterable of strings, or None :param homephone: Home phone number. Multiple values are allowed. :type homephone: str, iterable of strings, or None :param workphone: Work phone number. Multiple values are allowed. :type workphone: str, iterable of strings, or None :rtype: segno.QRCode """ return segno.make_qr(make_vcard_data(name, displayname, email=email, phone=phone, fax=fax, videophone=videophone, memo=memo, nickname=nickname, birthday=birthday, url=url, pobox=pobox, street=street, city=city, region=region, zipcode=zipcode, country=country, org=org, lat=lat, lng=lng, source=source, rev=rev, title=title, photo_uri=photo_uri, cellphone=cellphone, homephone=homephone, workphone=workphone)) def make_geo_data(lat, lng): """\ Creates a geo location URI. :param float lat: Latitude :param float lng: Longitude :rtype: str """ def float_to_str(f): return f'{f:.8f}'.rstrip('0').rstrip('.') return f'geo:{float_to_str(lat)},{float_to_str(lng)}' def make_geo(lat, lng): """\ Returns a QR code which encodes geographic location using the ``geo`` URI scheme. :param float lat: Latitude :param float lng: Longitude :rtype: segno.QRCode """ return segno.make_qr(make_geo_data(lat, lng)) def make_make_email_data(to, cc=None, bcc=None, subject=None, body=None): """\ Creates either a simple "mailto:" URL or complete e-mail message with (blind) carbon copies and a subject and a body. :param to: The email address (recipient). Multiple values are allowed. :type to: str or iterable of strings :param cc: The carbon copy recipient. Multiple values are allowed. :type cc: str, iterable of strings, or None :param bcc: The blind carbon copy recipient. Multiple values are allowed. :type bcc: str, iterable of strings, or None :param subject: The subject. :type subject: str or None :param body: The message body. :type body: str or None :rtype: str """ def multi(val): if not val: return () if isinstance(val, str): return (val,) return tuple(val) delim = '?' data = ['mailto:'] if not to: raise ValueError('"to" must not be empty or None') data.append(','.join(multi(to))) for key, val in (('cc', cc), ('bcc', bcc)): vals = multi(val) if vals: data.append(f'{delim}{key}={",".join(vals)}') delim = '&' for key, val in (('subject', subject), ('body', body)): if val is not None: data.append(f'{delim}{key}={quote(val.encode("utf-8"))}') delim = '&' return ''.join(data) def make_email(to, cc=None, bcc=None, subject=None, body=None): """\ Encodes either a simple e-mail address or a complete message with (blind) carbon copies and a subject and a body. :param to: The email address (recipient). Multiple values are allowed. :type to: str or iterable of strings :param cc: The carbon copy recipient. Multiple values are allowed. :type cc: str, iterable of strings, or None :param bcc: The blind carbon copy recipient. Multiple values are allowed. :type bcc: str, iterable of strings, or None :param subject: The subject. :type subject: str or None :param body: The message body. :type body: str or None :rtype: segno.QRCode """ return segno.make_qr(make_make_email_data(to=to, cc=cc, bcc=bcc, subject=subject, body=body)) def _make_epc_qr_data(name, iban, amount, text=None, reference=None, bic=None, purpose=None, encoding=None): """\ Validates the input and creates the data for an EPC QR Code. DOES NOT belong to the public API, kept separate from make_epc_qr to apply tests on the raw data. See :py:func:`make_epc_qr` for a description of the parameters. """ # Ordering is important! encodings = ('utf-8', 'iso-8859-1', 'iso-8859-2', 'iso-8859-4', 'iso-8859-5', 'iso-8859-7', 'iso-8859-10', 'iso-8859-15') min_amount = decimal.Decimal('0.01') max_amount = decimal.Decimal('999999999.99') text = text.rstrip() if text else text reference = reference.rstrip() if reference else reference bic = bic.strip() if bic else bic name = name.strip() if name else name if encoding is not None: if isinstance(encoding, str): try: encoding = encodings.index(encoding.lower()) + 1 except ValueError: raise ValueError(f'Invalid encoding "{encoding}", use one of {encodings}') elif not isinstance(encoding, int) or not 1 <= encoding <= len(encodings): raise ValueError(f'Invalid encoding number only 1 .. 8 are allowed, got "{encoding}"') if (not text and not reference) or (text and reference): raise ValueError('Either a text or a creditor reference (ISO 11649) must be provided') if text and not 0 < len(text) <= 140: raise ValueError(f'Invalid text, max. 140 characters are allowed, got "{len(text)}"') elif reference and not 0 < len(reference) <= 35: raise ValueError('Invalid creditor reference (ISO 11649), max. 35 characters are allowed, ' f'got "{len(reference)}"') if name is None or not 0 < len(name) <= 70: raise ValueError(f'Invalid name, max. 70 characters are allowed, got "{name}"') if iban is None or not 4 < len(iban) <= 34: raise ValueError(f'Invalid IBAN, min. 5 and max. 34 characters are allowed, got "{iban}"') if bic and len(bic) not in (8, 11): raise ValueError(f'Invalid BIC, should be 8 or 11 characters long, got "{bic}"') if purpose and len(purpose) != 4: raise ValueError(f'Invalid purpose, 4 characters are allowed, got "{purpose}"') amount = decimal.Decimal(amount) if not min_amount <= amount <= max_amount: raise ValueError(f'Invalid amount, must be in bigger or equal {min_amount} and less or equal {max_amount}') tmp_data = ['BCD', # Service tag '002', # Version '', # character set (will be set later) 'SCT', # Identification bic or '', # BIC name, # Name of the recipient iban, # IBAN f'EUR{amount:.2f}'.rstrip('0').rstrip('.'), # Amount purpose or '', # Purpose reference or '', # Remittance ] if text: tmp_data.append(text) data = '\n'.join(tmp_data) charset = -1 if encoding is None else encoding if charset < 0: for idx, enc in enumerate(encodings[1:], start=2): try: data.encode(enc) charset = idx break except UnicodeEncodeError: pass if charset < 0: charset = 1 # Use UTF-8 tmp_data[2] = str(charset) # Set character set data = '\n'.join(tmp_data).encode(encodings[charset - 1]) # Max. payload: 331 bytes if len(data) > 331: # pragma: no cover raise ValueError(f'Payload is too big: Max. 331 bytes allowed, got {len(data)} bytes') return data def make_epc_qr(name, iban, amount, text=None, reference=None, bic=None, purpose=None, encoding=None): """\ Creates and returns an European Payments Council Quick Response Code (EPC QR Code) version 002. The returned :py:class:`segno.QRCode` uses always the error correction level "M" and utilizes max. version 13 to fulfill the constraints of the EPC QR Code standard. .. note:: Either the ``text`` or ``reference`` must be provided but not both .. note:: Neither the IBAN, BIC, nor remittance reference number or any other information is validated (aside from checks regarding the allowed string lengths). :param str name: Name of the recipient. :param str iban: International Bank Account Number (IBAN) :param amount: The amount (in EUR) to transfer. The currency is always Euro, no other currencies are supported. :type amount: int, float, decimal.Decimal :param str text: Remittance Information (unstructured) :param str reference: Remittance Information (structured) :param str bic: Bank Identifier Code (BIC). Optional, only required for non-EEA countries. :param str purpose: SEPA purpose code. :param encoding: By default, this function tries to find the best, minimal encoding. If another encoding should be used, the encoding name or the encoding constant (an integer) can be provided: ``1``: "UTF-8", ``2``: "ISO 8859-1", ``3``: "ISO 8859-2", ``4``: "ISO 8859-4", ``5``: "ISO 8859-5", ``6``: "ISO 8859-7", ``7``: "ISO 8859-10", ``8``: "ISO 8859-15" The encoding is case-insensitive. :type encoding: str or int :rtype: segno.QRCode """ # Create a QR Code, error correction level "M". # It's not allowed to use another level therefore boost_error must be disabled qr = segno.make_qr(_make_epc_qr_data(name, iban, amount, text, reference, bic, purpose, encoding), error='m', boost_error=False) # This shouldn't happen if qr.version > 13: # pragma: no cover raise ValueError(f'Invalid EPC QR Code, max. QR Code version 13 is allowed, got "{qr.designator}"') return qr ''', }, 'segno.utils': { 'is_package': False, 'source': r''' # # Copyright (c) 2016 - 2024 -- Lars Heuer # All rights reserved. # # License: BSD License # # type: ignore """\ Utility functions useful for writers or QR Code objects. DOES NOT belong to the public API. """ from itertools import chain, repeat from . import consts __all__ = ('get_default_border_size', 'get_border', 'get_symbol_size', 'check_valid_scale', 'check_valid_border', 'matrix_to_lines', 'matrix_iter', 'matrix_iter_verbose') def get_default_border_size(matrix_size): """\ Returns the default border size (quiet zone) for the provided version. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :rtype: int """ width, height = matrix_size return 4 if width > 17 and width == height else 2 def get_border(matrix_size, border): """\ Returns `border` if not ``None``, otherwise the default border size for the provided QR Code. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param border: The size of the quiet zone or ``None``. :type border: int or None :rtype: int """ return border if border is not None else get_default_border_size(matrix_size) def get_symbol_size(matrix_size, scale=1, border=None): """\ Returns the symbol size (width x height) with the provided border and scaling factor. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param scale: Indicates the size of a single module (default: 1). The size of a module depends on the used output format; i.e. in a PNG context, a scaling factor of 2 indicates that a module has a size of 2 x 2 pixel. Some outputs (i.e. SVG) accept floating point values. :type scale: int or float :param int border: The border size or ``None`` to specify the default quiet zone (4 for QR Codes, 2 for Micro QR Codes). :rtype: tuple (width, height) """ if border is None: border = get_default_border_size(matrix_size) width, height = matrix_size width += 2 * border height += 2 * border return width * scale, height * scale def check_valid_scale(scale): """\ Raises a :py:exc:`ValueError` iff `scale` is negative or zero. :param scale: Scaling factor. :type scale: float or int """ if scale <= 0: raise ValueError(f'The scale must not be negative or zero. Got: "{scale}"') def check_valid_border(border): """\ Raises a :py:exc:`ValueError` iff `border` is negative. :param int border: Indicating the size of the quiet zone. """ if border is not None and (int(border) != border or border < 0): raise ValueError(f'The border must not a non-negative integer value. Got: "{border}"') def matrix_to_lines(matrix, x, y, incby=1): """\ Converts the `matrix` into an iterable of ((x1, y1), (x2, y2)) tuples which represent a sequence (horizontal line) of dark modules. The path starts at the 1st row of the matrix and moves down to the last row. :param matrix: An iterable of bytearrays. :param x: Initial position on the x-axis. :param y: Initial position on the y-axis. :param incby: Value to move along the y-axis (default: 1). :rtype: iterable of (x1, y1), (x2, y2) tuples """ y -= incby # Move along y-axis so we can simply increment y in the loop last_bit = 0x1 for row in matrix: x1, x2 = x, x y += incby for bit in row: if last_bit != bit and not bit: yield (x1, y), (x2, y) x1 = x2 x2 += 1 if not bit: x1 += 1 last_bit = bit if last_bit: yield (x1, y), (x2, y) last_bit = 0x0 def matrix_iter(matrix, matrix_size, scale=1, border=None): """\ Returns an iterator / generator over the provided matrix which includes the border and the scaling factor. If either the `scale` or `border` value is invalid, a :py:exc:`ValueError` is raised. :param matrix: An iterable of bytearrays. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param int scale: The scaling factor (default: ``1``). :param int border: The border size or ``None`` to specify the default quiet zone (4 for QR Codes, 2 for Micro QR Codes). :raises: :py:exc:`ValueError` if an illegal scale or border value is provided """ check_valid_border(border) scale = int(scale) check_valid_scale(scale) border = get_border(matrix_size, border) width, height = matrix_size border_row = [0x0] * width width_range, height_range = range(-border, width + border), range(-border, height + border) for i in height_range: r = matrix[i] if 0 <= i < height else border_row row = tuple(chain.from_iterable(repeat(r[j] if 0 <= j < width else 0x0, scale) for j in width_range)) for s in repeat(None, scale): yield row def matrix_iter_verbose(matrix, matrix_size, scale=1, border=None): """\ Returns an iterator / generator over the provided matrix which includes the border and the scaling factor. This iterator / generator returns different values for dark / light modules and therefor the different parts (like the finder patterns, alignment patterns etc.) are distinguishable. If this information isn't necessary, use the :py:func:`matrix_iter()` function because it is much cheaper and faster. If either the `scale` or `border` value is invalid, a py:exc:`ValueError` is raised. :param matrix: An iterable of bytearrays. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param int scale: The scaling factor (default: ``1``). :param int border: The border size or ``None`` to specify the default quiet zone (4 for QR Codes, 2 for Micro QR Codes). :raises: :py:exc:`ValueError` if an illegal scale or border value is provided """ from segno import encoder check_valid_border(border) scale = int(scale) check_valid_scale(scale) border = get_border(matrix_size, border) width, height = matrix_size is_square = width == height is_micro = is_square and width < 21 # 21 == QR Code version 1 # Create an empty matrix with invalid 0x2 values alignment_matrix = encoder.make_matrix(width, height, reserve_regions=False, add_timing=False) encoder.add_alignment_patterns(alignment_matrix, width, height) def get_bit(i, j): # Check if we operate upon the matrix or the "virtual" border if 0 <= i < height and 0 <= j < width: val = matrix[i][j] if not is_micro: # Alignment pattern alignment_val = alignment_matrix[i][j] if alignment_val != 0x2: return (consts.TYPE_ALIGNMENT_PATTERN_LIGHT, consts.TYPE_ALIGNMENT_PATTERN_DARK)[alignment_val] if is_square and width > 41: # QR Codes < version 7 do not carry any version information if (i < 6 and width - 12 < j < width - 8) \ or (height - 12 < i < height - 8 and j < 6): return (consts.TYPE_VERSION_LIGHT, consts.TYPE_VERSION_DARK)[val] # Dark module if i == height - 8 and j == 8: return consts.TYPE_DARKMODULE # Timing - IMPORTANT: Check alignment (see above) in advance! if (not is_micro and ((i == 6 and 7 < j < width - 8) or (j == 6 and 7 < i < height - 8))) \ or (is_micro and ((i == 0 and j > 7) or (j == 0 and i > 7))): return (consts.TYPE_TIMING_LIGHT, consts.TYPE_TIMING_DARK)[val] # Format - IMPORTANT: Check timing (see above) in advance! if (i == 8 and (j < 9 or (not is_micro and j > width - 10))) \ or (j == 8 and (i < 8 or (not is_micro and i > height - 9))): return (consts.TYPE_FORMAT_LIGHT, consts.TYPE_FORMAT_DARK)[val] # Finder pattern # top left top right if (i < 7 and (j < 7 or (not is_micro and j > width - 8))) \ or (not is_micro and i > height - 8 and j < 7): # bottom left return (consts.TYPE_FINDER_PATTERN_LIGHT, consts.TYPE_FINDER_PATTERN_DARK)[val] # Separator # top left top right if (i < 8 and (j < 8 or (not is_micro and j > width - 9))) \ or (not is_micro and (i > height - 9 and j < 8)): # bottom left return consts.TYPE_SEPARATOR return (consts.TYPE_DATA_LIGHT, consts.TYPE_DATA_DARK)[val] else: return consts.TYPE_QUIET_ZONE width_range, height_range = range(-border, width + border), range(-border, height + border) for i in height_range: row = tuple(chain.from_iterable(repeat(get_bit(i, j), scale) for j in width_range)) for s in repeat(None, scale): yield row ''', }, 'segno.writers': { 'is_package': False, 'source': r''' # # Copyright (c) 2016 - 2024 -- Lars Heuer # All rights reserved. # # License: BSD License # # type: ignore """\ Standard serializers and utility functions for serializers. DOES NOT belong to the public API. The serializers are independent of the :py:class:`segno.QRCode` (and the :py:class:`segno.encoder.Code`) class; they just need a matrix (tuple of bytearrays). """ import io import re import zlib import codecs import base64 import gzip from xml.sax.saxutils import quoteattr, escape from struct import pack from itertools import chain, repeat import functools from functools import partial from functools import reduce from operator import itemgetter from contextlib import contextmanager from collections import defaultdict import time from . import consts from .utils import matrix_to_lines, get_symbol_size, get_border, \ check_valid_scale, check_valid_border, matrix_iter, matrix_iter_verbose from itertools import zip_longest from urllib.parse import quote __all__ = ('writable', 'write_svg', 'write_png', 'write_eps', 'write_pdf', 'write_txt', 'write_pbm', 'write_pam', 'write_ppm', 'write_xpm', 'write_xbm', 'write_tex', 'write_terminal') # Standard creator name CREATOR = 'Segno <https://pypi.org/project/segno/>' @contextmanager def writable(file_or_path, mode, encoding=None): """\ Returns a writable file-like object. Usage:: with writable(file_name_or_path, 'wb') as f: ... :param file_or_path: Either a file-like object or a filename. :param str mode: String indicating the writing mode (i.e. ``'wb'``) """ f = file_or_path must_close = False try: file_or_path.write if encoding is not None: f = codecs.getwriter(encoding)(file_or_path) except AttributeError: f = open(file_or_path, mode, encoding=encoding) must_close = True try: yield f finally: if must_close: f.close() def colorful(dark, light): """\ Decorator to inject a module type -> color mapping into the decorated function. """ def decorate(f): @functools.wraps(f) def wrapper(matrix, matrix_size, out, dark=dark, light=light, finder_dark=False, finder_light=False, data_dark=False, data_light=False, version_dark=False, version_light=False, format_dark=False, format_light=False, alignment_dark=False, alignment_light=False, timing_dark=False, timing_light=False, separator=False, dark_module=False, quiet_zone=False, **kw): cm = _make_colormap(*matrix_size, dark=dark, light=light, finder_dark=finder_dark, finder_light=finder_light, data_dark=data_dark, data_light=data_light, version_dark=version_dark, version_light=version_light, format_dark=format_dark, format_light=format_light, alignment_dark=alignment_dark, alignment_light=alignment_light, timing_dark=timing_dark, timing_light=timing_light, separator=separator, dark_module=dark_module, quiet_zone=quiet_zone) return f(matrix, matrix_size, out, cm, **kw) return wrapper return decorate def _valid_width_height_and_border(matrix_size, scale, border): """"\ Validates the scale and border and returns the width, height and the border. If the border is ``None`` the default border is returned. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. """ check_valid_scale(scale) check_valid_border(border) border = get_border(matrix_size, border) width, height = get_symbol_size(matrix_size, scale, border) return width, height, border @colorful(dark='#000', light=None) def write_svg(matrix, matrix_size, out, colormap, scale=1, border=None, xmldecl=True, svgns=True, title=None, desc=None, svgid=None, svgclass='segno', lineclass='qrline', omitsize=False, unit=None, encoding='utf-8', svgversion=None, nl=True, draw_transparent=False): """\ Serializes the QR code as SVG document. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write bytes. :param scale: Indicates the size of a single module (default: 1 which corresponds to 1 x 1 pixel per module). :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for a Micro QR Codes). :param bool xmldecl: Inidcates if the XML declaration header should be written (default: ``True``) :param bool svgns: Indicates if the SVG namespace should be written (default: ``True``). :param str title: Optional title of the generated SVG document. :param str desc: Optional description of the generated SVG document. :param svgid: The ID of the SVG document (if set to ``None`` (default), the SVG element won't have an ID). :param svgclass: The CSS class of the SVG document (if set to ``None``, the SVG element won't have a class). :param lineclass: The CSS class of the path element (which draws the "black" modules (if set to ``None``, the path won't have a class). :param bool omitsize: Indicates if width and height attributes should be omitted (default: ``False``). If these attributes are omitted, a ``viewBox`` attribute will be added to the document. :param str unit: Unit for width / height and other coordinates. By default, the unit is unspecified and all values are in the user space. Valid values: em, ex, px, pt, pc, cm, mm, in, and percentages :param str encoding: Encoding of the XML document. "utf-8" by default. :param float svgversion: SVG version (default: None) :param bool nl: Indicates if the document should have a trailing newline (default: ``True``) :param bool draw_transparent: Indicates if transparent SVG paths should be added to the graphic (default: ``False``) """ def svg_color(clr): return _color_to_webcolor(clr, allow_css3_colors=allow_css3_colors) if clr is not None else None def matrix_to_lines_verbose(): j = -.5 # stroke width / 2 invalid_color = -1 for row in matrix_iter_verbose(matrix, matrix_size, scale=1, border=border): last_color = invalid_color x1, x2 = 0, 0 j += 1 for c in (colormap[mt] for mt in row): if last_color != invalid_color and last_color != c: yield last_color, (x1, x2, j) x1 = x2 x2 += 1 last_color = c yield last_color, (x1, x2, j) width, height, border = _valid_width_height_and_border(matrix_size, scale, border) unit = unit or '' if unit and omitsize: raise ValueError(f'The unit "{unit}" has no effect if the size ' '(width and height) is omitted.') omit_encoding = encoding is None if omit_encoding: encoding = 'utf-8' allow_css3_colors = svgversion is not None and svgversion >= 2.0 is_multicolor = len(set(colormap.values())) > 2 need_background = not is_multicolor and colormap[consts.TYPE_QUIET_ZONE] is not None and not draw_transparent need_svg_group = scale != 1 and (need_background or is_multicolor) if is_multicolor: miter = matrix_to_lines_verbose() else: x, y = border, border + .5 dark = colormap[consts.TYPE_DATA_DARK] miter = ((dark, (x1, x2, y1)) for (x1, y1), (x2, y2) in matrix_to_lines(matrix, x, y)) xy = defaultdict(lambda: (0, 0)) coordinates = defaultdict(list) for clr, (x1, x2, y1) in miter: x, y = xy[clr] coordinates[clr].append((x1 - x, y1 - y, x2 - x1)) xy[clr] = x2, y1 if need_background: # Additional path for the background, will be modified after # the SVG paths have been generated coordinates[colormap[consts.TYPE_QUIET_ZONE]] = [(0, 0, width // scale)] if not draw_transparent: try: del coordinates[None] except KeyError: pass paths = {} scale_info = f' transform="scale({scale})"' if scale != 1 else '' p = '<path{}{}'.format(scale_info if not need_svg_group else '', '' if not lineclass else f' class={quoteattr(lineclass)}') for color, coord in coordinates.items(): path = p clr = svg_color(color) if clr is not None: opacity = None if isinstance(clr, tuple): clr, opacity = clr path += f' stroke={quoteattr(clr)}' if opacity is not None: path += f' stroke-opacity={quoteattr(str(opacity))}' path += ' d="' path += ''.join('{moveto}{x} {y}h{l}'.format(moveto=('m' if i > 0 else 'M'), x=x, l=length, y=(int(y) if int(y) == y else y)) for i, (x, y, length) in enumerate(coord)) path += '"/>' paths[color] = path if need_background: # This code is necessary since the path was generated by the loop above # but the background path is special: It has no stroke-color but a fill-color. # The fill-color needs to be closed. Further, it has no class attribute. k = colormap[consts.TYPE_QUIET_ZONE] paths[k] = re.sub(r'\sclass="[^"]+"', '', paths[k].replace('stroke', 'fill') .replace('"/>', f'v{height // scale}h-{width // scale}z"/>')) svg = '' if xmldecl: svg += '<?xml version="1.0"' if not omit_encoding: svg += f' encoding={quoteattr(encoding)}' svg += '?>\n' svg += '<svg' if svgns: svg += ' xmlns="http://www.w3.org/2000/svg"' if svgversion is not None and svgversion < 2.0: svg += f' version={quoteattr(str(svgversion))}' if not omitsize: svg += f' width="{width}{unit}" height="{height}{unit}"' if omitsize or unit: svg += f' viewBox="0 0 {width} {height}"' if svgid: svg += f' id={quoteattr(svgid)}' if svgclass: svg += f' class={quoteattr(svgclass)}' svg += '>' if title is not None: svg += f'<title>{escape(title)}</title>' if desc is not None: svg += f'<desc>{escape(desc)}</desc>' if need_svg_group: svg += f'<g{scale_info}>' svg += ''.join(sorted(paths.values(), key=len)) if need_svg_group: svg += '</g>' svg += '</svg>' if nl: svg += '\n' with writable(out, 'wt', encoding=encoding) as f: f.write(svg) _replace_quotes = partial(re.compile(br'(=)"([^"]+)"').sub, br"\1'\2'") def as_svg_data_uri(matrix, matrix_size, scale=1, border=None, xmldecl=False, svgns=True, title=None, desc=None, svgid=None, svgclass='segno', lineclass='qrline', omitsize=False, unit='', encoding='utf-8', svgversion=None, nl=False, encode_minimal=False, omit_charset=False, **kw): """\ Converts the matrix to a SVG data URI. The XML declaration is omitted by default (set ``xmldecl`` to ``True`` to enable it), further the newline is omitted by default (set ``nl`` to ``True`` to enable it). Aside from the missing ``out`` parameter and the different ``xmldecl`` and ``nl`` default values and the additional parameter ``encode_minimal`` and ``omit_charset`` this function uses the same parameters as the usual SVG serializer. :param bool encode_minimal: Indicates if the resulting data URI should use minimal percent encoding (disabled by default). :param bool omit_charset: Indicates if the ``;charset=...`` should be omitted (disabled by default) :rtype: str """ encode = partial(quote, safe=b"") if not encode_minimal else partial(quote, safe=b" :/='") buff = io.BytesIO() write_svg(matrix, matrix_size, buff, scale=scale, border=border, xmldecl=xmldecl, svgns=svgns, title=title, desc=desc, svgclass=svgclass, lineclass=lineclass, omitsize=omitsize, encoding=encoding, svgid=svgid, unit=unit, svgversion=svgversion, nl=nl, **kw) return f'data:image/svg+xml{(";charset=" + encoding if not omit_charset else "")},' \ + encode(_replace_quotes(buff.getvalue())) def write_svg_debug(matrix, matrix_size, out, scale=15, border=None, fallback_color='fuchsia', colormap=None, add_legend=True): # pragma: no cover """\ Internal SVG serializer which is useful for debugging purposes. This function is not exposed to the QRCode class by intention and the resulting SVG document is very inefficient (a lot of ``<rect/>`` elements). Dark modules are black and light modules are white by default. Provide a custom `colormap` to override these defaults. Unknown modules are red by default. :param matrix: The matrix :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: binary file-like object or file name :param scale: Scaling factor :param border: Quiet zone :param fallback_color: Color which is used for modules which are not 0x0 or 0x1 and for which no entry in `color_mapping` is defined. :param colormap: dict of module values to color mapping (optional) :param bool add_legend: Indicates if the bit values should be added to the matrix (default: True) """ clr_mapping = { 0x0: '#fff', 0x1: '#000', 0x2: 'red', 0x3: 'orange', 0x4: 'gold', 0x5: 'green', } if colormap is not None: clr_mapping.update(colormap) width, height, border = _valid_width_height_and_border(matrix_size, scale, border) matrix_width, matrix_height = matrix_size with writable(out, 'wt', encoding='utf-8') as f: legend = [] write = f.write write('<?xml version="1.0" encoding="utf-8"?>\n') write(f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {width} {height}">') write('<style type="text/css"><![CDATA[ text { font-size: 1px; ' 'font-family: Helvetica, Arial, sans; } ]]></style>') write(f'<g transform="scale({scale})">') for i in range(matrix_height): y = i + border for j in range(matrix_width): x = j + border bit = matrix[i][j] if add_legend and bit not in (0x0, 0x1): legend.append((x, y, bit)) fill = clr_mapping.get(bit, fallback_color) write(f'<rect x="{x}" y="{y}" width="1" height="1" fill="{fill}"/>') # legend may be empty if add_legend == False for x, y, val in legend: write(f'<text x="{x + .2}" y="{y + .9}">{val}</text>') write('</g></svg>\n') def write_eps(matrix, matrix_size, out, scale=1, border=None, dark='#000', light=None): """\ Serializes the QR code as EPS document. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write strings. :param scale: Indicates the size of a single module (default: 1 which corresponds to 1 point (1/72 inch) per module). :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). :param dark: Color of the modules (default: black). The color can be provided as ``(R, G, B)`` tuple (this method acceppts floats as R, G, B values), as web color name (like "red") or in hexadecimal format (``#RGB`` or ``#RRGGBB``). :param light: Optional background color (default: ``None`` = no background color). See `color` for valid values. """ import textwrap def write_line(writemeth, content): """\ Writes `content` and ``LF``. """ # Postscript: Max. 255 characters per line for line in textwrap.wrap(content, 254): writemeth(line) writemeth('\n') def rgb_to_floats(clr): """\ Converts the provided color into an acceptable format for Postscript's ``setrgbcolor`` """ def to_float(c): if isinstance(c, float): if not 0.0 <= c <= 1.0: raise ValueError(f'Invalid color "{c}". Not in range 0 .. 1') return c return 1 / 255.0 * c if c != 1 else c return tuple([to_float(i) for i in _color_to_rgb(clr)]) width, height, border = _valid_width_height_and_border(matrix_size, scale, border) stroke_color_is_black = _color_is_black(dark) stroke_color = dark if stroke_color_is_black else rgb_to_floats(dark) with writable(out, 'wt') as f: writeline = partial(write_line, f.write) writeline('%!PS-Adobe-3.0 EPSF-3.0') writeline(f'%%Creator: {CREATOR}') writeline(f'%%CreationDate: {time.strftime("%Y-%m-%d %H:%M:%S")}') writeline('%%DocumentData: Clean7Bit') writeline(f'%%BoundingBox: 0 0 {width} {height}') # Write the shortcuts writeline('/m { rmoveto } bind def') writeline('/l { rlineto } bind def') if light is not None: writeline('{0:f} {1:f} {2:f} setrgbcolor clippath fill'.format(*rgb_to_floats(light))) # noqa UP030 if stroke_color_is_black: # Reset RGB color back to black iff stroke color is black # In case stroke color != black set the RGB color later writeline('0 0 0 setrgbcolor') if not stroke_color_is_black: writeline('{0:f} {1:f} {2:f} setrgbcolor'.format(*stroke_color)) # noqa UP030 if scale != 1: writeline(f'{scale} {scale} scale') writeline('newpath') # Current pen position y-axis # Note: 0, 0 = lower left corner in PS coordinate system y = get_symbol_size(matrix_size, scale=1, border=0)[1] + border - .5 # .5 = linewidth / 2 line_iter = matrix_to_lines(matrix, border, y, incby=-1) # EPS supports absolute coordinates as well, but relative coordinates # are more compact and IMO nicer; so the 1st coordinate is absolute, all # other coordinates are relative (x1, y1), (x2, y2) = next(line_iter) coord = [f'{x1} {y1} moveto {x2 - x1} 0 l'] append_coord = coord.append x = x2 for (x1, y1), (x2, y2) in line_iter: append_coord(f' {x1 - x} {int(y1 - y)} m {x2 - x1} 0 l') x, y = x2, y2 writeline(''.join(coord)) writeline('stroke') writeline('%%EOF') def as_png_data_uri(matrix, matrix_size, scale=1, border=None, compresslevel=9, **kw): """\ Converts the provided matrix into a PNG data URI. See :func:`write_png` for a description of supported parameters. :rtype: str """ buff = io.BytesIO() write_png(matrix, matrix_size, buff, scale=scale, border=border, compresslevel=compresslevel, **kw) return f'data:image/png;base64,{base64.b64encode(buff.getvalue()).decode("ascii")}' @colorful(dark='#000', light='#fff') def write_png(matrix, matrix_size, out, colormap, scale=1, border=None, compresslevel=9, dpi=None): """\ Serializes the QR code as PNG image. By default, the generated PNG will be a greyscale image (black / white) with a bit depth of 1. If different colors are provided, an indexed-color image with the same bit depth is generated unless more than two colors are provided. This may require a bit depth of of 2 or 4. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write bytes. :param scale: Indicates the size of a single module (default: 1 which corresponds to 1 x 1 pixel per module). :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). :param int dpi: Optional DPI setting. By default (``None``), the PNG won't have any DPI information. Note that the DPI value is converted into meters since PNG does not support any DPI information. :param int compresslevel: Integer indicating the compression level (default: 9). 1 is fastest and produces the least compression, 9 is slowest and produces the most. 0 is no compression. :param dict colormap: Optional module type -> color mapping. If provided, the `color` and `background` arguments are ignored. All undefined module types will have the default colors (light: white, dark: black). See `color` for valid color values. ``None`` is accepted as valid color value as well (becomes transparent). """ def png_color(clr): return _color_to_rgb_or_rgba(clr, alpha_float=False) if clr is not None else transparent def chunk(name, data): """\ Returns a PNG chunk with checksum. """ chunk_head = name + data return pack(b'>I', len(data)) + chunk_head + pack(b'>I', zlib.crc32(chunk_head)) def scanline(row, filter_type=b'\0'): """\ Returns a single scanline. """ return bytearray(chain(filter_type, # See _pack_bits_into_byte, same code, but the bit depth is taken # into account (reduce(lambda x, y: (x << png_bit_depth) + y, e) for e in zip_longest(*[iter(row)] * (8 // png_bit_depth), fillvalue=0x0)))) scale = int(scale) width, height, border = _valid_width_height_and_border(matrix_size, scale, border) if dpi: dpi = int(dpi) if dpi < 0: raise ValueError('DPI value must not be negative') dpi = int(dpi // 0.0254) black = (0, 0, 0) white = (255, 255, 255) transparent = (-1, -1, -1, -1) # Invalid placeholder for transparent color dark_idx = consts.TYPE_FINDER_PATTERN_DARK qz_idx = consts.TYPE_QUIET_ZONE clr_map = {k: png_color(colormap[k]) for k in colormap} # Creating a palette here regardless of the image type (greyscale vs. index-colors) palette = sorted(set(clr_map.values()), key=itemgetter(0, 1, 2)) is_transparent = transparent in palette number_of_colors = len(palette) # Check if greyscale mode is applicable is_greyscale = number_of_colors == 2 and all(clr in (transparent, black, white) for clr in palette) png_color_type = 0 if is_greyscale else 3 png_bit_depth = 1 # Assume a bit depth of 1 (may change if PLTE is used) png_trans_idx = None if not is_greyscale: # PLTE if number_of_colors > 2: # Max. 15 different colors are supported, no need to support # bit depth 8 (more than 16 colors) png_bit_depth = 2 if number_of_colors < 5 else 4 palette.sort(key=len, reverse=True) # RGBA colors first if is_transparent: png_trans_idx = 0 rgb_values = _NAME2RGB.values() if len(palette[1]) == 3 else ((*clr, 0) for clr in _NAME2RGB.values()) # Choose a random color which becomes transparent. transparent_color = next(clr for clr in rgb_values if clr not in palette) palette[0] = transparent_color # Replace the placeholder "transparent" with the actual RGB(A) value clr_map.update({module_type: transparent_color for module_type, clr in clr_map.items() if clr == transparent}) elif is_transparent: # Greyscale and transparent if black in palette: # Since black is zero, it should be the first entry palette = [black, transparent] png_trans_idx = palette.index(transparent) if number_of_colors > 2: # Need the more expensive matrix iterator miter = matrix_iter_verbose(matrix, matrix_size, scale=1, border=0) color_index = {module_type: palette.index(clr) for module_type, clr in clr_map.items()} else: # Just two colors, use the cheap iterator which returns 0x0 or 0x1 miter = iter(matrix) # The code to create the image requires that TYPE_QUIET_ZONE is available color_index = {qz_idx: palette.index(clr_map[qz_idx])} color_index.update({0: color_index[qz_idx], 1: palette.index(clr_map[dark_idx])}) miter = ((color_index[b] for b in r) for r in miter) horizontal_border = b'' vertical_border = b'' if border > 0: # Calculate horizontal and vertical border qz_value = color_index[qz_idx] horizontal_border = scanline(repeat(qz_value, width)) * border * scale vertical_border = [qz_value] * border * scale # <https://www.w3.org/TR/PNG/#9Filters> # This variable holds the "Up" filter which indicates that this scanline # is equal to the above scanline (since it is filled with null bytes) same_as_above = b'' if scale > 1: # 2 == PNG Filter "Up" <https://www.w3.org/TR/PNG/#9-table91> same_as_above = scanline(repeat(0x0, width), filter_type=b'\2') * (scale - 1) miter = (chain(*(repeat(b, scale) for b in row)) for row in miter) idat = bytearray(horizontal_border) for row in miter: # Chain precalculated left border with row and right border idat += scanline(chain(vertical_border, row, vertical_border)) idat += same_as_above # This is b'' if no scaling factor was provided idat += horizontal_border with writable(out, 'wb') as f: write = f.write write(b'\211PNG\r\n\032\n') # Magic number # Header: # width, height, bitdepth, colortype, compression meth., filter, interlance write(chunk(b'IHDR', pack(b'>2I5B', width, height, png_bit_depth, png_color_type, 0, 0, 0))) if dpi: write(chunk(b'pHYs', pack(b'>LLB', dpi, dpi, 1))) if not is_greyscale: write(chunk(b'PLTE', b''.join(pack(b'>3B', *clr[:3]) for clr in palette))) # <https://www.w3.org/TR/PNG/#11tRNS> if len(palette[0]) > 3: # Color with alpha channel is the first entry in the palette write(chunk(b'tRNS', b''.join(pack(b'>B', clr[3]) for clr in palette if len(clr) > 3))) elif is_transparent: write(chunk(b'tRNS', pack(b'>B', png_trans_idx))) elif is_transparent: # Grayscale with Transparency # <https://www.w3.org/TR/PNG/#11tRNS> # 2 bytes for color type == 0 (greyscale) write(chunk(b'tRNS', pack(b'>1H', png_trans_idx))) write(chunk(b'IDAT', zlib.compress(idat, compresslevel))) write(chunk(b'IEND', b'')) def write_pdf(matrix, matrix_size, out, scale=1, border=None, dark='#000', light=None, compresslevel=9): """\ Serializes the QR code as PDF document. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write bytes. :param scale: Indicates the size of a single module (default: 1 which corresponds to 1 x 1 pixel per module). :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). :param dark: Color of the modules (default: black). The color can be provided as ``(R, G, B)`` tuple, as web color name (like "red") or in hexadecimal format (``#RGB`` or ``#RRGGBB``). :param light: Optional background color (default: ``None`` = no background color). See `color` for valid values. :param int compresslevel: Integer indicating the compression level (default: 9). 1 is fastest and produces the least compression, 9 is slowest and produces the most. 0 is no compression. """ def write_string(writemeth, s): writemeth(s.encode('ascii')) def to_pdf_color(clr): """\ Converts the provided color into an acceptable format for PDF's "DeviceRGB" color space. """ def to_float(c): if isinstance(c, float): if not 0.0 <= c <= 1.0: raise ValueError(f'Invalid color "{c}". Not in range 0 .. 1') return c return 1 / 255.0 * c if c != 1 else c return tuple([to_float(i) for i in _color_to_rgb(clr)]) width, height, border = _valid_width_height_and_border(matrix_size, scale, border) creation_date = f"{time.strftime('%Y%m%d%H%M%S')}{(time.timezone // 3600):+03d}'{(abs(time.timezone) % 60):02d}'" cmds = [] append_cmd = cmds.append if light is not None: # If the background color is defined, a rect is drawn in the background append_cmd('{} {} {} rg'.format(*to_pdf_color(light))) append_cmd(f'0 0 {width} {height} re') append_cmd('f q') # Set the stroke color only iff it is not black (default) if not _color_is_black(dark): append_cmd('{} {} {} RG'.format(*to_pdf_color(dark))) if scale > 1: append_cmd(f'{scale} 0 0 {scale} 0 0 cm') # Current pen position y-axis # Note: 0, 0 = lower left corner in PDF coordinate system y = get_symbol_size(matrix_size, scale=1, border=0)[1] + border - .5 # Set the origin in the upper left corner append_cmd(f'1 0 0 1 {border} {y} cm') miter = matrix_to_lines(matrix, 0, 0, incby=-1) # PDF supports absolute coordinates, only cmds.extend(f'{x1} {y1} m {x2} {y1} l' for (x1, y1), (x2, y2) in miter) append_cmd('S') graphic = zlib.compress((' '.join(cmds)).encode('ascii'), compresslevel) with writable(out, 'wb') as f: write = f.write writestr = partial(write_string, write) object_pos = [] write(b'%PDF-1.4\r%\xE2\xE3\xCF\xD3\r\n') for obj in ('obj <</Type /Catalog /Pages 2 0 R>>\r\nendobj\r\n', 'obj <</Type /Pages /Kids [3 0 R] /Count 1>>\r\nendobj\r\n', f'obj <</Type /Page /Parent 2 0 R /MediaBox [0 0 {width} {height}] /Contents 4 0 R>>\r\nendobj\r\n', f'obj <</Length {len(graphic)} /Filter /FlateDecode>>\r\nstream\r\n'): object_pos.append(f.tell()) writestr(f'{len(object_pos)} 0 {obj}') write(graphic) write(b'\r\nendstream\r\nendobj\r\n') object_pos.append(f.tell()) writestr(f'{len(object_pos)} 0 obj <</CreationDate(D:{creation_date})' f'/Producer({CREATOR})/Creator({CREATOR})\r\n>>\r\nendobj\r\n') object_pos.append(f.tell()) writestr(f'xref\r\n0 {len(object_pos)}\r\n0000000000 65535 f\r\n') for pos in object_pos[:-1]: writestr(f'{pos:010d} {0:05d} n\r\n') writestr(f'trailer <</Size {len(object_pos)}/Root 1 0 R/Info 5 0 R>>\r\n') xref_location = object_pos[-1] writestr(f'startxref\r\n{xref_location}\r\n%%EOF\r\n') def write_txt(matrix, matrix_size, out, border=None, dark='1', light='0'): """\ Serializes QR code in a text format. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write text. :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). :param dark: Character to use for the black modules (default: '1') :param light: Character to use for the white modules (default: '0') """ row_iter = matrix_iter(matrix, matrix_size, scale=1, border=border) colours = (str(light), str(dark)) with writable(out, 'wt') as f: write = f.write for row in row_iter: write(''.join(colours[i] for i in row)) write('\n') def write_pbm(matrix, matrix_size, out, scale=1, border=None, plain=False): """\ Serializes the matrix as `PBM <http://netpbm.sourceforge.net/doc/pbm.html>`_ image. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write binary data. :param scale: Indicates the size of a single module (default: 1 which corresponds to 1 x 1 pixel per module). :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). :param bool plain: Indicates if a P1 (ASCII encoding) image should be created (default: False). By default a (binary) P4 image is created. """ def pack_row(iterable): """\ Packs eight bits into one byte. """ return (reduce(lambda x, y: (x << 1) + y, e) for e in zip_longest(*[iter(iterable)] * 8, fillvalue=0x0)) width, height, border = _valid_width_height_and_border(matrix_size, scale, border) row_iter = matrix_iter(matrix, matrix_size, scale, border) with writable(out, 'wb') as f: write = f.write write(f'{("P4" if not plain else "P1")}\n' f'# Created by {CREATOR}\n' f'{width} {height}\n'.encode('ascii')) if not plain: for row in row_iter: write(bytearray(pack_row(row))) else: for row in row_iter: write(b''.join(str(i).encode('ascii') for i in row)) write(b'\n') def write_pam(matrix, matrix_size, out, scale=1, border=None, dark='#000', light='#fff'): """\ Serializes the matrix as `PAM <http://netpbm.sourceforge.net/doc/pam.html>`_ image. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write binary data. :param scale: Indicates the size of a single module (default: 1 which corresponds to 1 x 1 pixel per module). :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). :param dark: Color of the modules (default: black). The color can be provided as ``(R, G, B)`` tuple, as web color name (like "red") or in hexadecimal format (``#RGB`` or ``#RRGGBB``). :param light: Optional background color (default: white). See `color` for valid values. In addition, ``None`` is accepted which indicates a transparent background. """ def invert_row_bits(row): """\ Inverts the row bits 0 -> 1, 1 -> 0 """ return bytearray([b ^ 0x1 for b in row]) def row_to_color_values(row, colours): return b''.join(colours[b] for b in row) if not dark: raise ValueError(f'Invalid stroke color "{dark}"') width, height, border = _valid_width_height_and_border(matrix_size, scale, border) row_iter = matrix_iter(matrix, matrix_size, scale, border) depth, maxval, tuple_type = 1, 1, 'BLACKANDWHITE' transparency = False stroke_color = _color_to_rgb_or_rgba(dark, alpha_float=False) bg_color = _color_to_rgb_or_rgba(light, alpha_float=False) if light is not None else None colored_stroke = not (_color_is_black(stroke_color) or _color_is_white(stroke_color)) if bg_color is None: tuple_type = 'GRAYSCALE_ALPHA' if not colored_stroke else 'RGB_ALPHA' transparency = True bg_color = _invert_color(stroke_color[:3]) bg_color += (0,) if len(stroke_color) != 4: stroke_color += (255,) elif colored_stroke or not (_color_is_black(bg_color) or _color_is_white(bg_color)): tuple_type = 'RGB' is_rgb = tuple_type.startswith('RGB') colours = None if not is_rgb and transparency: depth = 2 colours = (b'\x01\x00', b'\x00\x01') elif is_rgb: maxval = max(chain(stroke_color, bg_color)) depth = 3 if not transparency else 4 fmt = f'>{depth}B'.encode('ascii') colours = (pack(fmt, *bg_color), pack(fmt, *stroke_color)) row_filter = invert_row_bits if colours is None else partial(row_to_color_values, colours=colours) with writable(out, 'wb') as f: write = f.write write('P7\n' f'# Created by {CREATOR}\n' f'WIDTH {width}\n' f'HEIGHT {height}\n' f'DEPTH {depth}\n' f'MAXVAL {maxval}\n' f'TUPLTYPE {tuple_type}\n' 'ENDHDR\n'.encode('ascii')) for row in row_iter: write(row_filter(row)) @colorful(dark='#000', light='#fff') def write_ppm(matrix, matrix_size, out, colormap, scale=1, border=None): """\ Serializes the matrix as `PPM <http://netpbm.sourceforge.net/doc/ppm.html>`_ image. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write binary data. :param scale: Indicates the size of a single module (default: 1 which corresponds to 1 x 1 pixel per module). :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). """ scale = int(scale) width, height, border = _valid_width_height_and_border(matrix_size, scale, border) if None in colormap.values(): raise ValueError('Transparency is not supported') for mt, clr in colormap.items(): colormap[mt] = _color_to_rgb(clr) row_iter = matrix_iter_verbose(matrix, matrix_size, scale, border) with writable(out, 'wb') as f: write = f.write write(f'P6 # Created by {CREATOR}\n{width} {height} 255\n'.encode('ascii')) for row in row_iter: write(b''.join(pack(b'>3B', *colormap[mt]) for mt in row)) def write_xpm(matrix, matrix_size, out, scale=1, border=None, dark='#000', light='#fff', name='img'): """\ Serializes the matrix as `XPM <https://en.wikipedia.org/wiki/X_PixMap>`_ image. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write binary data. :param scale: Indicates the size of a single module (default: 1 which corresponds to 1 x 1 pixel per module). :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). :param dark: Color of the modules (default: black). The color can be provided as ``(R, G, B)`` tuple, as web color name (like "red") or in hexadecimal format (``#RGB`` or ``#RRGGBB``). :param light: Optional background color (default: white). See `color` for valid values. ``None`` indicates a transparent background. :param str name: Name of the image (must be a valid C-identifier). Default: "img". """ width, height, border = _valid_width_height_and_border(matrix_size, scale, border) row_iter = matrix_iter(matrix, matrix_size, scale, border) stroke_color = color_to_rgb_hex(dark) if dark is not None else 'None' bg_color = color_to_rgb_hex(light) if light is not None else 'None' with writable(out, 'wt') as f: write = f.write write('/* XPM */\n' f'static char *{name}[] = {{\n' f'"{width} {height} 2 1",\n' f'" c {bg_color}",\n' f'"X c {stroke_color}",\n') for i, row in enumerate(row_iter): write(''.join(chain(['"'], (" " if not b else "X" for b in row), [f'"{("," if i < height - 1 else "")}\n']))) write('};\n') def write_xbm(matrix, matrix_size, out, scale=1, border=None, name='img'): """\ Serializes the matrix as `XBM <https://en.wikipedia.org/wiki/X_BitMap>`_ image. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write text data. :param scale: Indicates the size of a single module (default: 1 which corresponds to 1 x 1 in the provided unit per module). :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). :param name: Prefix for the variable names. Default: "img". The prefix is used to construct the variable names: ```#define <prefix>_width``` ```static unsigned char <prefix>_bits[]``` """ width, height, border = _valid_width_height_and_border(matrix_size, scale, border) row_iter = matrix_iter(matrix, matrix_size, scale, border) with writable(out, 'wt') as f: write = f.write write(f'#define {name}_width {width}\n' f'#define {name}_height {height}\n' f'static unsigned char {name}_bits[] = {{\n') for i, row in enumerate(row_iter, start=1): iter_ = zip_longest(*[iter(row)] * 8, fillvalue=0x0) # Reverse bits since XBM uses little endian bits = [f'0x{reduce(lambda x, y: (x << 1) + y, bits[::-1]):02x}' for bits in iter_] write(' ') write(', '.join(bits)) write(',\n' if i < height else '\n') write('};\n') def write_tex(matrix, matrix_size, out, scale=1, border=None, dark='black', unit='pt', url=None): """\ Serializes the matrix as LaTeX PGF picture. Requires the `PGF/TikZ <https://en.wikipedia.org/wiki/PGF/TikZ>`_ package (i.e. ``\\usepackage{pgf}``) in the LaTeX source. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write text data. :param scale: Indicates the size of a single module (default: 1 which corresponds to 1 x 1 in the provided unit per module). :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). :param str dark: LaTeX color name. The color name is taken at it is, so ensure that it refers either to a default color name or that the color was defined previously. :param unit: Unit of the drawing (default: ``pt``) :param url: Optional URL where the QR code should point to. Requires the "hyperref" package. Default: ``None``. """ def point(x, y): return f'\\pgfqpoint{{{x}{unit}}}{{{y}{unit}}}' check_valid_scale(scale) check_valid_border(border) border = get_border(matrix_size, border) end_marker = '' with writable(out, 'wt') as f: write = f.write write(f'% Creator: {CREATOR}\n') write(f'% Date: {time.strftime("%Y-%m-%dT%H:%M:%S")}\n') if url: write(f'\\href{{{url}}}{{') end_marker = '}' write('\\begin{pgfpicture}\n') write(f' \\pgfsetlinewidth{{{scale}{unit}}}\n') if dark and dark != 'black': write(f' \\color{{{dark}}}\n') x, y = border, -border for (x1, y1), (x2, y2) in matrix_to_lines(matrix, x, y, incby=-1): write(f' \\pgfpathmoveto{{{point(x1 * scale, y1 * scale)}}}\n') write(f' \\pgfpathlineto{{{point(x2 * scale, y2 * scale)}}}\n') write(' \\pgfusepath{stroke}\n') write(f'\\end{{pgfpicture}}{end_marker}\n') def write_terminal(matrix, matrix_size, out, border=None): """\ Function to write to a terminal which supports ANSI escape codes. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write text. :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). """ with writable(out, 'wt') as f: write = f.write colours = [f'\033[{i}m' for i in (7, 49)] for row in matrix_iter(matrix, matrix_size, scale=1, border=border): prev_bit = -1 cnt = 0 for bit in row: if bit == prev_bit: cnt += 1 else: if cnt: write(colours[prev_bit]) write(' ' * cnt) write('\033[0m') # reset color prev_bit = bit cnt = 1 if cnt: write(colours[prev_bit]) write(' ' * cnt) write('\033[0m') # reset color write('\n') def write_terminal_win(matrix, matrix_size, border=None): # pragma: no cover """\ Function to write a QR code to a MS Windows terminal. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). """ import sys import struct import ctypes write = sys.stdout.write std_out = ctypes.windll.kernel32.GetStdHandle(-11) csbi = ctypes.create_string_buffer(22) res = ctypes.windll.kernel32.GetConsoleScreenBufferInfo(std_out, csbi) if not res: raise OSError('Cannot find information about the console. ' 'Not running on the command line?') default_color = struct.unpack(b'hhhhHhhhhhh', csbi.raw)[4] set_color = partial(ctypes.windll.kernel32.SetConsoleTextAttribute, std_out) colours = (240, default_color) for row in matrix_iter(matrix, matrix_size, scale=1, border=border): prev_bit = -1 cnt = 0 for bit in row: if bit == prev_bit: cnt += 1 else: if cnt: set_color(colours[prev_bit]) write(' ' * cnt) prev_bit = bit cnt = 1 if cnt: set_color(colours[prev_bit]) write(' ' * cnt) set_color(default_color) # reset color write('\n') def write_terminal_compact(matrix, matrix_size, out, border=None): """\ Function to write a QR code to a terminal using unicode half-block characters. Custom colors are not used. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: Filename or a file-like object supporting to write text. :param int border: Integer indicating the size of the quiet zone. If set to ``None`` (default), the recommended border size will be used (``4`` for QR Codes, ``2`` for Micro QR Codes). """ blocks = {(1, 1): ' ', (0, 1): '\u2580', # Upper half block (1, 0): '\u2584', # Lower half block (0, 0): '\u2588', # Full block } it = [matrix_iter(matrix, matrix_size, scale=1, border=border)] * 2 with writable(out, 'wt') as f: write = f.write for top_row, bottom_row in zip_longest(*it, fillvalue=repeat(1)): write(''.join(blocks[pair] for pair in zip(top_row, bottom_row))) write('\n') def _color_to_rgb_or_rgba(color, alpha_float=True): """\ Returns the provided color as ``(R, G, B)`` or ``(R, G, B, A)`` tuple. If the alpha value is opaque, an RGB tuple is returned, otherwise an RGBA tuple. :param color: A web color name (i.e. ``darkblue``) or a hexadecimal value (``#RGB`` or ``#RRGGBB``) or a RGB(A) tuple (i.e. ``(R, G, B)`` or ``(R, G, B, A)``) :param bool alpha_float: Indicates if the alpha value should be returned as float value. If ``False``, the alpha value is an integer value in the range of ``0 .. 254``. :rtype: tuple """ rgba = _color_to_rgba(color, alpha_float=alpha_float) if rgba[3] in (1.0, 255): return rgba[:3] return rgba def _color_to_webcolor(color, allow_css3_colors=True, optimize=True): """\ Returns either a hexadecimal code or a color name. :param color: A web color name (i.e. ``darkblue``) or a hexadecimal value (``#RGB`` or ``#RRGGBB``) or a RGB(A) tuple (i.e. ``(R, G, B)`` or ``(R, G, B, A)``) :param bool allow_css3_colors: Indicates if a CSS3 color value like rgba(R G, B, A) is an acceptable result. :param bool optimize: Inidcates if the shortest possible color value should be returned (default: ``True``). :rtype: str :return: The provided color as web color: ``#RGB``, ``#RRGGBB``, ``rgba(R, G, B, A)``, or web color name. """ if _color_is_black(color): return '#000' elif _color_is_white(color): return '#fff' clr = _color_to_rgb_or_rgba(color) alpha_channel = None if len(clr) == 4: if allow_css3_colors: return 'rgba({0},{1},{2},{3})'.format(*clr) # noqa UP030 alpha_channel = clr[3] clr = clr[:3] hx = '#{0:02x}{1:02x}{2:02x}'.format(*clr) # noqa UP030 if optimize: if hx == '#d2b48c': hx = 'tan' # shorter elif hx == '#ff0000': hx = 'red' # shorter elif hx[1] == hx[2] and hx[3] == hx[4] and hx[5] == hx[6]: hx = f'#{hx[1]}{hx[3]}{hx[5]}' return hx if alpha_channel is None else (hx, alpha_channel) def color_to_rgb_hex(color): """\ Returns the provided color in hexadecimal representation. :param color: A web color name (i.e. ``darkblue``) or a hexadecimal value (``#RGB`` or ``#RRGGBB``) or a RGB(A) tuple (i.e. ``(R, G, B)`` or ``(R, G, B, A)``) :returns: ``#RRGGBB``. """ return '#{0:02x}{1:02x}{2:02x}'.format(*_color_to_rgb(color)) # noqa UP030 def _color_is_black(color): """\ Returns if the provided color represents "black". :param color: A web color name (i.e. ``darkblue``) or a hexadecimal value (``#RGB`` or ``#RRGGBB``) or a RGB(A) tuple (i.e. ``(R, G, B)`` or ``(R, G, B, A)``) :return: ``True`` if color is represents black, otherwise ``False``. """ try: color = color.lower() except AttributeError: pass return color in ('#000', '#000000', 'black', (0, 0, 0), (0, 0, 0, 255), (0, 0, 0, 1.0)) def _color_is_white(color): """\ Returns if the provided color represents "black". :param color: A web color name (i.e. ``darkblue``) or a hexadecimal value (``#RGB`` or ``#RRGGBB``) or a RGB(A) tuple (i.e. ``(R, G, B)`` or ``(R, G, B, A)``) :return: ``True`` if color is represents white, otherwise ``False``. """ try: color = color.lower() except AttributeError: pass return color in ('#fff', '#ffffff', 'white', (255, 255, 255), (255, 255, 255, 255), (255, 255, 255, 1.0)) def _color_to_rgb(color): """\ Converts web color names like "red" or hexadecimal values like "#36c", "#FFFFFF" and RGB tuples like ``(255, 255 255)`` into a (R, G, B) tuple. :param color: A web color name (i.e. ``darkblue``) or a hexadecimal value (``#RGB`` or ``#RRGGBB``) or a RGB tuple (i.e. ``(R, G, B)``)) :return: ``(R, G, B)`` tuple. """ rgb = _color_to_rgb_or_rgba(color) if len(rgb) != 3: raise ValueError(f'The alpha channel {rgb[3]} in color "{color}" cannot be ' 'converted to RGB') return rgb def _color_to_rgba(color, alpha_float=True): """\ Returns a (R, G, B, A) tuple. :param color: A web color name (i.e. ``darkblue``) or a hexadecimal value (``#RGB`` or ``#RRGGBB``) or a RGB(A) tuple (i.e. ``(R, G, B)`` or ``(R, G, B, A)``) :param bool alpha_float: Indicates if the alpha value should be returned as float value. If ``False``, the alpha value is an integer value in the range of ``0 .. 254``. :return: ``(R, G, B, A)`` tuple. """ res = [] alpha_channel = (1.0,) if alpha_float else (255,) if isinstance(color, tuple): col_length = len(color) is_valid = False if 3 <= col_length <= 4: for i, part in enumerate(color[:3]): is_valid = 0 <= part <= 255 res.append(part) if not is_valid or i == 2: break if is_valid: if col_length == 4: res.append(_alpha_value(color[3], alpha_float)) else: res.append(alpha_channel[0]) if is_valid: return tuple(res) raise ValueError(f'Unsupported color "{color}"') try: return _NAME2RGB[color.lower()] + alpha_channel except KeyError: try: clr = _hex_to_rgb_or_rgba(color, alpha_float=alpha_float) if len(clr) == 4: return clr else: return clr + alpha_channel except ValueError: raise ValueError(f'Unsupported color "{color}". Neither a known web ' 'color name nor a color in hexadecimal format.') def _hex_to_rgb_or_rgba(color, alpha_float=True): """\ Helper function to convert a color provided in hexadecimal format (``#RGB`` or ``#RRGGBB``) to a RGB(A) tuple. :param str color: Hexadecimal color name. :param bool alpha_float: Indicates if the alpha value should be returned as float value. If ``False``, the alpha value is an integer value in the range of ``0 .. 254``. :return: Tuple of integer values representing a RGB(A) color. :rtype: tuple :raises: :py:exc:`ValueError` in case the provided string could not converted into a RGB(A) tuple """ if color[0] == '#': color = color[1:] if 2 < len(color) < 5: # Expand RGB -> RRGGBB and RGBA -> RRGGBBAA color = ''.join([color[i] * 2 for i in range(len(color))]) color_len = len(color) if color_len not in (6, 8): raise ValueError(f'Input #{color} is not in #RRGGBB nor in #RRGGBBAA format') res = tuple([int(color[i:i + 2], 16) for i in range(0, color_len, 2)]) if alpha_float and color_len == 8: res = res[:3] + (_alpha_value(res[3], alpha_float),) return res _ALPHA_COMMONS = {255: 1.0, 128: .5, 64: .25, 32: .125, 16: .625, 0: 0.0} def _alpha_value(color, alpha_float): if alpha_float: if not isinstance(color, float): if 0 <= color <= 255: return _ALPHA_COMMONS.get(color, float('%.02f' % (color / 255.0))) else: if 0 <= color <= 1.0: return color else: if not isinstance(color, float): if 0 <= color <= 255: return color else: if 0 <= color <= 1.0: return color * 255.0 raise ValueError(f'Invalid alpha channel value: {color}') def _invert_color(rgb_or_rgba): """\ Returns the inverse color for the provided color. This function does not check if the color is a valid RGB / RGBA color. :param rgb: (R, G, B) or (R, G, B, A) tuple. """ return tuple([255 - c for c in rgb_or_rgba]) # <https://www.w3.org/TR/css-color-3/#svg-color> _NAME2RGB = { 'aliceblue': (240, 248, 255), 'antiquewhite': (250, 235, 215), 'aqua': (0, 255, 255), 'aquamarine': (127, 255, 212), 'azure': (240, 255, 255), 'beige': (245, 245, 220), 'bisque': (255, 228, 196), 'black': (0, 0, 0), 'blanchedalmond': (255, 235, 205), 'blue': (0, 0, 255), 'blueviolet': (138, 43, 226), 'brown': (165, 42, 42), 'burlywood': (222, 184, 135), 'cadetblue': (95, 158, 160), 'chartreuse': (127, 255, 0), 'chocolate': (210, 105, 30), 'coral': (255, 127, 80), 'cornflowerblue': (100, 149, 237), 'cornsilk': (255, 248, 220), 'crimson': (220, 20, 60), 'cyan': (0, 255, 255), 'darkblue': (0, 0, 139), 'darkcyan': (0, 139, 139), 'darkgoldenrod': (184, 134, 11), 'darkgray': (169, 169, 169), 'darkgreen': (0, 100, 0), 'darkgrey': (169, 169, 169), 'darkkhaki': (189, 183, 107), 'darkmagenta': (139, 0, 139), 'darkolivegreen': (85, 107, 47), 'darkorange': (255, 140, 0), 'darkorchid': (153, 50, 204), 'darkred': (139, 0, 0), 'darksalmon': (233, 150, 122), 'darkseagreen': (143, 188, 143), 'darkslateblue': (72, 61, 139), 'darkslategray': (47, 79, 79), 'darkslategrey': (47, 79, 79), 'darkturquoise': (0, 206, 209), 'darkviolet': (148, 0, 211), 'deeppink': (255, 20, 147), 'deepskyblue': (0, 191, 255), 'dimgray': (105, 105, 105), 'dimgrey': (105, 105, 105), 'dodgerblue': (30, 144, 255), 'firebrick': (178, 34, 34), 'floralwhite': (255, 250, 240), 'forestgreen': (34, 139, 34), 'fuchsia': (255, 0, 255), 'gainsboro': (220, 220, 220), 'ghostwhite': (248, 248, 255), 'gold': (255, 215, 0), 'goldenrod': (218, 165, 32), 'gray': (128, 128, 128), 'green': (0, 128, 0), 'greenyellow': (173, 255, 47), 'grey': (128, 128, 128), 'honeydew': (240, 255, 240), 'hotpink': (255, 105, 180), 'indianred': (205, 92, 92), 'indigo': (75, 0, 130), 'ivory': (255, 255, 240), 'khaki': (240, 230, 140), 'lavender': (230, 230, 250), 'lavenderblush': (255, 240, 245), 'lawngreen': (124, 252, 0), 'lemonchiffon': (255, 250, 205), 'lightblue': (173, 216, 230), 'lightcoral': (240, 128, 128), 'lightcyan': (224, 255, 255), 'lightgoldenrodyellow': (250, 250, 210), 'lightgray': (211, 211, 211), 'lightgreen': (144, 238, 144), 'lightgrey': (211, 211, 211), 'lightpink': (255, 182, 193), 'lightsalmon': (255, 160, 122), 'lightseagreen': (32, 178, 170), 'lightskyblue': (135, 206, 250), 'lightslategray': (119, 136, 153), 'lightslategrey': (119, 136, 153), 'lightsteelblue': (176, 196, 222), 'lightyellow': (255, 255, 224), 'lime': (0, 255, 0), 'limegreen': (50, 205, 50), 'linen': (250, 240, 230), 'magenta': (255, 0, 255), 'maroon': (128, 0, 0), 'mediumaquamarine': (102, 205, 170), 'mediumblue': (0, 0, 205), 'mediumorchid': (186, 85, 211), 'mediumpurple': (147, 112, 219), 'mediumseagreen': (60, 179, 113), 'mediumslateblue': (123, 104, 238), 'mediumspringgreen': (0, 250, 154), 'mediumturquoise': (72, 209, 204), 'mediumvioletred': (199, 21, 133), 'midnightblue': (25, 25, 112), 'mintcream': (245, 255, 250), 'mistyrose': (255, 228, 225), 'moccasin': (255, 228, 181), 'navajowhite': (255, 222, 173), 'navy': (0, 0, 128), 'oldlace': (253, 245, 230), 'olive': (128, 128, 0), 'olivedrab': (107, 142, 35), 'orange': (255, 165, 0), 'orangered': (255, 69, 0), 'orchid': (218, 112, 214), 'palegoldenrod': (238, 232, 170), 'palegreen': (152, 251, 152), 'paleturquoise': (175, 238, 238), 'palevioletred': (219, 112, 147), 'papayawhip': (255, 239, 213), 'peachpuff': (255, 218, 185), 'peru': (205, 133, 63), 'pink': (255, 192, 203), 'plum': (221, 160, 221), 'powderblue': (176, 224, 230), 'purple': (128, 0, 128), 'red': (255, 0, 0), 'rosybrown': (188, 143, 143), 'royalblue': (65, 105, 225), 'saddlebrown': (139, 69, 19), 'salmon': (250, 128, 114), 'sandybrown': (244, 164, 96), 'seagreen': (46, 139, 87), 'seashell': (255, 245, 238), 'sienna': (160, 82, 45), 'silver': (192, 192, 192), 'skyblue': (135, 206, 235), 'slateblue': (106, 90, 205), 'slategray': (112, 128, 144), 'slategrey': (112, 128, 144), 'snow': (255, 250, 250), 'springgreen': (0, 255, 127), 'steelblue': (70, 130, 180), 'tan': (210, 180, 140), 'teal': (0, 128, 128), 'thistle': (216, 191, 216), 'tomato': (255, 99, 71), 'turquoise': (64, 224, 208), 'violet': (238, 130, 238), 'wheat': (245, 222, 179), 'white': (255, 255, 255), 'whitesmoke': (245, 245, 245), 'yellow': (255, 255, 0), 'yellowgreen': (154, 205, 50), } def _make_colormap(matrix_width, matrix_height, dark, light, finder_dark=False, finder_light=False, data_dark=False, data_light=False, version_dark=False, version_light=False, format_dark=False, format_light=False, alignment_dark=False, alignment_light=False, timing_dark=False, timing_light=False, separator=False, dark_module=False, quiet_zone=False): """\ Creates and returns a module type -> color map. The result can be used for serializers which support more than two colors. Examples .. code-block:: python # All dark modules (data, version, ...) will be dark red, the dark # modules of the finder patterns will be blue # The light modules will be rendered in the serializer's default color # (usually white) cm = colormap(dark='darkred', finder_dark='blue') # Use the serializer's default colors for dark / light modules # (usually black and white) but the dark modules of the timing patterns # will be brown cm = colormap(timing_dark=(165, 42, 42)) :param int matrix_width: Matrix width :param int matrix_height: Matrix height :param dark: Default color of dark modules :param light: Default color of light modules :param finder_dark: Color of the dark modules of the finder patterns. :param finder_light: Color of the light modules of the finder patterns. :param data_dark: Color of the dark data modules. :param data_light: Color of the light data modules. :param version_dark: Color of the dark modules of the version information. :param version_light: Color of the light modules of the version information. :param format_dark: Color of the dark modules of the format information. :param format_light: Color of the light modules of the format information. :param alignment_dark: Color of the dark modules of the alignment patterns. :param alignment_light: Color of the light modules of the alignment patterns. :param timing_dark: Color of the dark modules of the timing patterns. :param timing_light: Color of the light modules of the timing patterns. :param separator: Color of the separator. :param dark_module: Color of the dark module. :param quiet_zone: Color of the quiet zone / border. :rtype: dict """ unsupported = () is_square = matrix_width == matrix_height if not is_square: # rMQR unsupported = [consts.TYPE_DARKMODULE, consts.TYPE_VERSION_DARK, consts.TYPE_VERSION_LIGHT] if matrix_width < 43: # rMQR R11x27, R13x27, … unsupported.extend((consts.TYPE_ALIGNMENT_PATTERN_DARK, consts.TYPE_ALIGNMENT_PATTERN_LIGHT)) elif matrix_width < 45: # QR Code version 7 unsupported = [consts.TYPE_VERSION_DARK, consts.TYPE_VERSION_LIGHT] if matrix_width < 21: # Lesser than QR Code version 1 => Micro QR code unsupported.extend([consts.TYPE_DARKMODULE, consts.TYPE_ALIGNMENT_PATTERN_DARK, consts.TYPE_ALIGNMENT_PATTERN_LIGHT]) mt2color = { consts.TYPE_FINDER_PATTERN_DARK: finder_dark if finder_dark is not False else dark, consts.TYPE_FINDER_PATTERN_LIGHT: finder_light if finder_light is not False else light, consts.TYPE_DATA_DARK: data_dark if data_dark is not False else dark, consts.TYPE_DATA_LIGHT: data_light if data_light is not False else light, consts.TYPE_VERSION_DARK: version_dark if version_dark is not False else dark, consts.TYPE_VERSION_LIGHT: version_light if version_light is not False else light, consts.TYPE_ALIGNMENT_PATTERN_DARK: alignment_dark if alignment_dark is not False else dark, consts.TYPE_ALIGNMENT_PATTERN_LIGHT: alignment_light if alignment_light is not False else light, consts.TYPE_TIMING_DARK: timing_dark if timing_dark is not False else dark, consts.TYPE_TIMING_LIGHT: timing_light if timing_light is not False else light, consts.TYPE_FORMAT_DARK: format_dark if format_dark is not False else dark, consts.TYPE_FORMAT_LIGHT: format_light if format_light is not False else light, consts.TYPE_SEPARATOR: separator if separator is not False else light, consts.TYPE_DARKMODULE: dark_module if dark_module is not False else dark, consts.TYPE_QUIET_ZONE: quiet_zone if quiet_zone is not False else light, } return {mt: val for mt, val in mt2color.items() if mt not in unsupported} _VALID_SERIALIZERS = { 'svg': write_svg, 'png': write_png, 'eps': write_eps, 'txt': write_txt, 'pdf': write_pdf, 'ans': write_terminal, 'pbm': write_pbm, 'pam': write_pam, 'ppm': write_ppm, 'tex': write_tex, 'xbm': write_xbm, 'xpm': write_xpm, } def save(matrix, matrix_size, out, kind=None, **kw): """\ Serializes the matrix in any of the supported formats. :param matrix: The matrix to serialize. :param tuple(int, int) matrix_size: Tuple of width and height of the matrix. :param out: A filename or a writable file-like object with a ``name`` attribute. If a stream like :py:class:`io.ByteIO` or :py:class:`io.StringIO` object without a ``name`` attribute is provided, use the `kind` parameter to specify the serialization format. :param kind: If the desired output format cannot be extracted from the filename, this parameter can be used to indicate the serialization format (i.e. "svg" to enforce SVG output) :param kw: Any of the supported keywords by the specific serialization method. """ is_stream = False if kind is None: try: fname = out.name is_stream = True except AttributeError: fname = out ext = fname[fname.rfind('.') + 1:].lower() else: ext = kind.lower() is_svgz = not is_stream and ext == 'svgz' try: serializer = _VALID_SERIALIZERS[ext if not is_svgz else 'svg'] except KeyError: raise ValueError(f'Unknown file extension ".{ext}"') if is_svgz: with gzip.open(out, 'wb', compresslevel=kw.pop('compresslevel', 9)) as f: serializer(matrix, matrix_size, f, **kw) else: serializer(matrix, matrix_size, out, **kw) ''', }, } class _BundledLoader(importlib.abc.Loader): def __init__(self, fullname, source, is_package): self.fullname = fullname self.source = source self.is_package = is_package def create_module(self, spec): return None def exec_module(self, module): module_path = self.fullname.replace(".", "/") + ".py" module.__file__ = "<%s:%s>" % (_BUNDLE_TAG, module_path) code = compile( self.source, module.__file__, "exec", dont_inherit=True, ) exec(code, module.__dict__) if self.is_package: package_path = self.fullname.replace(".", "/") module.__path__ = ["<%s:%s>" % (_BUNDLE_TAG, package_path)] class _BundledFinder(importlib.abc.MetaPathFinder): def __init__(self, prefixes): self._prefixes = prefixes def _owns(self, fullname): for prefix in self._prefixes: if fullname == prefix or fullname.startswith(prefix + "."): return True return False def find_spec(self, fullname, path, target=None): if not self._owns(fullname): return None item = _SOURCES.get(fullname) if item is None: return None loader = _BundledLoader(fullname, item["source"], item["is_package"]) return importlib.util.spec_from_loader( fullname, loader, is_package=item["is_package"], ) def install(): """Зарегистрировать bundled-пакеты в sys.meta_path.""" global _FINDER if _FINDER is not None: return _FINDER = _BundledFinder(_PREFIXES) sys.meta_path.insert(0, _FINDER) install()