Split view
‭example.py
1#!/usr/bin/env python3 1#!/usr/bin/env python3 2 2 3import math 3import math 4import itertools 4import itertools 5import textwrap 5import textwrap
6 6 7import click 7import click 8from reedmuller import reedmuller 8from reedmuller import reedmuller 9 9 10 10 11class Tag: 11class Tag: 12 """ Helper class to ease creation of SVG. All API functions that create SVG allow you to substitute this with your 12 """ Helper class to ease creation of SVG. All API functions that create SVG allow you to substitute this with your 13 own implementation by passing a ``tag`` parameter. """ 13 own implementation by passing a ``tag`` parameter. """ 14 14 15 def __init__(self, name, children=None, root=False, **attrs): 15 def __init__(self, name, children=None, root=False, **attrs): 16 if (fill := attrs.get('fill')) and isinstance(fill, tuple): 16 if (fill := attrs.get('fill')) and isinstance(fill, tuple): 17 attrs['fill'], attrs['fill-opacity'] = fill 17 attrs['fill'], attrs['fill-opacity'] = fill 18 if (stroke := attrs.get('stroke')) and isinstance(stroke, tuple): 18 if (stroke := attrs.get('stroke')) and isinstance(stroke, tuple): 19 attrs['stroke'], attrs['stroke-opacity'] = stroke 19 attrs['stroke'], attrs['stroke-opacity'] = stroke 20 self.name, self.attrs = name, attrs 20 self.name, self.attrs = name, attrs 21 self.children = children or [] 21 self.children = children or [] 22 self.root = root 22 self.root = root 23 23 24 def __str__(self): 24 def __str__(self): 25 prefix = '<?xml version="1.0" encoding="utf-8"?>\n' if self.root else '' 25 prefix = '<?xml version="1.0" encoding="utf-8"?>\n' if self.root else '' 26 opening = ' '.join([self.name] + [f'{key.replace("__", ":").replace("_", "-")}="{value}"' for key, value in self.attrs.items()]) 26 opening = ' '.join([self.name] + [f'{key.replace("__", ":").replace("_", "-")}="{value}"' for key, value in self.attrs.items()]) 27 if self.children: 27 if self.children: 28 children = '\n'.join(textwrap.indent(str(c), ' ') for c in self.children) 28 children = '\n'.join(textwrap.indent(str(c), ' ') for c in self.children) 29 return f'{prefix}<{opening}>\n{children}\n</{self.name}>' 29 return f'{prefix}<{opening}>\n{children}\n</{self.name}>'
30 else: 30 else: 31 return f'{prefix}<{opening}/>' 31 return f'{prefix}<{opening}/>' 32 32 33 33 34 @classmethod 34 @classmethod 35 def setup_svg(kls, tags, bounds, margin=0, unit='mm', pagecolor='white'): 35 def setup_svg(kls, tags, bounds, unit='mm', pagecolor='white', inkscape=False): 36 (min_x, min_y), (max_x, max_y) = bounds 36 (min_x, min_y), (max_x, max_y) = bounds 37 38 if margin: 39 min_x -= margin 40 min_y -= margin 41 max_x += margin 42 max_y += margin 43 37 44 w, h = max_x - min_x, max_y - min_y 38 w, h = max_x - min_x, max_y - min_y 45 w = 1.0 if math.isclose(w, 0.0) else w 39 w = 1.0 if math.isclose(w, 0.0) else w 46 h = 1.0 if math.isclose(h, 0.0) else h 40 h = 1.0 if math.isclose(h, 0.0) else h 47 41 42 if inkscape: 43 tags.insert(0, kls('sodipodi:namedview', [], id='namedview1', pagecolor=pagecolor, 44 inkscape__document_units=unit)) 48 namespaces = dict( 45 namespaces = dict( 49 xmlns="http://www.w3.org/2000/svg", 46 xmlns="http://www.w3.org/2000/svg", 47 xmlns__xlink="http://www.w3.org/1999/xlink", 48 xmlns__sodipodi='http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd', 49 xmlns__inkscape='http://www.inkscape.org/namespaces/inkscape') 50 51 else: 52 namespaces = dict( 53 xmlns="http://www.w3.org/2000/svg", 50 xmlns__xlink="http://www.w3.org/1999/xlink") 54 xmlns__xlink="http://www.w3.org/1999/xlink") 51 55 52 return kls('svg', tags, 56 return kls('svg', tags, 53 width=f'{w}{unit}', height=f'{h}{unit}', 57 width=f'{w}{unit}', height=f'{h}{unit}', 54 viewBox=f'{min_x} {min_y} {w} {h}', 58 viewBox=f'{min_x} {min_y} {w} {h}', 55 style=f'background-color:{pagecolor}', 59 style=f'background-color:{pagecolor}',
56 **namespaces, 60 **namespaces, 57 root=True) 61 root=True) 58 62 59 63 60@click.command() 64@click.command() 61@click.option('-h', '--height', type=float, default=20, help='Bar height in mm') 65@click.option('-h', '--height', type=float, default=20, help='Bar height in mm') 62@click.option('-t/-n', '--text/--no-text', default=True, help='Whether to add text containing the data under the bar code') 66@click.option('-t/-n', '--text/--no-text', default=True, help='Whether to add text containing the data under the bar code') 63@click.option('-f', '--font', default='sans-serif', help='Font for the text underneath the bar code') 67@click.option('-f', '--font', default='sans-serif', help='Font for the text underneath the bar code') 64@click.option('-s', '--font-size', type=float, default=12, help='Font size for the text underneath the bar code in points (pt)') 68@click.option('-s', '--font-size', type=float, default=12, help='Font size for the text underneath the bar code in points (pt)') 65@click.option('-b', '--bar-width', type=float, default=1.0, help='Bar width in mm') 69@click.option('-b', '--bar-width', type=float, default=1.0, help='Bar width in mm') 66@click.option('-m', '--margin', type=float, default=3.0, help='Margin around bar code in mm') 70@click.option('-m', '--margin', type=float, default=3.0, help='Margin around bar code in mm') 67@click.option('-c', '--color', default='black', help='SVG color for the bar code') 71@click.option('-c', '--color', default='black', help='SVG color for the bar code') 68@click.option('--text-color', default=None, help='SVG color for the text (defaults to the bar code\'s color)') 72@click.option('--text-color', default=None, help='SVG color for the text (defaults to the bar code\'s color)') 69@click.option('--dpi', type=float, default=96, help='DPI value to assume for internal SVG unit conversions') 73@click.option('--dpi', type=float, default=96, help='DPI value to assume for internal SVG unit conversions') 70@click.argument('data') 74@click.argument('data') 71@click.argument('outfile', type=click.File('w'), default='-') 75@click.argument('outfile', type=click.File('w'), default='-') 72def cli(data, outfile, height, text, font, font_size, bar_width, margin, color, text_color, dpi): 76def cli(data, outfile, height, text, font, font_size, bar_width, margin, color, text_color, dpi): 73 data = int(data, 16) 77 data = int(data, 16) 74 text_color = text_color or color 78 text_color = text_color or color 75 79 76 NUM_BITS = 26 80 NUM_BITS = 26 77 81 78 data_bits = [bool(data & (1<<i)) for i in range(NUM_BITS)] 82 data_bits = [bool(data & (1<<i)) for i in range(NUM_BITS)] 79 data_encoded = itertools.chain(*[ 83 data_encoded = itertools.chain(*[ 80 (a, not a) for a in data_bits 84 (a, not a) for a in data_bits 81 ]) 85 ]) 82 data_encoded = [True, False, True, False, *data_encoded, False, True, True, False, True] 86 data_encoded = [True, False, True, False, *data_encoded, False, True, True, False, True] 83 87 84 width = len(data_encoded) * bar_width 88 width = len(data_encoded) * bar_width 85 # 1 px = 0.75 pt 89 # 1 px = 0.75 pt 86 pt_to_mm = lambda pt: pt / 0.75 /dpi * 25.4 90 pt_to_mm = lambda pt: pt / 0.75 /dpi * 25.4 87 font_size = pt_to_mm(font_size) 91 font_size = pt_to_mm(font_size) 88 total_height = height + font_size*2 92 total_height = height + font_size*2 89 93 90 tags = [] 94 tags = [] 91 for key, group in itertools.groupby(enumerate(data_encoded), key=lambda x: x[1]): 95 for key, group in itertools.groupby(enumerate(data_encoded), key=lambda x: x[1]): 92 if key: 96 if key: 93 group = list(group) 97 group = list(group) 94 x0, _key = group[0] 98 x0, _key = group[0] 95 w = len(group) 99 w = len(group) 96 tags.append(Tag('path', stroke=color, stroke_width=w, d=f'M {(x0 + w/2)*bar_width} 0 l 0 {height}')) 100 tags.append(Tag('path', stroke=color, stroke_width=w, d=f'M {(x0 + w/2)*bar_width} 0 l 0 {height}')) 97 101 98 if text: 102 if text: 99 tags.append(Tag('text', children=[f'{data:07x}'], 103 tags.append(Tag('text', children=[f'{data:07x}'], 100 x=width/2, y=height + 0.5*font_size, 104 x=width/2, y=height + 0.5*font_size, 101 font_family=font, font_size=f'{font_size:.3f}px', 105 font_family=font, font_size=f'{font_size:.3f}px', 102 text_anchor='middle', dominant_baseline='hanging', 106 text_anchor='middle', dominant_baseline='hanging', 103 fill=text_color)) 107 fill=text_color)) 104 108
105 outfile.write(str(Tag.setup_svg(tags, bounds=((0, 0), (width, total_height)), margin=margin))) 109 outfile.write(str(Tag.setup_svg(tags, bounds=((0, 0), (width, total_height)), margin=margin))) 106 110 107 111 108if __name__ == '__main__': 112if __name__ == '__main__': 109 cli() 113 cli()