Source code for steenzout.barcode.writer

# -*- coding: utf-8 -*-

import os

import gzip
import logging
import xml.dom

try:
    import Image
    import ImageDraw
    import ImageFont
except ImportError:
    try:
        from PIL import Image, ImageDraw, ImageFont  # lint:ok
    except ImportError:
        __logger = logging.getLogger(__package__)
        __logger.info('PIL not found. Image output disabled.')
        Image = ImageDraw = ImageFont = None  # lint:ok

from abc import ABCMeta, abstractmethod

from steenzout.object import Object
from steenzout.barcode import metadata


SIZE = '{0:.3f}mm'
COMMENT = 'Autogenerated with %s %s' % (__package__, metadata.__release__)
PATH = os.path.dirname(os.path.abspath(__file__))
FONT = os.path.join(PATH, 'fonts/DejaVuSansMono.ttf')


[docs]def mm2px(value, dpi=300): """Converts the given value in millimeters into dots per inch. Args: value(float): value, in millimeters. dpi (int): resolution, in dots per inch. Returns: (float): value, in dots per inch. """ return (value * dpi) / 25.4
[docs]def pt2mm(value): """Converts given value in points to millimeters. Args: value (int): value in points. Returns: (float): value, in millimeters. """ return value * 0.352777778
def _set_attributes(element, **attributes): for key, value in attributes.items(): element.setAttribute(key, value)
[docs]def create_svg_object(): """Returns a blank SVG document. Returns: (:py:class:`xml.dom.minidom.DocumentType`): XML document. """ imp = xml.dom.getDOMImplementation() doctype = imp.createDocumentType( 'svg', '-//W3C//DTD SVG 1.1//EN', 'http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd' ) document = imp.createDocument(None, 'svg', doctype) _set_attributes(document.documentElement, version='1.1', xmlns='http://www.w3.org/2000/svg') return document
[docs]class Interface: """Writer interface.""" __metaclass__ = ABCMeta @classmethod def __subclasshook__(cls, clazz): if cls is Interface: if any('save' in C.__dict__ for C in clazz.__mro__): return True return NotImplemented @abstractmethod
[docs] def save(self, filename, content): """Saves contents to `filename`. Args: filename (str): filename without extension. content (str): output of rendering process. Returns: (str): the filename. """ return None
[docs]class Base(Object, Interface): """Base class for all writers. Initializes the basic writer options. Sub-classes can add more attributes and can set them directly or using `self.set_options(option=value)`. Args: initialize (function): Callback for initializing the inheriting writer. Is called: `callback_initialize(raw_code)` paint_module (function): Callback for painting one barcode module. Is called: `callback_paint_module(xpos, ypos, width, color)` paint_text (function): Callback for painting the text under the barcode. Is called: `callback_paint_text(xpos, ypos)` using `self.text` as text. finish (function): Callback for doing something with the completely rendered output. Is called: `return callback_finish()` and must return the rendered output. """ def __init__(self, initialize=None, paint_module=None, paint_text=None, finish=None): self._callbacks = dict( initialize=initialize, paint_module=paint_module, paint_text=paint_text, finish=finish ) self.module_width = 10 self.module_height = 10 self.font_size = 10 self.quiet_zone = 6.5 self.background = 'white' self.foreground = 'black' self.text = '' self.text_distance = 5 self.center_text = True
[docs] def calculate_size(self, modules_per_line, number_of_lines, dpi=300): """Calculates the size of the barcode in pixel. Args: modules_per_line (int): number of modules in one line. number_of_lines (int): number of lines of the barcode. dpi (int): DPI to calculate. Returns: (tuple[int, int]): Width and height of the barcode in pixel. """ width = 2 * self.quiet_zone + modules_per_line * self.module_width height = 2.0 + self.module_height * number_of_lines if self.font_size and self.text: height += pt2mm(self.font_size) / 2 + self.text_distance return int(mm2px(width, dpi)), int(mm2px(height, dpi))
@abstractmethod
[docs] def save(self, filename, output): """See :py:func:`Interface.save`."""
[docs] def register_callback(self, action, callback): """Register one of the three callbacks if not given at instance creation. Args: action (str): One of 'initialize', 'paint_module', 'paint_text', 'finish'. callback (function): The callback function for the given action. """ self._callbacks[action] = callback
[docs] def set_options(self, options): """Sets the given options as instance attributes (only if they are known). Args: options (dict): All known instance attributes and more if the child class has defined them before this call. """ for key, val in options.items(): key = key.lstrip('_') if hasattr(self, key): setattr(self, key, val)
[docs] def render(self, code): """Renders the barcode to whatever the inheriting writer provides, using the registered callbacks. Args: code (list[str]): list of strings matching the writer spec (only contain 0 or 1). """ if self._callbacks['initialize'] is not None: self._callbacks['initialize'](code) ypos = 1.0 for line in code: # Left quiet zone is x startposition xpos = self.quiet_zone for mod in line: if mod == '0': color = self.background else: color = self.foreground self._callbacks['paint_module'](xpos, ypos, self.module_width, color) xpos += self.module_width # Add right quiet zone to every line self._callbacks['paint_module'](xpos, ypos, self.quiet_zone, self.background) ypos += self.module_height if self.text and self._callbacks['paint_text'] is not None: ypos += self.text_distance if self.center_text: xpos = xpos / 2.0 else: xpos = self.quiet_zone + 4.0 self._callbacks['paint_text'](xpos, ypos) return self._callbacks['finish']()
[docs]class SVG(Base): def __init__(self): Base.__init__(self, self._init, self._create_module, self._create_text, self._finish) self.compress = False self.dpi = 25.4 self._document = None self._root = None def _init(self, code): width, height = self.calculate_size(len(code[0]), len(code), self.dpi) self._document = create_svg_object() self._root = self._document.documentElement attributes = dict(width=SIZE.format(width), height=SIZE.format(height)) _set_attributes(self._root, **attributes) self._root.appendChild(self._document.createComment(COMMENT)) background = self._document.createElement('rect') attributes = dict(width='100%', height='100%', style='fill:{0}'.format(self.background)) _set_attributes(background, **attributes) self._root.appendChild(background) def _create_module(self, xpos, ypos, width, color): element = self._document.createElement('rect') attributes = dict(x=SIZE.format(xpos), y=SIZE.format(ypos), width=SIZE.format(width), height=SIZE.format(self.module_height), style='fill:{0};'.format(color)) _set_attributes(element, **attributes) self._root.appendChild(element) def _create_text(self, xpos, ypos): element = self._document.createElement('text') attributes = dict(x=SIZE.format(xpos), y=SIZE.format(ypos), style='fill:{0};font-size:{1}pt;text-anchor:' 'middle;'.format(self.foreground, self.font_size)) _set_attributes(element, **attributes) text_element = self._document.createTextNode(self.text) element.appendChild(text_element) self._root.appendChild(element) def _finish(self): if self.compress: return self._document.toxml(encoding='UTF-8') else: return self._document.toprettyxml(indent=4 * ' ', newl=os.linesep, encoding='UTF-8')
[docs] def save(self, filename, content): """See :py:func:`Interface.save`.""" if self.compress: _filename = '%s.svgz' % filename with gzip.open('%s.svgz' % filename, 'wb') as output: output.write(content) else: _filename = '%s.svg' % filename with open(_filename, 'wb') as output: output.write(content) return _filename
if Image is None: ImageWriter = None else:
[docs] class ImageWriter(Base): def __init__(self): Base.__init__(self, self._init, self._paint_module, self._paint_text, self._finish) self.format = 'PNG' self.dpi = 300 self._image = None self._draw = None def _init(self, code): size = self.calculate_size(len(code[0]), len(code), self.dpi) self._image = Image.new('RGB', size, self.background) self._draw = ImageDraw.Draw(self._image) def _paint_module(self, xpos, ypos, width, color): size = [(mm2px(xpos, self.dpi), mm2px(ypos, self.dpi)), (mm2px(xpos + width, self.dpi), mm2px(ypos + self.module_height, self.dpi))] self._draw.rectangle(size, outline=color, fill=color) def _paint_text(self, xpos, ypos): font = ImageFont.truetype(FONT, self.font_size * 2) width, height = font.getsize(self.text) pos = (mm2px(xpos, self.dpi) - width // 2, mm2px(ypos, self.dpi) - height // 4) self._draw.text(pos, self.text, font=font, fill=self.foreground) def _finish(self): return self._image
[docs] def save(self, filename, output): """See :py:func:`Interface.save`.""" filename = '{0}.{1}'.format(filename, self.format.lower()) output.save(filename, self.format.upper()) return filename
DEFAULT_WRITER = SVG DEFAULT_WRITER_OPTIONS = { 'module_width': 0.2, 'module_height': 15.0, 'quiet_zone': 6.5, 'font_size': 10, 'text_distance': 5.0, 'background': 'white', 'foreground': 'black', 'write_text': True, 'text': '', }