File Coverage

blib/lib/Font/TTF/Utils.pm
Criterion Covered Total %
statement 115 239 48.1
branch 60 130 46.1
condition 30 60 50.0
subroutine 10 15 66.6
pod 10 12 83.3
total 225 456 49.3


line stmt bran cond sub pod time code
1             package Font::TTF::Utils;
2              
3             =head1 NAME
4              
5             Font::TTF::Utils - Utility functions to save fingers
6              
7             =head1 DESCRIPTION
8              
9             Lots of useful functions to save my fingers, especially for trivial tables
10              
11             =head1 FUNCTIONS
12              
13             The following functions are exported
14              
15             =cut
16              
17 1     1   3 use strict;
  1         1  
  1         24  
18 1     1   2 use vars qw(@ISA @EXPORT $VERSION @EXPORT_OK);
  1         1  
  1         2496  
19             require Exporter;
20              
21             @ISA = qw(Exporter);
22             @EXPORT = qw(TTF_Init_Fields TTF_Read_Fields TTF_Out_Fields TTF_Pack
23             TTF_Unpack TTF_word_utf8 TTF_utf8_word TTF_bininfo);
24             @EXPORT_OK = (@EXPORT, qw(XML_hexdump));
25             $VERSION = 0.0001;
26              
27             =head2 ($val, $pos) = TTF_Init_Fields ($str, $pos)
28              
29             Given a field description from the C section, creates an absolute entry
30             in the fields associative array for the class
31              
32             =cut
33              
34             sub TTF_Init_Fields
35             {
36 119     119 1 112 my ($str, $pos, $inval) = @_;
37 119         64 my ($key, $val, $res, $len, $rel);
38              
39 119         95 $str =~ s/\r?\n$//o;
40 119 50       113 if ($inval)
41 119         87 { ($key, $val) = ($str, $inval); }
42             else
43 0         0 { ($key, $val) = split(',\s*', $str); }
44 119 50 33     337 return (undef, undef, 0) unless (defined $key && $key ne "");
45 119 50       260 if ($val =~ m/^(\+?)(\d*)(\D+)(\d*)/oi)
46             {
47 119         122 $rel = $1;
48 119 100       180 if ($rel eq "+")
    50          
49 1         2 { $pos += $2; }
50             elsif ($2 ne "")
51 0         0 { $pos = $2; }
52 119         102 $val = $3;
53 119         93 $len = $4;
54             }
55 119 50       131 $len = "" unless defined $len;
56 119 100 66     289 $pos = 0 if !defined $pos || $pos eq "";
57 119         111 $res = "$pos:$val:$len";
58 119 100 100     602 if ($val eq "f" || $val eq 'v' || $val =~ m/^[l]/oi)
    100 100        
      66        
59 27 100       37 { $pos += 4 * ($len ne "" ? $len : 1); }
60             elsif ($val eq "F" || $val =~ m/^[s]/oi)
61 82 50       88 { $pos += 2 * ($len ne "" ? $len : 1); }
62             else
63 10 50       12 { $pos += 1 * ($len ne "" ? $len : 1); }
64              
65 119         279 ($key, $res, $pos);
66             }
67              
68              
69             =head2 TTF_Read_Fields($obj, $dat, $fields)
70              
71             Given a block of data large enough to account for all the fields in a table,
72             processes the data block to convert to the values in the objects instance
73             variables by name based on the list in the C block which has been run
74             through C
75              
76             =cut
77              
78             sub TTF_Read_Fields
79             {
80 198     198 1 182 my ($self, $dat, $fields) = @_;
81 198         143 my ($pos, $type, $res, $f, $arrlen, $arr, $frac);
82              
83 198         136 foreach $f (keys %{$fields})
  198         424  
84             {
85 1134         2455 ($pos, $type, $arrlen) = split(':', $fields->{$f});
86 1134 50       1683 $pos = 0 if $pos eq "";
87 1134 100       1075 if ($arrlen ne "")
88 4         9 { $self->{$f} = [TTF_Unpack("$type$arrlen", substr($dat, $pos))]; }
89             else
90 1130         1934 { $self->{$f} = TTF_Unpack("$type", substr($dat, $pos)); }
91             }
92 198         337 $self;
93             }
94              
95              
96             =head2 TTF_Unpack($fmt, $dat)
97              
98             A TrueType types equivalent of Perls C function. Thus $fmt consists of
99             type followed by an optional number of elements to read including *. The type
100             may be one of:
101              
102             c BYTE
103             C CHAR
104             f FIXED
105             F F2DOT14
106             l LONG
107             L ULONG
108             s SHORT
109             S USHORT
110             v Version number (FIXED)
111              
112             Note that C, C and C are not data types but units.
113              
114             Returns array of scalar (first element) depending on context
115              
116             =cut
117              
118             sub TTF_Unpack
119             {
120 2302     2302 1 3135 my ($fmt, $dat) = @_;
121 2302         1275 my ($res, $frac, $i, $arrlen, $type, @res);
122              
123 2302         6806 while ($fmt =~ s/^([cflsv])(\d+|\*)?//oi)
124             {
125 2302         2539 $type = $1;
126 2302         1589 $arrlen = $2;
127 2302 100 66     3456 $arrlen = 1 if !defined $arrlen || $arrlen eq "";
128 2302 100       2715 $arrlen = -1 if $arrlen eq "*";
129              
130 2302   100     7489 for ($i = 0; ($arrlen == -1 && $dat ne "") || $i < $arrlen; $i++)
      100        
131             {
132 2334 100       7121 if ($type eq "f")
    100          
    50          
    100          
    100          
    50          
133             {
134 6         11 ($res, $frac) = unpack("nn", $dat);
135 6         5 substr($dat, 0, 4) = "";
136 6 50       9 $res -= 65536 if $res > 32767;
137 6         9 $res += $frac / 65536.;
138             }
139             elsif ($type eq "v")
140             {
141 6         11 ($res, $frac) = unpack("nn", $dat);
142 6         8 substr($dat, 0, 4) = "";
143 6         25 $res = sprintf("%d.%04X", $res, $frac);
144             }
145             elsif ($type eq "F")
146             {
147 0         0 $res = unpack("n", $dat);
148 0         0 substr($dat, 0, 2) = "";
149             # $res -= 65536 if $res >= 32768;
150 0         0 $frac = $res & 0x3fff;
151 0         0 $res >>= 14;
152 0 0       0 $res -= 4 if $res > 1;
153             # $frac -= 16384 if $frac > 8191;
154 0         0 $res += $frac / 16384.;
155             }
156             elsif ($type =~ m/^[l]/oi)
157             {
158 36         39 $res = unpack("N", $dat);
159 36         34 substr($dat, 0, 4) = "";
160 36 50 33     60 $res -= (1 << 32) if ($type eq "l" && $res >= 1 << 31);
161             }
162             elsif ($type =~ m/^[s]/oi)
163             {
164 2266         2424 $res = unpack("n", $dat);
165 2266         1936 substr($dat, 0, 2) = "";
166 2266 100 100     5732 $res -= 65536 if ($type eq "s" && $res >= 32768);
167             }
168             elsif ($type eq "c")
169             {
170 0         0 $res = unpack("c", $dat);
171 0         0 substr($dat, 0, 1) = "";
172             }
173             else
174             {
175 20         21 $res = unpack("C", $dat);
176 20         20 substr($dat, 0, 1) = "";
177             }
178 2334         8933 push (@res, $res);
179             }
180             }
181 2302 100       5402 return wantarray ? @res : $res[0];
182             }
183              
184              
185             =head2 $dat = TTF_Out_Fields($obj, $fields, $len)
186              
187             Given the fields table from C writes out the instance variables from
188             the object to the filehandle in TTF binary form.
189              
190             =cut
191              
192             sub TTF_Out_Fields
193             {
194 10     10 1 13 my ($obj, $fields, $len) = @_;
195 10         18 my ($dat) = "\000" x $len;
196 10         10 my ($f, $pos, $type, $res, $arr, $arrlen, $frac);
197            
198 10         11 foreach $f (keys %{$fields})
  10         76  
199             {
200 194         380 ($pos, $type, $arrlen) = split(':', $fields->{$f});
201 194 100       231 if ($arrlen ne "")
202 4         5 { $res = TTF_Pack("$type$arrlen", @{$obj->{$f}}); }
  4         10  
203             else
204 190         266 { $res = TTF_Pack("$type", $obj->{$f}); }
205 194         240 substr($dat, $pos, length($res)) = $res;
206             }
207 10         39 $dat;
208             }
209              
210              
211             =head2 $dat = TTF_Pack($fmt, @data)
212              
213             The TrueType equivalent to Perl's C function. See details of C
214             for how to work the $fmt string.
215              
216             =cut
217              
218             sub TTF_Pack
219             {
220 196     196 1 183 my ($fmt, @obj) = @_;
221 196         111 my ($type, $i, $arrlen, $dat, $res, $frac);
222              
223 196         129 $dat = '';
224 196         488 while ($fmt =~ s/^([flscv])(\d+|\*)?//oi)
225             {
226 196         226 $type = $1;
227 196   100     427 $arrlen = $2 || "";
228 196 50       235 $arrlen = $#obj + 1 if $arrlen eq "*";
229 196 100       216 $arrlen = 1 if $arrlen eq "";
230            
231 196         250 for ($i = 0; $i < $arrlen; $i++)
232             {
233 200   100     282 $res = shift(@obj) || 0;
234 200 100       560 if ($type eq "f")
    100          
    50          
    100          
    100          
    50          
235             {
236 6         12 $frac = int(($res - int($res)) * 65536);
237 6         3 $res = (int($res) << 16) + $frac;
238 6         21 $dat .= pack("N", $res);
239             }
240             elsif ($type eq "v")
241             {
242 6 50       25 if ($res =~ s/\.([0-9a-f]+)$//oi)
243             {
244 6         6 $frac = $1;
245 6         9 $frac .= "0" x (4 - length($frac));
246             }
247             else
248 0         0 { $frac = 0; }
249 6         24 $dat .= pack('nn', $res, hex($frac));
250             }
251             elsif ($type eq "F")
252             {
253 0         0 $frac = int(($res - int($res)) * 16384);
254 0         0 $res = (int($res) << 14) + $frac;
255 0         0 $dat .= pack("n", $res);
256             }
257             elsif ($type =~ m/^[l]/oi)
258             {
259 36 50 33     94 $res += 1 << 32 if ($type eq 'L' && $res < 0);
260 36         92 $dat .= pack("N", $res);
261             }
262             elsif ($type =~ m/^[s]/oi)
263             {
264 132 50 66     260 $res += 1 << 16 if ($type eq 'S' && $res < 0);
265 132         311 $dat .= pack("n", $res);
266             }
267             elsif ($type eq "c")
268 0         0 { $dat .= pack("c", $res); }
269             else
270 20         51 { $dat .= pack("C", $res); }
271             }
272             }
273 196         239 $dat;
274             }
275              
276              
277             =head2 ($num, $range, $select, $shift) = TTF_bininfo($num)
278              
279             Calculates binary search information from a number of elements
280              
281             =cut
282              
283             sub TTF_bininfo
284             {
285 6     6 1 5 my ($num, $block) = @_;
286 6         6 my ($range, $select, $shift);
287              
288 6         6 $range = 1;
289 6         12 for ($select = 0; $range <= $num; $select++)
290 16         22 { $range *= 2; }
291 6         6 $select--; $range /= 2;
  6         7  
292 6         7 $range *= $block;
293              
294 6         4 $shift = $num * $block - $range;
295 6         28 ($num, $range, $select, $shift);
296             }
297              
298              
299             =head2 TTF_word_utf8($str)
300              
301             Returns the UTF8 form of the 16 bit string, assumed to be in big endian order,
302             including surrogate handling
303              
304             =cut
305              
306             sub TTF_word_utf8
307             {
308 122     122 1 90 my ($str) = @_;
309 122         62 my ($res, $i);
310 122         1252 my (@dat) = unpack("n*", $str);
311              
312 122 50       1217 return pack("U*", @dat) if ($] >= 5.006);
313 0         0 for ($i = 0; $i <= $#dat; $i++)
314             {
315 0         0 my ($dat) = $dat[$i];
316 0 0 0     0 if ($dat < 0x80) # Thanks to Gisle Aas for some of his old code
    0          
    0          
317 0         0 { $res .= chr($dat); }
318             elsif ($dat < 0x800)
319 0         0 { $res .= chr(0xC0 | ($dat >> 6)) . chr(0x80 | ($dat & 0x3F)); }
320             elsif ($dat >= 0xD800 && $dat < 0xDC00)
321             {
322 0         0 my ($dat1) = $dat[++$i];
323 0         0 my ($top) = (($dat & 0x3C0) >> 6) + 1;
324 0         0 $res .= chr(0xF0 | ($top >> 2))
325             . chr(0x80 | (($top & 1) << 4) | (($dat & 0x3C) >> 2))
326             . chr(0x80 | (($dat & 0x3) << 4) | (($dat1 & 0x3C0) >> 6))
327             . chr(0x80 | ($dat1 & 0x3F));
328             } else
329 0         0 { $res .= chr(0xE0 | ($dat >> 12)) . chr(0x80 | (($dat >> 6) & 0x3F))
330             . chr(0x80 | ($dat & 0x3F)); }
331             }
332 0         0 $res;
333             }
334              
335              
336             =head2 TTF_utf8_word($str)
337              
338             Returns the 16-bit form in big endian order of the UTF 8 string, including
339             surrogate handling to Unicode.
340              
341             =cut
342              
343             sub TTF_utf8_word
344             {
345 122     122 1 98 my ($str) = @_;
346 122         64 my ($res);
347              
348 122 50       1932 return pack("n*", unpack("U*", $str)) if ($^V ge v5.6.0);
349 0           $str = "$str"; # copy $str
350 0           while (length($str)) # Thanks to Gisle Aas for some of his old code
351             {
352 0           $str =~ s/^[\x80-\xBF]+//o;
353 0 0         if ($str =~ s/^([\x00-\x7F]+)//o)
    0          
    0          
    0          
    0          
354 0           { $res .= pack("n*", unpack("C*", $1)); }
355             elsif ($str =~ s/^([\xC0-\xDF])([\x80-\xBF])//o)
356 0           { $res .= pack("n", ((ord($1) & 0x1F) << 6) | (ord($2) & 0x3F)); }
357             elsif ($str =~ s/^([\0xE0-\xEF])([\x80-\xBF])([\x80-\xBF])//o)
358 0           { $res .= pack("n", ((ord($1) & 0x0F) << 12)
359             | ((ord($2) & 0x3F) << 6)
360             | (ord($3) & 0x3F)); }
361             elsif ($str =~ s/^([\xF0-\xF7])([\x80-\xBF])([\x80-\xBF])([\x80-\xBF])//o)
362             {
363 0           my ($b1, $b2, $b3, $b4) = (ord($1), ord($2), ord($3), ord($4));
364 0           $res .= pack("n", ((($b1 & 0x07) << 8) | (($b2 & 0x3F) << 2)
365             | (($b3 & 0x30) >> 4)) + 0xD600); # account for offset
366 0           $res .= pack("n", ((($b3 & 0x0F) << 6) | ($b4 & 0x3F)) + 0xDC00);
367             }
368             elsif ($str =~ s/^[\xF8-\xFF][\x80-\xBF]*//o)
369             { }
370             }
371 0           $res;
372             }
373              
374              
375             =head2 XML_hexdump($context, $dat)
376              
377             Dumps out the given data as a sequence of blocks each 16 bytes wide
378              
379             =cut
380              
381             sub XML_hexdump
382             {
383 0     0 1   my ($context, $depth, $dat) = @_;
384 0           my ($fh) = $context->{'fh'};
385 0           my ($i, $len, $out);
386              
387 0           $len = length($dat);
388 0           for ($i = 0; $i < $len; $i += 16)
389             {
390 0           $out = join(' ', map {sprintf("%02X", ord($_))} (split('', substr($dat, $i, 16))));
  0            
391 0           $fh->printf("%s%s\n", $depth, $i, $out);
392             }
393             }
394              
395              
396             =head2 XML_outhints
397              
398             Converts a binary string of hinting code into a textual representation
399              
400             =cut
401              
402             {
403             my (@hints) = (
404             ['SVTCA[0]'], ['SVTCA[1]'], ['SPVTCA[0]'], ['SPVTCA[1]'], ['SFVTCA[0]'], ['SFVTCA[1]'], ['SPVTL[0]'], ['SPVTL[1]'],
405             ['SFVTL[0]'], ['SFVTL[1]'], ['SPVFS'], ['SFVFS'], ['GPV'], ['GFV'], ['SVFTPV'], ['ISECT'],
406             # 10
407             ['SRP0'], ['SRP1'], ['SRP2'], ['SZP0'], ['SZP1'], ['SZP2'], ['SZPS'], ['SLOOP'],
408             ['RTG'], ['RTHG'], ['SMD'], ['ELSE'], ['JMPR'], ['SCVTCI'], ['SSWCI'], ['SSW'],
409             # 20
410             ['DUP'], ['POP'], ['CLEAR'], ['SWAP'], ['DEPTH'], ['CINDEX'], ['MINDEX'], ['ALIGNPTS'],
411             [], ['UTP'], ['LOOPCALL'], ['CALL'], ['FDEF'], ['ENDF'], ['MDAP[0]'], ['MDAP[1]'],
412             # 30
413             ['IUP[0]'], ['IUP[1]'], ['SHP[0]'], ['SHP[1]'], ['SHC[0]'], ['SHC[1]'], ['SHZ[0]'], ['SHZ[1]'],
414             ['SHPIX'], ['IP'], ['MSIRP[0]'], ['MSIRP[1]'], ['ALIGNRP'], ['RTDG'], ['MIAP[0]'], ['MIAP[1]'],
415             # 40
416             ['NPUSHB', -1, 1], ['NPUSHW', -1, 2], ['WS', 0, 0], ['RS', 0, 0], ['WCVTP', 0, 0], ['RCVT', 0, 0], ['GC[0]'], ['GC[1]'],
417             ['SCFS'], ['MD[0]'], ['MD[1]'], ['MPPEM'], ['MPS'], ['FLIPON'], ['FLIPOFF'], ['DEBUG'],
418             # 50
419             ['LT'], ['LTEQ'], ['GT'], ['GTEQ'], ['EQ'], ['NEQ'], ['ODD'], ['EVEN'],
420             ['IF'], ['EIF'], ['AND'], ['OR'], ['NOT'], ['DELTAP1'], ['SDB'], ['SDS'],
421             # 60
422             ['ADD'], ['SUB'], ['DIV'], ['MULT'], ['ABS'], ['NEG'], ['FLOOR'], ['CEILING'],
423             ['ROUND[0]'], ['ROUND[1]'], ['ROUND[2]'], ['ROUND[3]'], ['NROUND[0]'], ['NROUND[1]'], ['NROUND[2]'], ['NROUND[3]'],
424             # 70
425             ['WCVTF'], ['DELTAP2'], ['DELTAP3'], ['DELTAC1'], ['DELTAC2'], ['DELTAC3'], ['SROUND'], ['S45ROUND'],
426             ['JROT'], ['JROF'], ['ROFF'], [], ['RUTG'], ['RDTG'], ['SANGW'], [],
427             # 80
428             ['FLIPPT'], ['FLIPRGON'], ['FLIPRGOFF'], [], [], ['SCANCTRL'], ['SDPVTL[0]'], ['SDPVTL[1]'],
429             ['GETINFO'], ['IDEF'], ['ROLL'], ['MAX'], ['MIN'], ['SCANTYPE'], ['INSTCTRL'], [],
430             # 90
431             [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [],
432             # A0
433             [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [],
434             # B0
435             ['PUSHB1', 1, 1], ['PUSHB2', 2, 1], ['PUSHB3', 3, 1], ['PUSHB4', 4, 1], ['PUSHB5', 5, 1], ['PUSHB6', 6, 1], ['PUSHB7', 7, 1], ['PUSHB8', 8, 1],
436             ['PUSHW1', 1, 2], ['PUSHW2', 2, 2], ['PUSHW3', 3, 2], ['PUSHW4', 4, 2], ['PUSHW5', 5, 2], ['PUSHW6', 6, 2], ['PUSHW7', 7, 2], ['PUSHW8', 8, 2],
437             # C0
438             ['MDRP[0]'], ['MDRP[1]'], ['MDRP[2]'], ['MDRP[3]'], ['MDRP[4]'], ['MDRP[5]'], ['MDRP[6]'], ['MDRP[7]'],
439             ['MDRP[8]'], ['MDRP[9]'], ['MDRP[A]'], ['MDRP[B]'], ['MDRP[C]'], ['MDRP[D]'], ['MDRP[E]'], ['MDRP[F]'],
440             # D0
441             ['MDRP[10]'], ['MDRP[11]'], ['MDRP[12]'], ['MDRP[13]'], ['MDRP[14]'], ['MDRP[15]'], ['MDRP[16]'], ['MDRP[17]'],
442             ['MDRP[18]'], ['MDRP[19]'], ['MDRP[1A]'], ['MDRP[1B]'], ['MDRP[1C]'], ['MDRP[1D]'], ['MDRP[1E]'], ['MDRP[1F]'],
443             # E0
444             ['MIRP[0]'], ['MIRP[1]'], ['MIRP[2]'], ['MIRP[3]'], ['MIRP[4]'], ['MIRP[5]'], ['MIRP[6]'], ['MIRP[7]'],
445             ['MIRP[8]'], ['MIRP[9]'], ['MIRP[A]'], ['MIRP[B]'], ['MIRP[C]'], ['MIRP[D]'], ['MIRP[E]'], ['MIRP[F]'],
446             # F0
447             ['MIRP[10]'], ['MIRP[11]'], ['MIRP[12]'], ['MIRP[13]'], ['MIRP[14]'], ['MIRP[15]'], ['MIRP[16]'], ['MIRP[17]'],
448             ['MIRP[18]'], ['MIRP[19]'], ['MIRP[1A]'], ['MIRP[1B]'], ['MIRP[1C]'], ['MIRP[1D]'], ['MIRP[1E]'], ['MIRP[1F]']);
449              
450             my ($i);
451             my (%hints) = map { $_->[0] => $i++ if (defined $_->[0]); } @hints;
452              
453             sub XML_binhint
454             {
455 0     0 0   my ($dat) = @_;
456 0           my ($len) = length($dat);
457 0           my ($res, $i, $text, $size, $num);
458              
459 0           for ($i = 0; $i < $len; $i++)
460             {
461 0           ($text, $num, $size) = @{$hints[ord(substr($dat, $i, 1))]};
  0            
462 0 0         $num = 0 unless (defined $num);
463 0 0         $text = sprintf("UNK[%02X]", ord(substr($dat, $i, 1))) unless defined $text;
464 0           $res .= $text;
465 0 0         if ($num != 0)
466             {
467 0 0         if ($num < 0)
468             {
469 0           $i++;
470 0 0         my ($nnum) = unpack($num == -1 ? 'C' : 'n', substr($dat, $i, -$num));
471 0           $i += -$num - 1;
472 0           $num = $nnum;
473             }
474 0 0         $res .= "\t" . join(' ', unpack($size == 1 ? 'C*' : 'n*', substr($dat, $i + 1, $num * $size)));
475 0           $i += $num * $size;
476             }
477 0           $res .= "\n";
478             }
479 0           $res;
480             }
481              
482             sub XML_hintbin
483             {
484 0     0 0   my ($dat) = @_;
485 0           my ($l, $res, @words, $num);
486              
487 0           foreach $l (split(/\s*\n\s*/, $dat))
488             {
489 0           @words = split(/\s*/, $l);
490 0 0         next unless (defined $hints{$words[0]});
491 0           $num = $hints{$words[0]};
492 0           $res .= pack('C', $num);
493 0 0         if ($hints[$num][1] < 0)
    0          
494             {
495 0 0         $res .= pack($hints[$num][1] == -1 ? 'C' : 'n', $#words);
496 0 0         $res .= pack($hints[$num][2] == 1 ? 'C*' : 'n*', @words[1 .. $#words]);
497             }
498             elsif ($hints[$num][1] > 0)
499             {
500 0 0         $res .= pack($hints[$num][2] == 1 ? 'C*' : 'n*', @words[1 .. $hints[$num][1]]);
501             }
502             }
503 0           $res;
504             }
505             }
506              
507              
508             =head2 make_circle($f, $cmap, [$dia, $sb, $opts])
509              
510             Adds a dotted circle to a font. This function is very configurable. The
511             parameters passed in are:
512              
513             =over 4
514              
515             =item $f
516              
517             Font to work with. This is required.
518              
519             =item $cmap
520              
521             A cmap table (not the 'val' sub-element of a cmap) to add the glyph too. Optional.
522              
523             =item $dia
524              
525             Optional diameter for the main circle. Defaults to 80% em
526              
527             =item $sb
528              
529             Side bearing. The left and right side-bearings are always the same. This value
530             defaults to 10% em.
531              
532             =back
533              
534             There are various options to control all sorts of interesting aspects of the circle
535              
536             =over 4
537              
538             =item numDots
539              
540             Number of dots in the circle
541              
542             =item numPoints
543              
544             Number of curve points to use to create each dot
545              
546             =item uid
547              
548             Unicode reference to store this glyph under in the cmap. Defaults to 0x25CC
549              
550             =item pname
551              
552             Postscript name to give the glyph. Defaults to uni25CC.
553              
554             =item -dRadius
555              
556             Radius of each dot.
557              
558             =back
559              
560             =cut
561              
562             sub make_circle
563             {
564 0     0 1   my ($font, $cmap, $dia, $sb, %opts) = @_;
565 0           my ($upem) = $font->{'head'}{'unitsPerEm'};
566 0           my ($glyph) = Font::TTF::Glyph->new('PARENT' => $font, 'read' => 2, 'isDirty' => 1);
567 0           my ($PI) = 3.1415926535;
568 0           my ($R, $r, $xorg, $yorg);
569 0           my ($i, $j, $numg, $maxp);
570 0   0       my ($numc) = $opts{'-numDots'} || 16;
571 0   0       my ($nump) = ($opts{'-numPoints'} * 2) || 8;
572 0   0       my ($uid) = $opts{'-uid'} || 0x25CC;
573 0   0       my ($pname) = $opts{'-pname'} || 'uni25CC';
574              
575 0   0       $dia ||= $upem * .8; # .95 to fit exactly
576 0   0       $sb ||= $upem * .1;
577 0           $R = $dia / 2;
578 0   0       $r = $opts{'-dRadius'} || ($R * .1);
579 0           ($xorg, $yorg) = ($R + $r, $R);
580              
581 0           $xorg += $sb;
582 0           $font->{'post'}->read;
583 0           $font->{'glyf'}->read;
584 0           for ($i = 0; $i < $numc; $i++)
585             {
586 0           my ($pxorg, $pyorg) = ($xorg + $R * cos(2 * $PI * $i / $numc),
587             $yorg + $R * sin(2 * $PI * $i / $numc));
588 0           for ($j = 0; $j < $nump; $j++)
589             {
590 0 0         push (@{$glyph->{'x'}}, int ($pxorg + ($j & 1 ? 1/cos(2*$PI/$nump) : 1) * $r * cos(2 * $PI * $j / $nump)));
  0            
591 0 0         push (@{$glyph->{'y'}}, int ($pyorg + ($j & 1 ? 1/cos(2*$PI/$nump) : 1) * $r * sin(2 * $PI * $j / $nump)));
  0            
592 0 0         push (@{$glyph->{'flags'}}, $j & 1 ? 0 : 1);
  0            
593             }
594 0           push (@{$glyph->{'endPoints'}}, $#{$glyph->{'x'}});
  0            
  0            
595             }
596 0           $glyph->{'numberOfContours'} = $#{$glyph->{'endPoints'}} + 1;
  0            
597 0           $glyph->{'numPoints'} = $#{$glyph->{'x'}} + 1;
  0            
598 0           $glyph->update;
599 0           $numg = $font->{'maxp'}{'numGlyphs'};
600 0           $font->{'maxp'}{'numGlyphs'}++;
601              
602 0           $font->{'hmtx'}{'advance'}[$numg] = int($xorg + $R + $r + $sb + .5);
603 0           $font->{'hmtx'}{'lsb'}[$numg] = int($xorg - $R - $r + .5);
604 0           $font->{'loca'}{'glyphs'}[$numg] = $glyph;
605 0 0         $cmap->{'val'}{$uid} = $numg if ($cmap);
606 0           $font->{'post'}{'VAL'}[$numg] = $pname;
607 0           delete $font->{'hdmx'};
608 0           delete $font->{'VDMX'};
609 0           delete $font->{'LTSH'};
610            
611 0     0     $font->tables_do(sub {$_[0]->dirty;});
  0            
612 0           $font->update;
613 0           return ($numg - 1);
614             }
615              
616              
617             1;
618              
619             =head1 BUGS
620              
621             No known bugs
622              
623             =head1 AUTHOR
624              
625             Martin Hosken L.
626              
627              
628             =head1 LICENSING
629              
630             Copyright (c) 1998-2016, SIL International (http://www.sil.org)
631              
632             This module is released under the terms of the Artistic License 2.0.
633             For details, see the full text of the license in the file LICENSE.
634              
635              
636              
637             =cut
638              
639