Browse Source

fcl-pdf: added function TPDFDocument.AddFont(AFontStream: TStream; AName: String): Integer and function TFPFontCacheList.AddFontFromStream(AStream: TStream): integer

mattias 1 year ago
parent
commit
be68d66137

+ 3 - 2
packages/fcl-pdf/examples/testfppdf.lpi

@@ -1,7 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <?xml version="1.0" encoding="UTF-8"?>
 <CONFIG>
 <CONFIG>
   <ProjectOptions>
   <ProjectOptions>
-    <Version Value="11"/>
+    <Version Value="12"/>
     <General>
     <General>
       <Flags>
       <Flags>
         <SaveOnlyProjectUnits Value="True"/>
         <SaveOnlyProjectUnits Value="True"/>
@@ -9,9 +9,9 @@
         <MainUnitHasTitleStatement Value="False"/>
         <MainUnitHasTitleStatement Value="False"/>
         <SaveJumpHistory Value="False"/>
         <SaveJumpHistory Value="False"/>
         <SaveFoldState Value="False"/>
         <SaveFoldState Value="False"/>
+        <CompatibilityMode Value="True"/>
       </Flags>
       </Flags>
       <SessionStorage Value="InProjectDir"/>
       <SessionStorage Value="InProjectDir"/>
-      <MainUnit Value="0"/>
       <Title Value="testfppdf"/>
       <Title Value="testfppdf"/>
       <UseAppBundle Value="False"/>
       <UseAppBundle Value="False"/>
       <ResourceType Value="res"/>
       <ResourceType Value="res"/>
@@ -57,6 +57,7 @@
     </SearchPaths>
     </SearchPaths>
     <Linking>
     <Linking>
       <Debugging>
       <Debugging>
+        <DebugInfoType Value="dsDwarf2"/>
         <UseHeaptrc Value="True"/>
         <UseHeaptrc Value="True"/>
       </Debugging>
       </Debugging>
     </Linking>
     </Linking>

+ 47 - 14
packages/fcl-pdf/examples/testfppdf.lpr

@@ -53,7 +53,9 @@ type
     procedure   AdvancedShapes(D: TPDFDocument; APage: integer);
     procedure   AdvancedShapes(D: TPDFDocument; APage: integer);
     procedure   SampleMatrixTransform(D: TPDFDocument; APage: integer);
     procedure   SampleMatrixTransform(D: TPDFDocument; APage: integer);
     procedure   SampleLandscape(D: TPDFDocument; APage: integer);
     procedure   SampleLandscape(D: TPDFDocument; APage: integer);
-    procedure   TextInABox(const APage: TPDFPage; const AX, AY: TPDFFloat; const APointSize: integer; const ABoxColor: TARGBColor; const AFontName: string; const AText: UTF8String);
+    procedure   TextInABox(const APage: TPDFPage; const AX, AY: TPDFFloat;
+      const APointSize: integer; const ABoxColor: TARGBColor;
+      AFontName, AFontFamilyName: string; const AText: UTF8String);
   protected
   protected
     procedure   DoRun; override;
     procedure   DoRun; override;
   public
   public
@@ -76,6 +78,11 @@ var
   lOpts: TPDFOptions;
   lOpts: TPDFOptions;
 begin
 begin
   Result := TPDFDocument.Create(Nil);
   Result := TPDFDocument.Create(Nil);
+
+  // init search paths
+  Result.FontDirectory := ExpandFileName('fonts');
+
+  // set global props
   Result.Infos.Title := Application.Title;
   Result.Infos.Title := Application.Title;
   Result.Infos.Author := 'Graeme Geldenhuys';
   Result.Infos.Author := 'Graeme Geldenhuys';
   Result.Infos.Producer := 'fpGUI Toolkit 1.4.1';
   Result.Infos.Producer := 'fpGUI Toolkit 1.4.1';
@@ -105,6 +112,7 @@ begin
     Include(lOpts,poMetadataEntry);  
     Include(lOpts,poMetadataEntry);  
   Result.Options := lOpts;
   Result.Options := lOpts;
 
 
+  // add content
   Result.StartDocument;
   Result.StartDocument;
   S := Result.Sections.AddSection; // we always need at least one section
   S := Result.Sections.AddSection; // we always need at least one section
   lPageCount := cPageCount;
   lPageCount := cPageCount;
