unit charstrings; // reads charstrings from a CFF "file" of a PDF file. {$MODE OBJFPC} {$M+} interface uses type_fixes, classes, contnrs; function operator_P(aInput : TByte) : TBoolean; inline; // input == 12: extra operator type TStandardOpcode = ( opHStem = 1, opVStem = 3, opVMoveTo = 4, opRLineTo = 5, opHLineTo = 6, opVLineTo = 7, opRRCurveTo = 8, // op9 = 9, opCallSubr = 10, opReturn = 11, // ??? opEndChar = 14, // ??? opHStemHM = 18, opHintMask = 19, opCntrMask = 20, opRMoveTo = 21, opHMoveTo = 22, opVStemHM = 23, opRCurveLine = 24, opRLineCurve = 25, opVVCurveTo = 26, opHHCurveTo = 27, // ??? opCallGSubr = 29, opVHCurveTo = 30, opHVCurveTo = 31); TExtendedOpcode = (eoInvalid = 0, eoAnd = 3, eoOr = 4, eoNot = 5, eoAbs = 9, eoAdd = 10, eoSub = 11, eoDiv = 12, eoNeg = 14, eoEq = 15, eoDrop = 18, eoPut = 20, eoGet = 21, eoIfElse = 22, eoRandom = 23, eoMul = 24, eoSQRT = 26, eoDup = 27, eoExch = 28, eoIndex = 29, eoRoll = 30, eoHFlex = 34, eoFlex = 35, eoHFlex1 = 36, eoFlex1 = 37); type TOperationP = ^TOperation; TOperation = class public Opcode : TStandardOpcode; ExtendedOpcode : TExtendedOpcode; Arguments : Variant; // array of Variant. constructor Create(aOpcode : TStandardOpcode; aExtendedOpcode : TExtendedOpcode; aArguments : Variant{array}); end; // TODO TCharStringParser with overridable virtual procedure or whatever instead. TCharString = class private fOperationCount : Cardinal; fOperations : TObjectList; protected function GetOperation(aIndex : Cardinal) : TOperation; published class function ReadFromStream(aStream : TStream) : TCharString; // virtual; constructor Create(); public // workaround destructor Destroy(); override; property Operation[aIndex : Cardinal] : TOperation read GetOperation; property OperationCount : Cardinal read fOperationCount; end; implementation uses number_encodings, sysutils, variants; function operator_P(aInput : TByte) : TBoolean; inline; begin Result := (aInput < 28) or (aInput in [29,30,31]); end; constructor TOperation.Create(aOpcode : TStandardOpcode; aExtendedOpcode : TExtendedOpcode; aArguments : Variant{array}); begin Opcode := aOpcode; ExtendedOpcode := aExtendedOpcode; Arguments := aArguments; end; constructor TCharString.Create(); begin inherited Create(); fOperations := TObjectList.Create(True); end; destructor TCharString.Destroy(); begin FreeAndNil(fOperations); inherited Destroy(); end; const ChunkSize = 100; class function TCharString.ReadFromStream(aStream : TStream) : TCharString; // virtual; var b0 : TByte; b1 : TByte; operands : Variant; operandCount : Integer; operand : Variant; begin Result := TCharString.Create(); operandCount := 0; operands := VarArrayCreate([0, ChunkSize - 1], VarVariant); Result.fOperations.Clear(); if aStream.Read(b0, 1) = 0 then // EOF Exit; repeat if operator_P(b0) then begin if b0 = 12 then // extra b1 := aStream.ReadByte() else b1 := 0; //name = operators_by_opcode[b0]; VarArrayRedim(operands, operandCount - 1); // don't ask me why they want -1. Result.fOperations.Add(TOperation.Create(TStandardOpcode(b0), TExtendedOpcode(b1), operands)); operands := VarArrayCreate([0, ChunkSize - 1], VarVariant); end else begin operand := ReadType2Operand(aStream, b0); if operandCount < ChunkSize then begin operands[operandCount] := operand; Inc(operandCount); end else begin // FIXME raise exception end; end until aStream.Read(b0, 1) = 0; // EOF end; function TCharString.GetOperation(aIndex : Cardinal) : TOperation; begin Result := TOperation(fOperations[aIndex]); end; end.