File Coverage

lib/SIRTX/Font.pm
Criterion Covered Total %
statement 35 998 3.5
branch 0 530 0.0
condition 0 210 0.0
subroutine 12 70 17.1
pod 38 39 97.4
total 85 1847 4.6


line stmt bran cond sub pod time code
1             # Copyright (c) 2025-2026 Philipp Schafft
2              
3             # licensed under Artistic License 2.0 (see LICENSE file)
4              
5             # ABSTRACT: module for working with SIRTX font files
6              
7             package SIRTX::Font;
8              
9 1     1   204743 use v5.20;
  1         3  
10 1     1   4 use strict;
  1         1  
  1         16  
11 1     1   3 use warnings;
  1         3  
  1         33  
12 1     1   3 use feature 'bitwise';
  1         1  
  1         130  
13              
14 1     1   4 use Carp;
  1         1  
  1         55  
15 1     1   10 use List::Util qw(none);
  1         1  
  1         53  
16 1     1   589 use Data::Identifier;
  1         111040  
  1         5  
17 1     1   735 use Data::Identifier::Util v0.23;
  1         67373  
  1         61  
18 1     1   598 use SIRTX::Datecode;
  1         2682  
  1         45  
19 1     1   6 use Encode ();
  1         1  
  1         25  
20              
21             #use parent 'Data::Identifier::Interface::Userdata';
22              
23 1     1   3 use constant UTF_8 => Encode::find_encoding('UTF-8');
  1         1  
  1         4  
24              
25             use constant {
26 1         14155 MAGIC => pack('CCCCCCCC', 0x00, 0x07, ord('S'), ord('F'), 0x0d, 0x0a, 0xc0, 0x0a),
27             DATA_BIT => 0x40,
28             DATA_START_MARKER => 0x0600,
29             HEADER_MASTER => 0x00,
30             HEADER_EARLY_HINTS => 0x81,
31             HEADER_GEOMETRY_HINTS => 0x82,
32             HEADER_IDENTIFIER => 0xC3,
33             HEADER_DISPLAYINFO => 0x84,
34 1     1   119 };
  1         1  
