fpttfsubsetter.pp 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293
  1. {
  2. This file is part of the Free Component Library (FCL)
  3. Copyright (c) 2016 by Graeme Geldenhuys
  4. This unit creates a new TTF subset font file, reducing the file
  5. size in the process. This is primarily so the new font file can
  6. be embedded in PDF documents.
  7. See the file COPYING.FPC, included in this distribution,
  8. for details about the copyright.
  9. This program is distributed in the hope that it will be useful,
  10. but WITHOUT ANY WARRANTY; without even the implied warranty of
  11. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
  12. **********************************************************************}
  13. {$IFNDEF FPC_DOTTEDUNITS}
  14. unit fpTTFSubsetter;
  15. {$ENDIF FPC_DOTTEDUNITS}
  16. {$mode objfpc}{$H+}
  17. { $R+}
  18. // enable this define for more verbose output
  19. {.$define gdebug}
  20. interface
  21. {$IFDEF FPC_DOTTEDUNITS}
  22. uses
  23. System.Classes,
  24. System.SysUtils,
  25. System.Contnrs,
  26. FpPdf.Ttf.Parser,
  27. FpPdf.FontTextMapping;
  28. {$ELSE FPC_DOTTEDUNITS}
  29. uses
  30. Classes,
  31. SysUtils,
  32. contnrs,
  33. fpparsettf,
  34. FPFontTextMapping;
  35. {$ENDIF FPC_DOTTEDUNITS}
  36. type
  37. ETTFSubsetter = class(Exception);
  38. TArrayUInt32 = array of UInt32;
  39. // forward declaration
  40. TGIDList = class;
  41. TGIDListEnumerator = class;
  42. { TFontSubsetter }
  43. TFontSubsetter = class(TObject)
  44. private
  45. FPrefix: string;
  46. FHasAddedCompoundReferences: boolean; // one glyph made up of multiple glyphs
  47. FKeepTables: TStrings;
  48. FFontInfo: TTFFileInfo;
  49. FGlyphIDList: TTextMappingList;
  50. FStream: TFileStream; // original TTF file
  51. FGlyphLocations: array of UInt32;
  52. FGlyphIDs: TGIDList;
  53. function Int32HighestOneBit(const AValue: integer): integer;
  54. function Int32Log2(const AValue: integer): integer;
  55. function ToUInt32(const AHigh, ALow: UInt32): UInt32;
  56. function ToUInt32(const ABytes: AnsiString): UInt32;
  57. function GetRawTable(const ATableName: AnsiString): TMemoryStream;
  58. function WriteFileHeader(AOutStream: TStream; const nTables: integer): uint32;
  59. function WriteTableHeader(AOutStream: TStream; const ATag: AnsiString; const AOffset: UInt32; const AData: TStream): UInt32;
  60. function GetNewGlyphId(const OldGid: integer): Integer;
  61. procedure WriteTableBodies(AOutStream: TStream; const ATables: TStringList);
  62. procedure UpdateOrigGlyphIDList;
  63. // AGlyphID is the original GlyphID in the original TTF file
  64. function GetCharIDfromGlyphID(const AGlyphID: uint32): uint32;
  65. { Copy glyph data as-is for a specific glyphID. }
  66. function GetRawGlyphData(const AGlyphID: UInt16): TMemoryStream;
  67. procedure LoadLocations;
  68. // Stream writing functions.
  69. procedure WriteInt16(AStream: TStream; const AValue: Int16); inline;
  70. procedure WriteUInt16(AStream: TStream; const AValue: UInt16); inline;
  71. procedure WriteInt32(AStream: TStream; const AValue: Int32); inline;
  72. procedure WriteUInt32(AStream: TStream; const AValue: UInt32); inline;
  73. function ReadInt16(AStream: TStream): Int16; inline;
  74. function ReadUInt32(AStream: TStream): UInt32; inline;
  75. function ReadUInt16(AStream: TStream): UInt16; inline;
  76. procedure AddCompoundReferences;
  77. function buildHeadTable: TStream;
  78. function buildHheaTable: TStream;
  79. function buildMaxpTable: TStream;
  80. function buildFpgmTable: TStream;
  81. function buildPrepTable: TStream;
  82. function buildCvtTable: TStream;
  83. function buildGlyfTable(var newOffsets: TArrayUInt32): TStream;
  84. function buildLocaTable(var newOffsets: TArrayUInt32): TStream;
  85. function buildCmapTable: TStream;
  86. function buildHmtxTable: TStream;
  87. public
  88. constructor Create(const AFont: TTFFileInfo; const AGlyphIDList: TTextMappingList);
  89. constructor Create(const AFont: TTFFileInfo);
  90. destructor Destroy; override;
  91. procedure SaveToFile(const AFileName: String);
  92. procedure SaveToStream(const AStream: TStream);
  93. // Add the given Unicode codepoint to the subset.
  94. procedure Add(const ACodePoint: uint32);
  95. // The prefix to add to the font's PostScript name.
  96. property Prefix: string read FPrefix write FPrefix;
  97. end;
  98. TGIDItem = class(TObject)
  99. private
  100. FGID: integer;
  101. FGlyphData: TMemoryStream;
  102. FIsCompoundGlyph: boolean;
  103. FNewGID: integer;
  104. public
  105. constructor Create;
  106. destructor Destroy; override;
  107. property IsCompoundGlyph: boolean read FIsCompoundGlyph write FIsCompoundGlyph;
  108. property GID: integer read FGID write FGID;
  109. property GlyphData: TMemoryStream read FGlyphData write FGlyphData;
  110. property NewGID: integer read FNewGID write FNewGID;
  111. end;
  112. TGIDList = class(TObject)
  113. private
  114. FList: TFPObjectList;
  115. function GetCount: integer;
  116. function GetItems(i: integer): TGIDItem;
  117. procedure SetItems(i: integer; const AValue: TGIDItem);
  118. public
  119. constructor Create;
  120. destructor Destroy; override;
  121. function Add(const GID: Integer): integer; overload;
  122. function Add(const AObject: TGIDItem): integer; overload;
  123. procedure Clear;
  124. function Contains(const GID: integer): boolean;
  125. function GetEnumerator: TGIDListEnumerator;
  126. function GetNewGlyphID(const OriginalGID: integer): integer;
  127. procedure Sort;
  128. property Count: integer read GetCount;
  129. property Items[i: integer]: TGIDItem read GetItems write SetItems; default;
  130. end;
  131. TGIDListEnumerator = class(TObject)
  132. private
  133. FIndex: Integer;
  134. FList: TGIDList;
  135. public
  136. constructor Create(AList: TGIDList);
  137. function GetCurrent: TGIDItem;
  138. function MoveNext: Boolean;
  139. property Current: TGIDItem read GetCurrent;
  140. end;
  141. implementation
  142. {$IFDEF FPC_DOTTEDUNITS}
  143. uses
  144. System.Math;
  145. {$ELSE FPC_DOTTEDUNITS}
  146. uses
  147. math;
  148. {$ENDIF FPC_DOTTEDUNITS}
  149. resourcestring
  150. rsErrFontInfoNotAssigned = 'FontInfo was not assigned';
  151. rsErrFailedToReadFromStream = 'Failed to read from file stream';
  152. rsErrCantFindFontFile = 'Can''t find the actual TTF font file.';
  153. rsErrGlyphLocationsNotLoaded = 'Glyph Location data has not been loaded yet.';
  154. const
  155. PAD_BUF: array[ 1..3 ] of Byte = ( $0, $0, $0 );
  156. { TFontSubsetter }
  157. { The method simply returns the int value with a single one-bit, in the position
  158. of the highest-order one-bit in the specified value, or zero if the specified
  159. value is itself equal to zero. }
  160. function TFontSubsetter.Int32HighestOneBit(const AValue: integer): integer;
  161. var
  162. i: integer;
  163. begin
  164. i := AValue;
  165. i := i or (i shr 1);
  166. i := i or (i shr 2);
  167. i := i or (i shr 4);
  168. i := i or (i shr 8);
  169. i := i or (i shr 16);
  170. // i := i or (i shr 32);
  171. Result := i - (i shr 1);
  172. end;
  173. function TFontSubsetter.Int32Log2(const AValue: integer): integer;
  174. begin
  175. if AValue <= 0 then
  176. raise Exception.Create('Illegal argument');
  177. // Result := 31 - Integer.numberOfLeadingZeros(n);
  178. Result := Floor(Log10(AValue) / Log10(2));
  179. end;
  180. function TFontSubsetter.ToUInt32(const AHigh, ALow: UInt32): UInt32;
  181. begin
  182. result := ((AHigh and $FFFF) shl 16) or (ALow and $FFFF);
  183. end;
  184. function TFontSubsetter.ToUInt32(const ABytes: AnsiString): UInt32;
  185. var
  186. b: array of Byte absolute ABytes;
  187. begin
  188. Result := (b[0] and $FF) shl 24
  189. or (b[1] and $FF) shl 16
  190. or (b[2] and $FF) shl 8
  191. or (b[3] and $FF);
  192. end;
  193. function TFontSubsetter.GetRawTable(const ATableName: AnsiString): TMemoryStream;
  194. var
  195. lEntry: TTableDirectoryEntry;
  196. begin
  197. Result := nil;
  198. FillMem(@lEntry, SizeOf(TTableDirectoryEntry), 0);
  199. if not FFontInfo.GetTableDirEntry(ATableName, lEntry) then
  200. Exit;
  201. Result := TMemoryStream.Create;
  202. FStream.Seek(lEntry.offset, soFromBeginning);
  203. if Result.CopyFrom(FStream, lEntry.Length) <> lEntry.Length then
  204. raise ETTF.Create('GetRawTable: ' + rsErrFailedToReadFromStream);
  205. end;
  206. { AOutStream: the data output stream.
  207. nTables: the number of font tables.
  208. result: the file offset of the first TTF table to write. }
  209. function TFontSubsetter.WriteFileHeader(AOutStream: TStream; const nTables: integer): uint32;
  210. var
  211. mask: integer;
  212. searchRange: integer;
  213. entrySelector: integer;
  214. rangeShift: integer;
  215. begin
  216. WriteUInt32(AOutStream, $00010000);
  217. WriteUInt16(AOutStream, nTables);
  218. mask := Int32HighestOneBit(nTables);
  219. searchRange := mask * 16;
  220. WriteUInt16(AOutStream, searchRange);
  221. entrySelector := Int32Log2(mask);
  222. WriteUInt16(AOutStream, entrySelector);
  223. rangeShift := 16 * nTables - searchRange;
  224. WriteUInt16(AOutStream, rangeShift);
  225. result := $00010000 + ToUInt32(nTables, searchRange) + ToUInt32(entrySelector, rangeShift);
  226. end;
  227. function TFontSubsetter.WriteTableHeader(AOutStream: TStream; const ATag: AnsiString; const AOffset: UInt32;
  228. const AData: TStream): UInt32;
  229. var
  230. checksum, w: UInt32;
  231. n: integer;
  232. lByte: Byte;
  233. begin
  234. AData.Position := 0;
  235. checksum := 0;
  236. w := 0;
  237. for n := 0 to AData.Size-1 do
  238. begin
  239. lByte := AData.ReadByte;
  240. //checksum := checksum + (((lByte and $FF) shl 24) - n mod 4 * 8);
  241. w := w or (lByte shl ((3 - (n mod 4))*8));
  242. if n mod 4 = 3 then begin
  243. Inc(checksum, w);
  244. w := 0;
  245. end;
  246. end;
  247. Inc(checksum, w);
  248. //checksum := checksum and $FFFFFFFF;
  249. AOutStream.WriteBuffer(Pointer(ATag)^, 4); // Tag is always 4 bytes - written as-is, no NtoBE() required
  250. WriteUInt32(AOutStream, checksum);
  251. WriteUInt32(AOutStream, AOffset);
  252. WriteUInt32(AOutStream, AData.Size);
  253. {$ifdef gdebug}
  254. writeln(Format('tag: "%s" CRC: %8.8x offset: %8.8x (%2:7d bytes) size: %8.8x (%3:7d bytes)', [ATag, checksum, AOffset, AData.Size]));
  255. {$endif}
  256. // account for the checksum twice, once for the header field, once for the content itself
  257. Result := ToUInt32(ATag) + checksum + checksum + AOffset + AData.Size;
  258. end;
  259. function TFontSubsetter.GetNewGlyphId(const OldGid: integer): Integer;
  260. var
  261. itm: TGIDItem;
  262. begin
  263. result := -1;
  264. for itm in FGlyphIDs do
  265. begin
  266. if itm.GID = OldGID then
  267. begin
  268. Result := itm.NewGID;
  269. exit;
  270. end;
  271. end;
  272. end;
  273. procedure TFontSubsetter.WriteTableBodies(AOutStream: TStream; const ATables: TStringList);
  274. var
  275. i: integer;
  276. n: uint64;
  277. lData: TStream;
  278. begin
  279. for i := 0 to ATables.Count-1 do
  280. begin
  281. lData := TStream(ATables.Objects[i]);
  282. if lData <> nil then
  283. begin
  284. lData.Position := 0;
  285. n := lData.Size;
  286. AOutStream.CopyFrom(lData, lData.Size);
  287. end;
  288. if (n mod 4) <> 0 then
  289. begin
  290. {$ifdef gdebug}
  291. writeln('Padding applied at the end of ', ATables[i], ': ', 4 - (n mod 4), ' byte(s)');
  292. {$endif}
  293. AOutStream.WriteBuffer(PAD_BUF, 4 - (n mod 4));
  294. end;
  295. end;
  296. end;
  297. { This updates the original GlyphIDList passed in to the constructor - normally
  298. done by fcl-pdf. This allows fcl-pdf to use the NewGlyphID values in its
  299. generated PDF output. }
  300. procedure TFontSubsetter.UpdateOrigGlyphIDList;
  301. var
  302. i: integer;
  303. itm: TGIDItem;
  304. begin
  305. for itm in FGlyphIDs do
  306. begin
  307. for i := 0 to FGlyphIDList.Count-1 do
  308. begin
  309. if FGlyphIDList[i].GlyphID = itm.GID then
  310. begin
  311. FGlyphIDList[i].NewGlyphID := itm.NewGID;
  312. break;
  313. end;
  314. end;
  315. end;
  316. end;
  317. function TFontSubsetter.GetCharIDfromGlyphID(const AGlyphID: uint32): uint32;
  318. var
  319. i: integer;
  320. begin
  321. Result := 0;
  322. for i := 0 to Length(FFontInfo.Chars)-1 do
  323. if FFontInfo.Chars[i] = AGlyphID then
  324. begin
  325. Result := i;
  326. Exit;
  327. end;
  328. end;
  329. function TFontSubsetter.GetRawGlyphData(const AGlyphID: UInt16): TMemoryStream;
  330. var
  331. lGlyf: TTableDirectoryEntry;
  332. lSize: UInt16;
  333. begin
  334. Result := nil;
  335. if Length(FGlyphLocations) < 2 then
  336. raise ETTF.Create(rsErrGlyphLocationsNotLoaded);
  337. FillMem(@lGlyf, SizeOf(TTableDirectoryEntry), 0);
  338. FFontInfo.GetTableDirEntry(TTFTableNames[ttglyf], lGlyf);
  339. lSize := FGlyphLocations[AGlyphID+1] - FGlyphLocations[AGlyphID];
  340. Result := TMemoryStream.Create;
  341. if lSize > 0 then
  342. begin
  343. FStream.Seek(lGlyf.offset + FGlyphLocations[AGlyphID], soFromBeginning);
  344. if Result.CopyFrom(FStream, lSize) <> lSize then
  345. raise ETTF.Create('GetRawGlyphData: ' + rsErrFailedToReadFromStream)
  346. else
  347. Result.Position := 0;
  348. end;
  349. end;
  350. procedure TFontSubsetter.LoadLocations;
  351. var
  352. lLocaEntry: TTableDirectoryEntry;
  353. lGlyf: TTableDirectoryEntry;
  354. ms: TMemoryStream;
  355. numLocations: integer;
  356. n: integer;
  357. begin
  358. FillMem(@lGlyf, SizeOf(TTableDirectoryEntry), 0);
  359. FillMem(@lLocaEntry, SizeOf(TTableDirectoryEntry), 0);
  360. FFontInfo.GetTableDirEntry(TTFTableNames[ttglyf], lGlyf);
  361. if FFontInfo.GetTableDirEntry(TTFTableNames[ttloca], lLocaEntry) then
  362. begin
  363. ms := TMemoryStream.Create;
  364. try
  365. FStream.Seek(lLocaEntry.offset, soFromBeginning);
  366. if ms.CopyFrom(FStream, lLocaEntry.Length) <> lLocaEntry.Length then
  367. raise ETTF.Create('LoadLocations: ' + rsErrFailedToReadFromStream)
  368. else
  369. ms.Position := 0;
  370. if FFontInfo.Head.IndexToLocFormat = 0 then
  371. begin
  372. // Short offsets
  373. numLocations := lLocaEntry.Length shr 1;
  374. {$IFDEF gDEBUG}
  375. Writeln('Number of Glyph locations ( 16 bits offsets ): ', numLocations );
  376. {$ENDIF}
  377. SetLength(FGlyphLocations, numLocations);
  378. for n := 0 to numLocations-1 do
  379. FGlyphLocations[n] := BEtoN(ms.ReadWord) * 2;
  380. end
  381. else
  382. begin
  383. // Long offsets
  384. numLocations := lLocaEntry.Length shr 2;
  385. {$IFDEF gDEBUG}
  386. Writeln('Number of Glyph locations ( 32 bits offsets ): ', numLocations );
  387. {$ENDIF}
  388. SetLength(FGlyphLocations, numLocations);
  389. for n := 0 to numLocations-1 do
  390. FGlyphLocations[n] := BEtoN(ms.ReadDWord);
  391. end;
  392. finally
  393. ms.Free;
  394. end;
  395. end
  396. else
  397. begin
  398. {$ifdef gDEBUG}
  399. Writeln('WARNING: ''loca'' table is not found.');
  400. {$endif}
  401. end;
  402. end;
  403. procedure TFontSubsetter.WriteInt16(AStream: TStream; const AValue: Int16);
  404. begin
  405. AStream.WriteBuffer(NtoBE(AValue), 2);
  406. end;
  407. procedure TFontSubsetter.WriteUInt16(AStream: TStream; const AValue: UInt16);
  408. begin
  409. AStream.WriteWord(NtoBE(AValue));
  410. end;
  411. procedure TFontSubsetter.WriteInt32(AStream: TStream; const AValue: Int32);
  412. begin
  413. AStream.WriteBuffer(NtoBE(AValue), 4);
  414. end;
  415. procedure TFontSubsetter.WriteUInt32(AStream: TStream; const AValue: UInt32);
  416. begin
  417. AStream.WriteDWord(NtoBE(AValue));
  418. end;
  419. function TFontSubsetter.ReadInt16(AStream: TStream): Int16;
  420. begin
  421. Result:=Int16(ReadUInt16(AStream));
  422. end;
  423. function TFontSubsetter.ReadUInt32(AStream: TStream): UInt32;
  424. begin
  425. Result:=0;
  426. AStream.ReadBuffer(Result,SizeOf(Result));
  427. Result:=BEtoN(Result);
  428. end;
  429. function TFontSubsetter.ReadUInt16(AStream: TStream): UInt16;
  430. begin
  431. Result:=0;
  432. AStream.ReadBuffer(Result,SizeOf(Result));
  433. Result:=BEtoN(Result);
  434. end;
  435. procedure TFontSubsetter.AddCompoundReferences;
  436. var
  437. GlyphIDsToAdd: TStringList;
  438. n: integer;
  439. gs: TMemoryStream;
  440. buf: TGlyphHeader;
  441. i: integer;
  442. flags: uint16;
  443. glyphIndex: uint16;
  444. hasNested: boolean;
  445. begin
  446. if FHasAddedCompoundReferences then
  447. Exit;
  448. FHasAddedCompoundReferences := True;
  449. LoadLocations;
  450. repeat
  451. GlyphIDsToAdd := TStringList.Create;
  452. GlyphIDsToAdd.Duplicates := dupIgnore;
  453. GlyphIDsToAdd.Sorted := True;
  454. for n := 0 to FGlyphIDs.Count-1 do
  455. begin
  456. if not Assigned(FGlyphIDs[n].GlyphData) then
  457. FGlyphIDs[n].GlyphData := GetRawGlyphData(FGlyphIDs[n].GID);
  458. gs := FGlyphIDs[n].GlyphData;
  459. gs.Position := 0;
  460. if gs.Size > 0 then
  461. begin
  462. FillMem(@buf, SizeOf(TGlyphHeader), 0);
  463. gs.ReadBuffer(buf, SizeOf(Buf));
  464. {$IFDEF gDEBUG}
  465. writeln(' glyph data size: ', gs.Size);
  466. {$ENDIF}
  467. if buf.numberOfContours = -1 then
  468. begin
  469. FGlyphIDs[n].IsCompoundGlyph := True;
  470. {$IFDEF gDEBUG}
  471. writeln(' numberOfContours: ', buf.numberOfContours);
  472. {$ENDIF}
  473. repeat
  474. flags := ReadUInt16(gs);
  475. glyphIndex := ReadUInt16(gs);
  476. // find compound glyph IDs and add them to the GlyphIDsToAdd list
  477. if not FGlyphIDs.Contains(glyphIndex) then
  478. begin
  479. {$IFDEF gDEBUG}
  480. writeln(Format(' glyphIndex: %.4x (%0:d) ', [glyphIndex]));
  481. {$ENDIF}
  482. GlyphIDsToAdd.Add(IntToStr(glyphIndex));
  483. end;
  484. // ARG_1_AND_2_ARE_WORDS
  485. if (flags and (1 shl 0)) <> 0 then
  486. ReadUInt32(gs)
  487. else
  488. ReadUInt16(gs);
  489. // WE_HAVE_A_TWO_BY_TWO
  490. if (flags and (1 shl 7)) <> 0 then
  491. begin
  492. ReadUInt32(gs);
  493. ReadUInt32(gs);
  494. end
  495. // WE_HAVE_AN_X_AND_Y_SCALE
  496. else if (flags and (1 shl 6)) <> 0 then
  497. begin
  498. ReadUInt32(gs);
  499. end
  500. // WE_HAVE_A_SCALE
  501. else if (flags and (1 shl 3)) <> 0 then
  502. begin
  503. ReadUInt16(gs);
  504. end;
  505. until (flags and (1 shl 5)) = 0; // MORE_COMPONENTS
  506. end; { if buf.numberOfContours = -1 }
  507. end; { if gs.Size > 0 }
  508. end; { for n ... FGlyphIDs.Count-1 }
  509. if GlyphIDsToAdd.Count > 0 then
  510. begin
  511. for i := 0 to GlyphIDsToAdd.Count-1 do
  512. begin
  513. glyphIndex := StrToInt(GlyphIDsToAdd[i]);
  514. FGlyphIDs.Add(glyphIndex);
  515. end;
  516. end;
  517. hasNested := GlyphIDsToAdd.Count > 0;
  518. {$IFDEF gDEBUG}
  519. if hasNested then
  520. writeln('------------------');
  521. {$ENDIF}
  522. FreeAndNil(GlyphIDsToAdd);
  523. until (hasNested = false);
  524. end;
  525. function TFontSubsetter.buildHeadTable: TStream;
  526. var
  527. t: THead;
  528. rec: THead;
  529. i: Integer;
  530. begin
  531. Result := TMemoryStream.Create;
  532. t := FFontInfo.Head;
  533. FillMem(@rec, SizeOf(THead), 0);
  534. rec.FileVersion.Version := NtoBE(t.FileVersion.Version);
  535. rec.FontRevision.Version := NtoBE(t.FontRevision.Version);
  536. rec.CheckSumAdjustment := 0;
  537. rec.MagicNumber := NtoBE(t.MagicNumber);
  538. rec.Flags := NtoBE(t.Flags);
  539. rec.UnitsPerEm := NtoBE(t.UnitsPerEm);
  540. rec.Created := NtoBE(t.Created);
  541. rec.Modified := NtoBE(t.Modified);
  542. For i := 0 to 3 do
  543. rec.BBox[i] := NtoBE(t.BBox[i]);
  544. rec.MacStyle := NtoBE(t.MacStyle);
  545. rec.LowestRecPPEM := NtoBE(t.LowestRecPPEM);
  546. rec.FontDirectionHint := NtoBE(t.FontDirectionHint);
  547. // force long format of 'loca' table. ie: 'loca' table offsets are in 4-Bytes each, not Words.
  548. rec.IndexToLocFormat := NtoBE(Int16(1)); //NtoBE(t.IndexToLocFormat);
  549. rec.glyphDataFormat := NtoBE(t.glyphDataFormat);
  550. Result.WriteBuffer(rec, SizeOf(THead));
  551. end;
  552. function TFontSubsetter.buildHheaTable: TStream;
  553. var
  554. t: THHead;
  555. rec: THHead;
  556. hmetrics: UInt16;
  557. begin
  558. Result := TMemoryStream.Create;
  559. t := FFontInfo.HHead;
  560. FillMem(@rec, SizeOf(THHead), 0);
  561. rec.TableVersion.Version := NtoBE(t.TableVersion.Version);
  562. rec.Ascender := NtoBE(t.Ascender);
  563. rec.Descender := NtoBE(t.Descender);
  564. rec.LineGap := NtoBE(t.LineGap);
  565. rec.AdvanceWidthMax := NtoBE(t.AdvanceWidthMax);
  566. rec.MinLeftSideBearing := NtoBE(t.MinLeftSideBearing);
  567. rec.MinRightSideBearing := NtoBE(t.MinRightSideBearing);
  568. rec.XMaxExtent := NtoBE(t.XMaxExtent);
  569. rec.CaretSlopeRise := NtoBE(t.CaretSlopeRise);
  570. rec.CaretSlopeRun := NtoBE(t.CaretSlopeRun);
  571. rec.caretOffset := NtoBE(t.caretOffset);
  572. rec.metricDataFormat := NtoBE(t.metricDataFormat);
  573. // rec.numberOfHMetrics := NtoBE(t.numberOfHMetrics);
  574. hmetrics := FGlyphIDs.Count;
  575. if (FGlyphIDs.Items[FGlyphIDs.Count-1].GID >= t.numberOfHMetrics) and (not FGlyphIDs.Contains(t.numberOfHMetrics-1)) then
  576. inc(hmetrics);
  577. rec.numberOfHMetrics := NtoBE(hmetrics);
  578. Result.WriteBuffer(rec, SizeOf(THHead));
  579. end;
  580. function TFontSubsetter.buildMaxpTable: TStream;
  581. var
  582. t: TMaxP;
  583. rec: TMaxP;
  584. lCount: word;
  585. begin
  586. Result := TMemoryStream.Create;
  587. t := FFontInfo.MaxP;
  588. FillMem(@rec, SizeOf(TMaxP), 0);
  589. rec.VersionNumber.Version := NtoBE(t.VersionNumber.Version);
  590. lCount := FGlyphIDs.Count;
  591. rec.numGlyphs := NtoBE(lCount);
  592. rec.maxPoints := NtoBE(t.maxPoints);
  593. rec.maxContours := NtoBE(t.maxContours);
  594. rec.maxCompositePoints := NtoBE(t.maxCompositePoints);
  595. rec.maxCompositeContours := NtoBE(t.maxCompositeContours);
  596. rec.maxZones := NtoBE(t.maxZones);
  597. rec.maxTwilightPoints := NtoBE(t.maxTwilightPoints);
  598. rec.maxStorage := NtoBE(t.maxStorage);
  599. rec.maxFunctionDefs := NtoBE(t.maxFunctionDefs);
  600. rec.maxInstructionDefs := NtoBE(t.maxInstructionDefs);
  601. rec.maxStackElements := NtoBE(t.maxStackElements);
  602. rec.maxSizeOfInstructions := NtoBE(t.maxSizeOfInstructions);
  603. rec.maxComponentElements := NtoBE(t.maxComponentElements);
  604. rec.maxComponentDepth := NtoBE(t.maxComponentDepth);
  605. Result.WriteBuffer(rec, SizeOf(TMaxP));
  606. end;
  607. function TFontSubsetter.buildFpgmTable: TStream;
  608. begin
  609. Result := GetRawTable('fpgm');
  610. if Assigned(Result) then
  611. Result.Position := 0;
  612. end;
  613. function TFontSubsetter.buildPrepTable: TStream;
  614. begin
  615. Result := GetRawTable('prep');
  616. if Assigned(Result) then
  617. Result.Position := 0;
  618. end;
  619. function TFontSubsetter.buildCvtTable: TStream;
  620. begin
  621. Result := GetRawTable('cvt ');
  622. if Assigned(Result) then
  623. Result.Position := 0;
  624. end;
  625. function TFontSubsetter.buildGlyfTable(var newOffsets: TArrayUInt32): TStream;
  626. var
  627. n: integer;
  628. lOffset: uint32;
  629. lLen: uint32;
  630. gs: TMemoryStream;
  631. buf: TGlyphHeader;
  632. flags: uint16;
  633. glyphIndex: uint16;
  634. begin
  635. lOffset := 0;
  636. Result := TMemoryStream.Create;
  637. LoadLocations;
  638. { - Assign new glyph indexes
  639. - Retrieve glyph data if it doesn't yet exist (retrieved from original TTF file) }
  640. for n := 0 to FGlyphIDs.Count-1 do
  641. begin
  642. FGlyphIDs[n].NewGID := n;
  643. if not Assigned(FGlyphIDs[n].GlyphData) then
  644. FGlyphIDs[n].GlyphData := GetRawGlyphData(FGlyphIDs[n].GID);
  645. end;
  646. { - Now fix GlyphID references in Compound Glyphs to point to new GlyphIDs }
  647. for n := 0 to FGlyphIDs.Count-1 do
  648. begin
  649. if not FGlyphIDs[n].IsCompoundGlyph then
  650. Continue;
  651. {$IFDEF gDEBUG}
  652. writeln(Format('found compound glyph: %.4x glyphID: %d', [0, FGlyphIDs[n].GID]));
  653. {$ENDIF}
  654. gs := TMemoryStream(FGlyphIDs[n].GlyphData);
  655. gs.Position := 0;
  656. if gs.Size > 0 then
  657. begin
  658. FillMem(@buf, SizeOf(TGlyphHeader), 0);
  659. gs.ReadBuffer(buf, SizeOf(Buf));
  660. if buf.numberOfContours = -1 then
  661. begin
  662. repeat
  663. flags := ReadUInt16(gs);
  664. lOffset := gs.Position;
  665. glyphIndex := ReadUInt16(gs);
  666. // now write new GlyphID in it's place.
  667. gs.Position := lOffset;
  668. glyphIndex := FGlyphIDs.GetNewGlyphID(glyphIndex);
  669. WriteUInt16(gs, glyphIndex);
  670. // ARG_1_AND_2_ARE_WORDS
  671. if (flags and (1 shl 0)) <> 0 then
  672. ReadUInt32(gs)
  673. else
  674. ReadUInt16(gs);
  675. // WE_HAVE_A_TWO_BY_TWO
  676. if (flags and (1 shl 7)) <> 0 then
  677. begin
  678. ReadUInt32(gs);
  679. ReadUInt32(gs);
  680. end
  681. // WE_HAVE_AN_X_AND_Y_SCALE
  682. else if (flags and (1 shl 6)) <> 0 then
  683. begin
  684. ReadUInt32(gs);
  685. end
  686. // WE_HAVE_A_SCALE
  687. else if (flags and (1 shl 3)) <> 0 then
  688. begin
  689. ReadUInt16(gs);
  690. end;
  691. until (flags and (1 shl 5)) = 0; // MORE_COMPONENTS
  692. end; { if buf.numberOfContours = -1 }
  693. end; { if gs.Size > 0 }
  694. end; { for n ... FGlyphIDList.Count-1 }
  695. // write all glyph data to resulting data stream
  696. lOffset := 0;
  697. for n := 0 to FGlyphIDs.Count-1 do
  698. begin
  699. newOffsets[n] := lOffset;
  700. lOffset := lOffset + FGlyphIDs[n].GlyphData.Size;
  701. FGlyphIDs[n].GlyphData.Position := 0;
  702. Result.CopyFrom(FGlyphIDs[n].GlyphData, FGlyphIDs[n].GlyphData.Size);
  703. // 4-byte alignment
  704. if (lOffset mod 4) <> 0 then
  705. begin
  706. lLen := 4 - (lOffset mod 4);
  707. Result.WriteBuffer(PAD_BUF, lLen);
  708. lOffset := lOffset + lLen;
  709. end;
  710. end;
  711. newOffsets[n+1] := lOffset;
  712. end;
  713. // write as UInt32 as defined in head.indexToLocFormat field (long format).
  714. function TFontSubsetter.buildLocaTable(var newOffsets: TArrayUInt32): TStream;
  715. var
  716. i: integer;
  717. begin
  718. Result := TMemoryStream.Create;
  719. for i := 0 to Length(newOffsets)-1 do
  720. WriteUInt32(Result, newOffsets[i]);
  721. end;
  722. function TFontSubsetter.buildCmapTable: TStream;
  723. const
  724. // platform
  725. PLATFORM_UNICODE = 0;
  726. PLATFORM_MACINTOSH = 1;
  727. // value 2 is reserved; do not use
  728. PLATFORM_WINDOWS = 3;
  729. // Mac encodings
  730. ENCODING_MAC_ROMAN = 0;
  731. // Windows encodings
  732. ENCODING_WIN_SYMBOL = 0; // Unicode, non-standard character set
  733. ENCODING_WIN_UNICODE_BMP = 1; // Unicode BMP (UCS-2)
  734. ENCODING_WIN_SHIFT_JIS = 2;
  735. ENCODING_WIN_BIG5 = 3;
  736. ENCODING_WIN_PRC = 4;
  737. ENCODING_WIN_WANSUNG = 5;
  738. ENCODING_WIN_JOHAB = 6;
  739. ENCODING_WIN_UNICODE_FULL = 10; // Unicode Full (UCS-4)
  740. // Unicode encodings
  741. ENCODING_UNICODE_1_0 = 0;
  742. ENCODING_UNICODE_1_1 = 1;
  743. ENCODING_UNICODE_2_0_BMP = 3;
  744. ENCODING_UNICODE_2_0_FULL = 4;
  745. var
  746. segCount: UInt16;
  747. searchRange: UInt16;
  748. i: integer;
  749. startCode: Array of Integer;
  750. endCode: Array of Integer;
  751. idDelta: Array of Integer;
  752. lastChar: integer;
  753. prevChar: integer;
  754. lastGid: integer;
  755. curGid: integer;
  756. itm: TTextMapping;
  757. begin
  758. Result := TMemoryStream.Create;
  759. SetLength(startCode, FGlyphIDList.Count + 1);
  760. SetLength(endCode, FGlyphIDList.Count + 1);
  761. SetLength(idDelta, FGlyphIDList.Count + 1);
  762. // cmap header
  763. WriteUInt16(Result, 0); // version
  764. WriteUInt16(Result, 1); // numberSubTables
  765. // encoding record
  766. WriteUInt16(Result, PLATFORM_WINDOWS); // platformID
  767. WriteUInt16(Result, ENCODING_WIN_UNICODE_BMP); // platformSpecificID
  768. WriteUInt32(Result, 4 * 2 + 4); // offset
  769. // build Format 4 subtable (Unicode BMP)
  770. lastChar := 0;
  771. prevChar := lastChar;
  772. lastGid := GetNewGlyphId(FGlyphIDList[0].GlyphID);
  773. segCount := 0;
  774. for i := 0 to FGlyphIDList.Count-1 do
  775. begin
  776. itm := FGlyphIDList[i];
  777. if itm.CharID > $FFFF then
  778. raise Exception.Create('non-BMP Unicode character');
  779. curGid := GetNewGlyphId(itm.GlyphID);
  780. if (itm.CharID <> FGlyphIDList[prevChar].CharID+1) or ((curGid - lastGid) <> (itm.CharID - FGlyphIDList[lastChar].CharID)) then
  781. begin
  782. if (lastGid <> 0) then
  783. begin
  784. { don't emit ranges, which map to GID 0, the undef glyph is emitted at the very last segment }
  785. startCode[segCount] := FGlyphIDList[lastChar].CharID;
  786. endCode[segCount] := FGlyphIDList[prevChar].CharID;
  787. idDelta[segCount] := lastGid - FGlyphIDList[lastChar].CharID;
  788. inc(segCount);
  789. end
  790. else if not (FGlyphIDList[lastChar].CharID = FGlyphIDList[prevChar].CharID) then
  791. begin
  792. { shorten ranges which start with GID 0 by one }
  793. startCode[segCount] := FGlyphIDList[lastChar].CharID + 1;
  794. endCode[segCount] := FGlyphIDList[prevChar].CharID;
  795. idDelta[segCount] := lastGid - FGlyphIDList[lastChar].CharID;
  796. inc(segCount);
  797. end;
  798. lastGid := curGid;
  799. lastChar := i;
  800. end;
  801. prevChar := i;
  802. end;
  803. // trailing segment
  804. startCode[segCount] := FGlyphIDList[lastChar].CharID;
  805. endCode[segCount] := FGlyphIDList[prevChar].CharID;
  806. idDelta[segCount] := lastGid - FGlyphIDList[lastChar].CharID;
  807. inc(segCount);
  808. // GID 0
  809. startCode[segCount] := $FFFF;
  810. endCode[segCount] := $FFFF;
  811. idDelta[segCount] := 1;
  812. inc(segCount);
  813. // write format 4 subtable
  814. searchRange := trunc(2 * Power(2, Floor(Log2(segCount))));
  815. WriteUInt16(Result, 4); // format
  816. WriteUInt16(Result, 8 * 2 + segCount * 4*2); // length
  817. WriteUInt16(Result, 0); // language
  818. WriteUInt16(Result, segCount * 2); // segCountX2
  819. WriteUInt16(Result, searchRange); // searchRange
  820. WriteUInt16(Result, trunc(log2(searchRange / 2))); // entrySelector
  821. WriteUInt16(Result, 2 * segCount - searchRange); // rangeShift
  822. // write endCode
  823. for i := 0 to segCount-1 do
  824. WriteUInt16(Result, endCode[i]);
  825. // reservedPad
  826. WriteUInt16(Result, 0);
  827. // startCode
  828. for i := 0 to segCount-1 do
  829. WriteUInt16(Result, startCode[i]);
  830. // idDelta
  831. for i := 0 to segCount-1 do
  832. begin
  833. {$IFDEF gDEBUG}
  834. writeln(Format(' idDelta[%d] = %d', [i, idDelta[i]]));
  835. {$ENDIF}
  836. WriteInt16(Result, idDelta[i]);
  837. end;
  838. // idRangeOffset
  839. for i := 0 to segCount-1 do
  840. WriteUInt16(Result, 0);
  841. end;
  842. function TFontSubsetter.buildHmtxTable: TStream;
  843. var
  844. n: integer;
  845. GID: longint;
  846. LastGID: longint;
  847. begin
  848. Result := TMemoryStream.Create;
  849. LastGID := Length(FFontInfo.Widths)-1;
  850. for n := 0 to FGlyphIDs.Count-1 do
  851. begin
  852. GID := FGlyphIDs[n].GID;
  853. if GID > LastGID then
  854. GID := LastGID;
  855. WriteUInt16(Result, FFontInfo.Widths[GID].AdvanceWidth);
  856. WriteInt16(Result, FFontInfo.Widths[GID].LSB);
  857. end;
  858. end;
  859. constructor TFontSubsetter.Create(const AFont: TTFFileInfo; const AGlyphIDList: TTextMappingList);
  860. var
  861. i: integer;
  862. begin
  863. FFontInfo := AFont;
  864. if not Assigned(FFontInfo) then
  865. raise ETTFSubsetter.Create(rsErrFontInfoNotAssigned);
  866. FGlyphIDList := AGlyphIDList;
  867. FGlyphIDs := TGIDList.Create;
  868. // always copy GID 0
  869. FGlyphIDs.Add(0);
  870. FKeepTables := TStringList.Create;
  871. FHasAddedCompoundReferences := False;
  872. FPrefix := '';
  873. // create a default list
  874. FKeepTables.Add('head');
  875. FKeepTables.Add('hhea');
  876. FKeepTables.Add('maxp');
  877. FKeepTables.Add('hmtx');
  878. FKeepTables.Add('cmap');
  879. FKeepTables.Add('fpgm');
  880. FKeepTables.Add('prep');
  881. FKeepTables.Add('cvt ');
  882. FKeepTables.Add('loca');
  883. FKeepTables.Add('glyf');
  884. if Assigned(FGlyphIDList) then
  885. begin
  886. FGlyphIDList.Sort;
  887. for i := 0 to FGlyphIDList.Count-1 do
  888. FGlyphIDs.Add(FGlyphIDList[i].GlyphID);
  889. end;
  890. if FFontInfo.Filename <> '' then
  891. FStream := TFileStream.Create(FFontInfo.FileName, fmOpenRead or fmShareDenyNone)
  892. else
  893. raise ETTF.Create(rsErrCantFindFontFile);
  894. end;
  895. constructor TFontSubsetter.Create(const AFont: TTFFileInfo);
  896. begin
  897. Create(AFont, nil);
  898. end;
  899. destructor TFontSubsetter.Destroy;
  900. var
  901. i: integer;
  902. begin
  903. // the owner of FGlyphIDList doesn't need the GlyphData information
  904. for i := 0 to FGlyphIDList.Count-1 do
  905. FGlyphIDList[i].GlyphData.Free;
  906. FStream.Free;
  907. FKeepTables.Free;
  908. FreeAndNil(FGlyphIDs);
  909. inherited Destroy;
  910. end;
  911. procedure TFontSubsetter.SaveToFile(const AFileName: String);
  912. var
  913. fs: TFileStream;
  914. begin
  915. fs := TFileStream.Create(AFileName, fmCreate);
  916. try
  917. SaveToStream(fs);
  918. finally
  919. FreeAndNil(fs);
  920. end;
  921. end;
  922. procedure TFontSubsetter.SaveToStream(const AStream: TStream);
  923. var
  924. checksum: UInt32;
  925. offset: int64;
  926. head: TStream;
  927. hhea: TStream;
  928. maxp: TStream;
  929. hmtx: TStream;
  930. cmap: TStream;
  931. fpgm: TStream;
  932. prep: TStream;
  933. cvt: TStream;
  934. loca: TStream;
  935. glyf: TStream;
  936. newLoca: TArrayUInt32;
  937. tables: TStringList;
  938. i: integer;
  939. o: uint64;
  940. p: uint64;
  941. lPadding: byte;
  942. begin
  943. FGlyphIDs.Sort;
  944. // resolve compound glyph references
  945. AddCompoundReferences;
  946. // always copy GID 0
  947. FGlyphIDList.Add(0, 0);
  948. FGlyphIDList.Sort;
  949. SetLength(newLoca, FGlyphIDs.Count+1);
  950. head := buildHeadTable();
  951. hhea := buildHheaTable();
  952. maxp := buildMaxpTable();
  953. fpgm := buildFpgmTable();
  954. prep := buildPrepTable();
  955. cvt := buildCvtTable();
  956. glyf := buildGlyfTable(newLoca);
  957. loca := buildLocaTable(newLoca);
  958. cmap := buildCmapTable();
  959. hmtx := buildHmtxTable();
  960. tables := TStringList.Create;
  961. tables.CaseSensitive := True;
  962. if Assigned(cmap) then
  963. tables.AddObject('cmap', cmap);
  964. if Assigned(glyf) then
  965. tables.AddObject('glyf', glyf);
  966. tables.AddObject('head', head);
  967. tables.AddObject('hhea', hhea);
  968. tables.AddObject('hmtx', hmtx);
  969. if Assigned(loca) then
  970. tables.AddObject('loca', loca);
  971. tables.AddObject('maxp', maxp);
  972. tables.AddObject('fpgm', fpgm);
  973. tables.AddObject('prep', prep);
  974. tables.AddObject('cvt ', cvt);
  975. tables.Sort;
  976. // calculate checksum
  977. checksum := writeFileHeader(AStream, tables.Count);
  978. offset := 12 + (16 * tables.Count);
  979. lPadding := 0;
  980. for i := 0 to tables.Count-1 do
  981. begin
  982. if tables.Objects[i] <> nil then
  983. begin
  984. checksum := checksum + WriteTableHeader(AStream, tables.Strings[i], offset, TStream(tables.Objects[i]));
  985. p := TStream(tables.Objects[i]).Size;
  986. // table bodies must be 4-byte aligned - calculate the padding so the tableHeader.Offset field can reflect that.
  987. if (p mod 4) = 0 then
  988. lPadding := 0
  989. else
  990. lPadding := 4 - (p mod 4);
  991. o := p + lPadding;
  992. offset := offset + o;
  993. end;
  994. end;
  995. checksum := UInt32($B1B0AFBA) - checksum;
  996. // update head.ChecksumAdjustment field
  997. head.Seek(8, soBeginning);
  998. WriteUInt32(head, checksum);
  999. // write table bodies
  1000. WriteTableBodies(AStream, tables);
  1001. for i := 0 to tables.Count-1 do
  1002. TStream(tables.Objects[i]).Free;
  1003. tables.Free;
  1004. UpdateOrigGlyphIDList;
  1005. end;
  1006. procedure TFontSubsetter.Add(const ACodePoint: uint32);
  1007. var
  1008. gid: uint32;
  1009. begin
  1010. gid := FFontInfo.Chars[ACodePoint];
  1011. if gid <> 0 then
  1012. begin
  1013. FGlyphIDList.Add(ACodePoint, FFontInfo.Chars[ACodePoint]);
  1014. FGlyphIDs.Add(gid);
  1015. end;
  1016. end;
  1017. { TGIDList }
  1018. function TGIDList.GetCount: integer;
  1019. begin
  1020. Result := FList.Count;
  1021. end;
  1022. function TGIDList.GetItems(i: integer): TGIDItem;
  1023. begin
  1024. Result := FList[i] as TGIDItem;
  1025. end;
  1026. procedure TGIDList.SetItems(i: integer; const AValue: TGIDItem);
  1027. begin
  1028. FList[i] := AValue;
  1029. end;
  1030. constructor TGIDList.Create;
  1031. begin
  1032. FList := TFPObjectList.Create;
  1033. end;
  1034. destructor TGIDList.Destroy;
  1035. begin
  1036. FList.Free;
  1037. inherited Destroy;
  1038. end;
  1039. function TGIDList.Add(const GID: Integer): integer;
  1040. var
  1041. itm: TGIDItem;
  1042. begin
  1043. itm := TGIDItem.Create;
  1044. itm.GID := GID;
  1045. result := Add(itm);
  1046. end;
  1047. function TGIDList.Add(const AObject: TGIDItem): integer;
  1048. begin
  1049. Result := FList.Add(AObject);
  1050. end;
  1051. procedure TGIDList.Clear;
  1052. begin
  1053. FList.Clear;
  1054. end;
  1055. function TGIDList.Contains(const GID: integer): boolean;
  1056. var
  1057. itm: TGIDItem;
  1058. begin
  1059. Result := False;
  1060. for itm in self do
  1061. begin
  1062. if itm.GID = GID then
  1063. begin
  1064. Result := True;
  1065. Exit;
  1066. end;
  1067. end;
  1068. end;
  1069. function TGIDList.GetEnumerator: TGIDListEnumerator;
  1070. begin
  1071. Result := TGIDListEnumerator.Create(self);
  1072. end;
  1073. function TGIDList.GetNewGlyphID(const OriginalGID: integer): integer;
  1074. var
  1075. itm: TGIDItem;
  1076. begin
  1077. Result := -1;
  1078. for itm in self do
  1079. begin
  1080. if itm.GID = OriginalGID then
  1081. begin
  1082. Result := itm.NewGID;
  1083. Exit;
  1084. end;
  1085. end;
  1086. end;
  1087. function CompareByGID(A, B: TGIDItem): Integer; inline;
  1088. begin
  1089. if A.GID < B.GID then
  1090. Result := -1
  1091. else if A.GID > B.GID then
  1092. Result := 1
  1093. else
  1094. Result := 0;
  1095. end;
  1096. function CompareByGIDPtr(A, B: Pointer): Integer;
  1097. begin
  1098. Result := CompareByGID(TGIDItem(A), TGIDItem(B));
  1099. end;
  1100. procedure TGIDList.Sort;
  1101. begin
  1102. FList.Sort(@CompareByGIDPtr);
  1103. end;
  1104. { TGIDListEnumerator }
  1105. constructor TGIDListEnumerator.Create(AList: TGIDList);
  1106. begin
  1107. FIndex := -1;
  1108. FList := AList;
  1109. end;
  1110. function TGIDListEnumerator.GetCurrent: TGIDItem;
  1111. begin
  1112. Result := FList[FIndex];
  1113. end;
  1114. function TGIDListEnumerator.MoveNext: Boolean;
  1115. begin
  1116. Result := FIndex < (FList.Count-1);
  1117. if Result then
  1118. Inc(FIndex);
  1119. end;
  1120. { TGIDItem }
  1121. constructor TGIDItem.Create;
  1122. begin
  1123. FGID := -1;
  1124. FNewGID := -1;
  1125. FGlyphData := nil;
  1126. FIsCompoundGlyph := False;
  1127. end;
  1128. destructor TGIDItem.Destroy;
  1129. begin
  1130. FreeAndNil(FGlyphData);
  1131. inherited Destroy;
  1132. end;
  1133. end.