View Issue Details

IDProjectCategoryView StatusLast Update
0034063FPCPackagespublic2018-10-30 08:27
ReporterPetr-KAssigned ToMichael Van Canneyt 
PrioritynormalSeverityminorReproducibilityalways
Status closedResolutionfixed 
Product Version3.1.1Product BuildRevision: 36503 
Target Version3.2.0Fixed in Version3.1.1 
Summary0034063: fcl-pdf FontSubset invalid checksums
DescriptionI tested subset font by by Microsoft Font Validator 1.0 and by FontVal-2.1.1.
Both reports invalid table checksums and head font checksum.

I reimplemented checksum calculation by TrueType 1.0 Font Files Technical Specification. Calculation bust be 32 bit unsigned int with overflow, not int64 with clipping.

Result was tested in these validators.
Additional InformationSome pdf viewers (acroread4/ms edge/wacom sign pro pdf) cannot display correctly compressed PDFs with font subsets. I'l post multiple patches to solve this issue.

This is the last patch. For now :).

I plan to add trailer /ID and /Metadata to be more PDF/A.
TagsNo tags attached.
Fixed in Revision39530
FPCOldBugId
FPCTarget
Attached Files
  • fpttfsubsetter.patch (2,587 bytes)
    Index: packages/fcl-pdf/src/fpttfsubsetter.pp
    ===================================================================
    --- packages/fcl-pdf/src/fpttfsubsetter.pp	(revision 36503)
    +++ packages/fcl-pdf/src/fpttfsubsetter.pp	(working copy)
    @@ -42,6 +42,8 @@
       TGIDListEnumerator = class;
     
     
    +  { TFontSubsetter }
    +
       TFontSubsetter = class(TObject)
       private
         FPrefix: string;
    @@ -58,7 +60,7 @@
         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): int64;
    +    function    WriteTableHeader(AOutStream: TStream; const ATag: AnsiString; const AOffset: UInt32; const AData: TStream): UInt32;
         function    GetNewGlyphId(const OldGid: integer): Integer;
         procedure   WriteTableBodies(AOutStream: TStream; const ATables: TStringList);
         procedure   UpdateOrigGlyphIDList;
    @@ -251,21 +253,28 @@
     end;
     
     function TFontSubsetter.WriteTableHeader(AOutStream: TStream; const ATag: AnsiString; const AOffset: UInt32;
    -  const AData: TStream): int64;
    +  const AData: TStream): UInt32;
     var
    -  checksum: Int64;
    +  checksum, w: UInt32;
       n: integer;
       lByte: Byte;
     begin
       AData.Position := 0;
       checksum := 0;
    +  w := 0;
     
       for n := 0 to AData.Size-1 do
       begin
         lByte := AData.ReadByte;
    -    checksum := checksum + (((lByte and $FF) shl 24) - n mod 4 * 8);
    +    //checksum := checksum + (((lByte and $FF) shl 24) - n mod 4 * 8);
    +    w := w or (lByte shl ((3 - (n mod 4))*8));
    +    if n mod 4 = 3 then begin
    +      Inc(checksum, w);
    +      w := 0;
    +    end;
       end;
    -  checksum := checksum and $FFFFFFFF;
    +  Inc(checksum, w);
    +  //checksum := checksum and $FFFFFFFF;
     
       AOutStream.WriteBuffer(Pointer(ATag)^, 4); // Tag is always 4 bytes - written as-is, no NtoBE() required
       WriteUInt32(AOutStream, checksum);
    @@ -1011,7 +1020,7 @@
     
     procedure TFontSubsetter.SaveToStream(const AStream: TStream);
     var
    -  checksum: int64;
    +  checksum: UInt32;
       offset: int64;
       head: TStream;
       hhea: TStream;
    @@ -1088,11 +1097,11 @@
           offset := offset + o;
         end;
       end;
    -  checksum := $B1B0AFBA - (checksum and $ffffffff);
    +  checksum := UInt32($B1B0AFBA) - checksum;
     
       // update head.ChecksumAdjustment field
       head.Seek(8, soBeginning);
    -  WriteInt32(head, Int32(checksum));
    +  WriteUInt32(head, checksum);
     
       // write table bodies
       WriteTableBodies(AStream, tables);
    
    fpttfsubsetter.patch (2,587 bytes)