35              
36             my %_metadata_types = (
37             (map {$_ => 'uint'}
38             qw(width height bits),
39             qw(baseline vmiddleline hmiddleline),
40             qw(version_major version_minor),
41             ),
42             version_type => ['devel', 'beta', 'rc', 'stable'],
43             weight => ['normal', 'bold', 'thin', 'other'],
44             slant => ['roman', 'italic', 'oblique', 'other'],
45             reverse_slant => 'bool',
46             last_modification => 'SIRTX::Datecode',
47             font_tag => 'Data::Identifier',
48             font_name => 'string',
49             icontext => 'codepoint',
50             displaycolour => 'Data::Identifier',
51             );
52              
53             my %_glyph_metadata_types = (
54             resync => 'bool',
55             nomod => 'bool',
56             preskip => 'uint',
57             postskip => 'uint',
58             );
59              
60             my %_metadata_constrains = (
61             (
62             # base values:
63             map {$_ => [
64             ['>', 0],
65             ['<', 256],
66             ]}
67             qw(width height bits),
68             ),
69             baseline => [
70             ['>', 0],
71             ['>', 'vmiddleline'],
72             ['<', 'height'],
73             ],
74             vmiddleline => [
75             ['>', 0],
76             ['<', 'height'],
77             ],
78             hmiddleline => [
79             ['>', 0],
80             ['<', 'width'],
81             ],
82             version_major => [['<', 256]],
83             version_minor => [['<', 64]],
84             );
85              
86             my %_glyph_metadata_constrains = (
87             preskip => [
88             ['<', 4],
89             ['<', ':width'],
90             ],
91             postskip => [
92             ['<', 15],
93             ['<', ':width'],
94             ],
95             );
96              
97             my %_char_lists = (
98             'ascii' => [0x20 .. 0x7E],
99             'dec-mcs' => [
100             0x20 .. 0x7E, # ASCII
101             0xA0 .. 0xA3, 0xA5, 0xA7, 0xA9, 0xAA, 0xAB,
102             0xB0 .. 0xB3, 0xB5 .. 0xB7, 0xB9 .. 0xBD, 0xBF,
103             0xC0 .. 0xCF,
104             0xD1 .. 0xD6, 0xD8 .. 0xDC, 0xDF,
105             0xE0 .. 0xEF,
106             0xF1 .. 0xF6, 0xF8 .. 0xFC,
107             164, 338, 376, 339, 255, # Those with special mappings
108             ],
109             'dec-sg' => [
110             # 0x5X:
111             0x00A0,
112             # 0x6X:
113             0x25C6, 0x2592, 0x2409, 0x240C, 0x240D, 0x240A, 0x00B0, 0x00B1, 0x2424, 0x240B, 0x2518, 0x2510, 0x250C, 0x2514, 0x253C, 0x23BA,
114             # 0x7X:
115             0x23BB, 0x2500, 0x23BC, 0x23BD, 0x251C, 0x2524, 0x2534, 0x252C, 0x2502, 0x2264, 0x2265, 0x03C0, 0x2260, 0x00A3, 0x00B7,
116             ],
117             'dec-tech' => [
118             0x0020, 0x00AC, 0x00D7, 0x00F7, 0x0192, 0x0393, 0x0394, 0x0398, 0x039B, 0x039E, 0x03A0, 0x03A3, 0x03A5, 0x03A6, 0x03A8, 0x03A9,
119             0x03B1, 0x03B2, 0x03B3, 0x03B4, 0x03B5, 0x03B6, 0x03B7, 0x03B8, 0x03B9, 0x03BA, 0x03BB, 0x03BD, 0x03BE, 0x03C0, 0x03C1, 0x03C3,
120             0x03C4, 0x03C5, 0x03C6, 0x03C7, 0x03C8, 0x03C9, 0x2190, 0x2191, 0x2192, 0x2193, 0x21D2, 0x21D4, 0x2202, 0x2207, 0x221A, 0x221D,
121             0x221E, 0x2227, 0x2228, 0x2229, 0x222A, 0x222B, 0x2234, 0x223C, 0x2243, 0x2260, 0x2261, 0x2264, 0x2265, 0x2282, 0x2283, 0x231D,
122             0x231F, 0x2320, 0x2321, 0x239B, 0x239D, 0x239E, 0x23A0, 0x23A1, 0x23A3, 0x23A4, 0x23A6, 0x23A8, 0x23AC, 0x23B2, 0x23B3, 0x23B7,
123             0x2500, 0x2502, 0x250C, 0x2571, 0x2572, 0x27E9,
124             ],
125             'sirtx-characters' => [
126             0x20AC, 0x2191, 0x2193, 0x2190, 0x2192, 0x221E, 0x2261, 0x25C4, 0x25BA, 0x2642, 0x2640, 0x263A, 0x263B, 0x2665, 0x2660, 0x2663,
127             0x266A, 0x23A1, 0x23A6, 0x1F431, 0x1FBB0, 0x2026, 0x03A9, 0x231B, 0x00A4,
128             0xFFFD, 0xFFFC, 0x25B2, 0x25BC,
129             ],
130             'cp-850' => [
131             0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
132             0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
133             0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
134             0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
135             0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
136             0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x00A0,
137             0x00A1, 0x00A2, 0x00A3, 0x00A4, 0x00A5, 0x00A6, 0x00A7, 0x00A8, 0x00A9, 0x00AA, 0x00AB, 0x00AC, 0x00AD, 0x00AE, 0x00AF, 0x00B0,
138             0x00B1, 0x00B2, 0x00B3, 0x00B4, 0x00B5, 0x00B6, 0x00B7, 0x00B8, 0x00B9, 0x00BA, 0x00BB, 0x00BC, 0x00BD, 0x00BE, 0x00BF, 0x00C0,
139             0x00C1, 0x00C2, 0x00C3, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C8, 0x00C9, 0x00CA, 0x00CB, 0x00CC, 0x00CD, 0x00CE, 0x00CF, 0x00D0,
140             0x00D1, 0x00D2, 0x00D3, 0x00D4, 0x00D5, 0x00D6, 0x00D7, 0x00D8, 0x00D9, 0x00DA, 0x00DB, 0x00DC, 0x00DD, 0x00DE, 0x00DF, 0x00E0,
141             0x00E1, 0x00E2, 0x00E3, 0x00E4, 0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x00F0,
142             0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F5, 0x00F6, 0x00F7, 0x00F8, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FD, 0x00FE, 0x00FF, 0x0131,
143             0x0192, 0x2017, 0x2022, 0x203C, 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x21A8, 0x221F, 0x2302, 0x2500, 0x2502, 0x250C,
144             0x2510, 0x2514, 0x2518, 0x251C, 0x2524, 0x252C, 0x2534, 0x253C, 0x2550, 0x2551, 0x2554, 0x2557, 0x255A, 0x255D, 0x2560, 0x2563,
145             0x2566, 0x2569, 0x256C, 0x2580, 0x2584, 0x2588, 0x2591, 0x2592, 0x2593, 0x25A0, 0x25AC, 0x25B2, 0x25BA, 0x25BC, 0x25C4, 0x25CB,
146             0x25D8, 0x25D9, 0x263A, 0x263B, 0x263C, 0x2640, 0x2642, 0x2660, 0x2663, 0x2665, 0x2666, 0x266A, 0x266B,
147             ],
148             'cp-437' => [
149             0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025, 0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B, 0x002C, 0x002D, 0x002E, 0x002F,
150             0x0030, 0x0031, 0x0032, 0x0033, 0x0034, 0x0035, 0x0036, 0x0037, 0x0038, 0x0039, 0x003A, 0x003B, 0x003C, 0x003D, 0x003E, 0x003F,
151             0x0040, 0x0041, 0x0042, 0x0043, 0x0044, 0x0045, 0x0046, 0x0047, 0x0048, 0x0049, 0x004A, 0x004B, 0x004C, 0x004D, 0x004E, 0x004F,
152             0x0050, 0x0051, 0x0052, 0x0053, 0x0054, 0x0055, 0x0056, 0x0057, 0x0058, 0x0059, 0x005A, 0x005B, 0x005C, 0x005D, 0x005E, 0x005F,
153             0x0060, 0x0061, 0x0062, 0x0063, 0x0064, 0x0065, 0x0066, 0x0067, 0x0068, 0x0069, 0x006A, 0x006B, 0x006C, 0x006D, 0x006E, 0x006F,
154             0x0070, 0x0071, 0x0072, 0x0073, 0x0074, 0x0075, 0x0076, 0x0077, 0x0078, 0x0079, 0x007A, 0x007B, 0x007C, 0x007D, 0x007E, 0x00A0,
155             0x00A1, 0x00A2, 0x00A3, 0x00A5, 0x00A7, 0x00AA, 0x00AB, 0x00AC, 0x00B0, 0x00B1, 0x00B2, 0x00B5, 0x00B6, 0x00B7, 0x00BA, 0x00BB,
156             0x00BC, 0x00BD, 0x00BF, 0x00C4, 0x00C5, 0x00C6, 0x00C7, 0x00C9, 0x00D1, 0x00D6, 0x00DC, 0x00DF, 0x00E0, 0x00E1, 0x00E2, 0x00E4,
157             0x00E5, 0x00E6, 0x00E7, 0x00E8, 0x00E9, 0x00EA, 0x00EB, 0x00EC, 0x00ED, 0x00EE, 0x00EF, 0x00F1, 0x00F2, 0x00F3, 0x00F4, 0x00F6,
158             0x00F7, 0x00F9, 0x00FA, 0x00FB, 0x00FC, 0x00FF, 0x0192, 0x0393, 0x0398, 0x03A3, 0x03A6, 0x03A9, 0x03B1, 0x03B4, 0x03B5, 0x03C0,
159             0x03C3, 0x03C4, 0x03C6, 0x2022, 0x203C, 0x207F, 0x20A7, 0x2190, 0x2191, 0x2192, 0x2193, 0x2194, 0x2195, 0x21A8, 0x2219, 0x221A,
160             0x221E, 0x221F, 0x2229, 0x2248, 0x2261, 0x2264, 0x2265, 0x2302, 0x2310, 0x2320, 0x2321, 0x2500, 0x2502, 0x250C, 0x2510, 0x2514,
161             0x2518, 0x251C, 0x2524, 0x252C, 0x2534, 0x253C, 0x2550, 0x2551, 0x2552, 0x2553, 0x2554, 0x2555, 0x2556, 0x2557, 0x2558, 0x2559,
162             0x255A, 0x255B, 0x255C, 0x255D, 0x255E, 0x255F, 0x2560, 0x2561, 0x2562, 0x2563, 0x2564, 0x2565, 0x2566, 0x2567, 0x2568, 0x2569,
163             0x256A, 0x256B, 0x256C, 0x2580, 0x2584, 0x2588, 0x258C, 0x2590, 0x2591, 0x2592, 0x2593, 0x25A0, 0x25AC, 0x25B2, 0x25BA, 0x25BC,
164             0x25C4, 0x25CB, 0x25D8, 0x25D9, 0x263A, 0x263B, 0x263C, 0x2640, 0x2642, 0x2660, 0x2663, 0x2665, 0x2666, 0x266A, 0x266B,
165             ],
166             );
167              
168             $_char_lists{'cp-858'} = [map {$_ == 0x0131 ? 0x20AC : $_} @{$_char_lists{'cp-850'}}];
169              
170             {
171             my %important = map {$_ => undef} (); # initial values
172              
173             foreach my $list (qw(ascii dec-mcs dec-sg sirtx-characters)) {
174             $important{$_} = undef foreach @{$_char_lists{$list}};
175             }
176              
177             $_char_lists{important} = [map {int} keys %important];
178             }
179              
180             my %_default_alias_lists;
181             $_default_alias_lists{'common-small'} = {
182             };
183             $_default_alias_lists{'common-large'} = {
184             %{$_default_alias_lists{'common-small'}},
185             0x00B5 => 0x03BC, # MICRO SIGN -> GREEK SMALL LETTER MU
186             0x037E => 0x003B, # GREEK QUESTION MARK -> SEMICOLON
187             0x0387 => 0x00B7, # GREEK ANO TELEIA -> MIDDLE DOT
188             0x2024 => 0x002E, # ONE DOT LEADER -> FULL STOP
189             0x2126 => 0x03A9, # OHM SIGN -> GREEK CAPITAL LETTER OMEGA
190             0x212B => 0x00C5, # ANGSTROM SIGN -> LATIN CAPITAL LETTER A WITH RING ABOVE
191             0x2236 => 0x003A, # RATIO -> COLON
192             0x2666 => 0x25C6, # BLACK DIAMOND SUIT -> BLACK DIAMOND
193             0x2665 => 0x1F5A4, # BLACK HEART SUIT -> BLACK HEART
194             0x220E => 0x25A0, # END OF PROOF -> BLACK SQUARE
195             0x223C => 0x007E, # TILDE OPERATOR -> TILDE
196              
197             # Roman numbers:
198             0x2160 => 0x0049, # ROMAN NUMERAL ONE -> LATIN CAPITAL LETTER I
199             0x2164 => 0x0056, # ROMAN NUMERAL FIVE -> LATIN CAPITAL LETTER V
200             0x2169 => 0x0058, # ROMAN NUMERAL TEN -> LATIN CAPITAL LETTER X
201             0x216C => 0x004C, # ROMAN NUMERAL FIFTY -> LATIN CAPITAL LETTER L
202             0x216D => 0x0043, # ROMAN NUMERAL ONE HUNDRED -> LATIN CAPITAL LETTER C
203             0x216E => 0x0044, # ROMAN NUMERAL FIVE HUNDRED -> LATIN CAPITAL LETTER D
204             0x216F => 0x004D, # ROMAN NUMERAL ONE THOUSAND -> LATIN CAPITAL LETTER M
205              
206             0x2170 => 0x0069, # SMALL ROMAN NUMERAL ONE -> LATIN SMALL LETTER I
207             0x2174 => 0x0076, # SMALL ROMAN NUMERAL FIVE -> LATIN SMALL LETTER V
208             0x2179 => 0x0078, # SMALL ROMAN NUMERAL TEN -> LATIN SMALL LETTER X
209             0x217C => 0x006C, # SMALL ROMAN NUMERAL FIFTY -> LATIN SMALL LETTER L
210             0x217D => 0x0063, # SMALL ROMAN NUMERAL ONE HUNDRED -> LATIN SMALL LETTER C
211             0x217E => 0x0064, # SMALL ROMAN NUMERAL FIVE HUNDRED -> LATIN SMALL LETTER D
212             0x217F => 0x006D, # SMALL ROMAN NUMERAL ONE THOUSAND -> LATIN SMALL LETTER M
213              
214             # gr_la_ru
215             0x0391 => 0x0041, # GREEK CAPITAL LETTER ALPHA -> LATIN CAPITAL LETTER A
216             0x0392 => 0x0042, # GREEK CAPITAL LETTER BETA -> LATIN CAPITAL LETTER B
217             0x0395 => 0x0045, # GREEK CAPITAL LETTER EPSILON -> LATIN CAPITAL LETTER E
218             0x03A1 => 0x0050, # GREEK CAPITAL LETTER RHO -> LATIN CAPITAL LETTER P
219             0x0397 => 0x0048, # GREEK CAPITAL LETTER ETA -> LATIN CAPITAL LETTER H
220             0x03A4 => 0x0054, # GREEK CAPITAL LETTER TAU -> LATIN CAPITAL LETTER T
221             0x039A => 0x004B, # GREEK CAPITAL LETTER KAPPA -> LATIN CAPITAL LETTER K
222             0x039C => 0x004D, # GREEK CAPITAL LETTER MU -> LATIN CAPITAL LETTER M
223             0x039F => 0x004F, # GREEK CAPITAL LETTER OMICRON -> LATIN CAPITAL LETTER O
224             0x03A7 => 0x0058, # GREEK CAPITAL LETTER CHI -> LATIN CAPITAL LETTER X
225             0x03A5 => 0x0059, # GREEK CAPITAL LETTER UPSILON -> LATIN CAPITAL LETTER Y
226              
227             0x0410 => 0x0041, # CYRILLIC CAPITAL LETTER A -> LATIN CAPITAL LETTER A
228             0x0412 => 0x0042, # CYRILLIC CAPITAL LETTER VE -> LATIN CAPITAL LETTER B
229             0x0415 => 0x0045, # CYRILLIC CAPITAL LETTER IE -> LATIN CAPITAL LETTER E
230             0x0420 => 0x0050, # CYRILLIC CAPITAL LETTER ER -> LATIN CAPITAL LETTER P
231             0x041D => 0x0048, # CYRILLIC CAPITAL LETTER EN -> LATIN CAPITAL LETTER H
232             0x0422 => 0x0054, # CYRILLIC CAPITAL LETTER TE -> LATIN CAPITAL LETTER T
233             0x041A => 0x004B, # CYRILLIC CAPITAL LETTER KA -> LATIN CAPITAL LETTER K
234             0x041C => 0x004D, # CYRILLIC CAPITAL LETTER EM -> LATIN CAPITAL LETTER M
235             0x041E => 0x004F, # CYRILLIC CAPITAL LETTER O -> LATIN CAPITAL LETTER O
236             0x0425 => 0x0058, # CYRILLIC CAPITAL LETTER HA -> LATIN CAPITAL LETTER X
237             0x0423 => 0x0059, # CYRILLIC CAPITAL LETTER U -> LATIN CAPITAL LETTER Y
238              
239             # gr_la
240             0x039D => 0x004E, # GREEK CAPITAL LETTER NU -> LATIN CAPITAL LETTER N
241             0x0396 => 0x005A, # GREEK CAPITAL LETTER ZETA -> LATIN CAPITAL LETTER Z
242             0x0399 => 0x0049, # GREEK CAPITAL LETTER IOTA -> LATIN CAPITAL LETTER I
243             0x03B2 => 0x00DF, # GREEK SMALL LETTER BETA -> LATIN SMALL LETTER SHARP S
244             0x03BF => 0x006F, # GREEK SMALL LETTER OMICRON -> LATIN SMALL LETTER O
245             0x03AA => 0x00CF, # GREEK CAPITAL LETTER IOTA WITH DIALYTIKA -> LATIN CAPITAL LETTER I WITH DIAERESIS
246             0x03AB => 0x0178, # GREEK CAPITAL LETTER UPSILON WITH DIALYTIKA -> LATIN CAPITAL LETTER Y WITH DIAERESIS
247              
248             # la_ru
249             0x0421 => 0x0043, # CYRILLIC CAPITAL LETTER ES -> LATIN CAPITAL LETTER C
250             };
251             $_default_alias_lists{'common-all'} = {
252             %{$_default_alias_lists{'common-large'}},
253             0x00A0 => 0x0020, # NO-BREAK SPACE -> SPACE
254             };
255              
256             my %_drawing_single = (
257             l => 0x2574,
258             t => 0x2575,
259             r => 0x2576,
260             b => 0x2577,
261             tb => 0x2502,
262             lr => 0x2500,
263             br => 0x250C,
264             bl => 0x2510,
265             tr => 0x2514,
266             tl => 0x2518,
267             tbr => 0x251C,
268             tbl => 0x2524,
269             blr => 0x252C,
270             tlr => 0x2534,
271             tblr => 0x253C,
272             );
273              
274             my %_ttf_encodings = (
275             1 => {
276             0 => 'MACINTOSH',
277             },
278             3 => {
279             0 => 'UCS-2', # Symbol !?!?
280             1 => 'UCS-2',
281             },
282             );
283              
284             our $VERSION = v0.08;
285              
286              
287              
288             sub new {
289 0     0 1   my ($pkg, @args) = @_;
290 0           my $self = bless {
291             width => undef,
292             height => undef,
293             bits => undef,
294             glyphs => [],
295             glyph_attr => [],
296             chars => {},
297             util => Data::Identifier::Util->new,
298             }, $pkg;
299              
300 0 0         croak 'Stray options passed' if scalar @args;
301              
302 0           return $self;
303             }
304              
305              
306             sub gc {
307 0     0 1   my ($self) = @_;
308 0           my $chars = $self->{chars};
309 0           my $glyphs = $self->{glyphs};
310 0           my $glyph_attr = $self->{glyph_attr};
311 0           my $new = 0;
312 0           my %updates;
313             my %dedup;
314 0           my %attr;
315              
316 0           for (my $glyph = 0; $glyph < scalar(@{$glyphs}); $glyph++) {
  0            
317 0 0         if (defined $glyph_attr->[$glyph]) {
318 0   0       my $attr = $attr{$glyphs->[$glyph]} //= {};
319 0           %{$attr} = (%{$attr}, %{$glyph_attr->[$glyph]});
  0            
  0            
  0            
320             }
321             }
322              
323 0           foreach my $glyph (values %{$chars}) {
  0            
324 0   0       $updates{$glyph} //= $dedup{$glyphs->[$glyph]};
325 0   0       $updates{$glyph} //= $new++;
326 0           $dedup{$glyphs->[$glyph]} = $updates{$glyph};
327             }
328              
329             {
330 0           my @n;
  0            
331 0           my $last = -1;
332              
333 0           foreach my $glyph (sort {$updates{$a} <=> $updates{$b}} keys %updates) {
  0            
334 0 0         next if $last == $updates{$glyph};
335 0           $last = $updates{$glyph};
336 0           push(@n, $glyphs->[$glyph]);
337             }
338              
339 0           $glyphs = $self->{glyphs} = \@n;
340             }
341              
342 0           foreach my $char (keys %{$chars}) {
  0            
343 0           $chars->{$char} = $updates{$chars->{$char}};
344             }
345              
346 0           @{$glyph_attr} = ();
  0            
347 0           for (my $glyph = 0; $glyph < scalar(@{$glyphs}); $glyph++) {
  0            
348 0   0       $glyph_attr->[$glyph] = $attr{$glyphs->[$glyph]} // next;
349             }
350              
351 0           return $self;
352             }
353              
354             sub _check_constrains {
355 0     0     my ($self, $glyph) = @_;
356 0           my $constrains;
357             my $source;
358              
359 0 0         if (defined $glyph) {
360 0           $constrains = \%_glyph_metadata_constrains;
361 0           $source = $self->{glyph_attr}[$glyph];
362             } else {
363 0           $constrains = \%_metadata_constrains;
364 0           $source = $self;
365             }
366              
367 0           foreach my $key (keys %{$constrains}) {
  0            
368 0   0       my $v = $source->{$key} // next;
369 0           foreach my $constraint (@{$constrains->{$key}}) {
  0            
370 0           my ($cmp, $ref) = @{$constraint};
  0            
371 0           my $res;
372              
373 0 0         if ($ref =~ /^[0-9]+\z/) {
    0          
374 0           $ref = int($ref);
375             } elsif ($ref =~ /^:(.+)\z/) {
376 0   0       $ref = $self->{$1} // next;
377             } else {
378 0   0       $ref = $source->{$ref} // next;
379             }
380              
381 0 0         if ($cmp eq '<') {
    0          
    0          
    0          
    0          
382 0           $res = $v < $ref;
383             } elsif ($cmp eq '<=') {
384 0           $res = $v <= $ref;
385             } elsif ($cmp eq '>') {
386 0           $res = $v > $ref;
387             } elsif ($cmp eq '>=') {
388 0           $res = $v >= $ref;
389             } elsif ($cmp eq '==') {
390 0           $res = $v == $ref;
391             }
392              
393 0 0         unless ($res) {
394 0           croak sprintf('Constraint failed: %s %s %s is false', $key, @{$constraint});
  0            
395             }
396             }
397             }
398              
399 0           return 1;
400             }
401              
402             sub _set_value {
403 0     0     my ($self, $key, $value, $glyph) = @_;
404 0           my $type;
405             my $old;
406              
407 0 0         if (defined $glyph) {
408 0   0       $type = $_glyph_metadata_types{$key // croak 'No key given'};
409 0           $old = $self->{glyph_attr}[$glyph]{$key};
410             } else {
411 0   0       $type = $_metadata_types{$key // croak 'No key given'};
412 0           $old = $self->{$key};
413             }
414              
415 0 0         if ($key =~ s/:rgb\z//) {
416 0           require Data::Identifier::Generate;
417 0           $value = Data::Identifier::Generate->colour($value);
418             }
419              
420 0 0         croak 'Not a valid key: '.$key unless defined $type;
421 0 0         croak 'No value given' unless defined $value;
422              
423 0 0 0       if ($type eq 'uint') {
    0          
    0          
    0          
    0          
    0          
    0          
424 0 0         croak 'Not a valid value for key: '.$key.': '.$value unless $value =~ /^(?:0|[1-9][0-9]*)\z/;
425 0           $value = int($value);
426             } elsif ($type eq 'bool') {
427 0           $value = lc($value);
428 0 0 0       $value = 0 if $value eq 'no' || $value eq 'nein' || $value eq 'false' || $value eq 'off';
      0        
      0        
429 0 0         $value = $value ? 1 : 0;
430             } elsif ($type eq 'string') {
431 0           $value =~ s/^\s+//;
432 0           $value =~ s/\s+\z//;
433 0 0         if (length($value) == 0) {
434 0           croak 'Not a valid string: string is empty';
435             }
436             } elsif ($type eq 'codepoint') {
437 0           $value = $self->_parse_codepoint($value);
438             } elsif (ref $type) {
439 0 0   0     croak 'Value not part of enum: '.$key.': '.$value if none {$_ eq $value} @{$type};
  0            
  0            
440 0           } elsif (eval {$value->isa($type)}) {
441             # all good.
442 0           } elsif ($type eq 'Data::Identifier' && !eval{$value->isa('Data::Identifier')}) {
443 0           $value = Data::Identifier->new(from => $value);
444             } else {
445 0           croak 'BUG';
446             }
447              
448 0 0         if (defined $glyph) {
449 0           $self->{glyph_attr}[$glyph]{$key} = $value;
450             } else {
451 0           $self->{$key} = $value;
452             }
453 0 0         unless (eval {$self->_check_constrains($glyph)}) {
  0            
454 0 0         if (defined $glyph) {
455 0           $self->{glyph_attr}[$glyph]{$key} = $old;
456             } else {
457 0           $self->{$key} = $old;
458             }
459 0           die $@;
460             }
461             }
462              
463              
464             sub width {
465 0     0 1   my ($self, $n) = @_;
466              
467 0 0         $self->_set_value(width => $n) if defined $n;
468              
469 0   0       return $self->{width} // croak 'No width set';
470             }
471              
472              
473             sub height {
474 0     0 1   my ($self, $n) = @_;
475              
476 0 0         $self->_set_value(height => $n) if defined $n;
477              
478 0   0       return $self->{height} // croak 'No height set';
479             }
480              
481              
482             sub bits {
483 0     0 1   my ($self, $n) = @_;
484              
485 0 0         $self->_set_value(bits => $n) if defined $n;
486              
487 0   0       return $self->{bits} // croak 'No bits set';
488             }
489              
490              
491             sub list_attributes {
492 0     0 1   my ($self, @opts) = @_;
493              
494 0 0         croak 'Stray options passed' if scalar @opts;
495              
496 0           return keys %_metadata_types;
497             }
498              
499              
500             sub get_attribute {
501 0     0 1   my ($self, $key, @opts) = @_;
502              
503 0 0         croak 'Stray options passed' if scalar @opts;
504              
505 0 0         croak 'No key given' unless defined $key;
506 0 0         croak 'No such key' unless defined $_metadata_types{$key};
507              
508 0   0       return $self->{$key} // croak 'No value set';
509             }
510              
511              
512             sub set_attribute {
513 0     0 1   my ($self, $key, $value, @opts) = @_;
514              
515 0 0         croak 'Stray options passed' if scalar @opts;
516              
517 0           $self->_set_value($key, $value);
518             }
519              
520              
521             sub codepoints {
522 0     0 1   my ($self) = @_;
523 0 0         croak 'Must be called in scalar context' if wantarray;
524 0           return scalar keys %{$self->{chars}};
  0            
525             }
526              
527              
528             sub glyphs {
529 0     0 1   my ($self) = @_;
530 0 0         croak 'Must be called in scalar context' if wantarray;
531 0           return scalar @{$self->{glyphs}};
  0            
532             }
533              
534              
535             sub has_codepoint {
536 0     0 1   my ($self, $codepoint) = @_;
537              
538 0           $codepoint = $self->_parse_codepoint($codepoint);
539              
540 0           return defined $self->{chars}{$codepoint};
541             }
542              
543              
544             sub remove_codepoint {
545 0     0 1   my ($self, $codepoint) = @_;
546              
547 0           $codepoint = $self->_parse_codepoint($codepoint);
548              
549 0           delete $self->{chars}{$codepoint};
550              
551 0           return $self;
552             }
553              
554              
555             sub has_all_codepoints_from {
556 0     0 1   my ($self, @lists) = @_;
557              
558 0           foreach my $list (@lists) {
559 0   0       foreach my $codepoint (@{$_char_lists{$list} // croak 'Unknown list: '.$list}) {
  0            
560 0 0         return undef unless defined $self->{chars}{$codepoint};
561             }
562             }
563              
564 0           return 1;
565             }
566              
567              
568             sub missing_codepoints_from {
569 0     0 1   my ($self, @lists) = @_;
570 0           my %missing;
571              
572 0           foreach my $list (@lists) {
573 0   0       foreach my $codepoint (@{$_char_lists{$list} // croak 'Unknown list: '.$list}) {
  0            
574 0 0         $missing{$codepoint} = undef unless defined $self->{chars}{$codepoint};
575             }
576             }
577              
578 0           return map {int} keys %missing;
  0            
579             }
580              
581              
582             sub remove_codepoint_not_in {
583 0     0 1   my ($self, @lists) = @_;
584 0           my %required;
585              
586 0           foreach my $list (@lists) {
587 0   0       foreach my $codepoint (@{$_char_lists{$list} // croak 'Unknown list: '.$list}) {
  0            
588 0           $required{$codepoint} = undef;
589             }
590             }
591              
592 0           foreach my $codepoint (keys %{$self->{chars}}) {
  0            
593 0 0         delete $self->{chars}{$codepoint} unless exists $required{$codepoint};
594             }
595              
596 0           return $self;
597             }
598              
599              
600             sub glyph_for {
601 0     0 1   my ($self, $codepoint, $glyph) = @_;
602              
603 0           $codepoint = $self->_parse_codepoint($codepoint);
604              
605 0 0         if (defined $glyph) {
606 0           $glyph = int $glyph;
607 0 0 0       if ($glyph < 0 || $glyph >= scalar(@{$self->{glyphs}})) {
  0            
608 0           croak 'Invalid glyph: '.$glyph;
609             }
610 0           $self->{chars}{$codepoint} = $glyph;
611             }
612              
613 0   0       return $self->{chars}{$codepoint} // croak 'Codepoint unknown: '.$codepoint;
614             }
615              
616             sub _parse_codepoint {
617 0     0     my ($self, $codepoint) = @_;
618              
619 0 0         if ($codepoint =~ /^[Uu]\+([0-9a-fA-F]{4,6})$/) {
620 0           $codepoint = hex($1);
621             } else {
622 0           $codepoint = int($codepoint);
623             }
624              
625 0 0 0       croak 'Unicode character out of range: '.$codepoint if $codepoint < 0 || $codepoint > 0x10FFFF;
626              
627 0           return $codepoint;
628             }
629              
630              
631             sub default_glyph_for {
632 0     0 1   my ($self, $codepoint, $glyph) = @_;
633              
634 0           $codepoint = $self->_parse_codepoint($codepoint);
635              
636 0 0         return $self->{chars}{$codepoint} if defined $self->{chars}{$codepoint};
637 0           return $self->glyph_for($codepoint, $glyph);
638             }
639              
640              
641             sub alias_glyph {
642 0     0 1   my ($self, $from, $to) = @_;
643 0           $self->glyph_for($to, $self->glyph_for($from));
644 0           return $self;
645             }
646              
647              
648             sub default_alias_glyph {
649 0     0 1   my ($self, $from, $to) = @_;
650 0           $self->default_glyph_for($to, $self->glyph_for($from));
651 0           return $self;
652             }
653              
654              
655             sub add_default_aliases {
656 0     0 1   my ($self, $level) = @_;
657 0           my $chars = $self->{chars};
658              
659 0   0       $level //= 'common-small';
660 0   0       $level = $_default_alias_lists{$level} // croak 'Unknown alias level: '.$level;
661              
662             # This is a three pass process:
663             # * first add all forward aliases
664             # * second add all reverse aliases
665             # * thrid add all forward aliases again,
666             # as with the back aliases we might have new ones
667              
668             # Add forward aliases:
669 0   0       $chars->{$_} //= $chars->{$level->{$_}} foreach keys %{$level};
  0            
670              
671             # Add backwards aliases:
672 0   0       $chars->{$level->{$_}} //= $chars->{$_} foreach keys %{$level};
  0            
673              
674             # Retry: Add forward aliases:
675 0   0       $chars->{$_} //= $chars->{$level->{$_}} foreach keys %{$level};
  0            
676              
677             # The above might have added undef values, so remove them now:
678 0           delete $chars->{$_} foreach grep {!defined($chars->{$_})} keys %{$chars};
  0            
  0            
679              
680 0           return $self;
681             }
682              
683              
684             sub get_glyph_attribute {
685 0     0 1   my ($self, $glyph, $key, @opts) = @_;
686              
687 0 0         croak 'Stray options passed' if scalar @opts;
688              
689 0 0         croak 'No glyph given' unless defined $glyph;
690 0 0 0       croak 'Invalid glyph given' if $glyph < 0 || $glyph >= scalar(@{$self->{glyphs}});
  0            
691 0 0         croak 'No key given' unless defined $key;
692 0 0         croak 'No such key' unless defined $_glyph_metadata_types{$key};
693 0 0         croak 'No such glyph' unless exists $self->{glyph_attr}[$glyph];
694              
695 0   0       return $self->{glyph_attr}[$glyph]{$key} // croak 'No value set';
696             }
697              
698              
699             sub set_glyph_attribute {
700 0     0 1   my ($self, $glyph, $key, $value, @opts) = @_;
701              
702 0 0         croak 'Stray options passed' if scalar @opts;
703              
704 0           $self->_set_value($key, $value, $glyph);
705             }
706              
707             sub read {
708 0     0 1   my ($self, $in) = @_;
709 0           my $chars = $self->{chars};
710 0           my $glyphs = $self->{glyphs};
711 0           my $offset = scalar @{$glyphs};
  0            
712 0           my $bits;
713             my $count;
714 0           my $entry_size;
715 0           local $/ = \8;
716              
717 0           $in->binmode;
718              
719 0 0         croak 'Bad magic' unless scalar(<$in>) eq MAGIC;
720              
721             # Read headers, one at a time:
722 0           while (defined(my $header = <$in>)) {
723 0           my ($marker, $va, $vb, $vc, $type, $vx) = unpack('nCCCCn', $header);
724 0           my $extra;
725              
726 0 0         croak 'Bad marker' unless $marker == DATA_START_MARKER;
727              
728 0 0         if ($type & 0x40) {
729 0           my $len = 8 * $vc;
730 0           my $got;
731              
732 0 0         if ($len) {
733 0           read($in, $extra, $len);
734             } else {
735 0           $extra = '';
736             }
737              
738 0           $got = length($extra);
739 0 0         croak sprintf('Cannot read extra from header: got %u, expected %u', $got, $len) if $got != $len;
740             }
741              
742 0 0 0       if ($type == HEADER_MASTER) {
    0          
    0          
    0          
    0          
    0          
743 0           $self->width($va);
744 0           $self->height($vb);
745 0           $self->bits($vc);
746 0           $count = $vx;
747 0           last; # Master header is always the last one, in fact it marks the end of the header section
748             } elsif ($type == HEADER_EARLY_HINTS) {
749 0           $self->width($va);
750 0           $self->height($vb);
751 0           $self->_apply_font_flags($vx);
752             } elsif ($type == HEADER_GEOMETRY_HINTS) {
753 0           $self->set_attribute(hmiddleline => $va);
754 0           $self->set_attribute(vmiddleline => $vb);
755 0           $self->set_attribute(baseline => $vc);
756 0           $self->_apply_font_flags($vx);
757             } elsif ($type == HEADER_IDENTIFIER) {
758 0           $self->set_attribute(version_major => $va);
759 0           $self->set_attribute(version_minor => $vb >> 2);
760              
761 0 0         if (($vb & 0x3) == 3) {
    0          
    0          
    0          
762 0           $self->set_attribute(version_type => 'stable');
763             } elsif (($vb & 0x3) == 2) {
764 0           $self->set_attribute(version_type => 'rc');
765             } elsif (($vb & 0x3) == 1) {
766 0           $self->set_attribute(version_type => 'beta');
767             } elsif (($vb & 0x3) == 0) {
768 0           $self->set_attribute(version_type => 'devel');
769             }
770              
771 0 0         $self->set_attribute(last_modification => SIRTX::Datecode->new(datecode => $vx)) if $vx;
772              
773 0 0         if (length($extra) >= 16) {
774 0           my $uuid = substr($extra, 0, 16);
775 0 0         $self->set_attribute(font_tag => $self->{util}->unpack(uuid128 => $uuid)) if $uuid ne (chr(0) x 16);
776             }
777              
778 0 0         if (length($extra) > 16) {
779 0           my $name = substr($extra, 16);
780 0           $name =~ s/[\0\xff]+\z//;
781 0           $self->set_attribute(font_name => UTF_8->decode($name));
782             }
783             } elsif ($type == HEADER_DISPLAYINFO || $type == (HEADER_DISPLAYINFO|DATA_BIT)) {
784 0           my $icontext = ($va << 8) | $vb;
785 0 0 0       my $displaycolour = $vx != 0 && $vx != 0xFFFF ? eval {
786 0           require Data::Identifier::Wellknown;
787 0           Data::Identifier::Wellknown->import(':all');
788 0           $self->{util}->unpack('4+12', pack('n', $vx));
789             } : undef;
790 0 0 0       $self->set_attribute(icontext => $icontext) if $icontext != 0 && $icontext < 0xFFFD; # We exclude some extra characters here as well, as they might be an error.
791 0 0         $self->set_attribute(displaycolour => $displaycolour) if defined $displaycolour;
792              
793 0 0         if (length($extra) >= 8) {
794 0           my ($eic, $ergb) = unpack('NN', $extra);
795              
796 0 0         $self->set_attribute(icontext => $eic) if $eic <= 0x10FFFF;
797              
798 0 0         if ($ergb <= 0xFFFFFF) {
799 0           require Data::Identifier::Generate;
800 0           $self->set_attribute(displaycolour => Data::Identifier::Generate->colour('#'.unpack('H6', substr($extra, 5, 3))));
801             }
802             }
803             } elsif ($type & 0x80) {
804 0           carp sprintf('Unsupported optional header type: 0x%02X', $type);
805             } else {
806 0           croak sprintf('Unsupported header type: 0x%02X', $type);
807             }
808             }
809              
810 0 0         croak 'No Master header found' unless defined $count;
811 0 0         croak 'Bits value is not 1' unless $self->bits == 1;
812              
813             {
814 0           my $w = $self->width;
  0            
815 0           my $h = $self->height;
816 0 0         $entry_size = (int($w / 8) + ($w & 0x7 ? 1 : 0)) * $h;
817             }
818              
819 0           while (defined(my $data = <$in>)) {
820 0           my ($char, $len, $glyph) = unpack('Nnn', $data);
821 0 0         last if $char == 0xFFFFFFFF;
822              
823 0           for (my $i = 0; $i <= $len; $i++) {
824 0           $chars->{$char+$i} = $glyph + $offset + $i;
825             }
826             }
827              
828 0           $/ = \$entry_size;
829              
830 0           while (defined(my $data = <$in>)) {
831 0 0         croak 'Short read' unless length($data) == $entry_size;
832 0           push(@{$glyphs}, $data);
  0            
833             }
834              
835 0           return $self;
836             }
837              
838              
839             sub write {
840 0     0 1   my ($self, $out) = @_;
841 0           my $chars = $self->{chars};
842 0           my $glyphs = $self->{glyphs};
843 0           my %index;
844 0           my @list = sort {$a <=> $b} keys %{$chars};
  0            
  0            
845 0           my %index_update;
846             my @runs;
847 0           my @extra_headers;
848              
849             {
850 0           my $next_index = 0;
  0            
851 0           my $run;
852              
853 0           foreach my $idx (@list) {
854 0   0       my $glyph = $index_update{$chars->{$idx}} //= $next_index++;
855              
856 0 0         if (defined $run) {
857 0           my $next = $run->[1] + 1;
858 0 0 0       if ($idx == ($run->[0] + $next) && $glyph == ($run->[2] + $next)) {
859 0           $run->[1]++;
860             } else {
861 0           $run = undef;
862 0           redo;
863             }
864             } else {
865 0           push(@runs, $run = [$idx, 0, $glyph]);
866             }
867             }
868              
869             }
870              
871 0           $self->set_attribute(last_modification => SIRTX::Datecode->now);
872              
873 0           push(@extra_headers, eval { $self->_render_geometry_hints });
  0            
874 0           push(@extra_headers, eval { $self->_render_identity });
  0            
875 0           push(@extra_headers, eval { $self->_render_displayinfo });
  0            
876 0           @extra_headers = grep {defined} @extra_headers;
  0            
877              
878 0           $out->binmode;
879              
880             # Write magic:
881 0           print $out MAGIC;
882 0 0         eval { print $out $self->_render_early_hints(scalar(@extra_headers)) } if scalar(@extra_headers);
  0            
883 0           print $out $_ foreach @extra_headers;
884             # Write master header:
885 0           print $out pack('nCCCxn', DATA_START_MARKER, $self->width, $self->height, $self->bits, scalar(keys %index_update));
886             # Write codepoint -> glyph map:
887 0           print $out pack('Nnn', @{$_}) foreach @runs;
  0            
888 0           print $out pack('Nnn', 0xFFFFFFFF, 0, 0);
889              
890 0           foreach my $glyph (sort {$index_update{$a} <=> $index_update{$b}} keys %index_update) {
  0            
891 0           print $out $glyphs->[$glyph];
892             }
893             }
894              
895              
896             sub import_glyph {
897 0     0 1   my ($self, $in) = @_;
898              
899 0 0         if (!eval {$in->isa('Image::Magick')}) {
  0            
900 0           require Image::Magick;
901 0           my $p = Image::Magick->new;
902 0           $p->Read($in);
903 0           $in = $p;
904             }
905              
906 0           return $self->_import_glyph_wbmp($in->ImageToBlob(magick => 'wbmp'));
907             }
908              
909             sub _import_glyph_wbmp {
910 0     0     my ($self, $data) = @_;
911 0           my ($w, $h);
912              
913 0 0         croak 'Bad wbmp magic' unless substr($data, 0, 2) eq "\0\0";
914 0           ($w, $h) = unpack('CC', substr($data, 2, 2));
915              
916 0 0 0       croak 'Bad geometry' if ($w & 0x80) || ($h & 0x80);
917              
918 0           $self->width($w);
919 0           $self->height($h);
920 0           $self->bits(1);
921              
922 0           push(@{$self->{glyphs}}, substr($data, 4));
  0            
923              
924 0           return scalar(@{$self->{glyphs}}) - 1;
  0            
925             }
926              
927             sub _read_kv_file {
928 0     0     my ($self, $filename, $cb) = @_;
929              
930 0 0         open(my $in, '<:utf8', $filename) or croak 'Cannot open: '.$filename.': '.$!;
931 0           while (defined(my $line = <$in>)) {
932 0           my ($pg, $sg);
933              
934 0           $line =~ s/\r?\n$//;
935 0           $line =~ s/^\s+//;
936 0           $line =~ s/\s+$//;
937 0           $line =~ s/^(?:;|\/\/|#).*$//;
938 0           $line =~ s/\s+/ /g;
939              
940 0           ($pg, $sg) = $line =~ /^(.+?\S)(?:\s+(?:--|<-|->|=>|<=|=)\s+(\S.*))?$/;
941              
942 0 0         croak 'Invalid format' unless defined $pg;
943              
944 0           $cb->($pg, $sg);
945              
946             }
947              
948 0           return $self;
949             }
950              
951              
952             sub import_alias_map {
953 0     0 1   my ($self, $filename, %opts) = @_;
954 0           my $chars = $self->{chars};
955              
956 0 0         croak 'Stray options passed' if scalar keys %opts;
957              
958             $self->_read_kv_file($filename => sub {
959 0     0     my ($pg, $sg) = @_;
960 0           my $primary;
961              
962 0   0       $_ = [map {$self->_parse_codepoint($_)} grep {length} split/\s*,\s*|\s+/, $_ // ''] foreach $pg, $sg;
  0            
  0            
963              
964 0 0         croak 'Invalid primary list: list is empty' unless scalar @{$pg};
  0            
965              
966             # Alias all of primary, and then primary to secondary.
967 0           foreach my $char (@{$pg}) {
  0            
968 0   0       $primary //= $chars->{$char};
969             }
970 0 0         if (defined $primary) {
971 0           $chars->{$pg->[0]} = $primary;
972 0           foreach my $char (@{$pg}, @{$sg}) {
  0            
  0            
973 0   0       $chars->{$char} //= $primary;
974             }
975             }
976 0           });
977              
978 0           return $self;
979             }
980              
981              
982             sub import_attributes {
983 0     0 1   my ($self, $filename, %opts) = @_;
984              
985 0 0         croak 'Stray options passed' if scalar keys %opts;
986              
987             return $self->_read_kv_file($filename, sub {
988 0     0     my ($pg, $sg) = @_;
989 0           $self->set_attribute($pg, $sg);
990 0           });
991             }
992              
993              
994             sub import_font {
995 0     0 1   my ($self, $type, $in, %opts) = @_;
996              
997 0 0         croak 'No type given' unless defined $type;
998 0 0         croak 'No input given' unless defined $in;
999              
1000 0 0 0       if ($type eq 'auto' && !ref($in)) {
1001 0 0         if ($in =~ m#\.(sf|psf|hex|ttf)((?:\.gz)?)\z#) {
    0          
1002 0           $type = $1;
1003             } elsif ($in =~ m#[/\\][Uu]\+([0-9a-fA-F]{4,6})\.[^\.]+\z#) {
1004 0           $opts{codepoint} = hex $1;
1005 0           $type = 'glyph';
1006             }
1007             }
1008              
1009 0 0         $type = 'sf' if $type eq 'auto';
1010              
1011 0 0         if ($type eq 'psf') {
    0          
    0          
    0          
    0          
1012 0           return $self->import_psf($in, %opts);
1013             } elsif ($type eq 'hex') {
1014 0           return $self->import_hex($in, %opts);
1015             } elsif ($type eq 'sf') {
1016 0 0         croak 'Stray options passed' if scalar keys %opts;
1017              
1018 0 0         if (!ref $in) {
1019 0           my $gz = $in =~ /\.gz\z/;
1020 0           my $fh;
1021              
1022 0 0         open($fh, '<', $in) or croak $!;
1023 0           $fh->binmode;
1024 0 0         $fh->binmode('gzip') if $gz;
1025 0           $in = $fh;
1026             }
1027              
1028 0           return $self->read($in);
1029             } elsif ($type eq 'glyph') {
1030 0   0       my $codepoint = delete($opts{codepoint}) // croak 'No codepoint given';
1031 0           my $glyph;
1032              
1033 0 0         croak 'Stray options passed' if scalar keys %opts;
1034              
1035 0           $glyph = $self->import_glyph($in);
1036 0           $self->glyph_for($codepoint => $glyph);
1037             } elsif ($type eq 'ttf') {
1038 0           return $self->_import_ttf($in, %opts);
1039             } else {
1040 0           croak 'Unknown type given: '.$type;
1041             }
1042             }
1043              
1044              
1045             #@deprecated
1046             sub import_psf {
1047 0     0 1   my ($self, $in, %opts) = @_;
1048 0           my $first = scalar(@{$self->{glyphs}});
  0            
1049 0           my $chars = $self->{chars};
1050 0           my $data;
1051              
1052 0 0         croak 'Stray options passed' if scalar keys %opts;
1053              
1054 0 0         if (!ref $in) {
1055 0           my $gz = $in =~ /\.gz$/;
1056 0           my $fh;
1057              
1058 0 0         open($fh, '<', $in) or croak $!;
1059 0           $fh->binmode;
1060 0 0         $fh->binmode('gzip') if $gz;
1061 0           $in = $fh;
1062             }
1063              
1064 0 0         croak 'Cannot read magic' if $in->read($data, 2) != 2;
1065              
1066 0 0         if ($data eq pack('v', 0x0436)) {
    0          
1067 0           my ($mode, $height);
1068 0           my $glyphs;
1069              
1070 0 0         croak 'Cannot read header' if $in->read($data, 2) != 2;
1071 0           ($mode, $height) = unpack('CC', $data);
1072              
1073 0           $self->bits(1);
1074 0           $self->width(8);
1075 0           $self->height($height);
1076              
1077 0 0         $glyphs = $mode & 0x01 ? 512 : 256;
1078              
1079 0           for (my $i = 0; $i < $glyphs; $i++) {
1080 0 0         croak 'Cannot read glyph' if $in->read($data, $height) != $height;
1081 0           push(@{$self->{glyphs}}, ~. $data);
  0            
1082             }
1083              
1084 0 0         if ($mode & 0x06) {
1085             # we have a unicode table...
1086 0           my $cc = 0;
1087              
1088 0           while ($in->read($data, 2) == 2) {
1089 0           $data = unpack('v', $data);
1090              
1091 0 0         if ($data == 0xFFFF) {
1092 0 0         $cc++ if $cc < $glyphs;
1093             } else {
1094             #printf("0x%04x\n", $data) if $data > 0xFF00;
1095 0           $chars->{$data} = $first + $cc;
1096             }
1097             }
1098             } else {
1099 0           carp 'No Unicode mapping table found. Gylphs will likely end up at wrong code points';
1100              
1101 0           for (my $i = 0; $i < $glyphs; $i++) {
1102 0           $chars->{$i} = $first + $i;
1103             }
1104             }
1105             } elsif ($data eq pack('v', 0xb572)) {
1106 0           my ($magic2, $version, $headersize, $flags, $numglyph, $bytesperglyph, $height, $width);
1107              
1108 0 0         croak 'Cannot read magic part two' if $in->read($data, 2 + 7*4) != (2 + 7*4);
1109 0           ($magic2, $version, $headersize, $flags, $numglyph, $bytesperglyph, $height, $width) = unpack('vVVVVVVV', $data);
1110              
1111 0 0         croak 'Bad magic part two' if $magic2 != 0x864a;
1112 0 0         croak 'Bad version' if $version != 0;
1113 0 0         croak 'Bad headersize' if $headersize != 32;
1114              
1115 0           $self->bits(1);
1116 0           $self->width($width);
1117 0           $self->height($height);
1118              
1119 0           for (my $i = 0; $i < $numglyph; $i++) {
1120 0 0         croak 'Cannot read glyph' if $in->read($data, $bytesperglyph) != $bytesperglyph;
1121 0           push(@{$self->{glyphs}}, ~. $data);
  0            
1122             }
1123              
1124 0 0         if ($flags) {
1125 0           my $cc = 0;
1126 0           local $/ = chr(0xFF);
1127              
1128 0           while (defined(my $entry = <$in>)) {
1129 0           substr($entry, -1, 1, '');
1130 0           $chars->{ord($_)} = $first + $cc for split //, UTF_8->decode($entry);
1131 0           $cc++;
1132             }
1133             } else {
1134 0           carp 'No Unicode mapping table found. Gylphs will likely end up at wrong code points';
1135              
1136 0           for (my $i = 0; $i < $numglyph; $i++) {
1137 0           $chars->{$i} = $first + $i;
1138             }
1139             }
1140             } else {
1141 0           croak 'Bad magic';
1142             }
1143             }
1144              
1145              
1146             #@deprecated
1147             sub import_hex {
1148 0     0 1   my ($self, $in, %opts) = @_;
1149 0           my $cur = scalar(@{$self->{glyphs}});
  0            
1150 0           my $chars = $self->{chars};
1151 0           my $height;
1152             my $wb;
1153              
1154 0 0         if (!ref $in) {
1155 0           my $gz = $in =~ /\.gz$/;
1156 0           my $fh;
1157              
1158 0 0         open($fh, '<', $in) or croak $!;
1159 0           $fh->binmode;
1160 0 0         $fh->binmode('gzip') if $gz;
1161 0           $in = $fh;
1162             }
1163              
1164 0           $self->bits(1);
1165 0           $self->height(16);
1166              
1167             {
1168 0           my $w = $self->width;
  0            
1169 0 0         $wb = int($w/8) + (($w % 8) ? 1 : 0);
1170             }
1171 0           $height = $self->height;
1172              
1173 0           while (defined(my $line = <$in>)) {
1174 0 0         my ($cp, $pixel) = $line =~ /^([0-9A-F]{4,}):([0-9A-F]+)$/ or next;
1175 0           my $pixel_count;
1176              
1177 0           $cp = hex($cp);
1178 0           $pixel = pack('H*', $pixel);
1179              
1180 0           $pixel_count = length($pixel) / $height;
1181              
1182 0 0 0       if ($pixel_count == $wb) {
    0          
1183             # no-op
1184             } elsif ($pixel_count == 1 && $wb == 2) {
1185             # We need to fill the right side with zeros.
1186 0           $pixel = pack('v*', unpack('C*', $pixel));
1187             } else {
1188 0           next; # we cannot match this.
1189             }
1190              
1191 0           push(@{$self->{glyphs}}, ~. $pixel);
  0            
1192 0           $chars->{$cp} = $cur++;
1193             }
1194             }
1195              
1196             sub _import_ttf {
1197 0     0     require Font::FreeType;
1198 0           Font::FreeType->import(qw(FT_RENDER_MODE_MONO));
1199 0           require Image::Magick;
1200              
1201 0           my ($self, $in, %opts) = @_;
1202 0           my $face = Font::FreeType->new->face($in);
1203 0           my $height = $self->height;
1204 0           my $width = $self->width;
1205 0           my $baseline = $self->{baseline};
1206 0           my %flat_namedinfos;
1207              
1208 0 0         croak 'Stray options passed' if scalar keys %opts;
1209              
1210 0           $face->set_char_size($width, $height, 120, 72);
1211              
1212 0   0       $baseline //= int($height*$face->ascender/$face->height); # no better idea...
1213              
1214             {
1215 0           my $info = $face->namedinfos;
  0            
1216 0   0       foreach my $info (@{$info//[]}) {
  0            
1217 0           my $name_id = $info->name_id;
1218 0           my $platform_id = $info->platform_id;
1219 0           my $language_id = $info->language_id;
1220              
1221 0 0 0       if (($platform_id == 1 && $language_id == 0) || ($platform_id == 3 && $language_id == 0x0409)) {
      0        
      0        
1222 0   0       my $encoding = $_ttf_encodings{$info->platform_id}{$info->encoding_id} // next;
1223 0           my $value = Encode::decode($encoding, $info->string);
1224 0   0       $flat_namedinfos{$name_id} //= $value;
1225 0 0         $flat_namedinfos{$name_id} = [] if $flat_namedinfos{$name_id} ne $value;
1226             }
1227             }
1228             }
1229              
1230 0           $self->_set_value(baseline => $baseline);
1231 0 0         $self->_set_value(slant => $face->is_italic ? 'italic' : 'roman');
1232 0 0         $self->_set_value(weight => $face->is_bold ? 'bold' : 'normal');
1233 0           $self->_set_value(reverse_slant => 0);
1234 0           $self->_set_value(font_name => $face->family_name);
1235              
1236 0 0 0       if (defined($flat_namedinfos{5}) && !ref($flat_namedinfos{5})) {
1237 0           my $v = $flat_namedinfos{5};
1238 0           my ($version_major, $version_minor);
1239              
1240 0 0         if ($v =~ /^(?:MS core font:\s*)?[Vv]([0-9]+)\.([0-9]+)\s*\z/) {
    0          
1241 0           ($version_major, $version_minor) = (int($1), int($2));
1242             } elsif ($v =~ /^Version\s+([0-9]+)\.([0-9]+)(?:\s*;\s*[12][0-9]{3}-[01][0-9]-[0-3][0-9])?\s*\z/) {
1243 0           ($version_major, $version_minor) = (int($1), int($2));
1244             }
1245              
1246 0 0         if (defined $version_major) {
1247 0 0         $version_major = 0 if $version_major < 0;
1248 0 0         $version_major = 255 if $version_major > 255;
1249 0           $self->_set_value(version_major => $version_major);
1250             }
1251 0 0         if (defined $version_minor) {
1252 0 0         $version_minor = 0 if $version_minor < 0;
1253 0 0         $version_minor = 255 if $version_minor > 255;
1254 0           $self->_set_value(version_minor => $version_minor);
1255             }
1256             #warn sprintf('version: %s.%s', $version_major // '', $version_minor // '');
1257             }
1258              
1259             $face->foreach_char(sub {
1260 0     0     my $glyph = $_;
1261 0           my $code_point = $glyph->char_code;
1262 0           my ($bitmap, $left, $top) = $glyph->bitmap_magick(__PACKAGE__->FT_RENDER_MODE_MONO);
1263 0           my $p; # = Image::Magick->new;
1264 0           my $dy = $baseline - $top;
1265 0           my $error = 0;
1266 0           my $ok;
1267              
1268 0 0         $dy = 0 if $dy < 0;
1269              
1270 0           while ($error < 1) {
1271 0           $p = Image::Magick->new;
1272 0           $p->Set(size => sprintf('%ux%u', $width, $height));
1273 0           $p->Read('canvas:black');
1274 0 0         unless ($p->CopyPixels(image => $bitmap, x => 0, y => 0, dx => ($left > 0 ? $left : 0), dy => $dy)) {
    0          
1275 0           $ok = 1;
1276 0           last;
1277             }
1278 0           $error++;
1279             #warn sprintf('error: %u, codepoint: U+%04X', $error, $code_point);
1280 0           $left = 0;
1281 0           $dy = 0;
1282             }
1283              
1284 0 0         return unless $ok;
1285              
1286 0           $p->NegateImage;
1287              
1288 0           $self->glyph_for($code_point => $self->_import_glyph_wbmp($p->ImageToBlob(magick => 'wbmp')));
1289 0           });
1290             }
1291              
1292              
1293             sub import_directory {
1294 0     0 1   my ($self, $directory, %opts) = @_;
1295 0           my $chars = $self->{chars};
1296 0           my $incremental = delete $opts{incremental};
1297              
1298 0           require File::Spec;
1299              
1300 0 0         croak 'Stray options passed' if scalar keys %opts;
1301              
1302 0 0         opendir(my $dir, $directory) or croak 'Cannot open directory: '.$directory;
1303 0           while (defined(my $ent = readdir($dir))) {
1304 0 0         if ($ent =~ /^U\+([0-9A-F]{4,})\.(?:png|wbmp)$/) {
1305 0           my $codepoint = hex $1;
1306 0           my $fullname;
1307             my $glyph;
1308              
1309 0 0 0       next if $incremental && defined $chars->{$codepoint};
1310              
1311 0           $fullname = File::Spec->catfile($directory, $ent);
1312             # TODO: Handle symlinks here.
1313 0           $glyph = $self->import_glyph($fullname);
1314 0           $self->glyph_for($codepoint => $glyph);
1315             }
1316             }
1317 0           closedir($dir);
1318              
1319             {
1320 0           my $fullname = File::Spec->catfile($directory, 'font-attributes.txt');
1321 0 0         $self->import_attributes($fullname) if -f $fullname;
1322             }
1323              
1324             {
1325 0           my $fullname = File::Spec->catfile($directory, 'alias-map.txt');
  0            
  0            
1326 0 0         $self->import_alias_map($fullname) if -f $fullname;
1327             }
1328              
1329 0           return $self;
1330             }
1331              
1332              
1333             sub export_glyph_as_image_magick {
1334 0     0 1   my ($self, $glyph) = @_;
1335 0           my $p;
1336              
1337 0 0         $glyph = int($glyph) if defined $glyph;
1338 0 0 0       croak 'No valid glyph given' unless defined($glyph) && $glyph >= 0;
1339 0           $glyph = $self->{glyphs}[$glyph];
1340 0 0         croak 'No valid glyph given' unless defined($glyph);
1341              
1342 0 0 0       if ($self->width >= 128 || $self->height >= 128 || $self->bits != 1) {
      0        
1343 0           croak 'Unsupported glyph size';
1344             }
1345              
1346 0           require Image::Magick;
1347 0           $p = Image::Magick->new(magick => 'wbmp');
1348 0           $p->BlobToImage(pack('CCCC', 0, 0, $self->width, $self->height).$glyph);
1349              
1350 0           return $p;
1351             }
1352              
1353              
1354             sub export_alias_map {
1355 0     0 1   my ($self, $filename, %opts) = @_;
1356 0           my $chars = $self->{chars};
1357 0           my %glyph_map;
1358 0           local $, = ' ';
1359              
1360 0 0         croak 'Stray options passed' if scalar keys %opts;
1361              
1362 0           foreach my $char (keys %{$chars}) {
  0            
1363 0   0       push(@{$glyph_map{$chars->{$char}} //= []}, $char);
  0            
1364             }
1365              
1366 0 0         open(my $out, '>:utf8', $filename) or croak 'Cannot open file: '.$filename.': '.$!;
1367              
1368 0           foreach my $chars (grep {scalar(@{$_}) > 1} values %glyph_map) {
  0            
  0            
1369 0           $out->say(map {sprintf('U+%04X', $_)} sort {$a <=> $b} @{$chars});
  0            
  0            
  0            
1370             }
1371             }
1372              
1373              
1374             sub export_font {
1375 0     0 1   my ($self, $type, $out, %opts) = @_;
1376              
1377 0 0         croak 'Stray options passed' if scalar keys %opts;
1378              
1379 0 0         if (!ref $out) {
1380 0           my $gz = $out =~ /\.gz\z/;
1381 0           my $fh;
1382              
1383 0 0         open($fh, '>', $out) or croak $!;
1384 0           $fh->binmode;
1385 0 0         $fh->binmode('gzip') if $gz;
1386 0           $out = $fh;
1387             }
1388              
1389 0 0         if ($type eq 'sf') {
    0          
1390 0           return $self->write($out);
1391             } elsif ($type eq 'hex') {
1392 0           return $self->_export_hex($out);
1393             } else {
1394 0           croak 'Unknown type given: '.$type;
1395             }
1396             }
1397              
1398             sub _export_hex {
1399 0     0     my ($self, $out) = @_;
1400 0           my $width = $self->width;
1401 0           my $chars = $self->{chars};
1402 0           my $glyphs = $self->{glyphs};
1403              
1404 0           $self->bits(1);
1405 0           $self->height(16);
1406              
1407 0 0         if ($width & 0x7) {
1408 0           croak 'Unsupported font width for hex format: '.$width;
1409             }
1410              
1411 0           foreach my $codepoint (sort {$a <=> $b} keys %{$chars}) {
  0            
  0            
1412 0           $out->printf("%04X:%s\n", $codepoint, uc unpack('H*', ~. $glyphs->[$chars->{$codepoint}]));
1413             }
1414             }
1415              
1416              
1417             sub make_up_glyphs {
1418 0     0 1   my ($self) = @_;
1419 0           my $w = $self->width;
1420 0           my $h = $self->height;
1421 0           my $vmiddleline = eval {$self->get_attribute('vmiddleline')};
  0            
1422 0           my $hmiddleline = eval {$self->get_attribute('hmiddleline')};
  0            
1423 0           my $h8 = $h/8;
1424 0           my $w8 = $w/8;
1425 0 0         my $wb = int($w8) + (($w % 8) ? 1 : 0);
1426              
1427 0           $self->bits(1);
1428              
1429             # U+0020 SPACE
1430 0           $self->_make_up_glyphs_add_one(0x0020 => [map {chr(0xFF) x $wb} 1..$h]);
  0            
1431             # U+2588 FULL BLOCK
1432 0           $self->_make_up_glyphs_add_one(0x2588 => [map {chr(0x00) x $wb} 1..$h]);
  0            
1433              
1434             # U+2581 LOWER ONE EIGHTH BLOCK .. U+2587 LOWER SEVEN EIGHTHS BLOCK
1435 0           for (my $i = 1; $i < 8; $i++) {
1436 0 0         $self->_make_up_glyphs_add_one((0x2580 + $i) => [map {chr(($h - $_) < ($i*$h8) ? 0x00 : 0xFF) x $wb} 1..$h]);
  0            
1437             }
1438              
1439             # U+2594 UPPER ONE EIGHTH BLOCK
1440 0 0 0       $self->_make_up_glyphs_add_one(0x2594 => [map {chr(($h - $_) > (7*$h8) || $_ == 1 ? 0x00 : 0xFF) x $wb} 1..$h]);
  0            
1441             # U+2580 UPPER HALF BLOCK
1442 0 0         $self->_make_up_glyphs_add_one(0x2580 => [map {chr($_ > (4*$h8) ? 0xFF : 0x00) x $wb} 1..$h]);
  0            
1443              
1444             # Dear reader, have fun figuring out this!
1445             # The basic idea is that we generate a pattern that is 8 or 16 bit wide and use that for the blocks.
1446             # The pattern is created using bit shifts in units of 1/8ths.
1447             # We also ensure that at least a one pixel bar is present, even if we would shift it all out (e.g. 1/8ths of 4 pixels is still one pixel).
1448 0 0         if ($wb == 1) {
    0          
1449 0           my $pattern;
1450              
1451             # U+2589 LEFT SEVEN EIGHTHS BLOCK .. U+258F LEFT ONE EIGHTH BLOCK
1452 0           for (my $i = 7; $i > 0; $i--) {
1453 0           $pattern = chr((0xFF >> ($i*$w8)) & 0x7F);
1454 0           $self->_make_up_glyphs_add_one((0x258F - $i + 1) => [map {$pattern} 1..$h]);
  0            
1455             }
1456              
1457             # U+2590 RIGHT HALF BLOCK
1458 0           $pattern = chr((~(0xFF >> ($w/2))) & 0xFF);
1459 0           $self->_make_up_glyphs_add_one(0x2590 => [map {$pattern} 1..$h]);
  0            
1460              
1461             # U+2595 RIGHT ONE EIGHTH BLOCK
1462 0           $pattern = chr(((~(0xFF >> (8-$w8))) & (0xFE << (8 - $w)) & 0xFF));
1463 0           $self->_make_up_glyphs_add_one(0x2595 => [map {$pattern} 1..$h]);
  0            
1464             } elsif ($wb == 2) {
1465 0           my $pattern;
1466              
1467             # U+2589 LEFT SEVEN EIGHTHS BLOCK .. U+258F LEFT ONE EIGHTH BLOCK
1468 0           for (my $i = 7; $i > 0; $i--) {
1469 0           $pattern = pack('n', (0xFFFF >> ($i*$w8)) & 0x7FFF);
1470 0           $self->_make_up_glyphs_add_one((0x258F - $i + 1) => [map {$pattern} 1..$h]);
  0            
1471             }
1472              
1473             # U+2590 RIGHT HALF BLOCK
1474 0           $pattern = pack('n', (~(0xFFFF >> ($w/2))) & 0xFFFF);
1475 0           $self->_make_up_glyphs_add_one(0x2590 => [map {$pattern} 1..$h]);
  0            
1476              
1477             # U+2595 RIGHT ONE EIGHTH BLOCK
1478 0           $pattern = pack('n', ((~(0xFFFF >> (16-$w8))) & (0xFFFE << (16 - $w)) & 0xFFFF));
1479 0           $self->_make_up_glyphs_add_one(0x2595 => [map {$pattern} 1..$h]);
  0            
1480             }
1481              
1482 0 0 0       if (defined($vmiddleline) && defined($hmiddleline)) {
1483 0           my @init = map {0xFFFF} 1..$h;
  0            
1484 0           foreach my $key (keys %_drawing_single) {
1485 0           my @lines = @init;
1486 0           my $vbit = 0xFFFF ^ (0x8000 >> $vmiddleline);
1487              
1488 0 0         $lines[$hmiddleline] &= 0xFFFF >> $vmiddleline if $key =~ /l/;
1489 0 0         $lines[$hmiddleline] &= 0xFFFF << (16 - $vmiddleline) if $key =~ /r/;
1490 0 0         if ($key =~ /t/) {
1491 0           $lines[$_] &= $vbit foreach 0..$hmiddleline;
1492             }
1493 0 0         if ($key =~ /b/) {
1494 0           $lines[$_] &= $vbit foreach $hmiddleline..($h-1);
1495             }
1496              
1497 0 0         if ($wb == 1) {
    0          
1498 0           $self->_make_up_glyphs_add_one($_drawing_single{$key} => [map {pack('C', ($_ >> 8) & 0xFF)} @lines]);
  0            
1499             } elsif ($wb == 2) {
1500 0           $self->_make_up_glyphs_add_one($_drawing_single{$key} => [map {pack('n', $_)} @lines]);
  0            
1501             }
1502             }
1503             }
1504             }
1505              
1506             sub _make_up_glyphs_add_one {
1507 0     0     my ($self, $cp, $lines) = @_;
1508 0           my $w = $self->width;
1509 0           my $res = '';
1510 0           my $glyph;
1511              
1512 0 0         $w = int($w/8) + (($w % 8) ? 1 : 0);
1513              
1514 0           $self->height(scalar(@{$lines}));
  0            
1515              
1516 0           foreach my $line (@{$lines}) {
  0            
1517 0 0         if (ref $line) {
1518 0           $line = pack('C*', @{$line});
  0            
1519             }
1520 0 0         croak 'BUG!' unless length($line) == $w;
1521 0           $res .= $line;
1522             }
1523              
1524 0           push(@{$self->{glyphs}}, $res);
  0            
1525              
1526 0           $glyph = scalar(@{$self->{glyphs}}) - 1;
  0            
1527              
1528 0           $self->default_glyph_for($cp => $glyph);
1529              
1530 0           return $glyph;
1531             }
1532              
1533              
1534             sub analyse {
1535 0     0 1   my ($self) = @_;
1536 0           my $w = $self->width;
1537 0           my $msb;
1538             my $lsb;
1539 0           my $empty;
1540 0           my $mask;
1541              
1542 0 0         if ($w <= 8) {
    0          
1543 0           $msb = 0x80;
1544 0           $empty = 0xFF;
1545             } elsif ($w <= 16) {
1546 0           $msb = 0x8000;
1547 0           $empty = 0xFFFF;
1548             } else {
1549 0           croak 'Unsupported glyph width';
1550             }
1551 0           $mask = $empty >> $w;
1552 0           $lsb = $msb >> ($w - 1);
1553              
1554             #warn sprintf('MSB: 0x%04X, LSB: 0x%04X', $msb, $lsb);
1555              
1556             {
1557 0           my $baseline;
1558 0           my $matches = 0;
1559 0           foreach my $char (qw(A B C D E F)) { # All those characters should sit on the baseline
1560 0           eval {
1561 0           my @lines = $self->_analyse_read_char(ord($char));
1562              
1563 0           for (my $i = scalar(@lines) - 1; $i >= 0; $i--) {
1564 0           my $d = $lines[$i] | $mask;
1565 0 0         next if $d == $empty;
1566 0 0         if (defined $baseline) {
1567 0 0         last if $baseline != $i;
1568             } else {
1569 0           $baseline = $i;
1570             }
1571 0           $matches++;
1572 0           last;
1573             #printf("U+%04X %2u 0x%04X empty: 0x%04X\n", ord($char), $i, $d, $empty);
1574             }
1575             #say '---';
1576             };
1577             }
1578              
1579             # We require at least 4 matches.
1580 0 0         if ($matches >= 4) {
1581 0           eval { $self->_set_value(baseline => $baseline); }
  0            
1582             }
1583             #warn sprintf('Baseline: %u, matches: %u', $baseline, $matches);
1584             }
1585              
1586             {
1587 0           my $vmiddleline;
  0            
  0            
1588             my $hmiddleline;
1589 0           my $vmatches = 0;
1590 0           my $hmatches = 0;
1591              
1592             # We try to find rows and columns with only one pixel set. They must be the first or last.
1593 0           foreach my $cp (0x2500, 0x2502, 0x250C, 0x2510, 0x2514, 0x2518, 0x253C) {
1594 0           eval {
1595 0           my @lines = $self->_analyse_read_char($cp);
1596 0           my $vpixel;
1597             my $hpixel;
1598              
1599             mid:
1600 0           foreach my $l (@lines[0, -1]) {
1601 0           my $d = ($l | $mask) ^ $empty; # only set pixels are now set in $d
1602              
1603 0           for (my $i = 0; $i < $w; $i++) {
1604 0 0         if ($d & ($msb >> $i)) {
1605 0 0 0       if (defined($vpixel) && $vpixel != $i) {
1606 0           $vpixel = undef;
1607 0           last mid;
1608             } else {
1609 0           $vpixel = $i;
1610             }
1611             }
1612             }
1613             }
1614              
1615             mid:
1616 0           for (my $i = scalar(@lines) - 1; $i >= 0; $i--) {
1617 0           my $d = ($lines[$i] | $mask) ^ $empty; # only set pixels are now set in $d
1618              
1619             #warn sprintf('%2u 0x%04X', $i, $d);
1620 0           foreach my $k ($d & $msb, $d & $lsb) {
1621 0 0         next unless $k;
1622 0 0 0       if (defined($hpixel) && $hpixel != $i) {
1623 0           $hpixel = undef;
1624 0           last mid;
1625             } else {
1626 0           $hpixel = $i;
1627             }
1628             }
1629             }
1630              
1631              
1632 0 0         if (defined $vpixel) {
1633 0           $vpixel = $vpixel;
1634 0 0         if (defined $vmiddleline) {
1635 0 0         $vmatches++ if $vmiddleline == $vpixel;
1636             } else {
1637 0           $vmiddleline = $vpixel;
1638 0           $vmatches++;
1639             }
1640             }
1641 0 0         if (defined $hpixel) {
1642 0 0         if (defined $hmiddleline) {
1643 0 0         $hmatches++ if $hmiddleline == $hpixel;
1644             } else {
1645 0           $hmiddleline = $hpixel;
1646 0           $hmatches++;
1647             }
1648             }
1649              
1650             #printf("U+%04X %2u 0x%04X empty: 0x%04X, vpixel: %s\n", $cp, 0, $lines[0], $empty, $vpixel // '');
1651             }
1652             }
1653              
1654             # We require at least 4 matches.
1655 0 0         if ($vmatches >= 4) {
1656 0           eval { $self->_set_value(vmiddleline => $vmiddleline); }
  0            
1657             }
1658 0 0         if ($hmatches >= 4) {
1659 0           eval { $self->_set_value(hmiddleline => $hmiddleline); }
  0            
1660             }
1661             #warn sprintf('vmiddleline: %u, matches: %u', $vmiddleline, $vmatches);
1662             #warn sprintf('hmiddleline: %u, matches: %u', $hmiddleline, $hmatches);
1663             }
1664              
1665 0           foreach my $cp (sort {$a <=> $b} keys %{$self->{chars}}) {
  0            
  0            
1666 0   0       my $attr = $self->{glyph_attr}[$self->{chars}{$cp}] // {};
1667              
1668 0 0 0       $attr->{resync} //= 1 if $cp >= 0x2500 && $cp <= 0x257F;
      0        
1669              
1670 0 0 0       if ($cp > 0x0020 && $cp < 0x007F && !$attr->{resync}) { # TODO: Improve check
      0        
1671 0           my @lines = $self->_analyse_read_char($cp);
1672 0           my $v = $lines[0];
1673              
1674 0           $v &= $_ foreach @lines;
1675              
1676 0 0         if (!defined($attr->{preskip})) {
1677 0           my $x = $v << (24 - $w);
1678 0           my $preskip = 0;
1679              
1680 0           for (; $preskip < 3; $preskip++, $x <<= 1) {
1681 0 0         last if ($x & 0xC00000) != 0xC00000;
1682             }
1683              
1684 0           $attr->{preskip} = $preskip;
1685             #warn sprintf('U+%04X %02x -> preskip=%u', $cp, $v, $preskip);
1686             }
1687              
1688 0 0         if (!defined($attr->{postskip})) {
1689 0 0         my $x = $w & 7 ? $v >> (8 - ($w & 7)) : $v;
1690 0           my $k = $x;
1691 0           my $postskip = 0;
1692              
1693 0           for (; $postskip < 15; $postskip++, $x >>= 1) {
1694 0 0         last if ($x & 0x03) != 0x03;
1695             }
1696              
1697 0 0         $postskip = 15 if $postskip > 15;
1698 0           $attr->{postskip} = $postskip;
1699             #warn sprintf('U+%04X %02x -> postskip=%u', $cp, $k, $postskip);
1700             }
1701              
1702             #warn sprintf('U+%04X %02x', $cp, $v);
1703             }
1704              
1705 0 0 0       if ($cp == 0x0020 && !defined($attr->{postskip})) {
1706 0           my $postskip = int($w / 4);
1707 0 0         $postskip = 15 if $postskip > 15;
1708 0           $attr->{postskip} = $postskip;
1709             }
1710              
1711 0 0         $self->{glyph_attr}[$self->{chars}{$cp}] = $attr if scalar keys %{$attr};
  0            
1712             }
1713             }
1714              
1715             sub _analyse_read_char {
1716 0     0     my ($self, $cp) = @_;
1717 0           my $glyph = $self->glyph_for($cp);
1718 0           my $w = $self->width;
1719 0           my $p;
1720              
1721 0 0         $glyph = int($glyph) if defined $glyph;
1722 0 0 0       croak 'No valid glyph given' unless defined($glyph) && $glyph >= 0;
1723 0           $glyph = $self->{glyphs}[$glyph];
1724 0 0         croak 'No valid glyph given' unless defined($glyph);
1725              
1726 0 0         if ($self->bits != 1) {
1727 0           croak 'Unsupported glyph size';
1728             }
1729              
1730 0 0         if ($w <= 8) {
    0          
1731 0           return unpack('C*', $glyph);
1732             } elsif ($w <= 16) {
1733 0           return unpack('n*', $glyph);
1734             } else {
1735 0           croak 'Unsupported glyph width';
1736             }
1737             }
1738              
1739              
1740             #@returns SIRTX::Font::Renderer
1741             sub renderer {
1742 0     0 1   my ($self, @opts) = @_;
1743              
1744 0 0         croak 'Stray options passed' if scalar @opts;
1745              
1746 0           require SIRTX::Font::Renderer;
1747              
1748 0           return SIRTX::Font::Renderer->new(font => $self);
1749             }
1750              
1751              
1752             #@deprecated
1753             sub render {
1754 0     0 1   my ($self, @args) = @_;
1755 0           return $self->renderer->render(@args);
1756             }
1757              
1758             # TODO: This is not yet part of public API. make it public. reconsider how it should work before doing so.
1759             sub list_info {
1760 0     0 0   my ($self, $list) = @_;
1761 0           my $chars = $_char_lists{$list};
1762 0 0         return undef unless defined $chars;
1763             return {
1764             name => $list,
1765 0           characters => scalar(@{$chars}),
  0            
1766             };
1767             }
1768              
1769             # ---- Private helpers ----
1770              
1771             sub _render_font_flags {
1772 0     0     my ($self) = @_;
1773 0           my $slant = $self->get_attribute('slant');
1774 0           my $weight = $self->get_attribute('weight');
1775 0           my $res = 0;
1776              
1777 0 0         $res |= $self->has_all_codepoints_from('important') ? 1 << 5 : 0;
1778 0 0         $res |= $self->get_attribute('reverse_slant') ? 0 : 1 << 4;
1779              
1780 0 0         if ($slant eq 'roman') {
    0          
    0          
1781 0           $res |= 3 << 2;
1782             } elsif ($slant eq 'italic') {
1783 0           $res |= 1 << 2;
1784             } elsif ($slant eq 'oblique') {
1785 0           $res |= 2 << 2;
1786             } else {
1787 0           $res |= 0;
1788             }
1789              
1790 0 0         if ($weight eq 'normal') {
    0          
    0          
1791 0           $res |= 3;
1792             } elsif ($weight eq 'bold') {
1793 0           $res |= 1;
1794             } elsif ($weight eq 'thin') {
1795 0           $res |= 2;
1796             } else {
1797 0           $res |= 0;
1798             }
1799              
1800 0           return 0xFF40 | $res;
1801             }
1802              
1803             sub _apply_font_flags {
1804 0     0     my ($self, $flags) = @_;
1805              
1806             # lower byte:
1807 0 0 0       if (($flags & 0xC0) == 0x40) {
    0          
1808             # Data in this byte
1809 0           my $slant = $flags & 0x0C;
1810 0           my $weight = $flags & 0x03;
1811 0           $self->set_attribute(reverse_slant => ($flags & 0x10) == 0);
1812              
1813 0 0         if ($slant == 0x0C) {
    0          
    0          
1814 0           $self->set_attribute(slant => 'roman');
1815             } elsif ($slant == 0x04) {
1816 0           $self->set_attribute(slant => 'italic');
1817             } elsif ($slant == 0x08) {
1818 0           $self->set_attribute(slant => 'oblique');
1819             } else {
1820 0           $self->set_attribute(slant => 'other');
1821             }
1822              
1823 0 0         if ($weight == 0x03) {
    0          
    0          
1824 0           $self->set_attribute(weight => 'normal');
1825             } elsif ($weight == 0x01) {
1826 0           $self->set_attribute(weight => 'bold');
1827             } elsif ($weight == 0x02) {
1828 0           $self->set_attribute(weight => 'thin');
1829             } else {
1830 0           $self->set_attribute(weight => 'other');
1831             }
1832             } elsif (($flags & 0xC0) == 0xC0 || ($flags & 0xC0) == 0x00) {
1833             # no-op, no data
1834             } else {
1835 0           croak sprintf('Invalid or unsupported font flags: 0x%04X', $flags);
1836             }
1837              
1838             # upper byte:
1839 0 0 0       if (($flags & 0xC000) == 0xC000 || ($flags & 0xC000) == 0x0000) {
1840             # no-op, no data
1841             } else {
1842 0           croak sprintf('Invalid or unsupported font flags: 0x%04X', $flags);
1843             }
1844             }
1845              
1846             sub _render_early_hints {
1847 0     0     my ($self, $skips) = @_;
1848              
1849 0   0       $skips //= 0;
1850              
1851 0           return pack('nCCCCn', DATA_START_MARKER,
1852             $self->width, $self->height,
1853             $skips,
1854             HEADER_EARLY_HINTS,
1855             $self->_render_font_flags,
1856             );
1857             }
1858              
1859             sub _render_geometry_hints {
1860 0     0     my ($self) = @_;
1861 0           return pack('nCCCCn', DATA_START_MARKER,
1862             $self->get_attribute('hmiddleline'), $self->get_attribute('vmiddleline'),
1863             $self->get_attribute('baseline'),
1864             HEADER_GEOMETRY_HINTS,
1865             $self->_render_font_flags,
1866             );
1867             }
1868              
1869             sub _render_identity {
1870 0     0     my ($self) = @_;
1871 0           my $minor = $self->get_attribute('version_minor') << 2;
1872 0           my $version_type = $self->get_attribute('version_type');
1873 0           my $uuid = eval {$self->{util}->pack(uuid128 => $self->get_attribute('font_tag'))};
  0            
1874 0           my $extra = '';
1875              
1876 0 0         if ($version_type eq 'stable') {
    0          
    0          
    0          
1877 0           $minor |= 3;
1878             } elsif ($version_type eq 'rc') {
1879 0           $minor |= 2;
1880             } elsif ($version_type eq 'beta') {
1881 0           $minor |= 1;
1882             } elsif ($version_type eq 'devel') {
1883 0           $minor |= 0;
1884             } else {
1885 0           croak 'Invalid version type: '.$version_type;
1886             }
1887              
1888 0 0         if (defined $uuid) {
1889 0           $extra = $uuid;
1890             }
1891              
1892 0 0         if (defined(my $name = eval {$self->get_attribute('font_name')})) {
  0            
1893 0           my $len;
1894              
1895 0 0         if (length($extra) != 16) {
1896 0           $extra = chr(0) x 16;
1897             }
1898              
1899 0           $name = UTF_8->encode($name);
1900 0           $len = length($name);
1901              
1902 0 0         if ($len % 8) {
1903 0           $name .= chr(0);
1904 0           $len++;
1905 0 0         if ($len % 8) {
1906 0           $name .= chr(0xFF) x (8 - ($len % 8));
1907             }
1908             }
1909              
1910 0           $extra .= $name;
1911             }
1912              
1913             # TODO: Add support to write name of font
1914              
1915 0           return pack('nCCCCn', DATA_START_MARKER,
1916             $self->get_attribute('version_major'),
1917             $minor,
1918             length($extra) / 8,
1919             HEADER_IDENTIFIER,
1920             $self->get_attribute('last_modification')->datecode,
1921             ).$extra;
1922             }
1923              
1924             sub _render_displayinfo {
1925 0     0     my ($self) = @_;
1926 0           my $icontext = eval {$self->get_attribute('icontext')};
  0            
1927 0           my $displaycolour = eval {$self->get_attribute('displaycolour')};
  0            
1928 0           my $displaycolour_412;
1929             my $displaycolour_rgb;
1930 0           my $icontext_16;
1931 0           my $extra = '';
1932              
1933 0 0 0       return unless defined($icontext) || defined($displaycolour);
1934              
1935 0   0       $icontext //= 0xFFFFFFFF;
1936              
1937 0 0         if (defined $displaycolour) {
1938 0           require Data::Identifier::Wellknown;
1939 0           Data::Identifier::Wellknown->import(':all');
1940             }
1941 0 0         $displaycolour_412 = defined($displaycolour) ? eval {$self->{util}->pack('4+12', $displaycolour)} : undef;
  0            
1942 0   0       $displaycolour_412 //= pack('n', 0);
1943 0 0         $icontext_16 = $icontext >= 0xFFFF ? 0xFFFF : $icontext;
1944              
1945 0 0 0       if (defined($displaycolour) && eval { $displaycolour->generator->eq('55febcc4-6655-4397-ae3d-2353b5856b34') } ) {
  0            
1946 0           eval {
1947 0           $displaycolour_rgb = pack('xH6', substr($displaycolour->request, 1));
1948             };
1949             }
1950              
1951 0 0 0       if ((defined($icontext) && $icontext >= 0xFFFD) || defined($displaycolour_rgb)) {
      0        
1952 0 0         $extra .= defined($icontext) ? pack('N', $icontext & 0xFFFFFF) : chr(255) x 4;
1953 0 0         $extra .= defined($displaycolour_rgb) ? $displaycolour_rgb : chr(255) x 4;
1954             }
1955              
1956 0 0         return pack('nnCCa2', DATA_START_MARKER,
1957             $icontext_16,
1958             length($extra) / 8,
1959             HEADER_DISPLAYINFO | ($extra ne '' ? DATA_BIT : 0),
1960             $displaycolour_412,
1961             ).$extra;
1962             }
1963              
1964             1;
1965              
1966             __END__