File Coverage

blib/lib/Font/TTF/Utils.pm
Criterion Covered Total %
statement 116 240 48.3
branch 61 132 46.2
condition 30 60 50.0
subroutine 10 15 66.6
pod 10 12 83.3
total 227 459 49.4


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