@@ -177,18 +185,40 @@ end;
 
 
 { all units of measure are in millimeters }
 { all units of measure are in millimeters }
 procedure TPDFTestApp.SimpleText(D: TPDFDocument; APage: integer);
 procedure TPDFTestApp.SimpleText(D: TPDFDocument; APage: integer);
+const
+  FontNameTitle = 'Helvetica';
+  FontNameText1 = 'FreeSans-Regular'; // arbitrary name, could be 'Free Sans Regular' too
+  FontFamilyNameText1 = 'FreeSans'; // must correspond to the family name of the ttf
+  FontNameText2 = 'Times-BoldItalic';
+  FontNameWaterMark = 'Helvetica-Bold';
 var
 var
   P : TPDFPage;
   P : TPDFPage;
   FtTitle, FtText1, FtText2: integer;
   FtTitle, FtText1, FtText2: integer;
   FtWaterMark: integer;
   FtWaterMark: integer;
+  ms: TMemoryStream;
+  aFilename: String;
 begin
 begin
   P := D.Pages[APage];
   P := D.Pages[APage];
 
 
-  // create the fonts to be used (use one of the 14 Adobe PDF standard fonts)
-  FtTitle := D.AddFont('Helvetica');
-  FtText1 := D.AddFont('FreeSans.ttf', 'FreeSans');
-  FtText2 := D.AddFont('Times-BoldItalic');
-  FtWaterMark := D.AddFont('Helvetica-Bold');
+  // create the fonts to be used
+  FtTitle := D.AddFont(FontNameTitle); // use one of the 14 Adobe PDF standard fonts
+
+  // demonstrating loading a font from a stream (used glyphs will be embedded in the pdf)
+  aFilename:=IncludeTrailingPathDelimiter(D.FontDirectory)+'FreeSans.ttf';
+  ms:=TMemoryStream.Create;
+  try
+    ms.LoadFromFile(aFilename);
+    FtText1 := D.AddFont(ms,FontNameText1);
+    ms.Position:=0;
+    gTTFontCache.AddFontFromStream(ms);
+  finally
+    ms.Free;
+  end;
+  // alternatively you can load from file:
+  //  FtText1 := D.AddFont(aFilename,FontNameText1);
+
+  FtText2 := D.AddFont(FontNameText2); // use a standard font
+  FtWaterMark := D.AddFont(FontNameWaterMark); // use a standard font
 
 
   { Page title }
   { Page title }
   P.SetFont(FtTitle, 23);
   P.SetFont(FtTitle, 23);
@@ -203,7 +233,7 @@ begin
   // Write text using PDF standard fonts
   // Write text using PDF standard fonts
   P.SetFont(FtTitle, 12);
   P.SetFont(FtTitle, 12);
   P.SetColor(clBlue, false);
   P.SetColor(clBlue, false);
-  P.WriteText(25, 50, '(25mm,50mm) Helvetica: The quick brown fox jumps over the lazy dog.');
+  P.WriteText(25, 50, '(25mm,50mm) '+FontNameTitle+': The quick brown fox jumps over the lazy dog.');
   P.SetColor(clBlack, false);
   P.SetColor(clBlack, false);
   P.WriteText(25, 57, 'Click the URL:  http://www.freepascal.org');
   P.WriteText(25, 57, 'Click the URL:  http://www.freepascal.org');
   P.AddExternalLink(54, 58, 49, 5, 'http://www.freepascal.org', false);
   P.AddExternalLink(54, 58, 49, 5, 'http://www.freepascal.org', false);
@@ -223,7 +253,7 @@ begin
 
 
   P.SetFont(ftText2,16);
   P.SetFont(ftText2,16);
   P.SetColor($C00000, false);
   P.SetColor($C00000, false);
-  P.WriteText(50, 100, '(50mm,100mm) Times-BoldItalic: Big text at absolute position');
+  P.WriteText(50, 100, '(50mm,100mm) '+FontNameText2+': Big text at absolute position');
 
 
 
 
   // -----------------------------------
   // -----------------------------------
@@ -248,10 +278,10 @@ begin
   P.WriteText(25, 280, 'B субботу двадцать третьего мая приезжает твоя любимая теща.');
   P.WriteText(25, 280, 'B субботу двадцать третьего мая приезжает твоя любимая теща.');
 
 
   { draw a rectangle around the text }
   { draw a rectangle around the text }
