mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-04-06 14:47:55 +02:00
* Forgot to add subsetter font
git-svn-id: trunk@35084 -
This commit is contained in:
parent
b7083402cf
commit
4406ec7e68
1
.gitattributes
vendored
1
.gitattributes
vendored
@ -2600,6 +2600,7 @@ packages/fcl-pdf/src/fpparsettf.pp svneol=native#text/plain
|
||||
packages/fcl-pdf/src/fppdf.pp svneol=native#text/plain
|
||||
packages/fcl-pdf/src/fpttf.pp svneol=native#text/plain
|
||||
packages/fcl-pdf/src/fpttfencodings.pp svneol=native#text/plain
|
||||
packages/fcl-pdf/src/fpttfsubsetter.pp svneol=native#text/plain
|
||||
packages/fcl-pdf/tests/fonts/README.txt svneol=native#text/plain
|
||||
packages/fcl-pdf/tests/fpparsettf_test.pas svneol=native#text/plain
|
||||
packages/fcl-pdf/tests/fppdf_test.pas svneol=native#text/plain
|
||||
|
@ -35,11 +35,15 @@ begin
|
||||
T:=P.Targets.AddUnit('src/fpparsettf.pp');
|
||||
With T do
|
||||
Dependencies.AddUnit('fpttfencodings');
|
||||
T:=P.Targets.AddUnit('src/fpttfsubsetter.pp');
|
||||
With T do
|
||||
Dependencies.AddUnit('fpparsettf');
|
||||
T:=P.Targets.AddUnit('src/fpttf.pp');
|
||||
T:=P.Targets.AddUnit('src/fppdf.pp');
|
||||
With T do
|
||||
begin
|
||||
Dependencies.AddUnit('fpparsettf');
|
||||
Dependencies.AddUnit('fpttfsubsetter');
|
||||
end;
|
||||
|
||||
// md5.ref
|
||||
|
990
packages/fcl-pdf/src/fpttfsubsetter.pp
Normal file
990
packages/fcl-pdf/src/fpttfsubsetter.pp
Normal file
@ -0,0 +1,990 @@
|
||||
{
|
||||
This file is part of the Free Component Library (FCL)
|
||||
Copyright (c) 2016 by Graeme Geldenhuys
|
||||
|
||||
This unit creates a new TTF subset font file, reducing the file
|
||||
size in the process. This is primarily so the new font file can
|
||||
be embedded in PDF documents.
|
||||
|
||||
See the file COPYING.FPC, included in this distribution,
|
||||
for details about the copyright.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
|
||||
**********************************************************************}
|
||||
unit fpTTFSubsetter;
|
||||
|
||||
{$mode objfpc}{$H+}
|
||||
|
||||
{ $R+}
|
||||
|
||||
// enable this define for more verbose output
|
||||
{.$define gdebug}
|
||||
|
||||
interface
|
||||
|
||||
uses
|
||||
Classes,
|
||||
SysUtils,
|
||||
fpparsettf,
|
||||
FPFontTextMapping;
|
||||
|
||||
type
|
||||
ETTFSubsetter = class(Exception);
|
||||
|
||||
TArrayUInt32 = array of UInt32;
|
||||
|
||||
|
||||
TFontSubsetter = class(TObject)
|
||||
private
|
||||
FPrefix: string;
|
||||
FHasAddedCompoundReferences: boolean; // one glyph made up of multiple glyphs
|
||||
FKeepTables: TStrings;
|
||||
FFontInfo: TTFFileInfo;
|
||||
FGlyphIDList: TTextMappingList;
|
||||
FStream: TFileStream; // original TTF file
|
||||
FGlyphLocations: array of UInt32;
|
||||
function Int32HighestOneBit(const AValue: integer): integer;
|
||||
function Int32Log2(const AValue: integer): integer;
|
||||
function ToUInt32(const AHigh, ALow: UInt32): UInt32;
|
||||
function ToUInt32(const ABytes: AnsiString): UInt32;
|
||||
function GetRawTable(const ATableName: AnsiString): TMemoryStream;
|
||||
function WriteFileHeader(AOutStream: TStream; const nTables: integer): uint32;
|
||||
function WriteTableHeader(AOutStream: TStream; const ATag: AnsiString; const AOffset: UInt32; const AData: TStream): uint32;
|
||||
procedure WriteTableBodies(AOutStream: TStream; const ATables: TStringList);
|
||||
// AGlyphID is the original GlyphID in the original TTF file
|
||||
function GetCharIDfromGlyphID(const AGlyphID: uint32): uint32;
|
||||
{ Copy glyph data as-is for a specific glyphID. }
|
||||
function GetRawGlyphData(const AGlyphID: UInt16): TMemoryStream;
|
||||
procedure LoadLocations;
|
||||
// Stream writing functions.
|
||||
procedure WriteInt16(AStream: TStream; const AValue: Int16); inline;
|
||||
procedure WriteUInt32(AStream: TStream; const AValue: UInt32); inline;
|
||||
procedure WriteUInt16(AStream: TStream; const AValue: UInt16); inline;
|
||||
function ReadInt16(AStream: TStream): Int16; inline;
|
||||
function ReadUInt32(AStream: TStream): UInt32; inline;
|
||||
function ReadUInt16(AStream: TStream): UInt16; inline;
|
||||
|
||||
procedure AddCompoundReferences;
|
||||
function buildHeadTable: TStream;
|
||||
function buildHheaTable: TStream;
|
||||
function buildMaxpTable: TStream;
|
||||
function buildFpgmTable: TStream;
|
||||
function buildPrepTable: TStream;
|
||||
function buildCvtTable: TStream;
|
||||
function buildGlyfTable(var newOffsets: TArrayUInt32): TStream;
|
||||
function buildLocaTable(var newOffsets: TArrayUInt32): TStream;
|
||||
function buildCmapTable: TStream;
|
||||
function buildHmtxTable: TStream;
|
||||
public
|
||||
constructor Create(const AFont: TTFFileInfo; const AGlyphIDList: TTextMappingList);
|
||||
constructor Create(const AFont: TTFFileInfo);
|
||||
destructor Destroy; override;
|
||||
procedure SaveToFile(const AFileName: String);
|
||||
procedure SaveToStream(const AStream: TStream);
|
||||
// Add the given Unicode codepoint to the subset.
|
||||
procedure Add(const ACodePoint: uint32);
|
||||
// The prefix to add to the font's PostScript name.
|
||||
property Prefix: string read FPrefix write FPrefix;
|
||||
end;
|
||||
|
||||
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
math;
|
||||
|
||||
resourcestring
|
||||
rsErrFontInfoNotAssigned = 'FontInfo was not assigned';
|
||||
rsErrFailedToReadFromStream = 'Failed to read from file stream';
|
||||
rsErrCantFindFontFile = 'Can''t find the actual TTF font file.';
|
||||
rsErrGlyphLocationsNotLoaded = 'Glyph Location data has not been loaded yet.';
|
||||
|
||||
const
|
||||
PAD_BUF: array[ 1..3 ] of Byte = ( $0, $0, $0 );
|
||||
|
||||
|
||||
{ TFontSubsetter }
|
||||
|
||||
{ The method simply returns the int value with a single one-bit, in the position
|
||||
of the highest-order one-bit in the specified value, or zero if the specified
|
||||
value is itself equal to zero. }
|
||||
function TFontSubsetter.Int32HighestOneBit(const AValue: integer): integer;
|
||||
var
|
||||
i: integer;
|
||||
begin
|
||||
i := AValue;
|
||||
i := i or (i shr 1);
|
||||
i := i or (i shr 2);
|
||||
i := i or (i shr 4);
|
||||
i := i or (i shr 8);
|
||||
i := i or (i shr 16);
|
||||
// i := i or (i shr 32);
|
||||
Result := i - (i shr 1);
|
||||
end;
|
||||
|
||||
function TFontSubsetter.Int32Log2(const AValue: integer): integer;
|
||||
begin
|
||||
if AValue <= 0 then
|
||||
raise Exception.Create('Illegal argument');
|
||||
// Result := 31 - Integer.numberOfLeadingZeros(n);
|
||||
|
||||
Result := Floor(Log10(AValue) / Log10(2));
|
||||
end;
|
||||
|
||||
function TFontSubsetter.ToUInt32(const AHigh, ALow: UInt32): UInt32;
|
||||
begin
|
||||
result := ((AHigh and $FFFF) shl 16) or (ALow and $FFFF);
|
||||
end;
|
||||
|
||||
function TFontSubsetter.ToUInt32(const ABytes: AnsiString): UInt32;
|
||||
var
|
||||
b: array of Byte absolute ABytes;
|
||||
begin
|
||||
Result := (b[0] and $FF) shl 24
|
||||
or (b[1] and $FF) shl 16
|
||||
or (b[2] and $FF) shl 8
|
||||
or (b[3] and $FF);
|
||||
end;
|
||||
|
||||
function TFontSubsetter.GetRawTable(const ATableName: AnsiString): TMemoryStream;
|
||||
var
|
||||
lEntry: TTableDirectoryEntry;
|
||||
begin
|
||||
Result := nil;
|
||||
FillMem(@lEntry, SizeOf(TTableDirectoryEntry), 0);
|
||||
if not FFontInfo.GetTableDirEntry(ATableName, lEntry) then
|
||||
Exit;
|
||||
|
||||
Result := TMemoryStream.Create;
|
||||
FStream.Seek(lEntry.offset, soFromBeginning);
|
||||
if Result.CopyFrom(FStream, lEntry.Length) <> lEntry.Length then
|
||||
raise ETTF.Create('GetRawTable: ' + rsErrFailedToReadFromStream);
|
||||
end;
|
||||
|
||||
{ AOutStream: the data output stream.
|
||||
nTables: the number of font tables.
|
||||
result: the file offset of the first TTF table to write. }
|
||||
function TFontSubsetter.WriteFileHeader(AOutStream: TStream; const nTables: integer): uint32;
|
||||
var
|
||||
mask: integer;
|
||||
searchRange: integer;
|
||||
entrySelector: integer;
|
||||
rangeShift: integer;
|
||||
begin
|
||||
WriteUInt32(AOutStream, $00010000);
|
||||
WriteUInt16(AOutStream, nTables);
|
||||
|
||||
mask := Int32HighestOneBit(nTables);
|
||||
searchRange := mask * 16;
|
||||
WriteUInt16(AOutStream, searchRange);
|
||||
|
||||
entrySelector := Int32Log2(mask);
|
||||
WriteUInt16(AOutStream, entrySelector);
|
||||
|
||||
rangeShift := 16 * nTables - searchRange;
|
||||
WriteUInt16(AOutStream, rangeShift);
|
||||
|
||||
result := $00010000 + ToUInt32(nTables, searchRange) + ToUInt32(entrySelector, rangeShift);
|
||||
end;
|
||||
|
||||
function TFontSubsetter.WriteTableHeader(AOutStream: TStream; const ATag: AnsiString; const AOffset: UInt32;
|
||||
const AData: TStream): uint32;
|
||||
var
|
||||
checksum: UInt32;
|
||||
n: integer;
|
||||
lByte: Byte;
|
||||
begin
|
||||
AData.Position := 0;
|
||||
checksum := 0;
|
||||
|
||||
for n := 0 to AData.Size-1 do
|
||||
begin
|
||||
lByte := AData.ReadByte;
|
||||
checksum := checksum + (((lByte and $FF) shl 24) - n mod 4 * 8);
|
||||
end;
|
||||
checksum := checksum and $FFFFFFFF;
|
||||
|
||||
AOutStream.WriteBuffer(Pointer(ATag)^, 4); // Tag is always 4 bytes - written as-is, no NtoBE() required
|
||||
WriteUInt32(AOutStream, checksum);
|
||||
WriteUInt32(AOutStream, AOffset);
|
||||
WriteUInt32(AOutStream, AData.Size);
|
||||
|
||||
{$ifdef gdebug}
|
||||
writeln(Format('tag: "%s" CRC: %8.8x offset: %8.8x (%2:7d bytes) size: %8.8x (%3:7d bytes)', [ATag, checksum, AOffset, AData.Size]));
|
||||
{$endif}
|
||||
|
||||
// account for the checksum twice, once for the header field, once for the content itself
|
||||
Result := ToUInt32(ATag) + checksum + checksum + AOffset + AData.Size;
|
||||
end;
|
||||
|
||||
procedure TFontSubsetter.WriteTableBodies(AOutStream: TStream; const ATables: TStringList);
|
||||
var
|
||||
i: integer;
|
||||
n: uint64;
|
||||
lData: TStream;
|
||||
begin
|
||||
for i := 0 to ATables.Count-1 do
|
||||
begin
|
||||
lData := TStream(ATables.Objects[i]);
|
||||
if lData <> nil then
|
||||
begin
|
||||
lData.Position := 0;
|
||||
n := lData.Size;
|
||||
AOutStream.CopyFrom(lData, lData.Size);
|
||||
end;
|
||||
if (n mod 4) <> 0 then
|
||||
begin
|
||||
{$ifdef gdebug}
|
||||
writeln('Padding applied at the end of ', ATables[i], ': ', 4 - (n mod 4), ' byte(s)');
|
||||
{$endif}
|
||||
AOutStream.WriteBuffer(PAD_BUF, 4 - (n mod 4));
|
||||
end;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TFontSubsetter.GetCharIDfromGlyphID(const AGlyphID: uint32): uint32;
|
||||
var
|
||||
i: integer;
|
||||
begin
|
||||
Result := 0;
|
||||
for i := 0 to Length(FFontInfo.Chars)-1 do
|
||||
if FFontInfo.Chars[i] = AGlyphID then
|
||||
begin
|
||||
Result := i;
|
||||
Exit;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TFontSubsetter.GetRawGlyphData(const AGlyphID: UInt16): TMemoryStream;
|
||||
var
|
||||
lGlyf: TTableDirectoryEntry;
|
||||
lSize: UInt16;
|
||||
begin
|
||||
Result := nil;
|
||||
if Length(FGlyphLocations) < 2 then
|
||||
raise ETTF.Create(rsErrGlyphLocationsNotLoaded);
|
||||
FillMem(@lGlyf, SizeOf(TTableDirectoryEntry), 0);
|
||||
FFontInfo.GetTableDirEntry(TTFTableNames[ttglyf], lGlyf);
|
||||
|
||||
lSize := FGlyphLocations[AGlyphID+1] - FGlyphLocations[AGlyphID];
|
||||
Result := TMemoryStream.Create;
|
||||
if lSize > 0 then
|
||||
begin
|
||||
FStream.Seek(lGlyf.offset + FGlyphLocations[AGlyphID], soFromBeginning);
|
||||
if Result.CopyFrom(FStream, lSize) <> lSize then
|
||||
raise ETTF.Create('GetRawGlyphData: ' + rsErrFailedToReadFromStream)
|
||||
else
|
||||
Result.Position := 0;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFontSubsetter.LoadLocations;
|
||||
var
|
||||
lLocaEntry: TTableDirectoryEntry;
|
||||
lGlyf: TTableDirectoryEntry;
|
||||
ms: TMemoryStream;
|
||||
numLocations: integer;
|
||||
n: integer;
|
||||
begin
|
||||
FillMem(@lGlyf, SizeOf(TTableDirectoryEntry), 0);
|
||||
FillMem(@lLocaEntry, SizeOf(TTableDirectoryEntry), 0);
|
||||
|
||||
FFontInfo.GetTableDirEntry(TTFTableNames[ttglyf], lGlyf);
|
||||
if FFontInfo.GetTableDirEntry(TTFTableNames[ttloca], lLocaEntry) then
|
||||
begin
|
||||
ms := TMemoryStream.Create;
|
||||
try
|
||||
FStream.Seek(lLocaEntry.offset, soFromBeginning);
|
||||
if ms.CopyFrom(FStream, lLocaEntry.Length) <> lLocaEntry.Length then
|
||||
raise ETTF.Create('LoadLocations: ' + rsErrFailedToReadFromStream)
|
||||
else
|
||||
ms.Position := 0;
|
||||
|
||||
if FFontInfo.Head.IndexToLocFormat = 0 then
|
||||
begin
|
||||
// Short offsets
|
||||
numLocations := lLocaEntry.Length shr 1;
|
||||
{$IFDEF gDEBUG}
|
||||
Writeln('Number of Glyph locations ( 16 bits offsets ): ', numLocations );
|
||||
{$ENDIF}
|
||||
SetLength(FGlyphLocations, numLocations);
|
||||
for n := 0 to numLocations-1 do
|
||||
FGlyphLocations[n] := BEtoN(ms.ReadWord) * 2;
|
||||
end
|
||||
else
|
||||
begin
|
||||
// Long offsets
|
||||
numLocations := lLocaEntry.Length shr 2;
|
||||
{$IFDEF gDEBUG}
|
||||
Writeln('Number of Glyph locations ( 32 bits offsets ): ', numLocations );
|
||||
{$ENDIF}
|
||||
SetLength(FGlyphLocations, numLocations);
|
||||
for n := 0 to numLocations-1 do
|
||||
FGlyphLocations[n] := BEtoN(ms.ReadDWord);
|
||||
end;
|
||||
finally
|
||||
ms.Free;
|
||||
end;
|
||||
end
|
||||
else
|
||||
begin
|
||||
{$ifdef gDEBUG}
|
||||
Writeln('WARNING: ''loca'' table is not found.');
|
||||
{$endif}
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFontSubsetter.WriteInt16(AStream: TStream; const AValue: Int16);
|
||||
begin
|
||||
AStream.WriteBuffer(NtoBE(AValue), 2);
|
||||
end;
|
||||
|
||||
procedure TFontSubsetter.WriteUInt32(AStream: TStream; const AValue: UInt32);
|
||||
begin
|
||||
AStream.WriteDWord(NtoBE(AValue));
|
||||
end;
|
||||
|
||||
procedure TFontSubsetter.WriteUInt16(AStream: TStream; const AValue: UInt16);
|
||||
begin
|
||||
AStream.WriteWord(NtoBE(AValue));
|
||||
end;
|
||||
|
||||
function TFontSubsetter.ReadInt16(AStream: TStream): Int16;
|
||||
begin
|
||||
Result:=Int16(ReadUInt16(AStream));
|
||||
end;
|
||||
|
||||
function TFontSubsetter.ReadUInt32(AStream: TStream): UInt32;
|
||||
begin
|
||||
Result:=0;
|
||||
AStream.ReadBuffer(Result,SizeOf(Result));
|
||||
Result:=BEtoN(Result);
|
||||
end;
|
||||
|
||||
function TFontSubsetter.ReadUInt16(AStream: TStream): UInt16;
|
||||
begin
|
||||
Result:=0;
|
||||
AStream.ReadBuffer(Result,SizeOf(Result));
|
||||
Result:=BEtoN(Result);
|
||||
end;
|
||||
|
||||
procedure TFontSubsetter.AddCompoundReferences;
|
||||
var
|
||||
GlyphIDsToAdd: TStringList;
|
||||
n: integer;
|
||||
gs: TMemoryStream;
|
||||
buf: TGlyphHeader;
|
||||
i: integer;
|
||||
flags: uint16;
|
||||
glyphIndex: uint16;
|
||||
cid: uint16;
|
||||
hasNested: boolean;
|
||||
begin
|
||||
if FhasAddedCompoundReferences then
|
||||
Exit;
|
||||
FhasAddedCompoundReferences := True;
|
||||
|
||||
LoadLocations;
|
||||
|
||||
repeat
|
||||
GlyphIDsToAdd := TStringList.Create;
|
||||
GlyphIDsToAdd.Duplicates := dupIgnore;
|
||||
GlyphIDsToAdd.Sorted := True;
|
||||
|
||||
for n := 0 to FGlyphIDList.Count-1 do
|
||||
begin
|
||||
if not Assigned(FGlyphIDList[n].GlyphData) then
|
||||
FGlyphIDList[n].GlyphData := GetRawGlyphData(FGlyphIDList[n].GlyphID);
|
||||
gs := TMemoryStream(FGlyphIDList[n].GlyphData);
|
||||
gs.Position := 0;
|
||||
|
||||
if gs.Size > 0 then
|
||||
begin
|
||||
FillMem(@buf, SizeOf(TGlyphHeader), 0);
|
||||
gs.ReadBuffer(buf, SizeOf(Buf));
|
||||
|
||||
if buf.numberOfContours = -1 then
|
||||
begin
|
||||
FGlyphIDList[n].IsCompoundGlyph := True;
|
||||
{$IFDEF gDEBUG}
|
||||
writeln('char: ', IntToHex(FGlyphIDList[n].CharID, 4));
|
||||
writeln(' glyph data size: ', gs.Size);
|
||||
writeln(' numberOfContours: ', buf.numberOfContours);
|
||||
{$ENDIF}
|
||||
repeat
|
||||
flags := ReadUInt16(gs);
|
||||
glyphIndex := ReadUInt16(gs);
|
||||
// find compound glyph ID's and add them to the GlyphIDsToAdd list
|
||||
if not FGlyphIDList.Contains(glyphIndex) then
|
||||
begin
|
||||
{$IFDEF gDEBUG}
|
||||
writeln(Format(' glyphIndex: %.4x (%0:d) ', [glyphIndex]));
|
||||
{$ENDIF}
|
||||
GlyphIDsToAdd.Add(IntToStr(glyphIndex));
|
||||
end;
|
||||
// ARG_1_AND_2_ARE_WORDS
|
||||
if (flags and (1 shl 0)) <> 0 then
|
||||
ReadUInt32(gs)
|
||||
else
|
||||
ReadUInt16(gs);
|
||||
// WE_HAVE_A_TWO_BY_TWO
|
||||
if (flags and (1 shl 7)) <> 0 then
|
||||
begin
|
||||
ReadUInt32(gs);
|
||||
ReadUInt32(gs);
|
||||
end
|
||||
// WE_HAVE_AN_X_AND_Y_SCALE
|
||||
else if (flags and (1 shl 6)) <> 0 then
|
||||
begin
|
||||
ReadUInt32(gs);
|
||||
end
|
||||
// WE_HAVE_A_SCALE
|
||||
else if (flags and (1 shl 3)) <> 0 then
|
||||
begin
|
||||
ReadUInt16(gs);
|
||||
end;
|
||||
|
||||
until (flags and (1 shl 5)) = 0; // MORE_COMPONENTS
|
||||
end; { if buf.numberOfContours = -1 }
|
||||
end; { if gs.Size > 0 }
|
||||
end; { for n ... FGlyphIDList.Count-1 }
|
||||
|
||||
if GlyphIDsToAdd.Count > 0 then
|
||||
begin
|
||||
for i := 0 to GlyphIDsToAdd.Count-1 do
|
||||
begin
|
||||
glyphIndex := StrToInt(GlyphIDsToAdd[i]);
|
||||
cid := GetCharIDfromGlyphID(glyphIndex); // lookup original charID
|
||||
FGlyphIDList.Add(cid, glyphIndex);
|
||||
end;
|
||||
end;
|
||||
hasNested := GlyphIDsToAdd.Count > 0;
|
||||
FreeAndNil(GlyphIDsToAdd);
|
||||
until (hasNested = false);
|
||||
end;
|
||||
|
||||
function TFontSubsetter.buildHeadTable: TStream;
|
||||
var
|
||||
t: THead;
|
||||
rec: THead;
|
||||
i: Integer;
|
||||
begin
|
||||
Result := TMemoryStream.Create;
|
||||
|
||||
t := FFontInfo.Head;
|
||||
FillMem(@rec, SizeOf(THead), 0);
|
||||
rec.FileVersion.Version := NtoBE(t.FileVersion.Version);
|
||||
rec.FontRevision.Version := NtoBE(t.FontRevision.Version);
|
||||
rec.CheckSumAdjustment := 0;
|
||||
rec.MagicNumber := NtoBE(t.MagicNumber);
|
||||
rec.Flags := NtoBE(t.Flags);
|
||||
rec.UnitsPerEm := NtoBE(t.UnitsPerEm);
|
||||
rec.Created := NtoBE(t.Created);
|
||||
rec.Modified := NtoBE(t.Modified);
|
||||
For i := 0 to 3 do
|
||||
rec.BBox[i] := NtoBE(t.BBox[i]);
|
||||
rec.MacStyle := NtoBE(t.MacStyle);
|
||||
rec.LowestRecPPEM := NtoBE(t.LowestRecPPEM);
|
||||
rec.FontDirectionHint := NtoBE(t.FontDirectionHint);
|
||||
// force long format of 'loca' table. ie: 'loca' table offsets are in 4-Bytes each, not Words.
|
||||
rec.IndexToLocFormat := NtoBE(Int16(1)); //NtoBE(t.IndexToLocFormat);
|
||||
rec.glyphDataFormat := NtoBE(t.glyphDataFormat);
|
||||
|
||||
Result.WriteBuffer(rec, SizeOf(THead));
|
||||
end;
|
||||
|
||||
function TFontSubsetter.buildHheaTable: TStream;
|
||||
var
|
||||
t: THHead;
|
||||
rec: THHead;
|
||||
hmetrics: UInt16;
|
||||
begin
|
||||
Result := TMemoryStream.Create;
|
||||
|
||||
t := FFontInfo.HHead;
|
||||
FillMem(@rec, SizeOf(THHead), 0);
|
||||
rec.TableVersion.Version := NtoBE(t.TableVersion.Version);
|
||||
rec.Ascender := NtoBE(t.Ascender);
|
||||
rec.Descender := NtoBE(t.Descender);
|
||||
rec.LineGap := NtoBE(t.LineGap);
|
||||
rec.AdvanceWidthMax := NtoBE(t.AdvanceWidthMax);
|
||||
rec.MinLeftSideBearing := NtoBE(t.MinLeftSideBearing);
|
||||
rec.MinRightSideBearing := NtoBE(t.MinRightSideBearing);
|
||||
rec.XMaxExtent := NtoBE(t.XMaxExtent);
|
||||
rec.CaretSlopeRise := NtoBE(t.CaretSlopeRise);
|
||||
rec.CaretSlopeRun := NtoBE(t.CaretSlopeRun);
|
||||
rec.caretOffset := NtoBE(t.caretOffset);
|
||||
rec.metricDataFormat := NtoBE(t.metricDataFormat);
|
||||
// rec.numberOfHMetrics := NtoBE(t.numberOfHMetrics);
|
||||
|
||||
hmetrics := FGlyphIDList.Count;
|
||||
if (FGlyphIDList.Items[FGlyphIDList.Count-1].GlyphID >= t.numberOfHMetrics) and (not FGlyphIDList.Contains(t.numberOfHMetrics-1)) then
|
||||
inc(hmetrics);
|
||||
rec.numberOfHMetrics := NtoBE(hmetrics);
|
||||
|
||||
Result.WriteBuffer(rec, SizeOf(THHead));
|
||||
end;
|
||||
|
||||
function TFontSubsetter.buildMaxpTable: TStream;
|
||||
var
|
||||
t: TMaxP;
|
||||
rec: TMaxP;
|
||||
lCount: word;
|
||||
begin
|
||||
Result := TMemoryStream.Create;
|
||||
|
||||
t := FFontInfo.MaxP;
|
||||
FillMem(@rec, SizeOf(TMaxP), 0);
|
||||
rec.VersionNumber.Version := NtoBE(t.VersionNumber.Version);
|
||||
|
||||
lCount := FGlyphIDList.Count;
|
||||
rec.numGlyphs := NtoBE(lCount);
|
||||
|
||||
rec.maxPoints := NtoBE(t.maxPoints);
|
||||
rec.maxContours := NtoBE(t.maxContours);
|
||||
rec.maxCompositePoints := NtoBE(t.maxCompositePoints);
|
||||
rec.maxCompositeContours := NtoBE(t.maxCompositeContours);
|
||||
rec.maxZones := NtoBE(t.maxZones);
|
||||
rec.maxTwilightPoints := NtoBE(t.maxTwilightPoints);
|
||||
rec.maxStorage := NtoBE(t.maxStorage);
|
||||
rec.maxFunctionDefs := NtoBE(t.maxFunctionDefs);
|
||||
rec.maxInstructionDefs := NtoBE(t.maxInstructionDefs);
|
||||
rec.maxStackElements := NtoBE(t.maxStackElements);
|
||||
rec.maxSizeOfInstructions := NtoBE(t.maxSizeOfInstructions);
|
||||
rec.maxComponentElements := NtoBE(t.maxComponentElements);
|
||||
rec.maxComponentDepth := NtoBE(t.maxComponentDepth);
|
||||
|
||||
Result.WriteBuffer(rec, SizeOf(TMaxP));
|
||||
end;
|
||||
|
||||
function TFontSubsetter.buildFpgmTable: TStream;
|
||||
begin
|
||||
Result := GetRawTable('fpgm');
|
||||
Result.Position := 0;
|
||||
end;
|
||||
|
||||
function TFontSubsetter.buildPrepTable: TStream;
|
||||
begin
|
||||
Result := GetRawTable('prep');
|
||||
Result.Position := 0;
|
||||
end;
|
||||
|
||||
function TFontSubsetter.buildCvtTable: TStream;
|
||||
begin
|
||||
Result := GetRawTable('cvt ');
|
||||
Result.Position := 0;
|
||||
end;
|
||||
|
||||
function TFontSubsetter.buildGlyfTable(var newOffsets: TArrayUInt32): TStream;
|
||||
var
|
||||
n: integer;
|
||||
lOffset: uint32;
|
||||
lLen: uint32;
|
||||
gs: TMemoryStream;
|
||||
buf: TGlyphHeader;
|
||||
flags: uint16;
|
||||
glyphIndex: uint16;
|
||||
begin
|
||||
lOffset := 0;
|
||||
Result := TMemoryStream.Create;
|
||||
LoadLocations;
|
||||
|
||||
{ - Assign new glyph indexes
|
||||
- Retrieve glyph data in it doesn't yet exist (retrieved from original TTF file)
|
||||
- Now fix GlyphID references in Compound Glyphs to point to new GlyphIDs }
|
||||
for n := 0 to FGlyphIDList.Count-1 do
|
||||
begin
|
||||
FGlyphIDList[n].NewGlyphID := n;
|
||||
if not Assigned(FGlyphIDList[n].GlyphData) then
|
||||
FGlyphIDList[n].GlyphData := GetRawGlyphData(FGlyphIDList[n].GlyphID);
|
||||
if not FGlyphIDList[n].IsCompoundGlyph then
|
||||
Continue;
|
||||
{$IFDEF gDEBUG}
|
||||
writeln(Format('found compound glyph: %.4x glyphID: %d', [FGlyphIDList[n].CharID, FGlyphIDList[n].GlyphID]));
|
||||
{$ENDIF}
|
||||
gs := TMemoryStream(FGlyphIDList[n].GlyphData);
|
||||
gs.Position := 0;
|
||||
|
||||
if gs.Size > 0 then
|
||||
begin
|
||||
FillMem(@buf, SizeOf(TGlyphHeader), 0);
|
||||
gs.ReadBuffer(buf, SizeOf(Buf));
|
||||
|
||||
if buf.numberOfContours = -1 then
|
||||
begin
|
||||
repeat
|
||||
flags := ReadUInt16(gs);
|
||||
lOffset := gs.Position;
|
||||
glyphIndex := ReadUInt16(gs);
|
||||
// now write new GlyphID in it's place.
|
||||
gs.Position := lOffset;
|
||||
glyphIndex := FGlyphIDList.GetNewGlyphID(GetCharIDfromGlyphID(glyphIndex));
|
||||
WriteUInt16(gs, glyphIndex);
|
||||
|
||||
// ARG_1_AND_2_ARE_WORDS
|
||||
if (flags and (1 shl 0)) <> 0 then
|
||||
ReadUInt32(gs)
|
||||
else
|
||||
ReadUInt16(gs);
|
||||
// WE_HAVE_A_TWO_BY_TWO
|
||||
if (flags and (1 shl 7)) <> 0 then
|
||||
begin
|
||||
ReadUInt32(gs);
|
||||
ReadUInt32(gs);
|
||||
end
|
||||
// WE_HAVE_AN_X_AND_Y_SCALE
|
||||
else if (flags and (1 shl 6)) <> 0 then
|
||||
begin
|
||||
ReadUInt32(gs);
|
||||
end
|
||||
// WE_HAVE_A_SCALE
|
||||
else if (flags and (1 shl 3)) <> 0 then
|
||||
begin
|
||||
ReadUInt16(gs);
|
||||
end;
|
||||
|
||||
until (flags and (1 shl 5)) = 0; // MORE_COMPONENTS
|
||||
end; { if buf.numberOfContours = -1 }
|
||||
end; { if gs.Size > 0 }
|
||||
end; { for n ... FGlyphIDList.Count-1 }
|
||||
|
||||
// write all glyph data to resulting data stream
|
||||
lOffset := 0;
|
||||
for n := 0 to FGlyphIDList.Count-1 do
|
||||
begin
|
||||
newOffsets[n] := lOffset;
|
||||
lOffset := lOffset + FGlyphIDList[n].GlyphData.Size;
|
||||
FGlyphIDList[n].GlyphData.Position := 0;
|
||||
Result.CopyFrom(FGlyphIDList[n].GlyphData, FGlyphIDList[n].GlyphData.Size);
|
||||
// 4-byte alignment
|
||||
if (lOffset mod 4) <> 0 then
|
||||
begin
|
||||
lLen := 4 - (lOffset mod 4);
|
||||
Result.WriteBuffer(PAD_BUF, lLen);
|
||||
lOffset := lOffset + lLen;
|
||||
end;
|
||||
end;
|
||||
newOffsets[n+1] := lOffset;
|
||||
end;
|
||||
|
||||
// write as UInt32 as defined in head.indexToLocFormat field (long format).
|
||||
function TFontSubsetter.buildLocaTable(var newOffsets: TArrayUInt32): TStream;
|
||||
var
|
||||
i: integer;
|
||||
begin
|
||||
Result := TMemoryStream.Create;
|
||||
for i := 0 to Length(newOffsets)-1 do
|
||||
WriteUInt32(Result, newOffsets[i]);
|
||||
end;
|
||||
|
||||
function TFontSubsetter.buildCmapTable: TStream;
|
||||
const
|
||||
// platform
|
||||
PLATFORM_UNICODE = 0;
|
||||
PLATFORM_MACINTOSH = 1;
|
||||
// value 2 is reserved; do not use
|
||||
PLATFORM_WINDOWS = 3;
|
||||
|
||||
// Mac encodings
|
||||
ENCODING_MAC_ROMAN = 0;
|
||||
|
||||
// Windows encodings
|
||||
ENCODING_WIN_SYMBOL = 0; // Unicode, non-standard character set
|
||||
ENCODING_WIN_UNICODE_BMP = 1; // Unicode BMP (UCS-2)
|
||||
ENCODING_WIN_SHIFT_JIS = 2;
|
||||
ENCODING_WIN_BIG5 = 3;
|
||||
ENCODING_WIN_PRC = 4;
|
||||
ENCODING_WIN_WANSUNG = 5;
|
||||
ENCODING_WIN_JOHAB = 6;
|
||||
ENCODING_WIN_UNICODE_FULL = 10; // Unicode Full (UCS-4)
|
||||
|
||||
// Unicode encodings
|
||||
ENCODING_UNICODE_1_0 = 0;
|
||||
ENCODING_UNICODE_1_1 = 1;
|
||||
ENCODING_UNICODE_2_0_BMP = 3;
|
||||
ENCODING_UNICODE_2_0_FULL = 4;
|
||||
var
|
||||
segCount: UInt16;
|
||||
searchRange: UInt16;
|
||||
i: integer;
|
||||
startCode: Array of Integer;
|
||||
endCode: Array of Integer;
|
||||
idDelta: Array of Integer;
|
||||
lastChar: integer;
|
||||
prevChar: integer;
|
||||
lastGid: integer;
|
||||
itm: TTextMapping;
|
||||
begin
|
||||
Result := TMemoryStream.Create;
|
||||
SetLength(startCode, FGlyphIDList.Count);
|
||||
SetLength(endCode, FGlyphIDList.Count);
|
||||
SetLength(idDelta, FGlyphIDList.Count);
|
||||
|
||||
// cmap header
|
||||
WriteUInt16(Result, 0); // version
|
||||
WriteUInt16(Result, 1); // numberSubTables
|
||||
|
||||
// encoding record
|
||||
WriteUInt16(Result, PLATFORM_WINDOWS); // platformID
|
||||
WriteUInt16(Result, ENCODING_WIN_UNICODE_BMP); // platformSpecificID
|
||||
WriteUInt32(Result, 4 * 2 + 4); // offset
|
||||
|
||||
// build Format 4 subtable (Unicode BMP)
|
||||
lastChar := 0;
|
||||
prevChar := lastChar;
|
||||
lastGid := FGlyphIDList[0].NewGlyphID;
|
||||
segCount := 0;
|
||||
|
||||
for i := 0 to FGlyphIDList.Count-1 do
|
||||
begin
|
||||
itm := FGlyphIDList[i];
|
||||
if itm.CharID > $FFFF then
|
||||
raise Exception.Create('non-BMP Unicode character');
|
||||
|
||||
if (itm.CharID <> FGlyphIDList[prevChar].CharID+1) or ((itm.NewGlyphID - lastGid) <> (itm.CharID - FGlyphIDList[lastChar].CharID)) then
|
||||
begin
|
||||
if (lastGid <> 0) then
|
||||
begin
|
||||
{ don't emit ranges, which map to GID 0, the undef glyph is emitted at the very last segment }
|
||||
startCode[segCount] := FGlyphIDList[lastChar].CharID;
|
||||
endCode[segCount] := FGlyphIDList[prevChar].CharID;
|
||||
idDelta[segCount] := lastGid - FGlyphIDList[lastChar].CharID;
|
||||
inc(segCount);
|
||||
end
|
||||
else if not (FGlyphIDList[lastChar].CharID = FGlyphIDList[prevChar].CharID) then
|
||||
begin
|
||||
{ shorten ranges which start with GID 0 by one }
|
||||
startCode[segCount] := FGlyphIDList[lastChar].CharID + 1;
|
||||
endCode[segCount] := FGlyphIDList[prevChar].CharID;
|
||||
idDelta[segCount] := lastGid - FGlyphIDList[lastChar].CharID;
|
||||
inc(segCount);
|
||||
end;
|
||||
lastGid := itm.NewGlyphID;
|
||||
lastChar := i;
|
||||
end;
|
||||
prevChar := i;
|
||||
end;
|
||||
|
||||
// trailing segment
|
||||
startCode[segCount] := FGlyphIDList[lastChar].CharID;
|
||||
endCode[segCount] := FGlyphIDList[prevChar].CharID;
|
||||
idDelta[segCount] := lastGid - FGlyphIDList[lastChar].CharID;
|
||||
inc(segCount);
|
||||
|
||||
// GID 0
|
||||
startCode[segCount] := $FFFF;
|
||||
endCode[segCount] := $FFFF;
|
||||
idDelta[segCount] := 1;
|
||||
inc(segCount);
|
||||
|
||||
// write format 4 subtable
|
||||
searchRange := trunc(2 * Power(2, Floor(Log2(segCount))));
|
||||
WriteUInt16(Result, 4); // format
|
||||
WriteUInt16(Result, 8 * 2 + segCount * 4*2); // length
|
||||
WriteUInt16(Result, 0); // language
|
||||
WriteUInt16(Result, segCount * 2); // segCountX2
|
||||
WriteUInt16(Result, searchRange); // searchRange
|
||||
WriteUInt16(Result, trunc(log2(searchRange / 2))); // entrySelector
|
||||
WriteUInt16(Result, 2 * segCount - searchRange); // rangeShift
|
||||
|
||||
// write endCode
|
||||
for i := 0 to segCount-1 do
|
||||
WriteUInt16(Result, endCode[i]);
|
||||
|
||||
// reservedPad
|
||||
WriteUInt16(Result, 0);
|
||||
|
||||
// startCode
|
||||
for i := 0 to segCount-1 do
|
||||
WriteUInt16(Result, startCode[i]);
|
||||
|
||||
// idDelta
|
||||
for i := 0 to segCount-1 do
|
||||
WriteUInt16(Result, idDelta[i]);
|
||||
|
||||
// idRangeOffset
|
||||
for i := 0 to segCount-1 do
|
||||
WriteUInt16(Result, 0);
|
||||
end;
|
||||
|
||||
function TFontSubsetter.buildHmtxTable: TStream;
|
||||
var
|
||||
n: integer;
|
||||
begin
|
||||
Result := TMemoryStream.Create;
|
||||
for n := 0 to FGlyphIDList.Count-1 do
|
||||
begin
|
||||
WriteUInt16(Result, FFontInfo.Widths[FGlyphIDList[n].GlyphID].AdvanceWidth);
|
||||
WriteInt16(Result, FFontInfo.Widths[FGlyphIDList[n].GlyphID].LSB);
|
||||
end;
|
||||
end;
|
||||
|
||||
constructor TFontSubsetter.Create(const AFont: TTFFileInfo; const AGlyphIDList: TTextMappingList);
|
||||
begin
|
||||
FFontInfo := AFont;
|
||||
if not Assigned(FFontInfo) then
|
||||
raise ETTFSubsetter.Create(rsErrFontInfoNotAssigned);
|
||||
FGlyphIDList := AGlyphIDList;
|
||||
|
||||
FKeepTables := TStringList.Create;
|
||||
FHasAddedCompoundReferences := False;
|
||||
FPrefix := '';
|
||||
FhasAddedCompoundReferences := False;
|
||||
|
||||
// create a default list
|
||||
FKeepTables.Add('head');
|
||||
FKeepTables.Add('hhea');
|
||||
FKeepTables.Add('maxp');
|
||||
FKeepTables.Add('hmtx');
|
||||
FKeepTables.Add('cmap');
|
||||
FKeepTables.Add('fpgm');
|
||||
FKeepTables.Add('prep');
|
||||
FKeepTables.Add('cvt ');
|
||||
FKeepTables.Add('loca');
|
||||
FKeepTables.Add('glyf');
|
||||
|
||||
if Assigned(FGlyphIDList) then
|
||||
FGlyphIDList.Sort;
|
||||
|
||||
if FFontInfo.Filename <> '' then
|
||||
FStream := TFileStream.Create(FFontInfo.FileName, fmOpenRead or fmShareDenyNone)
|
||||
else
|
||||
raise ETTF.Create(rsErrCantFindFontFile);
|
||||
end;
|
||||
|
||||
constructor TFontSubsetter.Create(const AFont: TTFFileInfo);
|
||||
begin
|
||||
Create(AFont, nil);
|
||||
end;
|
||||
|
||||
destructor TFontSubsetter.Destroy;
|
||||
var
|
||||
i: integer;
|
||||
begin
|
||||
// the owner of FGlyphIDList doesn't need the GlyphData information
|
||||
for i := 0 to FGlyphIDList.Count-1 do
|
||||
FGlyphIDList[i].GlyphData.Free;
|
||||
FStream.Free;
|
||||
FKeepTables.Free;
|
||||
inherited Destroy;
|
||||
end;
|
||||
|
||||
procedure TFontSubsetter.SaveToFile(const AFileName: String);
|
||||
var
|
||||
fs: TFileStream;
|
||||
begin
|
||||
fs := TFileStream.Create(AFileName, fmCreate);
|
||||
try
|
||||
SaveToStream(fs);
|
||||
finally
|
||||
FreeAndNil(fs);
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TFontSubsetter.SaveToStream(const AStream: TStream);
|
||||
var
|
||||
checksum: uint64;
|
||||
offset: uint64;
|
||||
head: TStream;
|
||||
hhea: TStream;
|
||||
maxp: TStream;
|
||||
hmtx: TStream;
|
||||
cmap: TStream;
|
||||
fpgm: TStream;
|
||||
prep: TStream;
|
||||
cvt: TStream;
|
||||
loca: TStream;
|
||||
glyf: TStream;
|
||||
newLoca: TArrayUInt32;
|
||||
tables: TStringList;
|
||||
i: integer;
|
||||
o: uint64;
|
||||
p: uint64;
|
||||
lPadding: byte;
|
||||
begin
|
||||
// resolve compound glyph references
|
||||
AddCompoundReferences;
|
||||
|
||||
// always copy GID 0
|
||||
FGlyphIDList.Add(0, 0);
|
||||
FGlyphIDList.Sort;
|
||||
|
||||
SetLength(newLoca, FGlyphIDList.Count+1);
|
||||
|
||||
head := buildHeadTable(); // done
|
||||
hhea := buildHheaTable(); // done
|
||||
maxp := buildMaxpTable(); // done
|
||||
fpgm := buildFpgmTable(); // done
|
||||
prep := buildPrepTable(); // done
|
||||
cvt := buildCvtTable(); // done
|
||||
glyf := buildGlyfTable(newLoca); // done
|
||||
loca := buildLocaTable(newLoca); // done
|
||||
cmap := buildCmapTable();
|
||||
hmtx := buildHmtxTable();
|
||||
|
||||
tables := TStringList.Create;
|
||||
tables.CaseSensitive := True;
|
||||
if Assigned(cmap) then
|
||||
tables.AddObject('cmap', cmap);
|
||||
if Assigned(glyf) then
|
||||
tables.AddObject('glyf', glyf);
|
||||
tables.AddObject('head', head);
|
||||
tables.AddObject('hhea', hhea);
|
||||
tables.AddObject('hmtx', hmtx);
|
||||
if Assigned(loca) then
|
||||
tables.AddObject('loca', loca);
|
||||
tables.AddObject('maxp', maxp);
|
||||
tables.AddObject('fpgm', fpgm);
|
||||
tables.AddObject('prep', prep);
|
||||
tables.AddObject('cvt ', cvt);
|
||||
tables.Sort;
|
||||
|
||||
// calculate checksum
|
||||
checksum := writeFileHeader(AStream, tables.Count);
|
||||
offset := 12 + (16 * tables.Count);
|
||||
lPadding := 0;
|
||||
for i := 0 to tables.Count-1 do
|
||||
begin
|
||||
if tables.Objects[i] <> nil then
|
||||
begin
|
||||
checksum := checksum + WriteTableHeader(AStream, tables.Strings[i], offset, TStream(tables.Objects[i]));
|
||||
p := TStream(tables.Objects[i]).Size;
|
||||
// table bodies must be 4-byte aligned - calculate the padding so the tableHeader.Offset field can reflect that.
|
||||
if (p mod 4) = 0 then
|
||||
lPadding := 0
|
||||
else
|
||||
lPadding := 4 - (p mod 4);
|
||||
o := p + lPadding;
|
||||
offset := offset + o;
|
||||
end;
|
||||
end;
|
||||
checksum := $B1B0AFBA - (checksum and $ffffffff);
|
||||
|
||||
// update head.ChecksumAdjustment field
|
||||
head.Seek(8, soBeginning);
|
||||
WriteUInt32(head, checksum);
|
||||
|
||||
// write table bodies
|
||||
WriteTableBodies(AStream, tables);
|
||||
|
||||
for i := 0 to tables.Count-1 do
|
||||
TStream(tables.Objects[i]).Free;
|
||||
tables.Free;
|
||||
end;
|
||||
|
||||
procedure TFontSubsetter.Add(const ACodePoint: uint32);
|
||||
var
|
||||
gid: uint32;
|
||||
begin
|
||||
gid := FFontInfo.Chars[ACodePoint];
|
||||
if gid <> 0 then
|
||||
FGlyphIDList.Add(ACodePoint, FFontInfo.Chars[ACodePoint]);
|
||||
end;
|
||||
|
||||
|
||||
end.
|
||||
|
Loading…
Reference in New Issue
Block a user