PageRenderTime 53ms CodeModel.GetById 29ms RepoModel.GetById 0ms app.codeStats 0ms

/lib/Font/TTF/OS_2.pm

http://github.com/gonzoua/book-tools
Perl | 354 lines | 286 code | 65 blank | 3 comment | 33 complexity | 4009307fa1fb322e3322c69c37be2880 MD5 | raw file
  1. package Font::TTF::OS_2;
  2. =head1 NAME
  3. Font::TTF::OS_2 - the OS/2 table in a TTF font
  4. =head1 DESCRIPTION
  5. The OS/2 table has two versions and forms, one an extension of the other. This
  6. module supports both forms and the switching between them.
  7. =head1 INSTANCE VARIABLES
  8. No other variables than those in table and those in the standard:
  9. Version
  10. xAvgCharWidth
  11. usWeightClass
  12. usWidthClass
  13. fsType
  14. ySubscriptXSize
  15. ySubScriptYSize
  16. ySubscriptXOffset
  17. ySubscriptYOffset
  18. ySuperscriptXSize
  19. ySuperscriptYSize
  20. ySuperscriptXOffset
  21. ySuperscriptYOffset
  22. yStrikeoutSize
  23. yStrikeoutPosition
  24. sFamilyClass
  25. bFamilyType
  26. bSerifStyle
  27. bWeight
  28. bProportion
  29. bContrast
  30. bStrokeVariation
  31. bArmStyle
  32. bLetterform
  33. bMidline
  34. bXheight
  35. ulUnicodeRange1
  36. ulUnicodeRange2
  37. ulUnicodeRange3
  38. ulUnicodeRange4
  39. achVendID
  40. fsSelection
  41. usFirstCharIndex
  42. usLastCharIndex
  43. sTypoAscender
  44. sTypoDescender
  45. sTypoLineGap
  46. usWinAscent
  47. usWinDescent
  48. ulCodePageRange1
  49. ulCodePageRange2
  50. xHeight
  51. CapHeight
  52. defaultChar
  53. breakChar
  54. maxLookups
  55. Notice that versions 0, 1, 2 & 3 of the table are supported. Notice also that the
  56. Panose variable has been broken down into its elements.
  57. =head1 METHODS
  58. =cut
  59. use strict;
  60. use vars qw(@ISA @fields @lens @field_info @weights);
  61. use Font::TTF::Table;
  62. @ISA = qw(Font::TTF::Table);
  63. @field_info = (
  64. 'xAvgCharWidth' => 's',
  65. 'usWeightClass' => 'S',
  66. 'usWidthClass' => 'S',
  67. 'fsType' => 's',
  68. 'ySubscriptXSize' => 's',
  69. 'ySubScriptYSize' => 's',
  70. 'ySubscriptXOffset' => 's',
  71. 'ySubscriptYOffset' => 's',
  72. 'ySuperscriptXSize' => 's',
  73. 'ySuperscriptYSize' => 's',
  74. 'ySuperscriptXOffset' => 's',
  75. 'ySuperscriptYOffset' => 's',
  76. 'yStrikeoutSize' => 's',
  77. 'yStrikeoutPosition' => 's',
  78. 'sFamilyClass' => 's',
  79. 'bFamilyType' => 'C',
  80. 'bSerifStyle' => 'C',
  81. 'bWeight' => 'C',
  82. 'bProportion' => 'C',
  83. 'bContrast' => 'C',
  84. 'bStrokeVariation' => 'C',
  85. 'bArmStyle' => 'C',
  86. 'bLetterform' => 'C',
  87. 'bMidline' => 'C',
  88. 'bXheight' => 'C',
  89. 'ulUnicodeRange1' => 'L',
  90. 'ulUnicodeRange2' => 'L',
  91. 'ulUnicodeRange3' => 'L',
  92. 'ulUnicodeRange4' => 'L',
  93. 'achVendID' => 'L',
  94. 'fsSelection' => 'S',
  95. 'usFirstCharIndex' => 'S',
  96. 'usLastCharIndex' => 'S',
  97. 'sTypoAscender' => 'S',
  98. 'sTypoDescender' => 's',
  99. 'sTypoLineGap' => 'S',
  100. 'usWinAscent' => 'S',
  101. 'usWinDescent' => 'S',
  102. '' => '',
  103. 'ulCodePageRange1' => 'L',
  104. 'ulCodePageRange2' => 'L',
  105. '' => '',
  106. 'xHeight' => 's',
  107. 'CapHeight' => 's',
  108. 'defaultChar' => 'S',
  109. 'breakChar' => 'S',
  110. 'maxLookups' => 's',
  111. '' => '', # i.e. v3 is basically same as v2
  112. );
  113. @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);
  114. use Font::TTF::Utils;
  115. sub init
  116. {
  117. my ($k, $v, $c, $n, $i, $t, $j);
  118. $n = 0;
  119. @lens = (76, 84, 94, 94);
  120. for ($j = 0; $j < $#field_info; $j += 2)
  121. {
  122. if ($field_info[$j] eq '')
  123. {
  124. $n++;
  125. next;
  126. }
  127. ($k, $v, $c) = TTF_Init_Fields($field_info[$j], $c, $field_info[$j+1]);
  128. next unless defined $k && $k ne "";
  129. for ($i = $n; $i < 4; $i++)
  130. { $fields[$i]{$k} = $v; }
  131. }
  132. }
  133. =head2 $t->read
  134. Reads in the various values from disk (see details of OS/2 table)
  135. =cut
  136. sub read
  137. {
  138. my ($self) = @_;
  139. my ($dat, $ver);
  140. $self->SUPER::read or return $self;
  141. init unless defined $fields[2]{'xAvgCharWidth'};
  142. $self->{' INFILE'}->read($dat, 2);
  143. $ver = unpack("n", $dat);
  144. $self->{'Version'} = $ver;
  145. if ($ver < 4)
  146. {
  147. $self->{' INFILE'}->read($dat, $lens[$ver]);
  148. TTF_Read_Fields($self, $dat, $fields[$ver]);
  149. }
  150. $self;
  151. }
  152. =head2 $t->out($fh)
  153. Writes the table to a file either from memory or by copying.
  154. =cut
  155. sub out
  156. {
  157. my ($self, $fh) = @_;
  158. my ($ver);
  159. return $self->SUPER::out($fh) unless $self->{' read'};
  160. $ver = $self->{'Version'};
  161. $fh->print(pack("n", $ver));
  162. $fh->print(TTF_Out_Fields($self, $fields[$ver], $lens[$ver]));
  163. $self;
  164. }
  165. =head2 $t->XML_element($context, $depth, $key, $value)
  166. Tidies up the hex values to output them in hex
  167. =cut
  168. sub XML_element
  169. {
  170. my ($self) = shift;
  171. my ($context, $depth, $key, $value) = @_;
  172. my ($fh) = $context->{'fh'};
  173. if ($key =~ m/^ul(?:Unicode|CodePage)Range\d$/o)
  174. { $fh->printf("%s<%s>%08X</%s>\n", $depth, $key, $value, $key); }
  175. elsif ($key eq 'achVendID')
  176. { $fh->printf("%s<%s name='%s'/>\n", $depth, $key, pack('N', $value)); }
  177. else
  178. { return $self->SUPER::XML_element(@_); }
  179. $self;
  180. }
  181. =head2 $t->XML_end($context, $tag, %attrs)
  182. Now handle them on the way back in
  183. =cut
  184. sub XML_end
  185. {
  186. my ($self) = shift;
  187. my ($context, $tag, %attrs) = @_;
  188. if ($tag =~ m/^ul(?:Unicode|CodePage)Range\d$/o)
  189. { return hex($context->{'text'}); }
  190. elsif ($tag eq 'achVendID')
  191. { return unpack('N', $attrs{'name'}); }
  192. else
  193. { return $self->SUPER::XML_end(@_); }
  194. }
  195. =head2 $t->update
  196. Updates the OS/2 table by getting information from other sources:
  197. Updates the C<firstChar> and C<lastChar> values based on the MS table in the
  198. cmap.
  199. Updates the sTypoAscender, sTypoDescender & sTypoLineGap to be the same values
  200. as Ascender, Descender and Linegap from the hhea table (assuming it is dirty)
  201. and also sets usWinAscent to be the sum of Ascender+Linegap and usWinDescent to
  202. be the negative of Descender.
  203. =cut
  204. sub update
  205. {
  206. my ($self) = @_;
  207. my ($map, @keys, $table, $i, $avg, $hmtx);
  208. return undef unless ($self->SUPER::update);
  209. $self->{' PARENT'}{'cmap'}->update;
  210. $map = $self->{' PARENT'}{'cmap'}->find_ms || return undef;
  211. $hmtx = $self->{' PARENT'}{'hmtx'}->read;
  212. @keys = sort {$a <=> $b} grep {$_ < 0x10000} keys %{$map->{'val'}};
  213. $self->{'usFirstCharIndex'} = $keys[0];
  214. $self->{'usLastCharIndex'} = $keys[-1];
  215. $table = $self->{' PARENT'}{'hhea'}->read;
  216. # try any way we can to get some real numbers passed around!
  217. if (($self->{'fsSelection'} & 128) != 0)
  218. {
  219. # assume the user knows what they are doing and has sensible values already
  220. }
  221. elsif ($table->{'Ascender'} != 0 || $table->{'Descender'} != 0)
  222. {
  223. $self->{'sTypoAscender'} = $table->{'Ascender'};
  224. $self->{'sTypoDescender'} = $table->{'Descender'};
  225. $self->{'sTypoLineGap'} = $table->{'LineGap'};
  226. $self->{'usWinAscent'} = $self->{'sTypoAscender'} + $self->{'sTypoLineGap'};
  227. $self->{'usWinDescent'} = -$self->{'sTypoDescender'};
  228. }
  229. elsif ($self->{'sTypoAscender'} != 0 || $self->{'sTypoDescender'} != 0)
  230. {
  231. $table->{'Ascender'} = $self->{'sTypoAscender'};
  232. $table->{'Descender'} = $self->{'sTypoDescender'};
  233. $table->{'LineGap'} = $self->{'sTypoLineGap'};
  234. $self->{'usWinAscent'} = $self->{'sTypoAscender'} + $self->{'sTypoLineGap'};
  235. $self->{'usWinDescent'} = -$self->{'sTypoDescender'};
  236. }
  237. elsif ($self->{'usWinAscent'} != 0 || $self->{'usWinDescent'} != 0)
  238. {
  239. $self->{'sTypoAscender'} = $table->{'Ascender'} = $self->{'usWinAscent'};
  240. $self->{'sTypoDescender'} = $table->{'Descender'} = -$self->{'usWinDescent'};
  241. $self->{'sTypoLineGap'} = $table->{'LineGap'} = 0;
  242. }
  243. if ($self->{'Version'} < 3)
  244. {
  245. for ($i = 0; $i < 26; $i++)
  246. { $avg += $hmtx->{'advance'}[$map->{'val'}{$i + 0x0061}] * $weights[$i]; }
  247. $avg += $hmtx->{'advance'}[$map->{'val'}{0x0020}] * $weights[-1];
  248. $self->{'xAvgCharWidth'} = $avg / 1000;
  249. }
  250. elsif ($self->{'Version'} > 2)
  251. {
  252. $i = 0; $avg = 0;
  253. foreach (@{$hmtx->{'advance'}})
  254. {
  255. next unless ($_);
  256. $i++;
  257. $avg += $_;
  258. }
  259. $avg /= $i if ($i);
  260. $self->{'xAvgCharWidth'} = $avg;
  261. }
  262. foreach $i (keys %{$map->{'val'}})
  263. {
  264. if ($i >= 0x10000)
  265. {
  266. $self->{'ulUnicodeRange2'} |= 0x2000000;
  267. last;
  268. }
  269. }
  270. $self->{'Version'} = 1 if (defined $self->{'ulCodePageRange1'} && $self->{'Version'} < 1);
  271. $self->{'Version'} = 2 if (defined $self->{'maxLookups'} && $self->{'Version'} < 2);
  272. if ((exists $self->{' PARENT'}{'GPOS'} && $self->{' PARENT'}{'GPOS'}{' read'}) ||
  273. (exists $self->{' PARENT'}{'GSUB'} && $self->{' PARENT'}{'GSUB'}{' read'}))
  274. {
  275. # one or both of GPOS & GSUB exist and have been read or modified; so update usMaxContexts
  276. my ($lp, $ls);
  277. $lp = $self->{' PARENT'}{'GPOS'}->maxContext if exists $self->{' PARENT'}{'GPOS'};
  278. $ls = $self->{' PARENT'}{'GSUB'}->maxContext if exists $self->{' PARENT'}{'GSUB'};
  279. $self->{'maxLookups'} = $lp > $ls ? $lp : $ls;
  280. }
  281. $self;
  282. }
  283. 1;
  284. =head1 BUGS
  285. None known
  286. =head1 AUTHOR
  287. Martin Hosken Martin_Hosken@sil.org. See L<Font::TTF::Font> for copyright and
  288. licensing.
  289. =cut