PageRenderTime 62ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Font/Subsetter.pm

http://github.com/gonzoua/book-tools
Perl | 1606 lines | 1124 code | 171 blank | 311 comment | 283 complexity | c42fa9383a1b3113e3e19382fe194449 MD5 | raw file
  1. # Copyright (c) 2009 Philip Taylor
  2. #
  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
  10. # conditions:
  11. #
  12. # The above copyright notice and this permission notice shall be
  13. # included in all copies or substantial portions of the Software.
  14. #
  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.
  23. package Font::Subsetter;
  24. use strict;
  25. use warnings;
  26. use Carp;
  27. use Unicode::Normalize();
  28. use Digest::SHA qw(sha1_hex);
  29. use Encode;
  30. use Font::TTF;
  31. use Font::TTF::Font;
  32. if ($Font::TTF::VERSION =~ /^0\.([0-3].|4[0-5])$/) {
  33. 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>.";
  34. }
  35. # Tables can be:
  36. # REQUIRED - will fail if it's not present
  37. # FORBIDDEN - will fail if it's present
  38. # OPTIONAL - will be accepted regardless of whether it's there or not
  39. # IGNORED - like OPTIONAL, but no processing will take place
  40. # UNFINISHED - will emit a warning if it's present, because the code doesn't handle it properly yet
  41. # DROP - will be deleted from the font
  42. # The default for unmentioned tables is FORBIDDEN
  43. my %font_tables = (
  44. 'cmap' => ['REQUIRED'],
  45. 'head' => ['REQUIRED'],
  46. 'hhea' => ['REQUIRED'],
  47. 'hmtx' => ['REQUIRED'],
  48. 'maxp' => ['REQUIRED'],
  49. 'name' => ['REQUIRED'],
  50. 'OS/2' => ['REQUIRED'],
  51. 'post' => ['REQUIRED'],
  52. # TrueType outlines:
  53. 'cvt ' => ['IGNORED'],
  54. 'fpgm' => ['IGNORED'],
  55. 'glyf' => ['IGNORED'],
  56. 'loca' => ['OPTIONAL'],
  57. 'prep' => ['OPTIONAL'],
  58. # PostScript outlines: (TODO: support these?)
  59. 'CFF ' => ['FORBIDDEN'],
  60. 'VORG' => ['FORBIDDEN'],
  61. # Bitmap glyphs: (TODO: support these?)
  62. 'EBDT' => ['DROP', 'embedded bitmap glyphs will be lost'],
  63. 'EBLC' => ['DROP', 'embedded bitmap glyphs will be lost'],
  64. 'EBSC' => ['DROP', 'embedded bitmap glyphs will be lost'],
  65. # Advanced typographic tables:
  66. 'BASE' => ['UNFINISHED'],
  67. 'GDEF' => ['OPTIONAL'],
  68. 'GPOS' => ['OPTIONAL'],
  69. 'GSUB' => ['OPTIONAL'],
  70. 'JSTF' => ['UNFINISHED'],
  71. # OpenType tables:
  72. 'DSIG' => ['DROP'], # digital signature - don't need it here
  73. 'gasp' => ['IGNORED'],
  74. 'hdmx' => ['OPTIONAL'],
  75. 'kern' => ['OPTIONAL'],
  76. 'LTSH' => ['OPTIONAL'],
  77. 'PCLT' => ['UNFINISHED'],
  78. 'VDMX' => ['IGNORED'],
  79. 'vhea' => ['UNFINISHED'],
  80. 'vmtx' => ['UNFINISHED'],
  81. # SIL Graphite tables:
  82. 'Feat' => ['DROP'],
  83. 'Silf' => ['DROP'],
  84. 'Sill' => ['DROP'],
  85. 'Silt' => ['DROP'],
  86. 'Glat' => ['DROP'],
  87. 'Gloc' => ['DROP'],
  88. # FontForge tables:
  89. 'PfEd' => ['DROP'],
  90. 'FFTM' => ['DROP'],
  91. # Apple Advanced Typography tables:
  92. # (These get dropped because it's better to use cross-platform features instead)
  93. 'feat' => ['DROP'],
  94. 'morx' => ['DROP'],
  95. 'prop' => ['DROP'],
  96. # Undocumented(?) extension for some kind of maths stuff
  97. 'MATH' => ['DROP'],
  98. );
  99. sub check_tables {
  100. my ($self) = @_;
  101. my $font = $self->{font};
  102. my @tables = grep /^[^ ]...$/, sort keys %$font;
  103. for (@tables) {
  104. my $t = $font_tables{$_};
  105. if (not $t) {
  106. die "Uses unrecognised table '$_'\n";
  107. } else {
  108. my $status = $t->[0];
  109. if ($status eq 'FORBIDDEN') {
  110. die "Uses forbidden table '$_'\n";
  111. } elsif ($status eq 'UNFINISHED') {
  112. warn "Uses unhandled table '$_'\n";
  113. } elsif ($status eq 'DROP') {
  114. my $note = ($t->[1] ? ' - '.$t->[1] : '');
  115. warn "Dropping table '$_'$note\n";
  116. delete $font->{$_};
  117. } elsif ($status eq 'OPTIONAL') {
  118. } elsif ($status eq 'IGNORED') {
  119. } elsif ($status eq 'REQUIRED') {
  120. } else {
  121. die "Invalid table status $status";
  122. }
  123. }
  124. }
  125. # TODO: check required tables are present
  126. # TODO: check TrueType or PostScript tables are present
  127. }
  128. sub read_tables {
  129. my ($self) = @_;
  130. my $font = $self->{font};
  131. # Read all the tables that will be needed in the future.
  132. # (In particular, read them before modifying numGlyphs,
  133. # beacuse they often depend on that value.)
  134. for (qw(
  135. cmap hmtx name OS/2 post
  136. glyf loca
  137. BASE GDEF GPOS GSUB JSTF
  138. hdmx kern LTSH
  139. )) {
  140. $font->{$_}->read if $font->{$_};
  141. }
  142. }
  143. sub find_codepoint_glyph_mappings {
  144. my ($self) = @_;
  145. my $font = $self->{font};
  146. # Find the glyph->codepoint mappings
  147. my %glyphs;
  148. for my $table (@{$font->{cmap}{Tables}}) {
  149. for my $cp (keys %{$table->{val}}) {
  150. my $ucp; # Unicode code point
  151. if ($table->{Platform} == 0 # Unicode
  152. or ($table->{Platform} == 3 and # Windows
  153. ($table->{Encoding} == 1 or # Unicode BMP
  154. $table->{Encoding} == 10)) # Unicode full
  155. ) {
  156. $ucp = $cp;
  157. } elsif ($table->{Platform} == 1 # Mac
  158. and $table->{Encoding} == 0) # Roman
  159. {
  160. $ucp = ord(decode('MacRoman', pack C => $cp));
  161. } else {
  162. # This table might not map directly onto Unicode codepoints,
  163. # so warn about it
  164. warn "Unrecognised cmap table type (platform $table->{Platform}, encoding $table->{Encoding}) - ignoring its character/glyph mappings\n";
  165. next;
  166. }
  167. my $g = $table->{val}{$cp}; # glyph id
  168. $glyphs{$g}{$ucp} = 1;
  169. }
  170. }
  171. $self->{glyphs} = \%glyphs;
  172. }
  173. sub expand_wanted_chars {
  174. my ($self, $chars) = @_;
  175. # OS X browsers (via ATSUI?) appear to convert text into
  176. # NFC before rendering it.
  177. # So input like "i{combining grave}" is converted to "{i grave}"
  178. # before it's even passed to the font's substitution tables.
  179. # So if @chars contains i and {combining grave}, then we have to
  180. # add {i grave} because that might get used.
  181. #
  182. # So... Include all the unchanged characters. Also include the NFC
  183. # of each character. Then use NormalizationData to add any characters
  184. # that can result from NFCing a string of the wanted characters.
  185. if (0) { # change to 1 to disable all this fancy stuff
  186. my %cs = map { ord $_ => 1 } split '', $chars;
  187. return %cs;
  188. }
  189. my %cs = map { ord $_ => 1, ord Unicode::Normalize::NFC($_) => 1 } split '', $chars;
  190. require Font::Subsetter::NormalizationData;
  191. my %new_cs;
  192. for my $c (@Font::Subsetter::NormalizationData::data) {
  193. # Skip this if we've already got the composed character
  194. next if $cs{$c->[0]};
  195. # Skip this if we don't have all the decomposed characters
  196. next if grep !$cs{$_}, @{$c}[1..$#$c];
  197. # Otherwise we want the composed character
  198. $new_cs{$c->[0]} = 1;
  199. }
  200. $cs{$_} = 1 for keys %new_cs;
  201. return %cs;
  202. }
  203. sub want_feature {
  204. my ($self, $wanted, $feature) = @_;
  205. # If no feature list was specified, accept all features
  206. return 1 if not $wanted;
  207. # Otherwise find the four-character tag
  208. $feature =~ /^(\w{4})( _\d+)?$/ or die "Unrecognised feature tag syntax '$feature'";
  209. return $wanted->{$1} if exists $wanted->{$1};
  210. return $wanted->{DEFAULT} if exists $wanted->{DEFAULT};
  211. return 1;
  212. }
  213. sub find_wanted_lookup_ids {
  214. my ($self, $table) = @_;
  215. # If we wanted to include all lookups:
  216. # return 0..$#{$table->{LOOKUP}};
  217. # but actually we only want ones used by wanted features
  218. my %lookups;
  219. for my $feat_tag (@{$table->{FEATURES}{FEAT_TAGS}}) {
  220. next if not $self->want_feature($self->{features}, $feat_tag);
  221. for (@{$table->{FEATURES}{$feat_tag}{LOOKUPS}}) {
  222. $lookups{$_} = 1;
  223. }
  224. }
  225. # Iteratively add any chained lookups
  226. my $changed = 1;
  227. while ($changed) {
  228. $changed = 0;
  229. for my $lookup_id (0..$#{$table->{LOOKUP}}) {
  230. next unless $lookups{$lookup_id};
  231. my $lookup = $table->{LOOKUP}[$lookup_id];
  232. for my $sub (@{$lookup->{SUB}}) {
  233. if ($sub->{ACTION_TYPE} eq 'l') {
  234. for my $rule (@{$sub->{RULES}}) {
  235. for my $chain (@$rule) {
  236. for my $action (@{$chain->{ACTION}}) {
  237. for (0..@$action/2-1) {
  238. # action is array of (offset, lookup)
  239. $changed = 1 if not $lookups{$action->[$_*2+1]};
  240. $lookups{$action->[$_*2+1]} = 1;
  241. }
  242. }
  243. }
  244. }
  245. }
  246. }
  247. }
  248. }
  249. my @keys = sort { $a <=> $b } keys %lookups;
  250. return @keys;
  251. }
  252. sub find_wanted_glyphs {
  253. my ($self, $chars) = @_;
  254. my $font = $self->{font};
  255. my %wanted_chars = $self->expand_wanted_chars($chars);
  256. $self->{wanted_glyphs} = {};
  257. # http://www.microsoft.com/typography/otspec/recom.htm suggests that fonts
  258. # should include .notdef, .null, CR, space; so include them all here, if they
  259. # are already defined
  260. if ($font->{post}{VAL}) {
  261. for my $gid (0..$#{$font->{loca}{glyphs}}) {
  262. my $name = $font->{post}{VAL}[$gid];
  263. if ($name and ($name eq '.notdef' or $name eq '.null' or $name eq 'CR' or $name eq 'space')) {
  264. $self->{wanted_glyphs}{$gid} = 1;
  265. }
  266. }
  267. } else {
  268. # If post.FormatType == 3 then we don't have any glyph names
  269. # so just assume it's the first four
  270. $self->{wanted_glyphs}{$_} = 1 for 0..3;
  271. }
  272. # We want any glyphs used directly by any characters we want
  273. for my $gid (keys %{$self->{glyphs}}) {
  274. for my $cp (keys %{$self->{glyphs}{$gid}}) {
  275. $self->{wanted_glyphs}{$gid} = 1 if $wanted_chars{$cp};
  276. }
  277. }
  278. # Iteratively find new glyphs, until convergence
  279. my @newly_wanted_glyphs = keys %{$self->{wanted_glyphs}};
  280. while (@newly_wanted_glyphs) {
  281. my @new_glyphs;
  282. if ($font->{GSUB}) {
  283. # Handle ligatures and similar things
  284. # (e.g. if we want 'f' and 'i', we want the 'fi' ligature too)
  285. # (NOTE: a lot of this code is duplicating the form of
  286. # fix_gsub, so they ought to be kept roughly in sync)
  287. #
  288. # TODO: There's probably loads of bugs in here, so it
  289. # should be checked and tested more
  290. for my $lookup_id ($self->find_wanted_lookup_ids($font->{GSUB})) {
  291. my $lookup = $font->{GSUB}{LOOKUP}[$lookup_id];
  292. for my $sub (@{$lookup->{SUB}}) {
  293. # Handle the glyph-delta case
  294. if ($sub->{ACTION_TYPE} eq 'o') {
  295. my $adj = $sub->{ADJUST};
  296. if ($adj >= 32768) { $adj -= 65536 } # fix Font::TTF::Bug (http://rt.cpan.org/Ticket/Display.html?id=42727)
  297. my @covs = $self->coverage_array($sub->{COVERAGE});
  298. for (@covs) {
  299. # If we want the coveraged glyph, we also want
  300. # that glyph plus delta
  301. if ($self->{wanted_glyphs}{$_}) {
  302. my $new = $_ + $adj;
  303. next if $self->{wanted_glyphs}{$new};
  304. push @new_glyphs, $new;
  305. $self->{wanted_glyphs}{$new} = 1;
  306. }
  307. }
  308. next;
  309. }
  310. # Collect the rules which might match initially something
  311. my @rulesets;
  312. if ($sub->{RULES}) {
  313. if (($lookup->{TYPE} == 5 or $lookup->{TYPE} == 6)
  314. and $sub->{FORMAT} == 2) {
  315. # RULES corresponds to class values
  316. # TODO: ought to filter this by classes that contain wanted glyphs
  317. push @rulesets, @{$sub->{RULES}};
  318. } elsif (($lookup->{TYPE} == 5 or $lookup->{TYPE} == 6)
  319. and $sub->{FORMAT} == 3) {
  320. # COVERAGE is empty; accept all the RULEs, and
  321. # we'll look inside their MATCHes later
  322. push @rulesets, @{$sub->{RULES}};
  323. } else {
  324. # COVERAGE lists glyphs, and there's a RULE for
  325. # each, so extract the RULEs for wanted COVERAGE
  326. # values
  327. my @covs = $self->coverage_array($sub->{COVERAGE});
  328. die unless @{$sub->{RULES}} == @covs;
  329. for my $i (0..$#covs) {
  330. if ($self->{wanted_glyphs}{$covs[$i]}) {
  331. push @rulesets, $sub->{RULES}[$i];
  332. }
  333. }
  334. }
  335. }
  336. # Collect the rules whose MATCH matches
  337. my @rules;
  338. RULE: for my $rule (map @$_, @rulesets) {
  339. if (not defined $sub->{MATCH_TYPE}) {
  340. # No extra matching other than COVERAGE,
  341. # so just accept this rule
  342. } elsif ($sub->{MATCH_TYPE} eq 'g') {
  343. # RULES->MATCH/PRE/POST are arrays of glyphs that must all match
  344. for my $c (qw(MATCH PRE POST)) {
  345. next unless $rule->{$c};
  346. next RULE if grep { not $self->{wanted_glyphs}{$_} } @{$rule->{$c}};
  347. }
  348. } elsif ($sub->{MATCH_TYPE} eq 'o') {
  349. # RULES->MATCH/PRE/POST are arrays of coverage tables,
  350. # and at least one glyph from each table must match
  351. die unless @{$sub->{RULES}} == 1;
  352. die unless @{$sub->{RULES}[0]} == 1;
  353. for my $c (qw(MATCH PRE POST)) {
  354. next unless $sub->{RULES}[0][0]{$c};
  355. for (@{$sub->{RULES}[0][0]{$c}}) {
  356. my $matched = 0;
  357. for (keys %{$_->{val}}) {
  358. if ($self->{wanted_glyphs}{$_}) {
  359. $matched = 1;
  360. last;
  361. }
  362. }
  363. next RULE if not $matched;
  364. }
  365. }
  366. } elsif ($sub->{MATCH_TYPE} eq 'c') {
  367. # TODO: only includes rules using classes that contain
  368. # wanted glyphs.
  369. # For now, just conservatively accept everything.
  370. } else {
  371. die "Invalid MATCH_TYPE";
  372. }
  373. push @rules, $rule;
  374. }
  375. # Find the glyphs in the relevant actions
  376. for my $rule (@rules) {
  377. if ($sub->{ACTION_TYPE} eq 'g') {
  378. die unless $rule->{ACTION};
  379. for my $new (@{$rule->{ACTION}}) {
  380. next if $self->{wanted_glyphs}{$new};
  381. push @new_glyphs, $new;
  382. $self->{wanted_glyphs}{$new} = 1;
  383. # warn "adding $new";
  384. }
  385. } elsif ($sub->{ACTION_TYPE} eq 'l') {
  386. # do nothing - this is just a lookup to run some other rules
  387. } elsif ($sub->{ACTION_TYPE} eq 'a') {
  388. # do nothing - we don't want the alternative glyphs
  389. } else {
  390. die "Invalid ACTION_TYPE";
  391. }
  392. }
  393. }
  394. }
  395. }
  396. @newly_wanted_glyphs = @new_glyphs;
  397. }
  398. # Now we want to add glyphs that are used for composite rendering,
  399. # which don't participate in any GSUB behaviour
  400. @newly_wanted_glyphs = keys %{$self->{wanted_glyphs}};
  401. while (@newly_wanted_glyphs) {
  402. my @new_glyphs;
  403. if ($font->{loca}) {
  404. # If we want a composite glyph, we want all of its
  405. # component glyphs too
  406. # (e.g. &aacute; is the 'a' glyph plus the acute glyph):
  407. for my $gid (@newly_wanted_glyphs) {
  408. my $glyph = $font->{loca}{glyphs}[$gid];
  409. next unless $glyph;
  410. $glyph->read;
  411. next unless $glyph->{numberOfContours} == -1;
  412. $glyph->read_dat;
  413. for (@{$glyph->{comps}}) {
  414. next if $self->{wanted_glyphs}{$_->{glyph}};
  415. push @new_glyphs, $_->{glyph};
  416. $self->{wanted_glyphs}{$_->{glyph}} = 1;
  417. }
  418. $glyph->update;
  419. }
  420. }
  421. @newly_wanted_glyphs = @new_glyphs;
  422. }
  423. }
  424. sub update_classdef_table {
  425. my ($self, $table) = @_;
  426. die "Expected table" if not $table;
  427. die "Expected classdef" if $table->{cover};
  428. my @vals;
  429. for my $gid (keys %{$table->{val}}) {
  430. next if not $self->{wanted_glyphs}{$gid};
  431. my $v = $table->{val}{$gid};
  432. push @vals, $self->{glyph_id_old_to_new}{$gid}, $v;
  433. }
  434. my $ret = new Font::TTF::Coverage(0, @vals);
  435. # Font::TTF bug (http://rt.cpan.org/Ticket/Display.html?id=42716):
  436. # 'max' is not set by new(), so do it manually:
  437. my $max = 0;
  438. for (values %{$ret->{val}}) { $max = $_ if $_ > $max }
  439. $ret->{max} = $max;
  440. return $ret;
  441. }
  442. # Returns a map such that map[old_class_value] = new_class_value
  443. # (or undef if the class is removed)
  444. # This differs from update_classdef_table in that it can
  445. # reorder and optimise the class ids
  446. sub update_mapped_classdef_table {
  447. my ($self, $table) = @_;
  448. die "Expected table" if not $table;
  449. die "Expected classdef" if $table->{cover};
  450. my @vals;
  451. my %used_classes;
  452. $used_classes{0} = 1; # 0 is implicitly in every classdef
  453. for my $gid (keys %{$table->{val}}) {
  454. next if not $self->{wanted_glyphs}{$gid};
  455. my $v = $table->{val}{$gid};
  456. push @vals, $self->{glyph_id_old_to_new}{$gid}, $v;
  457. $used_classes{$v} = 1;
  458. }
  459. my @map_new_to_old = sort { $a <=> $b } keys %used_classes;
  460. my @map_old_to_new;
  461. $map_old_to_new[$map_new_to_old[$_]] = $_ for 0..$#map_new_to_old;
  462. # Update the class numbers
  463. for (0..@vals/2-1) {
  464. $vals[$_*2+1] = $map_old_to_new[$vals[$_*2+1]];
  465. }
  466. my $ret = new Font::TTF::Coverage(0, @vals);
  467. # Font::TTF bug (http://rt.cpan.org/Ticket/Display.html?id=42716):
  468. # 'max' is not set by new(), so do it manually:
  469. my $max = 0;
  470. for (values %{$ret->{val}}) { $max = $_ if $_ > $max }
  471. $ret->{max} = $max;
  472. return ($ret, \@map_old_to_new, \@map_new_to_old);
  473. }
  474. # Removes unwanted glyphs from a coverage table, for
  475. # cases where nobody else is referring to indexes in this table
  476. sub update_coverage_table {
  477. my ($self, $table) = @_;
  478. die "Expected table" if not $table;
  479. die "Expected cover" if not $table->{cover};
  480. my @vals = keys %{$table->{val}};
  481. @vals = grep $self->{wanted_glyphs}{$_}, @vals;
  482. @vals = sort { $a <=> $b } @vals;
  483. @vals = map $self->{glyph_id_old_to_new}{$_}, @vals;
  484. return new Font::TTF::Coverage(1, @vals);
  485. }
  486. # Returns a map such that map[new_coverage_index] = old_coverage_index
  487. sub update_mapped_coverage_table {
  488. my ($self, $table) = @_;
  489. die "Expected table" if not $table;
  490. die "Expected coverage" if not $table->{cover};
  491. my @map;
  492. my @new_vals;
  493. # Get the covered values (in order)
  494. my @vals = $self->coverage_array($table);
  495. for my $i (0..$#vals) {
  496. # Create a new list of all the wanted values
  497. if ($self->{wanted_glyphs}{$vals[$i]}) {
  498. push @new_vals, $self->{glyph_id_old_to_new}{$vals[$i]};
  499. push @map, $i;
  500. }
  501. }
  502. return (new Font::TTF::Coverage(1, @new_vals), @map);
  503. }
  504. sub coverage_array {
  505. my ($self, $table) = @_;
  506. Carp::confess "Expected table" if not $table;
  507. return sort { $table->{val}{$a} <=> $table->{val}{$b} } keys %{$table->{val}};
  508. }
  509. sub empty_coverage {
  510. my ($self, $table) = @_;
  511. Carp::confess "Expected table" if not $table;
  512. return 1 if not $table->{val};
  513. return 1 if not keys %{$table->{val}};
  514. return 0;
  515. }
  516. # Update the loca table to delete unwanted glyphs.
  517. # Must be called before all the other fix_* methods.
  518. sub remove_unwanted_glyphs {
  519. my ($self) = @_;
  520. my $font = $self->{font};
  521. return unless $font->{loca};
  522. my %glyph_id_old_to_new;
  523. my %glyph_id_new_to_old;
  524. my $glyphs = $font->{loca}{glyphs};
  525. my @new_glyphs;
  526. for my $i (0..$#$glyphs) {
  527. if ($self->{wanted_glyphs}{$i}) {
  528. push @new_glyphs, $glyphs->[$i];
  529. $glyph_id_old_to_new{$i} = $#new_glyphs;
  530. $glyph_id_new_to_old{$#new_glyphs} = $i;
  531. }
  532. }
  533. $font->{loca}{glyphs} = \@new_glyphs;
  534. $font->{maxp}{numGlyphs} = scalar @new_glyphs;
  535. $self->{glyph_id_old_to_new} = \%glyph_id_old_to_new;
  536. $self->{glyph_id_new_to_old} = \%glyph_id_new_to_old;
  537. }
  538. # Only the platform=3 encoding=1 cmap is really needed
  539. # (for Windows, OS X, Linux), so save space (and potentially
  540. # enhance cross-platformness) by stripping out all the others.
  541. # (But keep platform=3 encoding=10 too, for UCS-4 characters.)
  542. # (And Opera 10 on OS X wants one with platform=0 too.)
  543. sub strip_cmap {
  544. my ($self) = @_;
  545. my $font = $self->{font};
  546. if (not grep { $_->{Platform} == 3 and $_->{Encoding} == 1 } @{$font->{cmap}{Tables}}) {
  547. warn "No cmap found with platform=3 encoding=1 - the font is likely to not work on Windows.\n";
  548. # Stop now, instead of stripping out all of the cmap tables
  549. return;
  550. }
  551. my @matched_tables = grep {
  552. ($_->{Platform} == 3 and ($_->{Encoding} == 1 || $_->{Encoding} == 10))
  553. or ($_->{Platform} == 0)
  554. } @{$font->{cmap}{Tables}};
  555. $font->{cmap}{Tables} = \@matched_tables;
  556. }
  557. # Only the platform=3 encoding=1 names are really needed
  558. # (for Windows, OS X, Linux), so save space (and potentially
  559. # enhance cross-platformness) by stripping out all the others.
  560. sub strip_name {
  561. my ($self) = @_;
  562. my $font = $self->{font};
  563. for my $id (0..$#{$font->{name}{strings}}) {
  564. my $str = $font->{name}{strings}[$id];
  565. next if not $str;
  566. my $plat = 3;
  567. my $enc = 1;
  568. my $langs = $str->[$plat][$enc];
  569. if (not $langs) {
  570. warn "No name found with id=$id with platform=3 encoding=1 - the font is likely to not work on Windows.\n"
  571. unless $id == 18; # warn except for some Mac-specific names
  572. return;
  573. }
  574. $font->{name}{strings}[$id] = [];
  575. $font->{name}{strings}[$id][$plat][$enc] = $langs;
  576. # NOTE: this keeps all the languages for each string, which is
  577. # potentially wasteful if there are lots (but in practice most fonts
  578. # seem to only have English)
  579. }
  580. }
  581. sub fix_cmap {
  582. my ($self) = @_;
  583. my $font = $self->{font};
  584. # Delete mappings for unwanted glyphs
  585. for my $table (@{$font->{cmap}{Tables}}) {
  586. # (Already warned about unrecognised table types
  587. # in find_codepoint_glyph_mappings)
  588. my %new_vals;
  589. for my $cp (keys %{$table->{val}}) {
  590. my $gid = $table->{val}{$cp};
  591. if ($self->{wanted_glyphs}{$gid}) {
  592. $new_vals{$cp} = $self->{glyph_id_old_to_new}{$gid};
  593. }
  594. }
  595. $table->{val} = \%new_vals;
  596. if ($table->{Format} == 0) {
  597. @{$table->{val}}{0..255} = map { defined($_) ? $_ : 0 } @{$table->{val}}{0..255};
  598. }
  599. }
  600. }
  601. sub fix_head {
  602. # TODO: Should think about:
  603. # created
  604. # modified
  605. # xMin (depends on glyph data)
  606. # yMin (depends on glyph data)
  607. # xMax (depends on glyph data)
  608. # yMax (depends on glyph data)
  609. }
  610. sub fix_hhea {
  611. # TODO: Should think about:
  612. # advanceWidthMax (depends on hmtx)
  613. # minLeftSideBearing (depends on hmtx)
  614. # minRightSideBearing (depends on hmtx)
  615. # xMaxExtent (depends on hmtx)
  616. }
  617. sub fix_hmtx {
  618. my ($self) = @_;
  619. my $font = $self->{font};
  620. # Map the advance/lsb arrays from old to new glyph ids
  621. my @new_advances;
  622. my @new_lsbs;
  623. for my $gid (0..$font->{maxp}{numGlyphs}-1) {
  624. push @new_advances, $font->{hmtx}{advance}[$self->{glyph_id_new_to_old}{$gid}];
  625. push @new_lsbs, $font->{hmtx}{lsb}[$self->{glyph_id_new_to_old}{$gid}];
  626. }
  627. $font->{hmtx}{advance} = \@new_advances;
  628. $font->{hmtx}{lsb} = \@new_lsbs;
  629. }
  630. sub fix_maxp { # Must come after loca, prep, fpgm
  631. my ($self) = @_;
  632. my $font = $self->{font};
  633. # Update some of the 'max' values that Font::TTF
  634. # is capable of updating
  635. $font->{maxp}->update;
  636. }
  637. sub fix_os_2 { # Must come after cmap, hmtx, hhea, GPOS, GSUB
  638. my ($self) = @_;
  639. my $font = $self->{font};
  640. # Update some of the metric values that Font::TTF
  641. # is capable of updating
  642. $font->{'OS/2'}->update;
  643. if ($font->{'OS/2'}{Version} >= 2) {
  644. # TODO: handle cases where these are non-default
  645. warn "Unexpected defaultChar $font->{'OS/2'}{defaultChar}\n"
  646. unless $font->{'OS/2'}{defaultChar} == 0;
  647. warn "Unexpected breakChar $font->{'OS/2'}{breakChar}\n"
  648. unless $font->{'OS/2'}{breakChar} == 0x20;
  649. }
  650. }
  651. sub fix_post {
  652. my ($self) = @_;
  653. my $font = $self->{font};
  654. if ($font->{post}{FormatType} == 0) {
  655. warn "Invalid 'post' table type. (If you're using the obfuscate-font.pl script, make sure it comes *after* the subsetting.)\n";
  656. }
  657. # Update PostScript name mappings for new glyph ids
  658. if ($font->{post}{VAL}) {
  659. my @new_vals;
  660. for my $gid (0..$font->{maxp}{numGlyphs}-1) {
  661. push @new_vals, $font->{post}{VAL}[$self->{glyph_id_new_to_old}{$gid}];
  662. }
  663. $font->{post}{VAL} = \@new_vals;
  664. }
  665. }
  666. sub fix_loca {
  667. my ($self) = @_;
  668. my $font = $self->{font};
  669. # remove_unwanted_glyphs has already removed some
  670. # of the glyph data from this table
  671. # Update references inside composite glyphs
  672. for my $glyph (@{$font->{loca}{glyphs}}) {
  673. next unless $glyph;
  674. $glyph->read;
  675. next unless $glyph->{numberOfContours} == -1;
  676. $glyph->read_dat;
  677. for (@{$glyph->{comps}}) {
  678. # (find_unwanted_glyphs guarantees that the
  679. # component glyphs will be present)
  680. $_->{glyph} = $self->{glyph_id_old_to_new}{$_->{glyph}};
  681. }
  682. }
  683. }
  684. sub fix_gdef {
  685. my ($self) = @_;
  686. my $font = $self->{font};
  687. if ($font->{GDEF}{GLYPH}) {
  688. $font->{GDEF}{GLYPH} = $self->update_classdef_table($font->{GDEF}{GLYPH});
  689. if ($self->empty_coverage($font->{GDEF}{GLYPH})) {
  690. delete $font->{GDEF}{GLYPH};
  691. }
  692. }
  693. if ($font->{GDEF}{MARKS}) {
  694. $font->{GDEF}{MARKS} = $self->update_classdef_table($font->{GDEF}{MARKS});
  695. if ($self->empty_coverage($font->{GDEF}{MARKS})) {
  696. delete $font->{GDEF}{MARKS};
  697. }
  698. }
  699. if ($font->{GDEF}{ATTACH}) {
  700. die "TODO" if $font->{GDEF}{ATTACH}{POINTS};
  701. $font->{GDEF}{ATTACH}{COVERAGE} = $self->update_coverage_table($font->{GDEF}{ATTACH}{COVERAGE});
  702. if ($self->empty_coverage($font->{GDEF}{ATTACH}{COVERAGE})) {
  703. delete $font->{GDEF}{ATTACH};
  704. }
  705. }
  706. if ($font->{GDEF}{LIG}) {
  707. if ($font->{GDEF}{LIG}{LIGS}) {
  708. die "GDEF LIG LIGS != COVERAGE" if
  709. @{$font->{GDEF}{LIG}{LIGS}} != keys %{$font->{GDEF}{LIG}{COVERAGE}{val}};
  710. my @coverage_map;
  711. ($font->{GDEF}{LIG}{COVERAGE}, @coverage_map) = $self->update_mapped_coverage_table($font->{GDEF}{LIG}{COVERAGE});
  712. $font->{GDEF}{LIG}{LIGS} = [ map $font->{GDEF}{LIG}{LIGS}[$_], @coverage_map ];
  713. } else {
  714. $font->{GDEF}{LIG}{COVERAGE} = $self->update_coverage_table($font->{GDEF}{LIG}{COVERAGE});
  715. }
  716. if ($self->empty_coverage($font->{GDEF}{LIG}{COVERAGE})) {
  717. delete $font->{GDEF}{LIG};
  718. }
  719. }
  720. }
  721. sub fix_ttopen {
  722. my ($self, $table, $inner) = @_;
  723. my @lookups;
  724. my %lookup_map;
  725. for my $lookup_id ($self->find_wanted_lookup_ids($table)) {
  726. my $lookup = $table->{LOOKUP}[$lookup_id];
  727. my @subtables;
  728. for my $sub (@{$lookup->{SUB}}) {
  729. if ($inner->($lookup, $sub)) {
  730. push @subtables, $sub;
  731. }
  732. }
  733. # Only keep lookups that have some subtables
  734. if (@subtables) {
  735. $lookup->{SUB} = \@subtables;
  736. push @lookups, $lookup;
  737. $lookup_map{$lookup_id} = $#lookups;
  738. }
  739. }
  740. $table->{LOOKUP} = \@lookups;
  741. # Update lookup references inside actions
  742. for my $lookup (@{$table->{LOOKUP}}) {
  743. for my $sub (@{$lookup->{SUB}}) {
  744. if ($sub->{ACTION_TYPE} eq 'l') {
  745. for my $rule (@{$sub->{RULES}}) {
  746. for my $chain (@$rule) {
  747. my @actions;
  748. for my $action (@{$chain->{ACTION}}) {
  749. my @steps;
  750. for (0..@$action/2-1) {
  751. # action is array of (offset, lookup)
  752. # so just update the lookup
  753. if (exists $lookup_map{$action->[$_*2+1]}) {
  754. push @steps, ($action->[$_*2], $lookup_map{$action->[$_*2+1]});
  755. }
  756. }
  757. push @actions, \@steps;
  758. }
  759. $chain->{ACTION} = \@actions;
  760. }
  761. }
  762. }
  763. }
  764. }
  765. # Remove all features that are not wanted
  766. # and update all references to those features (in the languages list),
  767. # and update the features' lookup references
  768. my @features; # array of [tag, feature]
  769. my %kept_features;
  770. for my $feat_tag (@{$table->{FEATURES}{FEAT_TAGS}}) {
  771. next unless $self->want_feature($self->{features}, $feat_tag); # drop unwanted features
  772. my $feat = $table->{FEATURES}{$feat_tag};
  773. $feat->{LOOKUPS} = [ map { exists $lookup_map{$_} ? ($lookup_map{$_}) : () } @{$feat->{LOOKUPS}} ];
  774. next unless @{$feat->{LOOKUPS}}; # drop empty features to save some space
  775. push @features, [ $feat_tag, $feat ];
  776. $kept_features{$feat_tag} = 1;
  777. }
  778. $table->{FEATURES} = {
  779. FEAT_TAGS => [map $_->[0], @features],
  780. map +($_->[0] => $_->[1]), @features,
  781. };
  782. # Remove any references from scripts to features that no longer exist
  783. for my $script_tag (keys %{$table->{SCRIPTS}}) {
  784. my $script = $table->{SCRIPTS}{$script_tag};
  785. for my $tag ('DEFAULT', @{$script->{LANG_TAGS}}) {
  786. next if $script->{$tag}{' REFTAG'}; # ignore langs that are just copies of another
  787. $script->{$tag}{FEATURES} = [
  788. grep $kept_features{$_}, @{$script->{$tag}{FEATURES}}
  789. ];
  790. }
  791. }
  792. # TODO: it'd be nice to delete languages that have no features
  793. }
  794. sub fix_gpos {
  795. my ($self) = @_;
  796. my $font = $self->{font};
  797. $self->fix_ttopen($font->{GPOS},
  798. sub {
  799. my ($lookup, $sub) = @_;
  800. # There's always a COVERAGE here first.
  801. # (If it's empty, the client will skip the entire subtable,
  802. # so we could delete it entirely, but that would involve updating
  803. # the FEATURES->*->LOOKUPS lists too, so don't do that yet.)
  804. #
  805. # The rest depends on Type:
  806. #
  807. # Lookup Type 1 (Single Adjustment Positioning Subtable):
  808. # Format 1: Just COVERAGE, applies same value to all
  809. # Format 2: Just COVERAGE, RULES[n] gives value for each
  810. #
  811. # Lookup Type 2 (Pair Adjustment Positioning Subtable):
  812. # Format 1: COVERAGE gives first glyph, RULES[n][m]{MATCH}[0] gives second glyph
  813. # Format 2: COVERAGE gives first glyph, CLASS gives first glyph class, MATCH[0] gives second glyph class
  814. #
  815. # Lookup Type 3 (Cursive Attachment Positioning Subtable):
  816. # Format 1: Just COVERAGE, RULES[n] gives value for each
  817. #
  818. # Lookup Type 4 (MarkToBase Attachment Positioning Subtable):
  819. # Format 1: MATCH[0] gives mark coverage, COVERAGE gives base coverage, MARKS[n] per mark, RULES[n] per base
  820. #
  821. # Lookup Type 5 (MarkToLigature Attachment Positioning Subtable):
  822. # Format 1: pretty much the same as 4, but s/base/ligature/
  823. #
  824. # Lookup Type 6 (MarkToMark Attachment Positioning Subtable):
  825. # Format 1: pretty much the same as 4, but s/base/mark/
  826. #
  827. # Lookup Type 7 (Contextual Positioning Subtables):
  828. # Format 1: COVERAGE gives first glyph, RULES[n][m]{MATCH}[o] gives next glyphs
  829. # Format 2: COVERAGE gives first glyph, CLASS gives classes to glyphs, RULES[n] is per class
  830. # Format 3: COVERAGE absent, RULES[0][0]{MATCH}[o] gives glyph coverages
  831. #
  832. # Lookup Type 8 (Chaining Contextual Positioning Subtable):
  833. # Format 1: COVERAGE gives first glyph, RULES[n][m]{PRE/MATCH/POST} give context glyphs
  834. # Format 2: COVERAGE gives first glyph, PRE_CLASS/CLASS/POST_CLASS give classes
  835. # Format 3: COVERAGE absent, RULES[0][0]{PRE/MATCH/POST}[o] give coverages
  836. #
  837. # Lookup Type 9 (Extension Positioning):
  838. # Not supported
  839. die if $lookup->{TYPE} >= 9;
  840. # Update the COVERAGE table, and remember some mapping
  841. # information to update things that refer to the table
  842. my @coverage_map;
  843. my $old_coverage_count;
  844. if ($sub->{COVERAGE}) {
  845. $old_coverage_count = scalar keys %{$sub->{COVERAGE}{val}};
  846. ($sub->{COVERAGE}, @coverage_map) = $self->update_mapped_coverage_table($sub->{COVERAGE});
  847. # If there's no coverage left, then drop this subtable
  848. return 0 if $self->empty_coverage($sub->{COVERAGE});
  849. }
  850. if ($sub->{RULES} and $sub->{COVERAGE} and not
  851. # Skip cases where RULES is indexed by CLASS, not COVERAGE
  852. (($lookup->{TYPE} == 2 or
  853. $lookup->{TYPE} == 7 or
  854. $lookup->{TYPE} == 8)
  855. and $sub->{FORMAT} == 2)
  856. ) {
  857. # There's a RULES array per COVERAGE entry, so
  858. # shuffle them around to match the new COVERAGE
  859. if (@{$sub->{RULES}} != $old_coverage_count) {
  860. die "Internal error: RULES ($sub->{RULES}) does not match COVERAGE ($sub->{COVERAGE}) -- "
  861. . @{$sub->{RULES}} . " vs $old_coverage_count.";
  862. }
  863. $sub->{RULES} = [ map $sub->{RULES}[$_], @coverage_map ];
  864. }
  865. if (not defined $sub->{MATCH_TYPE} or $sub->{MATCH_TYPE} eq 'g') {
  866. if ($sub->{MATCH}) {
  867. die unless @{$sub->{MATCH}} == 1;
  868. die unless $sub->{MARKS};
  869. die unless @{$sub->{MARKS}} == keys %{$sub->{MATCH}[0]{val}};
  870. my @match_map;
  871. ($sub->{MATCH}[0], @match_map) = $self->update_mapped_coverage_table($sub->{MATCH}[0]);
  872. # If there's no coverage left, then drop this subtable
  873. return 0 if $self->empty_coverage($sub->{MATCH}[0]);
  874. # Update MARKS to correspond to the new MATCH coverage
  875. $sub->{MARKS} = [ map $sub->{MARKS}[$_], @match_map ];
  876. }
  877. # RULES->MATCH is an array of glyphs, so translate them all
  878. for (@{$sub->{RULES}}) {
  879. for (@$_) {
  880. $_->{MATCH} = [ map $self->{glyph_id_old_to_new}{$_},
  881. grep $self->{wanted_glyphs}{$_}, @{$_->{MATCH}} ];
  882. }
  883. }
  884. } elsif ($sub->{MATCH_TYPE}) {
  885. if ($sub->{MATCH_TYPE} eq 'o') {
  886. # RULES->MATCH/PRE/POST are arrays of coverage tables, so translate them all
  887. die unless @{$sub->{RULES}} == 1;
  888. die unless @{$sub->{RULES}[0]} == 1;
  889. my $r = $sub->{RULES}[0][0];
  890. for my $c (qw(MATCH PRE POST)) {
  891. $r->{$c} = [ map $self->update_coverage_table($_), @{$r->{$c}} ] if $r->{$c};
  892. }
  893. } elsif ($sub->{MATCH_TYPE} eq 'c') {
  894. die "Didn't expect any rule matches" if grep $_->{MATCH}, map @$_, @{$sub->{RULES}};
  895. die unless @{$sub->{MATCH}} == 1;
  896. my $class_map;
  897. ($sub->{CLASS}, undef, $class_map) = $self->update_mapped_classdef_table($sub->{CLASS});
  898. # Special case: If this results in an empty CLASS, it'll
  899. # break in FF3.5 on Linux, so assign all the COVERAGE glyphs onto
  900. # class 1 and update $class_map appropriately
  901. if ($sub->{CLASS}{max} == 0) {
  902. $sub->{CLASS} = new Font::TTF::Coverage(0, map +($_ => 1), keys %{$sub->{COVERAGE}{val}});
  903. $class_map = [0, 0]; # just duplicate class 0 into class 1 (this is a bit inefficient)
  904. }
  905. $sub->{RULES} = [ @{$sub->{RULES}}[@$class_map] ];
  906. # Update the MATCH classdef table
  907. my $match_map;
  908. ($sub->{MATCH}[0], undef, $match_map) = $self->update_mapped_classdef_table($sub->{MATCH}[0]);
  909. # If the MATCH table is now empty, drop this lookup
  910. # (else FF3.5 on Linux drops the GPOS table entirely)
  911. return 0 if @$match_map <= 1;
  912. # RULES[n] is a list of substitutions per MATCH class, so
  913. # update all those lists for the new classdef
  914. $sub->{RULES} = [ map { [ @{$_}[@$match_map] ] } @{$sub->{RULES}} ];
  915. } else {
  916. die "Invalid MATCH_TYPE";
  917. }
  918. }
  919. if (($lookup->{TYPE} == 7 or
  920. $lookup->{TYPE} == 8)
  921. and $sub->{FORMAT} == 2) {
  922. # Update some class tables
  923. for my $c (qw(CLASS PRE_CLASS POST_CLASS)) {
  924. $sub->{$c} = $self->update_classdef_table($sub->{$c}) if $sub->{$c};
  925. }
  926. }
  927. return 1;
  928. }
  929. );
  930. }
  931. sub fix_gsub {
  932. my ($self) = @_;
  933. my $font = $self->{font};
  934. $self->fix_ttopen($font->{GSUB},
  935. sub {
  936. my ($lookup, $sub) = @_;
  937. # There's always a COVERAGE here first.
  938. # (If it's empty, the client will skip the entire subtable,
  939. # so we could delete it entirely, but that would involve updating
  940. # the FEATURES->*->LOOKUPS lists and Contextual subtable indexes
  941. # too, so don't do that yet.)
  942. #
  943. # The rest depends on Type:
  944. #
  945. # Lookup Type 1 (Single Substitution Subtable):
  946. # Format 1: Just COVERAGE, and ADJUST gives glyph id delta
  947. # Format 2: Just COVERAGE, then RULES[n]{ACTION}[0] gives replacement glyph for each
  948. #
  949. # Lookup Type 2 (Multiple Substitution Subtable):
  950. # Format 1: Just COVERAGE, then RULES[n]{ACTION} gives replacement glyphs (must be at least 1)
  951. #
  952. # Lookup Type 3 (Alternate Substitution Subtable):
  953. # Format 1: Just COVERAGE, then RULES[n]{ACTION} gives alternate glyphs
  954. # [This can just be deleted since we have no way to use those glyphs]
  955. #
  956. # Lookup Type 4 (Ligature Substitution Subtable):
  957. # Format 1: COVERAGE gives first glyph, RULES[n]{MATCH}[m] gives next glyphs to match, RULES[n]{ACTION}[0] gives replacement glyph
  958. #
  959. # Lookup Type 5 (Contextual Substitution Subtable):
  960. # Format *: like type 7 in GPOS, but ACTION gives indexes into GSUB{LOOKUP}
  961. #
  962. # Lookup Type 6 (Chaining Contextual Substitution Subtable):
  963. # Format *: like type 8 in GPOS, but ACTION gives indexes into GSUB{LOOKUP}
  964. #
  965. # Lookup Type 7 (Extension Substitution):
  966. # Blah
  967. die if $lookup->{TYPE} >= 7;
  968. # Update the COVERAGE table, and remember some mapping
  969. # information to update things that refer to the table
  970. my @coverage_map;
  971. my $old_coverage_count;
  972. if ($sub->{COVERAGE}) {
  973. $old_coverage_count = scalar keys %{$sub->{COVERAGE}{val}};
  974. ($sub->{COVERAGE}, @coverage_map) = $self->update_mapped_coverage_table($sub->{COVERAGE});
  975. # If there's no coverage left, then drop this subtable
  976. return 0 if $self->empty_coverage($sub->{COVERAGE});
  977. }
  978. if ($sub->{ACTION_TYPE} eq 'o') {;
  979. my $adj = $sub->{ADJUST};
  980. if ($adj >= 32768) { $adj -= 65536 } # fix Font::TTF::Bug (http://rt.cpan.org/Ticket/Display.html?id=42727)
  981. my @covs = $self->coverage_array($sub->{COVERAGE});
  982. if (@covs == 0) {
  983. # Nothing's covered, but deleting this whole subtable is
  984. # non-trivial so just zero it out
  985. $sub->{ADJUST} = 0;
  986. } elsif (@covs == 1) {
  987. my $gid_base = $covs[0];
  988. my $old_gid_base = $self->{glyph_id_new_to_old}{$gid_base};
  989. my $old_gid = $old_gid_base + $adj;
  990. $sub->{ADJUST} = $self->{glyph_id_old_to_new}{$old_gid} - $gid_base;
  991. } else {
  992. # The glyphs are probably all reordered, so we can't just
  993. # adjust ADJUST.
  994. # So switch this to a format 2 table:
  995. $sub->{FORMAT} = 2;
  996. $sub->{ACTION_TYPE} = 'g';
  997. delete $sub->{ADJUST};
  998. my @gids;
  999. for (@covs) {
  1000. push @gids, $self->{glyph_id_old_to_new}{$self->{glyph_id_new_to_old}{$_} + $adj};
  1001. }
  1002. $sub->{RULES} = [ map [{ACTION => [$_]}], @gids ];
  1003. }
  1004. # Stop and keep this table, since done everything that's needed
  1005. return 1;
  1006. }
  1007. die if $sub->{ADJUST};
  1008. if ($sub->{RULES} and not
  1009. # Skip cases where RULES is indexed by CLASS, not COVERAGE,
  1010. # and cases where there's no COVERAGE at all
  1011. (($lookup->{TYPE} == 5 or $lookup->{TYPE} == 6)
  1012. and ($sub->{FORMAT} == 2 or $sub->{FORMAT} == 3))
  1013. ) {
  1014. # There's a RULES array per COVERAGE entry, so
  1015. # shuffle them around to match the new COVERAGE
  1016. die unless @{$sub->{RULES}} == $old_coverage_count;
  1017. $sub->{RULES} = [ map $sub->{RULES}[$_], @coverage_map ];
  1018. }
  1019. # TODO: refactor
  1020. if ($sub->{MATCH_TYPE}) {
  1021. # Fix all the glyph indexes
  1022. if ($sub->{MATCH_TYPE} eq 'g') {
  1023. # RULES->MATCH/PRE/POST are arrays of glyphs, so translate them all,
  1024. # and if they rely on any unwanted glyphs then drop the rule entirely
  1025. for my $i (0..$#{$sub->{RULES}}) {
  1026. my $ruleset = $sub->{RULES}[$i];
  1027. my @rules;
  1028. RULE: for my $rule (@$ruleset) {
  1029. for my $c (qw(MATCH PRE POST)) {
  1030. next unless $rule->{$c};
  1031. next RULE if grep { not $self->{wanted_glyphs}{$_} } @{$rule->{$c}};
  1032. $rule->{$c} = [ map $self->{glyph_id_old_to_new}{$_}, @{$rule->{$c}} ]
  1033. }
  1034. push @rules, $rule;
  1035. }
  1036. if (not @rules) {
  1037. # XXX: This is a really horrid hack.
  1038. # The proper solution is to delete the ruleset,
  1039. # and adjust COVERAGE to match.
  1040. push @rules, { ACTION => [0], MATCH => [-1] };
  1041. }
  1042. $sub->{RULES}[$i] = \@rules;
  1043. }
  1044. } elsif ($sub->{MATCH_TYPE} eq 'o') {
  1045. # RULES->MATCH/PRE/POST are arrays of coverage tables, so translate them all
  1046. die unless @{$sub->{RULES}} == 1;
  1047. die unless @{$sub->{RULES}[0]} == 1;
  1048. my $r = $sub->{RULES}[0][0];
  1049. for my $c (qw(MATCH PRE POST)) {
  1050. $r->{$c} = [ map $self->update_coverage_table($_), @{$r->{$c}} ] if $r->{$c};
  1051. }
  1052. } elsif ($sub->{MATCH_TYPE} eq 'c') {
  1053. # RULES refers to class values, which haven't changed at all,
  1054. # so we don't need to update those values
  1055. } else {
  1056. die "Invalid MATCH_TYPE";
  1057. }
  1058. }
  1059. my %class_maps;
  1060. for my $c (qw(CLASS PRE_CLASS POST_CLASS)) {
  1061. ($sub->{$c}, $class_maps{$c}) = $self->update_mapped_classdef_table($sub->{$c}) if $sub->{$c};
  1062. }
  1063. if ($sub->{MATCH_TYPE} and $sub->{MATCH_TYPE} eq 'c') {
  1064. # To make things work in Pango, we need to change all the
  1065. # class numbers so there aren't gaps:
  1066. my %classes = (
  1067. MATCH => 'CLASS',
  1068. PRE => 'PRE_CLASS',
  1069. POST => 'POST_CLASS',
  1070. );
  1071. my @rules;
  1072. for my $rule (@{$sub->{RULES}}) {
  1073. my @chains;
  1074. CHAIN: for my $chain (@$rule) {
  1075. for my $c (qw(MATCH PRE POST)) {
  1076. next unless $chain->{$c};
  1077. my $map = $class_maps{$classes{$c}} or die "Got a $c but no $classes{$c}";
  1078. # If any of the values are for a class that no longer has
  1079. # any entries, we should drop this whole chain because
  1080. # there's no chance it's going to match
  1081. next CHAIN if grep { not defined $map->[$_] } @{$chain->{$c}};
  1082. # Otherwise just update the class numbers
  1083. $chain->{$c} = [ map $map->[$_], @{$chain->{$c}} ];
  1084. }
  1085. push @chains, $chain;
  1086. }
  1087. push @rules, \@chains;
  1088. }
  1089. $sub->{RULES} = \@rules;
  1090. # If all the rules are empty, drop this whole subtable (which maybe is
  1091. # needed to avoid https://bugzilla.mozilla.org/show_bug.cgi?id=475242 ?)
  1092. return 0 if not grep @$_, @{$sub->{RULES}};
  1093. }
  1094. if ($sub->{ACTION_TYPE}) {
  1095. if ($sub->{ACTION_TYPE} eq 'g') {
  1096. for (@{$sub->{RULES}}) {
  1097. for (@$_) {
  1098. $_->{ACTION} = [ map $self->{glyph_id_old_to_new}{$_},
  1099. grep $self->{wanted_glyphs}{$_}, @{$_->{ACTION}} ];
  1100. }
  1101. }
  1102. } elsif ($sub->{ACTION_TYPE} eq 'l') {
  1103. # nothing to change here
  1104. } elsif ($sub->{ACTION_TYPE} eq 'a') {
  1105. # We don't want to bother with alternate glyphs at all,
  1106. # so just delete everything.
  1107. # (We need to have empty rules, and can't just delete them
  1108. # entirely, else FontTools becomes unhappy.)
  1109. # (TODO: Maybe we do want alternate glyphs?
  1110. # If so, be sure to update find_wanted_glyphs too)
  1111. for (@{$sub->{RULES}}) {
  1112. for (@$_) {
  1113. $_->{ACTION} = [];
  1114. }
  1115. }
  1116. } elsif ($sub->{ACTION_TYPE} eq 'o') {
  1117. die "Should have handled ACTION_TYPE o earlier";
  1118. } else {
  1119. die "Invalid ACTION_TYPE";
  1120. }
  1121. }
  1122. return 1;
  1123. }
  1124. );
  1125. }
  1126. # Fold certain GSUB features into the cmap table
  1127. sub fold_gsub {
  1128. my ($self, $features) = @_;
  1129. my $font = $self->{font};
  1130. my $table = $font->{GSUB};
  1131. # Find the lookup IDs corresponding to the desired features
  1132. my %wanted = (DEFAULT => 0);
  1133. $wanted{$_} = 1 for @$features;
  1134. my %lookups;
  1135. for my $feat_tag (@{$table->{FEATURES}{FEAT_TAGS}}) {
  1136. next if not $self->want_feature(\%wanted, $feat_tag);
  1137. for (@{$table->{FEATURES}{$feat_tag}{LOOKUPS}}) {
  1138. $lookups{$_} = $feat_tag;
  1139. }
  1140. }
  1141. # Find the glyph mapping from those lookups
  1142. my %glyph_map; # (old glyph id => new glyph id)
  1143. for my $lookup_id (0..$#{$table->{LOOKUP}}) {
  1144. next unless exists $lookups{$lookup_id};
  1145. my $lookup = $table->{LOOKUP}[$lookup_id];
  1146. if ($lookup->{TYPE} != 1) {
  1147. warn "GSUB lookup $lookup_id (from feature '$lookups{$lookup_id}') is not a 'single' type lookup (type=$lookup->{TYPE}), and cannot be applied.\n";
  1148. next;
  1149. }
  1150. # For each glyph, only the first substitution per lookup is applied,
  1151. # so we build a map of the firsts for this lookup (then fold it into
  1152. # the global map later)
  1153. my %lookup_glyph_map;
  1154. for my $sub (@{$lookup->{SUB}}) {
  1155. my @covs = $self->coverage_array($sub->{COVERAGE});
  1156. if ($sub->{ACTION_TYPE} eq 'o') {
  1157. my $adj = $sub->{ADJUST};
  1158. if ($adj >= 32768) { $adj -= 65536 } # fix Font::TTF::Bug (http://rt.cpan.org/Ticket/Display.html?id=42727)
  1159. for my $i (0..$#covs) {
  1160. my $old = $covs[$i];
  1161. my $new = $old + $adj;
  1162. $lookup_glyph_map{$old} = $new if not exists $lookup_glyph_map{$old};
  1163. }
  1164. } elsif ($sub->{ACTION_TYPE} eq 'g') {
  1165. next if @covs == 0 and not $sub->{RULES};
  1166. die unless @{$sub->{RULES}} == @covs;
  1167. for my $i (0..$#covs) {
  1168. my $old = $covs[$i];
  1169. die unless @{$sub->{RULES}[$i]} == 1;
  1170. die unless @{$sub->{RULES}[$i][0]{ACTION}} == 1;
  1171. my $new = $sub->{RULES}[$i][0]{ACTION}[0];
  1172. $lookup_glyph_map{$old} = $new;
  1173. }
  1174. } else {
  1175. die "Invalid ACTION_TYPE $sub->{ACTION_TYPE}";
  1176. }
  1177. }
  1178. # Fold the lookup's glyph map into the global glyph map
  1179. for my $gid (keys %lookup_glyph_map) {
  1180. # Add any new substitutions
  1181. $glyph_map{$gid} = $lookup_glyph_map{$gid} if not exists $glyph_map{$gid};
  1182. }
  1183. for my $gid (keys %glyph_map) {
  1184. # Handle chained substitutions
  1185. $glyph_map{$gid} = $lookup_glyph_map{$glyph_map{$gid}} if exists $lookup_glyph_map{$glyph_map{$gid}};
  1186. }
  1187. }
  1188. # Apply the glyph mapping to cmap
  1189. for my $table (@{$font->{cmap}{Tables}}) {
  1190. for my $cp (keys %{$table->{val}}) {
  1191. my $gid = $table->{val}{$cp};
  1192. $table->{val}{$cp} = $glyph_map{$gid} if exists $glyph_map{$gid};
  1193. }
  1194. }
  1195. }
  1196. sub fix_hdmx {
  1197. my ($self) = @_;
  1198. my $font = $self->{font};
  1199. for my $ppem (grep /^\d+$/, keys %{$font->{hdmx}}) {
  1200. my @new_widths;
  1201. for my $gid (0..$font->{maxp}{numGlyphs}-1) {
  1202. push @new_widths, $font->{hdmx}{$ppem}[$self->{glyph_id_new_to_old}{$gid}];
  1203. }
  1204. $font->{hdmx}{$ppem} = \@new_widths;
  1205. }
  1206. }
  1207. sub fix_kern {
  1208. my ($self) = @_;
  1209. my $font = $self->{font};
  1210. # We don't handle version 1 kern tables yet, so just drop them entirely.
  1211. # http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6kern.html
  1212. # https://bugzilla.mozilla.org/show_bug.cgi?id=487549
  1213. if ($font->{kern}{Version} != 0) {
  1214. warn "Unhandled kern table version $font->{kern}{Version} - deleting all kerning data\n";
  1215. delete $font->{kern};
  1216. return;
  1217. }
  1218. for my $table (@{$font->{kern}{tables}}) {
  1219. if ($table->{type} == 0) {
  1220. my %kern;
  1221. for my $l (keys %{$table->{kern}}) {
  1222. next unless $self->{wanted_glyphs}{$l};
  1223. for my $r (keys %{$table->{kern}{$l}}) {
  1224. next unless $self->{wanted_glyphs}{$r};
  1225. $kern{$self->{glyph_id_old_to_new}{$l}}{$self->{glyph_id_old_to_new}{$r}} = $table->{kern}{$l}{$r};
  1226. }
  1227. }
  1228. $table->{kern} = \%kern;
  1229. } elsif ($table->{type} == 2) {
  1230. die "kern table type 2 not supported yet";
  1231. } else {
  1232. die "Invalid kern table type";
  1233. }
  1234. }
  1235. }
  1236. sub fix_ltsh {
  1237. my ($self) = @_;
  1238. my $font = $self->{font};
  1239. my @glyphs;
  1240. for my $gid (0..$font->{maxp}{numGlyphs}-1) {
  1241. push @glyphs, $font->{LTSH}{glyphs}[$self->{glyph_id_new_to_old}{$gid}];
  1242. }
  1243. $font->{LTSH}{glyphs} = \@glyphs;
  1244. }
  1245. sub delete_copyright {
  1246. my ($self) = @_;
  1247. my $font = $self->{font};
  1248. # XXX - shouldn't be deleting copyright text
  1249. $font->{name}{strings}[0] = undef;
  1250. $font->{name}{strings}[10] = undef;
  1251. $font->{name}{strings}[13] = undef;
  1252. }
  1253. sub change_name {
  1254. my ($self, $uid) = @_;
  1255. my $font = $self->{font};
  1256. for (1,3,4,6) {
  1257. my $str = $font->{name}{strings}[$_];
  1258. for my $plat (0..$#$str) {
  1259. next unless $str->[$plat];
  1260. for my $enc (0..$#{$str->[$plat]}) {
  1261. next unless $str->[$plat][$enc];
  1262. for my $lang (keys %{$str->[$plat][$enc]}) {
  1263. next unless exists $str->[$plat][$enc]{$lang};
  1264. $str->[$plat][$enc]{$lang} = "$uid - subset of " . $str->[$plat][$enc]{$lang};
  1265. }
  1266. }
  1267. }
  1268. }
  1269. }
  1270. sub license_desc_subst {
  1271. my ($self, $new) = @_;
  1272. my $font = $self->{font};
  1273. my $str = $font->{name}{strings}[13];
  1274. for my $plat (0..$#$str) {
  1275. next unless $str->[$plat];
  1276. for my $enc (0..$#{$str->[$plat]}) {
  1277. next unless $str->[$plat][$enc];
  1278. for my $lang (keys %{$str->[$plat][$enc]}) {
  1279. next unless exists $str->[$plat][$enc]{$lang};
  1280. $str->[$plat][$enc]{$lang} =~ s/\$\{LICENSESUBST\}/$new/g;
  1281. }
  1282. }
  1283. }
  1284. }
  1285. # IE silently rejects non-CFF fonts if the Font Family Name is not a prefix of
  1286. # the Full Font Name. This can occur when automatically converting CFF fonts
  1287. # to non-CFF fonts, so it's useful to check and fix it here.
  1288. sub fix_full_font_name {
  1289. my ($self, $new) = @_;
  1290. my $font = $self->{font};
  1291. my $str1 = $font->{name}{strings}[1];
  1292. for my $plat (0..$#$str1) {
  1293. next unless $str1->[$plat];
  1294. for my $enc (0..$#{$str1->[$plat]}) {
  1295. next unless $str1->[$plat][$enc];
  1296. for my $lang (keys %{$str1->[$plat][$enc]}) {
  1297. next unless exists $str1->[$plat][$enc]{$lang};
  1298. my $name = $str1->[$plat][$enc]{$lang};
  1299. my $fullname = $font->{name}{strings}[4][$plat][$enc]{$lang};
  1300. if (substr($fullname, 0, length $name) ne $name) {
  1301. warn "Full Name ('$fullname') does not start with Family Name ('$name') and will break in IE - fixing automatically\n";
  1302. $font->{name}{strings}[4][$plat][$enc]{$lang} = $name;
  1303. }
  1304. }
  1305. }
  1306. }
  1307. }
  1308. sub new {
  1309. my $class = shift;
  1310. my $self = {};
  1311. bless $self, $class;
  1312. return $self;
  1313. }
  1314. sub preload {
  1315. my ($self, $filename) = @_;
  1316. my $font = Font::TTF::Font->open($filename) or die "Failed to open $filename: $!";
  1317. $self->{font} = $font;
  1318. $self->read_tables;
  1319. }
  1320. sub subset {
  1321. my ($self, $filename, $chars, $options) = @_;
  1322. $self->{features} = $options->{features};
  1323. my $uid = substr(sha1_hex("$filename $chars"), 0, 16);
  1324. if (not $self->{font}) {
  1325. $self->preload($filename);
  1326. }
  1327. my $font = $self->{font};
  1328. $self->check_tables;
  1329. $self->{num_glyphs_old} = $font->{maxp}{numGlyphs};
  1330. $self->fold_gsub($options->{fold_features})
  1331. if $options->{fold_features};
  1332. my $fsType = $font->{'OS/2'}{fsType};
  1333. warn "fsType is $fsType - subsetting and embedding might not be permitted by the license\n" if $fsType != 0;
  1334. $self->strip_cmap;
  1335. $self->strip_name;
  1336. $self->find_codepoint_glyph_mappings;
  1337. $self->find_wanted_glyphs($chars);
  1338. $self->remove_unwanted_glyphs;
  1339. $self->fix_cmap;
  1340. $self->fix_head;
  1341. $self->fix_hhea;
  1342. $self->fix_hmtx;
  1343. # name: nothing to fix (though maybe could be optimised?)
  1344. $self->fix_post;
  1345. # cvt_: nothing to fix
  1346. # fpgm: nothing to fix
  1347. # glyf: just a stub, in Font::TTF
  1348. $self->fix_loca;
  1349. # prep: nothing to fix
  1350. # BASE: TODO
  1351. $self->fix_gdef if $font->{GDEF};
  1352. $self->fix_gpos if $font->{GPOS};
  1353. $self->fix_gsub if $font->{GSUB};
  1354. # JSTF: TODO
  1355. $self->fix_hdmx if $font->{hdmx};
  1356. $self->fix_kern if $font->{kern};
  1357. $self->fix_ltsh if $font->{LTSH};
  1358. $self->fix_maxp; # Must come after loca, prep, fpgm
  1359. $self->fix_os_2; # Must come after cmap, hmtx, hhea, GPOS, GSUB
  1360. $self->fix_full_font_name;
  1361. $self->change_name($uid);
  1362. $self->license_desc_subst($options->{license_desc_subst})
  1363. if defined $options->{license_desc_subst};
  1364. $self->{num_glyphs_new} = $font->{maxp}{numGlyphs};
  1365. }
  1366. sub num_glyphs_old {
  1367. my ($self) = @_;
  1368. return $self->{num_glyphs_old};
  1369. }
  1370. sub num_glyphs_new {
  1371. my ($self) = @_;
  1372. return $self->{num_glyphs_new};
  1373. }
  1374. sub glyph_names {
  1375. my ($self) = @_;
  1376. my $font = $self->{font};
  1377. if (@{$font->{post}{VAL}}) {
  1378. return @{$font->{post}{VAL}};
  1379. }
  1380. my $n = $#{$font->{loca}{glyphs}};
  1381. return join ' ', map { chr($_) =~ /[a-zA-Z0-9- \|]/ ? "'".chr($_)."'" : sprintf 'U+%04x', $_ } map { keys %{$self->{glyphs}{$_}} }
  1382. map $self->{glyph_id_new_to_old}{$_}, 0..$n;
  1383. }
  1384. sub feature_status {
  1385. my ($self) = @_;
  1386. my $font = $self->{font};
  1387. my %feats;
  1388. my @feats;
  1389. for my $table (grep defined, $font->{GPOS}, $font->{GSUB}) {
  1390. for my $feature (@{$table->{FEATURES}{FEAT_TAGS}}) {
  1391. $feature =~ /^(\w{4})( _\d+)?$/ or die "Unrecognised feature tag syntax '$feature'";
  1392. my $tag = $1;
  1393. next if $feats{$tag}++;
  1394. push @feats, $tag;
  1395. }
  1396. }
  1397. return @feats;
  1398. }
  1399. sub write {
  1400. my ($self, $fh) = @_;
  1401. my $font = $self->{font};
  1402. $font->out($fh) or die $!;
  1403. }
  1404. sub release {
  1405. my ($self) = @_;
  1406. my $font = $self->{font};
  1407. $font->release;
  1408. }
  1409. 1;