From d2891b448a89707034419a95bc755c63b6924a71 Mon Sep 17 00:00:00 2001 From: jaseg Date: Mon, 28 Jul 2025 14:08:30 +0200 Subject: Add wsdiff article --- static/wsdiff-example.html | 889 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 889 insertions(+) create mode 100644 static/wsdiff-example.html (limited to 'static') diff --git a/static/wsdiff-example.html b/static/wsdiff-example.html new file mode 100644 index 0000000..59f6ceb --- /dev/null +++ b/static/wsdiff-example.html @@ -0,0 +1,889 @@ + + + + + diff: example_old.py / example.py + + + + + + + + +
+
+ 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() +
+
+
+ + + -- cgit