1 package Font::TTF::OldMort;
5 Font::TTF::OldMort - Glyph Metamorphosis table in a font
9 =head1 INSTANCE VARIABLES
13 table version number (Fixed: currently 1.0)
17 list of metamorphosis chains, each of which has its own fields:
23 chain's default subfeature flags (UInt32)
27 list of feature entries, each of which has fields:
43 list of metamorphosis subtables, each of which has fields:
49 subtable type (0: rearrangement; 1: contextual substitution; 2: ligature;
50 4: non-contextual substitution; 5: insertion)
54 processing direction ('LR' or 'RL')
58 applies to text in which orientation ('VH', 'V', or 'H')
62 the subfeature flags controlling whether the table is used (UInt32)
66 Further fields depend on the type of subtable:
76 array of lists of glyphs
80 array of arrays of hashes{'nextState', 'flags'}
84 Contextual substitution table:
90 array of lists of glyphs
94 array of array of hashes{'nextState', 'flags', 'actions'}, where C<actions>
95 is an array of two elements which are offsets to be added to [marked, current]
96 glyph to get index into C<mappings> (or C<undef> if no mapping to be applied)
100 list of glyph codes mapped to through the state table mappings
106 Non-contextual substitution table:
120 use Font::TTF::Utils;
121 use Font::TTF::AATutils;
124 @ISA = qw(Font::TTF::Table);
128 Reads the table into memory
135 my ($dat, $fh, $numChains);
137 $self->SUPER::read or return $self;
139 $fh = $self->{' INFILE'};
142 ($self->{'version'}, $numChains) = TTF_Unpack("fL", $dat);
145 foreach (1 .. $numChains) {
146 my $chainStart = $fh->tell();
148 my ($defaultFlags, $chainLength, $nFeatureEntries, $nSubtables) = TTF_Unpack("LLSS", $dat);
149 my $featureEntries = [];
150 foreach (1 .. $nFeatureEntries) {
152 my ($featureType, $featureSetting, $enableFlags, $disableFlags) = TTF_Unpack("SSLL", $dat);
153 push @$featureEntries, {
154 'type' => $featureType,
155 'setting' => $featureSetting,
156 'enable' => $enableFlags,
157 'disable' => $disableFlags
161 foreach (1 .. $nSubtables) {
162 my $subtableStart = $fh->tell();
164 my ($length, $coverage, $subFeatureFlags) = TTF_Unpack("SSL", $dat);
165 my $type = $coverage & 0x0007;
169 'direction' => (($coverage & 0x4000) ? 'RL' : 'LR'),
170 'orientation' => (($coverage & 0x2000) ? 'VH' : ($coverage & 0x8000) ? 'V' : 'H'),
171 'subFeatureFlags' => $subFeatureFlags
174 if ($type == 0) { # rearrangement
175 my ($classes, $states) = AAT_read_state_table($fh, 0);
176 $subtable->{'classes'} = $classes;
177 $subtable->{'states'} = $states;
180 elsif ($type == 1) { # contextual
181 my $stateTableStart = $fh->tell();
182 my ($classes, $states, $entries) = AAT_read_state_table($fh, 2);
184 $fh->seek($stateTableStart, IO::File::SEEK_SET);
186 my ($stateSize, $classTable, $stateArray, $entryTable, $mappingTables) = unpack("nnnnn", $dat);
187 my $limits = [$classTable, $stateArray, $entryTable, $mappingTables, $length - 8];
189 foreach (@$entries) {
190 my $actions = $_->{'actions'};
191 foreach (@$actions) {
192 $_ = $_ ? $_ - ($mappingTables / 2) : undef;
196 $subtable->{'classes'} = $classes;
197 $subtable->{'states'} = $states;
198 $subtable->{'mappings'} = [unpack("n*", AAT_read_subtable($fh, $stateTableStart, $mappingTables, $limits))];
201 elsif ($type == 2) { # ligature
202 my $stateTableStart = $fh->tell();
203 my ($classes, $states, $entries) = AAT_read_state_table($fh, 0);
205 $fh->seek($stateTableStart, IO::File::SEEK_SET);
207 my ($stateSize, $classTable, $stateArray, $entryTable,
208 $ligActionTable, $componentTable, $ligatureTable) = unpack("nnnnnnn", $dat);
209 my $limits = [$classTable, $stateArray, $entryTable, $ligActionTable, $componentTable, $ligatureTable, $length - 8];
213 foreach (@$entries) {
214 my $offset = $_->{'flags'} & 0x3fff;
215 $_->{'flags'} &= ~0x3fff;
217 if (not defined $actions{$offset}) {
218 $fh->seek($stateTableStart + $offset, IO::File::SEEK_SET);
222 my $action = unpack("N", $dat);
223 my ($last, $store, $component) = (($action & 0x80000000) != 0, ($action & 0xC0000000) != 0, ($action & 0x3fffffff));
224 $component -= 0x40000000 if $component > 0x1fffffff;
225 $component -= $componentTable / 2;
226 push @$actionList, { 'store' => $store, 'component' => $component };
229 push @$actionLists, $actionList;
230 $actions{$offset} = $#$actionLists;
232 $_->{'actions'} = $actions{$offset};
236 $subtable->{'componentTable'} = $componentTable;
237 my $components = [unpack("n*", AAT_read_subtable($fh, $stateTableStart, $componentTable, $limits))];
238 foreach (@$components) {
239 $_ = ($_ - $ligatureTable) . " +" if $_ >= $ligatureTable;
241 $subtable->{'components'} = $components;
243 $subtable->{'ligatureTable'} = $ligatureTable;
244 $subtable->{'ligatures'} = [unpack("n*", AAT_read_subtable($fh, $stateTableStart, $ligatureTable, $limits))];
246 $subtable->{'classes'} = $classes;
247 $subtable->{'states'} = $states;
248 $subtable->{'actionLists'} = $actionLists;
251 elsif ($type == 4) { # non-contextual
252 my ($format, $lookup) = AAT_read_lookup($fh, 2, $length - 8, undef);
253 $subtable->{'format'} = $format;
254 $subtable->{'lookup'} = $lookup;
257 elsif ($type == 5) { # insertion
258 my $stateTableStart = $fh->tell();
259 my ($classes, $states, $entries) = AAT_read_state_table($fh, 2);
263 foreach (@$entries) {
264 my $flags = $_->{'flags'};
265 my @insertCount = (($flags & 0x03e0) >> 5, ($flags & 0x001f));
266 my $actions = $_->{'actions'};
268 if ($insertCount[$_] > 0) {
269 $fh->seek($stateTableStart + $actions->[$_], IO::File::SEEK_SET);
270 $fh->read($dat, $insertCount[$_] * 2);
271 if (not defined $insertListHash{$dat}) {
272 push @$insertLists, [unpack("n*", $dat)];
273 $insertListHash{$dat} = $#$insertLists;
275 $actions->[$_] = $insertListHash{$dat};
278 $actions->[$_] = undef;
283 $subtable->{'classes'} = $classes;
284 $subtable->{'states'} = $states;
285 $subtable->{'insertLists'} = $insertLists;
289 die "unknown subtable type";
292 push @$subtables, $subtable;
293 $fh->seek($subtableStart + $length, IO::File::SEEK_SET);
297 'defaultFlags' => $defaultFlags,
298 'featureEntries' => $featureEntries,
299 'subtables' => $subtables
301 $fh->seek($chainStart + $chainLength, IO::File::SEEK_SET);
304 $self->{'chains'} = $chains;
311 Writes the table to a file either from memory or by copying
317 my ($self, $fh) = @_;
319 return $self->SUPER::out($fh) unless $self->{' read'};
321 my $chains = $self->{'chains'};
322 $fh->print(TTF_Pack("fL", $self->{'version'}, scalar @$chains));
325 my $chainStart = $fh->tell();
326 my ($featureEntries, $subtables) = ($_->{'featureEntries'}, $_->{'subtables'});
327 $fh->print(TTF_Pack("LLSS", $_->{'defaultFlags'}, 0, scalar @$featureEntries, scalar @$subtables)); # placeholder for length
329 foreach (@$featureEntries) {
330 $fh->print(TTF_Pack("SSLL", $_->{'type'}, $_->{'setting'}, $_->{'enable'}, $_->{'disable'}));
333 foreach (@$subtables) {
334 my $subtableStart = $fh->tell();
335 my $type = $_->{'type'};
336 my $coverage = $type;
337 $coverage += 0x4000 if $_->{'direction'} eq 'RL';
338 $coverage += 0x2000 if $_->{'orientation'} eq 'VH';
339 $coverage += 0x8000 if $_->{'orientation'} eq 'V';
341 $fh->print(TTF_Pack("SSL", 0, $coverage, $_->{'subFeatureFlags'})); # placeholder for length
343 if ($type == 0) { # rearrangement
344 AAT_write_state_table($fh, $_->{'classes'}, $_->{'states'}, 0);
347 elsif ($type == 1) { # contextual
348 my $stHeader = $fh->tell();
349 $fh->print(pack("nnnnn", (0) x 5)); # placeholders for stateSize, classTable, stateArray, entryTable, mappingTables
351 my $classTable = $fh->tell() - $stHeader;
352 my $classes = $_->{'classes'};
353 AAT_write_classes($fh, $classes);
355 my $stateArray = $fh->tell() - $stHeader;
356 my $states = $_->{'states'};
357 my ($stateSize, $entries) = AAT_write_states($fh, $classes, $stateArray, $states,
359 my $actions = $_->{'actions'};
360 ( $_->{'flags'}, @$actions )
364 my $entryTable = $fh->tell() - $stHeader;
365 my $offset = ($entryTable + 8 * @$entries) / 2;
366 foreach (@$entries) {
367 my ($nextState, $flags, @parts) = split /,/;
368 $fh->print(pack("nnnn", $nextState, $flags, map { $_ eq "" ? 0 : $_ + $offset } @parts));
371 my $mappingTables = $fh->tell() - $stHeader;
372 my $mappings = $_->{'mappings'};
373 $fh->print(pack("n*", @$mappings));
375 my $loc = $fh->tell();
376 $fh->seek($stHeader, IO::File::SEEK_SET);
377 $fh->print(pack("nnnnn", $stateSize, $classTable, $stateArray, $entryTable, $mappingTables));
378 $fh->seek($loc, IO::File::SEEK_SET);
381 elsif ($type == 2) { # ligature
382 my $stHeader = $fh->tell();
383 $fh->print(pack("nnnnnnn", (0) x 7)); # placeholders for stateSize, classTable, stateArray, entryTable, actionLists, components, ligatures
385 my $classTable = $fh->tell() - $stHeader;
386 my $classes = $_->{'classes'};
387 AAT_write_classes($fh, $classes);
389 my $stateArray = $fh->tell() - $stHeader;
390 my $states = $_->{'states'};
392 my ($stateSize, $entries) = AAT_write_states($fh, $classes, $stateArray, $states,
394 ( $_->{'flags'} & 0xc000, $_->{'actions'} )
398 my $actionLists = $_->{'actionLists'};
399 my %actionListOffset;
400 my $actionListDataLength = 0;
401 my @actionListEntries;
402 foreach (0 .. $#$entries) {
403 my ($nextState, $flags, $offset) = split(/,/, $entries->[$_]);
408 if (defined $actionListOffset{$offset}) {
409 $offset = $actionListOffset{$offset};
412 $actionListOffset{$offset} = $actionListDataLength;
413 my $list = $actionLists->[$offset];
414 $actionListDataLength += 4 * @$list;
415 push @actionListEntries, $list;
416 $offset = $actionListOffset{$offset};
419 $entries->[$_] = [ $nextState, $flags, $offset ];
421 my $entryTable = $fh->tell() - $stHeader;
422 my $ligActionLists = ($entryTable + @$entries * 4 + 3) & ~3;
423 foreach (@$entries) {
424 $_->[2] += $ligActionLists if defined $_->[2];
425 $fh->print(pack("nn", $_->[0], $_->[1] + $_->[2]));
427 $fh->print(pack("C*", (0) x ($ligActionLists - $entryTable - @$entries * 4)));
429 die "internal error" if $fh->tell() != $ligActionLists + $stHeader;
431 my $componentTable = $fh->tell() - $stHeader + $actionListDataLength;
433 foreach $actionList (@actionListEntries) {
434 foreach (0 .. $#$actionList) {
435 my $action = $actionList->[$_];
436 my $val = $action->{'component'} + $componentTable / 2;
437 $val += 0x40000000 if $val < 0;
439 $val |= 0x40000000 if $action->{'store'};
440 $val |= 0x80000000 if $_ == $#$actionList;
441 $fh->print(pack("N", $val));
445 die "internal error" if $fh->tell() != $componentTable + $stHeader;
447 my $components = $_->{'components'};
448 my $ligatureTable = $componentTable + @$components * 2;
449 $fh->print(pack("n*", map { (index($_, '+') >= 0 ? $ligatureTable : 0) + $_ } @$components));
451 my $ligatures = $_->{'ligatures'};
452 $fh->print(pack("n*", @$ligatures));
454 my $loc = $fh->tell();
455 $fh->seek($stHeader, IO::File::SEEK_SET);
456 $fh->print(pack("nnnnnnn", $stateSize, $classTable, $stateArray, $entryTable, $ligActionLists, $componentTable, $ligatureTable));
457 $fh->seek($loc, IO::File::SEEK_SET);
460 elsif ($type == 4) { # non-contextual
461 AAT_write_lookup($fh, $_->{'format'}, $_->{'lookup'}, 2, undef);
464 elsif ($type == 5) { # insertion
468 die "unknown subtable type";
471 my $length = $fh->tell() - $subtableStart;
472 my $padBytes = (4 - ($length & 3)) & 3;
473 $fh->print(pack("C*", (0) x $padBytes));
474 $length += $padBytes;
475 $fh->seek($subtableStart, IO::File::SEEK_SET);
476 $fh->print(pack("n", $length));
477 $fh->seek($subtableStart + $length, IO::File::SEEK_SET);
480 my $chainLength = $fh->tell() - $chainStart;
481 $fh->seek($chainStart + 4, IO::File::SEEK_SET);
482 $fh->print(pack("N", $chainLength));
483 $fh->seek($chainStart + $chainLength, IO::File::SEEK_SET);
487 =head2 $t->print($fh)
489 Prints a human-readable representation of the table
495 my ($self, $fh) = @_;
498 my $feat = $self->{' PARENT'}->{'feat'};
500 my $post = $self->{' PARENT'}->{'post'};
503 $fh = 'STDOUT' unless defined $fh;
505 $fh->printf("version %f\n", $self->{'version'});
507 my $chains = $self->{'chains'};
509 my $defaultFlags = $_->{'defaultFlags'};
510 $fh->printf("chain: defaultFlags = %08x\n", $defaultFlags);
512 my $featureEntries = $_->{'featureEntries'};
513 foreach (@$featureEntries) {
514 $fh->printf("\tfeature %d, setting %d : enableFlags = %08x, disableFlags = %08x # '%s: %s'\n",
515 $_->{'type'}, $_->{'setting'}, $_->{'enable'}, $_->{'disable'},
516 $feat->settingName($_->{'type'}, $_->{'setting'}));
519 my $subtables = $_->{'subtables'};
520 foreach (@$subtables) {
521 my $type = $_->{'type'};
522 my $subFeatureFlags = $_->{'subFeatureFlags'};
523 $fh->printf("\n\t%s table, %s, %s, subFeatureFlags = %08x # %s (%s)\n",
524 subtable_type_($type), $_->{'direction'}, $_->{'orientation'}, $subFeatureFlags,
525 "Default " . ((($subFeatureFlags & $defaultFlags) != 0) ? "On" : "Off"),
528 join(": ", $feat->settingName($_->{'type'}, $_->{'setting'}) )
529 } grep { ($_->{'enable'} & $subFeatureFlags) != 0 } @$featureEntries
532 if ($type == 0) { # rearrangement
533 print_classes_($fh, $_, $post);
536 my $states = $_->{'states'};
537 my @verbs = ( "0", "Ax->xA", "xD->Dx", "AxD->DxA",
538 "ABx->xAB", "ABx->xBA", "xCD->CDx", "xCD->DCx",
539 "AxCD->CDxA", "AxCD->DCxA", "ABxD->DxAB", "ABxD->DxBA",
540 "ABxCD->CDxAB", "ABxCD->CDxBA", "ABxCD->DCxAB", "ABxCD->DCxBA");
541 foreach (0 .. $#$states) {
542 $fh->printf("\t\tState %d:", $_);
543 my $state = $states->[$_];
546 $flags .= "!" if ($_->{'flags'} & 0x4000);
547 $flags .= "<" if ($_->{'flags'} & 0x8000);
548 $flags .= ">" if ($_->{'flags'} & 0x2000);
549 $fh->printf("\t(%s%d,%s)", $flags, $_->{'nextState'}, $verbs[($_->{'flags'} & 0x000f)]);
555 elsif ($type == 1) { # contextual
556 print_classes_($fh, $_, $post);
559 my $states = $_->{'states'};
560 foreach (0 .. $#$states) {
561 $fh->printf("\t\tState %d:", $_);
562 my $state = $states->[$_];
565 $flags .= "!" if ($_->{'flags'} & 0x4000);
566 $flags .= "*" if ($_->{'flags'} & 0x8000);
567 my $actions = $_->{'actions'};
568 $fh->printf("\t(%s%d,%s,%s)", $flags, $_->{'nextState'}, map { defined $_ ? $_ : "=" } @$actions);
574 my $mappings = $_->{'mappings'};
575 foreach (0 .. $#$mappings) {
576 $fh->printf("\t\tMapping %d: %d [%s]\n", $_, $mappings->[$_], $post->{'VAL'}[$mappings->[$_]]);
580 elsif ($type == 2) { # ligature
581 print_classes_($fh, $_, $post);
584 my $states = $_->{'states'};
585 foreach (0 .. $#$states) {
586 $fh->printf("\t\tState %d:", $_);
587 my $state = $states->[$_];
590 $flags .= "!" if ($_->{'flags'} & 0x4000);
591 $flags .= "*" if ($_->{'flags'} & 0x8000);
592 $fh->printf("\t(%s%d,%s)", $flags, $_->{'nextState'}, defined $_->{'actions'} ? $_->{'actions'} : "=");
598 my $actionLists = $_->{'actionLists'};
599 foreach (0 .. $#$actionLists) {
600 $fh->printf("\t\tList %d:\t", $_);
601 my $actionList = $actionLists->[$_];
602 $fh->printf("%s\n", join(", ", map { ($_->{'component'} . ($_->{'store'} ? "*" : "") ) } @$actionList));
605 my $ligatureTable = $_->{'ligatureTable'};
608 my $components = $_->{'components'};
609 foreach (0 .. $#$components) {
610 $fh->printf("\t\tComponent %d: %s\n", $_, $components->[$_]);
614 my $ligatures = $_->{'ligatures'};
615 foreach (0 .. $#$ligatures) {
616 $fh->printf("\t\tLigature %d: %d [%s]\n", $_, $ligatures->[$_], $post->{'VAL'}[$ligatures->[$_]]);
620 elsif ($type == 4) { # non-contextual
621 my $lookup = $_->{'lookup'};
622 $fh->printf("\t\tLookup format %d\n", $_->{'format'});
623 if (defined $lookup) {
624 foreach (sort { $a <=> $b } keys %$lookup) {
625 $fh->printf("\t\t\t%d [%s] -> %d [%s])\n", $_, $post->{'VAL'}[$_], $lookup->{$_}, $post->{'VAL'}[$lookup->{$_}]);
630 elsif ($type == 5) { # insertion
631 print_classes_($fh, $_, $post);
634 my $states = $_->{'states'};
635 foreach (0 .. $#$states) {
636 $fh->printf("\t\tState %d:", $_);
637 my $state = $states->[$_];
640 $flags .= "!" if ($_->{'flags'} & 0x4000);
641 $flags .= "*" if ($_->{'flags'} & 0x8000);
642 my $actions = $_->{'actions'};
643 $fh->printf("\t(%s%d,%s,%s)", $flags, $_->{'nextState'}, map { defined $_ ? $_ : "=" } @$actions);
649 my $insertLists = $_->{'insertLists'};
650 foreach (0 .. $#$insertLists) {
651 my $insertList = $insertLists->[$_];
652 $fh->printf("\t\tList %d: %s\n", $_, join(", ", map { $_ . " [" . $post->{'VAL'}[$_] . "]" } @$insertList));
665 my ($fh, $subtable, $post) = @_;
667 my $classes = $subtable->{'classes'};
668 foreach (0 .. $#$classes) {
669 my $class = $classes->[$_];
670 if (defined $class) {
671 $fh->printf("\t\tClass %d:\t%s\n", $_, join(", ", map { $_ . " [" . $post->{'VAL'}[$_] . "]" } @$class));
689 $res = $types[$val] or ('Undefined (' . $val . ')');
702 Jonathan Kew L<Jonathan_Kew@sil.org>. See L<Font::TTF::Font> for copyright and