-  TextInABox(P, 25, 255, 23, clRed, 'FreeSans', '“Text in a Box gyj?”');
+  TextInABox(P, 25, 255, 23, clRed, FontNameText1, FontFamilyNameText1, '“Text in a Box?”');
 
 
   { lets make a hyperlink more prominent }
   { lets make a hyperlink more prominent }
-  TextInABox(P, 100, 255, 12, clMagenta, 'FreeSans', 'http://www.freepascal.org');
+  TextInABox(P, 100, 255, 12, clMagenta, FontNameText1, FontFamilyNameText1, 'http://www.freepascal.org');
   P.AddExternalLink(99, 255, 49, 5, 'http://www.freepascal.org', false);
   P.AddExternalLink(99, 255, 49, 5, 'http://www.freepascal.org', false);
 end;
 end;
 
 
@@ -753,8 +783,9 @@ begin
   P.WriteText(145, 95, Format('%d x %d  (mm)', [PixelsToMM(P.Paper.W), PixelsToMM(P.Paper.H)]));
   P.WriteText(145, 95, Format('%d x %d  (mm)', [PixelsToMM(P.Paper.W), PixelsToMM(P.Paper.H)]));
 end;
 end;
 
 
-procedure TPDFTestApp.TextInABox(const APage: TPDFPage; const AX, AY: TPDFFloat; const APointSize: integer;
-    const ABoxColor: TARGBColor; const AFontName: string; const AText: UTF8String);
+procedure TPDFTestApp.TextInABox(const APage: TPDFPage; const AX,
+  AY: TPDFFloat; const APointSize: integer; const ABoxColor: TARGBColor;
+  AFontName, AFontFamilyName: string; const AText: UTF8String);
 var
 var
   lFontIdx: integer;
   lFontIdx: integer;
   lFC: TFPFontCacheItem;
   lFC: TFPFontCacheItem;
@@ -766,6 +797,8 @@ var
   lDescenderHeightInMM: single;
   lDescenderHeightInMM: single;
   i: integer;
   i: integer;
 begin
 begin
+  if AFontFamilyName='' then AFontFamilyName:=AFontName;
+
   for i := 0 to APage.Document.Fonts.Count-1 do
   for i := 0 to APage.Document.Fonts.Count-1 do
   begin
   begin
     if APage.Document.Fonts[i].Name = AFontName then
     if APage.Document.Fonts[i].Name = AFontName then
@@ -778,9 +811,9 @@ begin
   APage.SetColor(clBlack, false);
   APage.SetColor(clBlack, false);
   APage.WriteText(AX, AY, AText);
   APage.WriteText(AX, AY, AText);
 
 
-  lFC := gTTFontCache.Find(AFontName, False, False);
+  lFC := gTTFontCache.Find(AFontFamilyName, False, False);
   if not Assigned(lFC) then
   if not Assigned(lFC) then
-    raise Exception.Create(AFontName + ' font not found');
+    raise Exception.Create(AFontFamilyName + ' font family not found');
 
 
   lHeight := lFC.TextHeight(AText, APointSize, lDescenderHeight);
   lHeight := lFC.TextHeight(AText, APointSize, lDescenderHeight);
   { convert the Font Units to mm as our PDFPage.UnitOfMeasure is set to mm. }
   { convert the Font Units to mm as our PDFPage.UnitOfMeasure is set to mm. }

+ 90 - 20
packages/fcl-pdf/src/fppdf.pp

@@ -871,6 +871,8 @@ type
   TPDFPageClass = class of TPDFPage;
   TPDFPageClass = class of TPDFPage;
 
 
 
 
+  { TPDFSection }
+
   TPDFSection = Class(TCollectionItem)
   TPDFSection = Class(TCollectionItem)
   private
   private
     FTitle: String;
     FTitle: String;
@@ -886,6 +888,8 @@ type
   end;
   end;
 
 
 
 
+  { TPDFSectionList }
+
   TPDFSectionList = Class(TCollection)
   TPDFSectionList = Class(TCollection)
   private
   private
     function GetS(AIndex : Integer): TPDFSection;
     function GetS(AIndex : Integer): TPDFSection;
@@ -895,16 +899,19 @@ type
   end;
   end;
 
 
 
 
+  { TPDFFont }
+
   TPDFFont = class(TCollectionItem)
   TPDFFont = class(TCollectionItem)
   private
   private
     FIsStdFont: boolean;
     FIsStdFont: boolean;
     FName: String;
     FName: String;
     FFontFilename: String;
     FFontFilename: String;
