fpparsettf.pp 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2014 by Michael Van Canneyt
  4. This unit reads and extracts info from a TTF font file.
  5. See the file COPYING.FPC, included in this distribution,
  6. for details about the copyright.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  10. **********************************************************************}
  11. {$IFNDEF FPC_DOTTEDUNITS}
  12. unit fpparsettf;
  13. {$ENDIF FPC_DOTTEDUNITS}
  14. {$mode objfpc}
  15. {$h+}
  16. { $R+}
  17. {.$define gdebug}
  18. interface
  19. {$IFDEF FPC_DOTTEDUNITS}
  20. uses
  21. System.Classes,
  22. System.SysUtils,
  23. FpPdf.Ttf.Encodings;
  24. {$ELSE FPC_DOTTEDUNITS}
  25. uses
  26. Classes,
  27. SysUtils,
  28. fpttfencodings;
  29. {$ENDIF FPC_DOTTEDUNITS}
  30. type
  31. ETTF = Class(Exception);
  32. // Tables recognized in this unit.
  33. TTTFTableType = (
  34. // these are for general font information
  35. ttUnknown,ttHead,tthhea,ttmaxp,tthmtx,ttcmap,ttname,ttOS2,ttpost,
  36. // these are used for font subsetting
  37. ttglyf,ttloca,ttcvt,ttprep,ttfpgm);
  38. TSmallintArray = Packed Array of Int16;
  39. TWordArray = Packed Array of UInt16; // redefined because the one in SysUtils is not a packed array
  40. { Signed Fixed 16.16 Float }
  41. TF16Dot16 = type Int32;
  42. TFixedVersionRec = packed record
  43. case Integer of
  44. 0: (Minor, Major: UInt16);
  45. 1: (Version: UInt32);
  46. end;
  47. { The file header record that starts at byte 0 of a TTF file }
  48. TTableDirectory = Packed Record
  49. FontVersion : TFixedVersionRec; { UInt32}
  50. Numtables : UInt16;
  51. SearchRange : UInt16;
  52. EntrySelector : UInt16;
  53. RangeShift : UInt16;
  54. end;
  55. TTableDirectoryEntry = Packed Record
  56. Tag: Array[1..4] of AnsiChar;
  57. checkSum : UInt32;
  58. offset : UInt32;
  59. Length : UInt32;
  60. end;
  61. TTableDirectoryEntries = Array of TTableDirectoryEntry;
  62. TLongHorMetric = Packed record
  63. AdvanceWidth : UInt16;
  64. LSB: Int16; { leftSideBearing }
  65. end;
  66. TLongHorMetricArray = Packed Array of TLongHorMetric;
  67. Type
  68. TPostScript = Packed Record
  69. Format : TFixedVersionRec; { UInt32 }
  70. ItalicAngle : TF16Dot16; { Int32 }
  71. UnderlinePosition : Int16;
  72. underlineThickness : Int16;
  73. isFixedPitch : UInt32;
  74. minMemType42 : UInt32;
  75. maxMemType42 : UInt32;
  76. minMemType1 : UInt32;
  77. maxMemType1 : UInt32;
  78. end;
  79. TMaxP = Packed Record
  80. VersionNumber : TFixedVersionRec; { UInt32 }
  81. numGlyphs : UInt16;
  82. maxPoints : UInt16;
  83. maxContours : UInt16;
  84. maxCompositePoints : UInt16;
  85. maxCompositeContours : UInt16;
  86. maxZones : UInt16;
  87. maxTwilightPoints : UInt16;
  88. maxStorage : UInt16;
  89. maxFunctionDefs : UInt16;
  90. maxInstructionDefs : UInt16;
  91. maxStackElements : UInt16;
  92. maxSizeOfInstructions : UInt16;
  93. maxComponentElements : UInt16;
  94. maxComponentDepth : UInt16;
  95. end;
  96. TOS2Data = Packed Record
  97. version : UInt16;
  98. xAvgCharWidth : Int16;
  99. usWeightClass : UInt16;
  100. usWidthClass : UInt16;
  101. fsType : Int16;
  102. ySubscriptXSize : Int16;
  103. ySubscriptYSize : Int16;
  104. ySubscriptXOffset : Int16;
  105. ySubscriptYOffset : Int16;
  106. ySuperscriptXSize : Int16;
  107. ySuperscriptYSize : Int16;
  108. ySuperscriptXOffset : Int16;
  109. ySuperscriptYOffset : Int16;
  110. yStrikeoutSize : Int16;
  111. yStrikeoutPosition : Int16;
  112. sFamilyClass : Int16; // we could split this into a record of Class & SubClass values.
  113. panose : Array[0..9] of byte;
  114. ulUnicodeRange1 : UInt32;
  115. ulUnicodeRange2 : UInt32;
  116. ulUnicodeRange3 : UInt32;
  117. ulUnicodeRange4 : UInt32;
  118. achVendID : Array[0..3] of AnsiChar;
  119. fsSelection : UInt16;
  120. usFirstCharIndex : UInt16;
  121. usLastCharIndex : UInt16;
  122. sTypoAscender: Int16;
  123. sTypoDescender : Int16;
  124. sTypoLineGap : Int16;
  125. usWinAscent : UInt16;
  126. usWinDescent : UInt16;
  127. ulCodePageRange1 : UInt32;
  128. ulCodePageRange2 : UInt32;
  129. sxHeight : Int16;
  130. sCapHeight : Int16;
  131. usDefaultChar : UInt16;
  132. usBreakChar : UInt16;
  133. usMaxContext : UInt16;
  134. end;
  135. { Nicely described at [https://www.microsoft.com/typography/otspec/head.htm] }
  136. THead = Packed record
  137. FileVersion : TFixedVersionRec; { UInt32 }
  138. FontRevision : TFixedVersionRec; { UInt32 }
  139. CheckSumAdjustment : UInt32;
  140. MagicNumber : UInt32;
  141. Flags : UInt16;
  142. UnitsPerEm: UInt16;
  143. Created : Int64;
  144. Modified : Int64;
  145. BBox: Packed array[0..3] of Int16;
  146. MacStyle : UInt16;
  147. LowestRecPPEM : UInt16;
  148. FontDirectionHint : Int16;
  149. IndexToLocFormat : Int16;
  150. glyphDataFormat : Int16;
  151. end;
  152. { structure described at [https://www.microsoft.com/typography/otspec/hhea.htm] }
  153. THHead = packed record
  154. TableVersion : TFixedVersionRec; { UInt32 }
  155. Ascender : Int16;
  156. Descender : Int16;
  157. LineGap : Int16;
  158. AdvanceWidthMax : UInt16;
  159. MinLeftSideBearing : Int16;
  160. MinRightSideBearing : Int16;
  161. XMaxExtent : Int16;
  162. CaretSlopeRise : Int16;
  163. CaretSlopeRun : Int16;
  164. caretOffset: Int16; // reserved field
  165. Reserved : Array[0..3] of Int16;
  166. metricDataFormat : Int16;
  167. numberOfHMetrics : UInt16;
  168. end;
  169. { Character to glyph mapping
  170. Structure described at [https://www.microsoft.com/typography/otspec/cmap.htm] }
  171. TCmapHeader = packed record
  172. Version: UInt16;
  173. SubTableCount: UInt16;
  174. end;
  175. TCmapSubTableEntry = packed record
  176. PlatformID: UInt16;
  177. EncodingID: UInt16;
  178. Offset: UInt32;
  179. end;
  180. TCmapSubTables = Array of TCmapSubTableEntry;
  181. TCmapFmt4 = packed record
  182. Format: UInt16;
  183. Length: UInt16;
  184. LanguageID: UInt16;
  185. SegmentCount2: UInt16;
  186. SearchRange: UInt16;
  187. EntrySelector: UInt16;
  188. RangeShift: UInt16;
  189. end;
  190. TUnicodeMapSegment = Packed Record
  191. StartCode : UInt16;
  192. EndCode : UInt16;
  193. IDDelta : Int16;
  194. IDRangeOffset : UInt16;
  195. end;
  196. TUnicodeMapSegmentArray = Array of TUnicodeMapSegment;
  197. TNameRecord = Packed Record
  198. PlatformID : UInt16;
  199. EncodingID : UInt16;
  200. LanguageID : UInt16;
  201. NameID : UInt16;
  202. StringLength : UInt16;
  203. StringOffset : UInt16;
  204. end;
  205. TNameEntry = Packed Record
  206. Info: TNameRecord;
  207. Value : AnsiString;
  208. end;
  209. TNameEntries = Array of TNameEntry;
  210. TGlyphHeader = packed record
  211. numberOfContours: int16;
  212. xMin: uint16;
  213. yMin: uint16;
  214. xMax: uint16;
  215. yMax: uint16;
  216. end;
  217. { As per the TTF specification document...
  218. https://www.microsoft.com/typography/tt/ttf_spec/ttch02.doc
  219. ...all TTF files are always stored in Big-Endian byte ordering (pg.31 Data Types).
  220. }
  221. TTFFileInfo = class(TObject)
  222. private
  223. FFilename: string;
  224. FTableDir : TTableDirectory;
  225. FTables : TTableDirectoryEntries;
  226. FMaxp : TMaxP;
  227. FCmapH : TCMapHeader;
  228. FSubtables : TCmapSubTables;
  229. FUnicodeMap : TCmapFmt4;
  230. FUnicodeMapSegments : TUnicodeMapSegmentArray;
  231. FHead : THead;
  232. FHHEad : THHead;
  233. FOS2Data : TOS2Data;
  234. FPostScript : TPostScript;
  235. FWidths: TLongHorMetricArray; // hmtx data
  236. // Needed to create PDF font def.
  237. FOriginalSize : Cardinal;
  238. FMissingWidth: Integer;
  239. FNameEntries: TNameEntries;
  240. { This only applies to TFixedVersionRec values. }
  241. function FixMinorVersion(const AMinor: word): word;
  242. function GetMissingWidth: integer;
  243. Protected
  244. // Stream reading functions.
  245. function ReadInt16(AStream: TStream): Int16; inline;
  246. function ReadUInt32(AStream: TStream): UInt32; inline;
  247. function ReadUInt16(AStream: TStream): UInt16; inline;
  248. // Parse the various well-known tables
  249. procedure ParseHead(AStream : TStream); virtual;
  250. procedure ParseHhea(AStream : TStream); virtual;
  251. procedure ParseMaxp(AStream : TStream); virtual;
  252. procedure ParseHmtx(AStream : TStream); virtual;
  253. procedure ParseCmap(AStream : TStream); virtual;
  254. procedure ParseName(AStream : TStream); virtual;
  255. procedure ParseOS2(AStream : TStream); virtual;
  256. procedure ParsePost(AStream : TStream); virtual;
  257. // Make differences for postscript fonts
  258. procedure PrepareEncoding(Const AEncoding : AnsiString);
  259. function MakeDifferences: AnsiString; virtual;
  260. // Utility function to convert FShort to natural units
  261. Function ToNatural(AUnit: Smallint) : Smallint;
  262. public
  263. Chars: TWordArray;
  264. CharWidth: array[0..255] of SmallInt;
  265. CharNames: PTTFEncodingNames;
  266. CharCodes: PTTFEncodingValues;
  267. CharBase: PTTFEncodingNames;
  268. PostScriptName: string;
  269. FamilyName: string;
  270. HumanFriendlyName: string; // aka FullName
  271. destructor Destroy; override;
  272. { Returns the Glyph Index value in the TTF file, where AValue is the ordinal value of a character. }
  273. function GetGlyphIndex(AValue: word): word;
  274. function GetTableDirEntry(const ATableName: string; var AEntry: TTableDirectoryEntry): boolean;
  275. // Load a TTF file from file or stream.
  276. Procedure LoadFromFile(const AFileName : String);
  277. Procedure LoadFromStream(AStream: TStream); virtual;
  278. // Checks if Embedded is allowed, and also prepares CharWidths array. NOTE: this is possibly not needed any more.
  279. procedure PrepareFontDefinition(const Encoding:string; Embed: Boolean);
  280. // The following are only valid after the file was succesfully read.
  281. Function Flags : Integer;
  282. Function Bold: Boolean;
  283. Function StemV: SmallInt;
  284. Function Embeddable : Boolean;
  285. Function Ascender: SmallInt;
  286. Function Descender: SmallInt;
  287. { Also know as the linegap. "Leading" is the gap between two lines. }
  288. Function Leading: SmallInt;
  289. Function CapHeight: SmallInt;
  290. { Returns the glyph advance width, based on the AIndex (glyph index) value. The result is in font units. }
  291. function GetAdvanceWidth(AIndex: word): word;
  292. function ItalicAngle: single;
  293. { max glyph bounding box values - as space separated values }
  294. function BBox: string;
  295. property MissingWidth: Integer read GetMissingWidth;
  296. { original font file size }
  297. property OriginalSize: Cardinal read FOriginalSize;
  298. property Filename: string read FFilename;
  299. Property Directory : TTableDirectory Read FTableDir;
  300. Property Tables : TTableDirectoryEntries Read FTables;
  301. Property Head : THead Read FHead;
  302. Property HHead : THHead Read FHHead;
  303. property CmapH : TCMapHeader Read FCmapH;
  304. property CmapSubtables : TCmapSubTables Read FSubtables;
  305. property CmapUnicodeMap : TCmapFmt4 Read FUnicodeMap;
  306. property CmapUnicodeMapSegments : TUnicodeMapSegmentArray Read FUnicodeMapSegments;
  307. Property Widths : TLongHorMetricArray Read FWidths;
  308. Property MaxP : TMaxP Read FMaxP;
  309. Property OS2Data : TOS2Data Read FOS2Data;
  310. Property PostScript : TPostScript Read FPostScript;
  311. property NameEntries: TNameEntries read FNameEntries;
  312. end;
  313. type
  314. TConvertResult = (trNoError, trNullSrc, trNullDest, trDestExhausted,
  315. trInvalidChar, trUnfinishedChar);
  316. TConvertOption = (toInvalidCharError, toInvalidCharToSymbol,
  317. toUnfinishedCharError, toUnfinishedCharToSymbol);
  318. TConvertOptions = set of TConvertOption;
  319. // Convert string to known table type
  320. Function GetTableType(Const AName : String) : TTTFTableType;
  321. function StrToUTF16Hex(const AValue: UnicodeString; AIncludeBOM: boolean = True): AnsiString;
  322. { To overcome the annoying compiler hint: "Local variable does not seem to be initialized" }
  323. procedure FillMem(Dest: pointer; Size: longint; Data: Byte );
  324. Const
  325. TTFTableNames : Array[TTTFTableType] of String
  326. = ('','head','hhea','maxp','hmtx','cmap','name','OS/2','post',
  327. 'glyf', 'loca', 'cvt ', 'prep', 'fpgm');
  328. Const
  329. // Platform IDs used in the Name section
  330. NamePlatFormIDAppleUnicode = 0;
  331. NamePlatFormIDMacIntosh = 1;
  332. NamePlatFormIDISO = 2;
  333. NamePlatFormIDMicrosoft = 3;
  334. // Name IDs used in the Name section
  335. NameIDCopyRight = 0;
  336. NameIDFontFamily = 1;
  337. NameIDFontSubFamily = 2;
  338. NameIDFontIdentifier = 3;
  339. NameIDFullFontName = 4;
  340. NamdIDVersionString = 5;
  341. NameIDPostScriptName = 6;
  342. NameIDTradeMark = 7;
  343. NameMSEncodingUndefined = 0;
  344. NameMSEncodingUGL = 1;
  345. implementation
  346. resourcestring
  347. rsFontEmbeddingNotAllowed = 'Font licence does not allow embedding';
  348. rsErrUnexpectedUnicodeSubtable = 'Unexpected unicode subtable format, expected 4, got %s';
  349. Function GetTableType(Const AName : String) : TTTFTableType;
  350. begin
  351. Result:=High(TTTFTableType);
  352. While (Result<>ttUnknown) and (CompareText(AName,TTFTableNames[Result])<>0) do
  353. Result:=Pred(Result);
  354. end;
  355. function StrToUTF16Hex(const AValue: UnicodeString; AIncludeBOM: boolean = True): AnsiString;
  356. var
  357. pc: ^Word;
  358. i: integer;
  359. begin
  360. if AIncludeBOM then
  361. Result := 'FEFF' // BOM marker to indicate UTF-16BE (big-endian) encoding scheme
  362. else
  363. Result := '';
  364. for i := 1 to Length(AValue) do
  365. begin
  366. pc := @AValue[i];
  367. Result := Result + AnsiString(IntToHex(pc^, 4));
  368. end;
  369. end;
  370. procedure FillMem(Dest: pointer; Size: longint; Data: Byte );
  371. begin
  372. FillChar(Dest^, Size, Data);
  373. end;
  374. function TTFFileInfo.ReadUInt32(AStream: TStream): UInt32;
  375. begin
  376. Result:=0;
  377. AStream.ReadBuffer(Result,SizeOf(Result));
  378. Result:=BEtoN(Result);
  379. end;
  380. function TTFFileInfo.ReadUInt16(AStream: TStream): UInt16;
  381. begin
  382. Result:=0;
  383. AStream.ReadBuffer(Result,SizeOf(Result));
  384. Result:=BEtoN(Result);
  385. end;
  386. function TTFFileInfo.ReadInt16(AStream: TStream): Int16;
  387. begin
  388. Result:=Int16(ReadUInt16(AStream));
  389. end;
  390. procedure TTFFileInfo.ParseHead(AStream : TStream);
  391. var
  392. i : Integer;
  393. begin
  394. AStream.ReadBuffer(FHead,SizeOf(FHead));
  395. FHead.FileVersion.Version := BEtoN(FHead.FileVersion.Version);
  396. FHead.FileVersion.Minor := FixMinorVersion(FHead.FileVersion.Minor);
  397. FHead.FontRevision.Version := BEtoN(FHead.FontRevision.Version);
  398. FHead.FontRevision.Minor := FixMinorVersion(FHead.FontRevision.Minor);
  399. FHead.Created := BEtoN(FHead.Created);
  400. FHead.Modified := BEtoN(FHead.Modified);
  401. For i:=0 to 3 do
  402. FHead.BBox[i]:=BEtoN(FHead.BBox[i]);
  403. FHead.CheckSumAdjustment:=BEtoN(FHead.CheckSumAdjustment);
  404. FHead.MagicNumber:=BEtoN(FHead.MagicNumber);
  405. FHead.Flags:=BEtoN(FHead.Flags);
  406. FHead.UnitsPerEm:=BEtoN(FHead.UnitsPerEm);
  407. FHead.MacStyle:=BEtoN(FHead.MacStyle);
  408. FHead.LowestRecPPEM:=BEtoN(FHead.LowestRecPPEM);
  409. FHead.FontDirectionHint:=BEtoN(FHead.FontDirectionHint);
  410. FHead.IndexToLocFormat:=BEtoN(FHead.IndexToLocFormat);
  411. FHead.glyphDataFormat:=BEtoN(FHead.glyphDataFormat);
  412. end;
  413. procedure TTFFileInfo.ParseHhea(AStream : TStream);
  414. begin
  415. AStream.ReadBuffer(FHHEad,SizeOf(FHHEad));
  416. FHHEad.TableVersion.Version := BEToN(FHHEad.TableVersion.Version);
  417. FHHEad.TableVersion.Minor := FixMinorVersion(FHHEad.TableVersion.Minor);
  418. FHHEad.Ascender:=BEToN(FHHEad.Ascender);
  419. FHHEad.Descender:=BEToN(FHHEad.Descender);
  420. FHHEad.LineGap:=BEToN(FHHEad.LineGap);
  421. FHHead.AdvanceWidthMax := BEToN(FHHead.AdvanceWidthMax);
  422. FHHEad.MinLeftSideBearing:=BEToN(FHHEad.MinLeftSideBearing);
  423. FHHEad.MinRightSideBearing:=BEToN(FHHEad.MinRightSideBearing);
  424. FHHEad.XMaxExtent:=BEToN(FHHEad.XMaxExtent);
  425. FHHEad.CaretSlopeRise:=BEToN(FHHEad.CaretSlopeRise);
  426. FHHEad.CaretSlopeRun:=BEToN(FHHEad.CaretSlopeRun);
  427. FHHEad.caretOffset := BEToN(FHHEad.caretOffset);
  428. FHHEad.metricDataFormat:=BEToN(FHHEad.metricDataFormat);
  429. FHHEad.numberOfHMetrics:=BEToN(FHHEad.numberOfHMetrics);
  430. end;
  431. procedure TTFFileInfo.ParseMaxp(AStream : TStream);
  432. begin
  433. AStream.ReadBuffer(FMaxP,SizeOf(TMaxP));
  434. With FMaxP do
  435. begin
  436. VersionNumber.Version := BEtoN(VersionNumber.Version);
  437. VersionNumber.Minor := FixMinorVersion(VersionNumber.Minor);
  438. numGlyphs:=BEtoN(numGlyphs);
  439. maxPoints:=BEtoN(maxPoints);
  440. maxContours:=BEtoN(maxContours);
  441. maxCompositePoints :=BEtoN(maxCompositePoints);
  442. maxCompositeContours :=BEtoN(maxCompositeContours);
  443. maxZones :=BEtoN(maxZones);
  444. maxTwilightPoints :=BEtoN(maxTwilightPoints);
  445. maxStorage :=BEtoN(maxStorage);
  446. maxFunctionDefs :=BEtoN(maxFunctionDefs);
  447. maxInstructionDefs :=BEtoN(maxInstructionDefs);
  448. maxStackElements :=BEtoN(maxStackElements);
  449. maxSizeOfInstructions :=BEtoN(maxSizeOfInstructions);
  450. maxComponentElements :=BEtoN(maxComponentElements);
  451. maxComponentDepth :=BEtoN(maxComponentDepth);
  452. end;
  453. end;
  454. procedure TTFFileInfo.ParseHmtx(AStream : TStream);
  455. var
  456. i : Integer;
  457. begin
  458. SetLength(FWidths,FHHead.numberOfHMetrics);
  459. AStream.ReadBuffer(FWidths[0],SizeOf(TLongHorMetric)*Length(FWidths));
  460. for I:=0 to FHHead.NumberOfHMetrics-1 do
  461. begin
  462. FWidths[I].AdvanceWidth:=BEtoN(FWidths[I].AdvanceWidth);
  463. FWidths[I].LSB:=BEtoN(FWidths[I].LSB);
  464. end;
  465. end;
  466. procedure TTFFileInfo.ParseCmap(AStream : TStream);
  467. var
  468. SegCount: Word;
  469. GiD,I,J,UE: Integer;
  470. TT,TableStartPos: LongWord;
  471. Segm : TUnicodeMapSegment;
  472. GlyphIDArray : Array of word;
  473. S : TStream;
  474. begin
  475. TableStartPos:=AStream.Position;
  476. FCMapH.Version:=ReadUInt16(AStream);
  477. FCMapH.SubtableCount:=ReadUInt16(AStream);
  478. SetLength(FSubtables,CMapH.SubtableCount);
  479. for I:= 0 to FCMapH.SubtableCount-1 do
  480. begin
  481. FSubtables[i].PlatformID:=ReadUInt16(AStream);
  482. FSubtables[i].EncodingID:=ReadUInt16(AStream);
  483. FSubtables[i].Offset:=ReadUInt32(AStream); // 4 bytes - Offset of subtable
  484. end;
  485. UE:=FCMapH.SubtableCount-1;
  486. if UE=-1 then
  487. // No CMap subtable entries, this is not an error, just exit.
  488. exit;
  489. While (UE>=0) and ((FSubtables[UE].PlatformID<>3) or (FSubtables[UE].EncodingID<> 1)) do
  490. Dec(UE);
  491. if (UE=-1) then
  492. exit;
  493. TT:=TableStartPos+FSubtables[UE].Offset;
  494. AStream.Position:=TT;
  495. FUnicodeMap.Format:= ReadUInt16(AStream); // 2 bytes - Format of subtable
  496. if (FUnicodeMap.Format<>4) then
  497. Raise ETTF.CreateFmt(rsErrUnexpectedUnicodeSubtable, [FUnicodeMap.Format]);
  498. FUnicodeMap.Length:=ReadUInt16(AStream);
  499. S:=TMemoryStream.Create;
  500. try
  501. // Speed up the process, read everything in a single mem block.
  502. S.CopyFrom(AStream,Int64(FUnicodeMap.Length)-4);
  503. S.Position:=0;
  504. FUnicodeMap.LanguageID:=ReadUInt16(S);
  505. FUnicodeMap.SegmentCount2:=ReadUInt16(S); // 2 bytes - Segments count
  506. FUnicodeMap.SearchRange:=ReadUInt16(S);
  507. FUnicodeMap.EntrySelector:=ReadUInt16(S);
  508. FUnicodeMap.RangeShift:=ReadUInt16(S);
  509. SegCount:=FUnicodeMap.SegmentCount2 div 2;
  510. SetLength(FUnicodeMapSegments,SegCount);
  511. for i:=0 to SegCount-1 do
  512. FUnicodeMapSegments[i].EndCode:=ReadUInt16(S);
  513. ReadUInt16(S);
  514. for i:=0 to SegCount-1 do
  515. FUnicodeMapSegments[i].StartCode:=ReadUInt16(S);
  516. for i:=0 to SegCount-1 do
  517. FUnicodeMapSegments[i].IDDelta:=ReadInt16(S);
  518. for i:=0 to SegCount-1 do
  519. FUnicodeMapSegments[i].IDRangeOffset:=ReadUInt16(S);
  520. UE:=S.Position;
  521. UE:=(S.Size-UE) div 2;
  522. SetLength(GlyphIDArray,UE);
  523. For J:=0 to UE-1 do
  524. GlyphIDArray[J]:=ReadUInt16(S);
  525. J:=0;
  526. for i:=0 to SegCount-1 do
  527. With FUnicodeMapSegments[i] do
  528. if (EndCode>J) then
  529. J:=EndCode;
  530. SetLength(Chars,J+1);
  531. for i:=0 to SegCount-1 do
  532. begin
  533. Segm:=FUnicodeMapSegments[i];
  534. for J:=Segm.StartCode to Segm.EndCode do
  535. if J<>$FFFF then // Last block has $FFFF as start/end code.
  536. begin
  537. if Segm.IDRangeOffset=0 then
  538. Gid:=J+Segm.IDDelta
  539. else
  540. begin
  541. Gid:=GlyphIDArray[Segm.IDRangeOffset div 2 + i-segcount - Segm.startCode+j];
  542. if (Gid>0) then
  543. Gid:= Gid+Segm.IDDelta;
  544. end;
  545. if (Gid>=65536) then
  546. Gid:=Gid-65536;
  547. if Gid>0 then
  548. Chars[J]:=Gid
  549. else
  550. Chars[J]:=0;
  551. end;
  552. end;
  553. finally
  554. S.Free;
  555. end;
  556. end;
  557. procedure TTFFileInfo.ParseName(AStream : TStream);
  558. var
  559. I,J,Count : Integer;
  560. StringOffset: Word;
  561. TableStartPos: LongWord;
  562. S : AnsiString;
  563. W : Widestring;
  564. N : TNameRecord;
  565. E : TNameEntries;
  566. WA : Array of word;
  567. begin
  568. TableStartPos:= AStream.Position; // memorize Table start position
  569. ReadUInt16(AStream); // skip 2 bytes - Format
  570. Count:=ReadUInt16(AStream); // 2 bytes
  571. StringOffset:=ReadUInt16(AStream); // 2 bytes
  572. E := FNameEntries;
  573. SetLength(E,Count);
  574. FillMem(@N, SizeOf(TNameRecord), 0);
  575. // Read Descriptors
  576. for I:=0 to Count-1 do
  577. begin
  578. AStream.ReadBuffer(N,SizeOf(TNameRecord));
  579. N.PlatFormID:=BeTon(N.PlatFormID);
  580. N.EncodingID:=BeTon(N.EncodingID);
  581. N.LanguageID:=BeTon(N.LanguageID);
  582. N.NameID:=BeTon(N.NameID);
  583. N.StringLength:=BeTon(N.StringLength);
  584. N.StringOffset:=BeToN(N.StringOffset);
  585. E[i].Info:=N;
  586. end; { for i ... }
  587. // Read Values
  588. for I:=0 to Count-1 do
  589. begin
  590. AStream.Position:=Int64(TableStartPos)+StringOffset+E[i].Info.StringOffset;
  591. if E[i].Info.EncodingID=1 then
  592. begin
  593. SetLength(WA,E[i].Info.StringLength div 2);
  594. SetLength(W,Length(WA));
  595. AStream.Read(WA[0],SizeOf(Word)*Length(W)); // 1 byte
  596. For J:=0 to Length(WA)-1 do
  597. W[J+1]:=WideChar(Beton(WA[J]));
  598. E[i].Value:=string(W);
  599. end
  600. else
  601. begin
  602. SetLength(S,E[i].Info.StringLength);
  603. AStream.Read(S[1],SizeOf(AnsiChar)*Length(S)); // 1 byte
  604. E[i].Value:=S;
  605. end;
  606. {$IFDEF gdebug}
  607. writeln('-------------------');
  608. writeln('LanguageID = ', E[i].Info.LanguageID);
  609. writeln('EncodingID = ', E[i].Info.EncodingID);
  610. writeln('NameID = ', E[i].Info.NameID);
  611. writeln('Value = ', E[i].Value);
  612. {$ENDIF}
  613. if (PostScriptName='')
  614. and (E[i].Info.NameID=NameIDPostScriptName)
  615. and (E[i].Info.EncodingID=NameMSEncodingUGL) then
  616. PostScriptName:=E[i].Value;
  617. if (FamilyName = '')
  618. and (E[i].Info.NameID = NameIDFontFamily)
  619. and (E[i].Info.LanguageID = 1033)
  620. and (E[i].Info.EncodingID = 1) then
  621. FamilyName := E[i].Value;
  622. if (HumanFriendlyName = '')
  623. and (E[i].Info.NameID = NameIDFullFontName)
  624. and (E[i].Info.LanguageID = 1033)
  625. and (E[i].Info.EncodingID = 1) then
  626. HumanFriendlyName := E[i].Value;
  627. end; { for i ... }
  628. end;
  629. procedure TTFFileInfo.ParseOS2(AStream : TStream);
  630. begin
  631. FillWord(FOS2Data,SizeOf(TOS2Data) div 2,0);
  632. // -18, so version 1 will not overflow
  633. AStream.ReadBuffer(FOS2Data,SizeOf(TOS2Data)-18);
  634. With FOS2Data do
  635. begin
  636. version:=BeToN(version);
  637. xAvgCharWidth:=BeToN(xAvgCharWidth);
  638. usWeightClass:=BeToN(usWeightClass);
  639. usWidthClass:=BeToN(usWidthClass);
  640. fsType:=BeToN(fsType);
  641. ySubscriptXSize:=BeToN(ySubscriptXSize);
  642. ySubscriptYSize:=BeToN(ySubscriptYSize);
  643. ySubscriptXOffset:=BeToN(ySubscriptXOffset);
  644. ySubscriptYOffset:=BeToN(ySubscriptYOffset);
  645. ySuperscriptXSize:=BeToN(ySuperscriptXSize);
  646. ySuperscriptYSize:=BeToN(ySuperscriptYSize);
  647. ySuperscriptXOffset:=BeToN(ySuperscriptXOffset);
  648. ySuperscriptYOffset:=BeToN(ySuperscriptYOffset);
  649. yStrikeoutSize:=BeToN(yStrikeoutSize);
  650. yStrikeoutPosition:=BeToN(yStrikeoutPosition);
  651. sFamilyClass:=BeToN(sFamilyClass);
  652. ulUnicodeRange1:=BeToN(ulUnicodeRange1);
  653. ulUnicodeRange2:=BeToN(ulUnicodeRange2);
  654. ulUnicodeRange3:=BeToN(ulUnicodeRange3);
  655. ulUnicodeRange4:=BeToN(ulUnicodeRange4);
  656. fsSelection:=BeToN(fsSelection);
  657. usFirstCharIndex:=BeToN(usFirstCharIndex);
  658. usLastCharIndex:=BeToN(usLastCharIndex);
  659. sTypoAscender:=BeToN(sTypoAscender);
  660. sTypoDescender:=BeToN(sTypoDescender);
  661. sTypoLineGap:=BeToN(sTypoLineGap);
  662. usWinAscent:=BeToN(usWinAscent);
  663. usWinDescent:=BeToN(usWinDescent);
  664. // We miss 7 fields
  665. end;
  666. With FOS2Data do
  667. begin
  668. // Read remaining 7 fields' data depending on version
  669. if Version>=1 then
  670. begin
  671. ulCodePageRange1:=ReadUInt32(AStream);
  672. ulCodePageRange2:=ReadUInt32(AStream);
  673. end;
  674. if Version>=2 then
  675. begin
  676. sxHeight:=ReadInt16(AStream);
  677. sCapHeight:=ReadInt16(AStream);
  678. usDefaultChar:=ReadUInt16(AStream);
  679. usBreakChar:=ReadUInt16(AStream);
  680. usMaxContext:=ReadUInt16(AStream);
  681. end;
  682. end;
  683. end;
  684. procedure TTFFileInfo.ParsePost(AStream : TStream);
  685. begin
  686. AStream.ReadBuffer(FPostScript,SizeOf(TPostScript));
  687. With FPostScript do
  688. begin
  689. Format.Version := BEtoN(Format.Version);
  690. Format.Minor := FixMinorVersion(Format.Minor);
  691. ItalicAngle:=BeToN(ItalicAngle);
  692. UnderlinePosition:=BeToN(UnderlinePosition);
  693. underlineThickness:=BeToN(underlineThickness);
  694. isFixedPitch:=BeToN(isFixedPitch);
  695. minMemType42:=BeToN(minMemType42);
  696. maxMemType42:=BeToN(maxMemType42);
  697. minMemType1:=BeToN(minMemType1);
  698. maxMemType1:=BeToN(maxMemType1);
  699. end;
  700. end;
  701. procedure TTFFileInfo.LoadFromFile(const AFileName: String);
  702. Var
  703. AStream: TFileStream;
  704. begin
  705. FFilename := AFilename;
  706. AStream:= TFileStream.Create(AFileName,fmOpenRead or fmShareDenyNone);
  707. try
  708. LoadFromStream(AStream);
  709. finally
  710. AStream.Free;
  711. end;
  712. end;
  713. procedure TTFFileInfo.LoadFromStream(AStream : TStream);
  714. var
  715. i: Integer;
  716. tt : TTTFTableType;
  717. begin
  718. FOriginalSize:= AStream.Size;
  719. AStream.ReadBuffer(FTableDir,Sizeof(TTableDirectory));
  720. With FTableDir do
  721. begin
  722. FontVersion.Version := BEtoN(FontVersion.Version);
  723. FontVersion.Minor := FixMinorVersion(FontVersion.Minor);
  724. Numtables:=BeToN(Numtables);
  725. SearchRange:=BeToN(SearchRange);
  726. EntrySelector:=BeToN(EntrySelector);
  727. RangeShift:=BeToN(RangeShift);
  728. end;
  729. SetLength(FTables,FTableDir.Numtables);
  730. AStream.ReadBuffer(FTables[0],FTableDir.NumTables*Sizeof(TTableDirectoryEntry));
  731. For I:=0 to Length(FTables)-1 do
  732. With FTables[I] do
  733. begin
  734. // note: Tag field doesn't require BEtoN processing.
  735. checkSum:=BeToN(checkSum);
  736. offset:=BeToN(offset);
  737. Length:=BeToN(Length);
  738. end;
  739. for I:=0 to FTableDir.NumTables-1 do
  740. begin
  741. TT:=GetTableType(FTables[I].Tag);
  742. if (TT<>ttUnknown) then
  743. begin
  744. AStream.Position:=FTables[i].Offset;
  745. Case TT of
  746. tthead: ParseHead(AStream);
  747. ttHhea: ParseHhea(AStream);
  748. ttmaxp: ParseMaxp(AStream);
  749. tthmtx: ParseHmtx(AStream);
  750. ttcmap: ParseCmap(AStream);
  751. ttname: ParseName(AStream);
  752. ttos2 : ParseOS2(AStream);
  753. ttPost: ParsePost(AStream);
  754. end;
  755. end;
  756. end;
  757. end;
  758. procedure TTFFileInfo.PrepareFontDefinition(const Encoding: string; Embed: Boolean);
  759. var
  760. I : Integer;
  761. begin
  762. if embed and not Embeddable then
  763. raise ETTF.Create(rsFontEmbeddingNotAllowed);
  764. PrepareEncoding(Encoding);
  765. // MissingWidth:=ToNatural(GetAdvanceWidth(Chars[CharCodes^[32]])); // AnsiChar(32) - Space character
  766. FMissingWidth := GetAdvanceWidth(Chars[CharCodes^[32]]); // AnsiChar(32) - Space character
  767. for I:=0 to 255 do
  768. begin
  769. if (CharCodes^[i]>=0) and (CharCodes^[i]<=High(Chars))
  770. and (GetAdvanceWidth(Chars[CharCodes^[i]])> 0) and (CharNames^[i]<> '.notdef') then
  771. CharWidth[I]:= ToNatural(GetAdvanceWidth(Chars[CharCodes^[I]]))
  772. else
  773. CharWidth[I]:= FMissingWidth;
  774. end;
  775. end;
  776. procedure TTFFileInfo.PrepareEncoding(const AEncoding: AnsiString);
  777. var
  778. TE : TTTFEncoding;
  779. V : PTTFEncodingValues;
  780. begin
  781. TE:=GetEncoding(AEncoding);
  782. if (TE<>teUnknown) then
  783. GetEncodingTables(Te,CharNames,CharCodes);
  784. // Needed to make difference
  785. GetEncodingTables(Te,CharBase,V);
  786. end;
  787. function TTFFileInfo.MakeDifferences: AnsiString;
  788. var
  789. i,l: Integer;
  790. begin
  791. Result:= '';
  792. L:= 0;
  793. for i:=32 to 255 do
  794. if CharNames^[i]<>CharBase^[i] then
  795. begin
  796. if (i<>l+1) then
  797. Result:= Result+IntToStr(i)+' ';
  798. l:=i;
  799. Result:= Result+'/'+CharNames^[i]+' ';
  800. end;
  801. end;
  802. function TTFFileInfo.Bold: Boolean;
  803. begin
  804. Bold:=(FOS2Data.fsSelection and 32)<>0;
  805. end;
  806. function TTFFileInfo.StemV: SmallInt;
  807. begin
  808. if Bold then
  809. StemV:= 120
  810. else
  811. StemV:= 70;
  812. end;
  813. function TTFFileInfo.Embeddable: Boolean;
  814. begin
  815. With FOS2Data do
  816. Result:=(FsType<> 2) and ((FsType and 512)= 0);
  817. end;
  818. function TTFFileInfo.Ascender: SmallInt;
  819. begin
  820. Result:=FOS2Data.sTypoAscender;
  821. end;
  822. function TTFFileInfo.Descender: SmallInt;
  823. begin
  824. Result := FOS2Data.sTypoDescender;
  825. end;
  826. function TTFFileInfo.Leading: SmallInt;
  827. begin
  828. Result := FOS2Data.sTypoLineGap;
  829. end;
  830. function TTFFileInfo.CapHeight: SmallInt;
  831. begin
  832. With FOS2Data do
  833. begin
  834. if Version>= 2 then
  835. Result:=sCapHeight
  836. else
  837. Result:=Ascender;
  838. end;
  839. end;
  840. function TTFFileInfo.GetGlyphIndex(AValue: word): word;
  841. begin
  842. result := Chars[AValue];
  843. end;
  844. function TTFFileInfo.GetTableDirEntry(const ATableName: string; var AEntry: TTableDirectoryEntry): boolean;
  845. var
  846. i: integer;
  847. begin
  848. FillMem(@AEntry, SizeOf(TTableDirectoryEntry), 0);
  849. Result := False;
  850. for i := Low(Tables) to High(Tables) do
  851. begin
  852. if CompareStr(Tables[i].Tag, ATableName) = 0 then
  853. begin
  854. Result := True;
  855. AEntry := Tables[i];
  856. Exit;
  857. end;
  858. end;
  859. end;
  860. function TTFFileInfo.GetAdvanceWidth(AIndex: word): word;
  861. var
  862. i: SizeInt;
  863. begin
  864. // There may be more glyphs than elements in the array, in which
  865. // case the last entry is to be used.
  866. // https://docs.microsoft.com/en-us/typography/opentype/spec/hmtx
  867. i := Length(Widths);
  868. if AIndex >= i then
  869. Dec(i)
  870. else
  871. i := AIndex;
  872. Result := Widths[i].AdvanceWidth;
  873. end;
  874. function TTFFileInfo.ItalicAngle: single;
  875. begin
  876. Result := FPostScript.ItalicAngle / 65536.0;
  877. end;
  878. function TTFFileInfo.BBox: string;
  879. var
  880. i: integer;
  881. begin
  882. Result := '';
  883. for i := 0 to 3 do
  884. begin
  885. if i > 0 then
  886. Result := Result + ' ';
  887. Result := Result + IntToStr(ToNatural(FHead.BBox[I]));
  888. end;
  889. end;
  890. destructor TTFFileInfo.Destroy;
  891. begin
  892. SetLength(FNameEntries, 0);
  893. inherited Destroy;
  894. end;
  895. { Implementation based on a PHP ttf reader unit.
  896. http://www.4real.gr/TTF.php.txt }
  897. function TTFFileInfo.FixMinorVersion(const AMinor: word): word;
  898. var
  899. d: double;
  900. begin
  901. d := AMinor / 65536;
  902. Result := round(d*10000);
  903. end;
  904. function TTFFileInfo.GetMissingWidth: integer;
  905. begin
  906. if FMissingWidth = 0 then
  907. begin
  908. FMissingWidth := GetAdvanceWidth(Chars[CharCodes^[32]]); // 32 is in reference to the Space character
  909. end;
  910. Result := FMissingWidth;
  911. end;
  912. function TTFFileInfo.ToNatural(AUnit: Smallint): Smallint;
  913. begin
  914. if FHead.UnitsPerEm=0 then
  915. Result:=0
  916. else
  917. Result:=Round(AUnit*1000/FHead.UnitsPerEm);
  918. end;
  919. function TTFFileInfo.Flags: Integer;
  920. begin
  921. Result := 32;
  922. if FPostScript.IsFixedPitch<>0 then
  923. Result := Result+1;
  924. if FPostScript.ItalicAngle<>0 then
  925. Result := Result+64;
  926. end;
  927. end.