diff --git a/example.html b/example.html
new file mode 100644
index 0000000..3c62f4b
--- /dev/null
+++ b/example.html
@@ -0,0 +1,890 @@
+
+
+
+
+
+ diff: example_old.py / example.py
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
1 #!/usr/bin/env python3
+
1 #!/usr/bin/env python3
+
2
+
2
+
3 import math
+
3 import math
+
4 import itertools
+
4 import itertools
+
5 import textwrap
+
5 import textwrap
+
Collapse 24 unchanged lines
+
6
+
6
+
7 import click
+
7 import click
+
8 from reedmuller import reedmuller
+
8 from reedmuller import reedmuller
+
9
+
9
+
10
+
10
+
11 class Tag :
+
11 class 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 } ' ,
+
Collapse 49 unchanged lines
+
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 = ' - ' )
+
72 def cli ( data , outfile , height , text , font , font_size , bar_width , margin , color , text_color , dpi ) :
+
76 def 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
+
108 if __name__ == ' __main__ ' :
+
112 if __name__ == ' __main__ ' :
+
109 cli ( )
+
113 cli ( )
+
+
+
+
+
+
diff --git a/example.py b/example.py
new file mode 100644
index 0000000..2e22e20
--- /dev/null
+++ b/example.py
@@ -0,0 +1,113 @@
+#!/usr/bin/env python3
+
+import math
+import itertools
+import textwrap
+
+import click
+from 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{self.name}>'
+ else:
+ return f'{prefix}<{opening}/>'
+
+
+ @classmethod
+ def setup_svg(kls, tags, bounds, unit='mm', pagecolor='white', inkscape=False):
+ (min_x, min_y), (max_x, max_y) = bounds
+
+ 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)
+ text_color = text_color or color
+
+ NUM_BITS = 26
+
+ data_bits = [bool(data & (1<\n{children}\n{self.name}>'
+ else:
+ return f'{prefix}<{opening}/>'
+
+
+ @classmethod
+ def setup_svg(kls, tags, bounds, margin=0, unit='mm', pagecolor='white'):
+ (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
+
+ 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)
+ text_color = text_color or color
+
+ NUM_BITS = 26
+
+ data_bits = [bool(data & (1<
@@ -55,14 +59,92 @@ DIFF_STYLE_TOGGLE = r'''
'''
MAIN_CSS = r'''
+
+@media (prefers-color-scheme: light) {
+ html {
+ --c-bg-primary: #ffffff;
+ --c-fg-primary: #000000;
+ --c-bg-auxiliary: #f8f8f8;
+ --c-fg-auxiliary: #a0a0a0;
+ --c-border-line: #e0e0e0;
+ --c-bg-insert: #ecfdf0;
+ --c-bg-delete: #fbe9eb;
+ --c-bg-delete-lineno: #f9d7dc;
+ --c-fg-delete-lineno: #ae969a;
+ --c-bg-delete-word: #fac5cd;
+ --c-fg-delete-word: #400000;
+ --c-fg-insert-word: #004000;
+ --c-bg-insert-word: #c7f0d2;
+ --c-fg-insert-lineno: #9bb0a1;
+ --c-bg-insert-lineno: #ddfbe6;
+ --c-bg-empty: #f0f0f0;
+ --c-fg-foldline: #bbbbbb;
+ --c-border-delete: #e0c8c8; /* pick a darker border color inside the light red gutter */
+ }
+}
+
+@media (prefers-color-scheme: dark) {
+ html {
+ --c-bg-primary: #010409;
+ --c-fg-primary: #a0a0a0;
+ --c-bg-auxiliary: #0d1117;
+ --c-fg-auxiliary: #f0f6fc;
+ --c-fg-foldline: #bbbbbb;
+ --c-border-line: #3d444d;
+ --c-bg-insert: #223738;
+ --c-bg-delete: #280d1f;
+ --c-bg-delete-lineno: #421632;
+ --c-fg-delete-lineno: #ae969a;
+ --c-bg-delete-word: #421632;
+ --c-fg-delete-word: #fac5cd;
+ --c-fg-insert-word: #c7f0d2;
+ --c-bg-insert-word: #325148;
+ --c-fg-insert-lineno: #9bb0a1;
+ --c-bg-insert-lineno: #325148;
+ --c-bg-empty: #080b0f;
+ --c-border-delete: #e0c8c8;
+ }
+}
+
+@media print {
+ html {
+ /* Copy of the light theme, but we clip all light gray backgrounds to white. */
+ --c-bg-primary: #ffffff;
+ --c-fg-primary: #000000;
+ --c-bg-auxiliary: #ffffff;
+ --c-fg-auxiliary: #a0a0a0;
+ --c-border-line: #e0e0e0;
+ --c-bg-insert: #ecfdf0;
+ --c-bg-delete: #fbe9eb;
+ --c-bg-delete-lineno: #f9d7dc;
+ --c-fg-delete-lineno: #ae969a;
+ --c-bg-delete-word: #fac5cd;
+ --c-fg-delete-word: #400000;
+ --c-fg-insert-word: #004000;
+ --c-bg-insert-word: #c7f0d2;
+ --c-fg-insert-lineno: #9bb0a1;
+ --c-bg-insert-lineno: #ddfbe6;
+ --c-bg-empty: #ffffff;
+ --c-fg-foldline: #bbbbbb;
+ --c-border-delete: #e0c8c8;
+ }
+}
+
@layer wsd-base-style {
+ html {
+ background-color: var(--c-bg-primary);
+ height: 100%;
+ width: 100%;
+ }
+
#wsd-js-controls {
display: none;
- background-color: #f8f8f8;
+ color: var(--c-fg-primary);
+ background-color: var(--c-bg-auxiliary);
padding: 5px 20px;
font-size: 10pt;
font-weight: bold;
- border: 1px solid #e0e0e0;
+ border: 1px solid var(--c-border-line);
position: sticky;
top: 0;
z-index: 1;
@@ -79,8 +161,8 @@ MAIN_CSS = r'''
}
.wsd-file-title {
- background-color: #f8f8f8;
- border-bottom: solid 1px #e0e0e0;
+ background-color: var(--c-bg-auxiliary);
+ border-bottom: solid 1px var(--c-border-line);
}
}
@@ -117,8 +199,8 @@ MAIN_CSS = r'''
.wsd-file-container {
font-family: monospace;
font-size: 9pt;
- background-color: #f8f8f8;
- border: solid 1px #e0e0e0;
+ background-color: var(--c-bg-auxiliary);
+ border: solid 1px var(--c-border-line);
margin: 15px;
}
@@ -140,17 +222,23 @@ MAIN_CSS = r'''
direction: rtl;
}
+ .wsd-diff-files {
+ color: var(--c-fg-primary);
+ }
+
.wsd-diff {
+ background-color: var(--c-bg-primary);
overflow-x: auto;
display: grid;
align-items: start;
- border-top: 1px solid #e0e0e0;
+ border-top: 1px solid var(--c-border-line);
}
.wsd-line {
padding-left: calc(4em + 5px);
text-indent: -4em;
padding-top: 2px;
+ align-self: stretch; /* Make sure empty lines don't collapse */
}
/* Make individual syntax tokens wrap anywhere */
@@ -164,31 +252,31 @@ MAIN_CSS = r'''
}
.wsd-line.wsd-left.wsd-change, .wsd-line.wsd-left.wsd-insert {
- background-color: #fbe9eb;
+ background-color: var(--c-bg-delete);
}
.wsd-line.wsd-right.wsd-change, .wsd-line.wsd-right.wsd-insert {
- background-color: #ecfdf0;
+ background-color: var(--c-bg-insert);
}
.wsd-lineno.wsd-left.wsd-change, .wsd-lineno.wsd-left.wsd-insert {
- background-color: #f9d7dc;
- color: #ae969a;
+ background-color: var(--c-bg-delete-lineno);
+ color: var(--c-fg-delete-lineno);
}
.wsd-lineno.wsd-right.wsd-change, .wsd-lineno.wsd-right.wsd-insert {
- background-color: #ddfbe6;
- color: #9bb0a1;
+ background-color: var(--c-bg-insert-lineno);
+ color: var(--c-fg-insert-lineno);
}
.wsd-right > .wsd-word-change {
- background-color: #c7f0d2;
- color: #004000;
+ background-color: var(--c-bg-insert-word);
+ color: var(--c-fg-insert-word);
}
.wsd-left > .wsd-word-change {
- background-color: #fac5cd;
- color: #400000;
+ background-color: var(--c-bg-delete-word);
+ color: var(--c-fg-delete-word);
}
.wsd-lineno {
@@ -199,22 +287,18 @@ MAIN_CSS = r'''
overflow: clip;
position: relative;
text-align: right;
- color: #a0a0a0;
- background-color: #f8f8f8;
- border-right: 1px solid #e0e0e0;
+ color: var(--c-fg-auxiliary);
+ background-color: var(--c-bg-auxiliary);
+ border-right: 1px solid var(--c-border-line);
align-self: stretch;
}
- .wsd-lineno.wsd-change, .wsd-lineno.wsd-insert {
- color: #000000;
- }
-
.wsd-lineno::after {
position: absolute;
right: 0;
content: "\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳\a↳";
white-space: pre;
- color: #a0a0a0;
+ color: var(--c-fg-auxiliary);
}
/* Default rules for split diff for wide screens (laptops) */
@@ -223,7 +307,7 @@ MAIN_CSS = r'''
}
.wsd-empty {
- background-color: #f0f0f0;
+ background-color: var(--c-bg-empty);
align-self: stretch;
}
@@ -247,16 +331,17 @@ MAIN_CSS = r'''
grid-column: 1 / span 4;
display: flex;
justify-content: center;
- color: #a0a0a0;
+ color: var(--c-fg-auxiliary);
- background-image: radial-gradient(#BBBBBB 1px, transparent 0);
+ background-image: radial-gradient(var(--c-fg-foldline) 1px, transparent 0);
background-size: 10px 10px;
background-position: center;
background-repeat: repeat-x;
+ background-color: var(--c-bg-auxiliary)
}
.wsd-collapse-controls > label {
- background-color: #f8f8f8;
+ background-color: var(--c-bg-auxiliary);
}
.wsd-collapse:has(input[type="checkbox"]:checked) > span {
@@ -299,7 +384,7 @@ MAIN_CSS = r'''
content: "";
align-self: stretch;
grid-column: 1;
- border-right: 1px solid #e0e0e0;
+ border-right: 1px solid var(--c-border-line);
margin-right: -6px; /* move border into column gap, and 1px over to align with other borders */
}
@@ -307,12 +392,12 @@ MAIN_CSS = r'''
content: "";
align-self: stretch;
grid-column: 2;
- border-left: 1px solid #e0c8c8; /* pick a darker border color inside the light red gutter */
+ border-left: 1px solid var(--c-border-delete);
margin-left: -5px;
}
.wsd-lineno.wsd-left.wsd-insert {
- border-right: 1px solid #e0c8c8;
+ border-right: 1px solid var(--c-border-delete);
}
.wsd-lineno.wsd-right.wsd-change::after {
@@ -358,7 +443,7 @@ MAIN_CSS = r'''
}
.wsd-lineno.wsd-left.wsd-empty {
- background-color: #ddfbe6;
+ background-color: var(--c-bg-insert-lineno);
}
/* line continuation arrows only in right line number column */
@@ -368,7 +453,7 @@ MAIN_CSS = r'''
.wsd-lineno.wsd-left.wsd-insert::before {
content: "";
grid-column: 2;
- border-left: 1px solid #e0c8c8; /* pick a darker border color inside the light red gutter */
+ border-left: 1px solid var(--c-border-delete); /* pick a darker border color inside the light red gutter */
margin-left: -5px;
}
}
@@ -430,8 +515,7 @@ DIFF_STYLE_SCRIPT = r'''
document.getElementById('wsd-js-controls').style = 'display: flex';
'''
-HTML_TEMPLATE = r'''
-
+HTML_TEMPLATE = r'''
@@ -464,70 +548,6 @@ HTML_TEMPLATE = r'''
'''
-PYGMENTS_CSS = '''
-body .wsd-hll { background-color: #ffffcc }
-body .wsd-c { color: #177500 } /* Comment */
-body .wsd-err { color: #000000 } /* Error */
-body .wsd-k { color: #A90D91 } /* Keyword */
-body .wsd-l { color: #1C01CE } /* Literal */
-body .wsd-n { color: #000000 } /* Name */
-body .wsd-o { color: #000000 } /* Operator */
-body .wsd-cm { color: #177500 } /* Comment.Multiline */
-body .wsd-cp { color: #633820 } /* Comment.Preproc */
-body .wsd-c1 { color: #177500 } /* Comment.Single */
-body .wsd-cs { color: #177500 } /* Comment.Special */
-body .wsd-kc { color: #A90D91 } /* Keyword.Constant */
-body .wsd-kd { color: #A90D91 } /* Keyword.Declaration */
-body .wsd-kn { color: #A90D91 } /* Keyword.Namespace */
-body .wsd-kp { color: #A90D91 } /* Keyword.Pseudo */
-body .wsd-kr { color: #A90D91 } /* Keyword.Reserved */
-body .wsd-kt { color: #A90D91 } /* Keyword.Type */
-body .wsd-ld { color: #1C01CE } /* Literal.Date */
-body .wsd-m { color: #1C01CE } /* Literal.Number */
-body .wsd-s { color: #C41A16 } /* Literal.String */
-body .wsd-na { color: #836C28 } /* Name.Attribute */
-body .wsd-nb { color: #A90D91 } /* Name.Builtin */
-body .wsd-nc { color: #3F6E75 } /* Name.Class */
-body .wsd-no { color: #000000 } /* Name.Constant */
-body .wsd-nd { color: #000000 } /* Name.Decorator */
-body .wsd-ni { color: #000000 } /* Name.Entity */
-body .wsd-ne { color: #000000 } /* Name.Exception */
-body .wsd-nf { color: #000000 } /* Name.Function */
-body .wsd-nl { color: #000000 } /* Name.Label */
-body .wsd-nn { color: #000000 } /* Name.Namespace */
-body .wsd-nx { color: #000000 } /* Name.Other */
-body .wsd-py { color: #000000 } /* Name.Property */
-body .wsd-nt { color: #000000 } /* Name.Tag */
-body .wsd-nv { color: #000000 } /* Name.Variable */
-body .wsd-ow { color: #000000 } /* Operator.Word */
-body .wsd-mb { color: #1C01CE } /* Literal.Number.Bin */
-body .wsd-mf { color: #1C01CE } /* Literal.Number.Float */
-body .wsd-mh { color: #1C01CE } /* Literal.Number.Hex */
-body .wsd-mi { color: #1C01CE } /* Literal.Number.Integer */
-body .wsd-mo { color: #1C01CE } /* Literal.Number.Oct */
-body .wsd-sb { color: #C41A16 } /* Literal.String.Backtick */
-body .wsd-sc { color: #2300CE } /* Literal.String.Char */
-body .wsd-sd { color: #C41A16 } /* Literal.String.Doc */
-body .wsd-s2 { color: #C41A16 } /* Literal.String.Double */
-body .wsd-se { color: #C41A16 } /* Literal.String.Escape */
-body .wsd-sh { color: #C41A16 } /* Literal.String.Heredoc */
-body .wsd-si { color: #C41A16 } /* Literal.String.Interpol */
-body .wsd-sx { color: #C41A16 } /* Literal.String.Other */
-body .wsd-sr { color: #C41A16 } /* Literal.String.Regex */
-body .wsd-s1 { color: #C41A16 } /* Literal.String.Single */
-body .wsd-ss { color: #C41A16 } /* Literal.String.Symbol */
-body .wsd-bp { color: #5B269A } /* Name.Builtin.Pseudo */
-body .wsd-vc { color: #000000 } /* Name.Variable.Class */
-body .wsd-vg { color: #000000 } /* Name.Variable.Global */
-body .wsd-vi { color: #000000 } /* Name.Variable.Instance */
-body .wsd-il { color: #1C01CE } /* Literal.Number.Integer.Long */
-'''
-
-from pygments.formatter import Formatter
-from pygments.token import STANDARD_TYPES
-
-from functools import lru_cache
-
@lru_cache(maxsize=256)
def get_token_class(ttype):
while not (name := STANDARD_TYPES.get(ttype)):
@@ -633,6 +653,7 @@ def html_diff_content(old, new, lexer, context_len=5, fold_min=5):
return '\n'.join(out)
def html_diff_block(old, new, filename, lexer, hide_filename=True, context_len=5, fold_min=5):
+ lexer.stripnl = False # Make pygments preserve leading and trailing empty lines.
code = html_diff_content(old, new, lexer, context_len=context_len, fold_min=fold_min)
filename = f''
if hide_filename:
@@ -675,7 +696,16 @@ def cli():
if args.syntax_css:
syntax_css = Path(args.syntax_css).read_text()
else:
- syntax_css = PYGMENTS_CSS
+ light_css = HtmlFormatter(classprefix='wsd-', style='xcode').get_style_defs()
+ dark_css = HtmlFormatter(classprefix='wsd-', style=witchhazel.WitchHazelStyle).get_style_defs()
+
+ syntax_css = textwrap.dedent(f'''@media print, (prefers-color-scheme: light) {{
+ {light_css}
+ }}
+
+ @media (prefers-color-scheme: dark) {{
+ {dark_css}
+ }}''')
if args.header:
print(string.Template(HTML_TEMPLATE).substitute(