#!/usr/bin/env python3 import math import itertools import textwrap import click from reedmuller.reedmuller import ReedMuller class Tag: """ Helper class to ease creation of SVG. All API functions that create SVG allow you to substitute this with your own implementation by passing a ``tag`` parameter. """ def __init__(self, name, children=None, root=False, **attrs): if (fill := attrs.get('fill')) and isinstance(fill, tuple): attrs['fill'], attrs['fill-opacity'] = fill if (stroke := attrs.get('stroke')) and isinstance(stroke, tuple): attrs['stroke'], attrs['stroke-opacity'] = stroke self.name, self.attrs = name, attrs self.children = children or [] self.root = root def __str__(self): prefix = '\n' if self.root else '' opening = ' '.join([self.name] + [f'{key.replace("__", ":").replace("_", "-")}="{value}"' for key, value in self.attrs.items()]) if self.children: children = '\n'.join(textwrap.indent(str(c), ' ') for c in self.children) return f'{prefix}<{opening}>\n{children}\n' else: return f'{prefix}<{opening}/>' @classmethod def setup_svg(kls, tags, bounds, margin=0, unit='mm', pagecolor='white', inkscape=False): (min_x, min_y), (max_x, max_y) = bounds if margin: min_x -= margin min_y -= margin max_x += margin max_y += margin w, h = max_x - min_x, max_y - min_y w = 1.0 if math.isclose(w, 0.0) else w h = 1.0 if math.isclose(h, 0.0) else h if inkscape: tags.insert(0, kls('sodipodi:namedview', [], id='namedview1', pagecolor=pagecolor, inkscape__document_units=unit)) namespaces = dict( xmlns="http://www.w3.org/2000/svg", xmlns__xlink="http://www.w3.org/1999/xlink", xmlns__sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd', xmlns__inkscape='http://www.inkscape.org/namespaces/inkscape') else: namespaces = dict( xmlns="http://www.w3.org/2000/svg", xmlns__xlink="http://www.w3.org/1999/xlink") return kls('svg', tags, width=f'{w}{unit}', height=f'{h}{unit}', viewBox=f'{min_x} {min_y} {w} {h}', style=f'background-color:{pagecolor}', **namespaces, root=True) @click.command() @click.option('-h', '--height', type=float, default=20, help='Bar height in mm') @click.option('-t/-n', '--text/--no-text', default=True, help='Whether to add text containing the data under the bar code') @click.option('-f', '--font', default='sans-serif', help='Font for the text underneath the bar code') @click.option('-s', '--font-size', type=float, default=12, help='Font size for the text underneath the bar code in points (pt)') @click.option('-b', '--bar-width', type=float, default=1.0, help='Bar width in mm') @click.option('-m', '--margin', type=float, default=3.0, help='Margin around bar code in mm') @click.option('-c', '--color', default='black', help='SVG color for the bar code') @click.option('--text-color', default=None, help='SVG color for the text (defaults to the bar code\'s color)') @click.option('--dpi', type=float, default=96, help='DPI value to assume for internal SVG unit conversions') @click.argument('data') @click.argument('outfile', type=click.File('w'), default='-') def cli(data, outfile, height, text, font, font_size, bar_width, margin, color, text_color, dpi): data = int(data, 16) data &= 0x3ffffff text_color = text_color or color rm = ReedMuller(3, 5) data_bits = [bool(data & (1<