+    FFontStream: TMemoryStream;
     FTrueTypeFile: TTFFileInfo;
     FTrueTypeFile: TTFFileInfo;
     { stores mapping of Char IDs to font Glyph IDs }
     { stores mapping of Char IDs to font Glyph IDs }
     FTextMappingList: TTextMappingList;
     FTextMappingList: TTextMappingList;
     FSubsetFont: TStream;
     FSubsetFont: TStream;
-    procedure   PrepareTextMapping;
+    procedure   PrepareTextMapping(aStream: TStream = nil);
     procedure   SetFontFilename(const AValue: string);
     procedure   SetFontFilename(const AValue: string);
     procedure   GenerateSubsetFont;
     procedure   GenerateSubsetFont;
   public
   public
@@ -913,7 +920,9 @@ type
     { Returns a string where each character is replaced with a glyph index value instead. }
     { Returns a string where each character is replaced with a glyph index value instead. }
     function    GetGlyphIndices(const AText: UnicodeString): AnsiString;
     function    GetGlyphIndices(const AText: UnicodeString): AnsiString;
     procedure   AddTextToMappingList(const AText: UnicodeString);
     procedure   AddTextToMappingList(const AText: UnicodeString);
+    procedure   LoadFromStream(aStream: TStream);
     Property    FontFile: string read FFontFilename write SetFontFilename;
     Property    FontFile: string read FFontFilename write SetFontFilename;
+    Property    FontStream: TMemoryStream read FFontStream;
     Property    Name: String Read FName Write FName;
     Property    Name: String Read FName Write FName;
     property    TextMapping: TTextMappingList read FTextMappingList;
     property    TextMapping: TTextMappingList read FTextMappingList;
     property    IsStdFont: boolean read FIsStdFont write FIsStdFont;
     property    IsStdFont: boolean read FIsStdFont write FIsStdFont;
@@ -921,6 +930,8 @@ type
   end;
   end;
 
 
 
 
+  { TPDFTrueTypeCharWidths }
+
   TPDFTrueTypeCharWidths = class(TPDFDocumentObject)
   TPDFTrueTypeCharWidths = class(TPDFDocumentObject)
   private
   private
     FEmbeddedFontNum: integer;
     FEmbeddedFontNum: integer;
@@ -1234,6 +1245,7 @@ type
     Function CreateImage(const ALeft, ABottom, AWidth, AHeight: TPDFFloat; ANumber: integer) : TPDFImage;
     Function CreateImage(const ALeft, ABottom, AWidth, AHeight: TPDFFloat; ANumber: integer) : TPDFImage;
     Function AddFont(AName : String) : Integer; overload;
     Function AddFont(AName : String) : Integer; overload;
     Function AddFont(AFontFile: String; AName : String) : Integer; overload;
     Function AddFont(AFontFile: String; AName : String) : Integer; overload;
+    Function AddFont(AFontStream: TStream; AName : String) : Integer; overload;
     Function AddLineStyleDef(ALineWidth : TPDFFloat; AColor : TARGBColor = clBlack; APenStyle : TPDFPenStyle = ppsSolid) : Integer;
     Function AddLineStyleDef(ALineWidth : TPDFFloat; AColor : TARGBColor = clBlack; APenStyle : TPDFPenStyle = ppsSolid) : Integer;
     function AddLineStyleDef(ALineWidth : TPDFFloat; AColor : TARGBColor = clBlack; ADashArray : TDashArray = []) : Integer;
     function AddLineStyleDef(ALineWidth : TPDFFloat; AColor : TARGBColor = clBlack; ADashArray : TDashArray = []) : Integer;
     procedure AddOutputIntent(const Subtype, OutputConditionIdentifier, Info: string; ICCProfile: TStream);
     procedure AddOutputIntent(const Subtype, OutputConditionIdentifier, Info: string; ICCProfile: TStream);
@@ -1735,16 +1747,22 @@ end;
 
 
 { TPDFFont }
 { TPDFFont }
 
 
-procedure TPDFFont.PrepareTextMapping;
+procedure TPDFFont.PrepareTextMapping(aStream: TStream);
 begin
 begin
