1 package Font::TTF::AATutils;
4 use vars qw(@ISA @EXPORT);
26 my ($var, $links, $depth, $processedVars, $type) = @_;
28 $processedVars = {} unless (defined $processedVars);
29 print("<?xml version='1.0' encoding='UTF-8'?>\n") if $depth == 0; # not necessarily true encoding for all text!
31 my $indent = "\t" x $depth;
33 my ($objType, $addr) = ($var =~ m/^.+=(.+)\((.+)\)$/);
34 unless (defined $type) {
36 if (defined $processedVars->{$addr}) {
38 printf("%s%s\n", $indent, "<a href=\"#$addr\">$objType</a>");
41 printf("%s%s\n", $indent, "<a>$objType</a>");
45 $processedVars->{$addr} = 1;
49 $type = ref $var unless defined $type;
52 printf("%s<ref val=\"%s\"/>\n", $indent, $$var);
54 elsif ($type eq 'SCALAR') {
55 printf("%s<scalar>%s</scalar>\n", $indent, $var);
57 elsif ($type eq 'ARRAY') {
58 # printf("%s<array>\n", $indent);
59 foreach (0 .. $#$var) {
60 if (ref($var->[$_])) {
61 printf("%s<arrayItem index=\"%d\">\n", $indent, $_);
62 xmldump($var->[$_], $links, $depth + 1, $processedVars);
63 printf("%s</arrayItem>\n", $indent);
66 printf("%s<arrayItem index=\"%d\">%s</arrayItem>\n", $indent, $_, $var->[$_]);
69 # printf("%s</array>\n", $indent);
71 elsif ($type eq 'HASH') {
72 # printf("%s<hash>\n", $indent);
73 foreach (sort keys %$var) {
74 if (ref($var->{$_})) {
75 printf("%s<hashElem key=\"%s\">\n", $indent, $_);
76 xmldump($var->{$_}, $links, $depth + 1, $processedVars);
77 printf("%s</hashElem>\n", $indent);
80 printf("%s<hashElem key=\"%s\">%s</hashElem>\n", $indent, $_, $var->{$_});
83 # printf("%s</hash>\n", $indent);
85 elsif ($type eq 'CODE') {
86 printf("%s<CODE/>\n", $indent, $var);
88 elsif ($type eq 'GLOB') {
89 printf("%s<GLOB/>\n", $indent, $var);
92 printf("%s<val>%s</val>\n", $indent, $var);
96 printf("%s<obj class=\"%s\" id=\"#%s\">\n", $indent, $type, $addr);
99 printf("%s<obj class=\"%s\">\n", $indent, $type);
101 xmldump($var, $links, $depth + 1, $processedVars, $objType);
102 printf("%s</obj>\n", $indent);
106 =head2 ($classes, $states) = AAT_read_subtable($fh, $baseOffset, $subtableStart, $limits)
110 sub AAT_read_subtable
112 my ($fh, $baseOffset, $subtableStart, $limits) = @_;
114 my $limit = 0xffffffff;
116 $limit = $_ if ($_ > $subtableStart and $_ < $limit);
118 die if $limit == 0xffffffff;
121 $fh->seek($baseOffset + $subtableStart, IO::File::SEEK_SET);
122 $fh->read($dat, $limit - $subtableStart);
127 =head2 $length = AAT_write_state_table($fh, $classes, $states, $numExtraTables, $packEntry)
129 $packEntry is a subroutine for packing an entry into binary form, called as
131 $dat = $packEntry($entry, $entryTable, $numEntries)
133 where the entry is a comma-separated list of nextStateOffset, flags, actions
137 sub AAT_pack_state_table
139 my ($classes, $states, $numExtraTables, $packEntry) = @_;
141 my ($dat) = pack("n*", (0) x (4 + $numExtraTables)); # placeholders for stateSize, classTable, stateArray, entryTable
143 my ($firstGlyph, $lastGlyph) = (0xffff, 0, 0);
144 my (@classTable, $i);
145 foreach $i (0 .. $#$classes) {
146 my $class = $classes->[$i];
148 $firstGlyph = $_ if $_ < $firstGlyph;
149 $lastGlyph = $_ if $_ > $lastGlyph;
150 $classTable[$_] = $i;
154 my $classTable = length($dat);
155 $dat .= pack("nnC*", $firstGlyph, $lastGlyph - $firstGlyph + 1,
156 map { defined $classTable[$_] ? $classTable[$_] : 1 } ($firstGlyph .. $lastGlyph));
157 $dat .= pack("C", 0) if (($lastGlyph - $firstGlyph) & 1) == 0; # pad if odd number of glyphs
159 my $stateArray = length($dat);
160 my (@entries, %entries);
161 my $state = $states->[0];
162 my $stateSize = @$state;
163 die "stateSize below minimum allowed (4)" if $stateSize < 4;
164 die "stateSize (" . $stateSize . ") too small for max class number (" . $#$classes . ")" if $stateSize < $#$classes + 1;
165 warn "state array has unreachable columns" if $stateSize > $#$classes + 1;
168 die "inconsistent state size" if @$_ != $stateSize;
170 my $actions = $_->{'actions'};
171 my $entry = join(",", $stateArray + $_->{'nextState'} * $stateSize, $_->{'flags'}, ref($actions) eq 'ARRAY' ? @$actions : $actions);
172 if (not defined $entries{$entry}) {
173 push @entries, $entry;
174 $entries{$entry} = $#entries;
175 die "too many different state array entries" if $#entries == 256;
177 $dat .= pack("C", $entries{$entry});
180 $dat .= pack("C", 0) if (@$states & 1) != 0 and ($stateSize & 1) != 0; # pad if state array size is odd
182 my $entryTable = length($dat);
183 $dat .= map { &$packEntry($_, $entryTable, $#entries + 1) } @entries;
185 my ($dat1) = pack("nnnn", $stateSize, $classTable, $stateArray, $entryTable);
186 substr($dat, 0, length($dat1)) = $dat1;
191 sub AAT_write_state_table
193 my ($fh, $classes, $states, $numExtraTables, $packEntry) = @_;
195 my $stateTableStart = $fh->tell();
197 $fh->print(pack("n*", (0) x (4 + $numExtraTables))); # placeholders for stateSize, classTable, stateArray, entryTable
199 my ($firstGlyph, $lastGlyph) = (0xffff, 0, 0);
200 my (@classTable, $i);
201 foreach $i (0 .. $#$classes) {
202 my $class = $classes->[$i];
204 $firstGlyph = $_ if $_ < $firstGlyph;
205 $lastGlyph = $_ if $_ > $lastGlyph;
206 $classTable[$_] = $i;
210 my $classTable = $fh->tell() - $stateTableStart;
211 $fh->print(pack("nnC*", $firstGlyph, $lastGlyph - $firstGlyph + 1,
212 map { defined $classTable[$_] ? $classTable[$_] : 1 } ($firstGlyph .. $lastGlyph)));
213 $fh->print(pack("C", 0)) if (($lastGlyph - $firstGlyph) & 1) == 0; # pad if odd number of glyphs
215 my $stateArray = $fh->tell() - $stateTableStart;
216 my (@entries, %entries);
217 my $state = $states->[0];
218 my $stateSize = @$state;
219 die "stateSize below minimum allowed (4)" if $stateSize < 4;
220 die "stateSize (" . $stateSize . ") too small for max class number (" . $#$classes . ")" if $stateSize < $#$classes + 1;
221 warn "state array has unreachable columns" if $stateSize > $#$classes + 1;
224 die "inconsistent state size" if @$_ != $stateSize;
226 my $actions = $_->{'actions'};
227 my $entry = join(",", $stateArray + $_->{'nextState'} * $stateSize, $_->{'flags'}, ref($actions) eq 'ARRAY' ? @$actions : $actions);
228 if (not defined $entries{$entry}) {
229 push @entries, $entry;
230 $entries{$entry} = $#entries;
231 die "too many different state array entries" if $#entries == 256;
233 $fh->print(pack("C", $entries{$entry}));
236 $fh->print(pack("C", 0)) if (@$states & 1) != 0 and ($stateSize & 1) != 0; # pad if state array size is odd
238 my $entryTable = $fh->tell() - $stateTableStart;
239 $fh->print(map { &$packEntry($_, $entryTable, $#entries + 1) } @entries);
241 my $length = $fh->tell() - $stateTableStart;
242 $fh->seek($stateTableStart, IO::File::SEEK_SET);
243 $fh->print(pack("nnnn", $stateSize, $classTable, $stateArray, $entryTable));
245 $fh->seek($stateTableStart + $length, IO::File::SEEK_SET);
253 my ($firstGlyph, $lastGlyph) = (0xffff, 0, 0);
254 my (@classTable, $i);
255 foreach $i (0 .. $#$classes) {
256 my $class = $classes->[$i];
258 $firstGlyph = $_ if $_ < $firstGlyph;
259 $lastGlyph = $_ if $_ > $lastGlyph;
260 $classTable[$_] = $i;
264 my ($dat) = pack("nnC*", $firstGlyph, $lastGlyph - $firstGlyph + 1,
265 map { defined $classTable[$_] ? $classTable[$_] : 1 } ($firstGlyph .. $lastGlyph));
266 $dat .= pack("C", 0) if (($lastGlyph - $firstGlyph) & 1) == 0; # pad if odd number of glyphs
271 sub AAT_write_classes
273 my ($fh, $classes) = @_;
275 $fh->print(AAT_pack_classes($fh, $classes));
280 my ($classes, $stateArray, $states, $buildEntryProc) = @_;
282 my ($entries, %entryHash);
283 my $state = $states->[0];
284 my $stateSize = @$state;
286 die "stateSize below minimum allowed (4)" if $stateSize < 4;
287 die "stateSize (" . $stateSize . ") too small for max class number (" . $#$classes . ")" if $stateSize < $#$classes + 1;
288 warn "state array has unreachable columns" if $stateSize > $#$classes + 1;
292 die "inconsistent state size" if @$_ != $stateSize;
294 my $entry = join(",", $stateArray + $_->{'nextState'} * $stateSize, &$buildEntryProc($_));
295 if (not defined $entryHash{$entry}) {
296 push @$entries, $entry;
297 $entryHash{$entry} = $#$entries;
298 die "too many different state array entries" if $#$entries == 256;
300 $dat .= pack("C", $entryHash{$entry});
303 $dat .= pack("C", 0) if (@$states & 1) != 0 and ($stateSize & 1) != 0; # pad if state array size is odd
305 ($dat, $stateSize, $entries);
310 my ($fh, $classes, $stateArray, $states, $buildEntryProc) = @_;
312 my ($entries, %entryHash);
313 my $state = $states->[0];
314 my $stateSize = @$state;
316 die "stateSize below minimum allowed (4)" if $stateSize < 4;
317 die "stateSize (" . $stateSize . ") too small for max class number (" . $#$classes . ")" if $stateSize < $#$classes + 1;
318 warn "state array has unreachable columns" if $stateSize > $#$classes + 1;
321 die "inconsistent state size" if @$_ != $stateSize;
323 my $entry = join(",", $stateArray + $_->{'nextState'} * $stateSize, &$buildEntryProc($_));
324 if (not defined $entryHash{$entry}) {
325 push @$entries, $entry;
326 $entryHash{$entry} = $#$entries;
327 die "too many different state array entries" if $#$entries == 256;
329 $fh->print(pack("C", $entryHash{$entry}));
332 $fh->print(pack("C", 0)) if (@$states & 1) != 0 and ($stateSize & 1) != 0; # pad if state array size is odd
334 ($stateSize, $entries);
337 =head2 ($classes, $states, $entries) = AAT_read_state_table($fh, $numActionWords)
341 sub AAT_read_state_table
343 my ($fh, $numActionWords) = @_;
345 my $stateTableStart = $fh->tell();
348 my ($stateSize, $classTable, $stateArray, $entryTable) = unpack("nnnn", $dat);
350 my $classes; # array of lists of glyphs
352 $fh->seek($stateTableStart + $classTable, IO::File::SEEK_SET);
354 my ($firstGlyph, $nGlyphs) = unpack("nn", $dat);
355 $fh->read($dat, $nGlyphs);
356 foreach (unpack("C*", $dat)) {
358 my $class = $classes->[$_];
359 push(@$class, $firstGlyph);
360 $classes->[$_] = $class unless defined $classes->[$_];
365 $fh->seek($stateTableStart + $stateArray, IO::File::SEEK_SET);
366 my $states; # array of arrays of hashes{nextState, flags, actions}
368 my $entrySize = 4 + ($numActionWords * 2);
371 while ($#$states < $lastState) {
372 $fh->read($dat, $stateSize);
373 my @stateEntries = unpack("C*", $dat);
375 foreach (@stateEntries) {
376 if (not defined $entries->[$_]) {
377 my $loc = $fh->tell();
378 $fh->seek($stateTableStart + $entryTable + ($_ * $entrySize), IO::File::SEEK_SET);
379 $fh->read($dat, $entrySize);
380 my ($nextState, $flags, $actions);
381 ($nextState, $flags, @$actions) = unpack("n*", $dat);
382 $nextState -= $stateArray;
383 $nextState /= $stateSize;
384 $entries->[$_] = { 'nextState' => $nextState, 'flags' => $flags };
385 $entries->[$_]->{'actions'} = $actions if $numActionWords > 0;
386 $lastState = $nextState if ($nextState > $lastState);
387 $fh->seek($loc, IO::File::SEEK_SET);
389 push(@$state, $entries->[$_]);
391 push(@$states, $state);
394 ($classes, $states, $entries);
397 =head2 ($format, $lookup) = AAT_read_lookup($fh, $valueSize, $length, $default)
403 my ($fh, $valueSize, $length, $default) = @_;
405 my $lookupStart = $fh->tell();
406 my ($dat, $unpackChar);
407 if ($valueSize == 1) {
410 elsif ($valueSize == 2) {
413 elsif ($valueSize == 4) {
417 die "unsupported value size";
421 my $format = unpack("n", $dat);
425 $fh->read($dat, $length - 2);
427 $lookup = { map { $i++; ($_ != $default) ? ($i, $_) : () } unpack($unpackChar . "*", $dat) };
430 elsif ($format == 2) {
432 my ($unitSize, $nUnits, $searchRange, $entrySelector, $rangeShift) = unpack("nnnnn", $dat);
433 die if $unitSize != 4 + $valueSize;
434 foreach (1 .. $nUnits) {
435 $fh->read($dat, $unitSize);
436 my ($lastGlyph, $firstGlyph, $value) = unpack("nn" . $unpackChar, $dat);
437 if ($firstGlyph != 0xffff and $value != $default) {
438 foreach ($firstGlyph .. $lastGlyph) {
439 $lookup->{$_} = $value;
445 elsif ($format == 4) {
447 my ($unitSize, $nUnits, $searchRange, $entrySelector, $rangeShift) = unpack("nnnnn", $dat);
448 die if $unitSize != 6;
449 foreach (1 .. $nUnits) {
450 $fh->read($dat, $unitSize);
451 my ($lastGlyph, $firstGlyph, $offset) = unpack("nnn", $dat);
452 if ($firstGlyph != 0xffff) {
453 my $loc = $fh->tell();
454 $fh->seek($lookupStart + $offset, IO::File::SEEK_SET);
455 $fh->read($dat, ($lastGlyph - $firstGlyph + 1) * $valueSize);
456 my @values = unpack($unpackChar . "*", $dat);
457 foreach (0 .. $lastGlyph - $firstGlyph) {
458 $lookup->{$firstGlyph + $_} = $values[$_] if $values[$_] != $default;
460 $fh->seek($loc, IO::File::SEEK_SET);
465 elsif ($format == 6) {
467 my ($unitSize, $nUnits, $searchRange, $entrySelector, $rangeShift) = unpack("nnnnn", $dat);
468 die if $unitSize != 2 + $valueSize;
469 foreach (1 .. $nUnits) {
470 $fh->read($dat, $unitSize);
471 my ($glyph, $value) = unpack("n" . $unpackChar, $dat);
472 $lookup->{$glyph} = $value if $glyph != 0xffff and $value != $default;
476 elsif ($format == 8) {
478 my ($firstGlyph, $glyphCount) = unpack("nn", $dat);
479 $fh->read($dat, $glyphCount * $valueSize);
481 $lookup = { map { $firstGlyph++; $_ != $default ? ($firstGlyph, $_) : () } unpack($unpackChar . "*", $dat) };
485 die "unknown lookup format";
488 $fh->seek($lookupStart + $length, IO::File::SEEK_SET);
493 =head2 AAT_write_lookup($fh, $format, $lookup, $valueSize, $default)
499 my ($format, $lookup, $valueSize, $default) = @_;
502 if ($valueSize == 1) {
505 elsif ($valueSize == 2) {
508 elsif ($valueSize == 4) {
512 die "unsupported value size";
515 my ($dat) = pack("n", $format);
517 my ($firstGlyph, $lastGlyph) = (0xffff, 0);
518 foreach (keys %$lookup) {
519 $firstGlyph = $_ if $_ < $firstGlyph;
520 $lastGlyph = $_ if $_ > $lastGlyph;
522 my $glyphCount = $lastGlyph - $firstGlyph + 1;
525 $dat .= pack($packChar . "*", map { defined $lookup->{$_} ? $lookup->{$_} : defined $default ? $default : $_ } (0 .. $lastGlyph));
528 elsif ($format == 2) {
530 my $segStart = $firstGlyph;
532 foreach ($firstGlyph .. $lastGlyph + 1) {
533 my $val = $lookup->{$_};
534 $val = $default unless defined $val;
536 $dat1 .= pack("nn" . $packChar, $_ - 1, $segStart, $prev) if $prev != $default;
541 $dat1 .= pack("nn" . $packChar, 0xffff, 0xffff, 0);
542 my $unitSize = 4 + $valueSize;
543 $dat .= pack("nnnnn", $unitSize, TTF_bininfo(length($dat1) / $unitSize, $unitSize));
547 elsif ($format == 4) {
548 my $segArray = new Font::TTF::Segarr($valueSize);
549 $segArray->add_segment($firstGlyph, 1, map { $lookup->{$_} } ($firstGlyph .. $lastGlyph));
550 my ($start, $end, $offset);
551 $offset = 12 + @$segArray * 6 + 6; # 12 is size of format word + binSearchHeader; 6 bytes per segment; 6 for terminating segment
553 foreach (@$segArray) {
554 $start = $_->{'START'};
555 $end = $start + $_->{'LEN'} - 1;
556 $dat1 .= pack("nnn", $end, $start, $offset);
557 $offset += $_->{'LEN'} * 2;
559 $dat1 .= pack("nnn", 0xffff, 0xffff, 0);
560 $dat .= pack("nnnnn", 6, TTF_bininfo(length($dat1) / 6, 6));
562 foreach (@$segArray) {
564 $dat .= pack($packChar . "*", @$dat1);
568 elsif ($format == 6) {
569 die "unsupported" if $valueSize != 2;
570 my $dat1 = pack("n*", map { $_, $lookup->{$_} } sort { $a <=> $b } grep { $lookup->{$_} ne $default } keys %$lookup);
571 my $unitSize = 2 + $valueSize;
572 $dat .= pack("nnnnn", $unitSize, TTF_bininfo(length($dat1) / $unitSize, $unitSize));
576 elsif ($format == 8) {
577 $dat .= pack("nn", $firstGlyph, $lastGlyph - $firstGlyph + 1);
578 $dat .= pack($packChar . "*", map { defined $lookup->{$_} ? $lookup->{$_} : defined $default ? $default : $_ } ($firstGlyph .. $lastGlyph));
582 die "unknown lookup format";
585 my $padBytes = (4 - (length($dat) & 3)) & 3;
586 $dat .= pack("C*", (0) x $padBytes);
593 my ($fh, $format, $lookup, $valueSize, $default) = @_;
595 my $lookupStart = $fh->tell();
597 if ($valueSize == 1) {
600 elsif ($valueSize == 2) {
603 elsif ($valueSize == 4) {
607 die "unsupported value size";
610 $fh->print(pack("n", $format));
612 my ($firstGlyph, $lastGlyph) = (0xffff, 0);
613 foreach (keys %$lookup) {
614 $firstGlyph = $_ if $_ < $firstGlyph;
615 $lastGlyph = $_ if $_ > $lastGlyph;
617 my $glyphCount = $lastGlyph - $firstGlyph + 1;
620 $fh->print(pack($packChar . "*", map { defined $lookup->{$_} ? $lookup->{$_} : defined $default ? $default : $_ } (0 .. $lastGlyph)));
623 elsif ($format == 2) {
625 my $segStart = $firstGlyph;
627 foreach ($firstGlyph .. $lastGlyph + 1) {
628 my $val = $lookup->{$_};
629 $val = $default unless defined $val;
631 $dat .= pack("nn" . $packChar, $_ - 1, $segStart, $prev) if $prev != $default;
636 $dat .= pack("nn" . $packChar, 0xffff, 0xffff, 0);
637 my $unitSize = 4 + $valueSize;
638 $fh->print(pack("nnnnn", $unitSize, TTF_bininfo(length($dat) / $unitSize, $unitSize)));
642 elsif ($format == 4) {
643 my $segArray = new Font::TTF::Segarr($valueSize);
644 $segArray->add_segment($firstGlyph, 1, map { $lookup->{$_} } ($firstGlyph .. $lastGlyph));
645 my ($start, $end, $offset);
646 $offset = 12 + @$segArray * 6 + 6; # 12 is size of format word + binSearchHeader; 6 bytes per segment; 6 for terminating segment
648 foreach (@$segArray) {
649 $start = $_->{'START'};
650 $end = $start + $_->{'LEN'} - 1;
651 $dat .= pack("nnn", $end, $start, $offset);
652 $offset += $_->{'LEN'} * 2;
654 $dat .= pack("nnn", 0xffff, 0xffff, 0);
655 $fh->print(pack("nnnnn", 6, TTF_bininfo(length($dat) / 6, 6)));
657 foreach (@$segArray) {
659 $fh->print(pack($packChar . "*", @$dat));
663 elsif ($format == 6) {
664 die "unsupported" if $valueSize != 2;
665 my $dat = pack("n*", map { $_, $lookup->{$_} } sort { $a <=> $b } grep { $lookup->{$_} ne $default } keys %$lookup);
666 my $unitSize = 2 + $valueSize;
667 $fh->print(pack("nnnnn", $unitSize, TTF_bininfo(length($dat) / $unitSize, $unitSize)));
671 elsif ($format == 8) {
672 $fh->print(pack("nn", $firstGlyph, $lastGlyph - $firstGlyph + 1));
673 $fh->print(pack($packChar . "*", map { defined $lookup->{$_} ? $lookup->{$_} : defined $default ? $default : $_ } ($firstGlyph .. $lastGlyph)));
677 die "unknown lookup format";
680 my $length = $fh->tell() - $lookupStart;
681 my $padBytes = (4 - ($length & 3)) & 3;
682 $fh->print(pack("C*", (0) x $padBytes));
683 $length += $padBytes;