unit CFFs; // reads embedded font. {$MODE OBJFPC} {$M+} interface uses type_fixes, classes, charstrings, contnrs; {$PACKRECORDS C} type THeader = packed record MajorVersion : TByte; MinorVersion : TByte; HeaderSize : TByte; GlobalOffsetSize : TByte; end; TIndexOffset32Array = TUINT32Array; TIndex = class private fCount : TUINT16; fOffsetSize : TByte; //fEntries : Variant; fDataSize : TUINT32; fDataOffset : Int64; fOffsets32 : TIndexOffset32Array; published property OffsetSize : TByte read fOffsetSize; //property Offsets : TIndexOffsetArray protected function GetItem(aIndex : TUINT16) : Int64; // returns: file offset. //procedure DecodeIndex()M public property Item[aIndex: TUINT16] : Int64 read GetItem; property Count : TUINT16 read fCount; published constructor ReadFromStream(aStream : TStream); //protected //procedure FixupAfterReading(); virtual; end; type TNameIndex = class(TIndex) end; TStringIndex = class(TIndex) end; function operator_P(aInput : TByte) : TBoolean; inline; procedure Read(f : TStream); type TSID = TUINT16; TSIDArray = array of TSID; TOperatorArgumentType = (oaNone, oaSID, oaBoolean, oaNumber, oaArray); TDictKey = ( dkVersion = 0, dkNotice = 1, dkFullName = 2, dkFamilyName = 3, dkWeight = 4, dkFontBBox = 5, dkUniqueID = 13, dkXUID = 14, dkCharsetOffset = 15, dkEncodingOffset = 16, dkCharStringsOffset = 17, dkPrivateOffset = 18, // size and offset dkCopyright = 12 shl 8, dkIsFixedPitch = (12 shl 8) + 1, dkItalicAngle = (12 shl 8) + 2, dkUnderlinePosition = (12 shl 8) + 3, dkUnderlineThickness = (12 shl 8) + 4, dkPaintType = (12 shl 8) + 5, dkCharStringType = (12 shl 8) + 6, dkFontMatrix = (12 shl 8) + 7, dkStrokeWidth = (12 shl 8) + 8, dkSyntheticBase = (12 shl 8) + 20, // index dkPostScript = (12 shl 8) + 21, // code dkBaseFontName = (12 shl 8) + 22, dkBaseFontBlend = (12 shl 8) + 23, // -- for CIDFonts: dkROS = (12 shl 8) + 30, dkCIDFontVersion = (12 shl 8) + 31, dkCIDFontRevision = (12 shl 8) + 32, dkCIDFontType = (12 shl 8) + 33, dkCIDCount = (12 shl 8) + 34, dkUIDBase = (12 shl 8) + 35, dkFontDictArrayOffset = (12 shl 8) + 36, dkFontDictSelectOffset = (12 shl 8) + 37, dkFontName = (12 shl 8) + 38 ); TDictKeySpec = record ID : TDictKey; Magic1 : TByte; Magic2 : TByte; // $FF if none. ArgumentType : array[0..1] of TOperatorArgumentType; end; TTable = class constructor ReadFromStream(aStream : TStream); protected procedure ReadRemainderFromStream(aStream : TStream); virtual; //def __repr__(self): //field_names = [entry[0] for entry in self.__class__.definition] + self.__class__.extra_field_names //return "%s(%s)" % (self.__class__.__name__, ", ".join(["%s=%r" % (field_name, getattr(self, field_name)) for field_name in field_names])) end; const DictKeys : array[0..33] of TDictKeySpec = ( (ID: dkVersion; Magic1: 0; Magic2: $FF; ArgumentType: (oaSID, oaNone)), (ID: dkNotice; Magic1: 1; Magic2: $FF; ArgumentType: (oaSID, oaNone)), (ID: dkCopyright; Magic1: 12; Magic2: 0; ArgumentType: (oaSID, oaNone)), (ID: dkFullName; Magic1: 2; Magic2: $FF; ArgumentType: (oaSID, oaNone)), (ID: dkFamilyName; Magic1: 3; Magic2: $FF; ArgumentType: (oaSID, oaNone)), (ID: dkWeight; Magic1: 4; Magic2: $FF; ArgumentType: (oaSID, oaNone)), (ID: dkisFixedPitch; Magic1: 12; Magic2: 1; ArgumentType: (oaBoolean, oaNone)), (ID: dkItalicAngle; Magic1: 12; Magic2: 2; ArgumentType: (oaNumber, oaNone)), (ID: dkUnderlinePosition; Magic1: 12; Magic2: 3; ArgumentType: (oaNumber, oaNone)), (ID: dkUnderlineThickness; Magic1: 12; Magic2: 4; ArgumentType: (oaNumber, oaNone)), (ID: dkPaintType; Magic1: 12; Magic2: 5; ArgumentType: (oaNumber, oaNone)), (ID: dkCharStringType; Magic1: 12; Magic2: 6; ArgumentType: (oaNumber, oaNone)), (ID: dkFontMatrix; Magic1: 12; Magic2: 7; ArgumentType: (oaArray, oaNone)), (ID: dkUniqueID; Magic1: 13; Magic2: $FF; ArgumentType: (oaNumber, oaNone)), (ID: dkFontBBox; Magic1: 5; Magic2: $FF; ArgumentType: (oaArray, oaNone)), (ID: dkStrokeWidth; Magic1: 12; Magic2: 8; ArgumentType: (oaNumber, oaNone)), (ID: dkXUID; Magic1: 14; Magic2: $FF; ArgumentType: (oaArray, oaNone)), (ID: dkCharsetOffset; Magic1: 15; Magic2: $FF; ArgumentType: (oaNumber, oaNone)), // charset offset(0)")), (ID: dkEncodingOffset; Magic1: 16; Magic2: $FF; ArgumentType: (oaNumber, oaNone)), // encoding offset(0)")), (ID: dkCharStringsOffset; Magic1: 17; Magic2: $FF; ArgumentType: (oaNumber, oaNone)), // charstrings offset(0)")), (ID: dkPrivateOffset; Magic1: 18; Magic2: $FF; ArgumentType: (oaNumber, oaNumber)), // Private DICT size and offset(0)")), (ID: dkSyntheticBase; Magic1: 12; Magic2: 20; ArgumentType: (oaNumber, oaNone)), // synthetic base font index (ID: dkPostScript; Magic1: 12; Magic2: 21; ArgumentType: (oaSID, oaNone)), // code (ID: dkBaseFontName; Magic1: 12; Magic2: 22; ArgumentType: (oaSID, oaNone)), (ID: dkBaseFontBlend; Magic1: 12; Magic2: 23; ArgumentType: (oaNone, oaNone)), // FIXME delta type //-- for CIDFonts: (ID: dkROS; Magic1: 12; Magic2: 30; ArgumentType: (oaSID, oaNone)), (ID: dkCIDFontVersion; Magic1: 12; Magic2: 31; ArgumentType: (oaNumber, oaNone)), (ID: dkCIDFontRevision; Magic1: 12; Magic2: 32; ArgumentType: (oaNumber, oaNone)), (ID: dkCIDFontType; Magic1: 12; Magic2: 33; ArgumentType: (oaNumber, oaNone)), (ID: dkCIDCount; Magic1: 12; Magic2: 34; ArgumentType: (oaNumber, oaNone)), (ID: dkUIDBase; Magic1: 12; Magic2: 35; ArgumentType: (oaNumber, oaNone)), (ID: dkFontDictArrayOffset; Magic1: 12; Magic2: 36; ArgumentType: (oaNumber, oaNone)), //, Font DICT INDEX offset(0)")), (ID: dkFontDictSelectOffset; Magic1: 12; Magic2: 37; ArgumentType: (oaNumber, oaNone)), // , Font DICT select offset(0)")), (ID: dkFontName; Magic1: 12; Magic2: 38; ArgumentType: (oaSID, oaNone)) ); type TDict = class(TTable) private fKeys : TStringList; fValues : Variant; //extra_field_names = ["entries"] protected procedure ReadRemainderFromStream(aStream : TStream); override; public destructor Destroy(); override; published constructor Create(); function GetFirst(aKey : TDictKey) : Variant; end; TTopDict = class(TDict) end; TTopDictIndex = class(TIndex) // FIXME element_type = TTopDict; //procedure ReadRemainderFromStream(aStream : TStream); override; end; TEncodingFormat = class(TTable) end; TEncodingFormat0 = class(TEncodingFormat) public nCodes : TByte; Codes : TByteArray; //extra_field_names = ["codes"]; protected procedure ReadRemainderFromStream(aStream : TStream); override; end; type TEncodingRange1 = packed record First : TByte; nLeft : TByte; // excluding first. end; TEncodingRange1Array = array of TEncodingRange1; TEncodingFormat1 = class(TEncodingFormat) public nRanges : TByte; Ranges : TEncodingRange1Array; // extra_field_names = ["ranges"] protected procedure ReadRemainderFromStream(aStream : TStream); override; end; type TEncodingSupplement = packed record Code : TByte; Glyph : TUINT16; // BIG ENDIAN VALUE. SID. end; TEncodingSupplementArray = array of TEncodingSupplement; TEncodingSupplements = class(TTable) public nSupplements : TByte; Supplements : TEncodingSupplementArray; published constructor Create(); public destructor Destroy(); override; protected procedure ReadRemainderFromStream(aStream : TStream); override; end; TEncoding = class(TTable) private Format : TByte; public Body : TEncodingFormat; Supplements : TEncodingSupplements; // extra_field_names = ["body", "supplements"] protected procedure ReadRemainderFromStream(aStream : TStream); override; end; TCharsetRange1 = packed record First : TSID; // BIG ENDIAN VALUE! nLeft : TByte; // excluding first. end; TCharsetRange1Array = array of TCharsetRange1; TCharsetRange2 = packed record First : TSID; // BIG ENDIAN VALUE! nLeft : TUINT16; // BIG ENDIAN VALUE! end; TCharsetRange2Array = array of TCharsetRange2; TCharsetFormat = class(TTable) public nGlyphs : Integer; end; TCharsetFormat0 = class(TCharsetFormat) public // FIXME // SID glyph[nGlyphs-1] names, without ".notdef" //extra_field_names = ["entries"] Entries : TSIDArray; protected procedure ReadRemainderFromStream(aStream : TStream); override; end; TCharsetFormat1 = class(TCharsetFormat) public Ranges : TCharsetRange1Array; // extra_field_names = ["ranges"] protected procedure ReadRemainderFromStream(aStream : TStream); override; end; TCharsetFormat2 = class(TCharsetFormat) public Ranges : TCharsetRange2Array; //extra_field_names = ["ranges"] protected procedure ReadRemainderFromStream(aStream : TStream); override; end; TCharset = class(TTable) private Format : TByte; Body : TCharsetFormat; nGlyphs : Integer; // extra_field_names = ["body"] protected procedure ReadRemainderFromStream(aStream : TStream); override; published constructor ReadFromStream(aStream : TStream; anGlyphs : Integer); end; const StandardStrings : array[0..1] of string = ( '.notdef', 'space' // TODO ); nStdStrings = 391; type TCharStringsIndex = class(TIndex) // FIXME class ElementType : TCharString; end; implementation uses variants, sysutils, number_encodings; function operator_P(aInput : TByte) : TBoolean; begin Result := aInput < 22; end; type TIndexOffset16Array = TUINT16Array; TIndexOffset8Array = TUINT8Array; constructor TIndex.ReadFromStream(aStream : TStream); var fOffsets16 : TIndexOffset16Array; fOffsets8 : TIndexOffset8Array; fOffsetIndex : Integer; fByte : TByte; fWord : TUINT16; begin ReadBigEndian(aStream, fCount); if fCount <= 0 then Exit; // offsets = [] // data = None fOffsetSize := aStream.ReadByte(); case fOffsetSize of 32 div 8: ReadBigEndian(aStream, fOffsets32, fCount + 1); 16 div 8: begin ReadBigEndian(aStream, fOffsets16, fCount + 1); SetLength(fOffsets32, fCount + 1); for fOffsetIndex := 0 to fCount + 1 - 1 do fOffsets32[fOffsetIndex] := fOffsets16[fOffsetIndex]; end; 24 div 8: begin for fOffsetIndex := 0 to fCount + 1 - 1 do begin fByte := aStream.ReadByte(); ReadBigEndian(aStream, fWord); fOffsets32[fOffsetIndex] := (fByte shl 16) or fWord; end; end; 8 div 8: begin ReadBigEndian(aStream, fOffsets8, fCount + 1); SetLength(fOffsets32, fCount + 1); for fOffsetIndex := 0 to fCount + 1 - 1 do fOffsets32[fOffsetIndex] := fOffsets8[fOffsetIndex]; end else raise EReadError.Create('invalid value for field "offset_size" in CFF.'); end; // FIXME sort fOffsets32 fDataSize := fOffsets32[High(fOffsets32)] - 1; fDataOffset := aStream.Position; //DecodeIndex(); //FixupAfterReading(); end; { procedure TIndex.DecodeIndex(); var sorted_offsets : TIndexOffset32Array; begin sorted_offsets := fOffsets32; self.chunks = ; for index, offset in enumerate(sorted_offsets[:-1]) do begin end_offset = sorted_offsets[index + 1]; chunk = self.data[offset - 1 : end_offset - 1]; if hasattr(self.__class__, "element_type") then begin stream := StringIO.StringIO(chunk); chunk := self.__class__.element_type.ReadFromStream(stream); end; self.chunks[offset] := chunk; end; end; } function TIndex.GetItem(aIndex : TUINT16) : Int64; begin Result := fOffsets32[aIndex] + fDataOffset; end; {procedure TIndex.FixupAfterReading(); begin fEntries := VarArrayCreate([0,200], varVariant); self.entries = [] for offset in self.offsets[: -1]: chunk = self.chunks[offset] self.entries.append(chunk) end;} { TTable } constructor TTable.ReadFromStream(aStream : TStream); begin {for name, value in kwargs.items(): setattr(result, name, value)} ReadRemainderFromStream(aStream); end; procedure TTable.ReadRemainderFromStream(aStream : TStream); begin end; { end TTable } { TDict } const DictCapacity = 400; constructor TDict.Create(); begin inherited Create(); fKeys := TStringList.Create(); end; destructor TDict.Destroy(); begin FreeAndNil(fKeys); inherited Destroy(); end; procedure TDict.ReadRemainderFromStream(aStream : TStream); var operands : Variant; operand : Variant; operandCount : Integer; b0 : TByte; b1 : TByte; i : Integer; valueCount : Integer; begin operandCount := 0; valueCount := 0; fKeys.Clear; fValues := VarArrayCreate([0, DictCapacity - 1], varVariant); operands := VarArrayCreate([0, 47], varVariant); // official limit. while aStream.Read(b0, Sizeof(b0)) = 1 do begin if operator_P(b0) then begin if b0 = 12 then b1 := aStream.ReadByte() else b1 := $FF; for i := 0 to High(DictKeys) do with DictKeys[i] do if (b0 = Magic1) and (b1 = Magic2) then begin fKeys.Values[IntToStr(Integer(ID))] := IntToStr(valueCount); // eew... if valueCount >= DictCapacity then Abort(); // FIXME raise exception. VarArrayRedim(operands, operandCount - 1); fValues[valueCount] := operands; operands := VarArrayCreate([0, 47], varVariant); // official limit. Inc(valueCount); Break; end; {for parameter_index, parameter_type in enumerate(parameters): pass} end else begin operand := number_encodings.ReadOperand(aStream, b0); if operandCount < 47 then begin operands[operandCount] := operand; Inc(operandCount); end else begin // TODO raise exception. end; end; end; end; function TDict.GetFirst(aKey : TDictKey) : Variant; var i : string; begin Result := Null; i := fKeys.Values[IntToStr(Integer(aKey))]; // eew... if i <> '' then Result := fValues[StrToInt(i)]; end; { end TDict } { TEncodingSupplements } constructor TEncodingSupplements.Create(); begin inherited Create(); SetLength(Supplements, 0); end; destructor TEncodingSupplements.Destroy(); begin inherited Destroy(); end; procedure TEncodingSupplements.ReadRemainderFromStream(aStream : TStream); var i : TByte; begin inherited ReadRemainderFromStream(aStream); nSupplements := aStream.ReadByte(); SetLength(Supplements, nSupplements); if nSupplements > 0 then for i := 0 to nSupplements - 1 do begin aStream.ReadBuffer(Supplements[i], Sizeof(TEncodingSupplement)); ToHostByteOrderFromBigEndianM(Supplements[i].Glyph); end; end; { end TEncodingSupplements } { TCharset } constructor TCharset.ReadFromStream(aStream : TStream; anGlyphs : Integer); begin nGlyphs := anGlyphs; inherited ReadFromStream(aStream); end; procedure TCharset.ReadRemainderFromStream(aStream : TStream); begin inherited ReadRemainderFromStream(aStream); Self.Format := aStream.ReadByte(); case Self.Format and $7F of 0: Body := TCharsetFormat(TCharsetFormat0.ReadFromStream(aStream)); 1: Body := TCharsetFormat(TCharsetFormat1.ReadFromStream(aStream)); 2: Body := TCharsetFormat(TCharsetFormat2.ReadFromStream(aStream)); else assert(False); end; Body.nGlyphs := nGlyphs; end; { end TCharset } { TCharsetFormat0 } procedure TCharsetFormat0.ReadRemainderFromStream(aStream : TStream); var i : Integer; fValue16 : TUINT16; begin inherited ReadRemainderFromStream(aStream); SetLength(Entries, nGlyphs); if nGlyphs > 0 then for i := 0 to nGlyphs - 1 do begin aStream.ReadBuffer(fValue16, Sizeof(fValue16)); ToHostByteOrderFromBigEndianM(fValue16); // FIXME what to do on premature EOF? Entries[i] := fValue16; end; end; { end TCharsetFormat0 } { TCharsetFormat1 } procedure TCharsetFormat1.ReadRemainderFromStream(aStream : TStream); var uncovered : Integer; nRanges : Integer; begin inherited ReadRemainderFromStream(aStream); uncovered := nGlyphs - 1; // without ".notdef". SetLength(Ranges, 100); nRanges := 0; while (nRanges < 100) and (uncovered > 0) do begin aStream.ReadBuffer(Ranges[nRanges], Sizeof(TCharsetRange1)); ToHostByteOrderFromBigEndianM(Ranges[nRanges].First); Dec(uncovered, Ranges[nRanges].nLeft + 1); Inc(nRanges); end; end; { end TCharsetFormat1 } { TCharsetFormat2 } procedure TCharsetFormat2.ReadRemainderFromStream(aStream : TStream); var uncovered : Integer; nRanges : Integer; begin inherited ReadRemainderFromStream(aStream); uncovered := nGlyphs - 1; // without ".notdef". SetLength(Ranges, 100); nRanges := 0; while (nRanges < 100) and (uncovered > 0) do begin aStream.ReadBuffer(Ranges[nRanges], Sizeof(TCharsetRange2)); ToHostByteOrderFromBigEndianM(Ranges[nRanges].First); Dec(uncovered, Ranges[nRanges].nLeft + 1); Inc(nRanges); end; end; { end TCharsetFormat2 } { TEncodingFormat0 } procedure TEncodingFormat0.ReadRemainderFromStream(aStream : TStream); begin inherited ReadRemainderFromStream(aStream); nCodes := aStream.ReadByte(); SetLength(Codes, nCodes); assert(Sizeof(TByte) = 1); aStream.ReadBuffer(Codes[0], nCodes * Sizeof(TByte)); end; { end TEncodingFormat0 } { TEncodingFormat1 } procedure TEncodingFormat1.ReadRemainderFromStream(aStream : TStream); begin inherited ReadRemainderFromStream(aStream); nRanges := aStream.ReadByte(); SetLength(Ranges, nRanges); aStream.ReadBuffer(Ranges[0], nRanges); end; { end TEncodingFormat1 } { TEncoding } procedure TEncoding.ReadRemainderFromStream(aStream : TStream); begin inherited ReadRemainderFromStream(aStream); Self.Format := aStream.ReadByte(); if (Format and $7F) = 0 then Body := TEncodingFormat(TEncodingFormat0.ReadFromStream(aStream)) else if (Format and $7F) = 1 then Body := TEncodingFormat(TEncodingFormat1.ReadFromStream(aStream)) else assert((Format and $7F) in [0,1]); if (Self.Format and $80) <> 0 then self.supplements := TEncodingSupplements(TEncodingSupplements.ReadFromStream(aStream)) else self.supplements := TEncodingSupplements.Create(); end; { end TEncoding } procedure Read(f : TStream); var FontOffset : Int64; TopDict : TTopDict; CharStringType : Variant; // int. header : THeader; name_index : TNameIndex; Charset_offset : Int64; encoding : TEncoding; charset : TCharset; CharStrings_offset : Int64; top_dict_index : TTopDictIndex; string_index : TStringIndex; encoding_offset : Int64; char_strings_index : TCharStringsIndex; function GetString(SID : TSID) : ansistring; var offsetA, offsetB : Int64; size : Int64; begin if SID >= nStdStrings then begin offsetA := string_index.item[SID - nStdStrings]; offsetB := string_index.item[SID - nStdStrings + 1]; assert(offsetB >= offsetA); size := offsetB - offsetA; SetLength(Result, size); f.Seek(offsetA, soFromBeginning); f.ReadBuffer(Result[1], size); end else Result := StandardStrings[SID]; end; begin f.ReadBuffer(header, Sizeof(header)); SkipJunk(f, header.HeaderSize - 4); // skip junk. name_index := TNameIndex(TNameIndex.ReadFromStream(f)); top_dict_index := TTopDictIndex(TTopDictIndex.ReadFromStream(f)); string_index := TStringIndex(TStringIndex.ReadFromStream(f)); // FIXME handle the other fonts in the set. FontOffset := top_dict_index.item[0]; f.Seek(FontOffset, soFromBeginning); TopDict := TTopDict(TTopDict.ReadFromStream(f)); encoding_offset := TopDict.GetFirst(dkEncodingOffset)[0]; f.Seek(encoding_offset, soFromBeginning); encoding := TEncoding(TEncoding.ReadFromStream(f)); CharStringType := TopDict.GetFirst(dkCharStringType)[0]; // FIXME fall back to 2. assert(CharStringType = 2); // CFF CharStrings_offset := TopDict.GetFirst(dkCharStringsOffset)[0]; f.Seek(CharStrings_offset, soFromBeginning); char_strings_index := TCharStringsIndex.ReadFromStream(f); //print "Q", char_strings_index Charset_offset := TopDict.GetFirst(dkCharsetOffset)[0]; if Charset_offset > 2 then begin f.Seek(Charset_offset, soFromBeginning); charset := TCharset.ReadFromStream(f, char_strings_index.count - 1); // one less for the actual count since there is a sentinel at the end. end; { Global Subr INDEX -- FDSelect CharStrings INDEX per-font Font DICT INDEX per-font Private DICT per-font Local Subr INDEX per-font Copyright etc } end; end.