added pdf document writer

This commit is contained in:
Moritz Lamprecht 2023-08-22 12:26:55 +02:00 committed by Maxim Ganetsky
parent b59196e223
commit 0fafee823a

View File

@ -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.