diff --git a/components/fpvectorial/pdfvectorialwriter.pas b/components/fpvectorial/pdfvectorialwriter.pas new file mode 100644 index 0000000000..24f3cb5574 --- /dev/null +++ b/components/fpvectorial/pdfvectorialwriter.pas @@ -0,0 +1,744 @@ +unit pdfvectorialwriter; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, StrUtils, Graphics, Math, fpvectorial, fpPDF, fpTTF; + +const + {$IFDEF WINDOWS} + FONT_DIR = 'C:\Windows\Fonts\'; + {$ELSE} + {$IFDEF LINUX} // TODO: there are different font locations for linux + FONT_DIR = '/usr/local/share/fonts/'; + {$ELSE} + FONT_DIR = '~/Library/Fonts'; + {$ENDIF} + {$ENDIF} + + cInchToMM = 25.4; + cInchToCM = 2.54; + +type + { + T + ____v____ + | | + | | + L > | | < R + | | + |_______| + ^ + B + + every entity being rendered should have a bound box + limiting the space it can use. Those limits are set by + left, top, right, bottom spacings (in mm). X and Y + shall be used as cursors to add padding and navigate + + } + TvBoundBox = record + L, T, R, B: Double; + X, Y: Double; // cursor + end; + + TvEntityKind = (ekParagraph, ekList, ekTable, ekText, ekField, ekImage, ekNone); + + TvEntityInfo = record + Kind: TvEntityKind; + Box: TvBoundBox; + end; + + TvPDFFont = record + Font: TvFont; + Cache: TFPFontCacheItem; + ID: Integer; + end; + + TvPDFFontArray = array of TvPDFFont; + + TvPDFVectorialWriter = class(TvCustomVectorialWriter) + private + FPointSeparator: TFormatSettings; + + FDocument: TPDFDocument; + FSection: TPDFSection; + FPage: TPDFPage; + + FFonts: TvPDFFontArray; + + // default style + FStyle: TvStyle; + + // line space (TODO: customisability) + FLineSpace: Double; + FSpacing: Double; + + { utilities } + function GetBorderWidth(AType: TvTableBorderType): Double; + function GetStyle(AText: TvText; APara: TvParagraph): TvStyle; + function UnitToMM(AValue: Double; AUnit: TvUnits; Box: TvBoundBox): Double; + function GetEntityInfo(AEntity, AParent: TvEntity; ABox: TvBoundBox): TvEntityInfo; + function GetFontID(AName: String; IsBold, IsItalic: Boolean): Integer; + function GetFontIDnew(AName: String; IsBold, IsItalic: Boolean): Integer; + function GetFont(AName: String; IsBold, IsItalic: Boolean): TvPDFFont; + function TabsToSpaces(AText: String): String; + + { entity measurements } + function GetHeight(Entity, AParent: TvEntity): Double; + function GetHeight(AEntity, AParent: TvEntity; AKind: TvEntityKind): Double; + function GetTextHeight(AText: TvText; APara: TvParagraph): Double; + function GetTextWidth(AText: TvText; APara: TvParagraph): Double; + function GetFontHeight(AText: TvText): Double; + function GetFontWidth(AText: TvText): Double; + function GetParagraphHeight(APara: TvParagraph): Double; + function GetListHeight(AList: TvList): Double; + function GetTableHeight(ATable: TvTable): Double; + function GetRowHeight(ARow: TvTableRow; ALast: Boolean): Double; + function GetCellHeight(ACell: TvTableCell; ALast: Boolean): Double; + function GetFieldHeight(AField: TvField): Double; + function GetImageHeight(AImage: TvRasterImage): Double; + function GetWidth(AEntity, AParent: TvEntity; AKind: TvEntityKind; ABox: TvBoundBox): Double; + function GetImageWidth(AImage: TvRasterImage): Double; + + { add entities } + procedure AddFonts(AData: TvVectorialDocument); + function AddPage(APage: TvTextPageSequence; AData: TvVectorialDocument): TvBoundBox; + procedure AddParagraph(APara: TvParagraph; APage: TvTextPageSequence; AData: TvVectorialDocument; ABox: TvBoundBox); + procedure AddList(AList: TvList; var Box: TvBoundBox); + procedure AddTable(ATable: TvTable; APage: TvTextPageSequence; AData: TvVectorialDocument; ABox: TvBoundBox); + procedure AddText(AText: TvText; APara: TvParagraph; ABox: TvBoundBox); + procedure AddField(AField: TvField; ABox: TvBoundBox); + procedure AddImage(AImage: TvRasterImage; ABox: TvBoundBox); + public + constructor Create; override; + procedure WriteToFile(AFileName: String; AData: TvVectorialDocument); override; + procedure WriteToStream(AStream: TStream; AData: TvVectorialDocument); override; + end; + +implementation + +{ TvPDFVectorialWriter } + +function Inc(var AValue: Double; AIncrement: Double): Double; +begin + AValue := AValue + AIncrement; + Result := AValue; +end; + +function Dec(var AValue: Double; ADecrement: Double): Double; +begin + AValue := AValue - ADecrement; + Result := AValue; +end; + +// definitions for the border widths +function TvPDFVectorialWriter.GetBorderWidth(AType: TvTableBorderType): Double; +begin + case AType of + tbtNone: Result := 0; + tbtSingle: Result := 1; + tbtDefault: Result := 0.4; + tbtDouble: Result := 2; + tbtDashed: Result := 0; // TODO: add dashed line + end; +end; + +function TvPDFVectorialWriter.GetStyle(AText: TvText; APara: TvParagraph): TvStyle; +begin + Result := AText.GetCombinedStyle(APara); + if Result = nil then Result := FStyle; +end; + +function TvPDFVectorialWriter.GetEntityInfo(AEntity, AParent: TvEntity; ABox: TvBoundBox): TvEntityInfo; +begin + Result.Box := ABox; + if (AEntity is TvParagraph) then + Result.Kind := ekParagraph + else if (AEntity is TvList) then + Result.Kind := ekList + else if (AEntity is TvTable) then + Result.Kind := ekTable + else if (AEntity is TvText) then + Result.Kind := ekText + else if (AEntity is TvField) then + Result.Kind := ekField + else if (AEntity is TvRasterImage) then + Result.Kind := ekImage; + Result.Box.B := Result.Box.T + GetHeight(AEntity, AParent, Result.Kind); + Result.Box.R := GetWidth(AEntity, AParent, Result.Kind, Result.Box); + Result.Box.X := Result.Box.L; + Result.Box.Y := Result.Box.T; +end; + +// convert tabs to spaces (really only working for monospace fonts) +function TvPDFVectorialWriter.TabsToSpaces(AText: String): String; +const + TAB_SIZE = 6; +var + TabPos, i: Integer; + Tmp: String; +begin + for i := 0 to AText.CountChar(#9) do + begin + TabPos := Pos(#9, AText) - 1; + Tmp := AText.Substring(0, TabPos); + while Tmp.Length mod TAB_SIZE <> 0 do + Tmp := Tmp + ' '; + AText := AText.Replace(AText.Substring(0, TabPos) + #9, Tmp); + end; + Result := AText; +end; + +function TvPDFVectorialWriter.GetHeight(AEntity, AParent: TvEntity; AKind: TvEntityKind): Double; +begin + case AKind of + ekParagraph: Result := GetParagraphHeight(TvParagraph(AEntity)); + ekList: Result := GetListHeight(TvList(AEntity)); + ekTable: Result := GetTableHeight(TvTable(AEntity)); + ekText: Result := GetTextHeight(TvText(AEntity), TvParagraph(AParent)); + ekField: Result := GetFieldHeight(TvField(AEntity)); + ekImage: Result := GetImageHeight(TvRasterImage(AEntity)); + end; +end; + +function TvPDFVectorialWriter.GetWidth(AEntity, AParent: TvEntity; AKind: TvEntityKind; ABox: TvBoundBox): Double; +begin + case AKind of + ekText: Result := ABox.L + GetTextWidth(TvText(AEntity), TvParagraph(AParent)); + ekImage: Result := ABox.L + GetImageWidth(TvRasterImage(AEntity)); + else Result := ABox.R; // TODO: add width for other entities + end; +end; + +function TvPDFVectorialWriter.GetHeight(Entity, AParent: TvEntity): Double; +begin + if Entity is TvText then + Exit(GetTextHeight(TvText(Entity), TvParagraph(AParent))); + if Entity is TvParagraph then + Exit(GetParagraphHeight(TvParagraph(Entity))); + if Entity is TvList then + Exit(GetListHeight(TvList(Entity))); + if Entity is TvTable then + Exit(GetTableHeight(TvTable(Entity))); + if Entity is TvField then + Exit(GetFieldHeight(TvField(Entity))); + if Entity is TvRasterImage then + Exit(GetImageHeight(TvRasterImage(Entity))); +end; + +function TvPDFVectorialWriter.GetTextHeight(AText: TvText; APara: TvParagraph): Double; +begin + AText.Style := GetStyle(AText, APara); + Result := AText.Style.MarginTop + GetFontHeight(AText) + AText.Style.MarginBottom + FSpacing; +end; + +function TvPDFVectorialWriter.GetTextWidth(AText: TvText; APara: TvParagraph): Double; +begin + AText.Style := GetStyle(AText, APara); + Result := AText.Style.MarginLeft + GetFontWidth(AText) + AText.Style.MarginLeft; +end; + +function TvPDFVectorialWriter.GetFontHeight(AText: TvText): Double; +var + Descender: Single; // how far does the text dip below the baseline + Font: TvPDFFont; +begin + Font := GetFont(AText.Style.Font.Name, AText.Style.Font.Bold, AText.Style.Font.Italic); + Assert(Font.Cache <> nil, 'no font cache found'); + Result := Font.Cache.TextHeight(AText.Value.Text, AText.Style.Font.Size, Descender); + //Result := (Result * cInchToMM) / gTTFontCache.DPI; + //Result := PDFToMM(AText.Style.Font.Size); + Result := PDFToMM(Result); // seems to be the best way +end; + +function TvPDFVectorialWriter.GetFontWidth(AText: TvText): Double; +var + Font: TvPDFFont; +begin + Font := GetFont(AText.Style.Font.Name, AText.Style.Font.Bold, AText.Style.Font.Italic); + Assert(Font.Cache <> nil, 'no font cache found'); + Result := Font.Cache.TextWidth(AText.Value.Text, AText.Style.Font.Size); + Result := (Result * cInchToMM) / gTTFontCache.DPI; +end; + +function TvPDFVectorialWriter.GetParagraphHeight(APara: TvParagraph): Double; +var + Tmp: Double; + i: Integer; +begin + Result := 0; + if APara.GetEntitiesCount = 0 then + begin + if APara.Style <> nil then + begin + // empty paragraphs need to take space as if there + // is some text, like in odt and docx documents + APara.Style.MarginTop := 0; + APara.Style.MarginBottom := 0; + APara.AddText(''); + end; + end; + for i := 0 to APara.GetEntitiesCount - 1 do + begin + Tmp := GetHeight(APara.GetEntity(i), APara); + if Tmp > Result then Result := Tmp; + end; +end; + +function TvPDFVectorialWriter.GetListHeight(AList: TvList): Double; +begin + Result := 0; // TODO: add list height +end; + +function TvPDFVectorialWriter.GetTableHeight(ATable: TvTable): Double; +var + i: Integer; +begin + Result := 0; + for I := 0 to ATable.GetRowCount - 1 do + begin + if i = ATable.GetRowCount - 1 then + Inc(Result, GetRowHeight(ATable.GetRow(i), true)) + else + Inc(Result, GetRowHeight(ATable.GetRow(i), false)); + end; +end; + +function TvPDFVectorialWriter.GetRowHeight(ARow: TvTableRow; ALast: Boolean): Double; +var + Tmp: Double; + i: Integer; +begin + Result := 0; + for i := 0 to ARow.GetCellCount - 1 do + begin // get height of tallest cell + Tmp := GetCellHeight(ARow.GetCell(i), ALast); + if Tmp > Result then Result := Tmp; + end; +end; + +function TvPDFVectorialWriter.GetCellHeight(ACell: TvTableCell; ALast: Boolean): Double; +var + Para: TvParagraph; + i: Integer; +begin + Result := ACell.SpacingTop + ACell.SpacingBottom; + for i := 0 to ACell.GetEntitiesCount - 1 do + Inc(Result, GetHeight(ACell.GetEntity(i), ACell)); + if ALast and (ACell.GetEntity(ACell.GetEntitiesCount - 1) is TvParagraph) then + begin + Para := TvParagraph(ACell.GetEntity(ACell.GetEntitiesCount - 1)); + if (Para.GetEntity(Para.GetEntitiesCount - 1) is TvText) then + Inc(Result, FLineSpace - FSpacing); + end; +end; + +function TvPDFVectorialWriter.GetFieldHeight(AField: TvField): Double; +begin + Result := 0; // TODO: add field height +end; + +function TvPDFVectorialWriter.GetImageHeight(AImage: TvRasterImage): Double; +begin + if IsZero(AImage.Width) or IsZero(AImage.Height) then + Result := (AImage.RasterImage.Height * cInchToCM) / 96 + else + Result := AImage.Height * 10; +end; + +function TvPDFVectorialWriter.GetImageWidth(AImage: TvRasterImage): Double; +begin + if IsZero(AImage.Width) or IsZero(AImage.Height) then + Result := (AImage.RasterImage.Width * cInchToCM) / 96 + else + Result := AImage.Width * 10; +end; + +function TvPDFVectorialWriter.UnitToMM(AValue: Double; AUnit: TvUnits; Box: TvBoundBox): Double; +begin + case AUnit of + dimMillimeter: Result := AValue; + dimPercent: Result := (Box.R - Box.L) * (AValue / 100); + dimPoint: Result := PDFTomm(AValue); + end; +end; + +// returns the font ID for a specific style +function TvPDFVectorialWriter.GetFontID(AName: String; IsBold, IsItalic: Boolean): Integer; +var + i: Integer; +begin + Result := 0; + for i := 0 to Length(FFonts) - 1 do + begin + if FFonts[i].Font.Name = AName then + begin + if (FFonts[i].Font.Bold = IsBold) and (FFonts[i].Font.Italic = IsItalic) then + Exit(FFonts[i].ID); + end; + end; +end; + +function TvPDFVectorialWriter.GetFontIDnew(AName: String; IsBold, IsItalic: Boolean): Integer; +var + Search: String; +begin + Search := AName; + if IsBold then Search := Search + ' Bold'; + if IsItalic then Search := Search + ' Italic'; + Result := FDocument.Fonts.FindFont(Search); + if Result = -1 then Result := 0; +end; + +function TvPDFVectorialWriter.GetFont(AName: String; IsBold, IsItalic: Boolean): TvPDFFont; +var + i: Integer; +begin + Result := FFonts[0]; + for i := 0 to Length(FFonts) - 1 do + begin + if FFonts[i].Font.Name = AName then + begin + if (FFonts[i].Font.Bold = IsBold) and (FFonts[i].Font.Italic = IsItalic) then + Exit(FFonts[i]); + end; + end; +end; + +procedure TvPDFVectorialWriter.AddFonts(AData: TvVectorialDocument); +var + Cache: TFPFontCacheItem; + NewName: String; + Font: TvFont; + i, j: Integer; +begin + SetLength(FFonts, 1); + FFonts[0].Font := FStyle.Font; + FFonts[0].Cache := gTTFontCache.Find(FStyle.Font.Name, FStyle.Font.Bold, FStyle.Font.Italic); + FFonts[0].ID := FDocument.AddFont(FFonts[0].Cache.FileName, FStyle.Font.Name); + j := 1; + for i := 0 to AData.GetStyleCount - 1 do + begin + Font := AData.GetStyle(i).Font; + if (Font.Name = '') then Continue; + NewName := Font.Name; + if Font.Bold then + NewName := NewName + ' Bold'; + if Font.Italic then NewName := NewName + ' Italic'; + if FDocument.Fonts.FindFont(NewName) = -1 then + begin + Cache := gTTFontCache.Find(Font.Name, Font.Bold, Font.Italic); + if Cache = nil then Continue; + SetLength(FFonts, Length(FFonts) + 1); + Assert(Length(FFonts) > j, 'array should be large enough'); + FFonts[j].Font := Font; + FFonts[j].ID := FDocument.AddFont(Cache.FileName, NewName); + FFonts[j].Cache := Cache; + j := j + 1; + end; + end; +end; + +// add pdf page to document +function TvPDFVectorialWriter.AddPage(APage: TvTextPageSequence; AData: TvVectorialDocument): TvBoundBox; +begin + // add new page + FPage := FDocument.Pages.AddPage; + FSection.AddPage(FPage); + + // page options could be made selectable + FPage.PaperType := ptA4; + FPage.Orientation := ppoPortrait; + FPage.UnitOfMeasure := uomMillimeters; + + // set bounds for page + Result.T := APage.MarginTop; + Result.L := APage.MarginLeft; + Result.B := PDFTomm(FPage.Paper.H) - APage.MarginBottom; + Result.R := PDFTomm(FPage.Paper.W) - APage.MarginRight; + Result.X := Result.L; + Result.Y := Result.T; +end; + +procedure TvPDFVectorialWriter.AddParagraph(APara: TvParagraph; APage: TvTextPageSequence; AData: TvVectorialDocument; ABox: TvBoundBox); +var + Info: TvEntityInfo; + Entity: TvEntity; + i: Integer; +begin + if APara.Style = nil then APara.Style := FStyle; + for i := 0 to APara.GetEntitiesCount - 1 do + begin + Entity := APara.GetEntity(i); + Info := GetEntityInfo(Entity, APara, ABox); + case Info.Kind of + ekText: + begin + case TvText(Entity).Style.Alignment of + vsaRight: + begin + Info.Box.R := ABox.R; + Info.Box.L := ABox.R - TvText(Entity).Style.MarginRight - GetFontWidth(TvText(Entity)); + Info.Box.X := Info.Box.L; + end; + vsaCenter: + begin + Info.Box.R := ABox.R; + Info.Box.X := Info.Box.X + ((Info.Box.R - Info.Box.L) / 2) - (GetFontWidth(TvText(Entity)) / 2) - TvText(Entity).Style.MarginLeft; + end; + end; + AddText(TvText(Entity), APara, Info.Box); + end; + ekField: AddField(TvField(Entity), Info.Box); + ekImage: + begin + case APara.Style.Alignment of + vsaRight: + begin + Info.Box.R := ABox.R; + Info.Box.L := ABox.R - GetImageWidth(TvRasterImage(Entity)); + Info.Box.X := Info.Box.L; + end; + vsaCenter: + begin + Info.Box.R := ABox.R; + Info.Box.X := Info.Box.X + ((Info.Box.R - Info.Box.L) / 2) - (GetImageWidth(TvRasterImage(Entity)) / 2); + end; + end; + AddImage(TvRasterImage(Entity), Info.Box); + end; + end; + // move to the right + ABox.L := Info.Box.R; + end; +end; + +procedure TvPDFVectorialWriter.AddList(AList: TvList; var Box: TvBoundBox); +begin + // TODO: add support for lists + raise Exception.Create('Unsupported Entity: TvList'); +end; + +procedure TvPDFVectorialWriter.AddTable(ATable: TvTable; APage: TvTextPageSequence; AData: TvVectorialDocument; ABox: TvBoundBox); +var + RBox, CBox, EBox: TvBoundBox; + CWidth, CLineWidth: Double; + Info: TvEntityInfo; + Cell: TvTableCell; + i, j, k, l: Integer; + Entity: TvEntity; + Row: TvTableRow; + CSame: Boolean; +begin + // set table bounds + RBox := ABox; + CSame := (ATable.ColWidths = nil); + CWidth := UnitToMM(100 / ATable.GetColCount(), dimPercent, ABox); + for i := 0 to ATable.GetRowCount - 1 do + begin + // current row + Row := ATable.GetRow(i); + if i = ATable.GetRowCount - 1 then + RBox.B := RBox.T + GetRowHeight(Row, true) + else + RBox.B := RBox.T + GetRowHeight(Row, false); + CBox := RBox; + l := 0; // counter for col widths + for j := 0 to Row.GetCellCount - 1 do + begin + // current cell + Cell := Row.GetCell(j); + + // set cursor + CBox.X := CBox.L + Cell.SpacingLeft; + CBox.Y := CBox.T + Cell.SpacingTop; + + if not CSame then // get cell width + CWidth := UnitToMM(ATable.ColWidths[l], ATable.ColWidthsUnits, ABox); + + // setup bounds + spanning + CBox.R := CBox.L + CWidth; + if Cell.SpannedCols > 1 then + begin + if not CSame then + begin + for k := 1 to Cell.SpannedCols - 1 do + Inc(CBox.R, UnitToMM(ATable.ColWidths[l + k], ATable.ColWidthsUnits, ABox)); + end + else Inc(CBox.R, CWidth * (Cell.SpannedCols - 1)); + end; + + // top border + FPage.SetColor(FPColorToTColorRef(Cell.Borders.Top.Color)); + CLineWidth := GetBorderWidth(Cell.Borders.Top.LineType); + if CLineWidth > 0 then + FPage.DrawLine(CBox.L, CBox.T, CBox.R, CBox.T, CLineWidth); + + // bottom border + FPage.SetColor(FPColorToTColorRef(Cell.Borders.Bottom.Color)); + CLineWidth := GetBorderWidth(Cell.Borders.Bottom.LineType); + if CLineWidth > 0 then + FPage.DrawLine(CBox.L, CBox.B, CBox.R, CBox.B, CLineWidth); + + // left border + FPage.SetColor(FPColorToTColorRef(Cell.Borders.Left.Color)); + CLineWidth := GetBorderWidth(Cell.Borders.Left.LineType); + if CLineWidth > 0 then + FPage.DrawLine(CBox.L, CBox.T, CBox.L, CBox.B, CLineWidth); + + // right border + FPage.SetColor(FPColorToTColorRef(Cell.Borders.Right.Color)); + CLineWidth := GetBorderWidth(Cell.Borders.Right.LineType); + if CLineWidth > 0 then + FPage.DrawLine(CBox.R, CBox.T, CBox.R, CBox.B, CLineWidth); + + // handle entities + for k := 0 to Cell.GetEntitiesCount - 1 do + begin + Entity := Cell.GetEntity(k); + EBox := CBox; + EBox.T := CBox.Y; + EBox.L := CBox.X; + EBox.R := EBox.R - Cell.SpacingRight; + EBox.B := EBox.B - Cell.SpacingBottom; + Info := GetEntityInfo(Entity, Cell, EBox); + case Info.Kind of + ekParagraph: AddParagraph(TvParagraph(Entity), APage, AData, EBox); + ekList: AddList(TvList(Entity), EBox); + ekTable: AddTable(TvTable(Entity), APage, AData, EBox); + end; + CBox.Y := Info.Box.B; + end; + // next cell + CBox.L := CBox.R; + l := l + Cell.SpannedCols; + end; + // next row + RBox.T := RBox.B; + end; + // update coordinates + ABox.T := ABox.B; +end; + +procedure TvPDFVectorialWriter.AddText(AText: TvText; APara: TvParagraph; ABox: TvBoundBox); +var + Text: String; + FID: Integer; +begin + // margins + Inc(ABox.Y, AText.Style.MarginTop); + Inc(ABox.X, AText.Style.MarginLeft); + + // move textbaseline down + Inc(ABox.Y, GetFontHeight(AText)); + + // write text + FID := GetFontIDnew(AText.Style.Font.Name, AText.Style.Font.Bold, AText.Style.Font.Italic); + FPage.SetFont(FID, Round(AText.Style.Font.Size)); + Text := TabsToSpaces(AText.Value.Text); // replace tabs (cannot be printed) + FPage.WriteText(ABox.X, ABox.Y, Text, 0, AText.Style.Font.Underline, AText.Style.Font.StrikeThrough); +end; + +procedure TvPDFVectorialWriter.AddField(AField: TvField; ABox: TvBoundBox); +begin + // TODO: add support for fields + raise Exception.Create('Unsupported Entity: TvField'); +end; + +procedure TvPDFVectorialWriter.AddImage(AImage: TvRasterImage; ABox: TvBoundBox); +var + Image: TPDFImageItem; + H, W: Double; +begin + Image := FDocument.Images.AddImageItem; + Image.Image := AImage.RasterImage; + H := GetImageHeight(AImage); + W := GetImageWidth(AImage); + Inc(ABox.Y, H); + FPage.DrawImage(ABox.X, ABox.Y, W, H, Image.ID); +end; + +constructor TvPDFVectorialWriter.Create; +begin + inherited Create; + + FPointSeparator := DefaultFormatSettings; + FPointSeparator.DecimalSeparator := '.'; + FPointSeparator.ThousandSeparator := '#'; // disable + + FDocument := TPDFDocument.Create(nil); + FDocument.FontDirectory := FONT_DIR; + + FLineSpace := 1; // default line space + // only using half of the spacing to ensure compatibility + FSpacing := FLineSpace * 50/100; // 50% + + // build font cache + gTTFontCache.SearchPath.Add(FONT_DIR); + gTTFontCache.BuildFontCache; +end; + +procedure TvPDFVectorialWriter.WriteToFile(AFileName: String; AData: TvVectorialDocument); +var + OStream: TFileStream; +begin + if ExtractFileExt(AFilename) = '' then + AFilename := AFilename + STR_PDF_EXTENSION; + OStream := TFileStream.Create(AFileName, fmCreate); + try + WriteToStream(OStream, AData); + finally + FreeAndNil(OStream); + end; +end; + +procedure TvPDFVectorialWriter.WriteToStream(AStream: TStream; AData: TvVectorialDocument); +var + TextPage: TvTextPageSequence; + Info: TvEntityInfo; + Empty: TvBoundBox; + Entity: TvEntity; + i, j: Integer; +begin + // default style + FStyle := AData.AddStyle; + FStyle.Font.Name := 'Times New Roman'; + FStyle.Font.Size := 11; + FStyle.MarginLeft := 0; + FStyle.MarginRight := 0; + FStyle.MarginTop := FSpacing / 2; + FStyle.MarginBottom := FSpacing / 2; + + FDocument.StartDocument; + AddFonts(AData); + FDocument.Options := FDocument.Options + [poPageOriginAtTop, poNoEmbeddedFonts]; + FSection := FDocument.Sections.AddSection; + AData.GuessDocumentSize(); + for i := 0 to AData.GetPageCount - 1 do // iterate through pages + begin + TextPage := AData.GetPageAsText(i); + Empty := AddPage(TextPage, AData); + for j := 0 to TextPage.GetEntitiesCount - 1 do // iterate through entities + begin + Entity := TextPage.GetEntity(j); + Info := GetEntityInfo(Entity, nil, Empty); + case Info.Kind of + ekParagraph: AddParagraph(TvParagraph(Entity), TextPage, AData, Info.Box); + ekList: AddList(TvList(Entity), Info.Box); + ekTable: AddTable(TvTable(Entity), TextPage, AData, Info.Box); + else raise Exception.Create('Unsupported Entity'); + end; + Empty.T := Info.Box.B; // decrease free space + end; + end; + FDocument.SaveToStream(AStream); +end; + +initialization + RegisterVectorialWriter(TvPDFVectorialWriter, vfPDF); + +end. +