Activities

Petr-K

2018-07-30 16:02

reporter  

fpttfsubsetter.patch (2,587 bytes)
Index: packages/fcl-pdf/src/fpttfsubsetter.pp
===================================================================
--- packages/fcl-pdf/src/fpttfsubsetter.pp	(revision 36503)
+++ packages/fcl-pdf/src/fpttfsubsetter.pp	(working copy)
@@ -42,6 +42,8 @@
   TGIDListEnumerator = class;
 
 
+  { TFontSubsetter }
+
   TFontSubsetter = class(TObject)
   private
     FPrefix: string;
@@ -58,7 +60,7 @@
     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): int64;
+    function    WriteTableHeader(AOutStream: TStream; const ATag: AnsiString; const AOffset: UInt32; const AData: TStream): UInt32;
     function    GetNewGlyphId(const OldGid: integer): Integer;
     procedure   WriteTableBodies(AOutStream: TStream; const ATables: TStringList);
     procedure   UpdateOrigGlyphIDList;
@@ -251,21 +253,28 @@
 end;
 
 function TFontSubsetter.WriteTableHeader(AOutStream: TStream; const ATag: AnsiString; const AOffset: UInt32;
-  const AData: TStream): int64;
+  const AData: TStream): UInt32;
 var
-  checksum: Int64;
+  checksum, w: UInt32;
   n: integer;
   lByte: Byte;
 begin
   AData.Position := 0;
   checksum := 0;
+  w := 0;
 
   for n := 0 to AData.Size-1 do
   begin
     lByte := AData.ReadByte;
-    checksum := checksum + (((lByte and $FF) shl 24) - n mod 4 * 8);
+    //checksum := checksum + (((lByte and $FF) shl 24) - n mod 4 * 8);
+    w := w or (lByte shl ((3 - (n mod 4))*8));
+    if n mod 4 = 3 then begin
+      Inc(checksum, w);
+      w := 0;
+    end;
   end;
-  checksum := checksum and $FFFFFFFF;
+  Inc(checksum, w);
+  //checksum := checksum and $FFFFFFFF;
 
   AOutStream.WriteBuffer(Pointer(ATag)^, 4); // Tag is always 4 bytes - written as-is, no NtoBE() required
   WriteUInt32(AOutStream, checksum);
@@ -1011,7 +1020,7 @@
 
 procedure TFontSubsetter.SaveToStream(const AStream: TStream);
 var
-  checksum: int64;
+  checksum: UInt32;
   offset: int64;
   head: TStream;
   hhea: TStream;
@@ -1088,11 +1097,11 @@
       offset := offset + o;
     end;
   end;
-  checksum := $B1B0AFBA - (checksum and $ffffffff);
+  checksum := UInt32($B1B0AFBA) - checksum;
 
   // update head.ChecksumAdjustment field
   head.Seek(8, soBeginning);
-  WriteInt32(head, Int32(checksum));
+  WriteUInt32(head, checksum);
 
   // write table bodies
   WriteTableBodies(AStream, tables);
fpttfsubsetter.patch (2,587 bytes)

Michael Van Canneyt

2018-07-30 16:11

administrator   ~0109767

Tested and applied, thank you very much !

Looking forward to PDF/A additions, it's on my wishlist too :)

Issue History

Date Modified Username Field Change
2018-07-30 16:02 Petr-K New Issue
2018-07-30 16:02 Petr-K File Added: fpttfsubsetter.patch
2018-07-30 16:06 Michael Van Canneyt Assigned To => Michael Van Canneyt
2018-07-30 16:06 Michael Van Canneyt Status new => assigned
2018-07-30 16:11 Michael Van Canneyt Fixed in Revision => 39530
2018-07-30 16:11 Michael Van Canneyt Note Added: 0109767
2018-07-30 16:11 Michael Van Canneyt Status assigned => resolved
2018-07-30 16:11 Michael Van Canneyt Fixed in Version => 3.1.1
2018-07-30 16:11 Michael Van Canneyt Resolution open => fixed
2018-07-30 16:11 Michael Van Canneyt Target Version => 3.2.0
2018-10-30 08:27 Petr-K Status resolved => closed