-  if FFontFilename <> '' then
+  if (FFontFilename = '') and (FFontStream=nil) then
+    exit;
+  // only create objects when needed
+  if FTextMappingList<>nil then
+    Exception.Create('TPDFFont.PrepareTextMapping already created');
+  FTextMappingList := TTextMappingList.Create;
+  FTrueTypeFile := TTFFileInfo.Create;
+  if FFontStream<>nil then
   begin
   begin
-    // only create objects when needed
-    FTextMappingList := TTextMappingList.Create;
-    FTrueTypeFile := TTFFileInfo.Create;
+    FFontStream.Position:=0;
+    FTrueTypeFile.LoadFromStream(FFontStream);
+  end else
     FTrueTypeFile.LoadFromFile(FFontFilename);
     FTrueTypeFile.LoadFromFile(FFontFilename);
-    FTrueTypeFile.PrepareFontDefinition('cp1252', True);
-  end;
+  FTrueTypeFile.PrepareFontDefinition('cp1252', True);
 end;
 end;
 
 
 procedure TPDFFont.SetFontFilename(const AValue: string);
 procedure TPDFFont.SetFontFilename(const AValue: string);
@@ -1787,9 +1805,10 @@ end;
 
 
 destructor TPDFFont.Destroy;
 destructor TPDFFont.Destroy;
 begin
 begin
-  FTextMappingList.Free;
-  FTrueTypeFile.Free;
-  FSubSetFont.Free;
+  FreeAndNil(FFontStream);
+  FreeAndNil(FTextMappingList);
+  FreeAndNil(FTrueTypeFile);
+  FreeAndNil(FSubSetFont);
   inherited Destroy;
   inherited Destroy;
 end;
 end;
 
 
@@ -1835,6 +1854,18 @@ begin
   end;
   end;
 end;
 end;
 
 
+procedure TPDFFont.LoadFromStream(aStream: TStream);
+begin
+  if FFontStream=aStream then Exit;
+  if FFontStream<>nil then
+    raise Exception.Create('TPDFFont.SetFontStream has already a stream');
+  if FFontFilename<>'' then
+    raise Exception.Create('TPDFFont.SetFontStream has already a file');
+  FFontStream:=TMemoryStream.Create;
+  FFontStream.CopyFrom(aStream,aStream.Size-aStream.Position);
+  PrepareTextMapping;
+end;
+
 { TPDFTrueTypeCharWidths }
 { TPDFTrueTypeCharWidths }
 
 
 // TODO: (optional improvement) CID -> Unicode mappings, use ranges to generate a smaller CMap
 // TODO: (optional improvement) CID -> Unicode mappings, use ranges to generate a smaller CMap
@@ -3030,7 +3061,7 @@ begin
     Raise EPDF.CreateFmt(rsErrInvalidSectionPage,[AIndex]);
     Raise EPDF.CreateFmt(rsErrInvalidSectionPage,[AIndex]);
 end;
 end;
 
 
-function TPDFSection.GetP: INteger;
+function TPDFSection.GetP: Integer;
 begin
 begin
   if Assigned(FPages) then
   if Assigned(FPages) then
     Result:=FPages.Count
     Result:=FPages.Count
@@ -4795,6 +4826,7 @@ var
   M, Buf : TMemoryStream;
   M, Buf : TMemoryStream;
   E : TPDFDictionaryItem;
   E : TPDFDictionaryItem;
   D : TPDFDictionary;
   D : TPDFDictionary;
+  aFont: TPDFFont;
 begin
 begin
   if GetE(0).FKey.Name='' then
   if GetE(0).FKey.Name='' then
     GetE(0).Write(AStream)  // write a charwidth array of a font
     GetE(0).Write(AStream)  // write a charwidth array of a font
@@ -4849,6 +4881,7 @@ begin
           begin
           begin
             Value:=E.FKey.Name;
             Value:=E.FKey.Name;
             NumFnt:=StrToInt(Copy(Value, Succ(Pos(' ', Value)), Length(Value) - Pos(' ', Value)));
             NumFnt:=StrToInt(Copy(Value, Succ(Pos(' ', Value)), Length(Value) - Pos(' ', Value)));
+            aFont:=Document.Fonts[NumFnt];
             if poSubsetFont in Document.Options then
             if poSubsetFont in Document.Options then
             begin
             begin
 
 
@@ -4871,9 +4904,15 @@ begin
             end
             end
             else
             else
             begin
             begin
