File Coverage

blib/lib/Font/TTF/OpenTypeLigatures.pm
Criterion Covered Total %
statement 41 41 100.0
branch 12 12 100.0
condition 8 9 88.8
subroutine 9 9 100.0
pod 2 2 100.0
total 72 73 98.6


line stmt bran cond sub pod time code
1             package Font::TTF::OpenTypeLigatures;
2 2     2   43797 use Carp qw/croak/;
  2         5  
  2         141  
3 2     2   1984 use Font::TTF::Font;
  2         224223  
  2         77  
4 2     2   17 use warnings;
  2         10  
  2         49  
5 2     2   10 use strict;
  2         2  
  2         103  
6              
7             =head1 NAME
8              
9             Font::TTF::OpenTypeLigatures - Transforms OpenType glyphs based on GSUB tables
10              
11             =head1 VERSION
12              
13             Version 0.01
14              
15             =cut
16              
17             our $VERSION = '0.01';
18              
19              
20             =head1 SYNOPSIS
21              
22             use Font::TTF::OpenTypeLigatures;
23              
24             my $foo = Font::TTF::OpenTypeLigatures->new($fontfile, %options);
25             @glyph_ids = $foo->substitute(@glyph_ids);
26             ...
27              
28             =head1 DESCRIPTION
29              
30             This module is a building block for fine typography systems implemented
31             in Perl. It reads the GSUB table of OpenType fonts to transform glyphs
32             based on selected OpenType features. The most common use of this is to
33             implement ligatures, but OpenType supports a variety of features such as
34             alternates, old-style numbers, non-Roman contextual substitutions and so
35             on.
36              
37             =head1 METHODS
38              
39             =head2 new
40              
41             The constructor takes a font file path and a set of options. The options
42             will determine which substitutions are performed. The default options
43             will substitute ligatures in Latin-script texts. You may supply:
44              
45             =over 3
46              
47             =item script
48              
49             Four-letter code for the script in which your text is written. (See
50             http://www.microsoft.com/typography/developers/opentype/scripttags.aspx
51             for a list of these.)
52              
53             =item lang
54              
55             Three-letter language tag. If this is not given, or there are no special
56             features for this language, the default language for the script is used.
57              
58             =item features
59              
60             This is a I matching the features you want to
61             support. The default is C.
62              
63             =back
64              
65             If there are any problems, the constructor will die with an error
66             message.
67              
68             =cut
69              
70 2     2   2671 use Memoize;
  2         5888  
  2         2193  
71             sub new {
72             my ($class, $ff, %options) = @_;
73             my $self = bless { }, $class;
74             my $script = (lc $options{script}) || "latn";
75             my $wanted = $options{features} || "liga";
76             my $lang = sprintf "%3s ", uc $options{lang};
77             my $f = $self->{ff} = Font::TTF::Font->open($ff) or croak "Couldn't open font file";
78             $f->read;
79             $f->{GSUB}->read;
80             my $languages = $f->{GSUB}{SCRIPTS}{$script};
81             if (!$languages) { croak "Font doesn't support script '$script'" }
82             my $features = ($languages->{uc $options{lang}} || $languages->{DEFAULT})->{FEATURES};
83             return $self unless $features;
84             my @ligs =
85             grep { $_->{TYPE} == 4 } # XXX Contextual substitutions only for now
86             map { $f->{GSUB}{LOOKUP}[$_] }
87             map { @{ $f->{GSUB}{FEATURES}{$_}{LOOKUPS} } }
88             grep /$wanted/, @$features;
89             my %ligtable;
90             for my $lig (@ligs) {
91             for (@{$lig->{SUB}}) {
92             while (my ($k, $v) = each %{$_->{COVERAGE}{val}}) {
93             for (@{$_->{RULES}[$v]}) {
94             my $target = \%ligtable;
95             my $final = pop @{$_->{MATCH}};
96             for ($k, @{$_->{MATCH}}) {
97             $target->{$_} ||= {};
98             $target = $target->{$_};
99             }
100             $target->{$final}{FINAL} = join(",",@{$_->{ACTION}});
101             }
102             }
103             }
104             }
105             $self->{ligtable} = \%ligtable;
106             return $self;
107             }
108             memoize("new");
109              
110             =head2 substitute
111              
112             This performs contextual substitution on a list of numeric glyph IDs,
113             returning a substituted list.
114              
115             =cut
116              
117             sub substitute {
118 5     5 1 4287 my ($self, @list) = @_;
119 5         7 my @output;
120 5         8 push @list, -1;
121 5     10   26 my $s = $self->stream(sub { push @output, @_ });
  10         28  
122 5         16 $s->($_) for @list;
123 5         30 return @output;
124             }
125              
126             =head2 stream
127              
128             my $substitutor = $self->stream( \&output );
129             for (@glyphids) { $substitutor->($_) }
130              
131             This creates a stateful closure subroutine which acts as a
132             glyph-by-glyph substitution stream. Once a substitution is processed, or
133             no substitution is needed for the glyph ID stream, the closure calls the
134             provided output subroutine.
135              
136             This allows you to interpose the stream in between an input and output
137             mechanism, and not worry about maintaining ligature substitution state
138             yourself.
139              
140             Passing -1 to the substitutor drains the stream.
141              
142             =cut
143              
144             sub stream {
145 5     5 1 8 my ($self, $outputsub) = @_;
146 5         8 my $state = {};
147 5         5 my $closure;
148             $closure = sub {
149 26     26   31 my $input = shift;
150             # Despatch simple case
151 26 100 100     146 $outputsub->($input),return if !%$state and !$self->{ligtable}{$input}
      100        
152             and $input != -1;
153              
154 22   66     73 $state->{target} ||= $self->{ligtable};
155 22 100       35 push @{$state->{list}}, $input unless $input == -1;
  16         34  
156 22 100       59 if (defined $state->{target}{$input}) {
    100          
157 11         36 $state->{target} = $state->{target}{$input};
158             # And wait for next thing
159             } elsif ( $state->{target}{FINAL} ) {
160             # This swallows everything apart from current input
161 4         9 $outputsub->($state->{target}{FINAL});
162 4         5 $state = {};
163 4         19 $closure->($input);
164             } else {
165             # Output one and unwind the stack
166 7 100       9 my @list = @{$state->{list} || []};
  7         27  
167 7         12 $state = {};
168 7 100       21 $outputsub->(shift @list) if @list;
169 7         24 $closure->($_) for @list;
170             }
171             }
172 5         32 }
173              
174             =head1 AUTHOR
175              
176             Simon Cozens, C<< >>
177              
178             =head1 BUGS
179              
180             Please report any bugs or feature requests to C, or through
181             the web interface at L. I will be notified, and then you'll
182             automatically be notified of progress on your bug as I make changes.
183              
184              
185              
186              
187             =head1 SUPPORT
188              
189             You can find documentation for this module with the perldoc command.
190              
191             perldoc Font::TTF::OpenTypeLigatures
192              
193              
194             You can also look for information at:
195              
196             =over 4
197              
198             =item * RT: CPAN's request tracker
199              
200             L
201              
202             =item * AnnoCPAN: Annotated CPAN documentation
203              
204             L
205              
206             =item * CPAN Ratings
207              
208             L
209              
210             =item * Search CPAN
211              
212             L
213              
214             =back
215              
216              
217             =head1 ACKNOWLEDGEMENTS
218              
219              
220             =head1 COPYRIGHT & LICENSE
221              
222             Copyright 2011 Simon Cozens.
223              
224             This program is released under the following license: Perl
225              
226              
227             =cut
228              
229             1; # End of Font::TTF::OpenTypeLigatures