Font.pm 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647
  1. #------------------------------------------------------------------------------
  2. # File: Font.pm
  3. #
  4. # Description: Read meta information from font files
  5. #
  6. # Revisions: 2010/01/15 - P. Harvey Created
  7. #
  8. # References: 1) http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6.html
  9. # 2) http://www.microsoft.com/typography/otspec/otff.htm
  10. # 3) http://partners.adobe.com/public/developer/opentype/index_font_file.html
  11. # 4) http://partners.adobe.com/public/developer/en/font/5178.PFM.pdf
  12. # 5) http://opensource.adobe.com/svn/opensource/flex/sdk/trunk/modules/compiler/src/java/flex2/compiler/util/MimeMappings.java
  13. # 6) http://www.adobe.com/devnet/font/pdfs/5004.AFM_Spec.pdf
  14. #------------------------------------------------------------------------------
  15. package Image::ExifTool::Font;
  16. use strict;
  17. use vars qw($VERSION %ttLang);
  18. use Image::ExifTool qw(:DataAccess :Utils);
  19. $VERSION = '1.08';
  20. sub ProcessOTF($$);
  21. # TrueType 'name' platform codes
  22. my %ttPlatform = (
  23. 0 => 'Unicode',
  24. 1 => 'Macintosh',
  25. 2 => 'ISO',
  26. 3 => 'Windows',
  27. 4 => 'Custom',
  28. );
  29. # convert TrueType 'name' character encoding to ExifTool Charset (ref 1/2)
  30. my %ttCharset = (
  31. Macintosh => {
  32. 0 => 'MacRoman', 17 => 'MacMalayalam',
  33. 1 => 'MacJapanese', 18 => 'MacSinhalese',
  34. 2 => 'MacChineseTW', 19 => 'MacBurmese',
  35. 3 => 'MacKorean', 20 => 'MacKhmer',
  36. 4 => 'MacArabic', 21 => 'MacThai',
  37. 5 => 'MacHebrew', 22 => 'MacLaotian',
  38. 6 => 'MacGreek', 23 => 'MacGeorgian',
  39. 7 => 'MacCyrillic', 24 => 'MacArmenian', # 7=Russian
  40. 8 => 'MacRSymbol', 25 => 'MacChineseCN',
  41. 9 => 'MacDevanagari', 26 => 'MacTibetan',
  42. 10 => 'MacGurmukhi', 27 => 'MacMongolian',
  43. 11 => 'MacGujarati', 28 => 'MacGeez',
  44. 12 => 'MacOriya', 29 => 'MacCyrillic', # 29=Slavic
  45. 13 => 'MacBengali', 30 => 'MacVietnam',
  46. 14 => 'MacTamil', 31 => 'MacSindhi',
  47. 15 => 'MacTelugu', 32 => '', # 32=uninterpreted
  48. 16 => 'MacKannada',
  49. },
  50. Windows => {
  51. 0 => 'Symbol', 4 => 'Big5',
  52. 1 => 'UCS2', 5 => 'Wansung',
  53. 2 => 'ShiftJIS', 6 => 'Johab',
  54. 3 => 'PRC', 10 => 'UCS4',
  55. },
  56. Unicode => {
  57. # (we don't currently handle the various Unicode flavours)
  58. 0 => 'UCS2', # Unicode 1.0 semantics
  59. 1 => 'UCS2', # Unicode 1.1 semantics
  60. 2 => 'UCS2', # ISO 10646 semantics
  61. 3 => 'UCS2', # Unicode 2.0 and onwards semantics, Unicode BMP only.
  62. 4 => 'UCS2', # Unicode 2.0 and onwards semantics, Unicode full repertoire.
  63. # 5 => Unicode Variation Sequences (not used in Naming table)
  64. },
  65. ISO => { # (deprecated)
  66. 0 => 'UTF8', # (7-bit ASCII)
  67. 1 => 'UCS2', # ISO 10646
  68. 2 => 'Latin', # ISO 8859-1
  69. },
  70. Custom => { },
  71. );
  72. # convert TrueType 'name' language code to ExifTool language code
  73. %ttLang = (
  74. # Macintosh language codes (also used by QuickTime.pm)
  75. # oddities:
  76. # 49 - Cyrllic version 83 - Roman
  77. # 50 - Arabic version 84 - Arabic
  78. # 146 - with dot above
  79. Macintosh => {
  80. 0 => 'en', 24 => 'lt', 48 => 'kk', 72 => 'ml', 129 => 'eu',
  81. 1 => 'fr', 25 => 'pl', 49 => 'az', 73 => 'kn', 130 => 'ca',
  82. 2 => 'de', 26 => 'hu', 50 => 'az', 74 => 'ta', 131 => 'la',
  83. 3 => 'it', 27 => 'et', 51 => 'hy', 75 => 'te', 132 => 'qu',
  84. 4 => 'nl', 28 => 'lv', 52 => 'ka', 76 => 'si', 133 => 'gn',
  85. 5 => 'sv', 29 => 'smi', 53 => 'ro', 77 => 'my', 134 => 'ay',
  86. 6 => 'es', 30 => 'fo', 54 => 'ky', 78 => 'km', 135 => 'tt',
  87. 7 => 'da', 31 => 'fa', 55 => 'tg', 79 => 'lo', 136 => 'ug',
  88. 8 => 'pt', 32 => 'ru', 56 => 'tk', 80 => 'vi', 137 => 'dz',
  89. 9 => 'no', 33 => 'zh-CN', 57 => 'mn-MN', 81 => 'id', 138 => 'jv',
  90. 10 => 'he', 34 => 'nl', 58 => 'mn-CN', 82 => 'tl', 139 => 'su',
  91. 11 => 'ja', 35 => 'ga', 59 => 'ps', 83 => 'ms-MY', 140 => 'gl',
  92. 12 => 'ar', 36 => 'sq', 60 => 'ku', 84 => 'ms-BN', 141 => 'af',
  93. 13 => 'fi', 37 => 'ro', 61 => 'ks', 85 => 'am', 142 => 'br',
  94. 14 => 'iu', 38 => 'cs', 62 => 'sd', 86 => 'ti', 144 => 'gd',
  95. 15 => 'is', 39 => 'sk', 63 => 'bo', 87 => 'om', 145 => 'vg',
  96. 16 => 'mt', 40 => 'sl', 64 => 'ne', 88 => 'so', 146 => 'ga',
  97. 17 => 'tr', 41 => 'yi', 65 => 'sa', 89 => 'sw', 147 => 'rar',
  98. 18 => 'hr', 42 => 'sr', 66 => 'mr', 90 => 'rw', 148 => 'el',
  99. 19 => 'zh-TW', 43 => 'mk', 67 => 'bn', 91 => 'rn', 149 => 'kl',
  100. 20 => 'ur', 44 => 'bg', 68 => 'as', 92 => 'ny', 150 => 'az',
  101. 21 => 'hi', 45 => 'uk', 69 => 'gu', 93 => 'mg',
  102. 22 => 'th', 46 => 'be', 70 => 'pa', 94 => 'eo',
  103. 23 => 'ko', 47 => 'uz', 71 => 'or', 128 => 'cy',
  104. },
  105. # Windows language codes (http://msdn.microsoft.com/en-us/library/0h88fahh(VS.85).aspx)
  106. # Notes: This isn't an exact science. The reference above gives language codes
  107. # which are different from some ISO 639-1 numbers. Also, some Windows language
  108. # codes don't appear to have ISO 639-1 equivalents.
  109. # 0x0428 - fa by ref above
  110. # 0x048c - no ISO equivalent
  111. # 0x081a/0x83c - sr-SP
  112. # 0x0c0a - modern?
  113. # 0x2409 - Carribean country code not found in ISO 3166-1
  114. Windows => {
  115. 0x0401 => 'ar-SA', 0x0438 => 'fo', 0x0481 => 'mi', 0x1409 => 'en-NZ',
  116. 0x0402 => 'bg', 0x0439 => 'hi', 0x0482 => 'oc', 0x140a => 'es-CR',
  117. 0x0403 => 'ca', 0x043a => 'mt', 0x0483 => 'co', 0x140c => 'fr-LU',
  118. 0x0404 => 'zh-TW', 0x043b => 'se-NO', 0x0484 => 'gsw', 0x141a => 'bs-BA',
  119. 0x0405 => 'cs', 0x043c => 'gd', 0x0485 => 'sah', 0x143b => 'smj-SE',
  120. 0x0406 => 'da', 0x043d => 'yi', 0x0486 => 'ny', 0x1801 => 'ar-MA',
  121. 0x0407 => 'de-DE', 0x043e => 'ms-MY', 0x0487 => 'rw', 0x1809 => 'en-IE',
  122. 0x0408 => 'el', 0x043f => 'kk', 0x048c => 'Dari', 0x180a => 'es-PA',
  123. 0x0409 => 'en-US', 0x0440 => 'ky', 0x0801 => 'ar-IQ', 0x180c => 'fr-MC',
  124. 0x040a => 'es-ES', 0x0441 => 'sw', 0x0804 => 'zh-CN', 0x181a => 'sr-BA',
  125. 0x040b => 'fi', 0x0442 => 'tk', 0x0807 => 'de-CH', 0x183b => 'sma-NO',
  126. 0x040c => 'fr-FR', 0x0443 => 'uz-UZ', 0x0809 => 'en-GB', 0x1c01 => 'ar-TN',
  127. 0x040d => 'he', 0x0444 => 'tt', 0x080a => 'es-MX', 0x1c09 => 'en-ZA',
  128. 0x040e => 'hu', 0x0445 => 'bn-IN', 0x080c => 'fr-BE', 0x1c0a => 'es-DO',
  129. 0x040f => 'is', 0x0446 => 'pa', 0x0810 => 'it-CH', 0x1c1a => 'sr-BA',
  130. 0x0410 => 'it-IT', 0x0447 => 'gu', 0x0813 => 'nl-BE', 0x1c3b => 'sma-SE',
  131. 0x0411 => 'ja', 0x0448 => 'wo', 0x0814 => 'nn', 0x2001 => 'ar-OM',
  132. 0x0412 => 'ko', 0x0449 => 'ta', 0x0816 => 'pt-PT', 0x2009 => 'en-JM',
  133. 0x0413 => 'nl-NL', 0x044a => 'te', 0x0818 => 'ro-MO', 0x200a => 'es-VE',
  134. 0x0414 => 'no-NO', 0x044b => 'kn', 0x0819 => 'ru-MO', 0x201a => 'bs-BA',
  135. 0x0415 => 'pl', 0x044c => 'ml', 0x081a => 'sr-RS', 0x203b => 'sms',
  136. 0x0416 => 'pt-BR', 0x044d => 'as', 0x081d => 'sv-FI', 0x2401 => 'ar-YE',
  137. 0x0417 => 'rm', 0x044e => 'mr', 0x082c => 'az-AZ', 0x2409 => 'en-CB',
  138. 0x0418 => 'ro', 0x044f => 'sa', 0x082e => 'dsb', 0x240a => 'es-CO',
  139. 0x0419 => 'ru', 0x0450 => 'mn-MN', 0x083b => 'se-SE', 0x243b => 'smn',
  140. 0x041a => 'hr', 0x0451 => 'bo', 0x083c => 'ga', 0x2801 => 'ar-SY',
  141. 0x041b => 'sk', 0x0452 => 'cy', 0x083e => 'ms-BN', 0x2809 => 'en-BZ',
  142. 0x041c => 'sq', 0x0453 => 'km', 0x0843 => 'uz-UZ', 0x280a => 'es-PE',
  143. 0x041d => 'sv-SE', 0x0454 => 'lo', 0x0845 => 'bn-BD', 0x2c01 => 'ar-JO',
  144. 0x041e => 'th', 0x0456 => 'gl', 0x0850 => 'mn-CN', 0x2c09 => 'en-TT',
  145. 0x041f => 'tr', 0x0457 => 'kok', 0x085d => 'iu-CA', 0x2c0a => 'es-AR',
  146. 0x0420 => 'ur', 0x045a => 'syr', 0x085f => 'tmh', 0x3001 => 'ar-LB',
  147. 0x0421 => 'id', 0x045b => 'si', 0x086b => 'qu-EC', 0x3009 => 'en-ZW',
  148. 0x0422 => 'uk', 0x045d => 'iu-CA', 0x0c01 => 'ar-EG', 0x300a => 'es-EC',
  149. 0x0423 => 'be', 0x045e => 'am', 0x0c04 => 'zh-HK', 0x3401 => 'ar-KW',
  150. 0x0424 => 'sl', 0x0461 => 'ne', 0x0c07 => 'de-AT', 0x3409 => 'en-PH',
  151. 0x0425 => 'et', 0x0462 => 'fy', 0x0c09 => 'en-AU', 0x340a => 'es-CL',
  152. 0x0426 => 'lv', 0x0463 => 'ps', 0x0c0a => 'es-ES', 0x3801 => 'ar-AE',
  153. 0x0427 => 'lt', 0x0464 => 'fil', 0x0c0c => 'fr-CA', 0x380a => 'es-UY',
  154. 0x0428 => 'tg', 0x0465 => 'dv', 0x0c1a => 'sr-RS', 0x3c01 => 'ar-BH',
  155. 0x042a => 'vi', 0x0468 => 'ha', 0x0c3b => 'se-FI', 0x3c0a => 'es-PY',
  156. 0x042b => 'hy', 0x046a => 'yo', 0x0c6b => 'qu-PE', 0x4001 => 'ar-QA',
  157. 0x042c => 'az-AZ', 0x046b => 'qu-BO', 0x1001 => 'ar-LY', 0x4009 => 'en-IN',
  158. 0x042d => 'eu', 0x046c => 'st', 0x1004 => 'zh-SG', 0x400a => 'es-BO',
  159. 0x042e => 'hsb', 0x046d => 'ba', 0x1007 => 'de-LU', 0x4409 => 'en-MY',
  160. 0x042f => 'mk', 0x046e => 'lb', 0x1009 => 'en-CA', 0x440a => 'es-SV',
  161. 0x0430 => 'st', 0x046f => 'kl', 0x100a => 'es-GT', 0x4809 => 'en-SG',
  162. 0x0431 => 'ts', 0x0470 => 'ig', 0x100c => 'fr-CH', 0x480a => 'es-HN',
  163. 0x0432 => 'tn', 0x0478 => 'yi', 0x101a => 'hr-BA', 0x4c0a => 'es-NI',
  164. 0x0434 => 'xh', 0x047a => 'arn', 0x103b => 'smj-NO',0x500a => 'es-PR',
  165. 0x0435 => 'zu', 0x047c => 'moh', 0x1401 => 'ar-DZ', 0x540a => 'es-US',
  166. 0x0436 => 'af', 0x047e => 'br', 0x1404 => 'zh-MO',
  167. 0x0437 => 'ka', 0x0480 => 'ug', 0x1407 => 'de-LI',
  168. },
  169. Unicode => { },
  170. ISO => { },
  171. Custom => { },
  172. );
  173. # eclectic table of tags for various format font files
  174. %Image::ExifTool::Font::Main = (
  175. GROUPS => { 2 => 'Document' },
  176. NOTES => q{
  177. This table contains a collection of tags found in font files of various
  178. formats. ExifTool current recognizes OTF, TTF, TTC, DFONT, PFA, PFB, PFM,
  179. AFM, ACFM and AMFM font files.
  180. },
  181. name => {
  182. SubDirectory => { TagTable => 'Image::ExifTool::Font::Name' },
  183. },
  184. PFM => {
  185. Name => 'PFMHeader',
  186. SubDirectory => { TagTable => 'Image::ExifTool::Font::PFM' },
  187. },
  188. PSInfo => {
  189. Name => 'PSFontInfo',
  190. SubDirectory => { TagTable => 'Image::ExifTool::Font::PSInfo' },
  191. },
  192. AFM => {
  193. Name => 'AFM',
  194. SubDirectory => { TagTable => 'Image::ExifTool::Font::AFM' },
  195. },
  196. numfonts => 'NumFonts',
  197. fontname => 'FontName',
  198. postfont => {
  199. Name => 'PostScriptFontName',
  200. Description => 'PostScript Font Name',
  201. },
  202. );
  203. # TrueType name tags (ref 1/2)
  204. %Image::ExifTool::Font::Name = (
  205. GROUPS => { 2 => 'Document' },
  206. NOTES => q{
  207. The following tags are extracted from the TrueType font "name" table found
  208. in OTF, TTF, TTC and DFONT files. These tags support localized languages by
  209. adding a hyphen followed by a language code to the end of the tag name (eg.
  210. "Copyright-fr" or "License-en-US"). Tags with no language code use the
  211. default language of "en".
  212. },
  213. 0 => { Name => 'Copyright', Groups => { 2 => 'Author' } },
  214. 1 => 'FontFamily',
  215. 2 => 'FontSubfamily',
  216. 3 => 'FontSubfamilyID',
  217. 4 => 'FontName', # full name
  218. 5 => 'NameTableVersion',
  219. 6 => { Name => 'PostScriptFontName', Description => 'PostScript Font Name' },
  220. 7 => 'Trademark',
  221. 8 => 'Manufacturer',
  222. 9 => 'Designer',
  223. 10 => 'Description',
  224. 11 => 'VendorURL',
  225. 12 => 'DesignerURL',
  226. 13 => 'License',
  227. 14 => 'LicenseInfoURL',
  228. 16 => 'PreferredFamily',
  229. 17 => 'PreferredSubfamily',
  230. 18 => 'CompatibleFontName',
  231. 19 => 'SampleText',
  232. 20 => {
  233. Name => 'PostScriptFontName',
  234. Description => 'PostScript Font Name',
  235. },
  236. 21 => 'WWSFamilyName',
  237. 22 => 'WWSSubfamilyName',
  238. );
  239. # PostScript Font Metric file header (ref 4)
  240. %Image::ExifTool::Font::PFM = (
  241. GROUPS => { 2 => 'Document' },
  242. PROCESS_PROC => \&Image::ExifTool::ProcessBinaryData,
  243. NOTES => 'Tags extracted from the PFM file header.',
  244. 0 => {
  245. Name => 'PFMVersion',
  246. Format => 'int16u',
  247. PrintConv => 'sprintf("%x.%.2x",$val>>8,$val&0xff)',
  248. },
  249. 6 => { Name => 'Copyright', Format => 'string[60]', Groups => { 2 => 'Author' } },
  250. 66 => { Name => 'FontType', Format => 'int16u' },
  251. 68 => { Name => 'PointSize', Format => 'int16u' },
  252. 70 => { Name => 'YResolution', Format => 'int16u' },
  253. 72 => { Name => 'XResolution', Format => 'int16u' },
  254. 74 => { Name => 'Ascent', Format => 'int16u' },
  255. 76 => { Name => 'InternalLeading', Format => 'int16u' },
  256. 78 => { Name => 'ExternalLeading', Format => 'int16u' },
  257. 80 => { Name => 'Italic' },
  258. 81 => { Name => 'Underline' },
  259. 82 => { Name => 'Strikeout' },
  260. 83 => { Name => 'Weight', Format => 'int16u' },
  261. 85 => { Name => 'CharacterSet' },
  262. 86 => { Name => 'PixWidth', Format => 'int16u' },
  263. 88 => { Name => 'PixHeight', Format => 'int16u' },
  264. 90 => { Name => 'PitchAndFamily' },
  265. 91 => { Name => 'AvgWidth', Format => 'int16u' },
  266. 93 => { Name => 'MaxWidth', Format => 'int16u' },
  267. 95 => { Name => 'FirstChar' },
  268. 96 => { Name => 'LastChar' },
  269. 97 => { Name => 'DefaultChar' },
  270. 98 => { Name => 'BreakChar' },
  271. 99 => { Name => 'WidthBytes', Format => 'int16u' },
  272. # 101 => { Name => 'DeviceTypeOffset', Format => 'int32u' },
  273. # 105 => { Name => 'FontNameOffset', Format => 'int32u' },
  274. # 109 => { Name => 'BitsPointer', Format => 'int32u' },
  275. # 113 => { Name => 'BitsOffset', Format => 'int32u' },
  276. );
  277. # PostScript FontInfo attributes (PFA, PFB) (ref PH)
  278. %Image::ExifTool::Font::PSInfo = (
  279. GROUPS => { 2 => 'Document' },
  280. NOTES => 'Tags extracted from PostScript font files (PFA and PFB).',
  281. FullName => { },
  282. FamilyName => { Name => 'FontFamily' },
  283. Weight => { },
  284. ItalicAngle => { },
  285. isFixedPitch=> { },
  286. UnderlinePosition => { },
  287. UnderlineThickness => { },
  288. Copyright => { Groups => { 2 => 'Author' } },
  289. Notice => { Groups => { 2 => 'Author' } },
  290. version => { },
  291. FontName => { },
  292. FontType => { },
  293. FSType => { },
  294. );
  295. # Adobe Font Metrics tags (AFM) (ref 6)
  296. %Image::ExifTool::Font::AFM = (
  297. GROUPS => { 2 => 'Document' },
  298. NOTES => 'Tags extracted from Adobe Font Metrics files (AFM, ACFM and AMFM).',
  299. 'Creation Date' => { Name => 'CreateDate', Groups => { 2 => 'Time' } },
  300. FontName => { },
  301. FullName => { },
  302. FamilyName => { Name => 'FontFamily' },
  303. Weight => { },
  304. Version => { },
  305. Notice => { Groups => { 2 => 'Author' } },
  306. EncodingScheme => { },
  307. MappingScheme => { },
  308. EscChar => { },
  309. CharacterSet=> { },
  310. Characters => { },
  311. IsBaseFont => { },
  312. # VVector => { },
  313. IsFixedV => { },
  314. CapHeight => { },
  315. XHeight => { },
  316. Ascender => { },
  317. Descender => { },
  318. );
  319. #------------------------------------------------------------------------------
  320. # Read information from a TrueType font collection (TTC) (refs 2,3)
  321. # Inputs: 0) ExifTool ref, 1) dirInfo ref
  322. # Returns: 1 on success, 0 if this wasn't a valid TrueType font collection
  323. sub ProcessTTC($$)
  324. {
  325. my ($et, $dirInfo) = @_;
  326. my $raf = $$dirInfo{RAF};
  327. my ($buff, $i);
  328. return 0 unless $raf->Read($buff, 12) == 12;
  329. return 0 unless $buff =~ /^ttcf\0[\x01\x02]\0\0/;
  330. SetByteOrder('MM');
  331. my $num = Get32u(\$buff, 8);
  332. # might as well put a limit on the number of fonts we will parse (< 256)
  333. return 0 unless $num < 0x100 and $raf->Read($buff, $num * 4) == $num * 4;
  334. $et->SetFileType('TTC');
  335. return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3;
  336. my $tagTablePtr = GetTagTable('Image::ExifTool::Font::Main');
  337. $et->HandleTag($tagTablePtr, 'numfonts', $num);
  338. # loop through all fonts in the collection
  339. for ($i=0; $i<$num; ++$i) {
  340. my $n = $i + 1;
  341. $et->VPrint(0, "Font $n:\n");
  342. $$et{SET_GROUP1} = "+$n";
  343. my $offset = Get32u(\$buff, $i * 4);
  344. $raf->Seek($offset, 0) or last;
  345. ProcessOTF($et, $dirInfo) or last;
  346. }
  347. delete $$et{SET_GROUP1};
  348. return 1;
  349. }
  350. #------------------------------------------------------------------------------
  351. # Read information from a TrueType font file (OTF or TTF) (refs 1,2)
  352. # Inputs: 0) ExifTool ref, 1) dirInfo ref
  353. # Returns: 1 on success, 0 if this wasn't a valid TrueType font file
  354. sub ProcessOTF($$)
  355. {
  356. my ($et, $dirInfo) = @_;
  357. my $raf = $$dirInfo{RAF};
  358. my ($tbl, $buff, $pos, $i);
  359. my $base = $$dirInfo{Base} || 0;
  360. return 0 unless $raf->Read($buff, 12) == 12;
  361. return 0 unless $buff =~ /^(\0\x01\0\0|OTTO|true|typ1|\xa5(kbd|lst))[\0\x01]/;
  362. $et->SetFileType($1 eq 'OTTO' ? 'OTF' : 'TTF');
  363. return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3;
  364. SetByteOrder('MM');
  365. my $numTables = Get16u(\$buff, 4);
  366. return 0 unless $numTables > 0 and $numTables < 0x200;
  367. my $len = $numTables * 16;
  368. return 0 unless $raf->Read($tbl, $len) == $len;
  369. my $verbose = $et->Options('Verbose');
  370. my $oldIndent = $$et{INDENT};
  371. $$et{INDENT} .= '| ';
  372. $et->VerboseDir('TrueType', $numTables) if $verbose;
  373. for ($pos=0; $pos<$len; $pos+=16) {
  374. # look for 'name' table
  375. my $tag = substr($tbl, $pos, 4);
  376. next unless $tag eq 'name' or $verbose;
  377. my $offset = Get32u(\$tbl, $pos + 8);
  378. my $size = Get32u(\$tbl, $pos + 12);
  379. unless ($raf->Seek($offset+$base, 0) and $raf->Read($buff, $size) == $size) {
  380. $et->Warn("Error reading '$tag' data");
  381. next;
  382. }
  383. if ($verbose) {
  384. $tag =~ s/([\0-\x1f\x80-\xff])/sprintf('\x%.2x',ord $1)/ge;
  385. my $str = sprintf("%s%d) Tag '%s' (offset 0x%.4x, %d bytes)\n",
  386. $$et{INDENT}, $pos/16, $tag, $offset, $size);
  387. $et->VPrint(0, $str);
  388. $et->VerboseDump(\$buff, Addr => $offset) if $verbose > 2;
  389. next unless $tag eq 'name';
  390. }
  391. next unless $size >= 8;
  392. my $entries = Get16u(\$buff, 2);
  393. my $recEnd = 6 + $entries * 12;
  394. if ($recEnd > $size) {
  395. $et->Warn('Truncated name record');
  396. last;
  397. }
  398. my $strStart = Get16u(\$buff, 4);
  399. if ($strStart < $recEnd or $strStart > $size) {
  400. $et->Warn('Invalid string offset');
  401. last;
  402. }
  403. # parse language-tag record (in format 1 Naming table only) (ref 2)
  404. my %langTag;
  405. if (Get16u(\$buff, 0) == 1 and $recEnd + 2 <= $size) {
  406. my $langTags = Get16u(\$buff, $recEnd);
  407. if ($langTags and $recEnd + 2 + $langTags * 4 < $size) {
  408. for ($i=0; $i<$langTags; ++$i) {
  409. my $pt = $recEnd + 2 + $i * 4;
  410. my $langLen = Get16u(\$buff, $pt);
  411. # make sure the language string length is reasonable (UTF-16BE)
  412. last if $langLen == 0 or $langLen & 0x01 or $langLen > 40;
  413. my $langPt = Get16u(\$buff, $pt + 2) + $strStart;
  414. last if $langPt + $langLen > $size;
  415. my $lang = substr($buff, $langPt, $langLen);
  416. $lang = $et->Decode($lang,'UCS2','MM','UTF8');
  417. $lang =~ tr/-_a-zA-Z0-9//dc; # remove naughty characters
  418. $langTag{$i + 0x8000} = $lang;
  419. }
  420. }
  421. }
  422. my $tagTablePtr = GetTagTable('Image::ExifTool::Font::Name');
  423. $$et{INDENT} .= '| ';
  424. $et->VerboseDir('Name', $entries) if $verbose;
  425. for ($i=0; $i<$entries; ++$i) {
  426. my $pt = 6 + $i * 12;
  427. my $platform = Get16u(\$buff, $pt);
  428. my $encoding = Get16u(\$buff, $pt + 2);
  429. my $langID = Get16u(\$buff, $pt + 4);
  430. my $nameID = Get16u(\$buff, $pt + 6);
  431. my $strLen = Get16u(\$buff, $pt + 8);
  432. my $strPt = Get16u(\$buff, $pt + 10) + $strStart;
  433. if ($strPt + $strLen <= $size) {
  434. my $val = substr($buff, $strPt, $strLen);
  435. my ($lang, $charset, $extra);
  436. my $sys = $ttPlatform{$platform};
  437. # translate from specified encoding
  438. if ($sys) {
  439. $lang = $ttLang{$sys}{$langID} || $langTag{$langID};
  440. $charset = $ttCharset{$sys}{$encoding};
  441. if (not $charset) {
  442. if (not defined $charset and not $$et{FontWarn}) {
  443. $et->Warn("Unknown $sys character set ($encoding)");
  444. $$et{FontWarn} = 1;
  445. }
  446. } else {
  447. # translate to ExifTool character set
  448. $val = $et->Decode($val, $charset);
  449. }
  450. } else {
  451. $et->Warn("Unknown platform ($platform) for name $nameID");
  452. }
  453. # get the tagInfo for our specific language (use 'en' for default)
  454. my $tagInfo = $et->GetTagInfo($tagTablePtr, $nameID);
  455. if ($tagInfo and $lang and $lang ne 'en') {
  456. my $langInfo = Image::ExifTool::GetLangInfo($tagInfo, $lang);
  457. $tagInfo = $langInfo if $langInfo;
  458. }
  459. if ($verbose) {
  460. $langID > 0x400 and $langID = sprintf('0x%x', $langID);
  461. $extra = ", Plat=$platform/" . ($sys || 'Unknown') . ', ' .
  462. "Enc=$encoding/" . ($charset || 'Unknown') . ', ' .
  463. "Lang=$langID/" . ($lang || 'Unknown');
  464. }
  465. $et->HandleTag($tagTablePtr, $nameID, $val,
  466. TagInfo => $tagInfo,
  467. DataPt => \$buff,
  468. DataPos => $offset,
  469. Start => $strPt,
  470. Size => $strLen,
  471. Index => $i,
  472. Extra => $extra,
  473. );
  474. }
  475. }
  476. $$et{INDENT} = $oldIndent . '| ';
  477. last unless $verbose;
  478. }
  479. $$et{INDENT} = $oldIndent;
  480. return 1;
  481. }
  482. #------------------------------------------------------------------------------
  483. # Read information from an Adobe Font Metrics file (AFM, ACFM, AMFM) (ref 6)
  484. # Inputs: 0) ExifTool ref, 1) dirInfo ref
  485. # Returns: 1 on success, 0 if this wasn't a recognized AFM-type file
  486. sub ProcessAFM($$)
  487. {
  488. my ($et, $dirInfo) = @_;
  489. my $raf = $$dirInfo{RAF};
  490. my ($buff, $comment);
  491. require Image::ExifTool::PostScript;
  492. local $/ = Image::ExifTool::PostScript::GetInputRecordSeparator($raf);
  493. $raf->ReadLine($buff);
  494. return 0 unless $buff =~ /^Start(Comp|Master)?FontMetrics\s+\d+/;
  495. my $ftyp = $1 ? ($1 eq 'Comp' ? 'ACFM' : 'AMFM') : 'AFM';
  496. $et->SetFileType($ftyp, 'application/x-font-afm');
  497. return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3;
  498. my $tagTablePtr = GetTagTable('Image::ExifTool::Font::AFM');
  499. for (;;) {
  500. $raf->ReadLine($buff) or last;
  501. if (defined $comment and $buff !~ /^Comment\s/) {
  502. $et->FoundTag('Comment', $comment);
  503. undef $comment;
  504. }
  505. $buff =~ /^(\w+)\s+(.*?)[\x0d\x0a]/ or next;
  506. my ($tag, $val) = ($1, $2);
  507. if ($tag eq 'Comment' and $val =~ /^(Creation Date):\s+(.*)/) {
  508. ($tag, $val) = ($1, $2);
  509. }
  510. $val =~ s/^\((.*)\)$/$1/; # (some values may be in brackets)
  511. if ($tag eq 'Comment') {
  512. # concatinate all comments into a single value
  513. $comment = defined($comment) ? "$comment\n$val" : $val;
  514. next;
  515. }
  516. unless ($et->HandleTag($tagTablePtr, $tag, $val)) {
  517. # end parsing if we start any subsection
  518. last if $tag =~ /^Start/ and $tag ne 'StartDirection';
  519. }
  520. }
  521. return 1;
  522. }
  523. #------------------------------------------------------------------------------
  524. # Read information from various format font files
  525. # Inputs: 0) ExifTool ref, 1) dirInfo ref
  526. # Returns: 1 on success, 0 if this wasn't a recognized Font file
  527. sub ProcessFont($$)
  528. {
  529. my ($et, $dirInfo) = @_;
  530. my $raf = $$dirInfo{RAF};
  531. my ($buff, $buf2, $rtnVal);
  532. return 0 unless $raf->Read($buff, 24) and $raf->Seek(0,0);
  533. if ($buff =~ /^(\0\x01\0\0|OTTO|true|typ1)[\0\x01]/) { # OTF, TTF
  534. $rtnVal = ProcessOTF($et, $dirInfo);
  535. } elsif ($buff =~ /^ttcf\0[\x01\x02]\0\0/) { # TTC
  536. $rtnVal = ProcessTTC($et, $dirInfo);
  537. } elsif ($buff =~ /^Start(Comp|Master)?FontMetrics\s+\d+/s) { # AFM
  538. $rtnVal = ProcessAFM($et, $dirInfo);
  539. } elsif ($buff =~ /^(.{6})?%!(PS-(AdobeFont-|Bitstream )|FontType1-)/s) {# PFA, PFB
  540. $raf->Seek(6,0) and $et->SetFileType('PFB') if $1;
  541. require Image::ExifTool::PostScript;
  542. $rtnVal = Image::ExifTool::PostScript::ProcessPS($et, $dirInfo);
  543. } elsif ($buff =~ /^\0[\x01\x02]/ and $raf->Seek(0, 2) and # PFM
  544. # validate file size
  545. $raf->Tell() > 117 and $raf->Tell() == unpack('x2V',$buff) and
  546. # read PFM header
  547. $raf->Seek(0,0) and $raf->Read($buff,117) == 117 and
  548. # validate "DeviceType" string (must be "PostScript\0")
  549. SetByteOrder('II') and $raf->Seek(Get32u(\$buff, 101), 0) and
  550. # the DeviceType should be "PostScript\0", but FontForge
  551. # incorrectly writes "Postscript\0", so ignore case
  552. $raf->Read($buf2, 11) == 11 and lc($buf2) eq "postscript\0")
  553. {
  554. $et->SetFileType('PFM');
  555. return 1 if $$et{OPTIONS}{FastScan} and $$et{OPTIONS}{FastScan} == 3;
  556. SetByteOrder('II');
  557. my $tagTablePtr = GetTagTable('Image::ExifTool::Font::Main');
  558. # process the PFM header
  559. $et->HandleTag($tagTablePtr, 'PFM', $buff);
  560. # extract the font names
  561. my $nameOff = Get32u(\$buff, 105);
  562. if ($raf->Seek($nameOff, 0) and $raf->Read($buff, 256) and
  563. $buff =~ /^([\x20-\xff]+)\0([\x20-\xff]+)\0/)
  564. {
  565. $et->HandleTag($tagTablePtr, 'fontname', $1);
  566. $et->HandleTag($tagTablePtr, 'postfont', $2);
  567. }
  568. $rtnVal = 1;
  569. } else {
  570. $rtnVal = 0;
  571. }
  572. return $rtnVal;
  573. }
  574. 1; # end
  575. __END__
  576. =head1 NAME
  577. Image::ExifTool::Font - Read meta information from font files
  578. =head1 SYNOPSIS
  579. This module is used by Image::ExifTool
  580. =head1 DESCRIPTION
  581. This module contains the routines required by Image::ExifTool to read meta
  582. information from various format font files. Currently recognized font file
  583. types are OTF, TTF, TTC, DFONT, PFA, PFB, PFM, AFM, ACFM and AMFM.
  584. =head1 AUTHOR
  585. Copyright 2003-2016, Phil Harvey (phil at owl.phy.queensu.ca)
  586. This library is free software; you can redistribute it and/or modify it
  587. under the same terms as Perl itself.
  588. =head1 REFERENCES
  589. =over 4
  590. =item L<http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6.html>
  591. =item L<http://www.microsoft.com/typography/otspec/otff.htm>
  592. =item L<http://partners.adobe.com/public/developer/opentype/index_font_file.html>
  593. =item L<http://partners.adobe.com/public/developer/en/font/5178.PFM.pdf>
  594. =item L<http://opensource.adobe.com/svn/opensource/flex/sdk/trunk/modules/compiler/src/java/flex2/compiler/util/MimeMappings.java>
  595. =item L<http://www.adobe.com/devnet/font/pdfs/5004.AFM_Spec.pdf>
  596. =back
  597. =head1 SEE ALSO
  598. L<Image::ExifTool::TagNames/Font Tags>,
  599. L<Image::ExifTool(3pm)|Image::ExifTool>
  600. =cut