-              M:=TMemoryStream.Create;
+              if aFont.FontStream<>nil then
+              begin
+                M:=aFont.FontStream;
+                M.Position:=0;
+              end else
+                M:=TMemoryStream.Create;
               try
               try
-                m.LoadFromFile(Document.FontFiles[NumFnt]);
+                if aFont.FontStream=nil then
+                  m.LoadFromFile(Document.FontFiles[NumFnt]);
                 Buf := TMemoryStream.Create;
                 Buf := TMemoryStream.Create;
                 try
                 try
                   // write fontfile stream (could be compressed or not) to a temporary buffer so we can get the size
                   // write fontfile stream (could be compressed or not) to a temporary buffer so we can get the size
@@ -4890,7 +4929,8 @@ begin
                   Buf.Free;
                   Buf.Free;
                 end;
                 end;
               finally
               finally
-                M.Free;
+                if aFont.FontStream=nil then
+                  M.Free;
               end;
               end;
             end;
             end;
           end;
           end;
@@ -5691,6 +5731,9 @@ var
   s: string;
   s: string;
 begin
 begin
   Result := False;
   Result := False;
+  if AFont.TextMapping<>nil then
+    exit(true);
+
   if ExtractFilePath(AFont.FontFile) <> '' then
   if ExtractFilePath(AFont.FontFile) <> '' then
     // assume AFont.FontFile is the full path to the TTF file
     // assume AFont.FontFile is the full path to the TTF file
     lFName := AFont.FontFile
     lFName := AFont.FontFile
@@ -5713,6 +5756,8 @@ var
   N: TPDFName;
   N: TPDFName;
   Arr: TPDFArray;
   Arr: TPDFArray;
   lFontXRef: integer;
   lFontXRef: integer;
+  aFilename: String;
+  TTF: TTFFileInfo;
 begin
 begin
   lFontXRef := GlobalXRefCount; // will be used a few lines down in AddFontNameToPages()
   lFontXRef := GlobalXRefCount; // will be used a few lines down in AddFontNameToPages()
 
 
@@ -5743,7 +5788,20 @@ begin
     FDict.AddReference('ToUnicode', GlobalXRefCount);
     FDict.AddReference('ToUnicode', GlobalXRefCount);
     CreateToUnicode(EmbeddedFontNum);
     CreateToUnicode(EmbeddedFontNum);
   end;
   end;
-  FontFiles.Add(Fonts[EmbeddedFontNum].FTrueTypeFile.Filename);
+  TTF:=Fonts[EmbeddedFontNum].FTrueTypeFile;
+  aFilename:=TTF.Filename;
+  if ExtractFilename(aFilename)='' then
+  begin
+    aFilename:='';
+    if TTF.Bold then
+      aFilename:=aFilename+'Bold';
+    if TTF.ItalicAngle<>0 then
+      aFilename:=aFilename+'Italic';
+    if aFilename='' then
+      aFilename:='Regular';
+    aFilename:=TTF.FamilyName+'-'+aFilename;
+  end;
+  FontFiles.Add(aFilename);
 end;
 end;
 
 
 procedure TPDFDocument.CreateTTFDescendantFont(const EmbeddedFontNum: integer);
 procedure TPDFDocument.CreateTTFDescendantFont(const EmbeddedFontNum: integer);
@@ -6534,9 +6592,8 @@ end;
 function TPDFDocument.AddFont(AName: String): Integer;
 function TPDFDocument.AddFont(AName: String): Integer;
 var
 var
   F: TPDFFont;
   F: TPDFFont;
-  i: integer;
 begin
 begin
-  { reuse existing font definition if it exists }
+  // reuse existing font definition if it exists
   Result:=Fonts.FindFont(AName);
   Result:=Fonts.FindFont(AName);
   if Result>=0 then exit;
   if Result>=0 then exit;
   F := Fonts.AddFontDef;
   F := Fonts.AddFontDef;
@@ -6548,10 +6605,9 @@ end;
 function TPDFDocument.AddFont(AFontFile: String; AName: String): Integer;
 function TPDFDocument.AddFont(AFontFile: String; AName: String): Integer;
 var
 var
   F: TPDFFont;
   F: TPDFFont;
-  i: integer;
   lFName: string;
   lFName: string;
 begin
 begin
-  { reuse existing font definition if it exists }
+  // reuse existing font definition if it exists
   Result:=Fonts.FindFont(AName);
   Result:=Fonts.FindFont(AName);
   if Result>=0 then exit;
   if Result>=0 then exit;
   F := Fonts.AddFontDef;
   F := Fonts.AddFontDef;
