1 package Font::TTF::OS_2;
5 Font::TTF::OS_2 - the OS/2 table in a TTF font
9 The OS/2 table has two versions and forms, one an extension of the other. This
10 module supports both forms and the switching between them.
12 =head1 INSTANCE VARIABLES
14 No other variables than those in table and those in the standard:
63 Notice that versions 0, 1, 2 & 3 of the table are supported. Notice also that the
64 Panose variable has been broken down into its elements.
71 use vars qw(@ISA @fields @lens @field_info @weights);
74 @ISA = qw(Font::TTF::Table);
76 'xAvgCharWidth' => 's',
77 'usWeightClass' => 'S',
78 'usWidthClass' => 'S',
80 'ySubscriptXSize' => 's',
81 'ySubScriptYSize' => 's',
82 'ySubscriptXOffset' => 's',
83 'ySubscriptYOffset' => 's',
84 'ySuperscriptXSize' => 's',
85 'ySuperscriptYSize' => 's',
86 'ySuperscriptXOffset' => 's',
87 'ySuperscriptYOffset' => 's',
88 'yStrikeoutSize' => 's',
89 'yStrikeoutPosition' => 's',
90 'sFamilyClass' => 's',
96 'bStrokeVariation' => 'C',
101 'ulUnicodeRange1' => 'L',
102 'ulUnicodeRange2' => 'L',
103 'ulUnicodeRange3' => 'L',
104 'ulUnicodeRange4' => 'L',
106 'fsSelection' => 'S',
107 'usFirstCharIndex' => 'S',
108 'usLastCharIndex' => 'S',
109 'sTypoAscender' => 'S',
110 'sTypoDescender' => 's',
111 'sTypoLineGap' => 'S',
112 'usWinAscent' => 'S',
113 'usWinDescent' => 'S',
115 'ulCodePageRange1' => 'L',
116 'ulCodePageRange2' => 'L',
120 'defaultChar' => 'S',
123 '' => '', # i.e. v3 is basically same as v2
126 @weights = qw(64 14 27 35 100 20 14 42 63 3 6 35 20 56 56 17 4 49 56 71 31 10 18 3 18 2 166);
128 use Font::TTF::Utils;
132 my ($k, $v, $c, $n, $i, $t, $j);
135 @lens = (76, 84, 94, 94);
136 for ($j = 0; $j < $#field_info; $j += 2)
138 if ($field_info[$j] eq '')
143 ($k, $v, $c) = TTF_Init_Fields($field_info[$j], $c, $field_info[$j+1]);
144 next unless defined $k && $k ne "";
145 for ($i = $n; $i < 4; $i++)
146 { $fields[$i]{$k} = $v; }
153 Reads in the various values from disk (see details of OS/2 table)
162 $self->SUPER::read or return $self;
164 init unless defined $fields[2]{'xAvgCharWidth'};
165 $self->{' INFILE'}->read($dat, 2);
166 $ver = unpack("n", $dat);
167 $self->{'Version'} = $ver;
170 $self->{' INFILE'}->read($dat, $lens[$ver]);
171 TTF_Read_Fields($self, $dat, $fields[$ver]);
179 Writes the table to a file either from memory or by copying.
185 my ($self, $fh) = @_;
188 return $self->SUPER::out($fh) unless $self->{' read'};
190 $ver = $self->{'Version'};
191 $fh->print(pack("n", $ver));
192 $fh->print(TTF_Out_Fields($self, $fields[$ver], $lens[$ver]));
197 =head2 $t->XML_element($context, $depth, $key, $value)
199 Tidies up the hex values to output them in hex
206 my ($context, $depth, $key, $value) = @_;
207 my ($fh) = $context->{'fh'};
209 if ($key =~ m/^ul(?:Unicode|CodePage)Range\d$/o)
210 { $fh->printf("%s<%s>%08X</%s>\n", $depth, $key, $value, $key); }
211 elsif ($key eq 'achVendID')
212 { $fh->printf("%s<%s name='%s'/>\n", $depth, $key, pack('N', $value)); }
214 { return $self->SUPER::XML_element(@_); }
219 =head2 $t->XML_end($context, $tag, %attrs)
221 Now handle them on the way back in
228 my ($context, $tag, %attrs) = @_;
230 if ($tag =~ m/^ul(?:Unicode|CodePage)Range\d$/o)
231 { return hex($context->{'text'}); }
232 elsif ($tag eq 'achVendID')
233 { return unpack('N', $attrs{'name'}); }
235 { return $self->SUPER::XML_end(@_); }
240 Updates the OS/2 table by getting information from other sources:
242 Updates the C<firstChar> and C<lastChar> values based on the MS table in the
245 Updates the sTypoAscender, sTypoDescender & sTypoLineGap to be the same values
246 as Ascender, Descender and Linegap from the hhea table (assuming it is dirty)
247 and also sets usWinAscent to be the sum of Ascender+Linegap and usWinDescent to
248 be the negative of Descender.
255 my ($map, @keys, $table, $i, $avg, $hmtx);
257 return undef unless ($self->SUPER::update);
259 $self->{' PARENT'}{'cmap'}->update;
260 $map = $self->{' PARENT'}{'cmap'}->find_ms || return undef;
261 $hmtx = $self->{' PARENT'}{'hmtx'}->read;
263 @keys = sort {$a <=> $b} grep {$_ < 0x10000} keys %{$map->{'val'}};
265 $self->{'usFirstCharIndex'} = $keys[0];
266 $self->{'usLastCharIndex'} = $keys[-1];
268 $table = $self->{' PARENT'}{'hhea'}->read;
270 # try any way we can to get some real numbers passed around!
271 if (($self->{'fsSelection'} & 128) != 0)
273 # assume the user knows what they are doing and has sensible values already
275 elsif ($table->{'Ascender'} != 0 || $table->{'Descender'} != 0)
277 $self->{'sTypoAscender'} = $table->{'Ascender'};
278 $self->{'sTypoDescender'} = $table->{'Descender'};
279 $self->{'sTypoLineGap'} = $table->{'LineGap'};
280 $self->{'usWinAscent'} = $self->{'sTypoAscender'} + $self->{'sTypoLineGap'};
281 $self->{'usWinDescent'} = -$self->{'sTypoDescender'};
283 elsif ($self->{'sTypoAscender'} != 0 || $self->{'sTypoDescender'} != 0)
285 $table->{'Ascender'} = $self->{'sTypoAscender'};
286 $table->{'Descender'} = $self->{'sTypoDescender'};
287 $table->{'LineGap'} = $self->{'sTypoLineGap'};
288 $self->{'usWinAscent'} = $self->{'sTypoAscender'} + $self->{'sTypoLineGap'};
289 $self->{'usWinDescent'} = -$self->{'sTypoDescender'};
291 elsif ($self->{'usWinAscent'} != 0 || $self->{'usWinDescent'} != 0)
293 $self->{'sTypoAscender'} = $table->{'Ascender'} = $self->{'usWinAscent'};
294 $self->{'sTypoDescender'} = $table->{'Descender'} = -$self->{'usWinDescent'};
295 $self->{'sTypoLineGap'} = $table->{'LineGap'} = 0;
298 if ($self->{'Version'} < 3)
300 for ($i = 0; $i < 26; $i++)
301 { $avg += $hmtx->{'advance'}[$map->{'val'}{$i + 0x0061}] * $weights[$i]; }
302 $avg += $hmtx->{'advance'}[$map->{'val'}{0x0020}] * $weights[-1];
303 $self->{'xAvgCharWidth'} = $avg / 1000;
305 elsif ($self->{'Version'} > 2)
308 foreach (@{$hmtx->{'advance'}})
315 $self->{'xAvgCharWidth'} = $avg;
318 foreach $i (keys %{$map->{'val'}})
322 $self->{'ulUnicodeRange2'} |= 0x2000000;
327 $self->{'Version'} = 1 if (defined $self->{'ulCodePageRange1'} && $self->{'Version'} < 1);
328 $self->{'Version'} = 2 if (defined $self->{'maxLookups'} && $self->{'Version'} < 2);
330 if ((exists $self->{' PARENT'}{'GPOS'} && $self->{' PARENT'}{'GPOS'}{' read'}) ||
331 (exists $self->{' PARENT'}{'GSUB'} && $self->{' PARENT'}{'GSUB'}{' read'}))
333 # one or both of GPOS & GSUB exist and have been read or modified; so update usMaxContexts
335 $lp = $self->{' PARENT'}{'GPOS'}->maxContext if exists $self->{' PARENT'}{'GPOS'};
336 $ls = $self->{' PARENT'}{'GSUB'}->maxContext if exists $self->{' PARENT'}{'GSUB'};
337 $self->{'maxLookups'} = $lp > $ls ? $lp : $ls;
351 Martin Hosken Martin_Hosken@sil.org. See L<Font::TTF::Font> for copyright and