latin: alternate tap styling using crossed dashes
[sheet.git] / writing-latn.inc.pl
1 use 5.014;
2 use utf8;
3 use warnings;
4 use List::Util qw( pairs pairmap sum );
5
6 my %C = (
7         red    => '#EC1C24',
8         blue   => '#3953A3',
9         yellow => '#F9EC31',
10         black  => '#231F20',
11 );
12 my $U = 0;  # optional unicode alternatives
13
14 my @wrapstyle = (
15         'td { white-space: normal; word-spacing: 10em }',
16                 # force line break between words
17         '.sample { word-spacing: 0 }',
18         '.sample svg { margin-right: 1ex; white-space: nowrap; display: inline-block }',
19                 # larger space between letters
20 );
21 my $spacestyle = '.sample svg { margin-right: 0.5ex }';  # separate letters
22 my @tapstyle = (
23         @wrapstyle,
24         '{ line-height: 1ex }',
25         'td:not(.sample) { vertical-align: top }',
26         '.sample { font-size: 80% }',
27 );
28
29 my @hueorder = (
30         2,11,20,19,18,21,24,15,6,7,8,5,13, # red .. magenta, grey
31         1,10,9,12,3,4,0, 14,23,22,25,16,17,26, # dark, light hues
32 );
33
34 # Order to put similar sounds close to each other:
35 #         ┌ R Y G C B M X
36 #        ┌┼──────────────
37 #        W│ o e y h s f -
38 #         │muaixqgkdtbp l
39 #        K│ w n j c z v r
40
41 my @hueletters = ((26) x 27);
42 @hueletters[map { ord($_) - ord('a') } qw(
43         u a i x q g k d t b p m l   w n j c z v r   o e y h s f
44 )] = @hueorder;
45
46 sub disptap {
47         my $code = shift;
48         my ($prefix, @dots) = $code =~ m/\A(-?)(\d)(\d)/ or return $code;
49         if ($U) {
50                 # unicode glyph alternative as DOMINO TILE HORIZONTAL-0a-0b
51                 return $prefix . chr(0x1F031 + ($dots[0] * 7) + $dots[1]);
52         }
53         return $prefix . join(' ', map { '·' x $_ } @dots);
54 }
55
56 sub dispdomino {
57         my $code = shift;
58         my ($prefix, @dots) = $code =~ m/\A(-?)(\d)(\d)/ or return $code;
59         # unicode glyph alternative as DOMINO TILE HORIZONTAL-0a-0b
60         return $prefix . chr(0x1F031 + ($dots[0] * 7) + $dots[1]);
61 }
62
63 sub dispdash {
64         my $code = shift;
65         my ($prefix, @dots) = $code =~ m/\A(-?)(\d)(\d)/ or return $code;
66         my ($w, $h) = (12, 9);
67         my @w = map { $w / ($_ || 1) } @dots;
68         return sprintf(
69                 '<svg height="20" viewBox="-.5 -.5 %s %s">'
70                 . '<path d="%s" /></svg>',
71                 $w + 1, $h + 1, join(' ',
72                         "m0,$h l+$w[0],-$h" x $dots[0], # slashes
73                         "m0,$h l-$w[1],-$h" x $dots[1], # backslashes
74                 )
75         );
76 }
77
78 sub dispblock {
79         my $code = shift;
80         my ($prefix, $shape, $rotate) = $code =~ m/\A(-?)(\w)(\d?)/
81                 or return $code;
82         my %path = (
83                 S => 'm0,1h1v-1h1',
84                 Z => 'm0,0h1v1h1',
85                 L => 'm0,0v2h1',
86                 v => 'm0,0v1h1',
87                 J => 'm1,0v2h-1',
88                 T => 'm0,0h2.5h-1.5v1',
89                 O => 'm0,0h1v1h-1',
90                 I => 'm0,1h3',
91                 i => 'm0,1h2',
92                 o => 'm0,1h1',
93         );
94         my %col = qw(
95                 S 120  Z 0  J 240  L 30  T 300  O 60  I 180
96                 v 45,50%  i 165,50%  o 165,0%
97         );
98         s/\z(?<!%)/,100%/ for values %col;
99         my @gaps = (grep $_,
100                 $code =~ /[Ii]$|T[23]|L3?$|S1|Z$|J1|v3?$/ ? 'gapl1' : (),
101                 $code =~ /T$|L2|Z$/ ? 'gapr1' : (),
102         );
103         return sprintf(
104                 '<svg height="24" viewBox="-.5 -.5 %s 4"%s>'
105                 . '<path d="%s" stroke="%s" fill="none"%s /></svg>',
106                 $code eq 'I' ? 4 : $code =~ /T3|[LJO]$|[Iio]1/ ? 2 : 3,
107                 @gaps ? qq( class="@gaps") : '',
108                 $path{$shape}, "hsl($col{$shape},50%)", join('',
109                         $rotate && sprintf(' transform="rotate(%s 1 1)"', $rotate * 90),
110                         $prefix && ' style="opacity:.5"',
111                 ),
112         );
113 }
114
115 sub dispbar {
116         my $code = shift or return '';
117
118         return join '', pairmap {
119                 ($a =~ tr/123/❘❙❚/r) . ($b =~ tr/321/  /dr)
120         } split //, $code if $U;
121
122         my @cols = split //, $code;  # bar and space widths
123         my $width = sum(@cols);
124         return sprintf(
125                 '<svg width="%d" height="%d" viewBox="-.5 0 %d %d"><path d="%s"/></svg>',
126                 $width * 2, 14, $width, 7, join(' ',
127                         'M0,0',
128                         map {
129                                 join('m1,-7', ('v7') x $_->[0]),  # line per bar width
130                                 (map { sprintf 'm%d,-7', $_ + 1 } $_->[1] || ()),  # space forward
131                         }
132                         pairs @cols
133                 )
134         );
135 }
136
137 sub disphues {
138         my ($index, $hues, $opaque) = @_;
139         $index >= 0 or $index = 26;
140         my @lum = ($index % 3, $index / 3 % 3, $index / 9);  # hue opacities (0..2)x3
141         my @lumf = $opaque ? ('hsl(%s,100%%,50%%)', 'hsl(%s,100%%,25%%)') :
142                 ('hsl(%s,100%%,50%%)', 'hsla(%s,100%%,50%%,.5)');
143         return sprintf(
144                 '<svg width="16" height="16" viewBox="0 0 12 12">%s</svg>',
145                 join '', map {
146                         my $colf = $lumf[ $lum[$_] ];
147                         !$colf ? () : sprintf('<circle cx="%d" cy="%d" r="%d" fill="%s"/>',
148                                 5 + $_, $_ == 1 ? 7 : 5, 5, sprintf($colf, $hues->[$_])
149                         );
150                 } 0 .. 2
151         );
152 }
153
154 (
155 uppercase => {
156         list => [qw{ A B C D E F G H I J K L M N O P Q R S T U V W X Y Z }],
157 },
158 lowercase => {
159         list => [qw{ a b c d e f g h i j k l m n o p q r s t u v w x y z }],
160 },
161 suetterlin => {
162         name => 'Sütterlin',
163         style => [
164                 '@font-face {
165                         font-family: Suetterlin; /* R. G. Arens */
166                         src: url("/suetterlin.ttf");
167                 }',
168                 'td { font-family: Suetterlin }',
169         ],
170         list => [qw{ a b c d e f g h i j k l m n o p q r ſ s t u v w x y z }],
171 },
172 roman => {
173         name => 'Old Roman Cursive',
174         style => [
175                 'svg path { stroke-linecap: round; stroke-linejoin: round }',
176                 '.sample svg { margin-right: -10px }',
177         ],
178         list => [
179                 map {
180                         s{\A-?\K(\w.+)}
181                          {<svg width="20" height="20" viewBox="0 0 12 20"><path d="$1"/></svg>}r
182                 }
183                 "m2,4 c1,2 8,9 8,9 M2,15 6,9",
184                 "m2,4 c0,0 3,-2 4,1 1,2 0,9 3,9 1,-0 2,-1 2,-1 m-6,-2 c-5,4 -0,6 1,3",
185                 "m4,7 4,-2 m-4,4 c0,0 -2,7 3,6",
186                 "m3,2 c0,0 7,10 7,12 m-2,-4 c-5,2 -4,9 1,3",
187                 "m3,10 4,0 m2,-7 c0,0 -7,1 -5,16",
188                 "m4,11 5,-2 m-3,-4 7,-4 M0,18 c0,0 4,3 5,-3 2,-6 2,-10 2,-10",
189                 "m3,6 7,-2 m-7,4 c-2,5 4,9 6,6 l1,3",
190                 "m7,9 4,-0 m-8,0 c4,0 6,-1 5,6 M1,3 c2,-0 2,12 2,12",
191                 '>', # i = j
192                 "m6,8 -0,7",
193                 "-m9,5 -5,4 4,4 m-4,-10 -1,13",
194                 "m3,2 c0,0 -1,9 1,10 5,2 6,2 6,2",
195                 "m0,16 3,-10 4,6 2,-5 5,4",
196                 "m2,16 1,-9 5,8 2,-9",
197                 "m5,8 c-2,0 -2,6 1,5 4,-2 1,-5 1,-5",
198                 "m5,8 c0,0 -1,8 2,6 m-3,-7 5,3",
199                 "m3,6 9,12 m-9,-11 c-4,1 -3,4 -3,4 0,0 2,2 6,-1",
200                 "m0,6 c3,-1 3,-1 6,0 2,1 3,3 6,1 m-7,-1 -2,11",
201                 "m13,3 c0,0 -5,2 -8,4 -2,3 -1,5 -2,9 -1,1 -4,2 -4,2",
202                 "m2,7 8,0 m-4,1 c0,0 -1,8 3,5",
203                 '>', # u = v
204                 "m2,5 c0,0 3,5 6,3 3,-2 2,-3 2,-3",
205                 '-',
206                 "m3,19 c-1,-6 6,-17 6,-17 M1,8 c0,0 10,1 10,1",
207                 "-m0,7 c2,-3 5,-2 5,1 l0,11 c0,0 -0,-4 -0,-10 -0,-4 4,-4 6,-3",
208                 "-m3,6 c4,-1 3,3 3,3 -1,3 -2,5 -1,5 1,1 3,0 3,0",
209         ],
210 },
211 sutton => {
212         name => 'Sutton <abbr title="American Sign Lanugage">ASL</abbr>',
213         style => $spacestyle,
214         list => [
215                 # American manual alphabet in Sutton (U+1D800+) notation
216                 map { !!$_ && pack 'W*', map { hex "1D$_" } unpack '(A3)*', $_ } qw{
217                 8F7a9c    847a9c    86Da9c    801a9c    84Aa9c
218                 8CEa9c    8F0       815aa2    892a9c    892a9c9A2aac
219                 840a9c    8DCa9c    88Da9c
220                 819a9c    876a9c    840a9caA1 8F0a9caA1 81Aa9c
221                 903a9c    8FBa9c    815a9c    80Ea9c    887a9c
222                 806a9c    89Aa9c    800a9c945aaa
223                 0         965aa6
224         }],
225 },
226 graffiti => {
227         name => 'Palm Graffiti',
228         style => [
229                 '@font-face {
230                         font-family: Graffiti;
231                         src: url("/graffiti.ttf");
232                 }',
233                 'td { font-family: Graffiti }',
234         ],
235         list => [qw{ a b c d e f g h i j k l m n o p q r s t u v w * y z }],
236 },
237 unistrokes => {
238         name => 'Unistrokes',
239         url   => 'https://www.google.com/patents/US5596656', # by Xerox
240         style => 'svg path { stroke-linecap: round; stroke-linejoin: round }',
241         list => [
242                 map { '<svg width="14" height="16" viewBox="-1 -1 8 10">'.$_.'</svg>' }
243                 map {
244                         sprintf('<circle cx="%s" cy="%s" r="1"/>', m/\AM(\d+),(\d+)(.?)/) . # start point
245                         (!!$3 && qq(<path d="$_"/>))
246                 }
247                 'M3,8 V0',
248                 'M0,0 6,4 0,8',
249                 'M6,0 0,4 6,8',
250                 'M6,0 0,4 6,8',
251                 'M6,4 H0',
252                 'M6,0 0,0 0,8',
253                 'M0,8 6,8 6,0',
254                 'M0,0 6,0 6,8',
255                 'M3,0 V8',
256                 'M6,0 6,8 0,8',
257                 'M0,8 6,0',
258                 'M0,0 0,8 6,8',
259                 'M6,8 3,0 0,8',
260                 'M0,8 3,0 6,8',
261                 'M6,0 Q0,6 3,8 6,6 0,0',
262                 'M0,0 Q4,8 6,4 4,0 0,8',
263                 'M6,0 Q2,8 0,4 2,0 6,8',
264                 'M0,0 6,8',
265                 'M6,0 0,0 6,8 0,8',
266                 'M0,4 H6',
267                 'M6,0 3,8 0,0',
268                 'M0,0 3,8 6,0',
269                 'M0,0 0,8 6,0 6,8',
270                 'M0,0 Q6,6 3,8 0,6 6,0',
271                 'M6,0 0,8',
272                 'M0,0 6,0 0,8 6,8',
273                 'M3,4',
274         ],
275 },
276 edgewrite => {
277         name => 'EdgeWrite',
278         url   => 'http://depts.washington.edu/ewrite/', # patented US7729542
279         style => 'svg path { stroke-linecap: round; stroke-linejoin: round }',
280         list => [
281                 map { '<svg width="14" height="14" viewBox="-1 -1 10 10">'.$_.'</svg>' }
282                 map {
283                         my @route = split //;
284                         my @coords = map { $_ % 2 << 3, $_ >> 1 << 3 } @route; # x,y,
285                         sprintf('<circle cx="%s" cy="%s" r="1"/><path d="M%s"/>',
286                                 @coords[0, 1],  # start point
287                                 join(' ', map {
288                                         my $pos = join(',', @coords[$_*2, $_*2 + 1]);
289                                         $_ > 1 && $route[$_] == $route[$_ - 2] # curve back
290                                                 ? 'Q4,4 '.$pos.'L' : $pos
291                                 } 0 .. $#route),
292                         )
293                 }
294                 # corners (0..3) clockwise from top-left in order
295                 qw(
296                         213 0232 1023 1323 103 102 10132 0213 02 132 02123 023 20313 2031
297                         10231 0102 10131 201 1032 013 0231 021 02131 0312 0313 0123  01
298                 )
299         ],
300 },
301 ita2 => {
302         name => '<abbr title="International Telegraph Alphabet">ITA</abbr>2',
303         style => [@wrapstyle, 'td { font-size: 50% }'],
304         list => [map { tr/01/○●/r =~ s/..\K/ /r } qw(
305                 11000 10011 01110 10010 10000 10110 01011 00101 01100 11010 11110 01001 00111
306                 00110 00011 01101 11101 01010 10100 00001 11100 01111 11001 10111 10101 10001
307                 00100
308         )],
309 },
310 moon => {
311         list => [
312                 map { qq(<svg width="14" height="14" viewBox="-.5 -.5 7 7"><path d="$_"/></svg>) }
313                 'M0,6 3,0 6,6',
314                 'M1,0 V4 A2,2 0,0,0 5,4',
315                 'M5,0 A4.5,3 0,0,0 5,6',
316                 'M1,0 A4.5,3 0,0,1 1,6',
317                 'M0,6 V0 H6',
318                 'M1,6 V2 A2,2 0,0,1 5,2',
319                 'M5,6 V2 A2,2 0,1,0 1,2',
320                 'M1.5,3 A1.5,1.5 0,0,0 4.5,3 1.5,1.5 0,0,0 1.5,3 M3,1.5 A1,1.5 0,0,0 3,4.5',
321                 'M3,0 V6',
322                 'M5,0 V4 A2,2 0,0,1 1,4',
323                 'M6,0 0,3 6,6',
324                 'M0,0 V6 H6',
325                 'M0,0 H6 V6',
326                 'M0,6 V2 L6,6 V0',
327                 'M0,3 A3,3 0,0,0 6,3 3,3 0,0,0 0,3',
328                 'M6,4 H2 A2,1 0,0,1 2,2',
329                 'M0,4 H4 A2,1 0,0,0 4,2',
330                 'M0,0 6,6',
331                 'M0,6 6,0',
332                 'M0,3 H6',
333                 'M0,0 V3 A3,3 0,0,0 6,3 V0',
334                 'M0,0 3,6 6,0',
335                 'M0,6 V3 A3,3 0,0,1 6,3 V6',
336                 'M0,0 6,3 0,6',
337                 'M6,0 V6 H0',
338                 'M0,0 H6 L2,6 H6',
339         ],
340         style => 'svg path { stroke-linecap: round; stroke-linejoin: round }',
341 },
342 braille => {
343         list => [qw{ ⠁ ⠃ ⠉ ⠙ ⠑ ⠋ ⠛ ⠓ ⠊ ⠚ ⠅ ⠇ ⠍ ⠝ ⠕ ⠏ ⠟ ⠗ ⠎ ⠞ ⠥ ⠧ ⠺ ⠭ ⠽ ⠵ }],
344 },
345 tactile => {
346         name => '5-point Tactile',
347         list => [
348                 map { '<svg width="9" height="12" viewBox="0 0 18 24">'.$_.'</svg>' }
349                 map {
350                         join '', map { sprintf '<circle cx="%d" cy="%d" r="4"/>',
351                                 !$_ ?  9 : $_ & 1 ? 4 : 14,
352                                 !$_ ? 12 : $_ < 3 ? 4 : 20,
353                         } split //
354                 }
355                 qw{
356                         4 234 012 14 0 014 023 12
357                         02 024 0134 23 013 03 01 123
358                         0124 13 04 1 34 0123 134 0234
359                         034 124
360                 }
361         ],
362 },
363 morse => {
364         style => $spacestyle,
365         list => [map {tr/.-/‧‑/r} qw{
366                 .- -... -.-. -.. . ..-. --. .... .. .--- -.- .-.. --
367                 -. --- .--. --.- .-. ... - ..- ...- .-- -..- -.-- --..
368         }],
369 },
370 tap => {
371         name => 'Tap code',
372         style => \@tapstyle,
373         list => [map { disptap($_) } qw{
374                 11 12 13 14 15 21 22 23  > 24 25 31 32
375                 33 34 35 41 42 43 44 45 51 52 53 54 55
376         }],
377 },
378 shorttap => {
379         name => 'Short Tap',
380         style => \@tapstyle,
381         list => [map { disptap($_) } qw{
382                 11 12 13 14 21 22 23 20 > 31 -13 32 33
383                 30 41 42 -13 43 40 10 51 52 53 50 -31 -40
384         }],
385         altlist => [map { dispdash($_) } qw{
386                 10 14 -13 12 20 23 22 21 30 -34 13 33 32
387                 31 40 43 -13 42 41 11 50 -23 -50 -1341 -31 -41
388         }],
389 },
390 domino => {
391         name => 'Domino tiles',
392         list => [map { dispdomino($_) } qw{
393                 10 11 20 21 22 30 31 32 33 40 41 42 43
394                 44 50 51 52 53 54 55 60 61 62 63 64 65
395         }],
396 },
397 tetromino => {
398         style => [
399                 'svg path { stroke-linecap: square }',
400                 '.sample .gapl1 + .gapr1 { margin-left: -6.3px }',
401         ],
402         name => 'Tetrominos',
403         list => [map { dispblock($_) } qw{
404                 T2 T1 I  T3
405                 i  L1 J  L3
406                 o1 I1 L2 L  -S1 Z1
407                 O  J2 v2 -J3 S  T
408                 J1 v1 v  v3 i1 Z
409         }],
410 },
411 cards => {
412         style => 'td { font-family: Symbola, "DejaVu Sans", serif, sans }',
413         list => [(
414                 map { chr(0x1F0A0 + $_), sprintf('<b>%s</b>', chr(0x1F0B0 + $_)) }  # spades, hearts
415                 1 .. 11, 13, 14  # A 2-10 J Q K
416         ), '', chr(0x1F0CF), chr(0x1F0DF) ],
417 },
418 maritime => {
419         name => 'Maritime flags',
420         style => $spacestyle,
421         list => [
422                 # International Code of Signals, SVG fills
423                 map { !!$_ && '<svg width="20" height="20" viewBox="0 0 30 30">'.s/\n?\t+//gr.'</svg>' }
424                 split /\n\n/, qq{
425                         <path fill="$C{blue}" d="M0,0 h30 l-7.5,15 7.5,15 h-30 z"/>
426                         <path fill="white" d="M0,0 h15 v30 h-15"/>
427
428                         <path fill="$C{red}" d="M0,0 h30 l-7.5,15 7.5,15 h-30 z"/>
429
430                         <path fill="$C{blue}" d="M0,0 h30v30 h-30z"/>
431                         <path fill="white" d="M0,6  h30 v18 h-30" />
432                         <path fill="$C{red}" d="M0,12 h30 v6  h-30" />
433
434                         <path fill="$C{yellow}" d="M0,0 h30v30 h-30z"/>
435                         <path fill="$C{blue}" d="M0,6 h30 v18 h-30"/>
436
437                         <path fill="$C{red}" d="M0,0 h30v30 h-30z"/>
438                         <path fill="$C{blue}" d="M0,0 h30 v15 h-30"/>
439
440                         <path fill="white" d="M0,0 h30v30 h-30z"/>
441                         <path fill="$C{red}" d="M15,0 l15,15 -15,15 -15,-15"/>
442
443                         <path fill="$C{blue}" d="M0,0 h30v30 h-30z"/>
444                         <path fill="$C{yellow}" d="M 0,0 h5 v30 h-5"/>
445                         <path fill="$C{yellow}" d="M10,0 h5 v30 h-5"/>
446                         <path fill="$C{yellow}" d="M20,0 h5 v30 h-5"/>
447
448                         <path fill="$C{red}" d="M0,0 h30v30 h-30z"/>
449                         <path fill="white" d="M0,0 h15 v30 h-15"/>
450
451                         <path fill="$C{yellow}" d="M0,0 h30v30 h-30z"/>
452                         <circle fill="$C{black}" r="7.5" cx="15" cy="15"/>
453
454                         <path fill="$C{blue}" d="M0,0 h30v30 h-30z"/>
455                         <path fill="white" d="M0,10 h30 v10 h-30"/>
456
457                         <path fill="$C{blue}" d="M0,0 h30v30 h-30z"/>
458                         <path fill="$C{yellow}" d="M0,0 h15 v30 h-15"/>
459
460                         <path fill="$C{black}" d="M0,0 h30v30 h-30z"/>
461                         <path fill="$C{yellow}" d="M0,0 h15 v15 h-15 M15,15 h15 v15 h-15"/>
462
463                         <path fill="white" d="M0,0 h30v30 h-30z"/>
464                         <path fill="$C{blue}" d="M4,0h22l-11,11 M4,30h22l-11,-11
465                                 M0,4v22l11,-11 M30,4v22l-11,-11"/>
466
467                         <path fill="white" d="M0,0 h30v30 h-30z"/>
468                         <path fill="$C{blue}" d="
469                                 M0,0     h7.5v7.5h-7.5 m0,7.5h7.5v7.5h-7.5
470                                 m7.5,-15 h7.5v7.5h-7.5 m0,7.5h7.5v7.5h-7.5
471                                 m7.5,-30 h7.5v7.5h-7.5 m0,7.5h7.5v7.5h-7.5
472                                 m7.5,-15 h7.5v7.5h-7.5 m0,7.5h7.5v7.5h-7.5
473                         "/>
474
475                         <path fill="$C{yellow}" d="M0,0 h30v30 h-30z"/>
476                         <path fill="$C{red}" d="M0,0 h30 v30"/>
477
478                         <path fill="$C{blue}" d="M0,0 h30v30 h-30z"/>
479                         <path fill="white" d="M10,10 h10 v10 h-10"/>
480
481                         <path fill="$C{yellow}" d="M0,0 h30v30 h-30z"/>
482
483                         <path fill="$C{red}" d="M0,0 h30v30 h-30z"/>
484                         <path fill="$C{yellow}" d="M12.5,0 v30 h5 v-30"/>
485                         <path fill="$C{yellow}" d="M0,12.5 h30 v5 h-30"/>
486
487                         <path fill="white" d="M0,0 h30v30 h-30z"/>
488                         <path fill="$C{blue}" d="M10,10 h10 v10 h-10"/>
489
490                         <path fill="white" d="M0,0 h30v30 h-30z"/>
491                         <path fill="$C{red}" d="M0,0 h10 v30 h-10"/>
492                         <path fill="$C{blue}" d="M20,0 h10 v30 h-10"/>
493
494                         <path fill="white" d="M0,0 h30v30 h-30z"/>
495                         <path fill="$C{red}" d="M0,0 h15 v15 h-15 M15,15 h15 v15 h-15"/>
496
497                         <path fill="$C{red}" d="M0,0 h30v30 h-30z"/>
498                         <path fill="white" d="M4,0h22l-11,11 M4,30h22l-11,-11
499                                 M0,4v22l11,-11 M30,4v22l-11,-11"/>
500
501                         <path fill="$C{blue}" d="M0,0 h30v30 h-30z"/>
502                         <path fill="white" d="M5,5 h20 v20 h-20"/>
503                         <path fill="$C{red}" d="M10,10 h10 v10 h-10"/>
504
505                         <path fill="white" d="M0,0 h30v30 h-30z"/>
506                         <path fill="$C{blue}" d="M12.5,0 v30 h5 v-30"/>
507                         <path fill="$C{blue}" d="M0,12.5 h30 v5 h-30"/>
508
509                         <path fill="$C{red}" d="M0,0 h30v30 h-30z"/>
510                         <path fill="$C{yellow}" d="M0,0h6l-6,6 M12,0h6l-18,18v-6
511                                 M24,0h6l-30,30v-6 M30,6v6l-18,18h-6 M30,18v6l-6,6h-6"/>
512
513                         <path fill="$C{black}" d="M0,0 h30v30 h-30z"/>
514                         <path fill="$C{blue}" d="M30,0 v31 l-15,-15"/>
515                         <path fill="$C{yellow}" d="M0,0  h31 l-15,15"/>
516                         <path fill="$C{red}" d="M0,30 h31 l-15,-15"/>
517
518
519
520                         <path fill="$C{blue}" d="M0,5 30,15 0,25"/>
521                         <path fill="$C{yellow}" d="M0,9 20,15 0,21"/>
522
523                         <path fill="$C{blue}" d="M0,5 30,15 0,25"/>
524                         <path fill="white" d="M15,10 30,15 15,20"/>
525                 },
526         ],
527 },
528 flag => {
529         name => 'Flag semaphore',
530         list => [
531                 map {
532                         local $_ = $_;
533                         s/[1-4]\K(?=[4-9])/ /; # prevent unwanted vertical crossing
534                         tr/1-9/↙←↖↑↗→↘↓/;
535                         s{(\S)(?=.)}{<span style="position:absolute">$1</span>};
536                         $_
537                 }
538                 qw(
539                         1 2 3 4 5  6 7 21 31 46  14 51 16 17 23
540                         24 25 26 27 34  35 47 56 57 36  67
541                 )
542         ],
543 },
544 chappe => {
545         name => 'Chappe semaphore',
546         list => [
547                 map {
548                         my ($r, $pr, $pl) = split //, $_;
549                         /^\D$/ ? $_ : sprintf(
550                                 join('',
551                                         '<svg width="16" height="20" viewBox="0 0 10 15">',
552                                         '<path d="M5,6 v7"/>',
553                                         '<path d="M0,%s h10 %s" transform="rotate(%d 5 6)"/>',
554                                         '</svg>',
555                                 ),
556                                 ['6', '3v3', '9v-3']->[$pl],
557                                 [ '',  'v3',  'v-3']->[$pr],
558                                 $r * 45,
559                         );
560                 }
561                 # 360° rotation (0-7) and position state (0-2) of left and right bars
562                 qw(
563                   021 121 221 321 421 521 621 721
564                   > 022 122 222 322 011 111 211 311
565                   001 101 201 301 401 501 601 701 020
566                 )
567         ],
568 },
569 prussian => {
570         name => 'Prussian semaphore',
571         list => [
572                 map { /^\D+$/ ? $_ : sprintf
573                         join('',
574                                 '<svg width="10" height="20" viewBox="0 0 8 18">',
575                                 '<path d="M4,1 v18"/>',
576                                 (map {(
577                                         qq(<path d="M0 $_ h4" transform="rotate(%d 4 $_)"/>),
578                                         qq(<path d="M4 $_ h4" transform="rotate(-%d 4 $_)"/>),
579                                 )} 3, 7, 14),
580                                 '</svg>',
581                         ),
582                         map { ($_ - 2) * 45 % 360 } split //, $_
583                 }
584                 # rotation state (0-3) for left and right bar of 3 rows
585                 qw(
586                         003000 000200 203300 000030 033030 000130 000330 032330 > 031330
587                         022020 130120 001320 233010 030210 022310 203001 233001
588                         131001 231301 000202 023302 230003 032003 201003 101003
589                 )
590         ],
591 },
592 code39 => {
593         name => 'Code 39',
594         list => [map { dispbar($_) } qw(
595                 2111121121 1121121121 2121121111 1111221121 2111221111 1121221111
596                 1111122121 2111122111 1121122111 1111222111 2111111221 1121111221
597                 2121111211 1111211221 2111211211 1121211211 1111112221 2111112211
598                 1121112211 1111212211 2211111121 1221111121 2221111111 1211211121
599                 2211211111 1221211111            1221112111 0 1211212111
600         )], # ISO/IEC 16388
601 },
602 code93 => {
603         name => 'Code 93',
604         list => [map { dispbar($_) } qw(
605                 211113 211212 211311 221112 221211 231111 112113 112212 112311 122112
606                 132111 111123 111222 111321 121122 131121 212112 212211 211122 211221
607                 221121 222111 112122 112221 122121 123111        311211 0 111141
608         )],
609 },
610 code128 => {
611         name => 'Code 128',
612         list => [map { dispbar($_) } qw(
613                 111323 131123 131321 112313 132113 132311 211313 231113 231311 112133
614                 112331 132131 113123 113321 133121 313121 211331 231131 213113 213311
615                 213131 311123 311321 331121 312113 312311      212222 0 211412 23311120
616         )],
617 },
618 rm4scc => {
619         name => '<abbr title="Royal Mail 4-State Customer Code">RM4SCC</abbr>',
620         list => [
621                 map {
622                         my $len = length $_;
623                         !$len ? '' : sprintf(
624                                 '<svg width="%d" height="20" viewBox="0 0 %d 6">'
625                                 . '<path d="M1%s"/></svg>',
626                                 $len * 5, $len * 2,
627                                 join ' m2',
628                                 map { sprintf ',%dv%dm0,-%d',
629                                         ($_ & 1 ? 0 : 2),  2 + ($_ & 2) + ($_ & 1) * 2,
630                                         ($_ & 1 ? 0 : 2) + 2 + ($_ & 2) + ($_ & 1) * 2,
631                                 }
632                                 split //
633                         );
634                 }
635                 qw(
636                                                                 2121 2301
637                         0132 0312 0330 2112 2130 2310
638                         1023 1203 1221 3003 3021 3201
639                         1032 1212 1230 3012 3030 3210
640                         1122 1302 1320 3102 3120 3300
641                         0033
642                 ), # 0 for space
643                 '', 1, 3  # start/end
644         ],
645 },
646 rgbmap => {
647         name => 'RGBmap',
648         style => [
649                 'svg { isolation: isolate }',
650                 'svg circle { mix-blend-mode: screen }',
651                 '.sample { background: black }',
652         ],
653         list => [
654                 map { disphues($_, [0, 240, 120], 1) } # Red, Blue, Green
655                 @hueorder[23..25,20..22, 12, 6..11,0..5, 16..18, 13..15, 19, 26],
656         ],
657 },
658 cmymap => {
659         name => 'CMYmap',
660         style => [
661                 'svg { isolation: isolate }',  # mix on white
662                 'svg circle { mix-blend-mode: multiply }',
663                 '.sample { background: white }',
664         ],
665         list => [
666                 map { disphues($_, [180, 60, 300]) } # Cyan, Yellow, Magenta
667 #               @hueorder[13..18, 19, 0..11, 20..25, 12, 26],
668                 @hueletters
669         ],
670 },
671 dni => {
672         name => "D'ni",
673         style => [
674                 'svg { border: 1px solid currentColor }',
675                 '.sample svg + svg { border-left: 0 }',
676         ],
677         list => [
678                 map {
679                         state $v = [
680                                 '',
681                                 'M0,4 8,4',
682                                 'M0,8 Q4,4 8,8',
683                                 'M0,4 4,8 8,4',
684                                 'M2,0 2,4 8,4',
685                                 'M0,0 8,8 M0,8 8,0', # cross
686                                 'M3.5,4 h1', # dot
687                         ];
688                         state $h = [
689                                 '',
690                                 'M4,0 4,8',
691                                 'M0,0 Q4,4 0,8',
692                                 'M4,-.5 0,4 4,8.5',
693                                 'M4,8 4,2 8,2',
694                         ];
695                         sprintf(
696                                 '<svg width="16" height="16" viewBox="0 0 8 8"><path d="%s"/></svg>',
697                                 $h->[$_ % 5] . $v->[$_ / 5] || $v->[6],
698                         );
699                 } 0 .. 5*5
700         ],
701 },
702 pigpen => {
703         style => [
704                 'svg path { stroke-linecap: square }',
705                 '.sample svg { margin-right: 0.1em }',
706         ],
707         list => [
708                 map {
709                         qq(<svg width="12" height="12" viewBox="-.5 -.5 7 7">$_</svg>)
710                 }
711                 map {
712                         local $_ = $_;
713                         s/^H/mX,0/ or s/^V/m0,X/ or s/^/m0,0/;
714                         s/[hv]\K|X/6/g;
715                         s/(?:v|,[^0]).*?v\K/-/;
716                         s/(?:h|m[^0]).*?h\K/-/;
717                         m/h/ or s/v/l3,/g;
718                         m/v/ or s/h([^h]*)/l$1,3/g;
719                         my $dot = s/\.// && qq(<circle cx="3" cy="3" r="1"/>);
720                         qq(<path d="$_"/>$dot)
721                 }
722                 qw(
723                         Hvh  vhv  vh  hvh  vhvh  Hhvh  hv  Vvhv  Hhv
724                         Hvh. vhv. vh. hvh. vhvh. Hhvh. hv. Vvhv. Hhv.
725                         vv  hh  Hhh  Vvv
726                         vv. hh. Hhh. Vvv.
727                 ),
728         ],
729 },
730 nyctographs => {
731         style => [
732                 'svg path { stroke-linecap: round; stroke-linejoin: round }',
733                 '.sample svg {
734                         background: rgba(0,0,0, .1);
735                         padding: 0.1em;
736                         margin-right: 0.2em;
737                 }',
738         ],
739         list => [
740                 map { s/M[\d,\hM]+(?=[M"])//gr }  # clean up superfluous moves
741                 map { sprintf
742                         '<svg width="14" height="14" viewBox="-.5 -.5 5 5">'
743                         . '<path d="M0,0%s %s4,0 %s4,4 %s0,4 %s0,0"/></svg>',
744                         'h.5v.5h-.5v-.5',  # start anchor
745                         map { ['M', 'h0M', 'L']->[$_] }
746                         split //
747                 }
748                 # draw style (0=empty, 1=dot, 2=line connect) to right, down, left, up
749                 qw(
750                         0010 0112 2022 2220 2000 2012 0122 0202 0020 0220 0012 0022 2202
751                         0222 2222 0102 0200 2201 2002 2200 0100 0110 0120 2001 2010 2020
752                         0000
753                 ),
754         ],
755 },
756 chromacons => {
757         title => 'Colour Alphabet by Paul Green-Armytage (2010)',
758         style => [
759                 #'.sample { word-break: break-all }',
760                 '.sample { background: white }',
761         ],
762         list => [
763                 map {
764                         sprintf('<span%s>%s</span>',
765                                 !!$_ && sprintf(' style="background:#%s" title="%s"', split /:/),
766                                 chr(8195) . (!$_ && chr(8203)) # em space (plus zwsp for spaces)
767                         );
768                 }
769                 qw{
770                         F0A3FF:Amethyst 0075DC:Blue     993F00:Caramel  4C005C:Damson
771                         191919:Ebony    005C31:Forest   2BCE48:Green    FFCC99:Honeydew
772                         808080:Iron     94FFB5:Jade     8F7C00:Khaki    9DCC00:Lime
773                         C20088:Mallow   003380:Navy     FFA405:Orpiment FFA8BB:Pink
774                         426600:Quagmire FF0010:Red      5EF1F2:Sky      00998F:Turquoise
775                         E0FF66:Uranium  740AFF:Violet   990000:Wine     FFFF80:Xanthin
776                         FFFF00:Yellow   FF5005:Zinnia   0
777                 }
778         ],
779 },
780 );