@@ -6567,6 +6623,20 @@ begin
   Result := Fonts.Count-1;
   Result := Fonts.Count-1;
 end;
 end;
 
 
+function TPDFDocument.AddFont(AFontStream: TStream; AName: String): Integer;
+var
+  F: TPDFFont;
+begin
+  // reuse existing font definition if it exists
+  Result:=Fonts.FindFont(AName);
+  if Result>=0 then exit;
+  F := Fonts.AddFontDef;
+  F.Name := AName;
+  F.IsStdFont := False;
+  F.LoadFromStream(AFontStream);
+  Result := Fonts.Count-1;
+end;
+
 function TPDFDocument.AddLineStyleDef(ALineWidth: TPDFFloat; AColor: TARGBColor;
 function TPDFDocument.AddLineStyleDef(ALineWidth: TPDFFloat; AColor: TARGBColor;
   APenStyle: TPDFPenStyle): Integer;
   APenStyle: TPDFPenStyle): Integer;
 
 

+ 43 - 7
packages/fcl-pdf/src/fpttf.pp

@@ -54,10 +54,13 @@ type
   TFPFontCacheList = class;
   TFPFontCacheList = class;
 
 
 
 
+  { TFPFontCacheItem }
+
   TFPFontCacheItem = class(TObject)
   TFPFontCacheItem = class(TObject)
   private
   private
     FFamilyName: String;
     FFamilyName: String;
     FFileName: String;
     FFileName: String;
+    FStream: TStream;
     FStyleFlags: TTrueTypeFontStyles;
     FStyleFlags: TTrueTypeFontStyles;
     FFileInfo: TTFFileInfo;
     FFileInfo: TTFFileInfo;
     FOwner: TFPFontCacheList; // reference to FontCacheList that owns this instance
     FOwner: TFPFontCacheList; // reference to FontCacheList that owns this instance
@@ -76,13 +79,15 @@ type
     function    GetHumanFriendlyName: string;
     function    GetHumanFriendlyName: string;
     function    GetFileInfo: TTFFileInfo;
     function    GetFileInfo: TTFFileInfo;
   public
   public
-    constructor Create(const AFilename: String);
+    constructor Create(const AFilename: String); overload;
+    constructor Create(const AStream: TStream); overload; // AStream is freed on destroy
     destructor  Destroy; override;
     destructor  Destroy; override;
     { Result is in pixels }
     { Result is in pixels }
     function    TextWidth(const AStr: utf8string; const APointSize: single): single;
     function    TextWidth(const AStr: utf8string; const APointSize: single): single;
     { Result is in pixels }
     { Result is in pixels }
     function    TextHeight(const AText: utf8string; const APointSize: single; out ADescender: single): single;
     function    TextHeight(const AText: utf8string; const APointSize: single; out ADescender: single): single;
     property    FileName: String read FFileName;
     property    FileName: String read FFileName;
+    property    Stream: TStream read FStream;
     property    FamilyName: String read GetFamilyName;
     property    FamilyName: String read GetFamilyName;
     property    PostScriptName: string read GetPostScriptName;
     property    PostScriptName: string read GetPostScriptName;
     property    HumanFriendlyName: string read GetHumanFriendlyName;
     property    HumanFriendlyName: string read GetHumanFriendlyName;
@@ -103,7 +108,7 @@ type
   TFPFontCacheList = class(TObject)
   TFPFontCacheList = class(TObject)
   private
   private
     FBuildFontCacheIgnoresErrors: Boolean;
     FBuildFontCacheIgnoresErrors: Boolean;
-    FList: TObjectList;
+    FList: TObjectList; // list of TFPFontCacheItem
     FSearchPath: TStringList;
     FSearchPath: TStringList;
     FDPI: integer;
     FDPI: integer;
     procedure   SearchForFonts(const AFontPath: String);
     procedure   SearchForFonts(const AFontPath: String);
@@ -120,9 +125,10 @@ type
     destructor  Destroy; override;
     destructor  Destroy; override;
     procedure   BuildFontCache;
     procedure   BuildFontCache;
     function    Add(const AObject: TFPFontCacheItem): integer;
     function    Add(const AObject: TFPFontCacheItem): integer;
