# # Copyright (C) 2010 Martin Owens # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110, USA. # """ Base module for rendering barcodes for Inkscape. """ import itertools import sys from lxml import etree (TEXT_POS_BOTTOM, TEXT_POS_TOP) = range(2) (WHITE_BAR, BLACK_BAR, TALL_BAR) = range(3) TEXT_TEMPLATE = 'font-size:%dpx;text-align:center;text-anchor:middle;' SVG_URI = u'http://www.w3.org/2000/svg' # pylint: disable=abstract-class-not-used class Barcode(object): """Provide a base class for all barcode renderers""" default_height = 30 font_size = 9 name = None def error(self, text, msg): """Cause an error to be reported""" sys.stderr.write( "Error encoding '%s' as %s barcode: %s\n" % (text, self.name, msg)) return "ERROR" def encode(self, text): """ Replace this with the encoding function, it should return a string of ones and zeros """ raise NotImplementedError("You need to write an encode() function.") def __init__(self, param): param = param or {} self.document = param.get('document', None) self.known_ids = [] self._extra = [] self.pos_x = int(param.get('x', 0)) self.pos_y = int(param.get('y', 0)) self.text = param.get('text', None) self.scale = param.get('scale', 1) self.height = param.get('height', self.default_height) self.pos_text = param.get('text_pos', TEXT_POS_BOTTOM) if self.document: self.known_ids = list(self.document.xpath('//@id')) if not self.text: raise ValueError("No string specified for barcode.") def get_id(self, name='element'): """Get the next useful id (and claim it)""" index = 0 while name in self.known_ids: index += 1 name = 'barcode%d' % index self.known_ids.append(name) return name def add_extra_barcode(self, barcode, **kw): """Add an extra barcode along side this one, used for ean13 extras""" from . import getBarcode kw['height'] = self.height kw['document'] = self.document kw['scale'] = None self._extra.append(getBarcode(barcode, **kw).generate()) def generate(self): """Generate the actual svg from the coding""" string = self.encode(self.text) if string == 'ERROR': return name = self.get_id('barcode') # use an svg group element to contain the barcode barcode = etree.Element('{%s}g' % SVG_URI) barcode.set('id', name) barcode.set('style', 'fill: black;') if self.scale: barcode.set('transform', 'translate(%d,%d) scale(%f)' % ( self.pos_x, self.pos_y, self.scale)) else: barcode.set('transform', 'translate(%d,%d)' % ( self.pos_x, self.pos_y)) bar_id = 1 bar_offset = 0 tops = set() for datum in self.graphical_array(string): # Datum 0 tells us what style of bar is to come next style = self.get_style(int(datum[0])) # Datum 1 tells us what width in units, # style tells us how wide a unit is width = int(datum[1]) * int(style['width']) if style['write']: tops.add(style['top']) rect = etree.SubElement(barcode, '{%s}rect' % SVG_URI) rect.set('x', str(bar_offset)) rect.set('y', str(style['top'])) if self.pos_text == TEXT_POS_TOP: rect.set('y', str(style['top'] + self.font_size)) rect.set('id', "%s_bar%d" % (name, bar_id)) rect.set('width', str(width)) rect.set('height', str(style['height'])) bar_offset += width bar_id += 1 for extra in self._extra: if extra is not None: barcode.append(extra) bar_width = bar_offset # Add text at the bottom of the barcode text = etree.SubElement(barcode, '{%s}text' % SVG_URI) text.set('x', str(int(bar_width / 2))) text.set('y', str(min(tops) + self.font_size - 1)) if self.pos_text == TEXT_POS_BOTTOM: text.set('y', str(self.height + max(tops) + self.font_size)) text.set('style', TEXT_TEMPLATE % self.font_size) text.set('{http://www.w3.org/XML/1998/namespace}space', 'preserve') text.set('id', '%s_text' % name) text.text = str(self.text) return barcode def graphical_array(self, code): """Converts black and white markets into a space array""" return [(x, len(list(y))) for x, y in itertools.groupby(code)] def get_style(self, index): """Returns the styles that should be applied to each bar""" result = {'width' : 1, 'top' : 0, 'write' : True} if index == BLACK_BAR: result['height'] = int(self.height) if index == TALL_BAR: result['height'] = int(self.height) + int(self.font_size / 2) if index == WHITE_BAR: result['write'] = False return result