1 # Copyright (c) 2009 Philip Taylor
3 # Permission is hereby granted, free of charge, to any person
4 # obtaining a copy of this software and associated documentation
5 # files (the "Software"), to deal in the Software without
6 # restriction, including without limitation the rights to use,
7 # copy, modify, merge, publish, distribute, sublicense, and/or sell
8 # copies of the Software, and to permit persons to whom the
9 # Software is furnished to do so, subject to the following
12 # The above copyright notice and this permission notice shall be
13 # included in all copies or substantial portions of the Software.
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 # OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 # HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 # WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 # FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 # OTHER DEALINGS IN THE SOFTWARE.
24 package Font::Subsetter;
30 use Unicode::Normalize();
31 use Digest::SHA qw(sha1_hex);
37 if ($Font::TTF::VERSION =~ /^0\.([0-3].|4[0-5])$/) {
38 die "You are using an old version of Font::TTF ($Font::TTF::VERSION) - you need at least v0.46, and preferably the latest SVN trunk from <http://scripts.sil.org/cms/scripts/page.php?site_id=nrsi&id=fontutils>.";
42 # REQUIRED - will fail if it's not present
43 # FORBIDDEN - will fail if it's present
44 # OPTIONAL - will be accepted regardless of whether it's there or not
45 # IGNORED - like OPTIONAL, but no processing will take place
46 # UNFINISHED - will emit a warning if it's present, because the code doesn't handle it properly yet
47 # DROP - will be deleted from the font
48 # The default for unmentioned tables is FORBIDDEN
50 'cmap' => ['REQUIRED'],
51 'head' => ['REQUIRED'],
52 'hhea' => ['REQUIRED'],
53 'hmtx' => ['REQUIRED'],
54 'maxp' => ['REQUIRED'],
55 'name' => ['REQUIRED'],
56 'OS/2' => ['REQUIRED'],
57 'post' => ['REQUIRED'],
59 'cvt ' => ['IGNORED'],
60 'fpgm' => ['IGNORED'],
61 'glyf' => ['IGNORED'],
62 'loca' => ['OPTIONAL'],
63 'prep' => ['OPTIONAL'],
64 # PostScript outlines: (TODO: support these?)
65 'CFF ' => ['FORBIDDEN'],
66 'VORG' => ['FORBIDDEN'],
67 # Bitmap glyphs: (TODO: support these?)
68 'EBDT' => ['DROP', 'embedded bitmap glyphs will be lost'],
69 'EBLC' => ['DROP', 'embedded bitmap glyphs will be lost'],
70 'EBSC' => ['DROP', 'embedded bitmap glyphs will be lost'],
71 # Advanced typographic tables:
72 'BASE' => ['UNFINISHED'],
73 'GDEF' => ['OPTIONAL'],
74 'GPOS' => ['OPTIONAL'],
75 'GSUB' => ['OPTIONAL'],
76 'JSTF' => ['UNFINISHED'],
78 'DSIG' => ['DROP'], # digital signature - don't need it here
79 'gasp' => ['IGNORED'],
80 'hdmx' => ['OPTIONAL'],
81 'kern' => ['OPTIONAL'],
82 'LTSH' => ['OPTIONAL'],
83 'PCLT' => ['UNFINISHED'],
84 'VDMX' => ['IGNORED'],
85 'vhea' => ['UNFINISHED'],
86 'vmtx' => ['UNFINISHED'],
87 # SIL Graphite tables:
97 # Apple Advanced Typography tables:
98 # (These get dropped because it's better to use cross-platform features instead)
102 # Undocumented(?) extension for some kind of maths stuff
108 my $font = $self->{font};
110 my @tables = grep /^[^ ]...$/, sort keys %$font;
112 my $t = $font_tables{$_};
114 die "Uses unrecognised table '$_'\n";
116 my $status = $t->[0];
117 if ($status eq 'FORBIDDEN') {
118 die "Uses forbidden table '$_'\n";
119 } elsif ($status eq 'UNFINISHED') {
120 warn "Uses unhandled table '$_'\n";
121 } elsif ($status eq 'DROP') {
122 my $note = ($t->[1] ? ' - '.$t->[1] : '');
123 warn "Dropping table '$_'$note\n";
125 } elsif ($status eq 'OPTIONAL') {
126 } elsif ($status eq 'IGNORED') {
127 } elsif ($status eq 'REQUIRED') {
129 die "Invalid table status $status";
133 # TODO: check required tables are present
134 # TODO: check TrueType or PostScript tables are present
139 my $font = $self->{font};
141 # Read all the tables that will be needed in the future.
142 # (In particular, read them before modifying numGlyphs,
143 # beacuse they often depend on that value.)
145 cmap hmtx name OS/2 post
147 BASE GDEF GPOS GSUB JSTF
150 $font->{$_}->read if $font->{$_};
154 sub find_codepoint_glyph_mappings {
156 my $font = $self->{font};
158 # Find the glyph->codepoint mappings
161 for my $table (@{$font->{cmap}{Tables}}) {
162 for my $cp (keys %{$table->{val}}) {
164 my $ucp; # Unicode code point
166 if ($table->{Platform} == 0 # Unicode
167 or ($table->{Platform} == 3 and # Windows
168 ($table->{Encoding} == 1 or # Unicode BMP
169 $table->{Encoding} == 10)) # Unicode full
172 } elsif ($table->{Platform} == 1 # Mac
173 and $table->{Encoding} == 0) # Roman
175 $ucp = ord(decode('MacRoman', pack C => $cp));
177 # This table might not map directly onto Unicode codepoints,
179 warn "Unrecognised cmap table type (platform $table->{Platform}, encoding $table->{Encoding}) - ignoring its character/glyph mappings\n";
183 my $g = $table->{val}{$cp}; # glyph id
184 $glyphs{$g}{$ucp} = 1;
187 $self->{glyphs} = \%glyphs;
190 sub expand_wanted_chars {
191 my ($self, $chars) = @_;
192 # OS X browsers (via ATSUI?) appear to convert text into
193 # NFC before rendering it.
194 # So input like "i{combining grave}" is converted to "{i grave}"
195 # before it's even passed to the font's substitution tables.
196 # So if @chars contains i and {combining grave}, then we have to
197 # add {i grave} because that might get used.
199 # So... Include all the unchanged characters. Also include the NFC
200 # of each character. Then use NormalizationData to add any characters
201 # that can result from NFCing a string of the wanted characters.
203 if (0) { # change to 1 to disable all this fancy stuff
204 my %cs = map { ord $_ => 1 } split '', $chars;
208 my %cs = map { ord $_ => 1, ord Unicode::Normalize::NFC($_) => 1 } split '', $chars;
209 require Font::Subsetter::NormalizationData;
211 for my $c (@Font::Subsetter::NormalizationData::data) {
212 # Skip this if we've already got the composed character
213 next if $cs{$c->[0]};
214 # Skip this if we don't have all the decomposed characters
215 next if grep !$cs{$_}, @{$c}[1..$#$c];
216 # Otherwise we want the composed character
217 $new_cs{$c->[0]} = 1;
219 $cs{$_} = 1 for keys %new_cs;
224 my ($self, $wanted, $feature) = @_;
225 # If no feature list was specified, accept all features
226 return 1 if not $wanted;
227 # Otherwise find the four-character tag
228 $feature =~ /^(\w{4})( _\d+)?$/ or die "Unrecognised feature tag syntax '$feature'";
229 return $wanted->{$1} if exists $wanted->{$1};
230 return $wanted->{DEFAULT} if exists $wanted->{DEFAULT};
234 sub find_wanted_lookup_ids {
235 my ($self, $table) = @_;
237 # If we wanted to include all lookups:
238 # return 0..$#{$table->{LOOKUP}};
239 # but actually we only want ones used by wanted features
242 for my $feat_tag (@{$table->{FEATURES}{FEAT_TAGS}}) {
243 next if not $self->want_feature($self->{features}, $feat_tag);
244 for (@{$table->{FEATURES}{$feat_tag}{LOOKUPS}}) {
249 # Iteratively add any chained lookups
253 for my $lookup_id (0..$#{$table->{LOOKUP}}) {
254 next unless $lookups{$lookup_id};
255 my $lookup = $table->{LOOKUP}[$lookup_id];
256 for my $sub (@{$lookup->{SUB}}) {
257 if ($sub->{ACTION_TYPE} eq 'l') {
258 for my $rule (@{$sub->{RULES}}) {
259 for my $chain (@$rule) {
260 for my $action (@{$chain->{ACTION}}) {
261 for (0..@$action/2-1) {
262 # action is array of (offset, lookup)
263 $changed = 1 if not $lookups{$action->[$_*2+1]};
264 $lookups{$action->[$_*2+1]} = 1;
274 my @keys = sort { $a <=> $b } keys %lookups;
278 sub find_wanted_glyphs {
279 my ($self, $chars) = @_;
280 my $font = $self->{font};
282 my %wanted_chars = $self->expand_wanted_chars($chars);
283 $self->{wanted_glyphs} = {};
285 # http://www.microsoft.com/typography/otspec/recom.htm suggests that fonts
286 # should include .notdef, .null, CR, space; so include them all here, if they
287 # are already defined
288 if ($font->{post}{VAL}) {
289 for my $gid (0..$#{$font->{loca}{glyphs}}) {
290 my $name = $font->{post}{VAL}[$gid];
291 if ($name and ($name eq '.notdef' or $name eq '.null' or $name eq 'CR' or $name eq 'space')) {
292 $self->{wanted_glyphs}{$gid} = 1;
296 # If post.FormatType == 3 then we don't have any glyph names
297 # so just assume it's the first four
298 $self->{wanted_glyphs}{$_} = 1 for 0..3;
301 # We want any glyphs used directly by any characters we want
302 for my $gid (keys %{$self->{glyphs}}) {
303 for my $cp (keys %{$self->{glyphs}{$gid}}) {
304 $self->{wanted_glyphs}{$gid} = 1 if $wanted_chars{$cp};
308 # Iteratively find new glyphs, until convergence
309 my @newly_wanted_glyphs = keys %{$self->{wanted_glyphs}};
310 while (@newly_wanted_glyphs) {
315 # Handle ligatures and similar things
316 # (e.g. if we want 'f' and 'i', we want the 'fi' ligature too)
317 # (NOTE: a lot of this code is duplicating the form of
318 # fix_gsub, so they ought to be kept roughly in sync)
320 # TODO: There's probably loads of bugs in here, so it
321 # should be checked and tested more
323 for my $lookup_id ($self->find_wanted_lookup_ids($font->{GSUB})) {
324 my $lookup = $font->{GSUB}{LOOKUP}[$lookup_id];
325 for my $sub (@{$lookup->{SUB}}) {
327 # Handle the glyph-delta case
328 if ($sub->{ACTION_TYPE} eq 'o') {
329 my $adj = $sub->{ADJUST};
330 if ($adj >= 32768) { $adj -= 65536 } # fix Font::TTF::Bug (http://rt.cpan.org/Ticket/Display.html?id=42727)
331 my @covs = $self->coverage_array($sub->{COVERAGE});
333 # If we want the coveraged glyph, we also want
334 # that glyph plus delta
335 if ($self->{wanted_glyphs}{$_}) {
337 next if $self->{wanted_glyphs}{$new};
338 push @new_glyphs, $new;
339 $self->{wanted_glyphs}{$new} = 1;
345 # Collect the rules which might match initially something
348 if (($lookup->{TYPE} == 5 or $lookup->{TYPE} == 6)
349 and $sub->{FORMAT} == 2) {
350 # RULES corresponds to class values
351 # TODO: ought to filter this by classes that contain wanted glyphs
352 push @rulesets, @{$sub->{RULES}};
353 } elsif (($lookup->{TYPE} == 5 or $lookup->{TYPE} == 6)
354 and $sub->{FORMAT} == 3) {
355 # COVERAGE is empty; accept all the RULEs, and
356 # we'll look inside their MATCHes later
357 push @rulesets, @{$sub->{RULES}};
359 # COVERAGE lists glyphs, and there's a RULE for
360 # each, so extract the RULEs for wanted COVERAGE
362 my @covs = $self->coverage_array($sub->{COVERAGE});
363 die unless @{$sub->{RULES}} == @covs;
364 for my $i (0..$#covs) {
365 if ($self->{wanted_glyphs}{$covs[$i]}) {
366 push @rulesets, $sub->{RULES}[$i];
372 # Collect the rules whose MATCH matches
374 RULE: for my $rule (map @$_, @rulesets) {
375 if (not defined $sub->{MATCH_TYPE}) {
376 # No extra matching other than COVERAGE,
377 # so just accept this rule
378 } elsif ($sub->{MATCH_TYPE} eq 'g') {
379 # RULES->MATCH/PRE/POST are arrays of glyphs that must all match
380 for my $c (qw(MATCH PRE POST)) {
381 next unless $rule->{$c};
382 next RULE if grep { not $self->{wanted_glyphs}{$_} } @{$rule->{$c}};
384 } elsif ($sub->{MATCH_TYPE} eq 'o') {
385 # RULES->MATCH/PRE/POST are arrays of coverage tables,
386 # and at least one glyph from each table must match
387 die unless @{$sub->{RULES}} == 1;
388 die unless @{$sub->{RULES}[0]} == 1;
389 for my $c (qw(MATCH PRE POST)) {
390 next unless $sub->{RULES}[0][0]{$c};
391 for (@{$sub->{RULES}[0][0]{$c}}) {
393 for (keys %{$_->{val}}) {
394 if ($self->{wanted_glyphs}{$_}) {
399 next RULE if not $matched;
402 } elsif ($sub->{MATCH_TYPE} eq 'c') {
403 # TODO: only includes rules using classes that contain
405 # For now, just conservatively accept everything.
407 die "Invalid MATCH_TYPE";
412 # Find the glyphs in the relevant actions
413 for my $rule (@rules) {
414 if ($sub->{ACTION_TYPE} eq 'g') {
415 die unless $rule->{ACTION};
416 for my $new (@{$rule->{ACTION}}) {
417 next if $self->{wanted_glyphs}{$new};
418 push @new_glyphs, $new;
419 $self->{wanted_glyphs}{$new} = 1;
420 # warn "adding $new";
422 } elsif ($sub->{ACTION_TYPE} eq 'l') {
423 # do nothing - this is just a lookup to run some other rules
424 } elsif ($sub->{ACTION_TYPE} eq 'a') {
425 # do nothing - we don't want the alternative glyphs
427 die "Invalid ACTION_TYPE";
434 @newly_wanted_glyphs = @new_glyphs;
437 # Now we want to add glyphs that are used for composite rendering,
438 # which don't participate in any GSUB behaviour
439 @newly_wanted_glyphs = keys %{$self->{wanted_glyphs}};
440 while (@newly_wanted_glyphs) {
444 # If we want a composite glyph, we want all of its
445 # component glyphs too
446 # (e.g. á is the 'a' glyph plus the acute glyph):
447 for my $gid (@newly_wanted_glyphs) {
448 my $glyph = $font->{loca}{glyphs}[$gid];
451 next unless $glyph->{numberOfContours} == -1;
453 for (@{$glyph->{comps}}) {
454 next if $self->{wanted_glyphs}{$_->{glyph}};
455 push @new_glyphs, $_->{glyph};
456 $self->{wanted_glyphs}{$_->{glyph}} = 1;
462 @newly_wanted_glyphs = @new_glyphs;
466 sub update_classdef_table {
467 my ($self, $table) = @_;
468 die "Expected table" if not $table;
469 die "Expected classdef" if $table->{cover};
471 for my $gid (keys %{$table->{val}}) {
472 next if not $self->{wanted_glyphs}{$gid};
473 my $v = $table->{val}{$gid};
474 push @vals, $self->{glyph_id_old_to_new}{$gid}, $v;
476 my $ret = new Font::TTF::Coverage(0, @vals);
477 # Font::TTF bug (http://rt.cpan.org/Ticket/Display.html?id=42716):
478 # 'max' is not set by new(), so do it manually:
480 for (values %{$ret->{val}}) { $max = $_ if $_ > $max }
485 # Returns a map such that map[old_class_value] = new_class_value
486 # (or undef if the class is removed)
487 # This differs from update_classdef_table in that it can
488 # reorder and optimise the class ids
489 sub update_mapped_classdef_table {
490 my ($self, $table) = @_;
491 die "Expected table" if not $table;
492 die "Expected classdef" if $table->{cover};
495 $used_classes{0} = 1; # 0 is implicitly in every classdef
496 for my $gid (keys %{$table->{val}}) {
497 next if not $self->{wanted_glyphs}{$gid};
498 my $v = $table->{val}{$gid};
499 push @vals, $self->{glyph_id_old_to_new}{$gid}, $v;
500 $used_classes{$v} = 1;
503 my @map_new_to_old = sort { $a <=> $b } keys %used_classes;
505 $map_old_to_new[$map_new_to_old[$_]] = $_ for 0..$#map_new_to_old;
507 # Update the class numbers
509 $vals[$_*2+1] = $map_old_to_new[$vals[$_*2+1]];
512 my $ret = new Font::TTF::Coverage(0, @vals);
513 # Font::TTF bug (http://rt.cpan.org/Ticket/Display.html?id=42716):
514 # 'max' is not set by new(), so do it manually:
516 for (values %{$ret->{val}}) { $max = $_ if $_ > $max }
518 return ($ret, \@map_old_to_new, \@map_new_to_old);
521 # Removes unwanted glyphs from a coverage table, for
522 # cases where nobody else is referring to indexes in this table
523 sub update_coverage_table {
524 my ($self, $table) = @_;
525 die "Expected table" if not $table;
526 die "Expected cover" if not $table->{cover};
527 my @vals = keys %{$table->{val}};
528 @vals = grep $self->{wanted_glyphs}{$_}, @vals;
529 @vals = sort { $a <=> $b } @vals;
530 @vals = map $self->{glyph_id_old_to_new}{$_}, @vals;
531 return new Font::TTF::Coverage(1, @vals);
534 # Returns a map such that map[new_coverage_index] = old_coverage_index
535 sub update_mapped_coverage_table {
536 my ($self, $table) = @_;
537 die "Expected table" if not $table;
538 die "Expected coverage" if not $table->{cover};
542 # Get the covered values (in order)
543 my @vals = $self->coverage_array($table);
544 for my $i (0..$#vals) {
545 # Create a new list of all the wanted values
546 if ($self->{wanted_glyphs}{$vals[$i]}) {
547 push @new_vals, $self->{glyph_id_old_to_new}{$vals[$i]};
551 return (new Font::TTF::Coverage(1, @new_vals), @map);
555 my ($self, $table) = @_;
556 Carp::confess "Expected table" if not $table;
557 return sort { $table->{val}{$a} <=> $table->{val}{$b} } keys %{$table->{val}};
561 my ($self, $table) = @_;
562 Carp::confess "Expected table" if not $table;
563 return 1 if not $table->{val};
564 return 1 if not keys %{$table->{val}};
568 # Update the loca table to delete unwanted glyphs.
569 # Must be called before all the other fix_* methods.
570 sub remove_unwanted_glyphs {
572 my $font = $self->{font};
574 return unless $font->{loca};
576 my %glyph_id_old_to_new;
577 my %glyph_id_new_to_old;
579 my $glyphs = $font->{loca}{glyphs};
581 for my $i (0..$#$glyphs) {
582 if ($self->{wanted_glyphs}{$i}) {
583 push @new_glyphs, $glyphs->[$i];
584 $glyph_id_old_to_new{$i} = $#new_glyphs;
585 $glyph_id_new_to_old{$#new_glyphs} = $i;
588 $font->{loca}{glyphs} = \@new_glyphs;
589 $font->{maxp}{numGlyphs} = scalar @new_glyphs;
591 $self->{glyph_id_old_to_new} = \%glyph_id_old_to_new;
592 $self->{glyph_id_new_to_old} = \%glyph_id_new_to_old;
596 # Only the platform=3 encoding=1 cmap is really needed
597 # (for Windows, OS X, Linux), so save space (and potentially
598 # enhance cross-platformness) by stripping out all the others.
599 # (But keep platform=3 encoding=10 too, for UCS-4 characters.)
600 # (And Opera 10 on OS X wants one with platform=0 too.)
603 my $font = $self->{font};
605 if (not grep { $_->{Platform} == 3 and $_->{Encoding} == 1 } @{$font->{cmap}{Tables}}) {
606 warn "No cmap found with platform=3 encoding=1 - the font is likely to not work on Windows.\n";
607 # Stop now, instead of stripping out all of the cmap tables
611 my @matched_tables = grep {
612 ($_->{Platform} == 3 and ($_->{Encoding} == 1 || $_->{Encoding} == 10))
613 or ($_->{Platform} == 0)
614 } @{$font->{cmap}{Tables}};
616 $font->{cmap}{Tables} = \@matched_tables;
619 # Only the platform=3 encoding=1 names are really needed
620 # (for Windows, OS X, Linux), so save space (and potentially
621 # enhance cross-platformness) by stripping out all the others.
624 my $font = $self->{font};
626 for my $id (0..$#{$font->{name}{strings}}) {
627 my $str = $font->{name}{strings}[$id];
631 my $langs = $str->[$plat][$enc];
633 warn "No name found with id=$id with platform=3 encoding=1 - the font is likely to not work on Windows.\n"
634 unless $id == 18; # warn except for some Mac-specific names
637 $font->{name}{strings}[$id] = [];
638 $font->{name}{strings}[$id][$plat][$enc] = $langs;
639 # NOTE: this keeps all the languages for each string, which is
640 # potentially wasteful if there are lots (but in practice most fonts
641 # seem to only have English)
647 my $font = $self->{font};
649 # Delete mappings for unwanted glyphs
651 for my $table (@{$font->{cmap}{Tables}}) {
652 # (Already warned about unrecognised table types
653 # in find_codepoint_glyph_mappings)
655 for my $cp (keys %{$table->{val}}) {
656 my $gid = $table->{val}{$cp};
657 if ($self->{wanted_glyphs}{$gid}) {
658 $new_vals{$cp} = $self->{glyph_id_old_to_new}{$gid};
661 $table->{val} = \%new_vals;
662 if ($table->{Format} == 0) {
663 @{$table->{val}}{0..255} = map { defined($_) ? $_ : 0 } @{$table->{val}}{0..255};
669 # TODO: Should think about:
672 # xMin (depends on glyph data)
673 # yMin (depends on glyph data)
674 # xMax (depends on glyph data)
675 # yMax (depends on glyph data)
679 # TODO: Should think about:
680 # advanceWidthMax (depends on hmtx)
681 # minLeftSideBearing (depends on hmtx)
682 # minRightSideBearing (depends on hmtx)
683 # xMaxExtent (depends on hmtx)
688 my $font = $self->{font};
690 # Map the advance/lsb arrays from old to new glyph ids
693 for my $gid (0..$font->{maxp}{numGlyphs}-1) {
694 push @new_advances, $font->{hmtx}{advance}[$self->{glyph_id_new_to_old}{$gid}];
695 push @new_lsbs, $font->{hmtx}{lsb}[$self->{glyph_id_new_to_old}{$gid}];
697 $font->{hmtx}{advance} = \@new_advances;
698 $font->{hmtx}{lsb} = \@new_lsbs;
701 sub fix_maxp { # Must come after loca, prep, fpgm
703 my $font = $self->{font};
705 # Update some of the 'max' values that Font::TTF
706 # is capable of updating
707 $font->{maxp}->update;
710 sub fix_os_2 { # Must come after cmap, hmtx, hhea, GPOS, GSUB
712 my $font = $self->{font};
714 # Update some of the metric values that Font::TTF
715 # is capable of updating
716 $font->{'OS/2'}->update;
718 if ($font->{'OS/2'}{Version} >= 2) {
719 # TODO: handle cases where these are non-default
720 warn "Unexpected defaultChar $font->{'OS/2'}{defaultChar}\n"
721 unless $font->{'OS/2'}{defaultChar} == 0;
722 warn "Unexpected breakChar $font->{'OS/2'}{breakChar}\n"
723 unless $font->{'OS/2'}{breakChar} == 0x20;
729 my $font = $self->{font};
731 if ($font->{post}{FormatType} == 0) {
732 warn "Invalid 'post' table type. (If you're using the obfuscate-font.pl script, make sure it comes *after* the subsetting.)\n";
735 # Update PostScript name mappings for new glyph ids
736 if ($font->{post}{VAL}) {
738 for my $gid (0..$font->{maxp}{numGlyphs}-1) {
739 push @new_vals, $font->{post}{VAL}[$self->{glyph_id_new_to_old}{$gid}];
741 $font->{post}{VAL} = \@new_vals;
750 my $font = $self->{font};
752 # remove_unwanted_glyphs has already removed some
753 # of the glyph data from this table
755 # Update references inside composite glyphs
756 for my $glyph (@{$font->{loca}{glyphs}}) {
759 next unless $glyph->{numberOfContours} == -1;
761 for (@{$glyph->{comps}}) {
762 # (find_unwanted_glyphs guarantees that the
763 # component glyphs will be present)
764 $_->{glyph} = $self->{glyph_id_old_to_new}{$_->{glyph}};
773 my $font = $self->{font};
775 if ($font->{GDEF}{GLYPH}) {
776 $font->{GDEF}{GLYPH} = $self->update_classdef_table($font->{GDEF}{GLYPH});
777 if ($self->empty_coverage($font->{GDEF}{GLYPH})) {
778 delete $font->{GDEF}{GLYPH};
782 if ($font->{GDEF}{MARKS}) {
783 $font->{GDEF}{MARKS} = $self->update_classdef_table($font->{GDEF}{MARKS});
784 if ($self->empty_coverage($font->{GDEF}{MARKS})) {
785 delete $font->{GDEF}{MARKS};
789 if ($font->{GDEF}{ATTACH}) {
790 die "TODO" if $font->{GDEF}{ATTACH}{POINTS};
791 $font->{GDEF}{ATTACH}{COVERAGE} = $self->update_coverage_table($font->{GDEF}{ATTACH}{COVERAGE});
792 if ($self->empty_coverage($font->{GDEF}{ATTACH}{COVERAGE})) {
793 delete $font->{GDEF}{ATTACH};
797 if ($font->{GDEF}{LIG}) {
799 if ($font->{GDEF}{LIG}{LIGS}) {
800 die "GDEF LIG LIGS != COVERAGE" if
801 @{$font->{GDEF}{LIG}{LIGS}} != keys %{$font->{GDEF}{LIG}{COVERAGE}{val}};
804 ($font->{GDEF}{LIG}{COVERAGE}, @coverage_map) = $self->update_mapped_coverage_table($font->{GDEF}{LIG}{COVERAGE});
805 $font->{GDEF}{LIG}{LIGS} = [ map $font->{GDEF}{LIG}{LIGS}[$_], @coverage_map ];
808 $font->{GDEF}{LIG}{COVERAGE} = $self->update_coverage_table($font->{GDEF}{LIG}{COVERAGE});
811 if ($self->empty_coverage($font->{GDEF}{LIG}{COVERAGE})) {
812 delete $font->{GDEF}{LIG};
819 my ($self, $table, $inner) = @_;
823 for my $lookup_id ($self->find_wanted_lookup_ids($table)) {
824 my $lookup = $table->{LOOKUP}[$lookup_id];
826 for my $sub (@{$lookup->{SUB}}) {
827 if ($inner->($lookup, $sub)) {
828 push @subtables, $sub;
832 # Only keep lookups that have some subtables
834 $lookup->{SUB} = \@subtables;
835 push @lookups, $lookup;
836 $lookup_map{$lookup_id} = $#lookups;
840 $table->{LOOKUP} = \@lookups;
842 # Update lookup references inside actions
843 for my $lookup (@{$table->{LOOKUP}}) {
844 for my $sub (@{$lookup->{SUB}}) {
845 if ($sub->{ACTION_TYPE} eq 'l') {
846 for my $rule (@{$sub->{RULES}}) {
847 for my $chain (@$rule) {
849 for my $action (@{$chain->{ACTION}}) {
851 for (0..@$action/2-1) {
852 # action is array of (offset, lookup)
853 # so just update the lookup
854 if (exists $lookup_map{$action->[$_*2+1]}) {
855 push @steps, ($action->[$_*2], $lookup_map{$action->[$_*2+1]});
858 push @actions, \@steps;
860 $chain->{ACTION} = \@actions;
867 # Remove all features that are not wanted
868 # and update all references to those features (in the languages list),
869 # and update the features' lookup references
871 my @features; # array of [tag, feature]
873 for my $feat_tag (@{$table->{FEATURES}{FEAT_TAGS}}) {
874 next unless $self->want_feature($self->{features}, $feat_tag); # drop unwanted features
875 my $feat = $table->{FEATURES}{$feat_tag};
876 $feat->{LOOKUPS} = [ map { exists $lookup_map{$_} ? ($lookup_map{$_}) : () } @{$feat->{LOOKUPS}} ];
877 next unless @{$feat->{LOOKUPS}}; # drop empty features to save some space
878 push @features, [ $feat_tag, $feat ];
879 $kept_features{$feat_tag} = 1;
882 $table->{FEATURES} = {
883 FEAT_TAGS => [map $_->[0], @features],
884 map +($_->[0] => $_->[1]), @features,
887 # Remove any references from scripts to features that no longer exist
888 for my $script_tag (keys %{$table->{SCRIPTS}}) {
889 my $script = $table->{SCRIPTS}{$script_tag};
890 for my $tag ('DEFAULT', @{$script->{LANG_TAGS}}) {
891 next if $script->{$tag}{' REFTAG'}; # ignore langs that are just copies of another
892 $script->{$tag}{FEATURES} = [
893 grep $kept_features{$_}, @{$script->{$tag}{FEATURES}}
899 # TODO: it'd be nice to delete languages that have no features
905 my $font = $self->{font};
907 $self->fix_ttopen($font->{GPOS},
909 my ($lookup, $sub) = @_;
911 # There's always a COVERAGE here first.
912 # (If it's empty, the client will skip the entire subtable,
913 # so we could delete it entirely, but that would involve updating
914 # the FEATURES->*->LOOKUPS lists too, so don't do that yet.)
916 # The rest depends on Type:
918 # Lookup Type 1 (Single Adjustment Positioning Subtable):
919 # Format 1: Just COVERAGE, applies same value to all
920 # Format 2: Just COVERAGE, RULES[n] gives value for each
922 # Lookup Type 2 (Pair Adjustment Positioning Subtable):
923 # Format 1: COVERAGE gives first glyph, RULES[n][m]{MATCH}[0] gives second glyph
924 # Format 2: COVERAGE gives first glyph, CLASS gives first glyph class, MATCH[0] gives second glyph class
926 # Lookup Type 3 (Cursive Attachment Positioning Subtable):
927 # Format 1: Just COVERAGE, RULES[n] gives value for each
929 # Lookup Type 4 (MarkToBase Attachment Positioning Subtable):
930 # Format 1: MATCH[0] gives mark coverage, COVERAGE gives base coverage, MARKS[n] per mark, RULES[n] per base
932 # Lookup Type 5 (MarkToLigature Attachment Positioning Subtable):
933 # Format 1: pretty much the same as 4, but s/base/ligature/
935 # Lookup Type 6 (MarkToMark Attachment Positioning Subtable):
936 # Format 1: pretty much the same as 4, but s/base/mark/
938 # Lookup Type 7 (Contextual Positioning Subtables):
939 # Format 1: COVERAGE gives first glyph, RULES[n][m]{MATCH}[o] gives next glyphs
940 # Format 2: COVERAGE gives first glyph, CLASS gives classes to glyphs, RULES[n] is per class
941 # Format 3: COVERAGE absent, RULES[0][0]{MATCH}[o] gives glyph coverages
943 # Lookup Type 8 (Chaining Contextual Positioning Subtable):
944 # Format 1: COVERAGE gives first glyph, RULES[n][m]{PRE/MATCH/POST} give context glyphs
945 # Format 2: COVERAGE gives first glyph, PRE_CLASS/CLASS/POST_CLASS give classes
946 # Format 3: COVERAGE absent, RULES[0][0]{PRE/MATCH/POST}[o] give coverages
948 # Lookup Type 9 (Extension Positioning):
951 die if $lookup->{TYPE} >= 9;
953 # Update the COVERAGE table, and remember some mapping
954 # information to update things that refer to the table
956 my $old_coverage_count;
957 if ($sub->{COVERAGE}) {
958 $old_coverage_count = scalar keys %{$sub->{COVERAGE}{val}};
959 ($sub->{COVERAGE}, @coverage_map) = $self->update_mapped_coverage_table($sub->{COVERAGE});
961 # If there's no coverage left, then drop this subtable
962 return 0 if $self->empty_coverage($sub->{COVERAGE});
965 if ($sub->{RULES} and $sub->{COVERAGE} and not
966 # Skip cases where RULES is indexed by CLASS, not COVERAGE
967 (($lookup->{TYPE} == 2 or
968 $lookup->{TYPE} == 7 or
969 $lookup->{TYPE} == 8)
970 and $sub->{FORMAT} == 2)
972 # There's a RULES array per COVERAGE entry, so
973 # shuffle them around to match the new COVERAGE
974 if (@{$sub->{RULES}} != $old_coverage_count) {
975 die "Internal error: RULES ($sub->{RULES}) does not match COVERAGE ($sub->{COVERAGE}) -- "
976 . @{$sub->{RULES}} . " vs $old_coverage_count.";
978 $sub->{RULES} = [ map $sub->{RULES}[$_], @coverage_map ];
981 if (not defined $sub->{MATCH_TYPE} or $sub->{MATCH_TYPE} eq 'g') {
983 die unless @{$sub->{MATCH}} == 1;
984 die unless $sub->{MARKS};
985 die unless @{$sub->{MARKS}} == keys %{$sub->{MATCH}[0]{val}};
987 ($sub->{MATCH}[0], @match_map) = $self->update_mapped_coverage_table($sub->{MATCH}[0]);
989 # If there's no coverage left, then drop this subtable
990 return 0 if $self->empty_coverage($sub->{MATCH}[0]);
992 # Update MARKS to correspond to the new MATCH coverage
993 $sub->{MARKS} = [ map $sub->{MARKS}[$_], @match_map ];
996 # RULES->MATCH is an array of glyphs, so translate them all
997 for (@{$sub->{RULES}}) {
999 $_->{MATCH} = [ map $self->{glyph_id_old_to_new}{$_},
1000 grep $self->{wanted_glyphs}{$_}, @{$_->{MATCH}} ];
1003 } elsif ($sub->{MATCH_TYPE}) {
1004 if ($sub->{MATCH_TYPE} eq 'o') {
1005 # RULES->MATCH/PRE/POST are arrays of coverage tables, so translate them all
1006 die unless @{$sub->{RULES}} == 1;
1007 die unless @{$sub->{RULES}[0]} == 1;
1008 my $r = $sub->{RULES}[0][0];
1009 for my $c (qw(MATCH PRE POST)) {
1010 $r->{$c} = [ map $self->update_coverage_table($_), @{$r->{$c}} ] if $r->{$c};
1012 } elsif ($sub->{MATCH_TYPE} eq 'c') {
1013 die "Didn't expect any rule matches" if grep $_->{MATCH}, map @$_, @{$sub->{RULES}};
1014 die unless @{$sub->{MATCH}} == 1;
1017 ($sub->{CLASS}, undef, $class_map) = $self->update_mapped_classdef_table($sub->{CLASS});
1018 # Special case: If this results in an empty CLASS, it'll
1019 # break in FF3.5 on Linux, so assign all the COVERAGE glyphs onto
1020 # class 1 and update $class_map appropriately
1021 if ($sub->{CLASS}{max} == 0) {
1022 $sub->{CLASS} = new Font::TTF::Coverage(0, map +($_ => 1), keys %{$sub->{COVERAGE}{val}});
1023 $class_map = [0, 0]; # just duplicate class 0 into class 1 (this is a bit inefficient)
1026 $sub->{RULES} = [ @{$sub->{RULES}}[@$class_map] ];
1028 # Update the MATCH classdef table
1030 ($sub->{MATCH}[0], undef, $match_map) = $self->update_mapped_classdef_table($sub->{MATCH}[0]);
1032 # If the MATCH table is now empty, drop this lookup
1033 # (else FF3.5 on Linux drops the GPOS table entirely)
1034 return 0 if @$match_map <= 1;
1036 # RULES[n] is a list of substitutions per MATCH class, so
1037 # update all those lists for the new classdef
1038 $sub->{RULES} = [ map { [ @{$_}[@$match_map] ] } @{$sub->{RULES}} ];
1041 die "Invalid MATCH_TYPE";
1045 if (($lookup->{TYPE} == 7 or
1046 $lookup->{TYPE} == 8)
1047 and $sub->{FORMAT} == 2) {
1048 # Update some class tables
1049 for my $c (qw(CLASS PRE_CLASS POST_CLASS)) {
1050 $sub->{$c} = $self->update_classdef_table($sub->{$c}) if $sub->{$c};
1061 my $font = $self->{font};
1063 $self->fix_ttopen($font->{GSUB},
1065 my ($lookup, $sub) = @_;
1067 # There's always a COVERAGE here first.
1068 # (If it's empty, the client will skip the entire subtable,
1069 # so we could delete it entirely, but that would involve updating
1070 # the FEATURES->*->LOOKUPS lists and Contextual subtable indexes
1071 # too, so don't do that yet.)
1073 # The rest depends on Type:
1075 # Lookup Type 1 (Single Substitution Subtable):
1076 # Format 1: Just COVERAGE, and ADJUST gives glyph id delta
1077 # Format 2: Just COVERAGE, then RULES[n]{ACTION}[0] gives replacement glyph for each
1079 # Lookup Type 2 (Multiple Substitution Subtable):
1080 # Format 1: Just COVERAGE, then RULES[n]{ACTION} gives replacement glyphs (must be at least 1)
1082 # Lookup Type 3 (Alternate Substitution Subtable):
1083 # Format 1: Just COVERAGE, then RULES[n]{ACTION} gives alternate glyphs
1084 # [This can just be deleted since we have no way to use those glyphs]
1086 # Lookup Type 4 (Ligature Substitution Subtable):
1087 # Format 1: COVERAGE gives first glyph, RULES[n]{MATCH}[m] gives next glyphs to match, RULES[n]{ACTION}[0] gives replacement glyph
1089 # Lookup Type 5 (Contextual Substitution Subtable):
1090 # Format *: like type 7 in GPOS, but ACTION gives indexes into GSUB{LOOKUP}
1092 # Lookup Type 6 (Chaining Contextual Substitution Subtable):
1093 # Format *: like type 8 in GPOS, but ACTION gives indexes into GSUB{LOOKUP}
1095 # Lookup Type 7 (Extension Substitution):
1098 die if $lookup->{TYPE} >= 7;
1100 # Update the COVERAGE table, and remember some mapping
1101 # information to update things that refer to the table
1103 my $old_coverage_count;
1104 if ($sub->{COVERAGE}) {
1105 $old_coverage_count = scalar keys %{$sub->{COVERAGE}{val}};
1106 ($sub->{COVERAGE}, @coverage_map) = $self->update_mapped_coverage_table($sub->{COVERAGE});
1108 # If there's no coverage left, then drop this subtable
1109 return 0 if $self->empty_coverage($sub->{COVERAGE});
1112 if ($sub->{ACTION_TYPE} eq 'o') {;
1113 my $adj = $sub->{ADJUST};
1114 if ($adj >= 32768) { $adj -= 65536 } # fix Font::TTF::Bug (http://rt.cpan.org/Ticket/Display.html?id=42727)
1115 my @covs = $self->coverage_array($sub->{COVERAGE});
1117 # Nothing's covered, but deleting this whole subtable is
1118 # non-trivial so just zero it out
1120 } elsif (@covs == 1) {
1121 my $gid_base = $covs[0];
1122 my $old_gid_base = $self->{glyph_id_new_to_old}{$gid_base};
1123 my $old_gid = $old_gid_base + $adj;
1124 $sub->{ADJUST} = $self->{glyph_id_old_to_new}{$old_gid} - $gid_base;
1126 # The glyphs are probably all reordered, so we can't just
1128 # So switch this to a format 2 table:
1130 $sub->{ACTION_TYPE} = 'g';
1131 delete $sub->{ADJUST};
1134 push @gids, $self->{glyph_id_old_to_new}{$self->{glyph_id_new_to_old}{$_} + $adj};
1136 $sub->{RULES} = [ map [{ACTION => [$_]}], @gids ];
1138 # Stop and keep this table, since done everything that's needed
1141 die if $sub->{ADJUST};
1143 if ($sub->{RULES} and not
1144 # Skip cases where RULES is indexed by CLASS, not COVERAGE,
1145 # and cases where there's no COVERAGE at all
1146 (($lookup->{TYPE} == 5 or $lookup->{TYPE} == 6)
1147 and ($sub->{FORMAT} == 2 or $sub->{FORMAT} == 3))
1149 # There's a RULES array per COVERAGE entry, so
1150 # shuffle them around to match the new COVERAGE
1151 die unless @{$sub->{RULES}} == $old_coverage_count;
1152 $sub->{RULES} = [ map $sub->{RULES}[$_], @coverage_map ];
1156 if ($sub->{MATCH_TYPE}) {
1157 # Fix all the glyph indexes
1158 if ($sub->{MATCH_TYPE} eq 'g') {
1159 # RULES->MATCH/PRE/POST are arrays of glyphs, so translate them all,
1160 # and if they rely on any unwanted glyphs then drop the rule entirely
1161 for my $i (0..$#{$sub->{RULES}}) {
1162 my $ruleset = $sub->{RULES}[$i];
1164 RULE: for my $rule (@$ruleset) {
1165 for my $c (qw(MATCH PRE POST)) {
1166 next unless $rule->{$c};
1167 next RULE if grep { not $self->{wanted_glyphs}{$_} } @{$rule->{$c}};
1168 $rule->{$c} = [ map $self->{glyph_id_old_to_new}{$_}, @{$rule->{$c}} ]
1173 # XXX: This is a really horrid hack.
1174 # The proper solution is to delete the ruleset,
1175 # and adjust COVERAGE to match.
1176 push @rules, { ACTION => [0], MATCH => [-1] };
1178 $sub->{RULES}[$i] = \@rules;
1180 } elsif ($sub->{MATCH_TYPE} eq 'o') {
1181 # RULES->MATCH/PRE/POST are arrays of coverage tables, so translate them all
1182 die unless @{$sub->{RULES}} == 1;
1183 die unless @{$sub->{RULES}[0]} == 1;
1184 my $r = $sub->{RULES}[0][0];
1185 for my $c (qw(MATCH PRE POST)) {
1186 $r->{$c} = [ map $self->update_coverage_table($_), @{$r->{$c}} ] if $r->{$c};
1188 } elsif ($sub->{MATCH_TYPE} eq 'c') {
1189 # RULES refers to class values, which haven't changed at all,
1190 # so we don't need to update those values
1192 die "Invalid MATCH_TYPE";
1197 for my $c (qw(CLASS PRE_CLASS POST_CLASS)) {
1198 ($sub->{$c}, $class_maps{$c}) = $self->update_mapped_classdef_table($sub->{$c}) if $sub->{$c};
1202 if ($sub->{MATCH_TYPE} and $sub->{MATCH_TYPE} eq 'c') {
1203 # To make things work in Pango, we need to change all the
1204 # class numbers so there aren't gaps:
1208 POST => 'POST_CLASS',
1211 for my $rule (@{$sub->{RULES}}) {
1213 CHAIN: for my $chain (@$rule) {
1214 for my $c (qw(MATCH PRE POST)) {
1215 next unless $chain->{$c};
1216 my $map = $class_maps{$classes{$c}} or die "Got a $c but no $classes{$c}";
1217 # If any of the values are for a class that no longer has
1218 # any entries, we should drop this whole chain because
1219 # there's no chance it's going to match
1220 next CHAIN if grep { not defined $map->[$_] } @{$chain->{$c}};
1221 # Otherwise just update the class numbers
1222 $chain->{$c} = [ map $map->[$_], @{$chain->{$c}} ];
1224 push @chains, $chain;
1226 push @rules, \@chains;
1228 $sub->{RULES} = \@rules;
1229 # If all the rules are empty, drop this whole subtable (which maybe is
1230 # needed to avoid https://bugzilla.mozilla.org/show_bug.cgi?id=475242 ?)
1231 return 0 if not grep @$_, @{$sub->{RULES}};
1234 if ($sub->{ACTION_TYPE}) {
1235 if ($sub->{ACTION_TYPE} eq 'g') {
1236 for (@{$sub->{RULES}}) {
1238 $_->{ACTION} = [ map $self->{glyph_id_old_to_new}{$_},
1239 grep $self->{wanted_glyphs}{$_}, @{$_->{ACTION}} ];
1242 } elsif ($sub->{ACTION_TYPE} eq 'l') {
1243 # nothing to change here
1244 } elsif ($sub->{ACTION_TYPE} eq 'a') {
1245 # We don't want to bother with alternate glyphs at all,
1246 # so just delete everything.
1247 # (We need to have empty rules, and can't just delete them
1248 # entirely, else FontTools becomes unhappy.)
1249 # (TODO: Maybe we do want alternate glyphs?
1250 # If so, be sure to update find_wanted_glyphs too)
1251 for (@{$sub->{RULES}}) {
1256 } elsif ($sub->{ACTION_TYPE} eq 'o') {
1257 die "Should have handled ACTION_TYPE o earlier";
1259 die "Invalid ACTION_TYPE";
1268 # Fold certain GSUB features into the cmap table
1270 my ($self, $features) = @_;
1272 my $font = $self->{font};
1273 my $table = $font->{GSUB};
1275 # Find the lookup IDs corresponding to the desired features
1277 my %wanted = (DEFAULT => 0);
1278 $wanted{$_} = 1 for @$features;
1281 for my $feat_tag (@{$table->{FEATURES}{FEAT_TAGS}}) {
1282 next if not $self->want_feature(\%wanted, $feat_tag);
1283 for (@{$table->{FEATURES}{$feat_tag}{LOOKUPS}}) {
1284 $lookups{$_} = $feat_tag;
1288 # Find the glyph mapping from those lookups
1290 my %glyph_map; # (old glyph id => new glyph id)
1292 for my $lookup_id (0..$#{$table->{LOOKUP}}) {
1293 next unless exists $lookups{$lookup_id};
1294 my $lookup = $table->{LOOKUP}[$lookup_id];
1295 if ($lookup->{TYPE} != 1) {
1296 warn "GSUB lookup $lookup_id (from feature '$lookups{$lookup_id}') is not a 'single' type lookup (type=$lookup->{TYPE}), and cannot be applied.\n";
1300 # For each glyph, only the first substitution per lookup is applied,
1301 # so we build a map of the firsts for this lookup (then fold it into
1302 # the global map later)
1303 my %lookup_glyph_map;
1305 for my $sub (@{$lookup->{SUB}}) {
1306 my @covs = $self->coverage_array($sub->{COVERAGE});
1307 if ($sub->{ACTION_TYPE} eq 'o') {
1308 my $adj = $sub->{ADJUST};
1309 if ($adj >= 32768) { $adj -= 65536 } # fix Font::TTF::Bug (http://rt.cpan.org/Ticket/Display.html?id=42727)
1310 for my $i (0..$#covs) {
1311 my $old = $covs[$i];
1312 my $new = $old + $adj;
1313 $lookup_glyph_map{$old} = $new if not exists $lookup_glyph_map{$old};
1315 } elsif ($sub->{ACTION_TYPE} eq 'g') {
1316 next if @covs == 0 and not $sub->{RULES};
1317 die unless @{$sub->{RULES}} == @covs;
1318 for my $i (0..$#covs) {
1319 my $old = $covs[$i];
1320 die unless @{$sub->{RULES}[$i]} == 1;
1321 die unless @{$sub->{RULES}[$i][0]{ACTION}} == 1;
1322 my $new = $sub->{RULES}[$i][0]{ACTION}[0];
1323 $lookup_glyph_map{$old} = $new;
1326 die "Invalid ACTION_TYPE $sub->{ACTION_TYPE}";
1330 # Fold the lookup's glyph map into the global glyph map
1331 for my $gid (keys %lookup_glyph_map) {
1332 # Add any new substitutions
1333 $glyph_map{$gid} = $lookup_glyph_map{$gid} if not exists $glyph_map{$gid};
1335 for my $gid (keys %glyph_map) {
1336 # Handle chained substitutions
1337 $glyph_map{$gid} = $lookup_glyph_map{$glyph_map{$gid}} if exists $lookup_glyph_map{$glyph_map{$gid}};
1341 # Apply the glyph mapping to cmap
1343 for my $table (@{$font->{cmap}{Tables}}) {
1344 for my $cp (keys %{$table->{val}}) {
1345 my $gid = $table->{val}{$cp};
1346 $table->{val}{$cp} = $glyph_map{$gid} if exists $glyph_map{$gid};
1353 my $font = $self->{font};
1355 for my $ppem (grep /^\d+$/, keys %{$font->{hdmx}}) {
1357 for my $gid (0..$font->{maxp}{numGlyphs}-1) {
1358 push @new_widths, $font->{hdmx}{$ppem}[$self->{glyph_id_new_to_old}{$gid}];
1360 $font->{hdmx}{$ppem} = \@new_widths;
1366 my $font = $self->{font};
1368 # We don't handle version 1 kern tables yet, so just drop them entirely.
1369 # http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6kern.html
1370 # https://bugzilla.mozilla.org/show_bug.cgi?id=487549
1371 if ($font->{kern}{Version} != 0) {
1372 warn "Unhandled kern table version $font->{kern}{Version} - deleting all kerning data\n";
1373 delete $font->{kern};
1377 for my $table (@{$font->{kern}{tables}}) {
1378 if ($table->{type} == 0) {
1380 for my $l (keys %{$table->{kern}}) {
1381 next unless $self->{wanted_glyphs}{$l};
1382 for my $r (keys %{$table->{kern}{$l}}) {
1383 next unless $self->{wanted_glyphs}{$r};
1384 $kern{$self->{glyph_id_old_to_new}{$l}}{$self->{glyph_id_old_to_new}{$r}} = $table->{kern}{$l}{$r};
1387 $table->{kern} = \%kern;
1388 } elsif ($table->{type} == 2) {
1389 die "kern table type 2 not supported yet";
1391 die "Invalid kern table type";
1398 my $font = $self->{font};
1401 for my $gid (0..$font->{maxp}{numGlyphs}-1) {
1402 push @glyphs, $font->{LTSH}{glyphs}[$self->{glyph_id_new_to_old}{$gid}];
1404 $font->{LTSH}{glyphs} = \@glyphs;
1407 sub delete_copyright {
1409 my $font = $self->{font};
1410 # XXX - shouldn't be deleting copyright text
1411 $font->{name}{strings}[0] = undef;
1412 $font->{name}{strings}[10] = undef;
1413 $font->{name}{strings}[13] = undef;
1417 my ($self, $uid) = @_;
1418 my $font = $self->{font};
1421 my $str = $font->{name}{strings}[$_];
1422 for my $plat (0..$#$str) {
1423 next unless $str->[$plat];
1424 for my $enc (0..$#{$str->[$plat]}) {
1425 next unless $str->[$plat][$enc];
1426 for my $lang (keys %{$str->[$plat][$enc]}) {
1427 next unless exists $str->[$plat][$enc]{$lang};
1428 $str->[$plat][$enc]{$lang} = "$uid - subset of " . $str->[$plat][$enc]{$lang};
1435 sub license_desc_subst {
1436 my ($self, $new) = @_;
1437 my $font = $self->{font};
1439 my $str = $font->{name}{strings}[13];
1440 for my $plat (0..$#$str) {
1441 next unless $str->[$plat];
1442 for my $enc (0..$#{$str->[$plat]}) {
1443 next unless $str->[$plat][$enc];
1444 for my $lang (keys %{$str->[$plat][$enc]}) {
1445 next unless exists $str->[$plat][$enc]{$lang};
1446 $str->[$plat][$enc]{$lang} =~ s/\$\{LICENSESUBST\}/$new/g;
1452 # IE silently rejects non-CFF fonts if the Font Family Name is not a prefix of
1453 # the Full Font Name. This can occur when automatically converting CFF fonts
1454 # to non-CFF fonts, so it's useful to check and fix it here.
1455 sub fix_full_font_name {
1456 my ($self, $new) = @_;
1457 my $font = $self->{font};
1459 my $str1 = $font->{name}{strings}[1];
1460 for my $plat (0..$#$str1) {
1461 next unless $str1->[$plat];
1462 for my $enc (0..$#{$str1->[$plat]}) {
1463 next unless $str1->[$plat][$enc];
1464 for my $lang (keys %{$str1->[$plat][$enc]}) {
1465 next unless exists $str1->[$plat][$enc]{$lang};
1466 my $name = $str1->[$plat][$enc]{$lang};
1467 my $fullname = $font->{name}{strings}[4][$plat][$enc]{$lang};
1468 if (substr($fullname, 0, length $name) ne $name) {
1469 warn "Full Name ('$fullname') does not start with Family Name ('$name') and will break in IE - fixing automatically\n";
1470 $font->{name}{strings}[4][$plat][$enc]{$lang} = $name;
1480 bless $self, $class;
1485 my ($self, $filename) = @_;
1486 my $font = Font::TTF::Font->open($filename) or die "Failed to open $filename: $!";
1487 $self->{font} = $font;
1492 my ($self, $filename, $chars, $options) = @_;
1494 $self->{features} = $options->{features};
1496 my $uid = substr(sha1_hex("$filename $chars"), 0, 16);
1498 if (not $self->{font}) {
1499 $self->preload($filename);
1502 my $font = $self->{font};
1504 $self->check_tables;
1506 $self->{num_glyphs_old} = $font->{maxp}{numGlyphs};
1508 $self->fold_gsub($options->{fold_features})
1509 if $options->{fold_features};
1511 my $fsType = $font->{'OS/2'}{fsType};
1512 warn "fsType is $fsType - subsetting and embedding might not be permitted by the license\n" if $fsType != 0;
1517 $self->find_codepoint_glyph_mappings;
1518 $self->find_wanted_glyphs($chars);
1519 $self->remove_unwanted_glyphs;
1525 # name: nothing to fix (though maybe could be optimised?)
1528 # cvt_: nothing to fix
1529 # fpgm: nothing to fix
1530 # glyf: just a stub, in Font::TTF
1532 # prep: nothing to fix
1535 $self->fix_gdef if $font->{GDEF};
1536 $self->fix_gpos if $font->{GPOS};
1537 $self->fix_gsub if $font->{GSUB};
1540 $self->fix_hdmx if $font->{hdmx};
1541 $self->fix_kern if $font->{kern};
1542 $self->fix_ltsh if $font->{LTSH};
1544 $self->fix_maxp; # Must come after loca, prep, fpgm
1545 $self->fix_os_2; # Must come after cmap, hmtx, hhea, GPOS, GSUB
1547 $self->fix_full_font_name;
1549 $self->change_name($uid);
1551 $self->license_desc_subst($options->{license_desc_subst})
1552 if defined $options->{license_desc_subst};
1554 $self->{num_glyphs_new} = $font->{maxp}{numGlyphs};
1557 sub num_glyphs_old {
1559 return $self->{num_glyphs_old};
1562 sub num_glyphs_new {
1564 return $self->{num_glyphs_new};
1569 my $font = $self->{font};
1570 if (@{$font->{post}{VAL}}) {
1571 return @{$font->{post}{VAL}};
1573 my $n = $#{$font->{loca}{glyphs}};
1574 return join ' ', map { chr($_) =~ /[a-zA-Z0-9- \|]/ ? "'".chr($_)."'" : sprintf 'U+%04x', $_ } map { keys %{$self->{glyphs}{$_}} }
1575 map $self->{glyph_id_new_to_old}{$_}, 0..$n;
1578 sub feature_status {
1580 my $font = $self->{font};
1583 for my $table (grep defined, $font->{GPOS}, $font->{GSUB}) {
1584 for my $feature (@{$table->{FEATURES}{FEAT_TAGS}}) {
1585 $feature =~ /^(\w{4})( _\d+)?$/ or die "Unrecognised feature tag syntax '$feature'";
1587 next if $feats{$tag}++;
1595 my ($self, $fh) = @_;
1596 my $font = $self->{font};
1597 $font->out($fh) or die $!;
1602 my $font = $self->{font};