+    function    AddFontFromStream(AStream: TStream): integer; // add a single font from stream, returns index
     procedure   AssignFontList(const AStrings: TStrings);
     procedure   AssignFontList(const AStrings: TStrings);
     procedure   Clear;
     procedure   Clear;
-    procedure   LoadFromFile(const AFilename: string);
+    procedure   LoadFromFile(const AFilename: string); // load list of filenames
     procedure   ReadStandardFonts;
     procedure   ReadStandardFonts;
     property    Count: integer read GetCount;
     property    Count: integer read GetCount;
     function    IndexOf(const AObject: TFPFontCacheItem): integer;
     function    IndexOf(const AObject: TFPFontCacheItem): integer;
@@ -221,14 +227,18 @@ end;
 
 
 procedure TFPFontCacheItem.LoadFileInfo;
 procedure TFPFontCacheItem.LoadFileInfo;
 begin
 begin
-  if FileExists(FFilename) then
+  if FStream<>nil then
+  begin
+    FFileInfo := TTFFileInfo.Create;
+    FFileInfo.LoadFromStream(FStream);
+  end else if (FFilename<>'') and FileExists(FFilename) then
   begin
   begin
     FFileInfo := TTFFileInfo.Create;
     FFileInfo := TTFFileInfo.Create;
     FFileInfo.LoadFromFile(FFilename);
     FFileInfo.LoadFromFile(FFilename);
-    BuildFontCacheItem;
   end
   end
   else
   else
     raise ETTF.CreateFmt(rsMissingFontFile, [FFilename]);
     raise ETTF.CreateFmt(rsMissingFontFile, [FFilename]);
+  BuildFontCacheItem;
 end;
 end;
 
 
 function TFPFontCacheItem.GetIsBold: boolean;
 function TFPFontCacheItem.GetIsBold: boolean;
@@ -333,9 +343,20 @@ begin
     raise ETTF.Create(rsNoFontFileName);
     raise ETTF.Create(rsNoFontFileName);
 end;
 end;
 
 
+constructor TFPFontCacheItem.Create(const AStream: TStream);
+begin
+  inherited Create;
+  if AStream = nil then
+    raise ETTF.Create(rsNoFontFileName);
+
+  FStream := AStream;
+  FStyleFlags := [fsRegular];
+end;
+
 destructor TFPFontCacheItem.Destroy;
 destructor TFPFontCacheItem.Destroy;
 begin
 begin
-  FFileInfo.Free;
+  FreeAndNil(FStream);
+  FreeAndNil(FFileInfo);
   inherited Destroy;
   inherited Destroy;
 end;
 end;
 
 
@@ -555,6 +576,20 @@ begin
   end;
   end;
 end;
 end;
 
 
+function TFPFontCacheList.AddFontFromStream(AStream: TStream): integer;
+var
+  ms: TMemoryStream;
+  Item: TFPFontCacheItem;
+begin
+  ms:=TMemoryStream.Create;
+  ms.CopyFrom(AStream,AStream.Size-AStream.Position);
+  ms.Position:=0;
+  Item:=TFPFontCacheItem.Create(ms);
+  Result:=Add(Item);
+  if Item.FamilyName='' then
+    raise EFontNotFound.Create('TFPFontCacheList.AddFontFromStream font has no family name');
+end;
+
 { This is operating system dependent. Our default implementation only supports
 { This is operating system dependent. Our default implementation only supports
   Linux, FreeBSD, Windows and OSX. On other platforms, no fonts will be loaded,
   Linux, FreeBSD, Windows and OSX. On other platforms, no fonts will be loaded,
   until a implementation is created.
   until a implementation is created.
@@ -699,7 +734,8 @@ begin
   Result:=DoFindPostScriptFontName(aFontName,aBold,aItalic,lfc);
   Result:=DoFindPostScriptFontName(aFontName,aBold,aItalic,lfc);
 end;
 end;
 
 
-function  TFPFontCacheList.DoFindPostScriptFontName(const AFontName: string; ABold: boolean; AItalic: boolean; Out aBaseFont : TFPFontCacheItem): String;
+function TFPFontCacheList.DoFindPostScriptFontName(const AFontName: string;
+  ABold: boolean; AItalic: boolean; out aBaseFont: TFPFontCacheItem): String;
 
 
 Var
 Var
    lNewFC : TFPFontCacheItem;
    lNewFC : TFPFontCacheItem;