xtool/contrib/mORMot/SynPdf.pas

11265 lines
392 KiB
ObjectPascal

/// PDF file generation
// - this unit is a part of the freeware Synopse framework,
// licensed under a MPL/GPL/LGPL tri-license; version 1.18
unit SynPdf;
{
This file is part of Synopse framework.
Synopse framework. Copyright (C) 2022 Arnaud Bouchez
Synopse Informatique - https://synopse.info
*** BEGIN LICENSE BLOCK *****
Version: MPL 1.1/GPL 2.0/LGPL 2.1
The contents of this file are subject to the Mozilla Public License Version
1.1 (the "License"); you may not use this file except in compliance with
the License. You may obtain a copy of the License at
http://www.mozilla.org/MPL
Software distributed under the License is distributed on an "AS IS" basis,
WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
for the specific language governing rights and limitations under the License.
The Original Code is Synopse framework.
The Initial Developer of the Original Code is Arnaud Bouchez.
Portions created by the Initial Developer are Copyright (C) 2022
the Initial Developer. All Rights Reserved.
Contributor(s):
Achim Kalwa
Alexander (chaa)
aweste
CoMPi
Damien (ddemars)
David Mead (MDW)
David Heffernan
FalconB
Florian Grummel
Harald Simon
Josh Kelley (joshkel)
Karel (vandrovnik)
Kukhtin Igor
LoukaO
Marsh
MChaos
Mehrdad Momeni (nosa)
mogulza
Nzsolt
Ondrej (reddwarf)
Pierre le Riche
Sinisa (sinisav)
Sundazer
Alternatively, the contents of this file may be used under the terms of
either the GNU General Public License Version 2 or later (the "GPL"), or
the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
in which case the provisions of the GPL or the LGPL are applicable instead
of those above. If you wish to allow use of your version of this file only
under the terms of either the GPL or the LGPL, and not to allow others to
use your version of this file under the terms of the MPL, indicate your
decision by deleting the provisions above and replace them with the notice
and other provisions required by the GPL or the LGPL. If you do not delete
the provisions above, a recipient may use your version of this file under
the terms of any one of the MPL, the GPL or the LGPL.
***** END LICENSE BLOCK *****
Sponsors: https://synopse.info/fossil/wiki?name=HelpDonate
Ongoing development and maintenance of the SynPDF library was sponsored
in part by:
https://www.helpndoc.com
Easy to use yet powerful help authoring environment which can generate
various documentation formats from a single source.
Thanks for your contribution!
}
{$I Synopse.inc} // define HASINLINE CPU32 CPU64
{$ifndef MSWINDOWS}
{ disable features requiring OS specific APIs
- until they are implemented }
{$define NO_USE_SYNGDIPLUS}
{$define NO_USE_UNISCRIBE}
{$define NO_USE_METAFILE}
{$define NO_USE_BITMAP}
{$endif}
{.$define USE_PDFALEVEL}
{ - if defined, the TPdfDocument*.Create() constructor will have an PDF/A level
value instead of the boolean value PDFA1}
{$ifdef NO_USE_PDFALEVEL}
{ this special conditional can be set globaly for an application which doesn't
need the PDF/A level features }
{$undef USE_PDFALEVEL}
{$endif}
{$define USE_PDFSECURITY}
{ - if defined, the TPdfDocument*.Create() constructor will have an additional
AEncryption: TPdfEncryption parameter able to create secured PDF files
- this feature will link the SynCrypto unit for MD5 and RC4 algorithms }
{$ifdef NO_USE_PDFSECURITY}
{ this special conditional can be set globaly for an application which doesn't
need the security features, therefore dependency to SynCrypto unit }
{$undef USE_PDFSECURITY}
{$endif}
{$define USE_UNISCRIBE}
{ - if defined, the PDF engine will use the Windows Uniscribe API to
render Ordering and Shaping of the text (useful for Hebrew, Arabic and
some Asiatic languages)
- this feature need the TPdfDocument.UseUniscribe property to be forced to
true according to the language of the text you want to render
- can be undefined to safe some KB if you're sure you won't need it }
{$ifdef NO_USE_UNISCRIBE}
{ this special conditional can be set globaly for an application which doesn't
need the UniScribe features }
{$undef USE_UNISCRIBE}
{$endif}
{$define USE_SYNGDIPLUS}
{ - if defined, the PDF engine will use SynGdiPlus to handle all
JPG, TIF, PNG and GIF image types (prefered way, but need XP or later OS)
- if you'd rather use the default jpeg unit (and add some more code to your
executable), undefine this conditional }
{$ifdef NO_USE_SYNGDIPLUS}
{ this special conditional can be set globaly for an application which doesn't
need the SynGdiPlus features (like TMetaFile drawing), and would rather
use the default jpeg unit }
{$undef USE_SYNGDIPLUS}
{$endif}
{$define USE_SYNZIP}
{ - if defined, the PDF engine will use SynZip to handle the ZIP/deflate
compression schema (this unit is faster than the default ZLib unit,
and used by other units of the framework)
- if you'd rather use the default ZLib unit (and add some more code to your
executable), undefine this conditional }
{$ifdef NO_USE_SYNZIP}
{ this special conditional can be set globaly for an application for which
standard ZLib unit is enough (not to be used with a mORMot application) }
{$undef USE_SYNZIP}
{$endif}
{$define USE_BITMAP}
{ - if defined, the PDF engine will support TBitmap
- it would induce a dependency to the VCL.Graphics unit }
{$ifdef NO_USE_BITMAP}
{ this special conditional can be set globaly for an application which doesn't
need the TBitmap features }
{$undef USE_BITMAP}
{$endif}
{$define USE_METAFILE}
{ - if defined, the PDF engine will support TMetaFile/TMetaFileCanvas
- it would induce a dependency to the VCL.Graphics unit }
{$ifdef NO_USE_METAFILE}
{ this special conditional can be set globaly for an application which doesn't
need the TMetaFile features }
{$undef USE_METAFILE}
{$endif}
{$define USE_ARC}
{ - if defined, the PDF engine will support ARC, inducing a dependency to Math.pas }
{$ifdef NO_USE_ARC}
{$undef USE_ARC}
{$endif}
{$ifdef USE_BITMAP}
{$define USE_GRAPHICS_UNIT}
{$endif}
{$ifdef USE_METAFILE}
{$define USE_GRAPHICS_UNIT}
{$endif}
interface
uses
{$ifdef MSWINDOWS}
Windows,
WinSpool,
{$ifdef USE_GRAPHICS_UNIT}
{$ifdef ISDELPHIXE2}
VCL.Graphics,
{$else}
Graphics,
{$endif}
{$endif}
{$endif MSWINDOWS}
{$ifdef USE_SYNGDIPLUS}
SynGdiPlus, // use our GDI+ library for handling TJpegImage and such
{$else}
jpeg,
{$endif}
SysConst,
SysUtils,
Classes,
{$ifdef USE_ARC}
Math,
{$endif}
{$ifdef ISDELPHIXE3}
System.Types,
System.AnsiStrings,
{$else}
{$ifdef HASINLINE}
Types,
{$endif}
{$endif}
{$ifdef USE_SYNZIP}
SynZip,
{$else}
ZLib,
{$endif}
{$ifdef USE_PDFSECURITY}
SynCrypto,
{$endif}
SynCommons,
SynLZ;
const
MWT_IDENTITY = 1;
MWT_LEFTMULTIPLY = 2;
MWT_RIGHTMULTIPLY = 3;
MWT_SET = 4;
{$NODEFINE MWT_IDENTITY}
{$NODEFINE MWT_LEFTMULTIPLY}
{$NODEFINE MWT_RIGHTMULTIPLY}
{ some low-level record definition for True Type format table reading }
type
PSmallIntArray = ^TSmallIntArray;
TSmallIntArray = array[byte] of SmallInt;
PPointArray = ^TPointArray;
TPointArray = array[word] of TPoint;
PSmallPointArray = ^TSmallPointArray;
TSmallPointArray = array[word] of TSmallPoint;
/// The 'cmap' table begins with an index containing the table version number
// followed by the number of encoding tables. The encoding subtables follow.
TCmapHeader = packed record
/// Version number (Set to zero)
version: word;
/// Number of encoding subtables
numberSubtables: word;
end;
/// points to every 'cmap' encoding subtables
TCmapSubTableArray = packed array[byte] of packed record
/// Platform identifier
platformID: word;
/// Platform-specific encoding identifier
platformSpecificID: word;
/// Offset of the mapping table
offset: Cardinal;
end;
/// The 'hhea' table contains information needed to layout fonts whose
// characters are written horizontally, that is, either left to right or
// right to left
TCmapHHEA = packed record
version: longint;
ascent: word;
descent: word;
lineGap: word;
advanceWidthMax: word;
minLeftSideBearing: word;
minRightSideBearing: word;
xMaxExtent: word;
caretSlopeRise: SmallInt;
caretSlopeRun: SmallInt;
caretOffset: SmallInt;
reserved: Int64;
metricDataFormat: SmallInt;
numOfLongHorMetrics: word;
end;
/// The 'head' table contains global information about the font
TCmapHEAD = packed record
version: longint;
fontRevision: longint;
checkSumAdjustment: cardinal;
magicNumber: cardinal;
flags: word;
unitsPerEm: word;
createdDate: Int64;
modifiedDate: Int64;
xMin: SmallInt;
yMin: SmallInt;
xMax: SmallInt;
yMax: SmallInt;
macStyle: word;
lowestRec: word;
fontDirection: SmallInt;
indexToLocFormat: SmallInt;
glyphDataFormat: SmallInt
end;
PCmapHEAD = ^TCmapHEAD;
/// header for the 'cmap' Format 4 table
// - this is a two-byte encoding format
TCmapFmt4 = packed record
format: word;
length: word;
language: word;
segCountX2: word;
searchRange: word;
entrySelector: word;
rangeShift: word;
end;
type
/// the PDF library use internaly AnsiString text encoding
// - the corresponding charset is the current system charset, or the one
// supplied as a parameter to TPdfDocument.Create
PDFString = AnsiString;
/// a PDF date, encoded as 'D:20100414113241'
TPdfDate = PDFString;
/// the internal pdf file format
TPdfFileFormat = (pdf13, pdf14, pdf15, pdf16);
/// the PDF/A level
TPdfALevel = (pdfaNone, pdfa1A, pdfa1B, pdfa2A, pdfa2B, pdfa3A, pdfa3B);
/// PDF exception, raised when an invalid value is given to a constructor
EPdfInvalidValue = class(Exception);
/// PDF exception, raised when an invalid operation is triggered
EPdfInvalidOperation = class(Exception);
/// Page mode determines how the document should appear when opened
TPdfPageMode = (
pmUseNone, pmUseOutlines, pmUseThumbs, pmFullScreen);
/// Line cap style specifies the shape to be used at the ends of open
// subpaths when they are stroked
TLineCapStyle = (
lcButt_End, lcRound_End, lcProjectingSquareEnd);
/// The line join style specifies the shape to be used at the corners of paths
// that are stroked
TLineJoinStyle = (
ljMiterJoin, ljRoundJoin, ljBevelJoin);
/// The text rendering mode determines whether text is stroked, filled, or used
// as a clipping path
TTextRenderingMode = (
trFill, trStroke, trFillThenStroke, trInvisible,
trFillClipping, trStrokeClipping, trFillStrokeClipping, trClipping);
/// The annotation types determines the valid annotation subtype of TPdfDoc
TPdfAnnotationSubType = (
asTextNotes, asLink);
/// The border style of an annotation
TPdfAnnotationBorder = (
abSolid, abDashed, abBeveled, abInset, abUnderline);
/// Destination Type determines default user space coordinate system of
// Explicit destinations
TPdfDestinationType = (
dtXYZ, dtFit, dtFitH, dtFitV, dtFitR, dtFitB, dtFitBH, dtFitBV);
/// The page layout to be used when the document is opened
TPdfPageLayout = (
plSinglePage, plOneColumn, plTwoColumnLeft, plTwoColumnRight);
/// Viewer preferences specifying how the reader User Interface must start
// - vpEnforcePrintScaling will set the file version to be PDF 1.6
TPdfViewerPreference = (
vpHideToolbar, vpHideMenubar, vpHideWindowUI, vpFitWindow, vpCenterWindow,
vpEnforcePrintScaling);
/// set of Viewer preferences
TPdfViewerPreferences = set of TPdfViewerPreference;
/// available known paper size (psA4 is the default on TPdfDocument creation)
TPDFPaperSize = (
psA4, psA5, psA3, psA2, psA1, psA0, psLetter, psLegal, psUserDefined);
/// define if streams must be compressed
TPdfCompressionMethod = (
cmNone, cmFlateDecode);
/// the available PDF color range
TPdfColor = -$7FFFFFFF-1..$7FFFFFFF;
/// the PDF color, as expressed in RGB terms
// - maps COLORREF / TColorRef as used e.g. under windows
TPdfColorRGB = cardinal;
/// the recognized families of the Standard 14 Fonts
TPdfFontStandard = (pfsTimes, pfsHelvetica, pfsCourier);
/// numerical ID for every XObject
TXObjectID = integer;
const
/// used for an used xref entry
PDF_IN_USE_ENTRY = 'n';
/// used for an unused (free) xref entry, e.g. the root entry
PDF_FREE_ENTRY = 'f';
/// used e.g. for the root xref entry
PDF_MAX_GENERATION_NUM = 65535;
PDF_ENTRY_CLOSED = 0;
PDF_ENTRY_OPENED = 1;
/// the Carriage Return and Line Feed values used in the PDF file generation
// - expect #13 and #10 under Windows, but #10 (e.g. only Line Feed) is enough
// for the PDF standard, and will create somewhat smaller PDF files
CRLF = #10;
/// the Line Feed value
LF = #10;
PDF_MIN_HORIZONTALSCALING = 10;
PDF_MAX_HORIZONTALSCALING = 300;
PDF_MAX_WORDSPACE = 300;
PDF_MIN_CHARSPACE = -30;
PDF_MAX_CHARSPACE = 300;
PDF_MAX_FONTSIZE = 2000;
PDF_MAX_ZOOMSIZE = 10;
PDF_MAX_LEADING = 300;
/// list of common fonts available by default since Windows 2000
// - to not embedd these fonts in the PDF document, and save some KB,
// just use the EmbeddedTTFIgnore property of TPdfDocument/TPdfDocumentGDI:
// ! PdfDocument.EmbeddedTTFIgnore.Text := MSWINDOWS_DEFAULT_FONTS;
// - note that this is useful only if the EmbeddedTTF property was set to TRUE
MSWINDOWS_DEFAULT_FONTS: RawUTF8 =
'Arial'#13#10'Courier New'#13#10'Georgia'#13#10+
'Impact'#13#10'Lucida Console'#13#10'Roman'#13#10'Symbol'#13#10+
'Tahoma'#13#10'Times New Roman'#13#10'Trebuchet'#13#10+
'Verdana'#13#10'WingDings';
type
/// PDF text paragraph alignment
TPdfAlignment = (paLeftJustify, paRightJustify, paCenter);
/// PDF gradient direction
TGradientDirection = (gdHorizontal, gdVertical);
/// a PDF coordinates rectangle
TPdfRect = record
Left, Top, Right, Bottom: Single;
end;
PPdfRect = ^TPdfRect;
/// a PDF coordinates box
TPdfBox = record
Left, Top, Width, Height: Single;
end;
PPdfBox = ^TPdfBox;
/// allowed types for PDF objects (i.e. TPdfObject)
TPdfObjectType = (otDirectObject, otIndirectObject, otVirtualObject);
TPdfObject = class;
TPdfCanvas = class;
TPdfFont = class;
TPdfFontTrueType = class;
TPdfDocument = class;
{$ifdef USE_PDFSECURITY}
/// the available encryption levels
// - in current version only RC4 40-bit and RC4 128-bit are available, which
// correspond respectively to PDF 1.3 and PDF 1.4 formats
// - for RC4 40-bit and RC4 128-bit, associated password are restricted to a
// maximum length of 32 characters and could contain only characters from the
// Latin-1 encoding (i.e. no accent)
TPdfEncryptionLevel = (elNone, elRC4_40, elRC4_128);
/// PDF can encode various restrictions on document operations which can be
// granted or denied individually (some settings depend on others, though):
// - Printing: If printing is not allowed, the print button in Acrobat will be
// disabled. Acrobat supports a distinction between high-resolution and
// low-resolution printing. Low-resolution printing generates a bitmapped
// image of the page which is suitable only for personal use, but prevents
// high-quality reproduction and re-distilling. Note that bitmap printing
// not only results in low output quality, but will also considerably slow
// down the printing process.
// - General Editing: If this is disabled, any document modification is
// prohibited. Content extraction and printing are allowed.
// - Content Copying and Extraction: If this is disabled, selecting document
// contents and copying it to the clipboard for repurposing the contents is
// prohibited. The accessibility interface also is disabled. If you need to
// search such documents with Acrobat you must select the Certified Plugins
// Only preference in Acrobat.
// - Authoring Comments and Form Fields: If this is disabled, adding,
// modifying, or deleting comments and form fields is prohibited. Form field
// filling is allowed.
// - Form Field Fill-in or Signing: If this is enabled, users can sign and
// fill in forms, but not create form fields.
// - Document Assembly: If this is disabled, inserting, deleting or rotating
// pages, or creating bookmarks and thumbnails is prohibited.
TPdfEncryptionPermission = (epPrinting, epGeneralEditing, epContentCopy,
epAuthoringComment, epFillingForms, epContentExtraction,
epDocumentAssembly, epPrintingHighResolution);
/// set of restrictions on PDF document operations
TPdfEncryptionPermissions = set of TPdfEncryptionPermission;
/// abstract class to handle PDF security
TPdfEncryption = class
protected
fLevel: TPdfEncryptionLevel;
fFlags: integer;
fInternalKey: TByteDynArray;
fPermissions: TPdfEncryptionPermissions;
fUserPassword: string;
fOwnerPassword: string;
fDoc: TPdfDocument;
procedure EncodeBuffer(const BufIn; var BufOut; Count: cardinal); virtual; abstract;
public
/// initialize the internal structures with the proper classes
// - do not call this method directly, but class function TPdfEncryption.New()
constructor Create(aLevel: TPdfEncryptionLevel; aPermissions: TPdfEncryptionPermissions;
const aUserPassword, aOwnerPassword: string); virtual;
/// prepare a specific document to be encrypted
// - internally used by TPdfDocument.NewDoc method
procedure AttachDocument(aDoc: TPdfDocument); virtual;
/// will create the expected TPdfEncryption instance, depending on aLevel
// - to be called as parameter of TPdfDocument/TPdfDocumentGDI.Create()
// - currently, only elRC4_40 and elRC4_128 levels are implemented
// - both passwords are expected to be ASCII-7 characters only
// - aUserPassword will be asked at file opening: to be set to '' for not
// blocking display, but optional permission
// - aOwnerPassword shall not be '', and will be used internally to cypher
// the pdf file content
// - aPermissions can be either one of the PDF_PERMISSION_ALL /
// PDF_PERMISSION_NOMODIF / PDF_PERSMISSION_NOPRINT / PDF_PERMISSION_NOCOPY /
// PDF_PERMISSION_NOCOPYNORPRINT set of options
// - typical use may be:
// ! Doc := TPdfDocument.Create(false,0,false,
// ! TPdfEncryption.New(elRC4_40,'','toto',PDF_PERMISSION_NOMODIF));
// ! Doc := TPdfDocument.Create(false,0,false,
// ! TPdfEncryption.New(elRC4_128,'','toto',PDF_PERMISSION_NOCOPYNORPRINT));
class function New(aLevel: TPdfEncryptionLevel;
const aUserPassword, aOwnerPassword: string;
aPermissions: TPdfEncryptionPermissions): TPdfEncryption;
end;
/// internal 32 bytes buffer, used during encryption process
TPdfBuffer32 = array[0..31] of byte;
/// handle PDF security with RC4+MD5 scheme in 40-bit and 128-bit
// - allowed aLevel parameters for Create() are only elRC4_40 and elRC4_128
TPdfEncryptionRC4MD5 = class(TPdfEncryption)
protected
fLastObjectNumber: integer;
fLastGenerationNumber: Integer;
fUserPass, fOwnerPass: TPdfBuffer32;
fLastRC4Key: TRC4;
procedure EncodeBuffer(const BufIn; var BufOut; Count: cardinal); override;
public
/// prepare a specific document to be encrypted
// - will compute the internal keys
procedure AttachDocument(aDoc: TPdfDocument); override;
end;
{$endif USE_PDFSECURITY}
/// buffered writer class, specialized for PDF encoding
TPdfWrite = class
protected
B, BEnd, BEnd4: PAnsiChar;
fDestStream: TStream;
fDestStreamPosition: integer;
fCodePage: integer;
fAddGlyphFont: (fNone, fMain, fFallBack);
fDoc: TPdfDocument;
Tmp: array[0..511] of AnsiChar;
/// internal Ansi->Unicode conversion, using the CodePage used in Create()
// - caller must release the returned memory via FreeMem()
function ToWideChar(const Ansi: PDFString; out DLen: Integer): PWideChar;
{$ifdef USE_UNISCRIBE}
/// internal method using the Windows Uniscribe API
// - return FALSE if PW was not appened to the PDF content, TRUE if OK
function AddUnicodeHexTextUniScribe(PW: PWideChar; WinAnsiTTF: TPdfFontTrueType;
NextLine: boolean; Canvas: TPdfCanvas): boolean;
{$endif}
/// internal method NOT using the Windows Uniscribe API
procedure AddUnicodeHexTextNoUniScribe(PW: PWideChar; TTF: TPdfFontTrueType;
NextLine: boolean; Canvas: TPdfCanvas);
/// internal methods handling font fall-back
procedure AddGlyphFromChar(Char: WideChar; Canvas: TPdfCanvas;
TTF: TPdfFontTrueType; NextLine: PBoolean);
procedure AddGlyphFlush(Canvas: TPdfCanvas; TTF: TPdfFontTrueType; NextLine: PBoolean);
public
/// create the buffered writer, for a specified destination stream
constructor Create(Destination: TPdfDocument; DestStream: TStream);
/// add a character to the buffer
function Add(c: AnsiChar): TPdfWrite; overload; {$ifdef HASINLINE}inline;{$endif}
/// add an integer numerical value to the buffer
function Add(Value: Integer): TPdfWrite; overload;
/// add an integer numerical value to the buffer
// - and append a trailing space
function AddWithSpace(Value: Integer): TPdfWrite; overload;
/// add an integer numerical value to the buffer
// - with a specified fixed number of digits (left filled by '0')
function Add(Value, DigitCount: Integer): TPdfWrite; overload;
/// add a floating point numerical value to the buffer
// - up to 2 decimals are written
function Add(Value: TSynExtended): TPdfWrite; overload;
/// add a floating point numerical value to the buffer
// - up to 2 decimals are written, together with a trailing space
function AddWithSpace(Value: TSynExtended): TPdfWrite; overload;
/// add a floating point numerical value to the buffer
// - this version handles a variable number of decimals, together with
// a trailing space - this is used by ConcatToCTM e.g. or enhanced precision
function AddWithSpace(Value: TSynExtended; Decimals: cardinal): TPdfWrite; overload;
/// direct raw write of some data
// - no conversion is made
function Add(Text: PAnsiChar; Len: integer): TPdfWrite; overload;
/// direct raw write of some data
// - no conversion is made
function Add(const Text: RawByteString): TPdfWrite; overload;
/// hexadecimal write of some row data
// - row data is written as hexadecimal byte values, one by one
function AddHex(const Bin: PDFString): TPdfWrite;
/// add a word value, as Big-Endian 4 hexadecimal characters
function AddHex4(aWordValue: cardinal): TPdfWrite;
/// convert some text into unicode characters, then write it as as Big-Endian
// 4 hexadecimal characters
// - Ansi to Unicode conversion uses the CodePage set by Create() constructor
function AddToUnicodeHex(const Text: PDFString): TPdfWrite;
/// write some unicode text as as Big-Endian 4 hexadecimal characters
function AddUnicodeHex(PW: PWideChar; WideCharCount: integer): TPdfWrite;
/// convert some text into unicode characters, then write it as PDF Text
// - Ansi to Unicode conversion uses the CodePage set by Create() constructor
// - use (...) for all WinAnsi characters, or <..hexa..> for Unicode characters
// - if NextLine is TRUE, the first written PDF Text command is not Tj but '
// - during the text process, corresponding TPdfTrueTypeFont properties are
// updated (Unicode version created if necessary, indicate used glyphs for
// further Font properties writting to the PDF file content...)
// - if the current font is not True Type, all Unicode characters are
// drawn as '?'
function AddToUnicodeHexText(const Text: PDFString; NextLine: boolean;
Canvas: TPdfCanvas): TPdfWrite;
/// write some Unicode text, as PDF text
// - incoming unicode text must end with a #0
// - use (...) for all WinAnsi characters, or <..hexa..> for Unicode characters
// - if NextLine is TRUE, the first written PDF Text command is not Tj but '
// - during the text process, corresponding TPdfTrueTypeFont properties are
// updated (Unicode version created if necessary, indicate used glyphs for
// further Font properties writting to the PDF file content...)
// - if the current font is not True Type, all Unicode characters are
// drawn as '?'
function AddUnicodeHexText(PW: PWideChar; NextLine: boolean;
Canvas: TPdfCanvas): TPdfWrite;
/// write some Unicode text, encoded as Glyphs indexes, corresponding
// to the current font
function AddGlyphs(Glyphs: PWord; GlyphsCount: integer; Canvas: TPdfCanvas;
AVisAttrsPtr: Pointer=nil): TPdfWrite;
/// add some WinAnsi text as PDF text
// - used by TPdfText object
// - will optionally encrypt the content
function AddEscapeContent(const Text: RawByteString): TPdfWrite;
/// add some WinAnsi text as PDF text
// - used by TPdfText object
function AddEscape(Text: PAnsiChar; TextLen: integer): TPdfWrite;
/// add some WinAnsi text as PDF text
// - used by TPdfCanvas.ShowText method for WinAnsi text
function AddEscapeText(Text: PAnsiChar; Font: TPdfFont): TPdfWrite;
/// add some PDF /property value
function AddEscapeName(Text: PAnsiChar): TPdfWrite;
{$ifdef MSWINDOWS}
/// add a PDF color, from its TPdfColorRGB RGB value
function AddColorStr(Color: TPdfColorRGB): TPdfWrite;
{$endif}
/// add a TBitmap.Scanline[] content into the stream
procedure AddRGB(P: PAnsiChar; PInc, Count: integer);
/// add an ISO 8601 encoded date time (e.g. '2010-06-16T15:06:59-07:00')
function AddIso8601(DateTime: TDateTime): TPdfWrite;
/// add an integer value as binary, specifying a storage size in bytes
function AddIntegerBin(value: integer; bytesize: cardinal): TPdfWrite;
public
/// flush the internal buffer to the destination stream
procedure Save; {$ifdef HASINLINE}inline;{$endif}
/// return the current position
// - add the current internal buffer stream position to the destination
// stream position
function Position: Integer; {$ifdef HASINLINE}inline;{$endif}
/// get the data written to the Writer as a PDFString
// - this method could not use Save to flush the data, if all input was
// inside the internal buffer (save some CPU and memory): so don't intend
// the destination stream to be flushed after having called this method
function ToPDFString: PDFString;
end;
/// object manager is a virtual class to manage instance of indirect PDF objects
TPdfObjectMgr = class(TObject)
public
procedure AddObject(AObject: TPdfObject); virtual; abstract;
function GetObject(ObjectID: integer): TPdfObject; virtual; abstract;
end;
/// master class for most PDF objects declaration
TPdfObject = class(TObject)
private
FObjectType: TPdfObjectType;
FObjectNumber: integer;
FGenerationNumber: integer;
FSaveAtTheEnd: boolean;
protected
procedure InternalWriteTo(W: TPdfWrite); virtual;
procedure SetObjectNumber(Value: integer);
function SpaceNotNeeded: boolean; virtual;
public
/// create the PDF object instance
constructor Create; virtual;
/// Write object to specified stream
// - If object is indirect object then write references to stream
procedure WriteTo(var W: TPdfWrite);
/// write indirect object to specified stream
// - this method called by parent object
procedure WriteValueTo(var W: TPdfWrite);
/// low-level force the object to be saved now
// - you should not use this low-level method, unless you want to force
// the FSaveAtTheEnd internal flag to be set to force, so that
// TPdfDocument.SaveToStreamDirectPageFlush would flush the object content
procedure ForceSaveNow;
/// the associated PDF Object Number
// - If you set an object number higher than zero, the object is considered
// as indirect. Otherwise, the object is considered as direct object.
property ObjectNumber: integer read FObjectNumber write SetObjectNumber;
/// the associated PDF Generation Number
property GenerationNumber: integer read FGenerationNumber;
/// the corresponding type of this PDF object
property ObjectType: TPdfObjectType read FObjectType;
end;
/// a virtual PDF object, with an associated PDF Object Number
TPdfVirtualObject = class(TPdfObject)
public
constructor Create(AObjectId: integer); reintroduce;
end;
/// a PDF object, storing a boolean value
TPdfBoolean = class(TPdfObject)
private
FValue: boolean;
protected
procedure InternalWriteTo(W: TPdfWrite); override;
public
constructor Create(AValue: Boolean); reintroduce;
property Value: boolean read FValue write FValue;
end;
/// a PDF object, storing a NULL value
TPdfNull = class(TPdfObject)
protected
procedure InternalWriteTo(W: TPdfWrite); override;
end;
/// a PDF object, storing a numerical (integer) value
TPdfNumber = class(TPdfObject)
private
FValue: integer;
protected
procedure InternalWriteTo(W: TPdfWrite); override;
public
constructor Create(AValue: Integer); reintroduce;
property Value: integer read FValue write FValue;
end;
/// a PDF object, storing a numerical (floating point) value
TPdfReal = class(TPdfObject)
private
FValue: double;
protected
procedure InternalWriteTo(W: TPdfWrite); override;
public
constructor Create(AValue: double); reintroduce;
property Value: double read FValue write FValue;
end;
/// a PDF object, storing a textual value
// - the value is specified as a PDFString
// - this object is stored as '(escapedValue)'
// - in case of MBCS, conversion is made into Unicode before writing, and
// stored as '<FEFFHexUnicodeEncodedValue>'
TPdfText = class(TPdfObject)
private
FValue: RawByteString;
protected
procedure InternalWriteTo(W: TPdfWrite); override;
function SpaceNotNeeded: boolean; override;
public
constructor Create(const AValue: RawByteString); reintroduce;
property Value: RawByteString read FValue write FValue;
end;
/// a PDF object, storing a textual value
// - the value is specified as an UTF-8 encoded string
// - this object is stored as '(escapedValue)'
// - in case characters with ANSI code higher than 8 Bits, conversion is made
// into Unicode before writing, and '<FEFFHexUnicodeEncodedValue>'
TPdfTextUTF8 = class(TPdfObject)
private
FValue: RawUTF8;
protected
procedure InternalWriteTo(W: TPdfWrite); override;
function SpaceNotNeeded: boolean; override;
public
constructor Create(const AValue: RawUTF8); reintroduce;
property Value: RawUTF8 read FValue write FValue;
end;
/// a PDF object, storing a textual value
// - the value is specified as a generic VCL string
// - this object is stored as '(escapedValue)'
// - in case characters with ANSI code higher than 8 Bits, conversion is made
// into Unicode before writing, and '<FEFFHexUnicodeEncodedValue>'
TPdfTextString = class(TPdfTextUTF8)
private
function GetValue: string;
procedure SetValue(const Value: string);
public
constructor Create(const AValue: string); reintroduce;
property Value: string read GetValue write SetValue;
end;
/// a PDF object, storing a raw PDF content
// - this object is stored into the PDF stream as the defined Value
TPdfRawText = class(TPdfText)
protected
function SpaceNotNeeded: boolean; override;
procedure InternalWriteTo(W: TPdfWrite); override;
end;
/// a PDF object, storing a textual value with no encryption
// - the value is specified as a memory buffer
// - this object is stored as '(escapedValue)'
TPdfClearText = class(TPdfText)
protected
procedure InternalWriteTo(W: TPdfWrite); override;
public
constructor Create(Buffer: pointer; Len: integer); reintroduce;
end;
/// a PDF object, storing a PDF name
// - this object is stored as '/Value'
TPdfName = class(TPdfText)
protected
procedure InternalWriteTo(W: TPdfWrite); override;
public
/// append the 'SUBSET+' prefix to the Value
// - used e.g. to notify that a font is included as a subset
procedure AppendPrefix;
end;
/// used to store an array of PDF objects
TPdfArray = class(TPdfObject)
private
FArray: TList;
FObjectMgr: TPdfObjectMgr;
function GetItems(Index: integer): TPdfObject; {$ifdef HASINLINE}inline;{$endif}
function GetItemCount: integer; {$ifdef HASINLINE}inline;{$endif}
protected
procedure InternalWriteTo(W: TPdfWrite); override;
function SpaceNotNeeded: boolean; override;
public
/// create an array of PDF objects
constructor Create(AObjectMgr: TPdfObjectMgr); reintroduce; overload;
/// create an array of PDF objects, with some specified TPdfNumber values
constructor Create(AObjectMgr: TPdfObjectMgr;
const AArray: array of Integer); reintroduce; overload;
/// create an array of PDF objects, with some specified TPdfNumber values
constructor Create(AObjectMgr: TPdfObjectMgr;
AArray: PWordArray; AArrayCount: integer); reintroduce; overload;
/// create an array of PDF objects, with some specified TPdfName values
constructor CreateNames(AObjectMgr: TPdfObjectMgr;
const AArray: array of PDFString); reintroduce; overload;
/// create an array of PDF objects, with some specified TPdfReal values
constructor CreateReals(AObjectMgr: TPdfObjectMgr;
const AArray: array of double); reintroduce; overload;
/// release the instance memory, and all embedded objects instances
destructor Destroy; override;
/// Add a PDF object to the array
// - if AItem already exists, do nothing
function AddItem(AItem: TPdfObject): integer;
/// insert a PDF object to the array
// - if AItem already exists, do nothing
procedure InsertItem(Index: Integer; AItem: TPdfObject);
/// retrieve a TPDFName object stored in the array
function FindName(const AName: PDFString): TPdfName;
/// remove a specified TPDFName object stored in the array
function RemoveName(const AName: PDFString): boolean;
/// retrieve an object instance, stored in the array
property Items[Index: integer]: TPdfObject read GetItems; default;
/// retrieve the array size
property ItemCount: integer read GetItemCount;
/// the associated PDF Object Manager
property ObjectMgr: TPdfObjectMgr read FObjectMgr;
/// direct access to the internal TList instance
// - not to be used normally
property List: TList read FArray;
end;
/// PDF dictionary element definition
TPdfDictionaryElement = class(TObject)
private
FKey: TPdfName;
FValue: TPdfObject;
FIsInternal: boolean;
function GetKey: PDFString;
public
/// create the corresponding Key / Value pair
constructor Create(const AKey: PDFString; AValue: TPdfObject; AInternal: Boolean=false);
/// release the element instance, and both associated Key and Value
destructor Destroy; override;
/// the associated Key Name
property Key: PDFString read GetKey;
/// the associated Value stored in this element
property Value: TPdfObject read FValue;
/// if this element was created as internal, i.e. not to be saved to the PDF content
property IsInternal: boolean read FIsInternal;
end;
/// a PDF Dictionary is used to manage Key / Value pairs
TPdfDictionary = class(TPdfObject)
private
FArray: TList;
FObjectMgr: TPdfObjectMgr;
function GetItems(Index: integer): TPdfDictionaryElement; {$ifdef HASINLINE}inline;{$endif}
function GetItemCount: integer; {$ifdef HASINLINE}inline;{$endif}
protected
function getTypeOf: PDFString;
function SpaceNotNeeded: boolean; override;
procedure DirectWriteto(W: TPdfWrite; Secondary: TPdfDictionary);
procedure InternalWriteTo(W: TPdfWrite); override;
public
/// create the PDF dictionary
constructor Create(AObjectMgr: TPdfObjectMgr); reintroduce;
/// release the dictionay instance, and all associated elements
destructor Destroy; override;
/// fast find a value by its name
function ValueByName(const AKey: PDFString): TPdfObject;
/// fast find a boolean value by its name
function PdfBooleanByName(const AKey: PDFString): TPdfBoolean; {$ifdef HASINLINE}inline;{$endif}
/// fast find a numerical (integer) value by its name
function PdfNumberByName(const AKey: PDFString): TPdfNumber; {$ifdef HASINLINE}inline;{$endif}
/// fast find a textual value by its name
function PdfTextByName(const AKey: PDFString): TPdfText; {$ifdef HASINLINE}inline;{$endif}
/// fast find a textual value by its name
// - return '' if not found, the TPdfText.Value otherwise
function PdfTextValueByName(const AKey: PDFString): PDFString; {$ifdef HASINLINE}inline;{$endif}
/// fast find a textual value by its name
// - return '' if not found, the TPdfTextUTF8.Value otherwise
function PdfTextUTF8ValueByName(const AKey: PDFString): RawUTF8; {$ifdef HASINLINE}inline;{$endif}
/// fast find a textual value by its name
// - return '' if not found, the TPdfTextString.Value otherwise
function PdfTextStringValueByName(const AKey: PDFString): string; {$ifdef HASINLINE}inline;{$endif}
/// fast find a numerical (floating-point) value by its name
function PdfRealByName(const AKey: PDFString): TPdfReal; {$ifdef HASINLINE}inline;{$endif}
/// fast find a name value by its name
function PdfNameByName(const AKey: PDFString): TPdfName; {$ifdef HASINLINE}inline;{$endif}
/// fast find a dictionary value by its name
function PdfDictionaryByName(const AKey: PDFString): TPdfDictionary; {$ifdef HASINLINE}inline;{$endif}
/// fast find an array value by its name
function PdfArrayByName(const AKey: PDFString): TPdfArray; {$ifdef HASINLINE}inline;{$endif}
/// add a specified Key / Value pair to the dictionary
// - create PdfDictionaryElement with given key and value, and add it to list
// - if the element exists, replace value of element by given value
// - internal items are local to the framework, and not to be saved to the PDF content
procedure AddItem(const AKey: PDFString; AValue: TPdfObject; AInternal: Boolean=false); overload;
/// add a specified Key / Value pair (of type TPdfName) to the dictionary
procedure AddItem(const AKey, AValue: PDFString); overload; {$ifdef HASINLINE}inline;{$endif}
/// add a specified Key / Value pair (of type TPdfNumber) to the dictionary
procedure AddItem(const AKey: PDFString; AValue: integer); overload; {$ifdef HASINLINE}inline;{$endif}
/// add a specified Key / Value pair (of type TPdfText) to the dictionary
procedure AddItemText(const AKey, AValue: PDFString); overload; {$ifdef HASINLINE}inline;{$endif}
/// add a specified Key / Value pair (of type TPdfTextUTF8) to the dictionary
// - the value can be any UTF-8 encoded text: it will be written as
// Unicode hexadecimal to the PDF stream, if necessary
procedure AddItemTextUTF8(const AKey: PDFString; const AValue: RawUTF8); overload;
{$ifdef HASINLINE}inline;{$endif}
/// add a specified Key / Value pair (of type TPdfTextUTF8) to the dictionary
// - the value is a generic VCL string: it will be written as
// Unicode hexadecimal to the PDF stream, if necessary
procedure AddItemTextString(const AKey: PDFString; const AValue: string); overload;
{$ifdef HASINLINE}inline;{$endif}
/// remove the element specified by its Key from the dictionary
// - if the element does not exist, do nothing
procedure RemoveItem(const AKey: PDFString);
/// retrieve any dictionary element
property Items[Index: integer]: TPdfDictionaryElement read GetItems; default;
/// retrieve the dictionary element count
property ItemCount: integer read GetItemCount;
/// retrieve the associated Object Manager
property ObjectMgr: TPdfObjectMgr read FObjectMgr;
/// retrieve the type of the pdfdictionary object, i.e. the 'Type' property name
property TypeOf: PDFString read getTypeOf;
/// direct access to the internal TList instance
// - not to be used normally
property List: TList read FArray;
end;
/// a temporary memory stream, to be stored into the PDF content
// - typicaly used for the page content
// - can be compressed, if the FlateDecode filter is set
TPdfStream = class(TPdfObject)
protected
FAttributes: TPdfDictionary;
FSecondaryAttributes: TPdfDictionary;
{$ifdef USE_PDFSECURITY}
FDoNotEncrypt: boolean;
{$endif}
FFilter: PDFString;
FWriter: TPdfWrite;
procedure InternalWriteTo(W: TPdfWrite); override;
public
/// create the temporary memory stream
// - an optional DontAddToFXref is available, if you don't want to add
// this object to the main XRef list of the PDF file
constructor Create(ADoc: TPdfDocument; DontAddToFXref: boolean=false); reintroduce;
/// release the memory stream
destructor Destroy; override;
/// retrieve the associated attributes, e.g. the stream Length
property Attributes: TPdfDictionary read FAttributes;
/// retrieve the associated buffered writer
// - use this TPdfWrite instance to write some data into the stream
property Writer: TPdfWrite read FWriter;
/// retrieve the associated filter name
property Filter: PDFString read FFilter write FFilter;
end;
/// used to handle object which are not defined in this library
TPdfBinary = class(TPdfObject)
protected
FStream: TMemoryStream;
procedure InternalWriteTo(W: TPdfWrite); override;
public
/// create the instance, i.e. its associated stream
constructor Create; override;
/// release the instance
destructor Destroy; override;
/// the associated memory stream, used to store the corresponding data
// - the content of this stream will be written to the resulting
property Stream: TMemoryStream read FStream;
end;
TPdfXref = class;
TPdfObjectStream = class;
/// the Trailer of the PDF File
TPdfTrailer = class(TObject)
private
FAttributes: TPdfDictionary;
FXrefAddress: integer;
FCrossReference: TPdfStream;
FObjectStream: TPdfObjectStream;
FXRef: TPdfXref;
protected
procedure WriteTo(var W: TPdfWrite);
public
constructor Create(AObjectMgr: TPdfObjectMgr);
destructor Destroy; override;
procedure ToCrossReference(Doc: TPdfDocument);
property XrefAddress: integer read FXrefAddress write FXrefAddress;
property Attributes: TPdfDictionary read FAttributes;
end;
/// store one entry in the XRef list of the PDF file
TPdfXrefEntry = class(TObject)
private
FEntryType: PDFString;
FByteOffset: integer;
FGenerationNumber: integer;
FObjectStreamIndex: integer;
FValue: TPdfObject;
public
/// create the entry, with the specified value
// - if the value is nil (e.g. root entry), the type is 'f' (PDF_FREE_ENTRY),
// otherwise the entry type is 'n' (PDF_IN_USE_ENTRY)
constructor Create(AValue: TPdfObject);
/// release the memory, and the associated value, if any
destructor Destroy; override;
/// write the XRef list entry
procedure SaveToPdfWrite(var W: TPdfWrite);
/// return either 'f' (PDF_FREE_ENTRY), either 'n' (PDF_IN_USE_ENTRY)
property EntryType: PDFString read FEntryType write FEntryType;
/// the position (in bytes) in the PDF file content stream
// - to be ignored if ObjectStreamIndex>=0
property ByteOffset: integer read FByteOffSet;
/// the index of this object in the global compressed /ObjStm object stream
// - equals -1 by default, i.e. if stored within the main file content stream
property ObjectStreamIndex: Integer read FObjectStreamIndex;
/// the associated Generation Number
// - mostly 0, or 65535 (PDF_MAX_GENERATION_NUM) for the root 'f' entry
property GenerationNumber: integer read FGenerationNumber write FGenerationNumber;
/// the associated PDF object
property Value: TPdfObject read FValue;
end;
/// store the XRef list of the PDF file
TPdfXref = class(TPdfObjectMgr)
private
FXrefEntries: TList;
function GetItem(ObjectID: integer): TPdfXrefEntry; {$ifdef HASINLINE}inline;{$endif}
function GetItemCount: integer; {$ifdef HASINLINE}inline;{$endif}
protected
procedure WriteTo(var W: TPdfWrite);
public
/// initialize the XRef object list
// - create first a void 'f' (PDF_FREE_ENTRY) as root
constructor Create;
/// release instance memory and all associated XRef objects
destructor Destroy; override;
/// register object to the xref table, and set corresponding object ID
procedure AddObject(AObject: TPdfObject); override;
/// retrieve an object from its object ID
function GetObject(ObjectID: integer): TPdfObject; override;
/// retrieve a XRef object instance, from its object ID
property Items[ObjectID: integer]: TPdfXrefEntry read GetItem; default;
/// retrieve the XRef object count
property ItemCount: integer read GetItemCount;
end;
/// any object stored to the PDF file
// - these objects are the main unit of the PDF file content
// - these objects are written in the PDF file, followed by a "xref" table
TPdfXObject = class(TPdfStream);
/// generic PDF Outlines entries, stored as a PDF dictionary
TPdfOutlines = class(TPdfDictionary);
/// generic PDF Optional Content entry
TPdfOptionalContentGroup = class(TPdfDictionary);
TPdfInfo = class;
TPdfCatalog = class;
TPdfDestination = class;
TPdfOutlineEntry = class;
TPdfOutlineRoot = class;
TPdfPage = class;
TPdfPageClass = class of TPdfPage;
/// potential font styles
TPdfFontStyle = (pfsBold, pfsItalic, pfsUnderline, pfsStrikeOut);
/// set of font styles
TPdfFontStyles = set of TPdfFontStyle;
/// the main class of the PDF engine, processing the whole PDF document
TPdfDocument = class(TObject)
protected
FRoot: TPdfCatalog;
FCurrentPages: TPdfDictionary;
FOutputIntents: TPdfArray;
FMetaData: TPdfStream;
FCanvas: TPdfCanvas;
FTrailer: TPdfTrailer;
FXref: TPdfXref;
FInfo: TPdfInfo;
FFontList: TList;
FObjectList: TList;
FOutlineRoot: TPdfOutlineRoot;
FStructTree: TPdfDictionary;
FXObjectList: TPdfArray;
FDefaultPageWidth: cardinal;
FDefaultPageHeight: Cardinal;
FDefaultPaperSize: TPDFPaperSize;
FCompressionMethod: TPdfCompressionMethod;
FUseOutlines: boolean;
FUseOptionalContent: boolean;
FCharSet: integer;
FCodePage: cardinal;
FTrueTypeFonts: TRawUTF8DynArray;
FTrueTypeFontLastName: RawUTF8;
FTrueTypeFontLastIndex: integer;
FDC: HDC;
FScreenLogPixels: Integer;
FPrinterPxPerInch: TPoint;
FStandardFontsReplace: boolean;
fEmbeddedTTF: boolean;
fEmbeddedWholeTTF: boolean;
fEmbeddedTTFIgnore: TRawUTF8List;
fRawPages: TList;
{$ifdef USE_UNISCRIBE}
fUseUniscribe: boolean;
{$endif}
fSelectedDCFontOld: HDC;
fForceJPEGCompression: Integer;
fForceNoBitmapReuse: boolean;
fUseFontFallBack: boolean;
fFontFallBackIndex: integer;
/// a list of Bookmark text keys, associated to a TPdfDest object
fBookMarks: TRawUTF8List;
fMissingBookmarks: TRawUTF8List;
/// internal temporary variable - used by CreateOutline
fLastOutline: TPdfOutlineEntry;
fFileFormat: TPdfFileFormat;
fPDFA: TPdfALevel;
fSaveToStreamWriter: TPdfWrite;
{$ifdef USE_PDFSECURITY}
fEncryption: TPdfEncryption;
fFileID: TMD5Digest;
fEncryptionObject: TPdfDictionary;
fCurrentObjectNumber: integer;
fCurrentGenerationNumber: integer;
{$endif USE_PDFSECURITY}
function GetGeneratePDF15File: boolean;
procedure SetGeneratePDF15File(const Value: boolean);
function GetInfo: TPdfInfo; {$ifdef HASINLINE}inline;{$endif}
function GetOutlineRoot: TPdfOutlineRoot; {$ifdef HASINLINE}inline;{$endif}
procedure SetStandardFontsReplace(const Value: boolean); {$ifdef HASINLINE}inline;{$endif}
function GetEmbeddedTTFIgnore: TRawUTF8List;
procedure SetDefaultPaperSize(const Value: TPDFPaperSize);
procedure SetDefaultPageHeight(const Value: cardinal);
procedure SetDefaultPageWidth(const Value: cardinal);
procedure SetUseOptionalContent(const Value: boolean);
procedure SetPDFA(const Value: TPdfALevel);
{$ifndef USE_PDFALEVEL}
function GetPDFA1: boolean;
procedure SetPDFA1(const Value: boolean);
{$endif}
function GetDefaultPageLandscape: boolean;
procedure SetDefaultPageLandscape(const Value: boolean);
procedure SetFontFallBackName(const Value: string);
function GetFontFallBackName: string;
protected
/// can be useful in descendant objects in other units
fTPdfPageClass: TPdfPageClass;
procedure RaiseInvalidOperation;
procedure CreateInfo;
/// get the PostScript Name of a TrueType Font
// - use the Naming Table ('name') of the TTF content if not 7 bit ascii
function TTFFontPostcriptName(aFontIndex: integer; AStyle: TPdfFontStyles;
AFont: TPdfFontTrueType): PDFString;
/// register the font in the font list
procedure RegisterFont(aFont: TPdfFont);
/// get the PDF font, from its internal PDF name (e.g. 'Helvetica-Bold')
// - if the specified font exists in the font list, returns the corresponding object
// - if the font doesn't exist yet, returns NIL
function GetRegisteredNotTrueTypeFont(const APDFFontName: PDFString): TPdfFont;
/// get the supplied TrueType Font from the internal font list
// - warning: the font index is FTrueTypeFonts.IndexOf(AName)+1, since
// font index 0 is reserved for all not True Type fonts
// - if the true type font doesn't exist yet, returns NIL
// - always return the WinAnsi version of the font: the caller has to
// use the UnicodeFont property to get the corresponding Unicode aware
// version, if it was used
function GetRegisteredTrueTypeFont(AFontIndex: integer;
AStyle: TPdfFontStyles; ACharSet: byte): TPdfFont; overload;
/// get the supplied TrueType Font from the internal font list
// - if the true type font doesn't exist yet, returns NIL
function GetRegisteredTrueTypeFont(const AFontLog: TLogFontW): TPdfFont; overload;
/// find an index of in FTrueTypeFonts[]
function GetTrueTypeFontIndex(const AName: RawUTF8): integer;
// select the specified font object, then return the fDC value
function GetDCWithFont(TTF: TPdfFontTrueType): HDC;
/// release the current document content
procedure FreeDoc;
public
/// create the PDF document instance, with a Canvas and a default A4 paper size
// - the current charset and code page are retrieved from the SysLocale
// value, so the PDF engine is MBCS ready
// - note that only Win-Ansi encoding allows use of embedded standard fonts
// - you can specify a Code Page to be used for the PDFString encoding;
// by default (ACodePage left to 0), the current system code page is used
// - you can create a PDF/A compliant document by setting APDFA to PDF/A Level
// or APDFA1 to true
// - you can set an encryption instance, by using TPdfEncryption.New()
constructor Create(AUseOutlines: Boolean=false; ACodePage: integer=0;
{$ifdef USE_PDFALEVEL]}APDFA: TPdfALevel=pdfaNone{$else}APDFA1: boolean=false{$endif}
{$ifdef USE_PDFSECURITY}; AEncryption: TPdfEncryption=nil{$endif}); reintroduce;
/// release the PDF document instance
destructor Destroy; override;
/// create a new document
// - this method is called first, by the Create constructor
// - you can call it multiple time if you want to reset the whole document content
procedure NewDoc;
/// add a Page to the current PDF document
function AddPage: TPdfPage; virtual;
/// register a font to the internal TTF font list
// - some fonts may not be enumerated in the system, e.g. after calling
// AddFontMemResourceEx, so could be registered by this method
// - to be called just after Create(), before anything is written
function AddTrueTypeFont(const TTFName: RawUtf8): boolean;
/// create a Pages object
// - Pages objects can be nested, to save memory used by the Viewer
// - only necessary if you have more than 8000 pages (this method is called
// by TPdfDocument.NewDoc, so you shouldn't have to use it)
function CreatePages(Parent: TPdfDictionary): TPdfDictionary;
/// register an object (typicaly a TPdfImage) to the PDF document
// - returns the internal index as added in FXObjectList[]
function RegisterXObject(AObject: TPdfXObject; const AName: PDFString): integer;
/// add then register an object (typicaly a TPdfImage) to the PDF document
// - returns the internal index as added in FXObjectList[]
function AddXObject(const AName: PDFString; AXObject: TPdfXObject): integer;
/// save the PDF file content into a specified Stream
procedure SaveToStream(AStream: TStream; ForceModDate: TDateTime=0); virtual;
/// prepare to save the PDF file content into a specified Stream
// - is called by SaveToStream() method
// - you can then append other individual pages with SaveToStreamCurrentPage
// to avoid most resource usage (e.g. for report creation)
// - shall be finished by a SaveToStreamDirectEnd call
procedure SaveToStreamDirectBegin(AStream: TStream; ForceModDate: TDateTime=0);
/// save the current page content to the PDF file
// - shall be made one or several times after a SaveToStreamDirectBegin() call
// and before a final SaveToStreamDirectEnd call
// - see TPdfDocumentGDI.SaveToStream() in this unit, and
// TGDIPages.ExportPDFStream() in mORMotReport.pas for real use cases
// - you can set FlushCurrentPageNow=true to force the current page to be
// part of the flushed content
procedure SaveToStreamDirectPageFlush(FlushCurrentPageNow: boolean=false); virtual;
/// prepare to save the PDF file content into a specified Stream
// - shall be made once after a SaveToStreamDirectBegin() call
// - is called by SaveToStream() method
procedure SaveToStreamDirectEnd;
/// save the PDF file content into a specified file
// - return FALSE on any writing error (e.g. if the file is opened in the
// Acrobar Reader)
function SaveToFile(const aFileName: TFileName): boolean;
/// retrieve a XObject from its name
// - this method will handle also the Virtual Objects
function GetXObject(const AName: PDFString): TPdfXObject;
/// retrieve a XObject index from its name
// - this method won't handle the Virtual Objects
function GetXObjectIndex(const AName: PDFString): integer;
{$ifdef USE_BITMAP}
/// retrieve a XObject TPdfImage index from its picture attributes
// - returns '' if this image is not already there
// - uses 4 hash codes, created with 4 diverse seeds, in order to avoid
// false positives
function GetXObjectImageName(const Hash: THash128Rec; Width, Height: Integer): PDFString;
{$endif USE_BITMAP}
/// wrapper to create an annotation
// - the annotation is set to a specified position of the current page
function CreateAnnotation(AType: TPdfAnnotationSubType; const ARect: TPdfRect;
BorderStyle: TPdfAnnotationBorder=abSolid; BorderWidth: integer=1): TPdfDictionary;
/// wrapper to create a Link annotation, specified by a bookmark
// - the link is set to a specified rectangular position of the current page
// - if the bookmark name is not existing (i.e. if it no such name has been
// defined yet via the CreateBookMark method), it's added to the internal
// fMissingBookmarks list, and will be linked at CreateBookMark method call
function CreateLink(const ARect: TPdfRect; const aBookmarkName: RawUTF8;
BorderStyle: TPdfAnnotationBorder=abSolid; BorderWidth: integer=1): TPdfDictionary;
/// wrapper to create a hyper-link, with a specific URL value
function CreateHyperLink(const ARect: TPdfRect; const url: RawUTF8;
BorderStyle: TPdfAnnotationBorder=abSolid; BorderWidth: integer=0): TPdfDictionary;
/// create an Outline entry at a specified position of the current page
// - the outline tree is created from the specified numerical level (0=root),
// just after the item added via the previous CreateOutline call
// - the title is a generic VCL string, to handle fully Unicode support
function CreateOutline(const Title: string; Level: integer; TopPosition: Single): TPdfOutlineEntry;
/// create a Destination
// - the current PDF Canvas page is associated with this destination object
function CreateDestination: TPdfDestination;
/// create an internal bookmark entry at a specified position of the current page
// - the current PDF Canvas page is associated with the destination object
// - a dtXYZ destination with the corresponding TopPosition Y value is defined
// - the associated bookmark name must be unique, otherwise an exception is raised
procedure CreateBookMark(TopPosition: Single; const aBookmarkName: RawUTF8);
{$ifdef USE_BITMAP}
/// create an image from a supplied bitmap
// - returns the internal XObject name of the resulting TPDFImage
// - if you specify a PPdfBox to draw the image at the given position/size
// - if the same bitmap content is sent more than once, the TPDFImage will
// be reused (it will therefore spare resulting pdf file space) - if the
// ForceNoBitmapReuse is FALSE
// - if ForceCompression property is set, the picture will be stored as a JPEG
// - you can specify a clipping rectangle region as ClipRc parameter
function CreateOrGetImage(B: TBitmap; DrawAt: PPdfBox=nil; ClipRc: PPdfBox=nil): PDFString;
{$endif USE_BITMAP}
// create a new optional content group (layer)
// - returns a TPdfOptionalContentGroup needed for TPDFCanvas.BeginMarkedContent
// - if ParentContentGroup is not nil, the new content group is a subgroup to ParentContentGroup
// - Title is the string shown in the PDF Viewer
// - Visible controls the initial state of the content group
function CreateOptionalContentGroup(ParentContentGroup: TPdfOptionalContentGroup;
const Title: string; Visible: Boolean=true): TPdfOptionalContentGroup;
// create a Radio Optional ContentGroup
// - ContentGroups is a array of TPdfOptionalContentGroups which should behave like
// radiobuttons, i.e. only one active at a time
// - visibility must be set with CreateOptionalContentGroup, only one group should be visible
procedure CreateOptionalContentRadioGroup(const ContentGroups: array of TPdfOptionalContentGroup);
/// retrieve the current PDF Canvas, associated to the current page
property Canvas: TPdfCanvas read fCanvas;
/// retrieve the PDF information, associated to the PDF document
property Info: TPdfInfo read GetInfo;
// retrieve the PDF Document Catalog, as root of the document's object hierarchy
property Root: TPdfCatalog read fRoot;
/// retrieve the PDF Outline, associated to the PDF document
// - UseOutlines must be set to TRUE before any use of the OutlineRoot property
property OutlineRoot: TPdfOutlineRoot read GetOutlineRoot;
/// the default page width, used for new every page creation (i.e. AddPage method call)
property DefaultPageWidth: cardinal read FDefaultPageWidth write SetDefaultPageWidth;
/// the default page height, used for new every page creation (i.e. AddPage method call)
property DefaultPageHeight: cardinal read FDefaultPageHeight write SetDefaultPageHeight;
/// the default page orientation
// - a call to this property will swap default page width and height if the
// orientation is not correct
property DefaultPageLandscape: boolean read GetDefaultPageLandscape write SetDefaultPageLandscape;
/// the default page size, used for every new page creation (i.e. AddPage method call)
// - a write to this property this will reset the default paper orientation
// to Portrait: you must explicitely set DefaultPageLandscape to true, if needed
property DefaultPaperSize: TPDFPaperSize read FDefaultPaperSize write SetDefaultPaperSize;
/// the compression method used for page content storage
// - is set by default to cmFlateDecode when the class instance is created
property CompressionMethod: TPdfCompressionMethod read FCompressionMethod write FCompressionMethod;
/// if set to TRUE, the used True Type fonts will be embedded to the PDF content
// - not set by default, to save disk space and produce tiny PDF
property EmbeddedTTF: boolean read fEmbeddedTTF write fEmbeddedTTF;
/// you can add some font names to this list, if you want these fonts
// NEVER to be embedded to the PDF file, even if the EmbeddedTTF property is set
// - if you want to ignore all standard windows fonts, use:
// ! EmbeddedTTFIgnore.Text := MSWINDOWS_DEFAULT_FONTS;
property EmbeddedTTFIgnore: TRawUTF8List read GetEmbeddedTTFIgnore;
/// if set to TRUE, the embedded True Type fonts will be totaly Embeddeded
// - by default, is set to FALSE, meaning that a subset of the TTF font is
// stored into the PDF file, i.e. only the used glyphs are stored
// - this option is only available if running on Windows XP or later
property EmbeddedWholeTTF: boolean read fEmbeddedWholeTTF write fEmbeddedWholeTTF;
/// used to define if the PDF document will use outlines
// - must be set to TRUE before any use of the OutlineRoot property
property UseOutlines: boolean read FUseoutlines write FUseoutlines;
// used to define if the PDF document will use optional content (layers)
// - will also force PDF 1.5 as minimal file format
// - must be set to TRUE before calling NewDoc
// - warning: setting a value to this propery after creation will call the
// NewDoc method, therefore will erase all previous content and pages
// (including Info properties)
property UseOptionalContent: boolean read FUseOptionalContent write SetUseOptionalContent;
/// the current Code Page encoding used for this PDF Document
property CodePage: cardinal read FCodePage;
/// the current CharSet used for this PDF Document
property CharSet: integer read FCharSet;
/// set if the PDF engine must use standard fonts substitution
// - if TRUE, 'Arial', 'Times New Roman' and 'Courier New' will be
// replaced by the corresponding internal Type 1 fonts, defined in the Reader
// - only works with current ANSI_CHARSET, i.e. if you want to display
// some other unicode characters, don't enable this property: all non WinAnsi
// glyphs would be replaced by a '?' sign
// - default value is false (i.e. not embedded standard font)
property StandardFontsReplace: boolean read FStandardFontsReplace write SetStandardFontsReplace;
{$ifdef USE_UNISCRIBE}
/// set if the PDF engine must use the Windows Uniscribe API to
// render Ordering and/or Shaping of the text
// - useful for Hebrew, Arabic and some Asiatic languages handling
// - set to FALSE by default, for faster content generation
// - you can set this property temporary to TRUE, when using the Canvas
// property, but this property must be set appropriately before the content
// generation if you use any TPdfDocumentGdi.VCLCanvas text output with
// such scripting (since the PDF rendering is done once just before the
// saving, e.g. before SaveToFile() or SaveToStream() methods calls)
// - the PDF engine don't handle Font Fallback yet: the font you use
// must contain ALL glyphs necessary for the supplied unicode text - squares
// or blanks will be drawn for any missing glyph/character
property UseUniscribe: boolean read fUseUniscribe write fUseUniscribe;
{$endif}
/// used to define if the PDF document will handle "font fallback" for
// characters not existing in the current font: it will avoid rendering
// block/square symbols instead of the correct characters (e.g. for Chinese text)
// - will use the font specified by FontFallBackName property to add any
// Unicode glyph not existing in the currently selected font
// - default value is TRUE
property UseFontFallBack: boolean read fUseFontFallBack write fUseFontFallBack;
/// set the font name to be used for missing characters
// - used only if UseFontFallBack is TRUE
// - default value is 'Arial Unicode MS', if existing
property FontFallBackName: string read GetFontFallBackName write SetFontFallBackName;
/// this property can force saving all canvas bitmaps images as JPEG
// - handle bitmaps added by VCLCanvas/TMetaFile and bitmaps added as TPdfImage
// - by default, this property is set to 0 by the constructor of this class,
// meaning that the JPEG compression is not forced, and the engine will use
// the native resolution of the bitmap - in this case, the resulting
// PDF file content will be bigger in size (e.g. use this for printing)
// - 60 is the prefered way e.g. for publishing PDF over the internet
// - 80/90 is a good ratio if you want to have a nice PDF to see on screen
// - of course, this doesn't affect vectorial (i.e. emf) pictures
property ForceJPEGCompression: integer read fForceJPEGCompression write fForceJPEGCompression;
/// this property can force all canvas bitmaps to be stored directly
// - by default, the library will try to match an existing same bitmap
// content, and reuse the existing pdf object - you can set this property
// for a faster process, if you do not want to use this feature
property ForceNoBitmapReuse: boolean read fForceNoBitmapReuse write fForceNoBitmapReuse;
/// direct read-only access to all corresponding TPdfPage
// - can be useful in inherited classe
property RawPages: TList read fRawPages;
/// the resolution used for pixel to PDF coordinates conversion
// - by default, contains the Number of pixels per logical inch
// along the screen width
// - you can override this value if you really need additional resolution
// for your bitmaps and such - this is useful only with TPdfDocumentGDI and
// its associated TCanvas: all TPdfDocument native TPdfCanvas methods use
// the native resolution of the PDF, i.e. more than 7200 DPI (since we
// write coordinates with 2 decimals per point - which is 1/72 inch)
property ScreenLogPixels: Integer read FScreenLogPixels write FScreenLogPixels;
/// is pdfaXXX if the file was created in order to be PDF/A compliant
// - set APDFA parameter to a level for Create constructor in order to use it
// - warning: setting a value to this propery after creation will call the
// NewDoc method, therefore will erase all previous content and pages
// (including Info properties)
property PDFA: TPdfALevel read fPDFA write SetPDFA;
{$ifndef USE_PDFALEVEL}
/// is TRUE if the file was created in order to be PDF/A-1 compliant
// - set APDFA1 parameter to true for Create constructor in order to use it
// - warning: setting a value to this propery after creation will call the
// NewDoc method, therefore will erase all previous content and pages
// (including Info properties)
property PDFA1: boolean read GetPDFA1 write SetPDFA1;
{$endif}
/// set to TRUE to force PDF 1.5 format, which may produce smaller files
property GeneratePDF15File: boolean read GetGeneratePDF15File write SetGeneratePDF15File;
end;
/// a PDF page
TPdfPage = class(TPdfDictionary)
private
function GetPageLandscape: Boolean;
procedure SetPageLandscape(const Value: Boolean);
protected
fDoc: TPdfDocument;
FMediaBox: TPdfArray;
FWordSpace: Single;
FCharSpace: Single;
FFontSize: Single;
FFont: TPdfFont;
FLeading: Single;
FHorizontalScaling: Single;
procedure SetWordSpace(Value: Single);
procedure SetCharSpace(Value: Single);
procedure SetFontSize(Value: Single);
procedure SetHorizontalScaling(Value: Single);
procedure SetLeading(Value: Single);
procedure SetPageWidth(AValue: integer); virtual;
procedure SetPageHeight(AValue: integer); virtual;
function GetPageWidth: Integer;
function GetPageHeight: Integer;
function GetResources(const AName: PDFString): TPdfDictionary; {$ifdef HASINLINE}inline;{$endif}
public
/// create the page with its internal VCL Canvas
constructor Create(ADoc: TPdfDocument); reintroduce; virtual;
/// calculate width of specified text according to current attributes
// - this function is compatible with MBCS strings
function TextWidth(const Text: PDFString): Single;
/// calculate the number of chars which can be displayed in the specified
// width, according to current attributes
// - this function is compatible with MBCS strings, and returns
// the index in Text, not the glyphs index
function MeasureText(const Text: PDFString; Width: Single): integer;
public
/// retrieve or set the word Space attribute, in PDF coordinates of 1/72 inch
property WordSpace: Single read FWordSpace write SetWordSpace;
/// retrieve or set the Char Space attribute, in PDF coordinates of 1/72 inch
property CharSpace: Single read FCharSpace write SetCharSpace;
/// retrieve or set the Horizontal Scaling attribute, in PDF coordinates of 1/72 inch
property HorizontalScaling: Single read FHorizontalScaling write SetHorizontalScaling;
/// retrieve or set the text Leading attribute, in PDF coordinates of 1/72 inch
property Leading: Single read FLeading write SetLeading;
/// retrieve or set the font Size attribute, in system TFont.Size units
property FontSize: Single read FFontSize write SetFontSize;
/// retrieve the current used font
// - for TPdfFontTrueType, this points not always to the WinAnsi version of
// the Font, but can also point to the Unicode Version, if the last
// drawn character by ShowText() was unicode - see TPdfWrite.AddUnicodeHexText
property Font: TPdfFont read FFont write FFont;
/// retrieve or set the current page width, in PDF coordinates of 1/72 inch
property PageWidth: integer read GetPageWidth write SetPageWidth;
/// retrieve or set the current page height, in PDF coordinates of 1/72 inch
property PageHeight: integer read GetPageHeight write SetPageHeight;
/// retrieve or set the paper orientation
property PageLandscape: Boolean read GetPageLandscape write SetPageLandscape;
end;
/// is used to define how TMetaFile text positioning is rendered
// - tpSetTextJustification will handle efficiently the fact that TMetaFileCanvas
// used SetTextJustification() API calls to justify text: it will converted
// to SetWordSpace() pdf rendering
// - tpExactTextCharacterPositining will use the individual glyph positioning
// information as specified within the TMetaFile content: resulting pdf size
// will be bigger, but font kerning will be rendered as expected
// - tpKerningFromAveragePosition will use global font kerning via
// SetHorizontalScaling() pdf rendering
TPdfCanvasRenderMetaFileTextPositioning = (
tpKerningFromAveragePosition, tpSetTextJustification, tpExactTextCharacterPositining);
/// is used to define how TMetaFile text is clipped
// - by default, text will be clipped with the specified TEMRText.ptlReference
// - you could set tcClipExplicit to clip following the specified rclBounds
// - or tcAlwaysClip to use the current clipping region (if any)
// - finally, tcNeverClip would disable whole text clipping process, which
// has been reported to be preferred e.g. on Wine
TPdfCanvasRenderMetaFileTextClipping = (
tcClipReference, tcClipExplicit, tcAlwaysClip, tcNeverClip);
{$ifdef USE_ARC}
/// is used to define the TMetaFile kind of arc to be drawn
TPdfCanvasArcType =(
acArc, acArcTo, acArcAngle, acPie, acChoord);
{$endif USE_ARC}
/// access to the PDF Canvas, used to draw on the page
TPdfCanvas = class(TObject)
protected
FContents: TPdfStream;
FPage: TPdfPage;
FPageFontList: TPdfDictionary;
FDoc: TPdfDocument;
// = 72/FDoc.FScreenLogPixels
FFactor: single;
// = ViewSize.cx/WinSize.cx*FFactor
FFactorX: single;
// = ViewSize.cy/WinSize.cy*FFactor
FFactorY: single;
// = (MulDiv(ViewOrg.x, WinSize.cx, ViewSize.cx) - WinOrg.x)*FFactor
FOffsetX: single;
// = FHeight - (MulDiv(ViewOrg.y, WinSize.cy, ViewSize.cy) - WinOrg.y)*FFactor
FOffsetY: single;
// = XOff,YOff parameters specified in RenderMetaFile()
FOffsetXDef, FOffsetYDef: Single;
// WorldTransform factor and offs
FWorldFactorX, FWorldFactorY, FWorldOffsetX, FWorldOffsetY, FAngle,
FWorldCos, FWorldSin: single;
FDevScaleX, FDevScaleY: single;
FWinSize, FViewSize: TSize;
FWinOrg, FViewOrg: TPoint;
FMappingMode: Integer;
FEmfBounds: TRect;
FPrinterPxPerInch: TPoint;
FNewPath: Boolean;
{$ifdef USE_UNISCRIBE}
/// if Uniscribe-related methods must handle the text from right to left
fRightToLeftText: Boolean;
{$endif}
/// parameters taken from RenderMetaFile() call
fUseMetaFileTextPositioning: TPdfCanvasRenderMetaFileTextPositioning;
fUseMetaFileTextClipping: TPdfCanvasRenderMetaFileTextClipping;
fKerningHScaleBottom: Single;
fKerningHScaleTop: Single;
// some cache
FPreviousRasterFontName: RawUTF8;
FPreviousRasterFontIndex: integer;
// result := FOffsetX + (X * fFactorX);
function I2X(X: Integer): Single; overload;
// result := FOffsetX + (X * fFactorX);
function I2X(X: Single): Single; overload;
// result := FOffsetY - Y * fFactorY;
function I2Y(Y: Integer): Single; overload;
// result := FOffsetY - Y * fFactorY;
function I2Y(Y: Single): Single; overload;
// wrapper call I2X() and I2Y() for conversion
procedure LineToI(x, y: Integer); overload;
procedure LineToI(x, y: Single); overload;
// wrapper call I2X() and I2Y() for conversion
procedure MoveToI(x, y: Integer); overload;
procedure MoveToI(x, y: Single); overload;
// wrapper call I2X() and I2Y() for conversion
procedure CurveToCI(x1, y1, x2, y2, x3, y3: integer);
// wrapper call I2X() and I2Y() for conversion
procedure RoundRectI(x1,y1,x2,y2,cx,cy: integer);
{$ifdef USE_ARC}
procedure ARCI(centerx, centery, W, H, Sx, Sy, Ex, Ey: integer;
clockwise: boolean; arctype: TPdfCanvasArcType; var position: TPoint);
{$endif USE_ARC}
// wrapper call I2X() and I2Y() for conversion (points to origin+size)
function BoxI(Box: TRect; Normalize: boolean): TPdfBox; {$ifdef HASINLINE}inline;{$endif}
// wrapper call I2X() and I2Y() for conversion
procedure PointI(x, y: Single); {$ifdef HASINLINE}inline;{$endif}
function RectI(Rect: TRect; Normalize: boolean): TPdfRect;
procedure DrawXObjectPrepare(const AXObjectName: PDFString);
// wrappers about offset calculation
function ViewOffsetX(X: Single): Single;
function ViewOffsetY(Y: Single): Single;
function GetWorldFactorX: Single;
function GetWorldFactorY: Single;
property WorldFactorX: Single read GetWorldFactorX write FWorldFactorX;
property WorldFactorY: Single read GetWorldFactorY write FWorldFactorY;
// property getters
function GetDoc: TPdfDocument; {$ifdef HASINLINE}inline;{$endif}
function GetPage: TPdfPage; {$ifdef HASINLINE}inline;{$endif}
public
/// create the PDF canvas instance
constructor Create(APdfDoc: TPdfDocument);
/// pushes a copy of the entire graphics state onto the stack
procedure GSave; { q }
/// restores the entire graphics state to its former value by popping
// it from the stack
procedure GRestore; { Q }
/// Modify the CTM by concatenating the specified matrix
// - The current transformation matrix (CTM) maps positions from user
// coordinates to device coordinates
// - This matrix is modified by each application of the ConcatToCTM method
// - CTM Initial value is a matrix that transforms default user coordinates
// to device coordinates
// - since floating-point precision does make sense for a transformation
// matrix, we added a custom decimal number parameter here
procedure ConcatToCTM(a, b, c, d, e, f: Single; Decimals: Cardinal=6); { cm }
/// Set the flatness tolerance in the graphics state
// - see Section 6.5.1, "Flatness Tolerance" of the PDF 1.3 reference:
// The flatness tolerance controls the maximum permitted distance in
// device pixels between the mathematically correct path and an
// approximation constructed from straight line segments
// - Flatness is a number in the range 0 to 100; a value of 0 specifies
// the output device's default flatness tolerance
procedure SetFlat(flatness: Byte); { i }
/// Set the line cap style in the graphics state
// - The line cap style specifies the shape to be used at the
// ends of open subpaths (and dashes, if any) when they are stroked
procedure SetLineCap(linecap: TLineCapStyle); { J }
/// Set the line dash pattern in the graphics state
// - The line dash pattern controls the pattern of dashes and gaps
// used to stroke paths. It is specified by a dash array and a dash phase.
// The dash array's elements are numbers that specify the lengths of
// alternating dashes and gaps; the dash phase specifies the distance into
// the dash pattern at which to start the dash. The elements of both the
// dash array and the dash phase are expressed in user space units.
// Before beginning to stroke a path, the dash array is cycled through,
// adding up the lengths of dashes and gaps. When the accumulated length
// equals the value specified by the dash phase, stroking of the path begins,
// using the dash array cyclically from that point onward.
procedure SetDash(const aarray: array of integer; phase: integer=0); { d }
/// Set the line join style in the graphics state
// - The line join style specifies the shape to be used at the
// corners of paths that are stroked
procedure SetLineJoin(linejoin: TLineJoinStyle); { j }
/// Set the line width in the graphics state
// - The line width parameter specifies the thickness of the line used
// to stroke a path. It is a nonnegative number expressed in user space
// units; stroking a path entails painting all points whose perpendicular
// distance from the path in user space is less than or equal to half the
// line width. The effect produced in device space depends on the current
// transformation matrix (CTM) in effect at the time the path is stroked.
// If the CTM specifies scaling by different factors in the x and y
// dimensions, the thickness of stroked lines in device space will vary
// according to their orientation. The actual line width achieved can differ
// from the requested width by as much as 2 device pixels, depending on
// the positions of lines with respect to the pixel grid.
procedure SetLineWidth(linewidth: Single); { w }
/// Set the miter limit in the graphics state
// - When two line segments meet at a sharp angle and mitered joins have been
// specified as the line join style, it is possible for the miter to extend
// far beyond the thickness of the line stroking the path. The miter limit
// imposes a maximum on the ratio of the miter length to the line width.
// When the limit is exceeded, the join is converted from a miter to a bevel
procedure SetMiterLimit(miterlimit: Single); { M }
/// change the current coordinates position
// - Begin a new subpath by moving the current point to coordinates
// (x, y), omitting any connecting line segment. If the previous path
// construction operator in the current path was also MoveTo(), the new MoveTo()
// overrides it; no vestige of the previous MoveTo() call remains in the path.
procedure MoveTo(x, y: Single); { m }
/// Append a straight line segment from the current point to the point (x, y).
// - The new current point is (x, y)
procedure LineTo(x, y: Single); { l }
/// Append a cubic Bezier curve to the current path
// - The curve extends from the current point to the point (x3, y3),
// using (x1, y1) and (x2, y2) as the Bezier control points
// - The new current point is (x3, y3)
procedure CurveToC(x1, y1, x2, y2, x3, y3: Single); { c }
/// Append a cubic Bezier curve to the current path
// - The curve extends from the current point to the point (x3, y3),
// using the current point and (x2, y2) as the Bezier control points
// - The new current point is (x3, y3)
procedure CurveToV(x2, y2, x3, y3: Single); { v }
/// Append a cubic Bezier curve to the current path
// - The curve extends from the current point to the point (x3, y3),
// using (x1, y1) and (x3, y3) as the Bezier control points
// - The new current point is (x3, y3)
procedure CurveToY(x1, y1, x3, y3: Single); { y }
/// Append a rectangle to the current path as a complete subpath, with
// lower-left corner (x, y) and dimensions width and height in user space
procedure Rectangle(x, y, width, height: Single); { re }
/// Close the current subpath by appending a straight line segment
// from the current point to the starting point of the subpath
// - This operator terminates the current subpath; appending another
// segment to the current path will begin a new subpath, even if the new
// segment begins at the endpoint reached by the h operation
// - If the current subpath is already closed or the current path is empty,
// it does nothing
procedure Closepath; { h }
/// End the path object without filling or stroking it
// - This operator is a "path-painting no-op", used primarily for the
// side effect of changing the clipping path
procedure NewPath; { n }
/// Stroke the path
procedure Stroke; { S }
/// Close and stroke the path
// - This operator has the same effect as the sequence ClosePath; Stroke;
procedure ClosePathStroke; { s }
/// Fill the path, using the nonzero winding number rule to determine
// the region to fill
procedure Fill; { f }
/// Fill the path, using the even-odd rule to determine the region to fill
procedure EoFill; { f* }
/// Fill and then stroke the path, using the nonzero winding number rule
// to determine the region to fill
// - This produces the same result as constructing two identical path
// objects, painting the first with Fill and the second with Stroke. Note,
// however, that the filling and stroking portions of the operation consult
// different values of several graphics state parameters, such as the color
procedure FillStroke; { B }
/// Close, fill, and then stroke the path, using the nonzero winding number
// rule to determine the region to fill
// - This operator has the same effect as the sequence ClosePath; FillStroke;
procedure ClosepathFillStroke; { b }
/// Fill and then stroke the path, using the even-odd rule to determine
// the region to fill
// - This operator produces the same result as FillStroke, except that
// the path is filled as if with Eofill instead of Fill
procedure EofillStroke; { B* }
/// Close, fill, and then stroke the path, using the even-odd rule to
// determine the region to fill
// - This operator has the same effect as the sequence Closepath; EofillStroke;
procedure ClosepathEofillStroke; { b* }
/// Nonzero winding clipping path set
// - Modify the current clipping path by intersecting it with the current path,
// using the nonzero winding number rule to determine which regions
// lie inside the clipping path
// - The graphics state contains a clipping path that limits the regions of
// the page affected by painting operators. The closed subpaths of this path
// define the area that can be painted. Marks falling inside this area will
// be applied to the page; those falling outside it will not. (Precisely what
// is considered to be inside a path is discussed under "Filling", above.)
// - The initial clipping path includes the entire page. Both clipping path
// methods (Clip and EoClip) may appear after the last path construction operator
// and before the path-painting operator that terminates a path object.
// Although the clipping path operator appears before the painting operator,
// it does not alter the clipping path at the point where it appears. Rather,
// it modifies the effect of the succeeding painting operator. After the path
// has been painted, the clipping path in the graphics state is set to the
// intersection of the current clipping path and the newly constructed path.
procedure Clip; { W }
/// Even-Odd winding clipping path set
// - Modify the current clipping path by intersecting it with the current path,
// using the even-odd rule to determine which regions lie inside the clipping path
procedure EoClip; { W* }
/// Set the character spacing
// - CharSpace is a number expressed in unscaled text space units.
// - Character spacing is used by the ShowText and ShowTextNextLine methods
// - Default value is 0
procedure SetCharSpace(charSpace: Single); { Tc }
/// Set the word spacing
// - WordSpace is a number expressed in unscaled text space units
// - word spacing is used by the ShowText and ShowTextNextLine methods
// - Default value is 0
procedure SetWordSpace(wordSpace: Single); { Tw }
/// Set the horizontal scaling to (scale/100)
// - hScaling is a number specifying the percentage of the normal width
// - Default value is 100 (e.g. normal width)
procedure SetHorizontalScaling(hScaling: Single); { Tz }
/// Set the text leading, Tl, to the specified leading value
// - leading which is a number expressed in unscaled text space units;
// it specifies the vertical distance between the baselines of adjacent
// lines of text
// - Text leading is used only by the MoveToNextLine and ShowTextNextLine methods
// - you can force the next line to be just below the current one by calling:
// ! SetLeading(Attributes.FontSize);
// - Default value is 0
procedure SetLeading(leading: Single); { TL }
/// Set the font, Tf, to font and the font size, Tfs , to size.
// - font is the name of a font resource in the Font subdictionary of the
// current resource dictionary (e.g. 'F0')
// - size is a number representing a scale factor
// - There is no default value for either font or size; they must be specified
// using this method before any text is shown
procedure SetFontAndSize(const fontshortcut: PDFString; size: Single); { Tf }
{$ifdef HASINLINE}inline;{$endif}
/// Set the text rendering mode
// - the text rendering mode determines whether text is stroked, filled,
// or used as a clipping path
procedure SetTextRenderingMode(mode: TTextRenderingMode); { Tr }
/// Set the text rise, Trise, to the specified value
// - rise is a number expressed in unscaled text space units, which
// specifies the distance, in unscaled text space units, to move the
// baseline up or down from its default location. Positive values of
// text rise move the baseline up. Adjustments to the baseline are
// useful for drawing superscripts or subscripts. The default location of
// the baseline can be restored by setting the text rise to 0.
// - Default value is 0
procedure SetTextRise(rise: word); { Ts }
/// Begin a text object
// - Text objects cannot be nested
procedure BeginText; {$ifdef HASINLINE}inline;{$endif} { BT }
/// End a text object, discarding the text matrix
procedure EndText; {$ifdef HASINLINE}inline;{$endif} { ET }
/// Move to the start of the next line, offset from the start of the current
// line by (tx ,ty)
// - tx and ty are numbers expressed in unscaled text space units
procedure MoveTextPoint(tx, ty: Single); {$ifdef HASINLINE}inline;{$endif} { Td }
/// set the Text Matrix to a,b,c,d and the text line Matrix x,y
procedure SetTextMatrix(a, b, c, d, x, y: Single); { Tm }
/// Move to the start of the next line
procedure MoveToNextLine; { T* }
{$ifdef HASVARUSTRING}
/// Show a text string
// - text is expected to be Unicode encoded
// - if NextLine is TRUE, moves to the next line and show a text string;
// in this case, method as the same effect as MoveToNextLine; ShowText(s);
procedure ShowText(const text: UnicodeString; NextLine: boolean=false); overload; inline; { Tj or ' }
{$endif}
/// Show a text string
// - text is expected to be Ansi-Encoded, in the current CharSet; if
// some Unicode or MBCS conversion is necessary, it will be notified to the
// corresponding
// - if NextLine is TRUE, moves to the next line and show a text string;
// in this case, method as the same effect as MoveToNextLine; ShowText(s);
procedure ShowText(const text: PDFString; NextLine: boolean=false); overload; { Tj or ' }
/// Show an Unicode Text string
// - if NextLine is TRUE, moves to the next line and show a text string;
// in this case, method as the same effect as MoveToNextLine; ShowText(s);
procedure ShowText(PW: PWideChar; NextLine: boolean=false); overload;
{$ifdef HASINLINE}inline;{$endif}
/// Show an Unicode Text string, encoded as Glyphs or the current font
// - PW must follow the ETO_GLYPH_INDEX layout, i.e. refers to an array as
// returned from the GetCharacterPlacement: all glyph indexes are 16-bit values
procedure ShowGlyph(PW: PWord; Count: integer); {$ifdef HASINLINE}inline;{$endif}
/// Paint the specified XObject
procedure ExecuteXObject(const xObject: PDFString); { Do }
/// Set the color space to a Device-dependent RGB value
// - this method set the color to use for nonstroking operations
procedure SetRGBFillColor(Value: TPdfColor); { rg }
/// Set the color space to a Device-dependent RGB value
// - this method set the color to use for stroking operations
procedure SetRGBStrokeColor(Value: TPdfColor); { RG }
/// Set the color space to a CMYK percent value
// - this method set the color to use for nonstroking operations
procedure SetCMYKFillColor(C, M, Y, K: integer); { k }
/// Set the color space to a CMYK value
// - this method set the color to use for stroking operations
procedure SetCMYKStrokeColor(C, M, Y, K: integer); { K }
/// assign the canvas to the specified page
procedure SetPage(APage: TPdfPage); virtual;
/// set the current font for the PDF Canvas
procedure SetPDFFont(AFont: TPdfFont; ASize: Single);
/// set the current font for the PDF Canvas
// - expect the font name to be either a standard embedded font
// ('Helvetica','Courier','Times') or its Windows equivalency (i.e.
// 'Arial','Courier New','Times New Roman'), either a UTF-8 encoded
// True Type font name available on the system
// - if no CharSet is specified (i.e. if it remains -1), the current document
// CharSet parameter is used
function SetFont(const AName: RawUTF8; ASize: Single; AStyle: TPdfFontStyles;
ACharSet: integer=-1; AForceTTF: integer=-1; AIsFixedWidth: boolean=false): TPdfFont; overload;
/// set the current font for the PDF Canvas
// - this method use the Win32 structure that defines the characteristics
// of the logical font
function SetFont(ADC: HDC; const ALogFont: TLogFontW; ASize: single): TPdfFont; overload;
/// show some text at a specified page position
procedure TextOut(X, Y: Single; const Text: PDFString);
/// show some unicode text at a specified page position
procedure TextOutW(X, Y: Single; PW: PWideChar);
/// show the text in the specified rectangle and alignment
// - optional clipping can be applied
procedure TextRect(ARect: TPdfRect; const Text: PDFString;
Alignment: TPdfAlignment; Clipping: boolean);
/// show the text in the specified rectangle and alignment
// - text can be multiline, separated by CR + LF (i.e. #13#10)
// - text can optionaly word wrap
// - note: this method only work with embedded fonts by now, not true type
// fonts (because it use text width measuring)
procedure MultilineTextRect(ARect: TPdfRect;
const Text: PDFString; WordWrap: boolean);
/// draw the specified object (typicaly an image) with stretching
procedure DrawXObject(X, Y, AWidth, AHeight: Single;
const AXObjectName: PDFString);
/// draw the specified object (typicaly an image) with stretching and clipping
procedure DrawXObjectEx(X, Y, AWidth, AHeight: Single;
ClipX, ClipY, ClipWidth, ClipHeight: Single; const AXObjectName: PDFString);
/// draw an ellipse
// - use Bezier curves internaly to draw the ellipse
procedure Ellipse(x, y, width, height: Single);
/// draw a rounded rectangle
// - use Bezier curves internaly to draw the rounded rectangle
procedure RoundRect(x1,y1,x2,y2,cx,cy: Single);
/// calculate width of specified text according to current Canvas attributes
// - works with MBCS strings
function TextWidth(const Text: PDFString): Single;
/// calculate width of specified text according to current Canvas attributes
// - this function compute the raw width of the specified text, and won't
// use HorizontalScaling, CharSpace nor WordSpace in its calculation
function UnicodeTextWidth(PW: PWideChar): Single;
/// calculate the number of chars which can be displayed in the specified
// width, according to current attributes
// - this function is compatible with MBCS strings, and returns
// the index in Text, not the glyphs index
// - note: this method only work with embedded fonts by now, not true type
// fonts (because text width measuring is not yet implemented for them)
function MeasureText(const Text: PDFString; AWidth: Single): integer;
/// get the index of the next word in the supplied text
// - this function is compatible with MBCS strings, and returns
// the index in Text, not the glyphs index
function GetNextWord(const S: PDFString; var Index: integer): PDFString;
{$ifdef USE_METAFILE}
/// draw a metafile content into the PDF page
// - not 100% of content is handled yet, but most common are (even
// metafiles embedded inside metafiles)
// - UseSetTextJustification is to be set to true to ensure better rendering
// if the EMF content used SetTextJustification() API call to justify text
// - KerningHScaleBottom/KerningHScaleTop are limits below which and over
// which Font Kerning is transformed into PDF Horizontal Scaling commands
// - TextClipping can be set to fix some issues e.g. when using Wine
procedure RenderMetaFile(MF: TMetaFile; ScaleX: Single=1.0; ScaleY: Single=0.0;
XOff: single=0.0; YOff: single=0.0;
TextPositioning: TPdfCanvasRenderMetaFileTextPositioning=tpSetTextJustification;
KerningHScaleBottom: single=99.0; KerningHScaleTop: single=101.0;
TextClipping: TPdfCanvasRenderMetaFileTextClipping=tcAlwaysClip);
{$endif USE_METAFILE}
// starts optional content (layer)
// - Group must be registered with TPdfDocument.CreateOptionalContentGroup
// - each BeginMarkedContent must have a corresponding EndMarkedContent
// - nested BeginMarkedContent/EndMarkedContent are possible
procedure BeginMarkedContent(Group: TPdfOptionalContentGroup);
// ends optional content (layer)
procedure EndMarkedContent;
public
/// retrieve the current Canvas content stream, i.e. where the PDF
// commands are to be written to
property Contents: TPdfStream read FContents;
/// retrieve the current Canvas Page
property Page: TPdfPage read GetPage;
/// retrieve the associated PDF document instance which created this Canvas
property Doc: TPdfDocument read GetDoc;
{$ifdef USE_UNISCRIBE}
/// if Uniscribe-related methods must handle the text from right to left
property RightToLeftText: Boolean read fRightToLeftText write fRightToLeftText;
{$endif}
end;
/// common ancestor to all dictionary wrapper classes
TPdfDictionaryWrapper = class(TPersistent)
private
FData: TPdfDictionary;
function GetHasData: boolean;
protected
procedure SetData(AData: TPdfDictionary);
public
/// the associated dictionary, containing all data
property Data: TPdfDictionary read FData write SetData;
/// return TRUE if has any data stored within
property HasData: boolean read GetHasData;
end;
/// defines the data stored inside a EMR_GDICOMMENT message
// - pgcOutline can be used to add an outline at the current position (i.e.
// the last Y parameter of a Move): the text is the associated title, UTF-8 encoded
// and the outline tree is created from the number of leading spaces in the title
// - pgcBookmark will create a destination at the current position (i.e.
// the last Y parameter of a Move), with some text supplied as bookmark name
// - pgcLink/pgcLinkNoBorder will create a asLink annotation, expecting the data
// to be filled with TRect inclusive-inclusive bounding rectangle coordinates,
// followed by the corresponding bookmark name
// - use the GDIComment*() functions to append the corresponding
// EMR_GDICOMMENT message to a metafile content
TPdfGDIComment =
(pgcOutline, pgcBookmark, pgcLink, pgcLinkNoBorder, pgcJpegDirect);
/// a dictionary wrapper class for the PDF document information fields
// - all values use the generic VCL string type, and will be encoded
// as Unicode if necessary
TPdfInfo = class(TPdfDictionaryWrapper)
private
function GetAuthor: string;
procedure SetAuthor(const Value: string);
function GetCreationDate: TDateTime;
procedure SetCreationDate(Value: TDateTime);
function GetCreator: string;
procedure SetCreator(const Value: string);
function GetKeywords: string;
procedure SetKeywords(const Value: string);
function GetSubject: string;
procedure SetSubject(const Value: string);
function GetTitle: string;
procedure SetTitle(const Value: string);
function GetModDate: TDateTime;
procedure SetModDate(Value: TDateTime);
public
/// the PDF document Author
property Author: string read GetAuthor write SetAuthor;
/// the PDF document Creation Date
property CreationDate: TDateTime read GetCreationDate write SetCreationDate;
/// the Software or Library name which created this PDF document
property Creator: string read GetCreator write SetCreator;
/// the PDF document associated key words
property Keywords: string read GetKeywords write SetKeywords;
/// the PDF document modification date
property ModDate: TDateTime read GetModDate write SetModDate;
/// the PDF document subject
property Subject: string read GetSubject write SetSubject;
/// the PDF document title
property Title: string read GetTitle write SetTitle;
end;
/// a dictionary wrapper class for the PDF document catalog fields
// - It contains references to other objects defining the document's contents,
// outline, article threads (PDF 1.1), named destinations, and other attributes.
// In addition, it contains information about how the document should be displayed
// on the screen, such as whether its outline and thumbnail page images should be
// displayed automatically and whether some location other than the first page
// should be shown when the document is opened
TPdfCatalog = class(TPdfDictionaryWrapper)
private
FOpenAction: TPdfDestination;
FOwner: TPdfDocument;
procedure SetPageLayout(Value: TPdfPageLayout);
procedure SetPageMode(Value: TPdfPageMode);
procedure SetNonFullScreenPageMode(Value: TPdfPageMode);
procedure SetViewerPreference(Value: TPdfViewerPreferences);
procedure SetPages(APages: TPdfDictionary);
function GetPageLayout: TPdfPageLayout;
function GetPageMode: TPdfPageMode;
function GetNonFullScreenPageMode: TPdfPageMode;
function GetViewerPreference: TPdfViewerPreferences;
function GetPages: TPdfDictionary;
protected
procedure SaveOpenAction;
public
/// a Destination to be displayed when the document is opened
property OpenAction: TPdfDestination read FOpenAction write FOpenAction;
/// The page layout to be used when the document is opened
property PageLayout: TPdfPageLayout read GetPageLayout write SetPageLayout;
/// Page mode determines how the document should appear when opened
property NonFullScreenPageMode: TPdfPageMode read GetNonFullScreenPageMode write SetNonFullScreenPageMode;
/// Page mode determines how the document should appear when opened
property PageMode: TPdfPageMode read GetPageMode write SetPageMode;
/// A viewer preferences dictionary specifying the way the document is to be
// displayed on the screen
// - If this entry is absent, viewer applications should use their own current
// user preference settings
property ViewerPreference: TPdfViewerPreferences read GetViewerPreference write SetViewerPreference;
/// The page tree node that is the root of the document's page tree
// - Required, must be an indirect reference
// - you can set a value to it in order to add some nested pages
property Pages: TPdfDictionary read GetPages write SetPages;
end;
/// a generic PDF font object
TPdfFont = class(TPdfDictionaryWrapper)
protected
fName: PDFString;
fShortCut: PDFString;
fFirstChar, fLastChar: integer;
fDefaultWidth: word;
fAscent, fDescent: integer;
fUnicode: boolean;
/// index in TrueTypeFontsIndex[] + 1, 0 if not a TPdfFontTrueType
// - same TPdfFontTrueType index may appear multiple times in the font list,
// e.g. with normal, bold and/or italic attributes
// - this hidden property is used by TPdfDocument for faster font list handling
fTrueTypeFontsIndex: integer;
/// contains a bit for every WinAnsi encoded char
// - encoding in TPdfFont, even if used by TPdfFontWinAnsi descendent only
fWinAnsiUsed: set of AnsiChar;
public
/// create the PDF font object instance
constructor Create(AXref: TPdfXref; const AName: PDFString);
/// mark some WinAnsi char as used
procedure AddUsedWinAnsiChar(aChar: AnsiChar); {$ifdef HASINLINE}inline;{$endif}
/// retrieve the width of a specified character
// - implementation of this method is either WinAnsi (by TPdfFontWinAnsi),
// either compatible with MBCS strings (TPdfFontCIDFontType2)
// - return 0 by default (descendant must handle the Ansi charset)
function GetAnsiCharWidth(const AText: PDFString; APos: integer): integer; virtual;
/// the internal PDF font name (e.g. 'Helvetica-Bold')
// - postscript font names are inside the unit: these postscript names
// could not match the "official" True Type font name, stored as
// UTF-8 in FTrueTypeFonts
property Name: PDFString read FName;
/// the internal PDF shortcut (e.g. 'F3')
property ShortCut: PDFString read FShortCut;
/// is set to TRUE if the font is dedicated to Unicode Chars
property Unicode: boolean read fUnicode;
end;
PPdfWinAnsiWidth = ^TPdfWinAnsiWidth;
TPdfWinAnsiWidth = array[#32..#255] of word;
/// a generic PDF font object, handling at least WinAnsi encoding
// - TPdfFontTrueType descendent will handle also Unicode chars,
// for all WideChar which are outside the WinAnsi selection
TPdfFontWinAnsi = class(TPdfFont)
protected
/// contain the Width array of the corresponding WinAnsi encoded char
fWinAnsiWidth: PPdfWinAnsiWidth;
public
/// retrieve the width of a specified character
// - implementation of this method expect WinAnsi encoding
// - return the value contained in fWinAnsiWidth[] by default
function GetAnsiCharWidth(const AText: PDFString; APos: integer): integer; override;
/// release the used memory
destructor Destroy; override;
end;
/// an embedded WinAnsi-Encoded standard Type 1 font
// - handle Helvetica, Courier and Times font by now
TPdfFontType1 = class(TPdfFontWinAnsi)
protected
public
/// create a standard font instance, with a given name and char widths
// - if WidthArray is nil, it will create a fixed-width font of 600 units
// - WidthArray[0]=Ascent, WidthArray[1]=Descent, WidthArray[2..]=Width(#32..)
constructor Create(AXref: TPdfXref; const AName: PDFString;
WidthArray: PSmallIntArray); reintroduce;
end;
/// an embedded Composite CIDFontType2
// - i.e. a CIDFont whose glyph descriptions are based on TrueType font technology
// - typicaly handle Japan or Chinese standard fonts
// - used with MBCS encoding, not WinAnsi
TPdfFontCIDFontType2 = class(TPdfFont)
{ TODO: implement standard TPdfFontCIDFontType2 MBCS font }
end;
/// handle Unicode glyph description for a True Type Font
// - cf http://www.microsoft.com/typography/OTSPEC/otff.htm#otttables
// - handle Microsoft cmap format 4 encoding (i.e. most used
// true type fonts on Windows)
TPdfTTF = class
protected
// we use TWordDynArray for auto garbage collection and generic handling
// - since the TTF file is big endian, we swap all words at loading, to
// be used directly by the Intel x86 code; integer (longint) values
// must take care of this byte swapping
fcmap,
fhead,
fhhea,
fhmtx: TWordDynArray;
public
// these are pointers to the useful data of the True Type Font:
/// Font header
head: ^TCmapHEAD;
/// Horizontal header
hhea: ^TCmapHHEA;
/// Character to glyph mapping (cmap) table, in format 4
fmt4: ^TCmapFmt4;
/// Start character code for each cmap format 4 segment
startCode: PWordArray;
/// End characterCode for each cmap format 4 segment
endCode: PWordArray;
/// Delta for all character codes in each cmap format 4 segment
idDelta: PSmallIntArray;
/// Offsets into glyphIndexArray or 0
idRangeOffset: PWordArray;
/// Glyph index array (arbitrary length)
glyphIndexArray: PWordArray;
public
/// create Unicode glyph description for a supplied True Type Font
// - the HDC of its corresponding document must have selected the font first
// - this constructor will fill fUsedWide[] and fUsedWideChar of aUnicodeTTF
// with every available unicode value, and its corresponding glyph and width
constructor Create(aUnicodeTTF: TPdfFontTrueType); reintroduce;
end;
/// this dynamic array stores details about used unicode characters
// - every used unicode character has its own width and glyph index in the
// true type font content
TUsedWide = array of packed record
case byte of
0: (
Width: word;
Glyph: word; );
1: (
Int: integer; );
end;
/// handle TrueType Font
// - handle both WinAnsi text and Unicode characters in two separate
// TPdfFontTrueType instances (since PDF need two separate fonts with
// diverse encoding)
TPdfFontTrueType = class(TPdfFontWinAnsi)
private
function GetWideCharUsed: Boolean; {$ifdef HASINLINE}inline;{$endif}
protected
fStyle: TPdfFontStyles;
fDoc: TPdfDocument;
// note: fUsedWide[] and fUsedWideChar are used:
// - in WinAnsi Fonts for glyphs used by ShowText
// - in Unicode Fonts for all available glyphs from TPdfTTF values
fUsedWideChar: TSortedWordArray;
fUsedWide: TUsedWide;
fHGDI: HGDIOBJ;
fFixedWidth: boolean;
fFontDescriptor: TPdfDictionary;
fFontFile2: TPdfStream;
fUnicodeFont: TPdfFontTrueType;
fWinAnsiFont: TPdfFontTrueType;
fIsSymbolFont: Boolean;
// below are some bigger structures
fLogFont: TLogFontW;
fM: TTextMetric;
fOTM: TOutlineTextmetric;
procedure CreateAssociatedUnicodeFont;
// update font description from used chars
procedure PrepareForSaving;
// low level adding of a glyph (returns the real glyph index found, 0 if none)
function GetAndMarkGlyphAsUsed(aGlyph: word): word;
public
/// create the TrueType font object instance
constructor Create(ADoc: TPdfDocument; AFontIndex: integer;
AStyle: TPdfFontStyles; const ALogFont: TLogFontW; AWinAnsiFont: TPdfFontTrueType); reintroduce;
/// release the associated memory and handles
destructor Destroy; override;
/// mark some UTF-16 codepoint as used
// - return the index in fUsedWideChar[] and fUsedWide[]
// - this index is the one just added, or the existing one if the value
// was found to be already in the fUserWideChar[] array
function FindOrAddUsedWideChar(aWideChar: WideChar): integer;
/// retrieve the width of an UTF-16 codepoint
// - WinAnsi characters are taken from fWinAnsiWidth[], unicode chars from
// fUsedWide[].Width
function GetWideCharWidth(aWideChar: WideChar): Integer;
/// is set to TRUE if the PDF used any true type encoding
property WideCharUsed: Boolean read GetWideCharUsed;
/// the associated Font Styles
property Style: TPdfFontStyles read fStyle;
/// is set to TRUE if the font has a fixed width
property FixedWidth: boolean read fFixedWidth;
/// points to the corresponding Unicode font
// - returns NIL if the Unicode font has not yet been created by the
// CreateUnicodeFont method
// - may return SELF if the font is itself the Unicode version
property UnicodeFont: TPdfFontTrueType read fUnicodeFont;
/// points to the corresponding WinAnsi font
// - always return a value, whatever it is self
property WinAnsiFont: TPdfFontTrueType read fWinAnsiFont;
end;
/// A destination defines a particular view of a document, consisting of the following:
// - The page of the document to be displayed
// - The location of the display window on that page
// - The zoom factor to use when displaying the page
TPdfDestination = class(TObject)
private
FDoc: TPdfDocument;
FPage: TPdfPage;
FType: TPdfDestinationType;
FValues: array[0..3] of Integer;
FZoom: Single;
FReference: TObject;
procedure SetElement(Index: integer; Value: Integer);
procedure SetZoom(Value: Single);
function GetElement(Index: integer): Integer;
function GetPageWidth: Integer;
function GetPageHeight: Integer;
public
/// create the PDF destination object
// - the current document page is associated with this destination
constructor Create(APdfDoc: TPdfDocument);
/// release the object
destructor Destroy; override;
/// retrieve the array containing the location of the display window
// - the properties values which are not used are ignored
function GetValue: TPdfArray;
/// Destination Type determines default user space coordinate system of
// Explicit destinations
property DestinationType: TPdfDestinationType read FType write FType;
/// the associated PDF document which created this Destination object
property Doc: TPdfDocument read FDoc;
/// the associated Page
property Page: TPdfPage read FPage;
/// retrieve the left coordinate of the location of the display window
property Left: Integer index 0 read GetElement write SetElement;
/// retrieve the top coordinate of the location of the display window
property Top: Integer index 1 read GetElement write SetElement;
/// retrieve the righ tcoordinate of the location of the display window
property Right: Integer index 2 read GetElement write SetElement;
/// retrieve the bottom coordinate of the location of the display window
property Bottom: Integer index 3 read GetElement write SetElement;
/// the page height of the current page
// - return the corresponding MediaBox value
property PageHeight: Integer read GetPageHeight;
/// the page width of the current page
// - return the corresponding MediaBox value
property PageWidth: Integer read GetPageWidth;
/// the associated Zoom factor
// - by default, the Zoom factor is 1
property Zoom: Single read FZoom write SetZoom;
/// an object associated to this destination, to be used for conveniance
property Reference: TObject read FReference write FReference;
end;
/// an Outline entry in the PDF document
TPdfOutlineEntry = class(TPdfDictionaryWrapper)
private
FParent: TPdfOutlineEntry;
FNext: TPdfOutlineEntry;
FPrev: TPdfOutlineEntry;
FFirst: TPdfOutlineEntry;
FLast: TPdfOutlineEntry;
FDest: TPdfDestination;
FDoc: TPdfDocument;
FTitle: string;
FOpened: boolean;
FCount: integer;
FReference: TObject;
FLevel: integer;
protected
procedure Save; virtual;
public
/// create the Outline entry instance
// - if TopPosition is set, a corresponding destination is created
// on the current PDF Canvas page, at this Y position
constructor Create(AParent: TPdfOutlineEntry;
TopPosition: integer=-1); reintroduce;
/// release the associated memory and reference object
destructor Destroy; override;
/// create a new entry in the outline tree
// - this is the main method to create a new entry
function AddChild(TopPosition: integer=-1): TPdfOutlineEntry;
/// the associated PDF document which created this Destination object
property Doc: TPdfDocument read FDoc;
/// the parent outline entry of this entry
property Parent: TPdfOutlineEntry read FParent;
/// the next outline entry of this entry
property Next: TPdfOutlineEntry read FNext;
/// the previous outline entry of this entry
property Prev: TPdfOutlineEntry read FPrev;
/// the first outline entry of this entry list
property First: TPdfOutlineEntry read FFirst;
/// the last outline entry of this entry list
property Last: TPdfOutlineEntry read FLast;
/// the associated destination
property Dest: TPdfDestination read FDest write FDest;
/// the associated title
// - is a generic VCL string, so is Unicode ready
property Title: string read FTitle write FTitle;
/// if the outline must be opened
property Opened: boolean read FOpened write FOpened;
/// an object associated to this destination, to be used for conveniance
property Reference: TObject read FReference write FReference;
/// an internal property (not exported to PDF content)
property Level: integer read FLevel write FLevel;
end;
/// Root entry for all Outlines of the PDF document
// - this is a "fake" entry which must be used as parent for all true
// TPdfOutlineEntry instances, but must not be used as a true outline entry
TPdfOutlineRoot = class(TPdfOutlineEntry)
public
/// create the Root entry for all Outlines of the PDF document
constructor Create(ADoc: TPdfDocument); reintroduce;
/// update internal parameters (like outline entries count) before saving
procedure Save; override;
end;
{$ifdef USE_METAFILE}
/// a PDF page, with its corresponding Meta File and Canvas
TPdfPageGDI = class(TPdfPage)
private
// don't use these fVCL* properties directly, but via TPdfDocumentGdi.VCLCanvas
fVCLMetaFileCompressed: RawByteString;
fVCLCanvasSize: TSize;
// it is in fact a TMetaFileCanvas instance from fVCLCurrentMetaFile
fVCLCurrentCanvas: TCanvas;
fVCLCurrentMetaFile: TMetaFile;
// allow to create the meta file and its canvas only if necessary, and
// compress the page content using SynLZ to reduce memory usage
procedure CreateVCLCanvas;
procedure SetVCLCurrentMetaFile;
procedure FlushVCLCanvas;
public
/// release associated memory
destructor Destroy; override;
end;
/// class handling PDF document creation using GDI commands
// - this class allows using a VCL standard Canvas class
// - handles also PDF creation directly from TMetaFile content
TPdfDocumentGDI = class(TPdfDocument)
private
fUseMetaFileTextPositioning: TPdfCanvasRenderMetaFileTextPositioning;
fUseMetaFileTextClipping: TPdfCanvasRenderMetaFileTextClipping;
fKerningHScaleTop: Single;
fKerningHScaleBottom: Single;
function GetVCLCanvas: TCanvas; {$ifdef HASINLINE}inline;{$endif}
function GetVCLCanvasSize: TSize; {$ifdef HASINLINE}inline;{$endif}
public
/// create the PDF document instance, with a VCL Canvas property
// - see TPdfDocument.Create connstructor for the arguments expectations
constructor Create(AUseOutlines: Boolean=false; ACodePage: integer=0;
{$ifdef USE_PDFALEVEL]}APDFA: TPdfALevel=pdfaNone{$else}APDFA1: boolean=false{$endif}
{$ifdef USE_PDFSECURITY}; AEncryption: TPdfEncryption=nil{$endif});
/// add a Page to the current PDF document
function AddPage: TPdfPage; override;
/// save the PDF file content into a specified Stream
// - this overridden method draw first the all VCLCanvas content into the PDF
procedure SaveToStream(AStream: TStream; ForceModDate: TDateTime=0); override;
/// save the current page content to the PDF file
// - this overridden method flush the content from the VCLCanvas into the PDF
// - it will reduce the used memory as much as possible, by-passing page
// content compression
// - typical use may be:
// ! with TPdfDocumentGDI.Create do
// ! try
// ! Stream := TFileStream.Create(FileName, fmCreate);
// ! try
// ! SaveToStreamDirectBegin(Stream);
// ! for i := 1 to 9 do
// ! begin
// ! AddPage;
// ! with VCLCanvas do
// ! begin
// ! Font.Name := 'Times new roman';
// ! Font.Size := 150;
// ! Font.Style := [fsBold, fsItalic];
// ! Font.Color := clNavy;
// ! TextOut(100, 100, 'Page ' + IntToStr(i));
// ! end;
// ! SaveToStreamDirectPageFlush; // direct writing
// ! end;
// ! SaveToStreamDirectEnd;
// ! finally
// ! Stream.Free;
// ! end;
// ! finally
// ! Free;
// ! end;
procedure SaveToStreamDirectPageFlush(FlushCurrentPageNow: boolean=false); override;
/// the VCL Canvas of the current page
property VCLCanvas: TCanvas read GetVCLCanvas;
/// the VCL Canvas size of the current page
// - useful to calculate coordinates for the current page
// - filled with (0,0) before first call to VCLCanvas property
property VCLCanvasSize: TSize read GetVCLCanvasSize;
/// defines how TMetaFile text positioning is rendered
// - default is tpSetTextJustification
// - tpSetTextJustification if content used SetTextJustification() API calls
// - tpExactTextCharacterPositining for exact font kerning, but resulting
// in bigger pdf size
// - tpKerningFromAveragePosition will compute average pdf Horizontal Scaling
// in association with KerningHScaleBottom/KerningHScaleTop properties
// - replace deprecated property UseSetTextJustification
property UseMetaFileTextPositioning: TPdfCanvasRenderMetaFileTextPositioning
read fUseMetaFileTextPositioning write fUseMetaFileTextPositioning;
/// defines how TMetaFile text clipping should be applied
// - tcNeverClip has been reported to work better e.g. when app is running
// on Wine
property UseMetaFileTextClipping: TPdfCanvasRenderMetaFileTextClipping
read fUseMetaFileTextClipping write fUseMetaFileTextClipping;
/// the % limit below which Font Kerning is transformed into PDF Horizontal
// Scaling commands (when text positioning is tpKerningFromAveragePosition)
// - set to 99.0 by default
property KerningHScaleBottom: Single read fKerningHScaleBottom write fKerningHScaleBottom;
/// the % limit over which Font Kerning is transformed into PDF Horizontal
// Scaling commands (when text positioning is tpKerningFromAveragePosition)
// - set to 101.0 by default
property KerningHScaleTop: Single read fKerningHScaleTop write fKerningHScaleTop;
end;
{$endif USE_METAFILE}
{$ifdef USE_BITMAP}
/// generic image object
// - is either bitmap encoded or jpeg encoded
TPdfImage = class(TPdfXObject)
private
fPixelHeight: Integer;
fPixelWidth: Integer;
fHash: THash128Rec;
public
/// create the image from a supplied VCL TGraphic instance
// - handle TBitmap and SynGdiPlus picture types, i.e. TJpegImage
// (stored as jpeg), and TGifImage/TPngImage (stored as bitmap)
// - use TPdfForm to handle TMetafile in vectorial format
// - an optional DontAddToFXref is available, if you don't want to add
// this object to the main XRef list of the PDF file
constructor Create(aDoc: TPdfDocument; aImage: TGraphic; DontAddToFXref: boolean); reintroduce;
/// create an image from a supplied JPEG file name
// - will raise an EFOpenError exception if the file doesn't exist
// - an optional DontAddToFXref is available, if you don't want to add
// this object to the main XRef list of the PDF file
constructor CreateJpegDirect(aDoc: TPdfDocument; const aJpegFileName: TFileName;
DontAddToFXref: boolean=true); reintroduce; overload;
/// create an image from a supplied JPEG content
// - an optional DontAddToFXref is available, if you don't want to add
// this object to the main XRef list of the PDF file
constructor CreateJpegDirect(aDoc: TPdfDocument; aJpegFile: TMemoryStream;
DontAddToFXref: boolean=true); reintroduce; overload;
/// width of the image, in pixels units
property PixelWidth: Integer read fPixelWidth;
/// height of the image, in pixels units
property PixelHeight: Integer read fPixelHeight;
end;
{$endif USE_BITMAP}
{$ifdef USE_METAFILE}
/// handle any form XObject
// - A form XObject (see Section 4.9, of PDF reference 1.3) is a self-contained
// description of an arbitrary sequence of graphics objects, defined as a
// PDF content stream
TPdfForm = class(TPdfXObject)
private
FFontList: TPdfDictionary;
public
/// create a form XObject from a supplied TMetaFile
constructor Create(aDoc: TPdfDocumentGDI; aMetaFile: TMetafile); reintroduce;
end;
{$endif USE_METAFILE}
/// a form XObject with a Canvas for drawing
// - once created, you can create this XObject, then draw it anywhere on
// any page - see sample
TPdfFormWithCanvas = class(TPdfXObject)
private
FFontList: TPdfDictionary;
FPage: TPdfPage;
FCanvas: TPdfCanvas;
public
/// create a form XObject with TPDFCanvas
constructor Create(aDoc: TPdfDocument; W, H: Integer); reintroduce;
/// release used memory
destructor Destroy; override;
/// close the internal canvas
procedure CloseCanvas;
/// access to the private canvas associated with the PDF form XObject
property Canvas: TPdfCanvas read FCanvas;
end;
/// used to handle compressed object stream (in PDF 1.5 format)
TPdfObjectStream = class(TPdfXObject)
protected
fObjectCount: integer;
fAddingStream: TPdfWrite;
fObject: array of record
Number: integer;
Position: integer;
end;
procedure InternalWriteTo(W: TPdfWrite); override;
public
/// create the instance, i.e. its associated stream
constructor Create(aDoc: TPdfDocument); reintroduce;
/// release internal memory structures
destructor Destroy; override;
/// add an object to this compressed object stream
// - returns the object index in this object stream
function AddObject(Value: TPdfObject): integer;
/// the number of compressed objects within this object stream
property ObjectCount: integer read fObjectCount;
end;
/// this function returns TRUE if the supplied text contain any MBCS character
// - typical call must check first if MBCS is currently enabled
// ! if SysLocale.FarEast and _HasMultiByteString(pointer(Text)) then ...
function _HasMultiByteString(Value: PAnsiChar): boolean;
/// convert a specified UTF-8 content into a PDFString value
function RawUTF8ToPDFString(const Value: RawUTF8): PDFString;
/// convert an unsigned integer into a PDFString text
function UInt32ToPDFString(Value: Cardinal): PDFString;
/// convert a date, into PDF string format, i.e. as 'D:20100414113241Z'
function _DateTimeToPdfDate(ADate: TDateTime): TPdfDate;
/// decode PDF date, encoded as 'D:20100414113241'
function _PdfDateToDateTime(const AText: TPdfDate): TDateTime;
/// wrapper to create a temporary PDF coordinates rectangle
function PdfRect(Left, Top, Right, Bottom: Single): TPdfRect; overload; {$ifdef HASINLINE}inline;{$endif}
/// wrapper to create a temporary PDF coordinates rectangle
function PdfRect(const Box: TPdfBox): TPdfRect; overload; {$ifdef HASINLINE}inline;{$endif}
/// wrapper to create a temporary PDF box
function PdfBox(Left, Top, Width, Height: Single): TPdfBox; {$ifdef HASINLINE}inline;{$endif}
/// reverse char orders for every hebrew and arabic words
// - just reverse all the UTF-16 codepoints in the supplied buffer
procedure L2R(W: PWideChar; L: integer);
/// convert some milli meters dimension to internal PDF twips value
function PdfCoord(MM: single): integer;
{$ifdef HASINLINE}inline;{$endif}
/// retrieve the paper size used by the current selected printer
function CurrentPrinterPaperSize: TPDFPaperSize;
/// retrieve the current printer resolution
function CurrentPrinterRes: TPoint;
/// append a EMR_GDICOMMENT message for handling PDF bookmarks
// - will create a PDF destination at the current position (i.e. the last Y
// parameter of a Move), with some text supplied as bookmark name
procedure GDICommentBookmark(MetaHandle: HDC; const aBookmarkName: RawUTF8);
/// append a EMR_GDICOMMENT message for handling PDF outline
// - used to add an outline at the current position (i.e. the last Y parameter of
// a Move): the text is the associated title, UTF-8 encoded and the outline tree
// is created from the specified numerical level (0=root)
procedure GDICommentOutline(MetaHandle: HDC; const aTitle: RawUTF8; aLevel: Integer);
/// append a EMR_GDICOMMENT message for creating a Link into a specified bookmark
procedure GDICommentLink(MetaHandle: HDC; const aBookmarkName: RawUTF8; const aRect: TRect;
NoBorder: boolean);
/// append a EMR_GDICOMMENT message for adding jpeg direct
procedure GDICommentJpegDirect(MetaHandle: HDC; const aFileName: RawUTF8; const aRect: TRect);
{$ifdef USE_PDFSECURITY}
const
/// allow all actions for a pdf encrypted file
// - to be used as parameter for TPdfEncryption.New() class method
PDF_PERMISSION_ALL: TPdfEncryptionPermissions =
[Low(TPdfEncryptionPermission)..high(TPdfEncryptionPermission)];
/// disable modification and annotation of a pdf encrypted file
// - to be used as parameter for TPdfEncryption.New() class method
PDF_PERMISSION_NOMODIF: TPdfEncryptionPermissions = [epPrinting,
epContentCopy, epPrintingHighResolution, epFillingForms,
epContentExtraction, epDocumentAssembly];
/// disable printing for a pdf encrypted file
// - to be used as parameter for TPdfEncryption.New() class method
PDF_PERSMISSION_NOPRINT: TPdfEncryptionPermissions = [epGeneralEditing,
epContentCopy, epAuthoringComment, epContentExtraction, epDocumentAssembly];
/// disable content extraction or copy for a pdf encrypted file
// - to be used as parameter for TPdfEncryption.New() class method
PDF_PERMISSION_NOCOPY: TPdfEncryptionPermissions = [epPrinting,
epAuthoringComment, epPrintingHighResolution, epFillingForms];
/// disable printing and content extraction or copy for a pdf encrypted file
// - to be used as parameter for TPdfEncryption.New() class method
PDF_PERMISSION_NOCOPYNORPRINT: TPdfEncryptionPermissions = [];
{$endif USE_PDFSECURITY}
(*
Windows Uniscribe APIs
Uniscribe is a set of APIs that allow a high degree of control for fine
typography and for processing complex scripts
- see http://msdn.microsoft.com/en-us/library/dd374091(v=VS.85).aspx
- used by both SynPDF.pas and mORMotReport.pas (for TGDIPages)
- NO_USE_UNISCRIBE conditional can be set globaly for an application
which doesn't need the UniScribe features
*)
{$ifdef USE_UNISCRIBE}
const
Usp10 = 'usp10.dll';
/// error returned by Uniscribe when the current selected font
// does not contain sufficient glyphs or shaping tables
USP_E_SCRIPT_NOT_IN_FONT = HRESULT((SEVERITY_ERROR shl 31) or (FACILITY_ITF shl 16)) or $200;
type
/// UniScribe script state flag elements
// - r0,r1,r2,r3,r4: map TScriptState.uBidiLevel
// - fOverrideDirection: Set when in LRO/RLO embedding
// - fInhibitSymSwap: Set by U+206A (ISS), cleared by U+206B (ASS)
// - fCharShape: Set by U+206D (AAFS), cleared by U+206C (IAFS)
// - fDigitSubstitute: Set by U+206E (NADS), cleared by U+206F (NODS)
// - fInhibitLigate: Equiv !GCP_Ligate, no Unicode control chars yet
// - fDisplayZWG: Equiv GCP_DisplayZWG, no Unicode control characters yet
// - fArabicNumContext: For EN->AN Unicode rule
// - fGcpClusters: For Generating Backward Compatible GCP Clusters (legacy Apps)
TScriptState_enum = (
r0,r1,r2,r3,r4,
fOverrideDirection, fInhibitSymSwap, fCharShape, fDigitSubstitute,
fInhibitLigate, fDisplayZWG, fArabicNumContext, fGcpClusters);
/// a set of UniScribe script state flags
TScriptState_set = set of TScriptState_enum;
PScriptState = ^TScriptState;
/// an UniScribe script state
// - uBidiLevel: Unicode Bidi algorithm embedding level (0..16)
// - fFlags: Script state flags
TScriptState = packed record
case Byte of
0: (uBidiLevel: Byte) {:5};
1: (fFlags: TScriptState_set)
end;
/// Uniscribe script analysis flag elements
// - s0,s1,s2,s3,s4,s5,s6,s7,s8,s9: map TScriptAnalysis.eScript
// - fRTL: Rendering direction
// - fLayoutRTL: Set for GCP classes ARABIC/HEBREW and LOCALNUMBER
// - fLinkBefore: Implies there was a ZWJ before this item
// - fLinkAfter: Implies there is a ZWJ following this item.
// - fLogicalOrder: Set by client as input to ScriptShape/Place
// - fNoGlyphIndex: Generated by ScriptShape/Place - this item does not use
// glyph indices
TScriptAnalysis_enum = (
s0,s1,s2,s3,s4,s5,s6,s7,s8,s9,
fRTL, fLayoutRTL, fLinkBefore, fLinkAfter, fLogicalOrder, fNoGlyphIndex);
/// a set of Uniscribe script analysis flags
TScriptAnalysis_set = set of TScriptAnalysis_enum;
PScriptAnalysis = ^TScriptAnalysis;
/// an Uniscribe script analysis
// - eScript: Shaping engine
// - fFlags: Script analysis flags
// - s: Script state
TScriptAnalysis = packed record
case Byte of
0: (eScript: Word);
1: (fFlags: TScriptAnalysis_set;
s: TScriptState);
end;
PScriptItem = ^TScriptItem;
/// a Uniscribe script item, after analysis of a unicode text
TScriptItem = packed record
/// Logical offset to first character in this item
iCharPos: Integer;
/// corresponding Uniscribe script analysis
a: TScriptAnalysis;
end;
/// all possible Uniscribe processing properties of a given language
// - fNumeric: if a script contains only digits
// - fComplex: Script requires special shaping or layout
// - fNeedsWordBreaking: Requires ScriptBreak for word breaking information
// - fNeedsCaretInfo: Requires caret restriction to cluster boundaries
// - bCharSet0 .. bCharSet7: Charset to use when creating font
// - fControl: Contains only control characters
// - fPrivateUseArea: This item is from the Unicode range U+E000 through U+F8FF
// - fNeedsCharacterJustify: Requires inter-character justification
// - fInvalidGlyph: Invalid combinations generate glyph wgInvalid in the glyph buffer
// - fInvalidLogAttr: Invalid combinations are marked by fInvalid in the logical attributes
// - fCDM: Contains Combining Diacritical Marks
// - fAmbiguousCharSet: Script does not correspond 1// :1 with a charset
// - fClusterSizeVaries: Measured cluster width depends on adjacent clusters
// - fRejectInvalid: Invalid combinations should be rejected
TScriptProperties_enum = (
fNumeric, fComplex, fNeedsWordBreaking, fNeedsCaretInfo,
bCharSet0, bCharSet1, bCharSet2, bCharSet3, bCharSet4, bCharSet5,
bCharSet6, bCharSet7,
fControl, fPrivateUseArea, fNeedsCharacterJustify, fInvalidGlyph,
fInvalidLogAttr, fCDM, fAmbiguousCharSet, fClusterSizeVaries, fRejectInvalid);
/// set of possible Uniscribe processing properties of a given language
TScriptProperties_set = set of TScriptProperties_enum;
PScriptProperties = ^TScriptProperties;
/// Contains information about Uniscribe special processing for each script
TScriptProperties = packed record
/// Primary and sublanguage associated with script
langid: Word;
/// set of possible Uniscribe processing properties for a given language
fFlags: TScriptProperties_set;
end;
PScriptPropertiesArray = ^TPScriptPropertiesArray;
/// an array of Uniscribe processing information
TPScriptPropertiesArray = array[byte] of PScriptProperties;
/// Uniscribe visual (glyph) attributes
// - a0 .. a3: map the Justification class number
// - fClusterStart: First glyph of representation of cluster
// - fDiacritic: Diacritic
// - fZeroWidth: Blank, ZWJ, ZWNJ etc, with no width
// - fReserved: General reserved bit
TScriptVisAttr_enum = (
a0,a1,a2,a3,
fClusterStart, {:1} // First glyph of representation of cluster
fDiacritic, {:1} // Diacritic
fZeroWidth, {:1} // Blank, ZWJ, ZWNJ etc, with no width
fReserved {:1} // General reserved
);
/// set of Uniscribe visual (glyph) attributes
TScriptVisAttr_set = set of TScriptVisAttr_enum;
PScriptVisAttr = ^TScriptVisAttr;
/// Contains the visual (glyph) attributes that identify clusters and
// justification points, as generated by ScriptShape
// - uJustification: Justification class
// - fFlags: Uniscribe visual (glyph) attributes
// - fShapeReserved: Reserved for use by shaping engines
TScriptVisAttr = packed record
case Byte of
0: (uJustification: Byte) {:4};
1: (fFlags: TScriptVisAttr_set;
fShapeReserved: Byte) {:8};
end;
TScriptControlAttr_enum = (
fContextDigits,
fInvertPreBoundDir,
fInvertPostBoundDir,
fLinkStringBefore,
fLinkStringAfter,
fNeutralOverride,
fNumericOverride,
fLegacyBidiClass,
fScr0, fScr1, fScr2, fScr3, fScr4, fScr5, fScr6, fScr7);
TScriptControlAttr_set = set of TScriptControlAttr_enum;
TScriptControl = packed record
uDefaultLanguage: Word;
fFlags: TScriptControlAttr_set;
end;
PScriptControl = ^TScriptControl;
/// Uniscribe function to break a Unicode string into individually shapeable items
// - pwcInChars: Pointer to a Unicode string to itemize.
// - cInChars: Number of characters in pwcInChars to itemize.
// - cMaxItems: Maximum number of SCRIPT_ITEM structures defining items to process.
// - psControl: Optional. Pointer to a SCRIPT_CONTROL structure indicating the
// type of itemization to perform. Alternatively, the application can set this
// parameter to NULL if no SCRIPT_CONTROL properties are needed.
// - psState: Optional. Pointer to a SCRIPT_STATE structure indicating
// the initial bidirectional algorithm state. Alternatively, the application
// can set this parameter to NULL if the script state is not needed.
// - pItems: Pointer to a buffer in which the function retrieves SCRIPT_ITEM
// structures representing the items that have been processed. The buffer
// should be cMaxItems*sizeof(SCRIPT_ITEM) + 1 bytes in length. It is invalid
// to call this function with a buffer to hold less than two SCRIPT_ITEM
// structures. The function always adds a terminal item to the item analysis
// array so that the length of the item with zero-based index "i" is
// always available as:
// ! pItems[i+1].iCharPos - pItems[i].iCharPos;
// - pcItems: Pointer to the number of SCRIPT_ITEM structures processed
function ScriptItemize(
const pwcInChars: PWideChar; cInChars: Integer; cMaxItems: Integer;
const psControl: pointer; const psState: pointer;
pItems: PScriptItem; var pcItems: Integer): HRESULT; stdcall; external Usp10;
/// Uniscribe function to retrieve information about the current scripts
// - ppSp: Pointer to an array of pointers to SCRIPT_PROPERTIES structures
// indexed by script.
// - piNumScripts: Pointer to the number of scripts. The valid range for this
// value is 0 through piNumScripts-1.
function ScriptGetProperties(out ppSp: PScriptPropertiesArray;
out piNumScripts: Integer): HRESULT; stdcall; external Usp10;
/// Uniscribe function to convert an array of run embedding levels to a map
// of visual-to-logical position and/or logical-to-visual position
// - cRuns: Number of runs to process
// - pbLevel: Array of run embedding levels
// - piVisualToLogical: List of run indices in visual order
// - piLogicalToVisual: List of visual run positions
function ScriptLayout(cRuns: Integer; const pbLevel: PByte;
piVisualToLogical: PInteger; piLogicalToVisual: PInteger): HRESULT; stdcall; external Usp10;
/// Uniscribe function to generate glyphs and visual attributes for an Unicode run
// - hdc: Optional (see under caching)
// - psc: Uniscribe font metric cache handle
// - pwcChars: Logical unicode run
// - cChars: Length of unicode run
// - cMaxGlyphs: Max glyphs to generate
// - psa: Result of ScriptItemize (may have fNoGlyphIndex set)
// - pwOutGlyphs: Output glyph buffer
// - pwLogClust: Logical clusters
// - psva: Visual glyph attributes
// - pcGlyphs: Count of glyphs generated
function ScriptShape(hdc: HDC; var psc: pointer; const pwcChars: PWideChar;
cChars: Integer; cMaxGlyphs: Integer; psa: PScriptAnalysis;
pwOutGlyphs: PWord; pwLogClust: PWord; psva: PScriptVisAttr;
var pcGlyphs: Integer): HRESULT; stdcall; external Usp10;
/// Uniscribe function to apply the specified digit substitution settings
// to the specified script control and script state structures
function ScriptApplyDigitSubstitution(
const psds: Pointer; const psControl: pointer;
const psState: pointer): HRESULT; stdcall; external Usp10;
// C++Builder code should #include <usp10.h> directly instead of using these
{$NODEFINE TScriptState }
{$NODEFINE PScriptState }
{$NODEFINE TScriptAnalysis }
{$NODEFINE PScriptAnalysis }
{$NODEFINE TScriptVisAttr }
{$NODEFINE PScriptVisAttr }
{$NODEFINE TScriptItem }
{$NODEFINE PScriptItem }
{$NODEFINE ScriptItemize }
{$NODEFINE ScriptGetProperties }
{$NODEFINE ScriptLayout }
{$NODEFINE ScriptShape }
{$NODEFINE ScriptApplyDigitSubstitution }
{$endif USE_UNISCRIBE}
implementation
const
// those constants are not defined in earlier Delphi revisions
cPI: single = 3.141592654;
cPIdiv180: single = 0.017453292;
c180divPI: single = 57.29577951;
c2PI: double = 6.283185307;
cPIdiv2: double = 1.570796326;
function RGBA(r, g, b, a: cardinal): COLORREF; {$ifdef HASINLINE}inline;{$endif}
begin
Result := ((r shr 8) or ((g shr 8) shl 8) or ((b shr 8) shl 16) or ((a shr 8) shl 24));
end;
procedure SwapBuffer(P: PWordArray; PLen: Integer);
var i: integer;
begin
for i := 0 to PLen-1 do
P^[i] := swap(P^[i]);
end;
function GetTTFData(aDC: HDC; aTableName: PAnsiChar; var Ref: TWordDynArray): pointer;
var L: cardinal;
begin
result := nil;
L := GetFontData(aDC,PCardinal(aTableName)^,0,nil,0);
if L=GDI_ERROR then
exit;
SetLength(ref,L shr 1+1);
if GetFontData(aDC,PCardinal(aTableName)^,0,pointer(ref),L)=GDI_ERROR then
exit;
result := pointer(ref);
SwapBuffer(Result,L shr 1);
end;
function PrinterDriverExists: boolean;
var Flags, Count, NumInfo: dword;
Level: Byte;
begin
// avoid using fPrinter.printers.count as this will raise an
// exception if no printer driver is installed...
Count := 0;
Flags := PRINTER_ENUM_CONNECTIONS or PRINTER_ENUM_LOCAL;
Level := 4;
EnumPrinters(Flags, nil, Level, nil, 0, Count, NumInfo);
result := (count > 0);
end;
function ParseFetchedPrinterStr(Str: PChar): PChar;
var
P: PChar;
begin
Result := Str;
if Str=nil then Exit;
P := Str;
while P^=' ' do Inc(P);
Result := P;
while (P^<>#0) and (P^<>',') do Inc(P);
if P^=',' then
P^ := #0;
end;
function CurrentPrinterPaperSize: TPDFPaperSize;
var PtrHdl: THandle;
PtrPPI: TPoint;
size: TSize;
tmp: integer;
PtrDestSize: TSize;
DefaultPrinter: array[0..1023] of Char;
PC: PChar;
begin
result := psUserDefined;
if not PrinterDriverExists then
exit;
GetProfileString('windows','device',nil,DefaultPrinter,SizeOf(DefaultPrinter)-1);
PC := ParseFetchedPrinterStr(DefaultPrinter);
if (PC=nil) or (PC^=#0) then
exit;
try
PtrHdl := CreateDC(nil,PC,nil,nil);
try
PtrPPI.x := GetDeviceCaps(PtrHdl, LOGPIXELSX);
PtrPPI.y := GetDeviceCaps(PtrHdl, LOGPIXELSY);
PtrDestSize.cx := GetDeviceCaps(PtrHdl, PHYSICALWIDTH);
PtrDestSize.cy := GetDeviceCaps(PtrHdl, PHYSICALHEIGHT);
size.cx := mulDiv(PtrDestSize.cx, 254,PtrPPI.x *10);
size.cy := mulDiv(PtrDestSize.cy, 254,PtrPPI.y *10);
finally
DeleteDC(PtrHdl);
end;
except
On Exception do // raised e.g. if no Printer is existing
exit;
end;
with size do begin
if cx < cy then begin // handle landscape or portrait at once
tmp := cx;
cx := cy;
cy := tmp;
end;
case cy of
148: result := psA5;
210: result := psA4; // A4 (297 x 210mm)
216: if cx=279 then
result := psLetter else
if cx=356 then
result := psLegal;
297: if cx=420 then
result := psA3;
end;
end;
end;
function CurrentPrinterRes: TPoint;
var DefaultPrinter: array[0..1023] of Char;
PC: PChar;
PtrHdl: THandle;
begin
result.X := 300;
result.Y := 300; // default standard printer resolution
if not PrinterDriverExists then
exit;
GetProfileString('windows','device',nil,DefaultPrinter,SizeOf(DefaultPrinter)-1);
PC := ParseFetchedPrinterStr(DefaultPrinter);
if (PC=nil) or (PC^=#0) then
exit;
try
PtrHdl := CreateDC(nil,PC,nil,nil);
try
result.x := GetDeviceCaps(PtrHdl, LOGPIXELSX);
result.y := GetDeviceCaps(PtrHdl, LOGPIXELSY);
finally
DeleteDC(PtrHdl);
end;
except
On Exception do // raised e.g. if no Printer is existing
exit;
end;
end;
procedure GDICommentBookmark(MetaHandle: HDC; const aBookmarkName: RawUTF8);
var Data: RawByteString;
D: PAnsiChar;
L: integer;
begin // high(TPdfGDIComment)<$47 so it will never begin with GDICOMMENT_IDENTIFIER
L := length(aBookmarkName);
SetLength(Data,L+1);
D := pointer(Data);
D^ := AnsiChar(pgcBookmark);
MoveFast(pointer(aBookmarkName)^,D[1],L);
Windows.GdiComment(MetaHandle,L+1,D);
end;
procedure GDICommentOutline(MetaHandle: HDC; const aTitle: RawUTF8; aLevel: Integer);
var Data: RawByteString;
D: PAnsiChar;
L: integer;
begin // high(TPdfGDIComment)<$47 so it will never begin with GDICOMMENT_IDENTIFIER
L := length(aTitle);
SetLength(Data,L+2);
D := pointer(Data);
D[0] := AnsiChar(pgcOutline);
D[1] := AnsiChar(aLevel);
MoveFast(pointer(aTitle)^,D[2],L);
Windows.GdiComment(MetaHandle,L+2,D);
end;
procedure GDICommentLink(MetaHandle: HDC; const aBookmarkName: RawUTF8; const aRect: TRect;
NoBorder: boolean);
var Data: RawByteString;
D: PAnsiChar;
L: integer;
begin // high(TPdfGDIComment)<$47 so it will never begin with GDICOMMENT_IDENTIFIER
L := length(aBookmarkName);
SetLength(Data,L+(1+sizeof(TRect)));
D := pointer(Data);
if NoBorder then
D^ := AnsiChar(pgcLinkNoBorder) else
D^ := AnsiChar(pgcLink);
PRect(D+1)^ := aRect;
MoveFast(pointer(aBookmarkName)^,D[1+sizeof(TRect)],L);
Windows.GdiComment(MetaHandle,L+(1+sizeof(TRect)),D);
end;
procedure GDICommentJpegDirect(MetaHandle: HDC; const aFileName: RawUTF8; const aRect: TRect);
var Data: RawByteString;
D: PAnsiChar;
L: integer;
begin // high(TPdfGDIComment)<$47 so it will never begin with GDICOMMENT_IDENTIFIER
L := length(aFileName);
SetLength(Data,L+(1+sizeof(TRect)));
D := pointer(Data);
D^ := AnsiChar(pgcJpegDirect);
PRect(D+1)^ := aRect;
MoveFast(pointer(aFileName)^,D[1+sizeof(TRect)],L);
Windows.GdiComment(MetaHandle,L+(1+sizeof(TRect)),D);
end;
{$ifndef DELPHI5OROLDER}
// used by TPdfFontTrueType.PrepareForSaving()
function GetTTCIndex(const FontName: RawUTF8; var ttcIndex: Word;
const FontCount: LongWord): Boolean;
// Looks up ttcIndex from list of font names in known ttc font collections.
// For some locales, the lookup may fail
// Result must not be greater than FontCount-1
const
// Font names for Simp/Trad Chinese, Japanese, Korean locales.
BATANG_KO = #48148#53461;
BATANGCHE_KO = BATANG_KO + #52404;
GUNGSUH_KO = #44417#49436;
GUNGSUHCHE_KO = GUNGSUH_KO + #52404;
GULIM_KO = #44404#47548;
GULIMCHE_KO = GULIM_KO + #52404;
DOTUM_KO = #46027#50880;
DOTUMCHE_KO = DOTUM_KO + #52404;
MINGLIU_CH = #32048#26126#39636;
PMINGLIU_CH = #26032 + MINGLIU_CH;
MINGLIU_HK_CH = MINGLIU_CH + '_hkscs';
MINGLIU_XB_CH = MINGLIU_CH + '-extb';
PMINGLIU_XB_CH = PMINGLIU_CH + '-extb';
MINGLIU_XBHK_CH = MINGLIU_CH + '-extb_hkscs';
MSGOTHIC_JA = #65325#65331#32#12468#12471#12483#12463;
MSPGOTHIC_JA = #65325#65331#32#65328#12468#12471#12483#12463;
MSMINCHO_JA = #65325#65331#32#26126#26397;
MSPMINCHO_JA = #65325#65331#32#65328#26126#26397;
SIMSUN_CHS = #23435#20307;
NSIMSUN_CHS = #26032#23435#20307;
var
lcfn: SynUnicode;
begin
result := True;
UTF8ToSynUnicode(fontName,lcfn);
lcfn := {$ifdef UNICODE}SysUtils.LowerCase{$else}WideLowerCase{$endif}(lcfn);
// batang.ttc (Korean)
if (lcfn='batang') or (lcfn=BATANG_KO) then
ttcIndex := 0 else
if (lcfn='batangche') or (lcfn=BATANGCHE_KO) then
ttcIndex := 1 else
if (lcfn='gungsuh') or (lcfn=GUNGSUH_KO) then
ttcIndex := 2 else
if (lcfn='gungsuhche') or (lcfn=GUNGSUHCHE_KO) then
ttcIndex := 3 else
// cambria.ttc
if lcfn='cambria' then
ttcIndex := 0 else
if lcfn='cambria math' then
ttcIndex := 1 else
// gulim.ttc (Korean)
if (lcfn='gulim') or (lcfn=GULIM_KO) then
ttcIndex := 0 else
if (lcfn='gulimche') or (lcfn=GULIMCHE_KO) then
ttcIndex := 1 else
if (lcfn='dotum') or (lcfn=DOTUM_KO) then
ttcIndex := 2 else
if (lcfn='dotumche') or (lcfn=DOTUMCHE_KO) then
ttcIndex := 3 else
// mingliu.ttc (Traditional Chinese)
if (lcfn='mingliu') or (lcfn=MINGLIU_CH) then
ttcIndex := 0 else
if (lcfn='pmingliu') or (lcfn=PMINGLIU_CH) then
ttcIndex := 1 else
if (lcfn='mingliu_hkscs') or (lcfn=MINGLIU_HK_CH) then
ttcIndex := 2 else
// mingliub.ttc (Traditional Chinese)
if (lcfn='mingliu-extb') or (lcfn=MINGLIU_XB_CH) then
ttcIndex := 0 else
if (lcfn='pmingliu-extb') or (lcfn=PMINGLIU_XB_CH) then
ttcIndex := 1 else
if (lcfn='mingliu_hkscs-extb') or (lcfn=MINGLIU_XBHK_CH) then
ttcIndex := 2 else
// msgothic.ttc (Japanese)
if (lcfn='ms gothic') or
(lcfn={$ifdef UNICODE}SysUtils.LowerCase{$else}WideLowerCase{$endif}(MSGOTHIC_JA)) then
ttcIndex := 0 // MSGOTHIC_JA contains full-width uppercase chars
else if (lcfn='ms pgothic') or
(lcfn={$ifdef UNICODE}SysUtils.LowerCase{$else}WideLowerCase{$endif}(MSPGOTHIC_JA)) then
ttcIndex := 1 else
if lcfn='ms ui gothic' then
ttcIndex := 2 else
// msmincho.ttc (Japanese)
if (lcfn='ms mincho') or
(lcfn={$ifdef UNICODE}SysUtils.LowerCase{$else}WideLowerCase{$endif}(MSMINCHO_JA)) then
ttcIndex := 0 else
if (lcfn='ms pmincho') or
(lcfn={$ifdef UNICODE}SysUtils.LowerCase{$else}WideLowerCase{$endif}(MSPMINCHO_JA)) then
ttcIndex := 1 else
// simsun.ttc (Simplified Chinese)
if (lcfn='simsun') or (lcfn=SIMSUN_CHS) then
ttcIndex := 0 else
if (lcfn='nsimsun') or (lcfn=NSIMSUN_CHS) then
ttcIndex := 1 else
result := False;
if result and (ttcIndex>(FontCount-1)) then
result := False;
end;
{$endif DELPHI5OROLDER}
{$ifdef USE_ARC}
type
tcaRes = (caMoveto, caLine, caCurve, caPosition);
teaDrawtype = record
res: tcaRes;
pts: array[0..2] of record x, y: single;
end;
end;
teaDrawArray = array of teaDrawtype;
function CalcCurveArcData(centerx, centery, W, H, Sx, Sy, Ex, Ey: integer;
aClockWise: boolean; arctype: TPdfCanvasArcType; out res: teaDrawArray): boolean;
type
TCoeff = array[0..3] of double;
TCoeffArray = array[0..1, 0..3] of TCoeff;
const
// coefficients for error estimation
// while using cubic Bezier curves for approximation
// 0 < b/a < 1/4
coeffsLow: TCoeffArray = (
((3.85268, -21.229, -0.330434, 0.0127842),
(-1.61486, 0.706564, 0.225945, 0.263682),
(-0.910164, 0.388383, 0.00551445, 0.00671814),
(-0.630184, 0.192402, 0.0098871, 0.0102527)),
((-0.162211, 9.94329, 0.13723, 0.0124084),
(-0.253135, 0.00187735, 0.0230286, 0.01264),
(-0.0695069, -0.0437594, 0.0120636, 0.0163087),
(-0.0328856, -0.00926032, -0.00173573, 0.00527385)));
// coefficients for error estimation
// while using cubic Bezier curves for approximation
// 1/4 <= b/a <= 1
coeffsHigh: TCoeffArray = (
((0.0899116, -19.2349, -4.11711, 0.183362),
(0.138148, -1.45804, 1.32044, 1.38474),
(0.230903, -0.450262, 0.219963, 0.414038),
(0.0590565, -0.101062, 0.0430592, 0.0204699)),
((0.0164649, 9.89394, 0.0919496, 0.00760802),
(0.0191603, -0.0322058, 0.0134667, -0.0825018),
(0.0156192, -0.017535, 0.00326508, -0.228157),
(-0.0236752, 0.0405821, -0.0173086, 0.176187)));
// safety factor to convert the "best" error approximation
// into a "max bound" error
safety: TCoeff = (0.001, 4.98, 0.207, 0.0067);
var fcx, fcy: double; // center of the ellipse
faRad, fbRad: double; // Semi-major axis
feta1, feta2: double; // Start End angle of the arc
fx1, fy1, fx2, fy2: double; //start and and endpoint
fxLeft, fyUp: double; // leftmost point of the arc
fwidth, fheight: double; // Horizontal width of the arc Vertical height of the arc
fArctype: TPdfCanvasArcType; //Indicator for center to endpoints line inclusion
fClockWise : boolean;
procedure InitFuncData;
var lambda1, lambda2 : double;
begin
fcx := centerx;
fcy := centery;
faRad := (W-1) / 2;
fbRad := (H-1) / 2;
fArctype := arctype;
// Calculate Rotation at Start and EndPoint
fClockWise := aClockWise;
if aclockwise then begin
lambda1 := ArcTan2(Sy - fcy, Sx - fcx);
lambda2 := ArcTan2(Ey - fcy, Ex - fcx);
end else begin
lambda2 := ArcTan2(Sy - fcy, Sx - fcx);
lambda1 := ArcTan2(Ey - fcy, Ex - fcx);
end;
feta1 := ArcTan2(sin(lambda1) / fbRad, cos(lambda1) / faRad);
feta2 := ArcTan2(sin(lambda2) / fbRad, cos(lambda2) / faRad);
// make sure we have eta1 <= eta2 <= eta1 + 2 PI
feta2 := feta2 - (c2PI * floor((feta2 - feta1) / c2PI));
// the preceding correction fails if we have exactly et2 - eta1 = 2 PI
// it reduces the interval to zero length
if SameValue(feta1, feta2) then
feta2 := feta2 + c2PI;
// start point
fx1 := fcx + (faRad * cos(feta1));
fy1 := fcy + (fbRad * sin(feta1));
// end point
fx2 := fcx + (faRad * cos(feta2));
fy2 := fcy + (fbRad * sin(feta2));
// Dimensions
fxLeft := min(fx1, fx2);
fyUp := min(fy1, fy2);
fwidth := max(fx1, fx2) - fxLeft;
fheight := max(fy1, fy2) - fyUp;
end;
function estimateError(etaA, etaB: double): double;
var coeffs: ^TCoeffArray;
c0, c1, cos2, cos4, cos6, dEta, eta, x: double;
function rationalFunction(x: double; const c: TCoeff): double;
begin
result := (x * (x * c[0] + c[1]) + c[2]) / (x + c[3]);
end;
begin
eta := 0.5 * (etaA + etaB);
x := fbRad / faRad;
dEta := etaB - etaA;
cos2 := cos(2 * eta);
cos4 := cos(4 * eta);
cos6 := cos(6 * eta);
// select the right coeficients set according to degree and b/a
if x < 0.25 then
coeffs := @coeffsLow else
coeffs := @coeffsHigh;
c0 := rationalFunction(x, coeffs[0][0]) +
cos2 * rationalFunction(x, coeffs[0][1]) +
cos4 * rationalFunction(x, coeffs[0][2]) +
cos6 * rationalFunction(x, coeffs[0][3]);
c1 := rationalFunction(x, coeffs[1][0]) +
cos2 * rationalFunction(x, coeffs[1][1]) +
cos4 * rationalFunction(x, coeffs[1][2]) +
cos6 * rationalFunction(x, coeffs[1][3]);
result := rationalFunction(x, safety) * faRad * exp(c0 + c1 * dEta);
end;
procedure BuildPathIterator;
var alpha: double;
found: Boolean;
n: integer;
dEta, etaB, etaA: double;
cosEtaB, sinEtaB, aCosEtaB, bSinEtaB, aSinEtaB, bCosEtaB, xB, yB, xBDot, yBDot: double;
i: integer;
t, xA, xADot, yA, yADot: double;
ressize: integer; // Index var for result Array
r: ^teaDrawtype;
lstartx, lstarty : double; // Start from
const
defaultFlatness = 0.5; // half a pixel
begin
// find the number of Bezier curves needed
found := false;
n := 1;
while (not found) and (n < 1024) do begin
dEta := (feta2 - feta1) / n;
if dEta <= cPIdiv2 then begin
etaB := feta1;
found := true;
for i := 0 to n - 1 do begin
etaA := etaB;
etaB := etaB + dEta;
found := (estimateError(etaA, etaB) <= defaultFlatness);
if not found then
break;
end;
end;
// if not found then
n := n shl 1;
end;
dEta := (feta2 - feta1) / n;
etaB := feta1;
cosEtaB := cos(etaB);
sinEtaB := sin(etaB);
aCosEtaB := faRad * cosEtaB;
bSinEtaB := fbRad * sinEtaB;
aSinEtaB := faRad * sinEtaB;
bCosEtaB := fbRad * cosEtaB;
xB := fcx + aCosEtaB;
yB := fcy + bSinEtaB;
xBDot := -aSinEtaB;
yBDot := +bCosEtaB;
lstartx := xB;
lstarty := yB;
// calculate and reserve Space for the result
ressize := n;
case fArctype of
acArc : inc(ressize,1); // first move
acArcTo: inc(ressize,3); // first line and move
acArcAngle: inc(ressize,1); // first move
acPie: inc(ressize,3); // first and last Line
acChoord: inc(ressize,2);
end;
SetLength(res, ressize);
r := pointer(res);
case fArctype of
acArc: begin // start with move
r^.res := caMoveto;
r^.pts[0].x := xB;
r^.pts[0].y := yB;
inc(r);
end;
acArcTo : begin // start with line and move
r^.res := caLine;
if fClockwise then begin
r^.pts[0].x := fx1;
r^.pts[0].y := fy1;
end else begin
r^.pts[0].x := fx2;
r^.pts[0].y := fy2;
end;
inc(r);
r^.res := caMoveto;
r^.pts[0].x := fx1;
r^.pts[0].y := fy1;
inc(r);
end;
acArcAngle: ;
acPie : begin
r^.res := caMoveto;
r^.pts[0].x := fcx;
r^.pts[0].y := fcy;
inc(r);
r^.res := caLine;
r^.pts[0].x := xB;
r^.pts[0].y := yB;
inc(r);
end;
acChoord : begin
r^.res := caMoveto;
r^.pts[0].x := xB;
r^.pts[0].y := yB;
inc(r);
end;
end;
t := tan(0.5 * dEta);
alpha := sin(dEta) * (sqrt(4 + 3 * t * t) - 1) / 3;
for i := 0 to n - 1 do begin
xA := xB;
yA := yB;
xADot := xBDot;
yADot := yBDot;
etaB := etaB + dEta;
cosEtaB := cos(etaB);
sinEtaB := sin(etaB);
aCosEtaB := faRad * cosEtaB;
bSinEtaB := fbRad * sinEtaB;
aSinEtaB := faRad * sinEtaB;
bCosEtaB := fbRad * cosEtaB;
xB := fcx + aCosEtaB;
yB := fcy + bSinEtaB;
xBDot := -aSinEtaB;
yBDot := bCosEtaB;
r^.res := caCurve;
r^.pts[0].x := xA + alpha * xADot;
r^.pts[0].y := yA + alpha * yADot;
r^.pts[1].x := xB - alpha * xBDot;
r^.pts[1].y := yB - alpha * yBDot;
r^.pts[2].x := xB;
r^.pts[2].y := yB;
inc(r);
end; // Loop
case fArctype of
acArcTo: begin
r^.res := caPosition;
if fClockWise then begin
r^.pts[0].x := fx2;
r^.pts[0].y := fy2;
end else begin
r^.pts[0].x := fx1;
r^.pts[0].y := fy1;
end
end;
acPie: begin
r^.res := caLine;
r^.pts[0].x := fcx;
r^.pts[0].y := fcy;
end;
acChoord: begin
r^.res := caLine;
r^.pts[0].x := lstartx;
r^.pts[0].y := lstarty;
end;
end;
end;
begin
res := nil;
InitFuncData; // Initialize Data
buildPathIterator;
result := length(res) > 1;
end;
{$endif USE_ARC}
{ TPdfObject }
constructor TPdfObject.Create;
begin
FObjectNumber := -1;
end;
procedure TPdfObject.ForceSaveNow;
begin
FSaveAtTheEnd := False;
end;
procedure TPdfObject.InternalWriteTo(W: TPdfWrite);
begin
{$ifdef USE_PDFSECURITY}
if FObjectNumber>0 then
with W.fDoc do begin
fCurrentObjectNumber := FObjectNumber;
fCurrentGenerationNumber := FGenerationNumber;
end;
{$endif USE_PDFSECURITY}
end;
procedure TPdfObject.SetObjectNumber(Value: integer);
begin
FObjectNumber := Value;
if Value > 0 then
FObjectType := otIndirectObject else
FObjectType := otDirectObject;
end;
function TPdfObject.SpaceNotNeeded: boolean;
begin
result := false;
end;
procedure TPdfObject.WriteTo(var W: TPdfWrite);
begin
if FObjectType=otDirectObject then
InternalWriteTo(W) else
W.AddWithSpace(FObjectNumber).AddWithSpace(FGenerationNumber).Add('R');
end;
procedure TPdfObject.WriteValueTo(var W: TPdfWrite);
begin
if FObjectType<>otIndirectObject then
raise EPdfInvalidOperation.Create('WriteValueTo');
W.AddWithSpace(FObjectNumber).AddWithSpace(FGenerationNumber).Add('obj'+CRLF);
InternalWriteTo(W);
W.Add(CRLF+'endobj'#10);
end;
{ PdfVirtualObject }
constructor TPdfVirtualObject.Create(AObjectId: integer);
begin
inherited Create;
FObjectNumber := AObjectId;
FObjectType := otVirtualObject;
end;
{ TPdfNull }
procedure TPdfNull.InternalWriteTo(W: TPdfWrite);
begin
W.Add('null');
end;
{ TPdfBoolean }
procedure TPdfBoolean.InternalWriteTo(W: TPdfWrite);
begin
W.Add(BOOL_UTF8[Value]);
end;
constructor TPdfBoolean.Create(AValue: Boolean);
begin
inherited Create;
Value := AValue;
end;
{ TPdfNumber }
procedure TPdfNumber.InternalWriteTo(W: TPdfWrite);
begin
W.Add(FValue);
end;
constructor TPdfNumber.Create(AValue: integer);
begin
inherited Create;
Value := AValue;
end;
{ TPdfReal }
procedure TPdfReal.InternalWriteTo(W: TPdfWrite);
begin
W.Add(Value);
end;
constructor TPdfReal.Create(AValue: double);
begin
inherited Create;
Value := AValue;
end;
{ TPdfText }
procedure TPdfText.InternalWriteTo(W: TPdfWrite);
begin
// if the value has multibyte character, convert the value to hex unicode.
// otherwise, escape characters.
if SysLocale.FarEast and _HasMultiByteString(pointer(FValue)) then
W.Add('<FEFF').AddToUnicodeHex(FValue).Add('>') else
W.Add('(').AddEscapeContent(FValue).Add(')');
end;
constructor TPdfText.Create(const AValue: RawByteString);
begin
inherited Create;
Value := AValue;
end;
function TPdfText.SpaceNotNeeded: boolean;
begin
result := True;
end;
{ TPdfTextUTF8 }
constructor TPdfTextUTF8.Create(const AValue: RawUTF8);
begin
inherited Create;
Value := AValue;
end;
procedure TPdfTextUTF8.InternalWriteTo(W: TPdfWrite);
var Len: Integer;
begin
// if the value has multibyte character, convert the value to hex unicode.
// otherwise, escape characters
if IsWinAnsiU8Bit(Pointer(FValue)) then
W.Add('(').AddEscapeContent(Utf8ToWinAnsi(FValue)).Add(')') else
W.Add('<FEFF').AddUnicodeHex(
Pointer(Utf8DecodeToRawUnicodeUI(FValue,@Len)),Len shr 1).Add('>');
end;
function TPdfTextUTF8.SpaceNotNeeded: boolean;
begin
result := True;
end;
{ TPdfTextString }
constructor TPdfTextString.Create(const AValue: string);
begin
inherited Create(StringToUTF8(AValue));
end;
function TPdfTextString.GetValue: string;
begin
result := UTF8ToString(FValue);
end;
procedure TPdfTextString.SetValue(const Value: string);
begin
FValue := StringToUTF8(Value);
end;
{ TPdfRawText }
procedure TPdfRawText.InternalWriteTo(W: TPdfWrite);
begin
W.Add(FValue);
end;
function TPdfRawText.SpaceNotNeeded: boolean;
begin
result := false;
end;
{ TPdfClearText }
constructor TPdfClearText.Create(Buffer: pointer; Len: integer);
var tmp: RawByteString;
begin
SetString(tmp,PAnsiChar(Buffer),Len);
inherited Create(tmp);
end;
procedure TPdfClearText.InternalWriteTo(W: TPdfWrite);
begin
W.Add('(').AddEscape(pointer(FValue),Length(FValue)).Add(')');
end;
{ TPdfName }
procedure TPdfName.InternalWriteTo(W: TPdfWrite);
begin
W.Add('/').AddEscapeName(pointer(FValue));
end;
procedure TPdfName.AppendPrefix;
var prefix: RawUtf8;
c: cardinal;
i: PtrInt;
begin
if self=nil then
exit;
SetLength(prefix, 7);
c := Random32; // we will consume only 24-bit of randomness
for i := 1 to 6 do
begin
prefix[i] := AnsiChar((c and 15) + 65);
c := c shr 4;
end;
prefix[7] := '+';
FValue := prefix+FValue; // we ensured a single subset per font
end;
{ TPdfArray }
function TPdfArray.GetItems(Index: integer): TPdfObject;
begin
result := TPdfObject(FArray[Index]);
if result.ObjectType=otVirtualObject then
if FObjectMgr<>nil then
result := FObjectMgr.GetObject(result.ObjectNumber) else
result := nil;
end;
function TPdfArray.GetItemCount: integer;
begin
if self=nil then
Result := 0 else
Result := FArray.Count;
end;
procedure TPdfArray.InternalWriteTo(W: TPdfWrite);
var i: integer;
begin
inherited;
W.Add('[');
for i := 0 to FArray.Count-1 do
with TPdfObject(FArray.List[i]) do begin
if (i<>0) and not SpaceNotNeeded then
W.Add(' ');
WriteTo(W);
end;
W.Add(']');
end;
constructor TPdfArray.Create(AObjectMgr: TPdfObjectMgr);
begin
inherited Create;
FArray := TList.Create;
FObjectMgr := AObjectMgr;
end;
constructor TPdfArray.Create(AObjectMgr: TPdfObjectMgr;
const AArray: array of Integer);
var i: integer;
begin
Create(AObjectMgr);
for i := 0 to High(AArray) do
AddItem(TPdfNumber.Create(AArray[i]));
end;
constructor TPdfArray.Create(AObjectMgr: TPdfObjectMgr;
AArray: PWordArray; AArrayCount: integer);
var i: integer;
begin
Create(AObjectMgr);
for i := 0 to AArrayCount-1 do
AddItem(TPdfNumber.Create(AArray^[i]));
end;
constructor TPdfArray.CreateNames(AObjectMgr: TPdfObjectMgr;
const AArray: array of PDFString);
var i: integer;
begin
Create(AObjectMgr);
for i := 0 to high(AArray) do
AddItem(TPdfName.Create(AArray[i]));
end;
constructor TPdfArray.CreateReals(AObjectMgr: TPdfObjectMgr;
const AArray: array of double);
var i: integer;
begin
Create(AObjectMgr);
for i := 0 to high(AArray) do
AddItem(TPdfReal.Create(AArray[i]));
end;
destructor TPdfArray.Destroy;
var i: integer;
begin
for i := 0 to FArray.Count-1 do
TPdfObject(FArray.List[i]).Free;
FArray.Free;
inherited;
end;
function TPdfArray.AddItem(AItem: TPdfObject): integer;
begin
result := FArray.IndexOf(AItem);
if result>=0 then
exit; // if AItem already exists, do nothing
if AItem.ObjectType=otDirectObject then
result := FArray.Add(AItem) else
result := FArray.Add(TPdfVirtualObject.Create(AItem.ObjectNumber))
end;
procedure TPdfArray.InsertItem(Index: Integer; AItem: TPdfObject);
begin
if FArray.IndexOf(AItem)>=0 then
exit; // if AItem already exists, do nothing
if AItem.ObjectType=otDirectObject then
FArray.Insert(Index, AItem) else
FArray.Insert(Index, TPdfVirtualObject.Create(AItem.ObjectNumber))
end;
function TPdfArray.FindName(const AName: PDFString): TPdfName;
var i: integer;
begin
for i := 0 to ItemCount-1 do begin
result := TPdfName(FArray.List[i]);
if (result<>nil) and result.InheritsFrom(TPdfName) and
(result.Value=AName) then
Exit;
end;
result := nil;
end;
function TPdfArray.RemoveName(const AName: PDFString): boolean;
var AObject: TPdfObject;
begin
result := false;
AObject := FindName(AName);
if AObject<>nil then begin
FArray.Remove(AObject);
if AObject.ObjectType=otDirectObject then
AObject.Free;
result := true;
end;
end;
function TPdfArray.SpaceNotNeeded: boolean;
begin
result := True;
end;
{ TPdfDictionaryElement }
function TPdfDictionaryElement.GetKey: PDFString;
begin
if self=nil then
result := '' else
result := FKey.Value;
end;
constructor TPdfDictionaryElement.Create(const AKey: PDFString; AValue: TPdfObject;
AInternal: Boolean);
begin
if not (AValue is TPdfObject) then
raise EPdfInvalidValue.Create('TPdfDictionaryElement');
FKey := TPdfName.Create(AKey);
FValue := AValue;
FIsInternal := AInternal;
end;
destructor TPdfDictionaryElement.Destroy;
begin
FKey.Free;
FValue.Free;
inherited;
end;
{ TPdfDictionary }
function TPdfDictionary.GetItems(Index: integer): TPdfDictionaryElement;
begin
result := TPdfDictionaryElement(FArray[Index]);
end;
function TPdfDictionary.GetItemCount: integer;
begin
if self=nil then
Result := 0 else
Result := FArray.Count;
end;
procedure TPdfDictionary.DirectWriteto(W: TPdfWrite; Secondary: TPdfDictionary);
procedure WriteArray(aArray: TList);
var i: integer;
Element: TPdfDictionaryElement;
begin
for i := 0 to aArray.Count-1 do begin
Element := aArray.List[i];
if not Element.IsInternal then begin
Element.FKey.WriteTo(W);
if not Element.FValue.SpaceNotNeeded then
W.Add(' ');
Element.FValue.WriteTo(W);
end;
end;
end;
begin
inherited InternalWriteTo(W);
W.Add('<<');
WriteArray(FArray);
if Secondary<>nil then
WriteArray(Secondary.FArray);
W.Add('>>');
end;
procedure TPdfDictionary.InternalWriteTo(W: TPdfWrite);
begin
DirectWriteto(W,nil);
end;
constructor TPdfDictionary.Create(AObjectMgr: TPdfObjectMgr);
begin
inherited Create;
FArray := TList.Create;
FObjectMgr := AObjectMgr;
end;
destructor TPdfDictionary.Destroy;
var i: integer;
begin
for i := 0 to FArray.Count-1 do
TPdfDictionaryElement(FArray[i]).Free;
FArray.Free;
inherited;
end;
function TPdfDictionary.ValueByName(const AKey: PDFString): TPdfObject;
var i: integer;
begin
if self<>nil then
for i := 0 to FArray.Count-1 do
with TPdfDictionaryElement(FArray.List[i]) do
if FKey.Value=AKey then begin
result := Value;
if result.ObjectType=otVirtualObject then
if FObjectMgr<>nil then
result := FObjectMgr.GetObject(result.ObjectNumber) else
result := nil;
exit;
end;
result := nil;
end;
function TPdfDictionary.PdfNumberByName(const AKey: PDFString): TPdfNumber;
begin
result := TPdfNumber(ValueByName(AKey));
end;
function TPdfDictionary.PdfTextByName(const AKey: PDFString): TPdfText;
begin
result := TPdfText(ValueByName(AKey));
end;
function TPdfDictionary.PdfTextValueByName(const AKey: PDFString): PDFString;
var P: TPdfText;
begin
P := TPdfText(ValueByName(AKey));
if P=nil then
result := '' else
result := P.Value;
end;
function TPdfDictionary.PdfTextStringValueByName(const AKey: PDFString): string;
var P: TPdfTextString;
begin
P := TPdfTextString(ValueByName(AKey));
if P=nil then
result := '' else
result := P.Value;
end;
function TPdfDictionary.PdfTextUTF8ValueByName(const AKey: PDFString): RawUTF8;
var P: TPdfTextUTF8;
begin
P := TPdfTextUTF8(ValueByName(AKey));
if P=nil then
result := '' else
result := P.Value;
end;
function TPdfDictionary.PdfRealByName(const AKey: PDFString): TPdfReal;
begin
result := TPdfReal(ValueByName(AKey));
end;
function TPdfDictionary.PdfNameByName(const AKey: PDFString): TPdfName;
begin
result := TPdfName(ValueByName(AKey));
end;
function TPdfDictionary.PdfDictionaryByName(const AKey: PDFString): TPdfDictionary;
begin
result := TPdfDictionary(ValueByName(AKey));
end;
function TPdfDictionary.PdfArrayByName(const AKey: PDFString): TPdfArray;
begin
result := TPdfArray(ValueByName(AKey));
end;
function TPdfDictionary.PdfBooleanByName(const AKey: PDFString): TPdfBoolean;
begin
result := TPdfBoolean(ValueByName(AKey));
end;
procedure TPdfDictionary.AddItem(const AKey: PDFString; AValue: TPdfObject;
AInternal: Boolean);
var FItem: TPdfDictionaryElement;
begin
if self=nil then
exit;
RemoveItem(AKey);
if AValue.ObjectType=otDirectObject then
FItem := TPdfDictionaryElement.Create(AKey, AValue, AInternal) else
FItem := TPdfDictionaryElement.Create(AKey,
TPdfVirtualObject.Create(AValue.ObjectNumber), AInternal);
FArray.Add(FItem);
end;
procedure TPdfDictionary.AddItem(const AKey, AValue: PDFString);
begin
AddItem(AKey,TPdfName.Create(AValue));
end;
procedure TPdfDictionary.AddItem(const AKey: PDFString; AValue: integer);
begin
AddItem(AKey,TPdfNumber.Create(AValue));
end;
procedure TPdfDictionary.AddItemText(const AKey, AValue: PDFString);
begin
AddItem(AKey,TPdfText.Create(AValue));
end;
procedure TPdfDictionary.AddItemTextUTF8(const AKey: PDFString; const AValue: RawUTF8);
begin
AddItem(AKey,TPdfTextUTF8.Create(AValue));
end;
procedure TPdfDictionary.AddItemTextString(const AKey: PDFString;
const AValue: string);
begin
AddItem(AKey,TPdfTextString.Create(AValue));
end;
procedure TPdfDictionary.RemoveItem(const AKey: PDFString);
var i: integer;
FElement: TPdfDictionaryElement;
begin
if Self<>nil then
for i := 0 to FArray.Count-1 do begin
FElement := FArray.List[i];
if FElement.FKey.Value=AKey then begin
FArray.Remove(FElement);
FElement.Free;
Break;
end;
end;
end;
function TPdfDictionary.getTypeOf: PDFString;
var PdfName: TPdfName;
begin
PdfName := PdfNameByName('Type');
if PdfName<>nil then
result := PdfName.Value else
result := '';
end;
function TPdfDictionary.SpaceNotNeeded: boolean;
begin
result := True;
end;
{ TPdfStream }
procedure TPdfStream.InternalWriteTo(W: TPdfWrite);
var FLength: TPdfNumber;
TmpStream: TMemoryStream;
TmpSize: integer;
Buf: pointer;
begin
inherited;
FLength := FAttributes.PdfNumberByName('Length');
FWriter.Save; // flush FWriter content
Buf := TMemoryStream(FWriter.fDestStream).Memory;
TmpSize := FWriter.Position;
TmpStream := nil;
try
if FFilter='FlateDecode' then
if TmpSize<100 then // don't compress tiny blocks
FFilter := '' else begin
TmpStream := THeapMemoryStream.Create;
{$ifdef USE_SYNZIP}
TmpSize := CompressStream(Buf,TmpSize,TmpStream,7,true);
{$else}
with TCompressionStream.Create(clMax, TmpStream) do
begin
Write(Buf^,TmpSize);
Free;
end;
TmpSize := TmpStream.Size;
{$endif}
Buf := TmpStream.Memory;
end;
FLength.Value := TmpSize;
if FFilter<>'' then
FAttributes.AddItem('Filter',FFilter);
FAttributes.DirectWriteTo(W,FSecondaryAttributes);
{$ifdef USE_PDFSECURITY}
if (TmpSize>0) and (W.fDoc.fEncryption<>nil) and not FDoNotEncrypt then
W.fDoc.fEncryption.EncodeBuffer(Buf^,Buf^,TmpSize);
{$endif USE_PDFSECURITY}
W.Add(#10'stream'#10).Add(Buf,TmpSize).
Add(#10'endstream');
FWriter.fDestStream.Size := 0; // release internal stream memory
finally
TmpStream.Free;
end;
end;
constructor TPdfStream.Create(ADoc: TPdfDocument; DontAddToFXref: boolean=false);
var FXref: TPdfXRef;
begin
inherited Create;
if DontAddToFXref then
FXRef := nil else begin
FXRef := ADoc.FXref;
FXRef.AddObject(self);
end;
FAttributes := TPdfDictionary.Create(FXref);
FAttributes.AddItem('Length', TPdfNumber.Create(0));
if ADoc.CompressionMethod=cmFlateDecode then
FFilter := 'FlateDecode';
FWriter := TPdfWrite.Create(ADoc,THeapMemoryStream.Create);
end;
destructor TPdfStream.Destroy;
begin
FWriter.fDestStream.Free;
FWriter.Free;
FAttributes.Free;
inherited;
end;
{ TPdfBinary }
procedure TPdfBinary.InternalWriteTo(W: TPdfWrite);
begin
inherited;
W.Add(Stream.Memory,FStream.Size);
end;
constructor TPdfBinary.Create;
begin
inherited;
FStream := THeapMemoryStream.Create;
end;
destructor TPdfBinary.Destroy;
begin
FStream.Free;
inherited;
end;
{ utility functions }
function _DateTimeToPdfDate(ADate: TDateTime): TPdfDate;
var D: array[2..8] of word;
i: PtrInt;
begin
DecodeDate(ADate,D[2],D[3],D[4]);
DecodeTime(ADate,D[5],D[6],D[7],D[8]);
SetLength(result,17);
YearToPChar(D[2],pointer(PtrInt(result)+2));
PWord(result)^ := ord('D')+ord(':')shl 8;
for i := 3 to 7 do
PWordArray(pointer(result))^[i] := TwoDigitLookupW[D[i]];
PByteArray(result)[16] := ord('Z');
// Assert(abs(_PdfDateToDateTime(result)-ADate)<MSecsPerSec);
end;
const // not existing before Delphi 7
HoursPerDay = 24;
MinsPerHour = 60;
SecsPerMin = 60;
MSecsPerSec = 1000;
MinsPerDay = HoursPerDay * MinsPerHour;
SecsPerDay = MinsPerDay * SecsPerMin;
MSecsPerDay = SecsPerDay * MSecsPerSec;
function _PdfDateToDateTime(const AText: TPdfDate): TDateTime;
var Y,M,D, H,MI,SS: cardinal;
begin
if Length(AText)<16 then
raise EConvertError.CreateRes(@SDateEncodeError);
Y := ord(AText[3])*1000+ord(AText[4])*100+ord(AText[5])*10+ord(AText[6])
-(48+480+4800+48000);
M := ord(AText[7])*10+ord(AText[8])-(48+480);
D := ord(AText[9])*10+ord(AText[10])-(48+480);
result := EncodeDate(Y,M,D);
H := ord(AText[11])*10+ord(AText[12])-(48+480);
MI := ord(AText[13])*10+ord(AText[14])-(48+480);
SS := ord(AText[15])*10+ord(AText[16])-(48+480);;
if (H<24) and (MI<60) and (SS<60) then // inlined EncodeTime()
result := result + (H * (MinsPerHour * SecsPerMin * MSecsPerSec) +
MI * (SecsPerMin * MSecsPerSec) + SS * MSecsPerSec) / MSecsPerDay else
raise EConvertError.CreateRes(@SDateEncodeError);
end;
function _HasMultiByteString(Value: PAnsiChar): boolean;
begin
if Value<>nil then
while true do
if Value^=#0 then
Break else
if not (Value^ in LeadBytes) then
inc(Value) else begin
result := true;
exit;
end;
result := false;
end;
function RawUTF8ToPDFString(const Value: RawUTF8): PDFString;
begin
result := CurrentAnsiConvert.UTF8BufferToAnsi(pointer(Value),length(Value));
end;
function UInt32ToPDFString(Value : Cardinal): PDFString;
var tmp: array[0..23] of AnsiChar;
P: PAnsiChar;
begin
P := StrUInt32(@tmp[23],Value);
SetString(result,P,@tmp[23]-P);
end;
function PdfRect(Left, Top, Right, Bottom: Single): TPdfRect;
begin
result.Left := Left;
result.Top := Top;
result.Right := Right;
result.Bottom := Bottom;
end;
function PdfRect(const Box: TPdfBox): TPdfRect;
begin
result.Left := Box.Left;
result.Top := Box.Top;
result.Right := Box.Left+Box.Width;
result.Bottom := Box.Top-Box.Height;
end;
function PdfBox(Left, Top, Width, Height: Single): TPdfBox;
begin
result.Left := Left;
result.Top := Top;
result.Width := Width;
result.Height := Height;
end;
function CombineTransform(xform1, xform2: XFORM): XFORM;
begin
Result.eM11 := xform1.eM11 * xform2.eM11 + xform1.eM12 * xform2.eM21;
Result.eM12 := xform1.eM11 * xform2.eM12 + xform1.eM12 * xform2.eM22;
Result.eM21 := xform1.eM21 * xform2.eM11 + xform1.eM22 * xform2.eM21;
Result.eM22 := xform1.eM21 * xform2.eM12 + xform1.eM22 * xform2.eM22;
Result.eDx := xform1.eDx * xform2.eM11 + xform1.eDy * xform2.eM21 + xform2.eDx;
Result.eDy := xform1.eDx * xform2.eM12 + xform1.eDy * xform2.eM22 + xform2.eDy;
end;
procedure InitTransformation(x: PXForm; var fIntFactorX, fIntFactorY,
fIntOffsetX, fIntOffsetY: Single);
begin
if Assigned(x) then
begin
fIntFactorX := x^.eM11;
fIntFactorY := x^.eM22;
fIntOffsetX := x^.eDx;
fIntOffsetY := x^.eDy;
end
else
begin
fIntFactorX := 1;
fIntFactorY := 1;
fIntOffsetX := 0;
fIntOffsetY := 0;
end;
end;
function DefaultIdentityMatrix: XFORM;
begin
Result.eM11 := 1;
Result.eM12 := 0;
Result.eM21 := 0;
Result.eM22 := 1;
Result.eDx := 0;
Result.eDy := 0;
end;
function ScaleRect(r: TRect; fScaleX, fScaleY: Single): TRect;
begin
Result.Left := Trunc(r.Left * fScaleX);
Result.Top := Trunc(r.Top * fScaleY);
Result.Right := Trunc(r.Right * fScaleX);
Result.Bottom := Trunc(r.Bottom * fScaleY);
end;
function PrepareTransformation(fIntFactorX, fIntFactorY, fIntOffsetX,
fIntOffsetY: Single): XForm;
begin
Result.eM11 := fIntFactorX;
Result.eM12 := 0;
Result.eM21 := 0;
Result.eM22 := fIntFactorY;
Result.eDx := fIntOffsetX;
Result.eDy := fIntOffsetY;
end;
{ TPdfWrite }
function TPdfWrite.Add(c: AnsiChar): TPdfWrite;
begin
if B>=Bend then // avoid GPF
Save;
B^ := c;
inc(B);
result := self;
end;
function TPdfWrite.Add(Value: Integer): TPdfWrite;
var t: array[0..23] of AnsiChar;
P: PAnsiChar;
begin
if BEnd-B<=24 then
Save;
if Cardinal(Value)<1000 then
if Cardinal(Value)<10 then begin
B^ := AnsiChar(Value+48);
inc(B);
end else
if Cardinal(Value)<100 then begin
PWord(B)^ := TwoDigitLookupW[Value];
inc(B,2);
end else begin
PCardinal(B)^ := PCardinal(SmallUInt32UTF8[Value])^;
inc(B,3);
end
else begin
P := StrInt32(@t[23],Value);
MoveFast(P^,B^,@t[23]-P);
inc(B,@t[23]-P);
end;
result := self;
end;
function TPdfWrite.Add(const Text: RawByteString): TPdfWrite;
var L: integer;
begin
if PtrInt(Text)<>0 then begin
{$ifdef HASINLINE}
L := length(Text);
{$else}
L := PInteger(PtrInt(Text)-4)^;
{$endif}
if BEnd-B<=L then begin
Save;
inc(fDestStreamPosition,L);
fDestStream.WriteBuffer(pointer(Text)^,L);
end else begin
MoveFast(pointer(Text)^,B^,L);
inc(B,L);
end;
end;
result := self;
end;
function TPdfWrite.Add(Text: PAnsiChar; Len: integer): TPdfWrite;
begin
if BEnd-B<=Len then begin
Save;
inc(fDestStreamPosition,Len);
fDestStream.WriteBuffer(Text^,Len);
end else begin
MoveFast(Text^,B^,Len);
inc(B,Len);
end;
result := self;
end;
function TPdfWrite.Add(Value, DigitCount: Integer): TPdfWrite;
var t: array[0..15] of AnsiChar;
i64: array[0..1] of Int64 absolute t;
begin
// assert(DigitCount<high(t));
if BEnd-B<=16 then
Save;
i64[0] := $3030303030303030; // t[0..14]='0'
i64[1] := $2030303030303030; // t[15]=' '
if Value<0 then
Value := 0;
StrUInt32(@t[15],Value);
inc(DigitCount); // includes trailing t[15]=' '
MoveFast(t[16-DigitCount],B^,DigitCount);
inc(B,DigitCount);
result := self;
end;
function TPdfWrite.Add(Value: TSynExtended): TPdfWrite;
var Buffer: ShortString;
L: integer;
begin
if BEnd-B<=32 then
Save;
str(Value:0:2,Buffer);
L := ord(Buffer[0]);
if Buffer[L]='0' then
if Buffer[L-1]='0' then // '3.00' -> '3'
dec(L,3) else
dec(L); // '3.40' -> '3.4'
MoveFast(Buffer[1],B^,L);
inc(B,L);
result := self;
end;
function TPdfWrite.AddColorStr(Color: TPdfColorRGB): TPdfWrite;
var X: array[0..3] of byte absolute Color;
begin
{$ifdef MSWINDOWS}
if integer(Color)<0 then
Color := GetSysColor(Color and $ff);
{$endif}
result := AddWithSpace(X[0]/255).AddWithSpace(X[1]/255).AddWithSpace(X[2]/255);
end;
function TPdfWrite.AddEscapeContent(const Text: RawByteString): TPdfWrite;
{$ifdef USE_PDFSECURITY}
var tmp: TSynTempBuffer;
{$endif USE_PDFSECURITY}
begin
if Text<>'' then
{$ifdef USE_PDFSECURITY}
if fDoc.fEncryption<>nil then begin
tmp.Init(length(Text));
fDoc.fEncryption.EncodeBuffer(pointer(Text)^,tmp.buf^,tmp.len);
AddEscape(tmp.buf,tmp.len);
tmp.Done;
end else
{$endif USE_PDFSECURITY}
AddEscape(pointer(Text),length(Text));
result := self;
end;
function TPdfWrite.AddEscape(Text: PAnsiChar; TextLen: integer): TPdfWrite;
var TextEnd: PAnsiChar;
begin
TextEnd := Text+TextLen;
while Text<TextEnd do begin
if B>=Bend4 then
Save;
case Text^ of
'(',')','\': PWord(B)^ := ord('\')+ord(Text^)shl 8;
#0: begin
PInteger(B)^ := ord('\')+ord('0')shl 8+ord('0')shl 16+ord('0')shl 24;
inc(B,2);
end;
#8: PWord(B)^ := ord('\')+ord('b')shl 8;
#9: PWord(B)^ := ord('\')+ord('t')shl 8;
#10: PWord(B)^ := ord('\')+ord('n')shl 8;
#12: PWord(B)^ := ord('\')+ord('f')shl 8;
#13: PWord(B)^ := ord('\')+ord('r')shl 8;
else begin
B^ := Text^;
Inc(B);
Inc(Text);
continue;
end;
end;
Inc(B,2);
Inc(Text);
end;
result := self;
end;
const // should be local for better code generation
HexChars: array[0..15] of AnsiChar = '0123456789ABCDEF';
function TPdfWrite.AddEscapeName(Text: PAnsiChar): TPdfWrite;
const ESCAPENAME: set of AnsiChar =
[#1..#31,'%','(',')','<','>','[',']','{','}','/','#',#127..#255];
var c: cardinal;
begin
if Text<>nil then
repeat
if B>=Bend4 then
Save;
c := ord(Text^);
if c=0 then
break else
if AnsiChar(c) in ESCAPENAME then begin
B[0] := '#';
B[1] := HexChars[c shr 4];
B[2] := HexChars[c and $F];
inc(B,3);
Inc(Text);
end else begin
B^ := AnsiChar(c);
Inc(B);
Inc(Text);
end;
until false;
result := self;
end;
function TPdfWrite.AddEscapeText(Text: PAnsiChar; Font: TPdfFont): TPdfWrite;
begin // this function is intented to use with Tj or '
if Text<>nil then
repeat
if B>=Bend4 then
Save;
if Font<>nil then
include(Font.fWinAnsiUsed,Text^);
case Text^ of
#0: Break;
#160: begin // fixed space is written as normal space
B^ := ' ';
inc(B);
inc(Text);
end;
#40,#41,#92: begin // see PDF 2nd ed. p. 290
B[1] := Text^;
B[0] := '\';
Inc(B,2);
Inc(Text);
end;
else begin
B^ := Text^;
Inc(B);
Inc(Text);
end;
end;
until false;
result := self;
end;
function TPdfWrite.AddHex(const Bin: PDFString): TPdfWrite;
var L, Len: integer;
PW: Pointer;
begin
Len := length(Bin);
PW := pointer(Bin);
repeat
L := Len;
if BEnd-B<=L*2 then begin
Save;
if L>high(Tmp) shr 1 then
L := high(Tmp) shr 1;
end;
BinToHex(PW,B,L);
inc(PtrInt(PW),L);
inc(B,L*2);
dec(Len,L);
until Len=0;
result := self;
end;
function TPdfWrite.AddHex4(aWordValue: cardinal): TPdfWrite;
var v: Cardinal;
begin
if B>=BEnd4 then
Save;
v := aWordValue shr 8;
aWordValue := aWordValue and $ff;
B[0] := HexChars[v shr 4]; // MSB stored first (BigEndian)
B[1] := HexChars[v and $F];
B[2] := HexChars[aWordValue shr 4]; // LSB stored last (BigEndian)
B[3] := HexChars[aWordValue and $F];
inc(B,4);
result := self;
end;
procedure TPdfWrite.AddRGB(P: PAnsiChar; PInc, Count: integer);
begin
while Count>0 do begin
dec(Count);
if B>=BEnd4 then
Save;
B[0] := P[2]; // write the RGB value in expected order
B[1] := P[1];
B[2] := P[0];
inc(B,3);
inc(P,PInc);
end;
end;
function TPdfWrite.AddIso8601(DateTime: TDateTime): TPdfWrite;
begin // add e.g. '2010-06-16T15:06:59'
result := Add(DateTimeToIso8601(DateTime,true,'T'));
end;
function TPdfWrite.AddWithSpace(Value: TSynExtended): TPdfWrite;
var Buffer: ShortString;
L: integer;
begin
if BEnd-B<=32 then
Save;
// Value := Trunc(Value * 100 + 0.5) / 100; // 2 decim rounding done by str()
if Abs(Value)<1E-2 then
Add('0 ') else begin
str(Value:0:2,Buffer); // fast conversion with no temp string, using '.'
L := ord(Buffer[0]);
if Buffer[L]='0' then
if Buffer[L-1]='0' then // '3.00' -> '3 '
dec(L,2) else // '3.40' -> '3.4 '
else inc(L); // '3.45' -> '3.45 '
Buffer[L] := ' '; // append space at the end
MoveFast(Buffer[1],B^,L);
inc(B,L);
end;
result := self;
end;
function TPdfWrite.AddIntegerBin(value: integer; bytesize: cardinal): TPdfWrite;
var i: cardinal;
begin
if BEnd-B<=4 then
Save;
for i := 1 to bytesize do
B[i-1] := PAnsiChar(@value)[bytesize-i];
inc(B,bytesize);
result := self;
end;
function TPdfWrite.AddWithSpace(Value: TSynExtended; Decimals: cardinal): TPdfWrite;
var Buffer: ShortString;
L: integer;
begin
if BEnd-B<=32 then
Save;
str(Value:0:Decimals,Buffer);
L := ord(Buffer[0])+1;
Buffer[L] := ' '; // append space at the end
MoveFast(Buffer[1],B^,L);
inc(B,L);
result := self;
end;
function TPdfWrite.ToWideChar(const Ansi: PDFString; out DLen: Integer): PWideChar;
var L: integer;
begin
L := Length(Ansi)*2+2; // maximum possible length
getmem(result,L);
DLen := TSynAnsiConvert.Engine(fCodePage).AnsiBufferToUnicode(
result,pointer(Ansi),Length(Ansi))-result; // use SynCommons fast conversion
end;
function TPdfWrite.AddToUnicodeHex(const Text: PDFString): TPdfWrite;
var PBuf: pointer;
Len: integer;
begin
PBuf := ToWideChar(Text,Len);
AddUnicodeHex(PBuf,Len);
FreeMem(PBuf);
result := self;
end;
function TPdfWrite.AddUnicodeHex(PW: PWideChar; WideCharCount: integer): TPdfWrite;
procedure BinToHex4(Bin, Hex: PAnsiChar; BinWords: integer); // BigEndian order
var j, v: cardinal;
begin
for j := 1 to BinWords do begin
v := byte(Bin^);
inc(Bin);
Hex[2] := HexChars[v shr 4]; // LSB stored last (BigEndian)
Hex[3] := HexChars[v and $F];
v := byte(Bin^);
inc(Bin);
Hex[0] := HexChars[v shr 4]; // MSB stored first (BigEndian)
Hex[1] := HexChars[v and $F];
inc(Hex,4);
end;
end;
var L: Integer;
{$ifdef USE_PDFSECURITY}
sectmp: TSynTempBuffer;
{$endif USE_PDFSECURITY}
begin
if WideCharCount>0 then begin
{$ifdef USE_PDFSECURITY}
if fDoc.fEncryption<>nil then begin
sectmp.Init(WideCharCount*2);
fDoc.fEncryption.EncodeBuffer(PW^,sectmp.buf^,WideCharCount*2);
PW := sectmp.buf;
end;
{$endif USE_PDFSECURITY}
repeat
L := WideCharCount;
if BEnd-B<=L*4 then begin
Save;
if L>high(Tmp) shr 2 then
L := high(Tmp) shr 2; // max WideCharCount allowed in Tmp[]
end;
BinToHex4(pointer(PW),B,L);
inc(PtrInt(PW),L*2);
inc(B,L*4);
dec(WideCharCount,L);
until WideCharCount=0;
{$ifdef USE_PDFSECURITY}
if fDoc.fEncryption<>nil then
sectmp.Done;
{$endif USE_PDFSECURITY}
end;
result := self;
end;
function TPdfWrite.AddToUnicodeHexText(const Text: PDFString;
NextLine: boolean; Canvas: TPdfCanvas): TPdfWrite;
var PBuf: pointer;
Len: integer;
begin
PBuf := ToWideChar(Text,Len);
AddUnicodeHexText(PBuf,NextLine,Canvas);
FreeMem(PBuf);
result := self;
end;
const
SHOWTEXTCMD: array[boolean] of PDFString = (' Tj'#10,' '''#10);
/// reverse char orders for every hebrew and arabic words
procedure L2R(W: PWideChar; L: integer);
var tmp: TSynTempBuffer;
i: integer;
begin
tmp.Init(W,L*2);
dec(L);
for i := 0 to L do
W[i] := PWideChar(tmp.buf)[L-i];
end;
function PdfCoord(MM: single): integer;
begin
result := round(2.8346456693*MM);
end;
{$ifdef USE_UNISCRIBE}
function TPdfWrite.AddUnicodeHexTextUniScribe(PW: PWideChar;
WinAnsiTTF: TPdfFontTrueType; NextLine: boolean; Canvas: TPdfCanvas): boolean;
// see http://msdn.microsoft.com/en-us/library/dd317792(v=VS.85).aspx
var L, i,j: integer;
res: HRESULT;
max, count, numSp: integer;
Sp: PScriptPropertiesArray;
items: array of TScriptItem;
level: array of byte;
VisualToLogical: array of integer;
psc: pointer; // opaque Uniscribe font metric cache
complex,R2L: boolean;
glyphs: array of TScriptVisAttr;
glyphsCount: integer;
OutGlyphs, LogClust: array of word;
AScriptControl: TScriptControl;
AScriptState: TScriptState;
procedure Append(i: Integer);
// local procedure used to add glyphs from items[i] to the PDF content stream
var L: integer;
W: PWideChar;
procedure DefaultAppend;
var tmpU: array of WideChar;
begin
SetLength(tmpU,L+1); // we need the text to be ending with #0
MoveFast(W^,tmpU[0],L*2);
AddUnicodeHexTextNoUniScribe(pointer(tmpU),WinAnsiTTF,false,Canvas);
end;
begin
L := items[i+1].iCharPos-items[i].iCharPos; // length of this shapeable item
if L=0 then
exit; // nothing to append
W := PW+items[i].iCharPos;
res := ScriptShape(0,psc,W,L,max,@items[i].a,
pointer(OutGlyphs),pointer(LogClust),pointer(glyphs),glyphsCount);
case res of
E_OUTOFMEMORY: begin // max was not big enough (should never happen)
DefaultAppend;
exit;
end;
E_PENDING, USP_E_SCRIPT_NOT_IN_FONT: begin // need HDC and a selected font object
res := ScriptShape(Canvas.FDoc.GetDCWithFont(WinAnsiTTF),
psc,W,L,max,@items[i].a,
pointer(OutGlyphs),pointer(LogClust),pointer(glyphs),glyphsCount);
if res<>0 then begin // we won't change font if necessary, sorry
// we shall implement the complex technic as stated by
// http://msdn.microsoft.com/en-us/library/dd374105(v=VS.85).aspx
DefaultAppend;
exit;
end;
end;
0: ; // success -> will add glyphs just below
else exit;
end;
// add glyphs to the PDF content
// (NextLine has already been handled: not needed here)
AddGlyphs(pointer(OutGlyphs),glyphsCount,Canvas,pointer(glyphs));
end;
begin
result := false; // on UniScribe error, handle as Unicode
// 1. Breaks a Unicode string into individually shapeable items
L := StrLenW(PW)+1; // include last #0
max := L+2; // should be big enough
SetLength(items,max);
count := 0;
FillCharFast(AScriptControl, SizeOf(TScriptControl), 0);
FillCharFast(AScriptState, SizeOf(TScriptState), 0);
if ScriptApplyDigitSubstitution(nil,@AScriptControl,@AScriptState) <> 0 then
exit;
if Canvas.RightToLeftText then
AScriptState.uBidiLevel := 1;
if ScriptItemize(PW,L,max,@AScriptControl,@AScriptState,pointer(items),count) <> 0 then
exit; // error trying processing Glyph Shaping -> fast return
// 2. guess if requiring glyph shaping or layout
ScriptGetProperties(sP,numSp);
complex := false;
R2L := false;
for i := 0 to Count-2 do // don't need Count-1 = Terminator
if fComplex in sP^[items[i].a.eScript and (1 shl 10-1)]^.fFlags then
complex := true else
if fRTL in items[i].a.fFlags then
R2L := true;
if not complex and not R2L then
exit; // avoid slower UniScribe if content does not require it
// 3. get Visual Order, i.e. how to render the content from left to right
SetLength(level,count);
for i := 0 to Count-1 do
level[i] := items[i].a.s.uBidiLevel;
SetLength(VisualToLogical,count);
if ScriptLayout(Count,pointer(level),pointer(VisualToLogical),nil)<>0 then
exit;
// 4. now we have enough information to start drawing
result := true;
if NextLine then
Canvas.MoveToNextLine; // manual NextLine handling
// 5. add glyphs for all shapeable items
max := (L*3)shr 1+32; // should be big enough - allocate only once
SetLength(glyphs,max);
SetLength(OutGlyphs,max);
SetLength(LogClust,max);
psc := nil; // cached for the same character style used
// append following logical order
for j := 0 to Count-2 do // Count-2: ignore last ending item
Append(VisualToLogical[j]);
end;
{$endif}
procedure TPdfWrite.AddGlyphFromChar(Char: WideChar; Canvas: TPdfCanvas;
TTF: TPdfFontTrueType; NextLine: PBoolean);
var aChanged: boolean;
aTTF: TPdfFontTrueType;
Glyph: word;
begin
assert((TTF<>nil) and (TTF=TTF.WinAnsiFont));
aChanged := fAddGlyphFont=fNone;
Glyph := TTF.fUsedWide[TTF.FindOrAddUsedWideChar(Char)].Glyph;
with Canvas.fDoc do
if (fPDFA <> pdfaNone) and (Glyph=0) and (fFontFallBackIndex<0) then
raise Exception.Create('PDFA expects font fallback to be enabled, '+
'and the required font is not available on this system') else
if (Glyph=0) and fUseFontFallBack and (fFontFallBackIndex>=0) then begin
if fAddGlyphFont=fMain then
AddGlyphFlush(Canvas,TTF,NextLine);
fAddGlyphFont := fFallBack;
aTTF := Canvas.SetFont('',Canvas.FPage.FontSize,TTF.fStyle,-1,fFontFallBackIndex) as TPdfFontTrueType;
assert(aTTF=aTTF.WinAnsiFont);
Glyph := aTTF.fUsedWide[aTTF.FindOrAddUsedWideChar(Char)].Glyph;
end else begin
if fAddGlyphFont=fFallBack then begin
AddGlyphFlush(Canvas,TTF,NextLine);
aChanged := true;
end;
fAddGlyphFont := fMain;
aTTF := TTF;
end;
if (Canvas.FPage.Font<>aTTF.UnicodeFont) and (aTTF.UnicodeFont=nil) then
aTTF.CreateAssociatedUnicodeFont;
Canvas.SetPDFFont(aTTF.UnicodeFont,Canvas.FPage.FontSize);
if aChanged then
Add('<');
AddHex4(Glyph);
end;
procedure TPdfWrite.AddGlyphFlush(Canvas: TPdfCanvas; TTF: TPdfFontTrueType;
NextLine: PBoolean);
var aNextLine: boolean;
begin
if fAddGlyphFont=fNone then
exit;
if NextLine=nil then
aNextLine := false else begin
aNextLine := NextLine^;
NextLine^ := false; // MoveToNextLine only once
end;
fAddGlyphFont := fNone;
Add('>').Add(SHOWTEXTCMD[aNextLine]);
end;
procedure TPdfWrite.AddUnicodeHexTextNoUniScribe(PW: PWideChar;
TTF: TPdfFontTrueType; NextLine: boolean; Canvas: TPdfCanvas);
var Ansi: integer;
isSymbolFont: Boolean;
begin
if TTF<>nil then begin
if TTF.UnicodeFont<>nil then
isSymbolFont := TTF.UnicodeFont.fIsSymbolFont else
isSymbolFont := TTF.fIsSymbolFont;
TTF := TTF.WinAnsiFont; // we expect the WinAnsi font in the code below
end else
isSymbolFont := false;
Ansi := WideCharToWinAnsi(cardinal(PW^));
if (TTF=nil) and (Ansi<0) then
Ansi := ord('?'); // WinAnsi only font shows ? glyph for unicode chars
while Ansi<>0 do begin
if (Ansi>0) and not isSymbolFont then begin
// add WinAnsi-encoded chars as such
if (TTF<>nil) and (Canvas.FPage.Font<>TTF) then
Canvas.SetPDFFont(TTF,Canvas.FPage.FontSize);
Add('(');
repeat
case Ansi of
40,41,92: Add('\'); // see PDF 2nd ed. p. 290
160: Ansi := 32; // fixed space is written as normal space
end;
TTF.AddUsedWinAnsiChar(AnsiChar(Ansi));
Add(AnsiChar(Ansi));
Inc(PW);
Ansi := WideCharToWinAnsi(cardinal(PW^));
if (TTF=nil) and (Ansi<0) then
Ansi := ord('?'); // WinAnsi only font shows ? glyph for unicode chars
until Ansi<=0;
Add(')').Add(SHOWTEXTCMD[NextLine]);
NextLine := false; // MoveToNextLine only once
end;
if Ansi=0 then
break;
// here we know that PW^ is not a Win-Ansi glyph, and that TTF exists
repeat
AddGlyphFromChar(PW^,Canvas,TTF,@NextLine);
inc(PW);
Ansi := WideCharToWinAnsi(cardinal(PW^));
if Ansi=160 then
Ansi := 32;
if Ansi=32 then
if WideCharToWinAnsi(cardinal(PW[1]))<0 then
continue; // we allow one space inside Unicode text
until Ansi>=0;
AddGlyphFlush(Canvas,TTF,@NextLine);
end;
end;
function TPdfWrite.AddUnicodeHexText(PW: PWideChar; NextLine: boolean;
Canvas: TPdfCanvas): TPdfWrite;
var TTF: TPdfFontTrueType;
begin
if PW<>nil then begin
with Canvas.FPage do
if FFont.FTrueTypeFontsIndex=0 then
TTF := nil else // mark we don't have an Unicode font, i.e. a TTF
TTF := TPdfFontTrueType(FFont);
{$ifdef USE_UNISCRIBE}
// use the Windows Uniscribe API if required
if not Canvas.fDoc.UseUniScribe or (TTF=nil) or
not AddUnicodeHexTextUniScribe(PW,TTF.WinAnsiFont,NextLine,Canvas) then
{$endif}
// fastest version, without Ordering and/or Shaping of the text
AddUnicodeHexTextNoUniScribe(PW,TTF,NextLine,Canvas);
end;
result := self;
end;
function TPdfWrite.AddGlyphs(Glyphs: PWord; GlyphsCount: integer;
Canvas: TPdfCanvas; AVisAttrsPtr: Pointer): TPdfWrite;
var TTF: TPdfFontTrueType;
first: boolean;
glyph: integer;
{$ifdef USE_UNISCRIBE}
AVisAttrs: PScriptVisAttr;
{$endif}
begin
if (Glyphs<>nil) and (GlyphsCount>0) then begin
with Canvas.FPage do
if FFont.FTrueTypeFontsIndex=0 then
TTF := nil else // mark we don't have an Unicode font, i.e. a TTF
TTF := TPdfFontTrueType(FFont);
if TTF<>nil then begin // we need a TTF font
if (Canvas.FPage.Font<>TTF.UnicodeFont) and (TTF.UnicodeFont=nil) then
TTF.CreateAssociatedUnicodeFont;
Canvas.SetPDFFont(TTF.UnicodeFont,Canvas.FPage.FontSize);
first := true;
{$ifdef USE_UNISCRIBE}
AVisAttrs := AVisAttrsPtr;
{$endif}
while GlyphsCount>0 do begin
{$ifdef USE_UNISCRIBE}
if (AVisAttrs=nil) or
not(AVisAttrs^.fFlags*[fDiacritic,fZeroWidth]=[fZeroWidth]) then
{$endif} begin
glyph := TTF.WinAnsiFont.GetAndMarkGlyphAsUsed(Glyphs^);
// this font shall by definition contain all needed glyphs
// -> no Font Fallback is to be implemented here
if first then begin
first := false;
Add('<');
end;
AddHex4(glyph);
end;
inc(Glyphs);
dec(GlyphsCount);
{$ifdef USE_UNISCRIBE}
if AVisAttrs<>nil then
inc(AVisAttrs);
{$endif}
end;
if not first then
Add('> Tj'#10);
end;
end;
result := self;
end;
function TPdfWrite.AddWithSpace(Value: Integer): TPdfWrite;
var t: array[0..25] of AnsiChar;
P: PAnsiChar;
L: integer;
begin
if BEnd-B<=16 then
Save;
if Cardinal(Value)<1000 then
if Cardinal(Value)<10 then begin
PWord(B)^ := Value+(48+32 shl 8);
inc(B,2);
end else
if Cardinal(Value)<100 then begin
PCardinal(B)^ := TwoDigitLookupW[Value]+32 shl 16;
inc(B,3);
end else begin
PCardinal(B)^ := PCardinal(SmallUInt32UTF8[Value])^+32 shl 24;
inc(B,4);
end
else begin
t[24] := ' ';
P := StrInt32(@t[24],Value);
L := @t[25]-P;
MoveFast(P^,B^,L);
inc(B,@t[25]-P);
end;
result := self;
end;
constructor TPdfWrite.Create(Destination: TPdfDocument; DestStream: TStream);
begin
fDoc := Destination;
fDestStream := DestStream;
fDestStreamPosition := fDestStream.Seek(0,soCurrent);
fCodePage := fDoc.CodePage;
B := @Tmp;
Bend := B+high(Tmp);
Bend4 := Bend-4;
end;
function TPdfWrite.Position: Integer;
begin
result := fDestStreamPosition+B-@Tmp;
end;
procedure TPdfWrite.Save;
var L: integer;
begin
L := B-@Tmp;
inc(fDestStreamPosition,L);
fDestStream.WriteBuffer(Tmp,L);
B := @Tmp;
end;
function TPdfWrite.ToPDFString: PDFString;
begin
if fDestStreamPosition=0 then
// we remained in the internal buffer -> not necessary to use stream
SetString(Result,Tmp,B-@Tmp) else begin
// we used the stream -> flush remaining, and get whole data at once
Save;
result := '';
SetLength(result,fDestStreamPosition);
fDestStream.Seek(0,soBeginning);
fDestStream.Read(pointer(result)^,fDestStreamPosition);
end;
end;
{ Utility functions }
const
PDF_PAGE_LAYOUT_NAMES: array[TPdfPageLayout] of PDFString = (
'SinglePage', 'OneColumn', 'TwoColumnLeft', 'TwoColumnRight');
PDF_PAGE_MODE_NAMES: array[TPdfPageMode] of PDFString = (
'UseNone', 'UseOutlines', 'UseThumbs', 'FullScreen');
PDF_ANNOTATION_TYPE_NAMES: array[0..12] of PDFString = (
'Text', 'Link', 'Sound', 'FreeText', 'Stamp', 'Square', 'Circle',
'StrikeOut', 'Highlight', 'Underline', 'Ink', 'FileAttachment', 'Popup');
PDF_DESTINATION_TYPE_NAMES: array[TPdfDestinationType] of PDFString = (
'XYZ', 'Fit', 'FitH', 'FitV', 'FitR', 'FitB', 'FitBH', 'FitBV');
procedure _Pages_AddKids(AParent: TPdfDictionary; AKid: TPdfDictionary);
var FKids: TPdfArray;
begin
// adding page object to the parent pages object.
FKids := AParent.PdfArrayByName('Kids');
FKids.AddItem(AKid);
AParent.PdfNumberByName('Count').Value := FKids.ItemCount;
end;
{ TPdfTrailer }
constructor TPdfTrailer.Create(AObjectMgr: TPdfObjectMgr);
begin
inherited Create;
FAttributes := TPdfDictionary.Create(AObjectMgr);
FAttributes.AddItem('Size', TPdfNumber.Create(0));
end;
procedure TPdfTrailer.WriteTo(var W: TPdfWrite);
type TXRefType = (xrefFree, xrefInUse, xrefInUseCompressed);
const TYPEWIDTH = 1;
var offsetWidth,genWidth, i: integer;
WR: TPdfWrite;
begin
if FCrossReference=nil then begin
W.Add('trailer' + CRLF);
FAttributes.WriteTo(W);
end else begin
if FXrefAddress<60000 then
offsetWidth := 2 else
if FXrefAddress<13421772 then
offsetWidth := 3 else
offsetWidth := 4;
if (FObjectStream=nil) or (FObjectStream.ObjectCount<255) then
genWidth := 1 else
if FObjectStream.ObjectCount<65535 then
genWidth := 2 else
genWidth := 3;
FAttributes.AddItem('W',TPdfArray.Create(nil,[TYPEWIDTH,offsetWidth,genWidth]));
WR := FCrossReference.Writer;
WR.AddIntegerBin(ord(xrefFree),TYPEWIDTH).
AddIntegerBin(0,offsetWidth).
AddIntegerBin(-1,genWidth);
for i := 1 to FXRef.ItemCount-1 do
with FXRef.Items[i] do begin
if ObjectStreamIndex>=0 then begin
assert(FObjectStream<>nil);
WR.AddIntegerBin(ord(xrefInUseCompressed),TYPEWIDTH);
WR.AddIntegerBin(FObjectStream.ObjectNumber,offsetWidth);
WR.AddIntegerBin(ObjectStreamIndex,genWidth);
end else begin
WR.AddIntegerBin(ord(xrefInUse),TYPEWIDTH);
WR.AddIntegerBin(ByteOffset,offsetWidth);
WR.AddIntegerBin(GenerationNumber,genWidth);
end;
end;
FCrossReference.WriteValueTo(W);
end;
W.Add(CRLF + 'startxref' + CRLF).Add(FXrefAddress).
Add(CRLF + '%%EOF' + CRLF);
end;
destructor TPdfTrailer.Destroy;
begin
FAttributes.Free;
inherited;
end;
procedure TPdfTrailer.ToCrossReference(Doc: TPdfDocument);
var i: integer;
{$ifdef USE_PDFSECURITY}
Enc: TPdfEncryption;
{$endif USE_PDFSECURITY}
begin
FXRef := Doc.FXref;
FCrossReference := TPdfStream.Create(Doc);
FCrossReference.FSecondaryAttributes := FAttributes;
FAttributes.AddItem('Type','XRef');
{$ifdef USE_PDFSECURITY}
FCrossReference.FDoNotEncrypt := true;
if Doc.fEncryption<>nil then
exit; // still a bug with encryption + objectstream
{$endif USE_PDFSECURITY}
FObjectStream := TPdfObjectStream.Create(Doc);
{$ifdef USE_PDFSECURITY}
FObjectStream.FDoNotEncrypt := true;
Enc := Doc.fEncryption;
try
Doc.fEncryption := nil; // force /ObjStm content not encrypted
{$endif USE_PDFSECURITY}
for i := 1 to FXRef.ItemCount-1 do
with FXRef.Items[i] do
if (ByteOffset<=0) and Value.InheritsFrom(TPdfDictionary) then begin
FByteOffset := maxInt; // mark already handlded
FObjectStreamIndex := FObjectStream.AddObject(Value);
end;
{$ifdef USE_PDFSECURITY}
finally
Doc.fEncryption := Enc;
end;
{$endif USE_PDFSECURITY}
end;
{ TPdfXrefEntry }
constructor TPdfXrefEntry.Create(AValue: TPdfObject);
begin
FByteOffset := -1;
FObjectStreamIndex := -1;
if AValue<>nil then begin
FEntryType := PDF_IN_USE_ENTRY;
FGenerationNumber := AValue.GenerationNumber;
FValue := AValue;
end else
FEntryType := PDF_FREE_ENTRY;
end;
destructor TPdfXrefEntry.Destroy;
begin
if FEntryType=PDF_IN_USE_ENTRY then
FValue.Free;
inherited;
end;
procedure TPdfXrefEntry.SaveToPdfWrite(var W: TPdfWrite);
begin
W.Add(FByteOffset,10).Add(FGenerationNumber,5).Add(FEntryType).Add(' '#10);
end;
{ TPdfXref }
constructor TPdfXref.Create;
var RootEntry: TPdfXrefEntry;
begin
FXrefEntries := TList.Create;
// create first a void PDF_FREE_ENTRY as root
RootEntry := TPdfXrefEntry.Create(nil);
RootEntry.GenerationNumber := PDF_MAX_GENERATION_NUM;
FXrefEntries.Add(RootEntry);
end;
destructor TPdfXref.Destroy;
var i: integer;
begin
for i := 0 to FXrefEntries.Count-1 do
GetItem(i).Free;
FXrefEntries.Free;
inherited;
end;
procedure TPdfXref.AddObject(AObject: TPdfObject);
var ObjectNumber: integer;
XrefEntry: TPdfXrefEntry;
begin
if (AObject=nil) or (AObject.ObjectType<>otDirectObject) then
raise EPdfInvalidOperation.Create('AddObject');
XrefEntry := TPdfXrefEntry.Create(AObject);
ObjectNumber := FXrefEntries.Add(XrefEntry);
AObject.SetObjectNumber(ObjectNumber);
end;
function TPdfXref.GetItem(ObjectID: integer): TPdfXrefEntry;
begin
Result := TPdfXrefEntry(FXrefEntries[ObjectID]);
end;
function TPdfXref.GetItemCount: integer;
begin
Result := FXrefEntries.Count;
end;
function TPdfXref.GetObject(ObjectID: integer): TPdfObject;
begin
if cardinal(ObjectID)<cardinal(FXrefEntries.Count) then
result := TPdfXrefEntry(FXrefEntries.List[ObjectID]).Value else
result := nil;
end;
procedure TPdfXref.WriteTo(var W: TPdfWrite);
var i: integer;
begin
W.Add('xref' + CRLF + '0 ').Add(FXrefEntries.Count).Add(#10);
for i := 0 to FXrefEntries.Count- 1 do
Items[i].SaveToPdfWrite(W);
end;
{ TPdfDocument }
function EnumFontsProcW(var LogFont: TLogFontW; var TextMetric: TTextMetric;
FontType: Integer; var List: TRawUTF8DynArray): Integer; stdcall;
// we enumerate all available fonts, whatever the charset is, because
// we may won't enumerate Arial or Times New Roman if current FCharSet is
// chinese e.g.
var Temp: RawUTF8;
begin
with LogFont do
if ((FontType=DEVICE_FONTTYPE) or (FontType=TRUETYPE_FONTTYPE)) and (lfFaceName[0]<>'@') then begin
Temp := RawUnicodeToUtf8(lfFaceName,StrLenW(lfFaceName));
if (pointer(List)=nil) or (List[high(List)]<>Temp) then
AddRawUTF8(List,Temp,true,true);
end;
Result := 1;
end;
function LCIDToCodePage(ALcid: LCID): Integer;
var Buffer: array [0..6] of Char;
begin
GetLocaleInfo(ALcid, LOCALE_IDEFAULTANSICODEPAGE, Buffer, SizeOf(Buffer));
Result:= StrToIntDef(Buffer, GetACP);
end;
constructor TPdfDocument.Create(AUseOutlines: Boolean; ACodePage: integer;
{$ifdef USE_PDFALEVEL]}APDFA: TPdfALevel{$else}APDFA1: boolean{$endif}
{$ifdef USE_PDFSECURITY}; AEncryption: TPdfEncryption{$endif});
var LFont: TLogFontW; // TLogFontW to add to FTrueTypeFonts array as UTF-8
i: integer;
begin
{$ifdef USE_PDFALEVEL]}
fPDFA:=APDFA;
{$else}
if APDFA1 then
fPDFA := pdfa1B else
fPDFA := pdfaNone;
{$endif USE_PDFALEVEL}
{$ifdef USE_PDFSECURITY}
fEncryption := AEncryption;
{$endif USE_PDFSECURITY}
fTPdfPageClass := TPdfPage;
if ACodePage=0 then
FCodePage := LCIDToCodePage(SysLocale.DefaultLCID) else // GetACP can be<>SysLocale
FCodePage := ACodePage;
FCharSet := CodePageToCharSet(FCodePage);
DefaultPaperSize := psA4;
fRawPages := TList.Create;
// retrieve the current reference GDI parameters
FDC := CreateCompatibleDC(0);
FScreenLogPixels := GetDeviceCaps(FDC, LOGPIXELSY);
FCanvas := TPdfCanvas.Create(Self); // need FScreenLogPixels
// retrieve true type fonts available for all charsets
FillCharFast(LFont, sizeof(LFont), 0);
LFont.lfCharset := DEFAULT_CHARSET; // enumerate ALL fonts
EnumFontFamiliesExW(FDC, LFont, @EnumFontsProcW, PtrInt(@FTrueTypeFonts), 0);
QuickSortRawUTF8(FTrueTypeFonts,length(FTrueTypeFonts),nil,@StrIComp);
FCompressionMethod := cmFlateDecode; // deflate by default
fBookMarks := TRawUTF8List.Create([fCaseSensitive,fNoDuplicate]);
fMissingBookmarks := TRawUTF8List.Create;
FUseOutlines := AUseOutlines;
fUseFontFallBack := true;
fFontFallBackIndex := GetTrueTypeFontIndex('Arial Unicode MS');
if fFontFallBackIndex<0 then
for i := 0 to high(FTrueTypeFonts) do
if PosEx('Unicode',FTrueTypeFonts[i])>0 then begin
fFontFallBackIndex := i;
break;
end;
NewDoc;
end;
function TPdfDocument.GetInfo: TPdfInfo;
begin
if FInfo=nil then
CreateInfo;
Result := FInfo;
end;
function TPdfDocument.GetOutlineRoot: TPdfOutlineRoot;
begin
if not UseOutlines then
RaiseInvalidOperation;
Result := FOutlineRoot;
end;
destructor TPdfDocument.Destroy;
begin
FreeDoc;
FCanvas.Free;
if fSelectedDCFontOld<>0 then
SelectObject(FDC,fSelectedDCFontOld);
DeleteDC(FDC);
FEmbeddedTTFIgnore.Free;
fRawPages.Free;
fBookMarks.Free;
fMissingBookmarks.Free;
inherited;
{$ifdef USE_PDFSECURITY}
fEncryption.Free;
{$endif USE_PDFSECURITY}
end;
function TPdfDocument.RegisterXObject(AObject: TPdfXObject; const AName: PDFString): integer;
begin
// check object and register it
if AObject=nil then
raise EPdfInvalidValue.Create('RegisterXObject: no AObject');
if AObject.Attributes.TypeOf<>'XObject' then
raise EPdfInvalidValue.Create('RegisterXObject: no XObject');
if AObject.ObjectType<>otIndirectObject then
FXref.AddObject(AObject);
if AObject.Attributes.ValueByName('Name')=nil then begin
if GetXObject(AName)<>nil then
raise EPdfInvalidValue.Createfmt('RegisterXObject: dup name %s', [AName]);
result := FXObjectList.AddItem(AObject);
AObject.Attributes.AddItem('Name', AName);
end else
result := -1;
end;
const
PDF_PRODUCER = 'Synopse PDF engine '+SYNOPSE_FRAMEWORK_VERSION;
procedure TPdfDocument.CreateInfo;
var FInfoDictionary: TPdfDictionary;
begin
FInfoDictionary := TPdfDictionary.Create(FXref);
FXref.AddObject(FInfoDictionary);
FInfoDictionary.AddItemText('Producer', PDF_PRODUCER);
FTrailer.Attributes.AddItem('Info', FInfoDictionary);
FInfo := TPdfInfo.Create;
FInfo.SetData(FInfoDictionary);
FObjectList.Add(FInfo);
end;
function TPdfDocument.CreatePages(Parent: TPdfDictionary): TPdfDictionary;
begin
// create pages object and register to xref.
result := TPdfDictionary.Create(FXref);
result.FSaveAtTheEnd := True;
FXref.AddObject(Result);
with result do begin
AddItem('Type', 'Pages');
AddItem('Kids', TPdfArray.Create(FXref));
AddItem('Count', 0);
end;
if (Parent<>nil) and (Parent.TypeOf='Pages') then
_Pages_AddKids(Parent, result) else
FRoot.Pages := Result;
end;
function TPdfDocument.GetXObjectIndex(const AName: PDFString): integer;
begin
for result := 0 to FXObjectList.ItemCount-1 do
with TPdfXObject(FXObjectList.FArray.List[result]) do
if (FObjectType<>otVirtualObject) and (Attributes<>nil) and
(TPdfName(Attributes.ValueByName('Name')).Value=AName) then
exit;
result := -1;
end;
function TPdfDocument.GetXObject(const AName: PDFString): TPdfXObject;
var i: integer;
begin
for i := 0 to FXObjectList.ItemCount-1 do begin
result := TPdfXObject(FXObjectList.FArray.List[i]);
if result.FObjectType=otVirtualObject then begin
result := TPdfXObject(FXRef.GetObject(result.FObjectNumber));
if (result=nil) or not result.InheritsFrom(TPdfXObject) then
continue;
end;
if result.Attributes<>nil then
if TPdfName(result.Attributes.ValueByName('Name')).Value=AName then
exit;
end;
Result := nil;
end;
{$ifdef USE_BITMAP}
function TPdfDocument.GetXObjectImageName(const Hash: THash128Rec;
Width, Height: Integer): PDFString;
var Obj: TPdfXObject;
Img: TPdfImage absolute Obj;
i: integer;
begin
for i := 0 to FXObjectList.ItemCount-1 do begin
Obj := TPdfXObject(FXObjectList.FArray.List[i]);
if Obj.FObjectType=otVirtualObject then
Obj := TPdfXObject(FXRef.GetObject(Obj.FObjectNumber));
if (Obj<>nil) and Obj.InheritsFrom(TPdfImage) and
(Img.PixelWidth=Width) and (Img.PixelHeight=Height) and
not IsZero(Img.fHash.b) and IsEqual(Img.fHash.b,Hash.b) and
(Obj.Attributes<>nil) then begin
result := TPdfName(Obj.Attributes.ValueByName('Name')).Value;
if result<>'' then
exit;
end;
end;
result := '';
end;
{$endif USE_BITMAP}
function TPdfDocument.CreateAnnotation(AType: TPdfAnnotationSubType;
const ARect: TPdfRect; BorderStyle: TPdfAnnotationBorder; BorderWidth: integer): TPdfDictionary;
var FAnnotation, FBorderStyle: TPdfDictionary;
aArray: TPdfArray;
aPage: TPdfPage;
const FLAGS_PRINT = 4;
BS: array [TPdfAnnotationBorder] of PDFString = ('S','D','B','I','U');
begin
// create new annotation and set the properties
FAnnotation := TPdfDictionary.Create(FXref);
FAnnotation.FSaveAtTheEnd := true;
FXref.AddObject(FAnnotation);
FAnnotation.AddItem('Type', 'Annot');
FAnnotation.AddItem('Subtype', PDF_ANNOTATION_TYPE_NAMES[ord(AType)]);
FAnnotation.AddItem('F',FLAGS_PRINT);
if (BorderStyle<>abSolid) or (BorderWidth<>1) then begin
FBorderStyle := TPdfDictionary.Create(FXRef);
if BorderStyle<>abSolid then
FBorderStyle.AddItem('S',BS[BorderStyle]);
if BorderWidth<>1 then
FBorderStyle.AddItem('W',BorderWidth);
FAnnotation.AddItem('BS',FBorderStyle);
end;
with ARect do
FAnnotation.AddItem('Rect',TPdfArray.CreateReals(FXRef,[Left,Top,Right,Bottom]));
// adding annotation to the current page
aPage := FCanvas.Page;
aArray := aPage.PdfArrayByName('Annots');
if aArray=nil then begin
aArray := TPdfArray.Create(FXRef);
aPage.AddItem('Annots', aArray);
end;
aArray.AddItem(FAnnotation);
Result := FAnnotation;
end;
function TPdfDocument.CreateLink(const ARect: TPdfRect; const aBookmarkName: RawUTF8;
BorderStyle: TPdfAnnotationBorder; BorderWidth: integer): TPdfDictionary;
var aDest: TPdfDestination;
begin
result := CreateAnnotation(asLink,ARect,BorderStyle,BorderWidth);
aDest := fBookmarks.GetObjectFrom(aBookmarkName);
if aDest=nil then
fMissingBookmarks.AddObject(aBookmarkName,result) else
result.AddItem('Dest',aDest.GetValue);
end;
function TPdfDocument.CreateHyperLink(const ARect: TPdfRect; const url : RawUTF8;
BorderStyle: TPdfAnnotationBorder; BorderWidth: integer): TPdfDictionary;
var aURIObj: TPdfDictionary;
begin
result := CreateAnnotation(asLink,ARect,BorderStyle,BorderWidth);
aURIObj := TPdfDictionary.Create(FXref);
aURIObj.FSaveAtTheEnd := true;
aURIObj.AddItem('S', 'URI');
aURIObj.AddItemTextUTF8('URI', url);
FXref.AddObject(aURIObj);
Result.AddItem('A', aURIObj);
end;
function TPdfDocument.CreateDestination: TPdfDestination;
begin
Result := TPdfDestination.Create(Self);
FObjectList.Add(Result);
end;
procedure TPdfDocument.CreateBookMark(TopPosition: Single; const aBookmarkName: RawUTF8);
var aDest: TPdfDestination;
i: integer;
begin
if Canvas.FPage=nil then
RaiseInvalidOperation; // we need a page to refer to
if (aBookmarkName='') or (fBookMarks.IndexOf(aBookmarkName)>=0) then
raise EPdfInvalidValue.CreateFmt('Duplicated or void bookmark name "%s"',[aBookmarkName]);
aDest := CreateDestination;
aDest.DestinationType := dtXYZ;
aDest.Zoom := 0; // will leave Zoom factor unchanged
aDest.Left := 0; // go to left side of the page
aDest.Top := Round(TopPosition);
fBookMarks.AddObject(aBookmarkName,aDest);
with fMissingBookmarks do
for i := Count-1 downto 0 do
if Strings[i]=aBookmarkName then begin
TPdfDictionary(Objects[i]).AddItem('Dest',aDest.GetValue);
Delete(i);
end;
end;
procedure TPdfDocument.NewDoc;
var CatalogDictionary: TPdfDictionary;
Dico: TPdfDictionary;
DicoD: TPdfDictionary;
RGB: TPdfStream;
ID: TPdfArray;
IDs: PDFString;
i: integer;
NeedFileID: boolean;
FileID: array[0..3] of cardinal;
{$ifndef USE_PDFSECURITY}
P: PAnsiChar;
{$endif USE_PDFSECURITY}
const
ICC: array[0..139] of cardinal = (
805437440,1161970753,4098,1920233069,541214546,542792024,134270983,318769920,989868800,
1886610273,1280331841,0,1701736302,0,0,0,0,3606446080,256,768802816,1161970753,
0,0,0,0,0,0,0,0,0,0,0,167772160,1953656931,4227858432,838860800,1668506980,805371904,
1795162112,1953526903,2617311232,335544320,1953524578,2952855552,335544320,1129469042,
3288399872,234881024,1129469031,3556835328,234881024,1129469026,3825270784,234881024,
1515804786,4093706240,335544320,1515804775,134348800,335544320,1515804770,469893120,
335544320,1954047348,0,2037411651,1751607666,808591476,1092628528,1700949860,1937330976,
1936549236,1668172064,1869640303,1702125938,100,1668506980,0,285212672,1651467329,
1196564581,824713282,691550521,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,542792024,0,
1374879744,256,3423994112,542792024,0,0,0,0,1987212643,0,16777216,13058,1987212643,0,
16777216,13058,1987212643,0,16777216,13058,542792024,0,412876800,2773417984,4228120576,
542792024,0,2368995328,748683264,2500788224,542792024,0,824573952,789577728,2629697536);
begin
fLastOutline := nil;
FreeDoc;
FXref := TPdfXref.Create;
FTrailer := TPdfTrailer.Create(FXref);
FFontList := TList.Create;
FXObjectList := TPdfArray.Create(FXref);
FXObjectList.FSaveAtTheEnd := true;
FObjectList := TList.Create;
FRoot := TPdfCatalog.Create;
FRoot.FOwner := self;
CatalogDictionary := TPdfDictionary.Create(FXref);
FXref.AddObject(CatalogDictionary);
CatalogDictionary.AddItem('Type', 'Catalog');
FTrailer.Attributes.AddItem('Root', CatalogDictionary);
FRoot.SetData(CatalogDictionary);
FRoot.PageLayout := plSinglePage;
FObjectList.Add(FRoot);
if UseOutlines then begin
FOutlineRoot := TPdfOutlineRoot.Create(Self);
FRoot.Data.AddItem('Outlines', FOutlineRoot.Data);
end;
CreateInfo;
FInfo.CreationDate := now;
FCurrentPages := CreatePages(nil); // nil -> create root Pages XObject
FRoot.SetPages(FCurrentPages);
NeedFileID := false;
if FUseOptionalContent then begin
if fFileFormat<pdf15 then
fFileFormat := pdf15;
Dico := TPdfDictionary.Create(FXRef);
DicoD := TPdfDictionary.Create(FXRef);
DicoD.AddItem('BaseState','ON'); // must be ON in default configuration
DicoD.AddItem('OFF',TPDFArray.Create(FXRef));
DicoD.AddItem('Order',TPDFArray.Create(FXRef));
DicoD.AddItem('ListMode','AllPages'); // default value but some viewers cause trouble when missing
DicoD.AddItem('RBGroups',TPDFArray.Create(FXRef));
Dico.AddItem('D',DicoD);
Dico.AddItem('OCGs',TPdfArray.Create(FXRef));
FRoot.Data.AddItem('OCProperties',Dico);
end;
{$ifdef USE_PDFSECURITY}
if fEncryption<>nil then
NeedFileID := true;
{$endif USE_PDFSECURITY}
if PDFA<>pdfaNone then begin
if fFileFormat<pdf14 then
fFileFormat := pdf14;
{$ifdef USE_PDFSECURITY}
if fEncryption<>nil then
raise EPdfInvalidOperation.Create('PDF/A not allowed when encryption is enabled');
{$endif USE_PDFSECURITY}
fUseFontFallBack := true;
FOutputIntents := TPdfArray.Create(FXref);
Dico := TPdfDictionary.Create(FXRef);
Dico.AddItem('Type','OutputIntent');
Dico.AddItem('S','GTS_PDFA1'); // there is no definition GTS_PDFA2 or GTS_PDFA3
Dico.AddItemText('OutputConditionIdentifier','sRGB');
Dico.AddItemText('RegistryName','http://www.color.org');
RGB := TPdfStream.Create(self);
RGB.Attributes.AddItem('N',3);
RGB.Writer.Add(@ICC,sizeof(ICC));
Dico.AddItem('DestOutputProfile',RGB);
FOutputIntents.AddItem(Dico);
CatalogDictionary.AddItem('OutputIntents',FOutputIntents);
FMetaData := TPdfStream.Create(Self);
FMetaData.Attributes.AddItem('Subtype','XML');
FMetaData.Attributes.AddItem('Type','Metadata');
FMetaData.FFilter := '';
CatalogDictionary.AddItem('MarkInfo',TPdfRawText.Create('<</Marked true>>'));
CatalogDictionary.AddItem('Metadata',FMetadata);
FStructTree := TPdfDictionary.Create(FXRef);
FRoot.Data.AddItem('StructTreeRoot',FStructTree);
NeedFileID := true;
end;
if NeedFileID then begin
Randomize;
for i := 0 to high(FileID) do
FileID[i] := cardinal(Random(MaxInt));
inc(FileID[0],GetTickCount);
{$ifdef USE_PDFSECURITY}
fFileID := MD5Buf(FileID[0],16);
IDs := '<'+RawByteString(MD5DigestToString(fFileID))+'>';
{$else}
SetLength(IDs,34);
P := pointer(IDs);
P[0] := '<';
SynCommons.BinToHex(PAnsiChar(@FileID[0]),P+1,16);
P[33] := '>';
{$endif USE_PDFSECURITY}
ID := TPdfArray.Create(FXref);
ID.AddItem(TPdfRawText.Create(IDs));
ID.AddItem(TPdfRawText.Create(IDs));
FTrailer.Attributes.AddItem('ID',ID);
end;
{$ifdef USE_PDFSECURITY}
if fEncryption<>nil then
fEncryption.AttachDocument(self);
{$endif USE_PDFSECURITY}
end;
function TPdfDocument.AddXObject(const AName: PDFString; AXObject: TPdfXObject): integer;
begin
if GetXObject(AName)<>nil then
raise EPdfInvalidValue.CreateFmt('AddXObject: dup name %s', [AName]);
// check whether AImage is valid PdfImage or not.
if (AXObject=nil) or (AXObject.Attributes=nil) or
(AXObject.Attributes.TypeOf<>'XObject') or
(AXObject.Attributes.PdfNameByName('Subtype')=nil) then
raise EPdfInvalidValue.CreateFmt('AddXObject: invalid TPdfImage %s', [AName]);
FXref.AddObject(AXObject);
result := RegisterXObject(AXObject, AName);
end;
function TPdfDocument.AddPage: TPdfPage;
var FResources: TPdfDictionary;
begin
if FCurrentPages=nil then
raise EPdfInvalidOperation.Create('AddPage');
// create a new page object and add it to the current pages dictionary
result := fTPdfPageClass.Create(self);
FXref.AddObject(result);
fRawPages.Add(result); // pages may be nested
_Pages_AddKids(FCurrentPages, result);
result.AddItem('Type', 'Page');
result.AddItem('Parent', FCurrentPages);
// create page resources
FResources := TPdfDictionary.Create(FXref);
result.AddItem('Resources',FResources);
FResources.AddItem('Font',TPdfDictionary.Create(FXref));
FResources.AddItem('XObject',TPdfDictionary.Create(FXref));
// create page content
FResources.AddItem('ProcSet',TPdfArray.CreateNames(FXref,['PDF','Text','ImageC']));
result.AddItem('Contents',TPdfStream.Create(self));
// assign this page to the current PDF canvas
FCanvas.SetPage(result);
end;
function TPdfDocument.AddTrueTypeFont(const TTFName: RawUtf8): boolean;
begin
result := GetTrueTypeFontIndex(TTFName) < 0;
if not result then
exit;
AddRawUTF8(FTrueTypeFonts,TTFName);
QuickSortRawUTF8(FTrueTypeFonts,length(FTrueTypeFonts),nil,@StrIComp);
end;
procedure TPdfDocument.FreeDoc;
var i: integer;
begin
if FXObjectList<>nil then begin
FreeAndNil(FXObjectList);
for i := FFontList.Count-1 downto 0 do
TObject(FFontList.List[i]).Free;
FreeAndNil(FFontList);
for i := FObjectList.Count-1 downto 0 do
TObject(FObjectList.List[i]).Free;
FreeAndNil(FObjectList);
FreeAndNil(FXref);
FreeAndNil(FTrailer);
end;
end;
procedure TPdfDocument.SaveToStream(AStream: TStream; ForceModDate: TDateTime=0);
begin
if FCanvas.Page=nil then
RaiseInvalidOperation;
SaveToStreamDirectBegin(AStream,ForceModDate);
SaveToStreamDirectEnd;
end;
procedure TPdfDocument.RaiseInvalidOperation;
begin
raise EPdfInvalidOperation.Create('TPdfDocument.Document is null');
end;
function TPdfDocument.SaveToFile(const aFileName: TFileName): boolean;
var FS: TFileStream;
begin
try
FS := TFileStream.Create(aFileName,fmCreate);
try
SaveToStream(FS);
result := true;
finally
FS.Free;
end;
except
on E: Exception do // error on file creation (opened in reader?)
result := false;
end;
end;
procedure TPdfDocument.SaveToStreamDirectBegin(AStream: TStream;
ForceModDate: TDateTime);
const PDFHEADER: array[TPdfFileFormat] of PDFString = (
'%PDF-1.3'#10, '%PDF-1.4'#10'%'#228#229#230#240#10,
'%PDF-1.5'#10'%'#241#242#243#244#10, '%PDF-1.6'#10'%'#245#246#247#248#10);
const PDFAPART: array[TPdfALevel] of PDFString = ('', '1', '1', '2', '2', '3', '3');
const PDFACONFORMANCE: array[TPdfALevel] of PDFString = ('', 'A', 'B', 'A', 'B', 'A', 'B');
begin
if fSaveToStreamWriter<>nil then
raise EPdfInvalidOperation.Create('SaveToStreamDirectBegin called twice');
// write all objects to specified stream
if ForceModDate=0 then
FInfo.ModDate := Now else
FInfo.ModDate := ForceModDate;
FRoot.SaveOpenAction;
// some PDF/A-1 requirements
if PDFA<>pdfaNone then begin
FMetaData.Writer.Add(RawByteString(
'<?xpacket begin="'#$EF#$BB#$BF'" id="W5M0MpCehiHzreSzNTczkc9d"?>'+
'<x:xmpmeta xmlns:x="adobe:ns:meta/" x:xmptk="SynPdf">'+
'<rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">'+
'<rdf:Description rdf:about="" xmlns:xmp="http://ns.adobe.com/xap/1.0/">'+
'<xmp:CreateDate>')).AddIso8601(Info.CreationDate).Add('Z</xmp:CreateDate>'+
'<xmp:ModifyDate>').AddIso8601(Info.ModDate).Add('Z</xmp:ModifyDate>'+
'<xmp:CreatorTool>').Add(StringToUTF8(Info.Creator)).
Add('</xmp:CreatorTool></rdf:Description>'+
'<rdf:Description rdf:about="" xmlns:dc="http://purl.org/dc/elements/1.1/">'+
'<dc:title><rdf:Alt><rdf:li xml:lang="x-default">').
Add(StringToUTF8(Info.Title)).Add('</rdf:li></rdf:Alt></dc:title>'+
'<dc:creator><rdf:Seq><rdf:li xml:lang="x-default">').
Add(StringToUTF8(Info.Author)).Add('</rdf:li></rdf:Seq></dc:creator>'+
'<dc:description><rdf:Alt><rdf:li xml:lang="x-default">').
Add(StringToUTF8(Info.Subject)).Add('</rdf:li></rdf:Alt></dc:description>'+
'</rdf:Description>'+
'<rdf:Description rdf:about="" xmlns:pdf="http://ns.adobe.com/pdf/1.3/">'+
'<pdf:Keywords>').Add(StringToUTF8(Info.Keywords)).Add('</pdf:Keywords>'+
'<pdf:Producer>'+PDF_PRODUCER+'</pdf:Producer></rdf:Description>'+
'<rdf:Description rdf:about="" xmlns:pdfaid="http://www.aiim.org/pdfa/ns/id/">'+
'<pdfaid:part>'+PDFAPART[PDFA]+'</pdfaid:part><pdfaid:conformance>'+PDFACONFORMANCE[PDFA]+'</pdfaid:conformance>'+
'</rdf:Description></rdf:RDF></x:xmpmeta><?xpacket end="w"?>');
end;
// write beginning of the content
fSaveToStreamWriter := TPdfWrite.Create(self,AStream);
fSaveToStreamWriter.Add(PDFHEADER[fFileformat]);
end;
procedure TPdfDocument.SaveToStreamDirectPageFlush(FlushCurrentPageNow: boolean);
var i: integer;
begin
if (self=nil) or (fSaveToStreamWriter=nil) or (FCanvas.FPage=nil) then
raise EPdfInvalidOperation.Create('SaveToStreamDirectPageFlush');
if FlushCurrentPageNow then
FCanvas.FPage.FSaveAtTheEnd := false;
for i := 1 to FXref.ItemCount-1 do // ignore FXref[0] = root PDF_FREE_ENTRY
with FXref.Items[i] do
if (ByteOffset<=0) and not Value.FSaveAtTheEnd then begin
fByteOffset := fSaveToStreamWriter.Position;
Value.WriteValueTo(fSaveToStreamWriter);
end;
end;
procedure TPdfDocument.SaveToStreamDirectEnd;
var i: integer;
begin
if (self=nil) or (fSaveToStreamWriter=nil) then
raise EPdfInvalidOperation.Create('SaveToStreamDirectEnd');
try
// saving outline tree
if UseOutlines then
FOutlineRoot.Save;
// update font details after all the pages are drawn
for i := 0 to FFontList.Count-1 do
with TPdfFontTrueType(FFontList.List[i]) do
if fTrueTypeFontsIndex<>0 then
PrepareForSaving;
// write pending objects
if fFileFormat>=pdf15 then
FTrailer.ToCrossReference(self);
for i := 1 to FXref.ItemCount-1 do
with FXref.Items[i] do
if ByteOffset<=0 then begin
fByteOffset := fSaveToStreamWriter.Position;
if Value<>FTrailer.FCrossReference then
Value.WriteValueTo(fSaveToStreamWriter);
end;
FTrailer.XrefAddress := fSaveToStreamWriter.Position;
if fFileFormat<pdf15 then
FXref.WriteTo(fSaveToStreamWriter);
FTrailer.Attributes.PdfNumberByName('Size').Value := FXref.ItemCount;
FTrailer.WriteTo(fSaveToStreamWriter);
fSaveToStreamWriter.Save; // flush TPdfWrite buffer into AStream
finally
FreeAndNil(fSaveToStreamWriter);
end;
end;
procedure TPdfDocument.RegisterFont(aFont: TPdfFont);
var st: shortstring;
begin
// fonts are not registered as xref, but registered in FontList[]
str(FFontList.Count,st);
aFont.FShortCut := 'F'+PDFString(st);
aFont.Data.AddItem('Name', aFont.FShortCut);
FFontList.Add(aFont);
end;
function TPdfDocument.GetRegisteredNotTrueTypeFont(const APDFFontName: PDFString): TPdfFont;
var i: integer;
begin
// if specified standard font exists in fontlist, returns it
with FFontList do
for i := 0 to Count-1 do begin
result := TPdfFont(List[i]);
if (result.FTrueTypeFontsIndex=0) and
(result.Name=APDFFontName) then
exit;
end;
result := nil;
end;
function TPdfDocument.GetRegisteredTrueTypeFont(AFontIndex: integer;
AStyle: TPdfFontStyles; ACharSet: byte): TPdfFont;
var i: integer;
begin
// if specified font exists in fontlist, returns the WinAnsi version
if AFontIndex>=0 then
with FFontList do
for i := 0 to Count-1 do begin
result := TPdfFont(List[i]);
if (result.FTrueTypeFontsIndex=AFontIndex) and
not TPdfFontTrueType(result).Unicode and
(TPdfFontTrueType(result).Style=AStyle) and
(TPdfFontTrueType(result).fLogFont.lfCharSet=ACharSet) then
exit;
end;
result := nil;
end;
function CompareLogFontW(const L1,L2: TLogFontW): boolean;
{$ifdef HASINLINE}inline;{$endif}
begin
if (L1.lfWeight<>L2.lfWeight) or (L1.lfItalic<>L2.lfItalic) then
// ignore lfHeight/lfUnderline/lfStrikeOut:
// font size/underline/strike are internal to PDF graphics state
result := false else
result := (AnsiICompW(L1.lfFaceName,L2.lfFaceName)=0);
end;
function TPdfDocument.GetTrueTypeFontIndex(const AName: RawUTF8): integer;
begin
if StrIComp(pointer(FTrueTypeFontLastName),pointer(AName))=0 then begin
result := FTrueTypeFontLastIndex; // simple but efficient cache
exit;
end;
result := FastFindPUTF8CharSorted(pointer(FTrueTypeFonts),high(FTrueTypeFonts),pointer(AName),@StrIComp);
if result>=0 then begin
FTrueTypeFontLastName := AName;
FTrueTypeFontLastIndex := result;
end;
end;
function TPdfDocument.GetRegisteredTrueTypeFont(const AFontLog: TLogFontW): TPdfFont;
var i: integer;
begin
// if specified font exists in fontlist, returns the WinAnsi version
with FFontList do
for i := 0 to Count-1 do begin
result := TPdfFont(List[i]);
if (result.FTrueTypeFontsIndex<>0) and
not TPdfFontTrueType(result).Unicode and
CompareLogFontW(TPdfFontTrueType(result).fLogFont,AFontLog) then
exit;
end;
result := nil;
end;
procedure TPdfDocument.SetStandardFontsReplace(const Value: boolean);
begin
if FCharSet<>ANSI_CHARSET then
FStandardFontsReplace := false else
FStandardFontsReplace := Value;
end;
function TPdfDocument.GetEmbeddedTTFIgnore: TRawUTF8List;
begin
if fEmbeddedTTFIgnore=nil then
fEmbeddedTTFIgnore := TRawUTF8List.Create([fCaseSensitive,fNoDuplicate]);
result := fEmbeddedTTFIgnore;
end;
procedure TPdfDocument.SetDefaultPaperSize(const Value: TPDFPaperSize);
const PAPERSIZE: array[TPDFPaperSize] of array[0..1] of integer =
( (595,842), (419,595), (842,1190), (1190,1683), (1683,2382), (2382,3369),
(612,792), (612,1008), (0,0) );
begin // psA4, psA5, psA3, psA2, psA1, psA0, psLetter, psLegal, psUserDefined
FDefaultPaperSize := Value;
if Value<>psUserDefined then begin
FDefaultPageWidth := PAPERSIZE[Value,0];
FDefaultPageHeight := PAPERSIZE[Value,1];
end;
end;
procedure TPdfDocument.SetDefaultPageHeight(const Value: cardinal);
begin
FDefaultPageHeight := Value;
FDefaultPaperSize := psUserDefined;
end;
procedure TPdfDocument.SetDefaultPageWidth(const Value: cardinal);
begin
FDefaultPageWidth := Value;
FDefaultPaperSize := psUserDefined;
end;
function TPdfDocument.GetDefaultPageLandscape: boolean;
begin
result := FDefaultPageWidth>FDefaultPageHeight;
end;
procedure TPdfDocument.SetDefaultPageLandscape(const Value: boolean);
var tmp: integer;
begin
if Value<>DefaultPageLandscape then begin
tmp := FDefaultPageHeight;
FDefaultPageHeight := FDefaultPageWidth;
FDefaultPageWidth := tmp;
end;
end;
function TPdfDocument.GetDCWithFont(TTF: TPdfFontTrueType): HDC;
begin
if self=nil then
result := 0 else begin
if fSelectedDCFontOld<>0 then // prevent resource leak
SelectObject(FDC,fSelectedDCFontOld);
fSelectedDCFontOld := SelectObject(FDC,TTF.fHGDI);
result := fDC;
end;
end;
const
TTFCFP_MAC_PLATFORMID = 1;
TTFCFP_MS_PLATFORMID = 3;
TTFCFP_SYMBOL_CHAR_SET = 0;
TTFCFP_UNICODE_CHAR_SET = 1;
TTFCFP_DONT_CARE = 65535;
TTFCFP_FLAGS_SUBSET = 1;
TTFMFP_SUBSET = 0;
TTFCFP_FLAGS_TTC = 4;
HEAD_TABLE = $64616568; // 'head'
TTCF_TABLE = $66637474; // 'ttcf'
type
/// a TTF name record used for the 'name' Format 4 table
TNameRecord = packed record
platformID: word;
encodingID: word;
languageID: word;
nameID: word;
length: word;
offset: word;
end;
/// header for the 'name' Format 4 table
TNameFmt4 = packed record
/// Format selector (=0/1)
format: word;
/// Number of name records
count: word;
/// Offset to start of string storage (from start of table)
stringOffset: word;
/// The name records where count is the number of records
FirstNameRecord: TNameRecord;
end;
function TrueTypeFontName(const aFontName: RawUTF8; AStyle: TPdfFontStyles): PDFString;
var i: Integer;
begin // from PDF 1.3 #5.5.2
SetString(result,PAnsiChar(pointer(aFontName)),length(aFontName));
for i := length(result) downto 1 do
if (Result[i]<=' ') or (Result[i]>=#127) then
Delete(result,i,1); // spaces and not ASCII chars are removed
if not IsAnsiCompatible(aFontName) then // unique non-void font name
result := result+PDFString(CardinalToHexLower(CRC32string(aFontName)));
if pfsItalic in AStyle then
if pfsBold in AStyle then
result := result+',BoldItalic' else
result := result+',Italic' else
if pfsBold in AStyle then
result := result+',Bold';
end;
function TPdfDocument.TTFFontPostcriptName(aFontIndex: integer; AStyle: TPdfFontStyles;
AFont: TPdfFontTrueType): PDFString;
// see http://www.microsoft.com/typography/OTSPEC/name.htm
const NAME_POSTCRIPT = 6;
var fName: TWordDynArray;
name: ^TNameFmt4;
aFontName: RawUTF8;
i, L: integer;
Rec: ^TNameRecord;
PW: pointer;
begin
aFontName := FTrueTypeFonts[aFontIndex];
if IsAnsiCompatible(aFontName) or (AFont=nil) then begin
result := TrueTypeFontName(aFontName,AStyle);
exit; // no need to search for the PostScript name field in TTF content
end;
name := GetTTFData(GetDCWithFont(AFont),'name',fName);
if (name=nil) or (name^.format<>0) then
exit;
Rec := @name^.FirstNameRecord;
for i := 0 to name^.count-1 do
if (Rec^.nameID=NAME_POSTCRIPT) and (Rec^.platformID=TTFCFP_MS_PLATFORMID) and
(Rec^.encodingID=1) and (Rec^.languageID=$409) then begin
PW := PAnsiChar(name)+name^.stringOffset+Rec^.offset;
L := Rec^.length shr 1;
if Rec^.offset and 1<>0 then begin // fix GetTTFData() wrong SwapBuffer()
dec(PByte(PW));
SwapBuffer(PW,L+1); // restore big-endian original unaligned buffer
inc(PByte(PW));
SwapBuffer(PW,L); // convert from big-endian at correct odd offset
end;
RawUnicodeToUtf8(PW,L,aFontName);
result := TrueTypeFontName(aFontName,AStyle); // adjust name and style
exit;
end else
inc(Rec);
end;
function TPdfDocument.CreateOutline(const Title: string; Level: integer;
TopPosition: Single): TPdfOutlineEntry;
begin
result := nil;
if self=nil then
exit;
if fLastOutline=nil then begin
if not UseOutlines then
exit; // will raise a GPF otherwise
result := OutlineRoot;
end else begin
result := fLastOutline;
while (Level<=result.Level) and (result.Parent<>nil) do
result := result.Parent;
end;
result := result.AddChild(Trunc(TopPosition));
result.Title := Title;
result.Level := Level;
fLastOutline := result;
end;
{$ifdef USE_BITMAP}
function TPdfDocument.CreateOrGetImage(B: TBitmap; DrawAt: PPdfBox; ClipRc: PPdfBox): PDFString;
var J: TJpegImage;
Img: TPdfImage;
Hash: THash128Rec;
y,w,h,row: integer;
nPals: cardinal;
Pals: array of TPaletteEntry;
const PERROW: array[TPixelFormat] of byte = (0,1,4,8,15,16,24,32,0);
procedure DoHash(bits: pointer; size: Integer);
begin
Hash.c0 := crc32c(Hash.c0,bits,size);
Hash.c1 := crc32c(Hash.c1,bits,size);
Hash.c2 := Hash.c2+Hash.c0; // naive, but sufficient, cascading
Hash.c3 := Hash.c3+Hash.c1;
end;
begin
result := '';
if (self=nil) or (B=nil) then exit;
w := B.Width;
h := B.Height;
if ForceNoBitmapReuse then
FillCharFast(Hash,sizeof(Hash),0) else begin
row := PERROW[B.PixelFormat];
if row=0 then begin
B.PixelFormat := pf24bit;
row := 24;
end;
Hash.c0 := 0;
Hash.c1 := 1400305337; // 3 prime numbers
Hash.c2 := 2468776129;
Hash.c3 := 3121238909;
if B.Palette<>0 then begin
nPals := 0;
if (GetObject(B.Palette,sizeof(nPals),@nPals)<>0) and (nPals>0) then begin
SetLength(Pals,nPals);
if GetPaletteEntries(B.Palette,0,nPals,Pals[0])=nPals then
DoHash(pointer(Pals),nPals*sizeof(TPaletteEntry));
end;
end;
row := BytesPerScanline(w,row,32);
for y := 0 to h-1 do
DoHash(B.ScanLine[y],row);
result := GetXObjectImageName(Hash,w,h); // search for matching image
end;
if result='' then begin
// create new if no existing TPdfImage match
if ForceJPEGCompression=0 then
Img := TPdfImage.Create(Canvas.fDoc,B,True) else begin
J := TJpegImage.Create;
try
J.Assign(B);
Img := TPdfImage.Create(Canvas.fDoc,J,False);
finally
J.Free;
end;
end;
Img.fHash := Hash;
result := 'SynImg'+UInt32ToPDFString(FXObjectList.ItemCount);
if ForceJPEGCompression=0 then
AddXObject(result,Img) else
RegisterXObject(Img, result);
end;
// draw bitmap as XObject
if DrawAt<>nil then begin
if ClipRc<>nil then
with DrawAt^ do
Canvas.DrawXObjectEx(Left,Top,Width,Height,
ClipRc^.Left,ClipRc^.Top,ClipRc^.Width,ClipRc^.Height, result) else
with DrawAt^ do
Canvas.DrawXObject(Left,Top,Width,Height, result);
end;
end;
{$endif USE_BITMAP}
function TPdfDocument.CreateOptionalContentGroup(
ParentContentGroup: TPdfOptionalContentGroup;
const Title: string; Visible: Boolean): TPdfOptionalContentGroup;
var Dico, DicoD: TPdfDictionary;
Arr: TPDFArray;
function FindParentContentGroupArray(Current: TPDFArray): TPDFArray;
var i: Integer;
begin
result := nil;
if Current=nil then
exit;
for i := 0 to Current.ItemCount-1 do
if Current.Items[i]=ParentContentGroup then begin
if (i<Current.ItemCount-1) and Current.Items[i+1].InheritsFrom(TPDFArray) then
result := TPDFArray(Current.Items[i+1]) else begin
result := TPDFArray.Create(FXRef);
Current.InsertItem(i+1, result);
end;
exit;
end;
for i := 0 to Current.ItemCount-1 do
if Current.Items[i].InheritsFrom(TPDFArray) then begin
result := FindParentContentGroupArray(TPDFArray(Current.Items[i]));
if result<>nil then
exit;
end;
end;
begin
if FUseOptionalContent then begin
result := TPdfOptionalContentGroup.Create(FXRef);
FXref.AddObject(result);
result.AddItem('Type','OCG');
result.AddItemTextString('Name',Title);
Dico := FRoot.Data.PdfDictionaryByName('OCProperties');
if Dico<>nil then begin
DicoD := Dico.PdfDictionaryByName('D');
if DicoD<>nil then begin
Arr := DicoD.PdfArrayByName('Order');
if ParentContentGroup<>nil then
Arr := FindParentContentGroupArray(Arr);
if Arr<>nil then
Arr.AddItem(result);
if not Visible then begin
Arr := DicoD.PdfArrayByName('OFF');
if Arr<>nil then
Arr.AddItem(result);
end;
end;
Arr := Dico.PdfArrayByName('OCGs');
if Arr<>nil then
Arr.AddItem(result);
end;
end else
result := nil;
end;
procedure TPdfDocument.CreateOptionalContentRadioGroup(
const ContentGroups: array of TPdfOptionalContentGroup);
var i: Integer;
Dico, DicoD: TPdfDictionary;
Arr, RadioArr: TPDFArray;
begin
if FUseOptionalContent and (Length(ContentGroups)>0) then begin
Dico := FRoot.Data.PdfDictionaryByName('OCProperties');
if Dico<>nil then begin
DicoD := Dico.PdfDictionaryByName('D');
if DicoD<>nil then begin
Arr := DicoD.PdfArrayByName('RBGroups');
if Arr<>nil then begin
RadioArr := TPDFArray.Create(FXref);
for i := Low(ContentGroups) to High(ContentGroups) do
if ContentGroups[i]<>nil then
RadioArr.AddItem(ContentGroups[i]);
if RadioArr.ItemCount>0 then
Arr.AddItem(RadioArr) else
FreeAndNil(RadioArr);
end;
end;
end;
end;
end;
procedure TPdfDocument.SetUseOptionalContent(const Value: boolean);
begin
FUseOptionalContent := Value;
NewDoc;
end;
procedure TPdfDocument.SetPDFA(const Value: TPdfALevel);
begin
fPDFA := Value;
NewDoc;
end;
{$ifndef USE_PDFALEVEL}
function TPdfDocument.GetPDFA1: boolean;
begin
result := (fPDFA = pdfa1B);
end;
procedure TPdfDocument.SetPDFA1(const Value: boolean);
begin
if Value then
SetPDFA(pdfa1B) else
SetPDFA(pdfaNone);
end;
{$endif USE_PDFALEVEL}
procedure TPdfDocument.SetFontFallBackName(const Value: string);
begin
fFontFallBackIndex := GetTrueTypeFontIndex(StringToUTF8(Value));
end;
function TPdfDocument.GetFontFallBackName: string;
begin
if fFontFallBackIndex>=0 then
result := UTF8ToString(FTrueTypeFonts[fFontFallBackIndex]) else
result := '';
end;
function TPdfDocument.GetGeneratePDF15File: boolean;
begin
result := (self<>nil) and (fFileFormat>=pdf15);
end;
procedure TPdfDocument.SetGeneratePDF15File(const Value: boolean);
begin
if fFileFormat<>pdf16 then
if Value then
fFileFormat := pdf15 else
fFileFormat := pdf14;
end;
{ TPdfCanvas }
constructor TPdfCanvas.Create(APdfDoc: TPdfDocument);
begin
FDoc := APdfDoc;
FFactor := 72/FDoc.FScreenLogPixels; // PDF expect 72 pixels per inch
FFactorX := FFactor;
FFactorY := FFactor;
FDevScaleX := 1;
FDevScaleY := 1;
FMappingMode := MM_TEXT;
fUseMetaFileTextPositioning := tpSetTextJustification;
fKerningHScaleBottom := 99.0;
fKerningHScaleTop := 101.0;
end;
function TPdfCanvas.GetPage: TPdfPage;
begin
if (Self<>nil) and (FPage<>nil) then
result := FPage else
raise EPdfInvalidOperation.Create('GetPage');
end;
procedure TPdfCanvas.SetPage(APage: TPdfPage);
begin
FPage := APage;
FPageFontList := FPage.GetResources('Font');
FContents := TPdfStream(FPage.ValueByName('Contents'));
FFactor := 72/FDoc.FScreenLogPixels; // PDF expect 72 pixels per inch
end;
procedure TPdfCanvas.SetPDFFont(AFont: TPdfFont; ASize: Single);
begin
// check if this font is already the current font
if (AFont=nil) or ((FPage.Font=AFont) and (FPage.FontSize=ASize)) then
Exit;
// add this font to the resource array of the current page
if FPageFontList.ValueByName(AFont.ShortCut)=nil then
FPageFontList.AddItem(AFont.ShortCut, AFont.Data);
// change the font
if FContents<>nil then
SetFontAndSize(AFont.ShortCut, ASize); // e.g. SetFontAndSize('F0',12)
FPage.Font := AFont;
FPage.FontSize := ASize;
end;
procedure InitializeLogFontW(const aFontName: RawUTF8; aStyle: TPdfFontStyles;
var aFont: TLogFontW);
begin
FillCharFast(AFont,sizeof(AFont),0);
with aFont do begin
lfHeight := -1000;
if pfsBold in AStyle then
lfWeight := FW_BOLD else
lfWeight := FW_NORMAL;
lfItalic := Byte(pfsItalic in AStyle);
lfUnderline := Byte(pfsUnderline in AStyle);
lfStrikeOut := Byte(pfsStrikeOut in AStyle);
UTF8ToWideChar(lfFaceName,Pointer(aFontName));
end;
end;
const // see PDF ref 9.6.2.2: Standard Type 1 Fonts
// WidthArray[30]=Ascent, WidthArray[31]=Descent,
// WidthArray[32..255]=Width(#32..#255)
ARIAL_W_ARRAY: array[30..255] of SmallInt = (
905,-212,278,278,355,556,556,889,667,191,333,333,389,584,278,333,
278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,
584,556,1015,667,667,722,722,667,611,778,722,278,500,667,556,833,
722,778,667,778,722,667,611,722,667,944,667,667,611,278,278,278,
469,556,333,556,556,500,556,556,278,556,556,222,222,500,222,833,
556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,
584,0,556,0,222,556,333,1000,556,556,333,1000,667,333,1000,0,
611,0,0,222,222,333,333,350,556,1000,333,1000,500,333,944,0,
500,667,0,333,556,556,556,556,260,556,333,737,370,556,584,0,
737,333,400,584,333,333,333,556,537,278,333,333,365,556,834,834,
834,611,667,667,667,667,667,667,1000,722,667,667,667,667,278,278,
278,278,722,722,778,778,778,778,778,584,778,722,722,722,722,667,
667,611,556,556,556,556,556,556,889,500,556,556,556,556,278,278,
278,278,556,556,556,556,556,556,556,584,611,556,556,556,556,500,556,500);
ARIAL_BOLD_W_ARRAY: array[30..255] of SmallInt = (
905,-212,278,333,474,556,556,889,722,238,333,333,389,584,278,333,
278,278,556,556,556,556,556,556,556,556,556,556,333,333,584,584,
584,611,975,722,722,722,722,667,611,778,722,278,556,722,611,833,
722,778,667,778,722,667,611,722,667,944,667,667,611,333,278,333,
584,556,333,556,611,556,611,556,333,611,611,278,278,556,278,889,
611,611,611,611,389,556,333,611,556,778,556,556,500,389,280,389,
584,0,556,0,278,556,500,1000,556,556,333,1000,667,333,1000,0,
611,0,0,278,278,500,500,350,556,1000,333,1000,556,333,944,0,
500,667,0,333,556,556,556,556,280,556,333,737,370,556,584,0,
737,333,400,584,333,333,333,611,556,278,333,333,365,556,834,834,
834,611,722,722,722,722,722,722,1000,722,667,667,667,667,278,278,
278,278,722,722,778,778,778,778,778,584,778,722,722,722,722,667,
667,611,556,556,556,556,556,556,889,556,556,556,556,556,278,278,
278,278,611,611,611,611,611,611,611,584,611,611,611,611,611,556,611,556);
ARIAL_ITALIC_W_ARRAY: array[30..255] of SmallInt = (
905,-212,278,278,355,556,556,889,667,191,333,333,389,584,278,333,
278,278,556,556,556,556,556,556,556,556,556,556,278,278,584,584,
584,556,1015,667,667,722,722,667,611,778,722,278,500,667,556,833,
722,778,667,778,722,667,611,722,667,944,667,667,611,278,278,278,
469,556,333,556,556,500,556,556,278,556,556,222,222,500,222,833,
556,556,556,556,333,500,278,556,500,722,500,500,500,334,260,334,
584,0,556,0,222,556,333,1000,556,556,333,1000,667,333,1000,0,
611,0,0,222,222,333,333,350,556,1000,333,1000,500,333,944,0,
500,667,0,333,556,556,556,556,260,556,333,737,370,556,584,0,
737,333,400,584,333,333,333,556,537,278,333,333,365,556,834,834,
834,611,667,667,667,667,667,667,1000,722,667,667,667,667,278,278,
278,278,722,722,778,778,778,778,778,584,778,722,722,722,722,667,
667,611,556,556,556,556,556,556,889,500,556,556,556,556,278,278,
278,278,556,556,556,556,556,556,556,584,611,556,556,556,556,500,556,500);
ARIAL_BOLDITALIC_W_ARRAY: array[30..255] of SmallInt = (
905,-212,278,333,474,556,556,889,722,238,333,333,389,584,278,333,
278,278,556,556,556,556,556,556,556,556,556,556,333,333,584,584,
584,611,975,722,722,722,722,667,611,778,722,278,556,722,611,833,
722,778,667,778,722,667,611,722,667,944,667,667,611,333,278,333,
584,556,333,556,611,556,611,556,333,611,611,278,278,556,278,889,
611,611,611,611,389,556,333,611,556,778,556,556,500,389,280,389,
584,0,556,0,278,556,500,1000,556,556,333,1000,667,333,1000,0,
611,0,0,278,278,500,500,350,556,1000,333,1000,556,333,944,0,
500,667,0,333,556,556,556,556,280,556,333,737,370,556,584,0,
737,333,400,584,333,333,333,611,556,278,333,333,365,556,834,834,
834,611,722,722,722,722,722,722,1000,722,667,667,667,667,278,278,
278,278,722,722,778,778,778,778,778,584,778,722,722,722,722,667,
667,611,556,556,556,556,556,556,889,556,556,556,556,556,278,278,
278,278,611,611,611,611,611,611,611,584,611,611,611,611,611,556,611,556);
TIMES_ROMAN_W_ARRAY: array[30..255] of SmallInt = (
891,-216,250,333,408,500,500,833,778,180,333,333,500,564,250,333,
250,278,500,500,500,500,500,500,500,500,500,500,278,278,564,564,
564,444,921,722,667,667,722,611,556,722,722,333,389,722,611,889,
722,722,556,722,667,556,611,722,722,944,722,722,611,333,278,333,
469,500,333,444,500,444,500,444,333,500,500,278,278,500,278,778,
500,500,500,500,333,389,278,500,500,722,500,500,444,480,200,480,
541,0,500,0,333,500,444,1000,500,500,333,1000,556,333,889,0,
611,0,0,333,333,444,444,350,500,1000,333,980,389,333,722,0,
444,722,0,333,500,500,500,500,200,500,333,760,276,500,564,0,
760,333,400,564,300,300,333,500,453,250,333,300,310,500,750,750,
750,444,722,722,722,722,722,722,889,667,611,611,611,611,333,333,
333,333,722,722,722,722,722,722,722,564,722,722,722,722,722,722,
556,500,444,444,444,444,444,444,667,444,444,444,444,444,278,278,
278,278,500,500,500,500,500,500,500,564,500,500,500,500,500,500,500,500);
TIMES_ITALIC_W_ARRAY: array[30..255] of SmallInt = (
891,-216,250,333,420,500,500,833,778,214,333,333,500,675,250,333,
250,278,500,500,500,500,500,500,500,500,500,500,333,333,675,675,
675,500,920,611,611,667,722,611,611,722,722,333,444,667,556,833,
667,722,611,722,611,500,556,722,611,833,611,556,556,389,278,389,
422,500,333,500,500,444,500,444,278,500,500,278,278,444,278,722,
500,500,500,500,389,389,278,500,444,667,444,444,389,400,275,400,
541,0,500,0,333,500,556,889,500,500,333,1000,500,333,944,0,
556,0,0,333,333,556,556,350,500,889,333,980,389,333,667,0,
389,556,0,389,500,500,500,500,275,500,333,760,276,500,675,0,
760,333,400,675,300,300,333,500,523,250,333,300,310,500,750,750,
750,500,611,611,611,611,611,611,889,667,611,611,611,611,333,333,
333,333,722,667,722,722,722,722,722,675,722,722,722,722,722,556,
611,500,500,500,500,500,500,500,667,444,444,444,444,444,278,278,
278,278,500,500,500,500,500,500,500,675,500,500,500,500,500,444,500,444);
TIMES_BOLD_W_ARRAY: array[30..255] of SmallInt = (
891,-216,250,333,555,500,500,1000,833,278,333,333,500,570,250,333,
250,278,500,500,500,500,500,500,500,500,500,500,333,333,570,570,
570,500,930,722,667,722,722,667,611,778,778,389,500,778,667,944,
722,778,611,778,722,556,667,722,722,1000,722,722,667,333,278,333,
581,500,333,500,556,444,556,444,333,500,556,278,333,556,278,833,
556,500,556,556,444,389,333,556,500,722,500,500,444,394,220,394,
520,0,500,0,333,500,500,1000,500,500,333,1000,556,333,1000,0,
667,0,0,333,333,500,500,350,500,1000,333,1000,389,333,722,0,
444,722,0,333,500,500,500,500,220,500,333,747,300,500,570,0,
747,333,400,570,300,300,333,556,540,250,333,300,330,500,750,750,
750,500,722,722,722,722,722,722,1000,722,667,667,667,667,389,389,
389,389,722,722,778,778,778,778,778,570,778,722,722,722,722,722,
611,556,500,500,500,500,500,500,722,444,444,444,444,444,278,278,
278,278,500,556,500,500,500,500,500,570,500,556,556,556,556,500,556,500);
TIMES_BOLDITALIC_W_ARRAY: array[30..255] of SmallInt = (
891,-216,250,389,555,500,500,833,778,278,333,333,500,570,250,333,
250,278,500,500,500,500,500,500,500,500,500,500,333,333,570,570,
570,500,832,667,667,667,722,667,667,722,778,389,500,667,611,889,
722,722,611,722,667,556,611,722,667,889,667,611,611,333,278,333,
570,500,333,500,500,444,500,444,333,500,556,278,278,500,278,778,
556,500,500,500,389,389,278,556,444,667,500,444,389,348,220,348,
570,0,500,0,333,500,500,1000,500,500,333,1000,556,333,944,0,
611,0,0,333,333,500,500,350,500,1000,333,1000,389,333,722,0,
389,611,0,389,500,500,500,500,220,500,333,747,266,500,606,0,
747,333,400,570,300,300,333,576,500,250,333,300,300,500,750,750,
750,500,667,667,667,667,667,667,944,667,667,667,667,667,389,389,
389,389,722,722,722,722,722,722,722,570,722,722,722,722,722,611,
611,500,500,500,500,500,500,500,722,444,444,444,444,444,278,278,
278,278,500,556,500,500,500,500,500,570,500,556,556,556,556,444,500,444);
STANDARDFONTS: array[0..11] of record
Name: PDFString;
Widths: PSmallIntArray;
end = (
(Name: 'Times-Roman'; Widths: @TIMES_ROMAN_W_ARRAY),
(Name: 'Times-Bold'; Widths: @TIMES_BOLD_W_ARRAY),
(Name: 'Times-Italic'; Widths: @TIMES_ITALIC_W_ARRAY),
(Name: 'Times-BoldItalic'; Widths: @TIMES_BOLDITALIC_W_ARRAY),
(Name: 'Helvetica'; Widths: @ARIAL_W_ARRAY),
(Name: 'Helvetica-Bold'; Widths: @ARIAL_BOLD_W_ARRAY),
(Name: 'Helvetica-Oblique'; Widths: @ARIAL_ITALIC_W_ARRAY),
(Name: 'Helvetica-BoldOblique'; Widths: @ARIAL_BOLDITALIC_W_ARRAY),
(Name: 'Courier'; Widths: nil), // Widths:nil -> set all widths to 600
(Name: 'Courier-Bold'; Widths: nil),
(Name: 'Courier-Oblique'; Widths: nil),
(Name: 'Courier-BoldOblique'; Widths: nil));
function TPdfCanvas.SetFont(const AName: RawUTF8; ASize: single; AStyle: TPdfFontStyles;
ACharSet: integer=-1; AForceTTF: integer=-1; AIsFixedWidth: boolean=false): TPdfFont;
const
STAND_FONTS_PDF: array[TPdfFontStandard] of RawUTF8 = ('Times','Helvetica','Courier');
STAND_FONTS_WIN: array[TPdfFontStandard] of RawUTF8 = ('Times New Roman','Arial','Courier New');
STAND_FONTS_UPPER: array[TPdfFontStandard] of PAnsiChar = ('TIMES','HELVETICA','COURIER');
procedure SetEmbeddedFont(Standard: TPdfFontStandard);
var BaseIndex: integer;
begin
BaseIndex := ord(Standard)*4+(byte(AStyle) and 3);
result := fDoc.GetRegisteredNotTrueTypeFont(STANDARDFONTS[BaseIndex].Name);
if result=nil then begin // font not already registered -> add now
with STANDARDFONTS[BaseIndex] do
result := TPdfFontType1.Create(fDoc.FXref,Name,Widths);
fDoc.RegisterFont(result);
end;
SetPDFFont(result,ASize);
end;
var AFont: TLogFontW;
FontIndex: integer;
f: TPdfFontStandard;
begin
result := nil;
if (self=nil) or (FDoc=nil) then
exit; // avoid GPF
if AForceTTF>=0 then
// an existing true type font has been specified
FontIndex := AForceTTF else begin
// handle use embedded fonts for standard fonts, if needed
if (fDoc.FCharSet=ANSI_CHARSET) and fDoc.StandardFontsReplace then begin
// standard/embedded fonts are WinAnsi only
for f := low(f) to high(f) do
if SameTextU(AName,STAND_FONTS_PDF[f]) or
SameTextU(AName,STAND_FONTS_WIN[f]) then begin
SetEmbeddedFont(f);
if result<>nil then
exit; // we got a standard/embedded font
end;
end;
if (FPreviousRasterFontName<>'') and (FPreviousRasterFontName=AName) then
FontIndex := FPreviousRasterFontIndex else begin
// search the font in the global system-wide true type fonts list
FontIndex := fDoc.GetTrueTypeFontIndex(AName);
if FontIndex<0 then begin // unknown, device or raster font
if AIsFixedWidth then // sounds to be fixed-width -> set 'Courier New'
FontIndex := fDoc.GetTrueTypeFontIndex(STAND_FONTS_WIN[pfsCourier]);
// do not exist as is: find equivalency of some "standard" font
for f := low(f) to high(f) do
if (FontIndex<0) and IdemPChar(pointer(AName),STAND_FONTS_UPPER[f]) then
FontIndex := fDoc.GetTrueTypeFontIndex(STAND_FONTS_WIN[f]);
if FontIndex<0 then begin // use variable width default font
FontIndex := FDoc.fFontFallBackIndex;
if FontIndex<0 then
FontIndex := fDoc.GetTrueTypeFontIndex('Arial');
if FontIndex<0 then
exit;
end;
FPreviousRasterFontName := AName;
FPreviousRasterFontIndex := FontIndex;
end;
end;
end;
if ACharSet<0 then
ACharSet := fDoc.CharSet; // force the current PDF Document charset
result := fDoc.GetRegisteredTrueTypeFont(FontIndex+1,AStyle,ACharSet);
if result=nil then begin
// a font of this kind is not already registered -> create it
FillCharFast(AFont,sizeof(AFont),0);
with AFont do begin
lfHeight := -1000;
if pfsBold in AStyle then
lfWeight := FW_BOLD else
lfWeight := FW_NORMAL;
lfItalic := Byte(pfsItalic in AStyle);
lfUnderline := Byte(pfsUnderline in AStyle);
lfStrikeOut := Byte(pfsStrikeOut in AStyle);
lfCharSet := ACharSet;
UTF8ToWideChar(lfFaceName,Pointer(fDoc.FTrueTypeFonts[FontIndex]));
end;
// we register now the WinAnsi font to the associated fDoc
result := TPdfFontTrueType.Create(fDoc,FontIndex,AStyle,AFont,nil);
end;
if AForceTTF<0 then
SetPDFFont(result,ASize);
end;
function TPdfCanvas.SetFont(ADC: HDC; const ALogFont: TLogFontW; ASize: single): TPdfFont;
var AStyle: TPdfFontStyles;
AName: RawUTF8;
begin
// try if the font is already registered
result := fDoc.GetRegisteredTrueTypeFont(ALogFont);
if Result<>nil then begin
SetPDFFont(result,ASize); // use the existing font, update size if necessary
exit;
end;
// font is not existing -> create new
AName := RawUnicodeToUtf8(ALogFont.lfFaceName,StrLenW(ALogFont.lfFaceName));
if ALogFont.lfItalic<>0 then
AStyle := [pfsItalic] else
byte(AStyle) := 0;
if ALogFont.lfUnderline<>0 then
include(AStyle,pfsUnderline);
if ALogFont.lfStrikeOut<>0 then
include(AStyle,pfsStrikeOut);
if ALogFont.lfWeight>=FW_SEMIBOLD then
include(AStyle,pfsBold);
result := SetFont(AName,ASize,AStyle,ALogFont.lfCharSet,-1,
ALogFont.lfPitchAndFamily and TMPF_FIXED_PITCH=0);
end;
procedure TPdfCanvas.TextOut(X, Y: Single; const Text: PDFString);
begin
if FContents<>nil then begin
FContents.Writer.Add('BT'#10).AddWithSpace(X).AddWithSpace(Y).Add('Td'#10);
ShowText(Text);
FContents.Writer.Add('ET'#10);
end;
end;
procedure TPdfCanvas.TextOutW(X, Y: Single; PW: PWideChar);
begin
if FContents<>nil then begin
FContents.Writer.Add('BT'#10).AddWithSpace(X).AddWithSpace(Y).Add('Td'#10);
ShowText(PW);
FContents.Writer.Add('ET'#10);
end;
end;
procedure TPdfCanvas.TextRect(ARect: TPdfRect; const Text: PDFString;
Alignment: TPdfAlignment; Clipping: boolean);
var tmpWidth: Single;
XPos: Single;
begin
// calculate text width and corresponding X position according to alignment
tmpWidth := TextWidth(Text);
case Alignment of
paCenter: XPos := Round((ARect.Right - ARect.Left - tmpWidth) / 2);
paRightJustify: XPos := ARect.Right - ARect.Left - Round(tmpWidth);
else
XPos := 0;
end;
// clipping client rect if needed
if Clipping then begin
GSave;
with ARect do begin
MoveTo(Left, Top);
LineTo(Left, Bottom);
LineTo(Right, Bottom);
LineTo(Right, Top);
end;
ClosePath;
Clip;
NewPath;
end;
// show the text in the specified rectangle and alignment
BeginText;
MoveTextPoint(ARect.Left + XPos, ARect.Top - FPage.FontSize * 0.85);
ShowText(Text);
EndText;
if Clipping then
GRestore;
end;
procedure TPdfCanvas.MultilineTextRect(ARect: TPdfRect; const Text: PDFString;
WordWrap: boolean);
var i: integer;
S1, S2: PDFString;
XPos, YPos: Single;
tmpXPos: Single;
tmpWidth: Single;
ln: integer;
ForceNL: boolean;
FText: PDFString;
procedure InternalShowText(S: PDFString; AWidth: Single);
var i: Integer;
begin
i := MeasureText(S, AWidth); // simple clipping
S := Copy(S, 1, i);
ShowText(S);
end;
begin
YPos := ARect.Top - FPage.FontSize*0.85;
XPos := ARect.Left;
FText := Text;
BeginText;
MoveTextPoint(XPos, YPos);
i := 1;
S2 := GetNextWord(FText, i);
XPos := XPos + TextWidth(S2);
if (Length(S2) > 0) and (S2[Length(S2)]=' ') then
XPos := XPos + FPage.WordSpace;
while i <= Length(FText) do begin
ln := Length(S2);
if (ln >= 2) and (S2[ln]=#10) and (S2[ln-1]=#13) then begin
S2 := Copy(S2, 1, ln - 2);
ForceNL := true;
end else
ForceNL := false;
S1 := GetNextWord(FText, i);
tmpWidth := TextWidth(S1);
TmpXPos := XPos + tmpWidth;
if (WordWrap and (TmpXPos > ARect.Right)) or ForceNL then begin
if S2<>'' then
InternalShowText(S2, ARect.Right - ARect.Left);
S2 := '';
MoveToNextLine;
ARect.Top := ARect.Top - FPage.Leading;
if ARect.Top < ARect.Bottom + FPage.FontSize then
Break;
XPos := ARect.Left;
end;
XPos := XPos + tmpWidth;
if (Length(S1) > 0) and (S1[Length(S1)]=' ') then
XPos := XPos + FPage.WordSpace;
S2 := S2 + S1;
end;
if S2<>'' then
InternalShowText(S2, ARect.Right - ARect.Left);
EndText;
end;
procedure TPdfCanvas.DrawXObjectPrepare(const AXObjectName: PDFString);
var XObject: TPdfXObject;
FPageResources: TPdfDictionary;
i: integer;
begin
// drawing object must be registered. check object name
XObject := fDoc.GetXObject(AXObjectName);
if XObject=nil then
raise EPdfInvalidValue.CreateFmt('DrawXObject: unknown %s', [AXObjectName]);
FPageResources := FPage.GetResources('XObject');
if FPageResources=nil then
raise EPdfInvalidValue.Create('DrawXObject: no XObject');
if FPageResources.ValueByName(AXObjectName)=nil then
FPageResources.AddItem(AXObjectName, XObject);
{$ifdef USE_METAFILE}
if XObject.InheritsFrom(TPdfForm) then
with TPdfForm(XObject).FFontList do
for i := 0 to ItemCount-1 do
with Items[i] do
if FPageFontList.ValueByName(Key)=nil then
FPageFontList.AddItem(Key, Value);
{$endif USE_METAFILE}
end;
procedure TPdfCanvas.DrawXObject(X, Y, AWidth, AHeight: Single;
const AXObjectName: PDFString);
begin
DrawXObjectPrepare(AXObjectName);
GSave;
ConcatToCTM(AWidth, 0, 0, AHeight, X, Y);
ExecuteXObject(AXObjectName);
GRestore;
end;
procedure TPdfCanvas.DrawXObjectEx(X, Y, AWidth, AHeight: Single;
ClipX, ClipY, ClipWidth, ClipHeight: Single; const AXObjectName: PDFString);
begin
DrawXObjectPrepare(AXObjectName);
GSave;
Rectangle(ClipX, ClipY, ClipWidth, ClipHeight);
Clip;
NewPath;
fNewPath := False;
ConcatToCTM(AWidth, 0, 0, AHeight, X, Y);
ExecuteXObject(AXObjectName);
GRestore;
end;
{* Special Graphics State *}
procedure TPdfCanvas.GSave;
begin
if FContents<>nil then
FContents.Writer.Add('q'#10);
end;
procedure TPdfCanvas.GRestore;
begin
if FContents<>nil then
FContents.Writer.Add('Q'#10);
end;
procedure TPdfCanvas.ConcatToCTM(a, b, c, d, e, f: Single; Decimals: Cardinal);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(a,Decimals).AddWithSpace(b,Decimals).AddWithSpace(c,Decimals).
AddWithSpace(d,Decimals).AddWithSpace(e,Decimals).AddWithSpace(f,Decimals).Add('cm'#10);
end;
{* General Graphics State *}
procedure TPdfCanvas.SetFlat(flatness: Byte);
begin
if FContents<>nil then
FContents.Writer.Add(flatness).Add(' i'#10);
end;
procedure TPdfCanvas.SetLineCap(linecap: TLineCapStyle);
begin
if FContents<>nil then
FContents.Writer.Add(ord(linecap)).Add(' J'#10);
end;
procedure TPdfCanvas.SetDash(const aarray: array of integer; phase: integer);
var i: integer;
begin
if FContents=nil then
exit;
FContents.Writer.Add('[');
if (High(aarray) >= 0) and (aarray[0]<>0) then
for i := 0 to High(aarray) do
FContents.Writer.AddWithSpace(aarray[i]);
FContents.Writer.Add('] ').Add(phase).Add(' d'#10);
end;
procedure TPdfCanvas.SetLineJoin(linejoin: TLineJoinStyle);
begin
if FContents<>nil then
FContents.Writer.Add(ord(linejoin)).Add(' j'#10);
end;
procedure TPdfCanvas.SetLineWidth(linewidth: Single);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(linewidth).Add('w'#10);
end;
procedure TPdfCanvas.SetMiterLimit(miterlimit: Single);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(miterlimit).Add('M'#10);
end;
{* Paths *}
procedure TPdfCanvas.MoveTo(x, y: Single);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(x).AddWithSpace(y).Add('m'#10);
end;
procedure TPdfCanvas.LineTo(x, y: Single);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(x).AddWithSpace(y).Add('l'#10);
end;
procedure TPdfCanvas.CurveToC(x1, y1, x2, y2, x3, y3: Single);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(x1).AddWithSpace(y1).AddWithSpace(x2).
AddWithSpace(y2).AddWithSpace(x3).AddWithSpace(y3).Add('c'#10);
end;
procedure TPdfCanvas.CurveToV(x2, y2, x3, y3: Single);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(x2).AddWithSpace(y2).AddWithSpace(x3).
AddWithSpace(y3).Add('v'#10);
end;
procedure TPdfCanvas.CurveToY(x1, y1, x3, y3: Single);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(x1).AddWithSpace(y1).AddWithSpace(x3).
AddWithSpace(y3).Add('y'#10);
end;
procedure TPdfCanvas.Rectangle(x, y, width, height: Single);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(x).AddWithSpace(y).AddWithSpace(width).
AddWithSpace(height).Add('re'#10);
end;
procedure TPdfCanvas.Closepath;
begin
if FContents<>nil then
FContents.Writer.Add('h'#10);
end;
procedure TPdfCanvas.NewPath;
begin
fNewPath := true;
if FContents<>nil then
FContents.Writer.Add('n'#10);
end;
procedure TPdfCanvas.Stroke;
begin
if FContents<>nil then
FContents.Writer.Add('S'#10);
end;
procedure TPdfCanvas.ClosePathStroke;
begin
if FContents<>nil then
FContents.Writer.Add('s'#10);
end;
procedure TPdfCanvas.Fill;
begin
if FContents<>nil then
FContents.Writer.Add('f'#10);
end;
procedure TPdfCanvas.Eofill;
begin
if FContents<>nil then
FContents.Writer.Add('f*'#10);
end;
procedure TPdfCanvas.FillStroke;
begin
if FContents<>nil then
FContents.Writer.Add('B'#10);
end;
procedure TPdfCanvas.ClosepathFillStroke;
begin
if FContents<>nil then
FContents.Writer.Add('b'#10);
end;
procedure TPdfCanvas.EofillStroke;
begin
if FContents<>nil then
FContents.Writer.Add('B*'#10);
end;
procedure TPdfCanvas.ClosepathEofillStroke;
begin
if FContents<>nil then
FContents.Writer.Add('b*'#10);
end;
procedure TPdfCanvas.Clip;
begin
if FContents<>nil then
FContents.Writer.Add('W'#10);
end;
procedure TPdfCanvas.Eoclip;
begin
if FContents<>nil then
FContents.Writer.Add('W*'#10);
end;
{* Text handling *}
procedure TPdfCanvas.SetCharSpace(charSpace: Single);
begin
if FPage.CharSpace=charSpace then Exit;
FPage.SetCharSpace(charSpace);
if FContents<>nil then
FContents.Writer.AddWithSpace(charSpace).Add('Tc'#10);
end;
procedure TPdfCanvas.SetWordSpace(wordSpace: Single);
begin
if FPage.WordSpace=wordSpace then Exit;
FPage.SetWordSpace(wordSpace);
if FContents<>nil then
FContents.Writer.AddWithSpace(wordSpace).Add('Tw'#10);
end;
procedure TPdfCanvas.SetHorizontalScaling(hScaling: Single);
begin
if FPage.HorizontalScaling=hScaling then Exit;
FPage.SetHorizontalScaling(hScaling);
if FContents<>nil then
FContents.Writer.Add(hScaling).Add(' Tz'#10);
end;
procedure TPdfCanvas.SetLeading(leading: Single);
begin
if FPage.Leading=leading then Exit;
FPage.SetLeading(leading);
if FContents<>nil then
FContents.Writer.AddWithSpace(leading).Add('TL'#10);
end;
procedure TPdfCanvas.SetFontAndSize(const fontshortcut: PDFString; size: Single);
begin
if FContents<>nil then
FContents.Writer.Add('/').Add(fontshortcut).Add(' ').AddWithSpace(size).Add('Tf'#10);
end;
procedure TPdfCanvas.SetTextRenderingMode(mode: TTextRenderingMode);
begin
if FContents<>nil then
FContents.Writer.Add(ord(mode)).Add(' Tr'#10);
end;
procedure TPdfCanvas.SetTextRise(rise: word);
begin
if FContents<>nil then
FContents.Writer.Add(rise).Add(' Ts'#10);
end;
procedure TPdfCanvas.BeginText;
begin
if FContents<>nil then
FContents.Writer.Add('BT'#10);
end;
procedure TPdfCanvas.EndText;
begin
if FContents<>nil then
FContents.Writer.Add('ET'#10);
end;
procedure TPdfCanvas.MoveTextPoint(tx, ty: Single);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(tx).AddWithSpace(ty).Add('Td'#10);
end;
procedure TPdfCanvas.SetTextMatrix(a, b, c, d, x, y: Single);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(a).AddWithSpace(b).AddWithSpace(c).
AddWithSpace(d).AddWithSpace(x).AddWithSpace(y).Add('Tm'#10);
end;
procedure TPdfCanvas.MoveToNextLine;
begin
if FContents<>nil then
FContents.Writer.Add('T*'#10);
end;
{$ifdef HASVARUSTRING}
procedure TPdfCanvas.ShowText(const text: UnicodeString; NextLine: boolean);
begin // direct call of the unicode text drawing method below
ShowText(pointer(text),NextLine);
end;
{$endif}
procedure TPdfCanvas.ShowText(const text: PDFString; NextLine: boolean);
begin
if (FContents<>nil) and (text<>'') then
if (fDoc.FCharSet=ANSI_CHARSET) or IsAnsiCompatible(text) then begin
if FPage.Font.Unicode and (FPage.FFont.FTrueTypeFontsIndex<>0) then
SetPDFFont(TPdfFontTrueType(FPage.Font).WinAnsiFont,FPage.FontSize);
FContents.Writer.Add('(').AddEscapeText(pointer(text),FPage.Font).Add(')').
Add(SHOWTEXTCMD[NextLine])
end else begin
if FPage.FFont.FTrueTypeFontsIndex<>0 then
// write TrueType text after conversion to unicode
FContents.Writer.AddToUnicodeHexText(text,NextLine,self) else
// this standard font should expect MBCS encoding
FContents.Writer.Add('<').AddHex(text).Add('>').Add(SHOWTEXTCMD[NextLine]);
end;
end;
procedure TPdfCanvas.ShowText(PW: PWideChar; NextLine: boolean);
begin
if FContents<>nil then
FContents.Writer.AddUnicodeHexText(PW,NextLine,self);
end;
procedure TPdfCanvas.ShowGlyph(PW: PWord; Count: integer);
begin
if FContents<>nil then
FContents.Writer.AddGlyphs(PW,Count,self);
end;
procedure TPdfCanvas.ExecuteXObject(const xObject: PDFString);
begin
if FContents<>nil then
FContents.Writer.Add('/').Add(xObject).Add(' Do'#10);
end;
procedure TPdfCanvas.SetRGBFillColor(Value: TPdfColor);
begin
if FContents<>nil then
FContents.Writer.AddColorStr(Value).Add('rg'#10);
end;
procedure TPdfCanvas.SetRGBStrokeColor(Value: TPdfColor);
begin
if FContents<>nil then
FContents.Writer.AddColorStr(Value).Add('RG'#10);
end;
procedure TPdfCanvas.SetCMYKFillColor(C, M, Y, K: integer);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(C/100).AddWithSpace(M/100).
AddWithSpace(Y/100).AddWithSpace(K/100).Add('k'#10);
end;
procedure TPdfCanvas.SetCMYKStrokeColor(C, M, Y, K: integer);
begin
if FContents<>nil then
FContents.Writer.AddWithSpace(C/100).AddWithSpace(M/100).
AddWithSpace(Y/100).AddWithSpace(K/100).Add('K'#10);
end;
function TPdfCanvas.TextWidth(const Text: PDFString): Single;
begin
result := FPage.TextWidth(Text);
end;
{$ifdef DELPHI5OROLDER}
function StrCharLength(const Str: PChar): Cardinal;
begin
result := Cardinal(CharNext(Str))-Cardinal(Str);
end;
function NextCharIndex(const S: string; Index: cardinal): cardinal;
begin
Result := Index + 1;
assert(Index <= cardinal(Length(S)));
if SysLocale.FarEast and (S[Index] in LeadBytes) then
Result := Index + StrCharLength(PChar(S) + Index - 1);
end;
{$endif}
const
DEFAULT_PDF_WIDTH = 600;
function TPdfCanvas.UnicodeTextWidth(PW: PWideChar): Single;
var Ansi: PDFString;
i, W, glyphW: integer;
begin
W := 0;
if PW<>nil then
if FPage.FFont.FTrueTypeFontsIndex=0 then begin
Ansi := CurrentAnsiConvert.UnicodeBufferToAnsi(PW,StrLenW(PW));
i := 1;
while i<=length(Ansi) do begin // loop is MBCS ready
inc(W,FPage.FFont.GetAnsiCharWidth(Ansi,i));
if SysLocale.FarEast then
i := NextCharIndex(Ansi,i) else
inc(i);
end;
end else
with TPdfFontTrueType(FPage.FFont).WinAnsiFont do
while PW^<>#0 do begin
glyphW := GetWideCharWidth(PW^);
if (glyphW=0) and (fDoc.fUseFontFallBack) and (fDoc.fFontFallBackIndex>=0) then
glyphW := (SetFont('',FPage.FontSize,fStyle,-1,fDoc.fFontFallBackIndex)
as TPdfFontTrueType).GetWideCharWidth(PW^);
if glyphW=0 then
glyphW := DEFAULT_PDF_WIDTH;
inc(W,glyphW);
inc(PW);
end;
Result := (W*fPage.fFontSize)/1000;
end;
function TPdfCanvas.MeasureText(const Text: PDFString; AWidth: Single): integer;
begin
result := FPage.MeasureText(Text, AWidth);
end;
const
// see http://paste.lisp.org/display/1105
BEZIER: single = 0.55228477716; // = 4/3 * (sqrt(2) - 1);
procedure TPdfCanvas.Ellipse(x, y, width, height: single);
var w2,h2,xw2,yh2: single;
begin
w2 := width/2;
h2 := height/2;
xw2 := x+w2;
yh2 := y+h2;
MoveTo(x, yh2);
CurveToC(x, yh2-h2*BEZIER, xw2-w2*BEZIER, y, xw2, y);
CurveToC(xw2+w2*BEZIER, y, x+width, yh2-h2*BEZIER, x+width, yh2);
CurveToC(x+width, yh2+h2*BEZIER, xw2+w2*BEZIER, y+height, xw2, y+height);
CurveToC(xw2-w2*BEZIER, y+height, x, yh2+h2*BEZIER, x, yh2);
end;
procedure TPdfCanvas.RoundRect(x1, y1, x2, y2, cx, cy: Single);
begin
cx := cx/2;
cy := cy/2;
MoveTo(x1+cx, y1);
LineTo(x2-cx, y1);
CurveToC(x2-cx+BEZIER*cx, y1, x2, y1+cy-BEZIER*cy, x2, y1+cy);
LineTo(x2, y2-cy);
CurveToC(x2, y2-cy+BEZIER*cy, x2-cx+BEZIER*cx, y2, x2-cx, y2);
LineTo(x1+cx, y2);
CurveToC(x1+cx-BEZIER*cx, y2, x1, y2-cy+BEZIER*cy, x1, y2-cy);
LineTo(x1, y1+cy);
CurveToC(x1, y1+cy-BEZIER*cy, x1+cx-BEZIER*cx, y1, x1+cx, y1);
ClosePath;
end;
function TPdfCanvas.GetNextWord(const S: PDFString; var Index: integer): PDFString;
var ln: integer;
i: integer;
begin
// getting a word from text
result := '';
ln := Length(S);
if Index > ln then
Exit;
i := Index;
while true do
if (S[i]=#10) and (S[i-1]=#13) or (S[i]=' ') then begin
result := Copy(S, Index, i - (Index -1));
break;
end else
if i >= ln then begin
result := Copy(S, Index, i - (Index - 1));
break;
end else
if SysLocale.PriLangID=LANG_JAPANESE then
if {$ifdef ISDELPHIXE4}System.AnsiStrings.{$endif}ByteType(S, i)=mbTrailByte then
if (S[i+1]<>#129) or not(S[i+2] in [#65,#66]) then begin
result := Copy(S, Index, i - (Index - 1));
break;
end else
inc(i) else
if ((i < ln) and
({$ifdef ISDELPHIXE4}System.AnsiStrings.{$endif}ByteType(S, i + 1)=mbLeadByte)) then begin
result := Copy(S, Index, i - (Index - 1));
break;
end else
inc(i) else
inc(i);
Index := i + 1;
end;
function TPdfCanvas.GetDoc: TPdfDocument;
begin
if fDoc<>nil then
result := fDoc else
raise EPdfInvalidOperation.Create('GetDoc');
end;
function TPdfCanvas.ViewOffsetX(X: Single): Single;
begin
Result := (((X - FWinOrg.X) * FViewSize.cx / FWinSize.cx) + FViewOrg.X)
end;
function TPdfCanvas.ViewOffsetY(Y: Single): Single;
begin
Result := ((Y - FWinOrg.Y) * FViewSize.cy / FWinSize.cy) + FViewOrg.Y
end;
function TPdfCanvas.GetWorldFactorX: Single;
begin
Result := FWorldFactorX
end;
function TPdfCanvas.GetWorldFactorY: Single;
begin
Result := FWorldFactorY
end;
function TPdfCanvas.I2X(X: Integer): Single;
begin
result := FOffsetXDef + (FWorldOffsetX + ViewOffsetX(X) * GetWorldFactorX) * FDevScaleX
end;
function TPdfCanvas.I2Y(Y: Integer): Single;
begin
result := FPage.GetPageHeight - FOffsetYDef -
(FWorldOffsetY + ViewOffsetY(Y) * GetWorldFactorY) * FDevScaleY
end;
function TPdfCanvas.I2X(X: Single): Single;
begin
result := FOffsetXDef + (FWorldOffsetX + ViewOffsetX(X) * GetWorldFactorX) * FDevScaleX
end;
function TPdfCanvas.I2Y(Y: Single): Single;
begin
result := FPage.GetPageHeight - FOffsetYDef -
(FWorldOffsetY + ViewOffsetY(Y) * GetWorldFactorY) * FDevScaleY
end;
procedure TPdfCanvas.LineToI(x, y: integer);
begin
LineTo(I2X(X),I2Y(Y));
end;
procedure TPdfCanvas.MoveToI(x, y: integer);
begin
MoveTo(I2X(X),I2Y(Y));
end;
procedure TPdfCanvas.CurveToCI(x1, y1, x2, y2, x3, y3: integer);
begin
CurveToC(I2X(x1),I2Y(y1),I2X(x2),I2Y(y2),I2X(x3),I2Y(y3));
end;
procedure TPdfCanvas.MoveToI(x, y: Single);
begin
MoveTo(I2X(X),I2Y(Y));
end;
procedure TPdfCanvas.LineToI(x, y: Single);
begin
LineTo(I2X(X),I2Y(Y));
end;
procedure TPdfCanvas.RoundRectI(x1, y1, x2, y2, cx, cy: integer);
begin
RoundRect(I2X(x1),I2Y(y1),I2X(x2),I2Y(y2),
cx * FDevScaleX * GetWorldFactorX,-cy * FDevScaleY * GetWorldFactorY);
end;
{$ifdef USE_ARC}
procedure TPdfCanvas.ARCI(centerx, centery, W, H, Sx, Sy, Ex, Ey: integer;
clockwise: boolean; arctype: TPdfCanvasArcType; var position: TPoint);
var res: teaDrawArray;
i: integer;
begin
if CalcCurveArcData(centerx, centery, W, H, Sx, Sy, Ex, Ey, clockwise, arctype, res) then
for I := 0 to High(res) do
with res[i] do
case res of
caMoveto:
MoveTo(I2X(pts[0].x), i2y(pts[0].y));
caLine:
LineTo(I2X(pts[0].x), i2y(pts[0].y));
caCurve:
CurveToC(I2X(pts[0].x), i2y(pts[0].y),
I2X(pts[1].x), i2y(pts[1].y),
I2X(pts[2].x), i2y(pts[2].y));
caPosition: begin
position.x := Round(pts[0].x);
position.y := Round(pts[0].y);
end;
end;
end;
{$endif USE_ARC}
procedure TPdfCanvas.PointI(x, y: Single);
begin
Rectangle(I2X(X),I2Y(Y),1E-2,1E-2); //smalest difference 1E-2 because of rounding to two decimals
end;
function TPdfCanvas.BoxI(Box: TRect; Normalize: boolean): TPdfBox;
var r: TPdfRect;
begin
// to PDF coordinates conversion
r := RectI(Box,Normalize);
result.Width := r.Right - r.Left;
result.Height := r.Bottom - r.Top;
result.Left := r.Left;
result.Top := r.Top;
end;
procedure NormalizeRect(var Rect: TRect); overload;
var tmp: integer;
begin // PDF can't draw twisted rects -> normalize such values
if Rect.Right<Rect.Left then begin
tmp := Rect.Left;
Rect.Left := Rect.Right;
Rect.Right := tmp;
end;
if Rect.Bottom<Rect.Top then begin
tmp := Rect.Top;
Rect.Top := Rect.Bottom;
Rect.Bottom := tmp;
end;
end;
procedure NormalizeRect(var Rect: TPdfRect); overload;
var tmp: Single;
begin // PDF can't draw twisted rects -> normalize such values
if Rect.Right<Rect.Left then begin
tmp := Rect.Left;
Rect.Left := Rect.Right;
Rect.Right := tmp;
end;
if Rect.Bottom<Rect.Top then begin
tmp := Rect.Top;
Rect.Top := Rect.Bottom;
Rect.Bottom := tmp;
end;
end;
function TPdfCanvas.RectI(Rect: TRect; Normalize: boolean): TPdfRect;
begin
result.Left := I2X(Rect.Left);
result.Right := I2X(Rect.Right-1);
result.Top := I2Y(Rect.Top);
result.Bottom := I2Y(Rect.Bottom-1);
if Normalize then
NormalizeRect(result);
end;
procedure TPdfCanvas.BeginMarkedContent(Group : TPdfOptionalContentGroup);
var Resources, Properties: TPdfDictionary;
ID: PDFString;
begin
if (FContents=nil) or not FDoc.UseOptionalContent then
exit;
if Group<>nil then begin
ID := 'oc'+UInt32ToPDFString(Group.ObjectNumber);
// register Group in page resources properties
Resources := FPage.PdfDictionaryByName('Resources');
if Resources<>nil then begin
Properties := Resources.PdfDictionaryByName('Properties');
if Properties = nil then begin
Properties := TPdfDictionary.Create(FDoc.FXRef);
Resources.AddItem('Properties', Properties);
end;
if Properties<>nil then
Properties.AddItem(ID,Group);
end;
FContents.Writer.Add('/OC /').Add(ID).Add(' BDC'#10);
end else
FContents.Writer.Add('/OC BMC'#10);
end;
procedure TPdfCanvas.EndMarkedContent;
begin
if (FContents<>nil) and FDoc.UseOptionalContent then
FContents.Writer.Add('EMC'#10);
end;
{ TPdfDictionaryWrapper }
procedure TPdfDictionaryWrapper.SetData(AData: TPdfDictionary);
begin
FData := AData;
if FData<>nil then
FData.FSaveAtTheEnd := true;
end;
function TPdfDictionaryWrapper.GetHasData: boolean;
begin
result := (FData=nil);
end;
{ TPdfInfo }
procedure TPdfInfo.SetAuthor(const Value: String);
begin
FData.AddItemTextString('Author', Value);
end;
procedure TPdfInfo.SetCreationDate(Value: TDateTime);
begin
FData.AddItemText('CreationDate', _DateTimeToPdfDate(Value));
end;
procedure TPdfInfo.SetModDate(Value: TDateTime);
begin
FData.AddItemText('ModDate', _DateTimeToPdfDate(Value));
end;
procedure TPdfInfo.SetCreator(const Value: String);
begin
FData.AddItemTextString('Creator', Value);
end;
procedure TPdfInfo.SetTitle(const Value: String);
begin
FData.AddItemTextString('Title', Value);
end;
procedure TPdfInfo.SetSubject(const Value: String);
begin
FData.AddItemTextString('Subject', Value);
end;
procedure TPdfInfo.SetKeywords(const Value: String);
begin
FData.AddItemTextString('Keywords', Value);
end;
function TPdfInfo.GetAuthor: String;
begin
result := FData.PdfTextStringValueByName('Author');
end;
function TPdfInfo.GetCreationDate: TDateTime;
var P: TPdfText;
begin
P := FData.PdfTextByName('CreationDate');
if P<>nil then
try
result := _PdfDateToDateTime(P.Value);
except
result := 0;
end else
result := 0;
end;
function TPdfInfo.GetModDate: TDateTime;
var P: TPdfText;
begin
P := FData.PdfTextByName('ModDate');
if P<>nil then
try
result := _PdfDateToDateTime(P.Value);
except
result := 0;
end else
result := 0;
end;
function TPdfInfo.GetCreator: String;
begin
result := FData.PdfTextStringValueByName('Creator');
end;
function TPdfInfo.GetTitle: String;
begin
result := FData.PdfTextStringValueByName('Title');
end;
function TPdfInfo.GetSubject: String;
begin
result := FData.PdfTextStringValueByName('Subject');
end;
function TPdfInfo.GetKeywords: String;
begin
result := FData.PdfTextStringValueByName('Keywords');
end;
{ TPdfCatalog }
procedure TPdfCatalog.SaveOpenAction;
begin
if (FOpenAction=nil) then
FData.RemoveItem('OpenAction') else
FData.AddItem('OpenAction', FOpenAction.GetValue);
end;
procedure TPdfCatalog.SetPageLayout(Value: TPdfPageLayout);
var FPageLayout: TPdfName;
begin
FPageLayout := FData.PdfNameByName('PageLayout');
if (FPageLayout=nil) or not FPageLayout.InheritsFrom(TPdfName) then
FData.AddItem('PageLayout', PDF_PAGE_LAYOUT_NAMES[Value]) else
FPageLayout.Value := PDF_PAGE_LAYOUT_NAMES[Value];
end;
function TPdfCatalog.GetPageLayout: TPdfPageLayout;
var FPageLayout: TPdfName;
S: PDFString;
begin
result := plSinglePage;
FPageLayout := FData.PdfNameByName('PageLayout');
if (FPageLayout=nil) or not FPageLayout.InheritsFrom(TPdfName) then
Exit;
S := FPageLayout.Value;
for result := low(TPdfPageLayout) to high(TPdfPageLayout) do
if PDF_PAGE_LAYOUT_NAMES[result]=S then
exit;
result := plSinglePage;
end;
function TPdfCatalog.GetNonFullScreenPageMode: TPdfPageMode;
var FDictionary: TPdfDictionary;
FPageMode: TPdfName;
S: PDFString;
begin
result := pmUseNone;
FDictionary := FData.PdfDictionaryByName('NonFullScreenPageMode');
if FDictionary=nil then
Exit;
FPageMode := FDictionary.PdfNameByName('NonFullScreenPageMode');
if (FPageMode=nil) or not (FPageMode is TPdfName) then
Exit;
S := FPageMode.Value;
for result := Low(TPdfPageMode) to High(TPdfPageMode) do
if PDF_PAGE_MODE_NAMES[result]=S then
exit;
result := pmUseNone;
end;
const
PDF_PAGE_VIEWER_NAMES: array[TPdfViewerPreference] of PDFString = (
'HideToolbar', 'HideMenubar', 'HideWindowUI', 'FitWindow', 'CenterWindow',
'PrintScaling');
function TPdfCatalog.GetViewerPreference: TPdfViewerPreferences;
var FDictionary: TPdfDictionary;
V: TPdfViewerPreference;
begin
result := [];
FDictionary := FData.PdfDictionaryByName('ViewerPreference');
if FDictionary<>nil then
for V := low(V) to high(V) do
if FDictionary.PdfBooleanByName(PDF_PAGE_VIEWER_NAMES[V])<>nil then
include(result,V);
end;
procedure TPdfCatalog.SetPageMode(Value: TPdfPageMode);
var FPageMode: TPdfName;
begin
FPageMode := FData.PdfNameByName('PageMode');
if (FPageMode=nil) or not (FPageMode is TPdfName) then
FData.AddItem('PageMode', PDF_PAGE_MODE_NAMES[Value]) else
FPageMode.Value := PDF_PAGE_MODE_NAMES[Value];
end;
procedure TPdfCatalog.SetNonFullScreenPageMode(Value: TPdfPageMode);
var FDictionary: TPdfDictionary;
FPageMode: TPdfName;
begin
FDictionary := FData.PdfDictionaryByName('ViewerPreferences');
if FDictionary=nil then begin
FDictionary := TPdfDictionary.Create(Data.ObjectMgr);
Data.AddItem('ViewerPreferences', FDictionary);
end;
// if Value is pmFullScreen, remove 'PageMode' element (use default value)
if (Value=pmFullScreen) or (Value=pmUseNone) then
FDictionary.RemoveItem('NonFullScreenPageMode') else begin
FPageMode := FDictionary.PdfNameByName('NonFullScreenPageMode');
if (FPageMode=nil) or not (FPageMode is TPdfName) then
FDictionary.AddItem('NonFullScreenPageMode', PDF_PAGE_MODE_NAMES[Value]) else
FPageMode.Value := PDF_PAGE_MODE_NAMES[Value];
end;
end;
procedure TPdfCatalog.SetViewerPreference(Value: TPdfViewerPreferences);
var V: TPdfViewerPreference;
FDictionary: TPdfDictionary;
begin
FDictionary := FData.PdfDictionaryByName('ViewerPreferences');
if (FDictionary=nil) and (Value<>[]) then begin
FDictionary := TPdfDictionary.Create(Data.ObjectMgr);
FData.AddItem('ViewerPreferences', FDictionary);
end;
if FDictionary<>nil then begin
for V := low(V) to high(V) do
if V in Value then
if V=vpEnforcePrintScaling then
FDictionary.AddItem(PDF_PAGE_VIEWER_NAMES[V],TPdfName.Create('None')) else
FDictionary.AddItem(PDF_PAGE_VIEWER_NAMES[V],TPdfBoolean.Create(true)) else
FDictionary.RemoveItem(PDF_PAGE_VIEWER_NAMES[V]);
if vpEnforcePrintScaling in Value then begin
FDictionary.AddItem('Enforce', TPdfArray.CreateNames(Data.ObjectMgr,['PrintScaling']));
if fOwner<>nil then
fOwner.fFileFormat := pdf16;
end else
FDictionary.RemoveItem('Enforce');
end;
end;
function TPdfCatalog.GetPageMode: TPdfPageMode;
var FPageMode: TPdfName;
S: PDFString;
begin
result := pmUseNone;
FPageMode := FData.PdfNameByName('PageMode');
if (FPageMode=nil) or not FPageMode.InheritsFrom(TPdfName) then
Exit;
S := FPageMode.Value;
for result := Low(PDF_PAGE_MODE_NAMES) to High(PDF_PAGE_MODE_NAMES) do
if PDF_PAGE_MODE_NAMES[result]=S then
exit;
result := pmUseNone;
end;
function TPdfCatalog.GetPages: TPdfDictionary;
begin
result := FData.PdfDictionaryByName('Pages');
if result=nil then
raise EPdfInvalidOperation.Create('GetPages');
end;
procedure TPdfCatalog.SetPages(APages: TPdfDictionary);
begin
if APages.TypeOf='Pages' then
FData.AddItem('Pages', APages);
end;
{ TPdfFont }
function TPdfFont.GetAnsiCharWidth(const AText: PDFString; APos: integer): integer;
begin
Result := 0;
end;
constructor TPdfFont.Create(AXref: TPdfXref; const AName: PDFString);
begin
inherited Create;
FName := AName;
Data := TPdfDictionary.Create(AXref);
AXref.AddObject(FData);
end;
procedure TPdfFont.AddUsedWinAnsiChar(aChar: AnsiChar);
begin
if Self<>nil then
include(fWinAnsiUsed,aChar);
end;
{ TPdfFontWinAnsi }
destructor TPdfFontWinAnsi.Destroy;
begin
FreeMem(fWinAnsiWidth);
inherited;
end;
function TPdfFontWinAnsi.GetAnsiCharWidth(const AText: PDFString; APos: integer): integer;
begin
if (fWinAnsiWidth<>nil) and (AText[APos]>=#32) then
result := fWinAnsiWidth[AText[APos]] else
result := fDefaultWidth;
end;
{ TPdfFontType1 }
constructor TPdfFontType1.Create(AXref: TPdfXref; const AName: PDFString;
WidthArray: PSmallIntArray);
var i: integer;
c: AnsiChar;
DefaultWidth: word;
Widths: TPdfArray;
begin
inherited Create(AXref, AName);
// adding standard element to the font definition
Data.AddItem('Type','Font');
Data.AddItem('Subtype','Type1');
Data.AddItem('Encoding','WinAnsiEncoding');
Data.AddItem('FirstChar',32);
Data.AddItem('LastChar',255);
Data.AddItem('BaseFont', FName);
// register font
if WidthArray=nil then begin
// [] -> Courier fixed-width font
fDefaultWidth := DEFAULT_PDF_WIDTH;
fAscent := 833;
fDescent := -300;
end else begin
// WidthArray[0]=Ascent, WidthArray[1]=Descent, WidthArray[2..]=Width(#32..)
fAscent := WidthArray^[0];
fDescent := WidthArray^[1];
// create "Width" table of the font (256-32=224)
Data.AddItem('Widths', TPdfArray.Create(AXref, @WidthArray^[2], 224), true);
end;
// initialize char widths array by default value (if missing width parameter
// is defined, use it as default value.)
if Data.PdfNumberByName('MissingWidth')<>nil then
DefaultWidth := Data.PdfNumberByName('MissingWidth').Value else
DefaultWidth := fDefaultWidth; // typicaly 600 for Times
GetMem(fWinAnsiWidth,sizeof(fWinAnsiWidth^));
for c := low(TPdfWinAnsiWidth) to high(TPdfWinAnsiWidth) do
fWinAnsiWidth^[c] := DefaultWidth;
FFirstChar := Data.PdfNumberByName('FirstChar').Value;
FLastChar := Data.PdfNumberByName('LastChar').Value;
// fill width array with "Widths" table values.
Widths := Data.PdfArrayByName('Widths');
if Widths<>nil then
for i := 0 to Widths.ItemCount-1 do
if FFirstChar+i >= 32 then
fWinAnsiWidth^[AnsiChar(FFirstChar+i)] := TPdfNumber(Widths[i]).Value;
end;
{ TPdfFontTrueType }
const
{ collection of flags defining various characteristics of the font
see PDF Reference 1.3 #5.7.1 }
PDF_FONT_FIXED_WIDTH = 1;
PDF_FONT_SERIF = 2;
PDF_FONT_SYMBOLIC = 4;
PDF_FONT_SCRIPT = 8;
PDF_FONT_STD_CHARSET = 32;
PDF_FONT_ITALIC = 64;
PDF_FONT_ALL_CAP = 65536;
PDF_FONT_SMALL_CAP = 131072;
PDF_FONT_FORCE_BOLD = 262144;
function TPdfFontTrueType.FindOrAddUsedWideChar(aWideChar: WideChar): integer;
var n, i: integer;
aSymbolAnsiChar: AnsiChar;
begin
if WinAnsiFont <> self then // WinAnsiFont.fUsedWide[] = glyphs for ShowText
begin
result := WinAnsiFont.FindOrAddUsedWideChar(aWideChar);
exit;
end;
result := fUsedWideChar.Add(ord(aWideChar));
if result<0 then begin
result := -(result+1); // this WideChar was already existing -> return index
exit;
end;
// this WideChar was just added -> reserve space in fUsedWide[]
if length(fUsedWide)=fUsedWideChar.Count-1 then
SetLength(fUsedWide,fUsedWideChar.Count+100);
n := fUsedWideChar.Count-1;
if result<n then
MoveFast(fUsedWide[result],fUsedWide[result+1],(n-result)*4);
// create associated Unicode Font if necessary
if UnicodeFont=nil then
CreateAssociatedUnicodeFont;
// update fUsedWide[result] for current glyph
i := UnicodeFont.fUsedWideChar.IndexOf(ord(aWideChar));
if (i<0) and UnicodeFont.fIsSymbolFont then begin
TSynAnsiConvert.Engine(fDoc.CodePage).UnicodeBufferToAnsi(
@aSymbolAnsiChar,@aWideChar,1);
aWideChar := WideChar($f000+ord(aSymbolAnsiChar));
i := UnicodeFont.fUsedWideChar.IndexOf(ord(aWideChar));
end;
if i<0 then // if this glyph doesn't exist in this font -> set to zero
i := 0 else
i := UnicodeFont.fUsedWide[i].int;
fUsedWide[result].int := i; // update Width and Glyph
end;
function TPdfFontTrueType.GetAndMarkGlyphAsUsed(aGlyph: word): word;
var i: integer;
begin
result := aGlyph;
// 1. check if not already registered as used
with WinAnsiFont do // WinAnsiFont.fUsedWide[] = glyphs used by ShowText
for i := 0 to fUsedWideChar.Count-1 do
if fUsedWide[i].Glyph=aGlyph then
exit; // fast return already existing glyph index
// 2. register this glyph, and return TTF glyph
with UnicodeFont do // UnicodeFont.fUsedWide[] = available glyphs from TPdfTTF
for i := 0 to fUsedWideChar.Count-1 do
if fUsedWide[i].Glyph=aGlyph then begin
result := WinAnsiFont.fUsedWide[
FindOrAddUsedWideChar(WideChar(fUsedWideChar.Values[i]))].Glyph;
exit; // result may be 0 if this glyph doesn't exist in the CMAP content
end;
result := 0; // returns 0 if not found
end;
constructor TPdfFontTrueType.Create(ADoc: TPdfDocument; AFontIndex: integer;
AStyle: TPdfFontStyles; const ALogFont: TLogFontW; AWinAnsiFont: TPdfFontTrueType);
var W: packed array of TABC;
c: AnsiChar;
aFontName: PDFString;
Flags: integer;
begin
if AWinAnsiFont<>nil then begin
fWinAnsiFont := AWinAnsiFont;
fUnicode := true;
fUnicodeFont := self;
fHGDI := AWinAnsiFont.fHGDI; // only one GDI resource is used for both
end else begin
fWinAnsiFont := self;
fHGDI := CreateFontIndirectW(ALogFont);
end;
if AWinAnsiFont<>nil then // we use the Postscript Name here
aFontName := AWinAnsiFont.fName else
aFontName := ADoc.TTFFontPostcriptName(AFontIndex,AStyle,self);
inherited Create(ADoc.FXref,aFontName);
fDoc := ADoc;
fTrueTypeFontsIndex := AFontIndex+1;
fStyle := AStyle;
// adding element to the dictionary
Data.AddItem('Type', 'Font');
Data.AddItem('BaseFont', FName);
// retrieve font details
fLogFont := ALogFont; // we always need our local copy
if Unicode then begin
// 1. Unicode Font
Data.AddItem('Subtype', 'Type0');
Data.AddItem('Encoding', 'Identity-H');
// Retrieve some font details from WinAnsi version
fFixedWidth := AWinAnsiFont.fFixedWidth;
fDefaultWidth := AWinAnsiFont.fDefaultWidth;
fM := AWinAnsiFont.fM;
fOTM := AWinAnsiFont.fOTM;
// get TrueType glyphs info
fDoc.GetDCWithFont(self);
TPdfTTF.Create(self).Free; // all the magic in one line :)
end else begin
// 2. WinAnsi Font
Data.AddItem('Subtype', 'TrueType');
Data.AddItem('Encoding', 'WinAnsiEncoding');
// retrieve default WinAnsi characters widths
fDoc.GetDCWithFont(self);
GetTextMetrics(fDoc.FDC,fM);
fOTM.otmSize := SizeOf(fOTM);
GetOutlineTextMetrics(fDoc.FDC,SizeOf(fOTM),@fOTM);
GetMem(fWinAnsiWidth,sizeof(fWinAnsiWidth^));
SetLength(W,224);
GetCharABCWidthsA(fDoc.FDC,32,255,W[0]);
with W[0] do
fDefaultWidth := integer(abcA+integer(abcB)+abcC);
if fM.tmPitchAndFamily and TMPF_FIXED_PITCH=0 then begin
fFixedWidth := true;
for c := #32 to #255 do
fWinAnsiWidth[c] := fDefaultWidth;
end else
for c := #32 to #255 do
with W[ord(c)-32] do
fWinAnsiWidth[c] := integer(abcA+integer(abcB)+abcC);
// create font descriptor (the WinAnsi one is used also for unicode)
FFontDescriptor := TPdfDictionary.Create(ADoc.FXref);
FFontDescriptor.FSaveAtTheEnd := true;
ADoc.FXref.AddObject(FFontDescriptor);
FFontDescriptor.AddItem('Type','FontDescriptor');
FFontDescriptor.AddItem('FontName',FName);
FFontDescriptor.AddItem('Ascent',fOTM.otmAscent);
FFontDescriptor.AddItem('CapHeight',666);
FFontDescriptor.AddItem('Descent',fOTM.otmDescent);
FFontDescriptor.AddItem('ItalicAngle',fOTM.otmItalicAngle);
FFontDescriptor.AddItem('StemV',87);
{ if fFixedWidth then
Flags := PDF_FONT_FIXED_WIDTH else
Flags := 0;
if (fsItalic in AStyle) and (fOTM.otmItalicAngle<>0) then
Flags := Flags or PDF_FONT_ITALIC;
if Flags=0 then
Flags := PDF_FONT_STD_CHARSET;}
if ALogFont.lfCharSet=SYMBOL_CHARSET then
Flags := PDF_FONT_SYMBOLIC else
Flags := PDF_FONT_STD_CHARSET;
FFontDescriptor.AddItem('Flags',Flags);
with fOTM.otmrcFontBox do
FFontDescriptor.AddItem('FontBBox',
TPdfArray.Create(fDoc.FXref, [Left,Bottom,Right,Top]));
Data.AddItem('FontDescriptor',fFontDescriptor);
end;
fAscent := fOTM.otmAscent;
fDescent := fOTM.otmDescent;
FDoc.RegisterFont(self);
end;
destructor TPdfFontTrueType.Destroy;
begin
if not Unicode then
DeleteObject(fHGDI);
inherited;
end;
function TPdfFontTrueType.GetWideCharUsed: Boolean;
begin
result := (fUsedWideChar.Count>0);
end;
function TPdfFontTrueType.GetWideCharWidth(aWideChar: WideChar): Integer;
var ref: TPdfFontTrueType;
begin
ref := WinAnsiFont; // WinAnsiFont.fUsedWide[] = glyphs used by ShowText
result := WideCharToWinAnsi(ord(aWideChar));
if result>=0 then
if (ref.fWinAnsiWidth<>nil) and (result>=32) then
result := ref.fWinAnsiWidth[AnsiChar(result)] else
result := ref.fDefaultWidth else
result := ref.fUsedWide[ref.FindOrAddUsedWideChar(aWideChar)].Width;
end;
{ font subset embedding using Windows XP CreateFontPackage() FontSub.dll
see http://msdn.microsoft.com/en-us/library/dd183502 }
function lpfnAllocate(Size: Integer): pointer; cdecl;
begin
GetMem(result,Size);
end;
function lpfnReAllocate(Buffer: pointer; Size: Integer): pointer; cdecl;
begin
ReallocMem(Buffer, Size);
result := Buffer;
end;
procedure lpfnFree(Buffer: pointer); cdecl;
begin
FreeMem(Buffer);
end;
type
TTtfTableDirectory = packed record
sfntVersion: cardinal; // 0x00010000 for version 1.0
numTables: word; // number of tables
searchRange: word; // HighBit(NumTables) x 16
entrySelector: word; // Log2(HighBit(NumTables))
rangeShift: word; // NumTables x 16 - SearchRange
end;
PTtfTableDirectory = ^TTtfTableDirectory;
TTtfTableEntry = packed record
tag: cardinal; // table identifier
checksum: cardinal; // checksum for this table
offset: cardinal; // offset from start of font file
length: cardinal; // length of this table
end;
PTtfTableEntry = ^TTtfTableEntry;
const
// see http://www.4real.gr/technical-documents-ttf-subset.html
// https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6.html
TTF_SUBSET: array[0..9] of array[0..3] of AnsiChar = (
'head', 'cvt ', 'fpgm', 'prep', 'hhea', 'maxp', 'hmtx', 'cmap', 'loca', 'glyf');
procedure ReduceTTF(out ttf: PDFString; SubSetData: pointer; SubSetSize: integer);
var dir: PTtfTableDirectory;
d, e: PTtfTableEntry;
head: PCmapHEAD;
n, i, len: PtrInt;
checksum: cardinal;
begin
SetLength(ttf, SubSetSize); // maximum size
d := pointer(ttf);
inc(PTtfTableDirectory(d));
// identify the tables to be included
e := SubSetData;
inc(PTtfTableDirectory(e));
n := 0;
if SubSetSize > SizeOf(dir^) then
for i := 1 to swap(PTtfTableDirectory(SubSetData)^.numTables) do begin
if IntegerScan(@TTF_SUBSET, length(TTF_SUBSET), e^.tag) <> nil then begin
d^ := e^;
inc(d);
inc(n);
end;
inc(e);
end;
// update the main directory
if n < 8 then begin // pdf expects 10, and our fixed dir^ below in 8..15
MoveFast(SubSetData^, pointer(ttf)^, SubSetSize); // paranoid
exit;
end;
dir := pointer(ttf);
dir^.sfntVersion := PTtfTableDirectory(SubSetData)^.sfntVersion;
dir^.numTables := swap(word(n));
//len := HighBit(n); // always 8 when n in 8..15
//dir^.searchRange := swap(len * 16);
//dir^.entrySelector := swap(Floor(log2(len))); // requires the Math unit
//dir^.rangeShift := swap((integer(n) - len) * 16);
dir^.searchRange := 32768; // pre-computed values for n in 8..15
dir^.entrySelector := 768;
dir^.rangeShift := 8192;
// include the associated data
checksum := 0;
head := nil;
e := pointer(ttf);
inc(PTtfTableDirectory(e));
for i := 1 to n do begin
len := bswap32(e^.length);
MoveFast(PByteArray(SubSetData)[bswap32(e^.offset)], d^, len);
e^.offset := bswap32(PtrUInt(d) - PtrUInt(ttf));
if e^.tag = HEAD_TABLE then // 'head' table
head := pointer(d);
while len and 3 <> 0 do begin // 32-bit padding
PByteArray(d)[len] := 0;
inc(len);
end;
inc(checksum, bswap32(e^.checksum)); // we didn't change the table itself
inc(PByte(d), len);
inc(e);
end;
// finalize the generated content
for i := 0 to ((SizeOf(dir^) + (integer(n) * SizeOf(e^))) shr 2) - 1 do
inc(checksum, PCardinalArray(ttf)[i]);
if head <> nil then
head^.checkSumAdjustment := bswap32($B1B0AFBA - checksum);
PStrLen(PtrUInt(ttf) - _STRLEN)^ := PtrUInt(d) - PtrUInt(ttf); // no realloc
end;
var
FontSub: THandle = INVALID_HANDLE_VALUE;
CreateFontPackage: function(puchSrcBuffer: pointer; ulSrcBufferSize: cardinal;
var puchFontPackageBuffer: PAnsiChar; var pulFontPackageBufferSize: cardinal;
var pulBytesWritten: Cardinal; usFlags, usTTCIndex, usSubsetFormat,
usSubsetLanguage, usSubsetPlatform, usSubsetEncoding: word;
pusSubsetKeepList: PWordArray; usSubsetKeepListCount: word;
lpfnAllocate, lpfnReAllocate, lpfnFree, reserved: pointer): cardinal; cdecl;
procedure TPdfFontTrueType.PrepareForSaving;
var c: AnsiChar;
i, n, L, ndx, count: integer;
Descendants: TPdfArray;
Descendant, CIDSystemInfo: TPdfDictionary;
ToUnicode: TPdfStream;
DS: TStream;
WR: TPdfWrite;
ttfSize: cardinal;
ttf: PDFString;
SubSetData: PAnsiChar;
SubSetMem: cardinal;
SubSetSize: cardinal;
Used: TSortedWordArray;
usFlags: Word; // For CreateFontPackage
ttcIndex: Word; // For CreateFontPackage
tableTag: Longword;
{$ifndef DELPHI5OROLDER}
ttcNumFonts: Longword;
{$endif}
begin
DS := THeapMemoryStream.Create;
WR := TPdfWrite.Create(fDoc,DS);
try
if Unicode then begin
// 1. Unicode Font (see PDF 1.3 reference #5.9)
// create descendant font
Descendant := TPdfDictionary.Create(fDoc.FXref);
Descendant.AddItem('Type','Font');
Descendant.AddItem('Subtype','CIDFontType2');
Descendant.AddItem('BaseFont',FName);
if fDoc.PDFA<>pdfaNone then
Descendant.AddItem('CIDToGIDMap','Identity');
CIDSystemInfo := TPdfDictionary.Create(FDoc.FXref);
CIDSystemInfo.AddItem('Supplement',0);
CIDSystemInfo.AddItemText('Ordering','Identity');
CIDSystemInfo.AddItemText('Registry','Adobe');
Descendant.AddItem('CIDSystemInfo',CIDSystemInfo);
n := WinAnsiFont.fUsedWideChar.Count;
if n>0 then begin
fFirstChar := WinAnsiFont.fUsedWide[0].Glyph;
fLastChar := WinAnsiFont.fUsedWide[n-1].Glyph;
end;
Descendant.AddItem('DW',WinAnsiFont.fDefaultWidth);
if (fDoc.PDFA<>pdfaNone) or not WinAnsiFont.fFixedWidth then begin
WR.Add('['); // fixed width will use /DW value
// WinAnsiFont.fUsedWide[] contains glyphs used by ShowText
for i := 0 to n-1 do
with WinAnsiFont.fUsedWide[i] do
if int<>0 then
WR.Add(Glyph).Add('[').Add(Width).Add(']');
Descendant.AddItem('W',TPdfRawText.Create(WR.Add(']').ToPDFString));
end;
Descendant.AddItem('FontDescriptor',WinAnsiFont.fFontDescriptor);
fDoc.FXref.AddObject(Descendant);
// create and associate descendant fonts array
Descendants := TPdfArray.Create(fDoc.FXref);
Descendants.AddItem(Descendant);
Data.AddItem('DescendantFonts',Descendants);
// create ToUnicode CMaping
ToUnicode := TPdfStream.Create(fDoc);
ToUnicode.Writer.Add('/CIDInit /ProcSet findresource begin'#10+
'12 dict begin'#10'begincmap'#10'/CIDSystemInfo'#10'<<'#10'/Registry (').
Add(ShortCut).Add('+0)'#10'/Ordering (UCS)'#10'/Supplement 0'#10'>> def'#10+
'/CMapName /').Add(ShortCut).Add('+0 def'#10'/CMapType 2 def'#10+
'1 begincodespacerange'#10'<').AddHex4(fFirstChar).Add('> <').
AddHex4(fLastChar).Add('>'#10'endcodespacerange'#10);
ndx := 0;
while n>0 do begin
if n>99 then
L := 99 else
L := n;
count := L; // calculate real count of items in this beginbfchar
for i := ndx to ndx+L-1 do
if WinAnsiFont.fUsedWide[i].int=0 then
dec(count);
ToUnicode.Writer.Add(count).Add(' beginbfchar'#10);
for i := ndx to ndx+L-1 do
with WinAnsiFont.fUsedWide[i] do
if int<>0 then
ToUnicode.Writer.Add('<').AddHex4(Glyph).
Add('> <').AddHex4(WinAnsiFont.fUsedWideChar.Values[i]).Add('>'#10);
dec(n,L);
inc(ndx,L);
ToUnicode.Writer.Add('endbfchar'#10);
end;
ToUnicode.Writer.Add('endcmap'#10+
'CMapName currentdict /CMap defineresource pop'#10'end'#10'end');
Data.AddItem('ToUnicode', ToUnicode);
end else begin
// 2. WinAnsi Font
for c := #32 to #255 do
if c in fWinAnsiUsed then begin
fFirstChar := ord(c);
Break;
end;
for c := #255 downto #32 do
if c in fWinAnsiUsed then begin
fLastChar := ord(c);
Break;
end;
if fFirstChar<>0 then begin
Data.AddItem('FirstChar',fFirstChar);
Data.AddItem('LastChar',fLastChar);
WR.Add('[');
for c := AnsiChar(fFirstChar) to AnsiChar(fLastChar) do
if c in fWinAnsiUsed then
WR.AddWithSpace(fWinAnsiWidth[c]) else
WR.Add('0 ');
FData.AddItem('Widths',TPdfRawText.Create(WR.Add(']').ToPDFString));
end;
// embedd True Type font into the PDF file (allow subset of used glyph)
if (fDoc.PDFA<>pdfaNone) or (fDoc.EmbeddedTTF and
((fDoc.fEmbeddedTTFIgnore=nil) or (fDoc.fEmbeddedTTFIgnore.
IndexOf(fDoc.FTrueTypeFonts[fTrueTypeFontsIndex-1])<0))) then begin
fDoc.GetDCWithFont(self);
{$ifndef DELPHI5OROLDER}
// is the font in a .ttc collection?
ttfSize := GetFontData(fDoc.FDC,TTCF_TABLE,0,nil,0);
if ttfSize<>GDI_ERROR then begin
// Yes, the font is in a .ttc collection
// find out how many fonts are included in the collection
if GetFontData(fDoc.FDC,TTCF_TABLE,8,@ttcNumFonts,4) <> GDI_ERROR then
ttcNumFonts := bswap32(ttcNumFonts) else
ttcNumFonts := 1;
// we need to find out the index of the font within the ttc collection
// (this is not easy, so GetTTCIndex uses lookup on known ttc fonts)
if (ttcNumFonts < 2) or not
GetTTCIndex(fDoc.FTrueTypeFonts[fTrueTypeFontsIndex-1],ttcIndex,ttcNumFonts) then
ttcIndex := 0;
usFlags := TTFCFP_FLAGS_SUBSET or TTFCFP_FLAGS_TTC;
tableTag := TTCF_TABLE;
end else
{$endif}
begin
ttfSize := GetFontData(fDoc.FDC,0,0,nil,0);
usFlags := TTFCFP_FLAGS_SUBSET;
ttcIndex := 0;
tableTag := 0;
end;
if ttfSize<>GDI_ERROR then begin
SetLength(ttf,ttfSize);
if GetFontData(fDoc.FDC,tableTag,0,pointer(ttf),ttfSize)<>GDI_ERROR then begin
fFontFile2 := TPdfStream.Create(fDoc);
if not fDoc.fEmbeddedWholeTTF then begin
if FontSub=INVALID_HANDLE_VALUE then begin
FontSub := SafeLoadLibrary('FontSub.dll');
if FontSub<>0 then
CreateFontPackage := GetProcAddress(FontSub,'CreateFontPackage');
end;
if (FontSub<>0) and (@CreateFontPackage<>nil) then begin
// subset magic is done by Windows (API available since XP) :)
Used.Count := 0;
for i := fFirstChar to fLastChar do
if AnsiChar(i) in fWinAnsiUsed then
Used.Add(WinAnsiConvert.AnsiToWide[i]);
with fUsedWideChar do
for i := 0 to Count-1 do
Used.Add(Values[i]);
if CreateFontPackage(pointer(ttf),ttfSize,
SubSetData,SubSetMem,SubSetSize,
usFlags,ttcIndex,TTFMFP_SUBSET,0,
TTFCFP_MS_PLATFORMID,TTFCFP_DONT_CARE,
pointer(Used.Values),Used.Count,
@lpfnAllocate,@lpfnReAllocate,@lpfnFree,nil)=0 then begin
// subset was created successfully -> save to PDF file
ReduceTTF(ttf,SubSetData,SubSetSize);
FreeMem(SubSetData);
// see 5.5.3 Font Subsets: begins with a tag followed by a +
TPdfName(fFontDescriptor.ValueByName('FontName')).AppendPrefix;
TPdfName(fFontDescriptor.ValueByName('BaseFont')).AppendPrefix;
end;
end;
end;
fFontFile2.Writer.Add(ttf);
fFontFile2.FAttributes.AddItem('Length1',length(ttf));
// /FontDescriptor is common to WinAnsi and Unicode fonts
fFontDescriptor.AddItem('FontFile2',fFontFile2);
end;
end;
end;
end;
finally
WR.Free;
DS.Free;
end;
end;
procedure TPdfFontTrueType.CreateAssociatedUnicodeFont;
begin
fUnicodeFont := TPdfFontTrueType.Create(
fDoc,fTrueTypeFontsIndex-1,fStyle,fLogFont,self);
end;
{ TPdfDestination }
constructor TPdfDestination.Create(APdfDoc: TPdfDocument);
begin
inherited Create;
FDoc := APdfDoc;
if FDoc=nil then
raise EPdfInvalidOperation.Create('TPdfDestination');
FPage := FDoc.Canvas.Page;
FZoom := 1;
end;
destructor TPdfDestination.Destroy;
begin
if FReference<>nil then
FReference.Free;
inherited;
end;
function TPdfDestination.GetElement(Index: integer): Integer;
begin
result := FValues[Index];
end;
procedure TPdfDestination.SetElement(Index: integer; Value: Integer);
begin
if FValues[Index]<>Value then
if Value < 0 then
FValues[Index] := -1 else
FValues[Index] := Value;
end;
procedure TPdfDestination.SetZoom(Value: Single);
begin
if Value<>FZoom then
if Value < 0 then
raise EPdfInvalidValue.Create('Zoom<0') else
if Value > PDF_MAX_ZOOMSIZE then
raise EPdfInvalidValue.CreateFmt('Zoom>%d', [PDF_MAX_ZOOMSIZE]) else
FZoom := Value;
end;
function TPdfDestination.GetPageWidth: Integer;
begin
if FPage.FMediaBox<> nil then
result := TPdfNumber(FPage.FMediaBox.Items[2]).Value else
result := FDoc.DefaultPageWidth;
end;
function TPdfDestination.GetPageHeight: Integer;
begin
if FPage.FMediaBox<>nil then
result := TPdfNumber(FPage.FMediaBox.Items[3]).Value else
result := FDoc.DefaultPageHeight;
end;
function TPdfDestination.GetValue: TPdfArray;
const
DEST_MAX_VALUE = 100;
begin
// create TPdfArray object from the specified values.
// the values which are not used are ignored.
result := TPdfArray.Create(FDoc.FXref);
with result do begin
AddItem(FPage);
AddItem(TPdfName.Create(PDF_DESTINATION_TYPE_NAMES[FType]));
case FType of
// if the type is dtXYZ, only Left, Top and Zoom values are used,
// other properties are ignored.
dtXYZ:
begin
if FValues[0] >= -DEST_MAX_VALUE then
AddItem(TPdfNumber.Create(Left)) else
AddItem(TPdfNull.Create);
if FValues[1] >= -DEST_MAX_VALUE then
AddItem(TPdfNumber.Create(Top)) else
AddItem(TPdfNull.Create);
if FZoom < 0 then
FZoom := 0;
AddItem(TPdfReal.Create(FZoom));
end;
// if the type is dtFitR, all values except Zoom are used.
dtFitR:
begin
if FValues[0] >= -DEST_MAX_VALUE then
AddItem(TPdfNumber.Create(Left)) else
AddItem(TPdfNull.Create);
if FValues[1] >= -DEST_MAX_VALUE then
AddItem(TPdfNumber.Create(Bottom)) else
AddItem(TPdfNull.Create);
if FValues[2] >= 0 then
AddItem(TPdfNumber.Create(Right)) else
AddItem(TPdfNull.Create);
if FValues[3] >= 0 then
AddItem(TPdfNumber.Create(Top)) else
AddItem(TPdfNull.Create);
end;
// if the type is dtFitH or dtFitBH, only Top property is used.
dtFitH, dtFitBH:
if FValues[1] >= -DEST_MAX_VALUE then
AddItem(TPdfNumber.Create(Top)) else
AddItem(TPdfNull.Create);
// if the type is dtFitV or dtFitBV, only Top property is used.
dtFitV, dtFitBV:
if FValues[0] >= -DEST_MAX_VALUE then
AddItem(TPdfNumber.Create(Left)) else
AddItem(TPdfNull.Create);
end;
end;
end;
{ TPdfOutlineEntry }
constructor TPdfOutlineEntry.Create(AParent: TPdfOutlineEntry;
TopPosition: integer=-1);
begin
inherited Create;
if AParent=nil then
raise EPdfInvalidValue.Create('CreateEntry');
FParent := AParent;
FDoc := AParent.Doc;
Data := TPdfDictionary.Create(FDoc.FXref);
FDoc.FXref.AddObject(Data);
FDoc.FObjectList.Add(Self);
if TopPosition>=0 then begin
if FDoc.Canvas.FPage=nil then
FDoc.RaiseInvalidOperation;
FDest := FDoc.CreateDestination;
FDest.DestinationType := dtXYZ;
FDest.Zoom := 0; // will leave Zoom factor unchanged
FDest.Left := 0; // go to left side of the page
FDest.Top := TopPosition;
end;
end;
destructor TPdfOutlineEntry.Destroy;
begin
if FReference<>nil then
FReference.Free;
inherited;
end;
function TPdfOutlineEntry.AddChild(TopPosition: Integer=-1): TPdfOutlineEntry;
var TmpEntry: TPdfOutlineEntry;
begin
// increment total Count variable
inc(FCount);
TmpEntry := Parent;
while TmpEntry<>nil do begin
TmpEntry.FCount := TmpEntry.FCount + 1;
TmpEntry := TmpEntry.Parent;
end;
result := TPdfOutlineEntry.Create(Self,TopPosition);
if FFirst=nil then
FFirst := Result;
if FLast<>nil then
FLast.FNext := Result;
Result.FPrev := FLast;
FLast := Result;
end;
procedure TPdfOutlineEntry.Save;
begin
Data.AddItem('Parent',FParent.Data);
if Opened then
Data.AddItem('Count', FCount) else
Data.AddItem('Count', -FCount);
Data.AddItemTextString('Title', FTitle);
if FDest<>nil then
Data.AddItem('Dest', FDest.GetValue);
if FFirst<>nil then
begin
Data.AddItem('First', FFirst.Data);
FFirst.Save;
end;
if FLast<>nil then
Data.AddItem('Last', FLast.Data);
if FPrev<>nil then
Data.AddItem('Prev', FPrev.Data);
if FNext<>nil then
begin
Data.AddItem('Next', FNext.Data);
FNext.Save;
end;
end;
{ TPdfOutlineRoot }
constructor TPdfOutlineRoot.Create(ADoc: TPdfDocument);
begin
// no inherited Create() for this "fake" entry
FDoc := ADoc;
FOpened := true;
Data := TPdfDictionary.Create(ADoc.FXref);
FDoc.FXref.AddObject(Data);
Data.AddItem('Type', 'Outlines');
FDoc.FObjectList.Add(Self);
end;
procedure TPdfOutlineRoot.Save;
begin
Data.AddItem('Count', FCount);
if FFirst<>nil then begin
Data.AddItem('First', FFirst.Data);
FFirst.Save;
end;
if FLast<>nil then
Data.AddItem('Last', FLast.Data);
end;
{ TPdfTTF }
constructor TPdfTTF.Create(aUnicodeTTF: TPdfFontTrueType);
var P: pointer;
SubTable: ^TCmapSubTableArray absolute P;
Header: ^TCmapHeader;
i, n, code, ndx: PtrInt;
off: cardinal;
glyphIndex: integer;
idDeltai, glyphi: PtrInt;
W, numOfLongHorMetrics: word;
fUnitsPerEmShr: cardinal;
begin
// retrieve the 'cmap' (character code mapping) table
// see http://developer.apple.com/fonts/TTRefMan/RM06/Chap6cmap.html
// and http://www.microsoft.com/typography/OTSPEC/cmap.htm
P := GetTTFData(aUnicodeTTF.fDoc.FDC,'cmap',fcmap);
if P=nil then
exit;
Header := P;
inc(PtrInt(P),SizeOf(TCmapHeader));
off := 0;
for i := 0 to Header^.numberSubtables-1 do
with SubTable^[i] do
if platformID=TTFCFP_MS_PLATFORMID then
if platformSpecificID=TTFCFP_SYMBOL_CHAR_SET then begin
aUnicodeTTF.fIsSymbolFont := true;
off := offset;
end else
if platformSpecificID=TTFCFP_UNICODE_CHAR_SET then begin
aUnicodeTTF.fIsSymbolFont := false;
off := offset;
break; // prefered specific ID
end;
if (off=0) or (off and 1<>0) then
exit; // we handle only Microsoft platform
i := LongRec(off).Lo; // offset swap to bswap conversion :)
LongRec(off).Lo := LongRec(off).Hi;
LongRec(off).Hi := i;
if off>cardinal(Length(fcmap)*2) then
exit; // avoid GPF
fmt4 := Pointer(PtrUInt(fcmap)+off);
with fmt4^ do begin
if format<>4 then
Exit; // we handle only cmap table format 4
endCode := pointer(PtrUInt(@format)+sizeof(TCmapFmt4));
startCode := pointer(PtrUInt(endCode)+segCountX2+2); // +2 = reservedPad
idDelta := pointer(PtrUInt(startCode)+segCountX2);
idRangeOffset := pointer(PtrUInt(idDelta)+segCountX2);
glyphIndexArray := pointer(PtrUInt(idRangeOffset)+segCountX2);
end;
// 'head', 'hmtx' (horizontal metrics) and 'hhea' (Horizontal Header) tables
// see http://developer.apple.com/fonts/TTRefMan/RM06/Chap6hmtx.html
head := GetTTFData(aUnicodeTTF.fDoc.FDC,'head',fhead);
if head=nil then
exit;
P := GetTTFData(aUnicodeTTF.fDoc.FDC,'hmtx',fhmtx);
if P=nil then
exit;
hhea := GetTTFData(aUnicodeTTF.fDoc.FDC,'hhea',fhhea);
if hhea=nil then
exit;
// fill aUnicodeTTF.fUsedWide[] and aUnicodeTTF.fUsedWideChar data
n := fmt4^.segCountX2 shr 1;
with aUnicodeTTF.fUsedWideChar do begin
for i := 0 to n-1 do
inc(Count,endCode[i]-startCode[i]+1);
SetLength(Values,Count);
SetLength(aUnicodeTTF.fUsedWide,Count);
end;
ndx := 0;
for i := 0 to n-1 do begin
idDeltai := idDelta[i];
glyphi := idRangeOffset[i];
if glyphi<>0 then
glyphi := glyphi shr 1+i-n-startCode[i];
for code := startCode[i] to endCode[i] do begin
aUnicodeTTF.fUsedWideChar.Values[ndx] := code;
if glyphi=0 then
glyphIndex := code+idDeltai else begin
glyphIndex := glyphIndexArray[glyphi+code];
if glyphIndex<>0 then
inc(glyphIndex,idDeltai);
end;
aUnicodeTTF.fUsedWide[ndx].Glyph := glyphIndex;
inc(ndx);
end;
end;
// UnitsPerEm range is from 16 to 16384. This value should be a power of 2.
// (from http://www.microsoft.com/typography/OTSPEC/head.htm)
fUnitsPerEmShr := 0; // fastest integer div for width calculating
for i := 14 downto 4 do
if GetBitPtr(@head^.UnitsPerEm,i) then begin
fUnitsPerEmShr := i;
break;
end;
if fUnitsPerEmShr<>0 then begin
W := (cardinal(fhmtx[0])*1000) shr fUnitsPerEmShr;
if aUnicodeTTF.FixedWidth then
for i := 0 to aUnicodeTTF.fUsedWideChar.Count-1 do
aUnicodeTTF.fUsedWide[i].Width := W
else begin
numOfLongHorMetrics := fhhea[17];
for i := 0 to aUnicodeTTF.fUsedWideChar.Count-1 do
with aUnicodeTTF.fUsedWide[i] do
if Glyph<>0 then
if Glyph<=numOfLongHorMetrics then
Width := (cardinal(fhmtx[Glyph*2])*1000) shr fUnitsPerEmShr else
Width := W;
end;
end;
end;
{ TPdfPage }
constructor TPdfPage.Create(ADoc: TPdfDocument);
begin
if ADoc=nil then // e.g. for TPdfForm.Create
inherited Create(nil) else begin
inherited Create(ADoc.FXRef);
fDoc := ADoc;
// set page size
FMediaBox := TPdfArray.Create(ADoc.FXref,
[0,0,ADoc.DefaultPageWidth,ADoc.DefaultPageHeight]);
AddItem('MediaBox',FMediaBox);
end;
FSaveAtTheEnd := true;
end;
function TPdfPage.GetPageHeight: Integer;
begin
result := TPdfNumber(FMediaBox.Items[3]).Value;
end;
function TPdfPage.GetPageLandscape: Boolean;
begin
result := PageWidth>PageHeight;
end;
function TPdfPage.GetPageWidth: Integer;
begin
result := TPdfNumber(FMediaBox.Items[2]).Value;
end;
function TPdfPage.GetResources(const AName: PDFString): TPdfDictionary;
begin
Result := PdfDictionaryByName('Resources').PdfDictionaryByName(AName);
end;
function TPdfPage.MeasureText(const Text: PDFString; Width: Single): integer;
var ch: AnsiChar;
tmpWidth: Single;
tmpTotalWidth: Single;
i: Integer;
begin
Result := 0;
tmpTotalWidth := 0;
i := 1;
while i<=Length(Text) do begin
ch := Text[i];
tmpWidth := FFont.GetAnsiCharWidth(Text, i) * FFontSize / 1000;
if (FHorizontalScaling<>0) and (FHorizontalScaling<>100) then
tmpWidth := tmpWidth * FHorizontalScaling / 100;
if tmpWidth > 0 then
tmpWidth := tmpWidth + FCharSpace else
tmpWidth := 0;
if (ch=' ') and (FWordSpace > 0) and (i<>Length(Text)) then
tmpWidth := tmpWidth + FWordSpace;
tmpTotalWidth := tmpTotalWidth + tmpWidth;
if tmpTotalWidth > Width then
Break;
if SysLocale.FarEast then
i := NextCharIndex(Text,i) else
inc(i);
Result := i;
end;
end;
procedure TPdfPage.SetCharSpace(Value: Single);
begin
if (Value < PDF_MIN_CHARSPACE) or (VALUE > PDF_MAX_CHARSPACE) then
raise EPdfInvalidValue.Create('SetCharSpace');
FCharSpace := Value;
end;
procedure TPdfPage.SetFontSize(Value: Single);
begin
if (Value < 0) or (Value > PDF_MAX_FONTSIZE) then
raise EPdfInvalidValue.Create('SetFontSize');
FFontSize := Value;
end;
procedure TPdfPage.SetHorizontalScaling(Value: Single);
begin
if Value < PDF_MIN_HORIZONTALSCALING then
Value := PDF_MIN_HORIZONTALSCALING else
if Value > PDF_MAX_HORIZONTALSCALING then
Value := PDF_MAX_HORIZONTALSCALING;
FHorizontalScaling := Value;
end;
procedure TPdfPage.SetLeading(Value: Single);
begin
if (Value < 0) or (Value > PDF_MAX_LEADING) then
raise EPdfInvalidValue.Create('SetLeading');
FLeading := Value;
end;
procedure TPdfPage.SetPageHeight(AValue: integer);
begin
TPdfNumber(FMediaBox.Items[3]).Value := AValue
end;
procedure TPdfPage.SetPageLandscape(const Value: Boolean);
var tmp: integer;
begin
if Value<>PageLandscape then begin
tmp := PageHeight;
PageHeight := PageWidth;
PageWidth := tmp;
end;
end;
procedure TPdfPage.SetPageWidth(AValue: integer);
begin
TPdfNumber(FMediaBox.Items[2]).Value := AValue
end;
procedure TPdfPage.SetWordSpace(Value: Single);
begin
if Value < 0 then
raise EPdfInvalidValue.Create('SetWordSpace');
FWordSpace := Value;
end;
function TPdfPage.TextWidth(const Text: PDFString): Single;
var i: integer;
ch: AnsiChar;
tmpWidth: Single;
begin
Result := 0;
i := 1;
while i<=Length(Text) do begin
ch := Text[i];
tmpWidth := FFont.GetAnsiCharWidth(Text, i) * FFontSize / 1000;
if (FHorizontalScaling<>0) and (FHorizontalScaling<>100) then
tmpWidth := tmpWidth * FHorizontalScaling / 100;
if tmpWidth > 0 then
tmpWidth := tmpWidth + FCharSpace else
tmpWidth := 0;
if (ch=' ') and (FWordSpace > 0) and (i<>Length(Text)) then
tmpWidth := tmpWidth + FWordSpace;
Result := Result + tmpWidth;
if SysLocale.FarEast then
i := NextCharIndex(Text,i) else
inc(i);
end;
Result := Result - FCharSpace;
end;
{$ifdef USE_METAFILE}
{ TPdfDocumentGDI }
function TPdfDocumentGDI.AddPage: TPdfPage;
begin
if (FCanvas<>nil) and (FCanvas.FPage<>nil) then
TPdfPageGdi(FCanvas.FPage).FlushVCLCanvas;
result := inherited AddPage;
FCanvas.FContents.FSaveAtTheEnd := true; // as expected in SaveToStream() below
end;
constructor TPdfDocumentGDI.Create(AUseOutlines: Boolean; ACodePage: integer;
{$ifdef USE_PDFALEVEL]}APDFA: TPdfALevel{$else}APDFA1: boolean{$endif}
{$ifdef USE_PDFSECURITY}; AEncryption: TPdfEncryption{$endif});
begin
inherited;
fTPdfPageClass := TPdfPageGdi;
fUseMetaFileTextPositioning := tpSetTextJustification;
fKerningHScaleBottom := 99.0;
fKerningHScaleTop := 101.0;
end;
function TPdfDocumentGDI.GetVCLCanvas: TCanvas;
begin
with TPdfPageGdi(FCanvas.FPage) do begin
if fVCLCurrentCanvas=nil then
CreateVCLCanvas;
result := fVCLCurrentCanvas;
end;
end;
function TPdfDocumentGDI.GetVCLCanvasSize: TSize;
begin
if (FCanvas<>nil) and (FCanvas.FPage<>nil) then
with TPdfPageGdi(FCanvas.FPage) do begin
if fVCLCurrentCanvas=nil then
CreateVCLCanvas;
result := fVCLCanvasSize;
end else
Int64(result) := 0;
end;
procedure TPdfDocumentGDI.SaveToStream(AStream: TStream; ForceModDate: TDateTime);
var i: integer;
P: TPdfPageGDI;
begin
// write the file header
SaveToStreamDirectBegin(AStream,ForceModDate);
// then draw the pages VCL Canvas content on the fly (miminal memory use)
for i := 0 to fRawPages.Count-1 do begin
P := fRawPages.List[i];
P.FlushVCLCanvas;
if P.fVCLMetaFileCompressed<>'' then begin
P.SetVCLCurrentMetaFile;
try
FCanvas.SetPage(P);
FCanvas.RenderMetaFile(P.fVCLCurrentMetaFile,1,1,0,0,
fUseMetaFileTextPositioning,KerningHScaleBottom,KerningHScaleTop,
fUseMetaFileTextClipping);
finally
FreeAndNil(P.fVCLCurrentMetaFile);
end;
inherited SaveToStreamDirectPageFlush;
end;
end;
// finish to write PDF content to destination stream
SaveToStreamDirectEnd;
end;
procedure TPdfDocumentGDI.SaveToStreamDirectPageFlush(FlushCurrentPageNow: boolean);
var P: TPdfPageGDI;
begin
if fRawPages.Count>0 then begin
P := fRawPages.List[fRawPages.Count-1];
if (P=FCanvas.FPage) and (P.fVCLMetaFileCompressed='') and
(P.fVCLCurrentMetaFile<>nil) and (P.fVCLCurrentCanvas<>nil) then begin
FreeAndNil(P.fVCLCurrentCanvas); // manual P.SetVCLCurrentMetaFile
try
FCanvas.FContents.FSaveAtTheEnd := false; // force flush NOW
FCanvas.RenderMetaFile(P.fVCLCurrentMetaFile,1,1,0,0,
fUseMetaFileTextPositioning,KerningHScaleBottom,KerningHScaleTop,
fUseMetaFileTextClipping);
finally
FreeAndNil(P.fVCLCurrentMetaFile);
end;
end;
end;
inherited SaveToStreamDirectPageFlush;
end;
{ TPdfPageGDI }
procedure TPdfPageGDI.SetVCLCurrentMetaFile;
var tmp: RawByteString;
Stream: TStream;
begin
assert(fVCLCurrentMetaFile=nil);
fVCLCurrentMetaFile := TMetaFile.Create;
fVCLCanvasSize.cx := MulDiv(PageWidth,FDoc.FScreenLogPixels,72);
fVCLCanvasSize.cy := MulDiv(PageHeight,FDoc.FScreenLogPixels,72);
fVCLCurrentMetaFile.Width := fVCLCanvasSize.cx;
fVCLCurrentMetaFile.Height := fVCLCanvasSize.cy;
if fVCLMetaFileCompressed<>'' then begin
SetLength(tmp,SynLZdecompressdestlen(pointer(fVCLMetaFileCompressed)));
SynLZdecompress1(Pointer(fVCLMetaFileCompressed),length(fVCLMetaFileCompressed),pointer(tmp));
Stream := TRawByteStringStream.Create(tmp);
try
fVCLCurrentMetaFile.LoadFromStream(Stream);
finally
Stream.Free;
end;
end;
end;
procedure TPdfPageGDI.CreateVCLCanvas;
begin
SetVCLCurrentMetaFile;
fVCLCurrentCanvas := TMetaFileCanvas.Create(fVCLCurrentMetaFile,FDoc.FDC);
end;
procedure TPdfPageGDI.FlushVCLCanvas;
var Stream: TRawByteStringStream;
len: integer;
begin
if (self=nil) or (fVCLCurrentCanvas=nil) then
exit;
FreeAndNil(fVCLCurrentCanvas);
assert(fVCLCurrentMetaFile<>nil);
Stream := TRawByteStringStream.Create;
try
fVCLCurrentMetaFile.SaveToStream(Stream);
len := Length(Stream.DataString);
SetLength(fVCLMetaFileCompressed,SynLZcompressdestlen(len));
SetLength(fVCLMetaFileCompressed,
SynLZcompress1(pointer(Stream.DataString),len,pointer(fVCLMetaFileCompressed)));
finally
Stream.Free;
end;
FreeAndNil(fVCLCurrentMetaFile);
end;
destructor TPdfPageGDI.Destroy;
begin
FreeAndNil(fVCLCurrentCanvas);
FreeAndNil(fVCLCurrentMetaFile);
inherited;
end;
type
TFontSpec = packed record
angle: SmallInt; // -360..+360
ascent, descent, cell: SmallInt;
end;
TPdfEnumStatePen = record
null: boolean;
color, style: integer;
width: Single;
end;
/// a state of the EMF enumeration engine, for the PDF canvas
// - used also for the SaveDC/RestoreDC stack
TPdfEnumState = record
Position: TPoint;
Moved: boolean;
WinSize, ViewSize: TSize;
WinOrg, ViewOrg: TPoint;
//transformation and clipping
WorldTransform: XFORM; //current
MetaRgn: TPdfBox; //clipping
ClipRgn: TPdfBox; //clipping
ClipRgnNull: Boolean; //clipping
MappingMode: Integer;
PolyFillMode: Integer;
StretchBltMode: Integer;
ArcDirection: Integer;
// current selected pen
pen: TPdfEnumStatePen;
// current selected brush
brush: record
null: boolean;
color: integer;
style: integer;
end;
// current selected font
font: record
color: integer;
align: integer;
BkMode, BkColor: integer;
spec: TFontSpec;
LogFont: TLogFontW; // better be the last entry in TPdfEnumState record
end;
end;
/// internal data used during drawing
// - contain the EMF enumeration engine state parameters
TPdfEnum = class
private
fStrokeColor: integer;
fFillColor: integer;
fPenStyle: integer;
fPenWidth: Single;
fInLined: boolean;
fInitTransformMatrix: XFORM;
fInitMetaRgn: TPdfBox;
procedure SetFillColor(const Value: integer);
procedure SetStrokeColor(const Value: integer);
protected
Canvas: TPdfCanvas;
// the pen/font/brush objects table, indexed like the THandleTable
obj: array of record
case kind: integer of
OBJ_PEN: (PenColor, PenStyle: integer; PenWidth: Single);
OBJ_FONT: (FontSpec: TFontSpec; LogFont: TLogFontW);
OBJ_BRUSH: (BrushColor: integer; BrushNull: boolean; BrushStyle: integer);
end;
// SaveDC/RestoreDC stack
nDC: integer;
DC: array[0..31] of TPdfEnumState;
public
constructor Create(ACanvas: TPdfCanvas);
procedure SaveDC;
procedure RestoreDC;
procedure NeedPen;
procedure NeedBrushAndPen;
procedure FlushPenBrush;
procedure SelectObjectFromIndex(iObject: integer);
procedure TextOut(var R: TEMRExtTextOut);
procedure ScaleMatrix(Custom: PXForm; iMode: Integer);
procedure HandleComment(Kind: TPdfGDIComment; P: PAnsiChar; Len: integer);
procedure CreateFont(aLogFont: PEMRExtCreateFontIndirect);
// if Canvas.Doc.JPEGCompression<>0, draw not as a bitmap but jpeg encoded
procedure DrawBitmap(xs,ys,ws,hs, xd,yd,wd,hd,usage: integer;
Bmi: PBitmapInfo; bits: pointer; clipRect: PRect; xSrcTransform: PXForm; dwRop: DWord;
transparent: TPdfColorRGB = $FFFFFFFF);
procedure FillRectangle(const Rect: TRect; ResetNewPath: boolean);
// the current value set to SetRGBFillColor (rg)
property FillColor: integer read fFillColor write SetFillColor;
// the current value set to SetRGBStrokeColor (RG)
property StrokeColor: integer read fStrokeColor write SetStrokeColor;
// WorldTransform
property InitTransformMatrix: XFORM read fInitTransformMatrix write fInitTransformMatrix;
// MetaRgn - clipping
procedure InitMetaRgn(ClientRect: TRect);
procedure SetMetaRgn;
// intersect - clipping
function IntersectClipRect(const ClpRect: TPdfBox; const CurrRect: TPdfBox): TPdfBox;
procedure ExtSelectClipRgn(data: PRgnDataHeader; iMode: DWord);
// get current clipping area
function GetClipRect: TPdfBox;
procedure GradientFill(data: PEMGradientFill);
procedure PolyPoly(data: PEMRPolyPolygon; iType: Integer);
end;
const
STOCKBRUSHCOLOR: array[WHITE_BRUSH..BLACK_BRUSH] of integer = (
clWhite, $AAAAAA, $808080, $666666, clBlack);
STOCKPENCOLOR: array[WHITE_PEN..BLACK_PEN] of integer = (
clWhite, clBlack);
function CenterPoint(const Rect: TRect): TPoint; {$ifdef HASINLINE}inline;{$endif}
begin
result.X := (Rect.Right+Rect.Left) div 2;
result.Y := (Rect.Bottom+Rect.Top) div 2;
end;
/// EMF enumeration callback function, called from GDI
// - draw most content on PDF canvas (do not render 100% GDI content yet)
function EnumEMFFunc(DC: HDC; var Table: THandleTable; R: PEnhMetaRecord;
NumObjects: DWord; E: TPdfEnum): LongBool; stdcall;
var i: integer;
InitTransX: XForm;
polytypes: PByteArray;
begin
result := true;
with E.DC[E.nDC] do
case R^.iType of
EMR_HEADER: begin
SetLength(E.obj,PEnhMetaHeader(R)^.nHandles);
WinOrg.X := 0;
WinOrg.Y := 0;
ViewOrg.X := 0;
ViewOrg.Y := 0;
MappingMode := GetMapMode(DC);
PolyFillMode := GetPolyFillMode(DC);
StretchBltMode := GetStretchBltMode(DC);
ArcDirection := AD_COUNTERCLOCKWISE;
InitTransX := DefaultIdentityMatrix;
E.InitTransformMatrix := InitTransX;
E.ScaleMatrix(@InitTransX, MWT_SET); // keep init
E.InitMetaRgn(PEnhMetaHeader(R)^.rclBounds);
end;
EMR_SETWINDOWEXTEX:
WinSize := PEMRSetWindowExtEx(R)^.szlExtent;
EMR_SETWINDOWORGEX:
WinOrg := PEMRSetWindowOrgEx(R)^.ptlOrigin;
EMR_SETVIEWPORTEXTEX:
ViewSize := PEMRSetViewPortExtEx(R)^.szlExtent;
EMR_SETVIEWPORTORGEX:
ViewOrg := PEMRSetViewPortOrgEx(R)^.ptlOrigin;
EMR_SETBKMODE:
font.BkMode := PEMRSetBkMode(R)^.iMode;
EMR_SETBKCOLOR:
if PEMRSetBkColor(R)^.crColor=cardinal(clNone) then
font.BkColor := 0 else
font.BkColor := PEMRSetBkColor(R)^.crColor;
EMR_SETTEXTCOLOR:
if PEMRSetTextColor(R)^.crColor=cardinal(clNone) then
font.Color := 0 else
font.Color := PEMRSetTextColor(R)^.crColor;
EMR_SETTEXTALIGN:
font.Align := PEMRSetTextAlign(R)^.iMode;
EMR_EXTTEXTOUTA, EMR_EXTTEXTOUTW:
E.TextOut(PEMRExtTextOut(R)^);
EMR_SAVEDC:
E.SaveDC;
EMR_RESTOREDC:
E.RestoreDC;
EMR_SETWORLDTRANSFORM:
E.ScaleMatrix(@PEMRSetWorldTransform(R)^.xform, MWT_SET);
EMR_CREATEPEN:
with PEMRCreatePen(R)^ do
if ihPen-1<cardinal(length(E.Obj)) then
with E.obj[ihPen-1] do begin
kind := OBJ_PEN;
PenColor := lopn.lopnColor;
PenWidth := lopn.lopnWidth.X;
PenStyle := lopn.lopnStyle;
end;
EMR_CREATEBRUSHINDIRECT:
with PEMRCreateBrushIndirect(R)^ do
if ihBrush-1<cardinal(length(E.Obj)) then
with E.obj[ihBrush-1] do begin
kind := OBJ_BRUSH;
BrushColor := lb.lbColor;
BrushNull := (lb.lbStyle=BS_NULL);
BrushStyle := lb.lbStyle;
end;
EMR_EXTCREATEFONTINDIRECTW:
E.CreateFont(PEMRExtCreateFontIndirect(R));
EMR_DELETEOBJECT:
with PEMRDeleteObject(R)^ do
if ihObject-1<cardinal(length(E.Obj)) then // avoid GPF
E.obj[ihObject-1].kind := 0;
EMR_SELECTOBJECT:
E.SelectObjectFromIndex(PEMRSelectObject(R)^.ihObject);
EMR_MOVETOEX: begin
Position := PEMRMoveToEx(R)^.ptl; // temp var to ignore unused moves
if E.Canvas.FNewPath then begin
E.Canvas.MoveToI(Position.X,Position.Y);
Moved := true;
end else
Moved := false;
end;
EMR_LINETO: begin
E.NeedPen;
if not E.Canvas.FNewPath and not Moved then
E.Canvas.MoveToI(Position.X,Position.Y);
E.Canvas.LineToI(PEMRLineTo(R)^.ptl.X,PEMRLineTo(R)^.ptl.Y);
Position := PEMRLineTo(R)^.ptl;
Moved := false;
E.fInLined := true;
if not E.Canvas.FNewPath then
if not pen.null then
E.Canvas.Stroke
end;
EMR_RECTANGLE, EMR_ELLIPSE: begin
E.NeedBrushAndPen;
with E.Canvas.BoxI(PEMRRectangle(R)^.rclBox,true) do
case R^.iType of
EMR_RECTANGLE: E.Canvas.Rectangle(Left,Top,Width,Height);
EMR_ELLIPSE: E.Canvas.Ellipse(Left,Top,Width,Height);
end;
E.FlushPenBrush;
end;
EMR_ROUNDRECT: begin
NormalizeRect(PEMRRoundRect(R)^.rclBox);
E.NeedBrushAndPen;
with PEMRRoundRect(R)^ do
E.Canvas.RoundRectI(rclBox.left,rclBox.top,rclBox.right,rclBox.bottom,
szlCorner.cx,szlCorner.cy);
E.FlushPenBrush;
end;
{$ifdef USE_ARC}
EMR_ARC: begin
NormalizeRect(PEMRARC(R)^.rclBox);
E.NeedPen;
with PEMRARC(R)^, CenterPoint(rclBox) do
E.Canvas.ARCI(x, y, rclBox.Right-rclBox.Left, rclBox.Bottom-rclBox.Top,
ptlStart.x, ptlStart.y, ptlEnd.x, ptlEnd.y,
e.dc[e.nDC].ArcDirection = AD_CLOCKWISE,
acArc, Position);
E.Canvas.Stroke;
end;
EMR_ARCTO: begin
NormalizeRect(PEMRARCTO(R)^.rclBox);
E.NeedPen;
if not E.Canvas.FNewPath and not Moved then
E.Canvas.MoveToI(Position.X,Position.Y);
with PEMRARC(R)^, CenterPoint(rclBox) do begin
// E.Canvas.LineTo(ptlStart.x, ptlStart.y);
E.Canvas.ARCI(x, y, rclBox.Right-rclBox.Left, rclBox.Bottom-rclBox.Top,
ptlStart.x, ptlStart.y, ptlEnd.x, ptlEnd.y,
e.dc[e.nDC].ArcDirection = AD_CLOCKWISE,
acArcTo,
Position);
Moved := false;
E.fInLined := true;
if not E.Canvas.FNewPath then
if not pen.null then
E.Canvas.Stroke ;
end;
end;
EMR_PIE: begin
NormalizeRect(PEMRPie(R)^.rclBox);
E.NeedBrushAndPen;
with PEMRPie(R)^, CenterPoint(rclBox) do
E.Canvas.ARCI(x, y, rclBox.Right-rclBox.Left, rclBox.Bottom-rclBox.Top,
ptlStart.x, ptlStart.y, ptlEnd.x, ptlEnd.y,
e.dc[e.nDC].ArcDirection = AD_CLOCKWISE,
acPie, Position);
if pen.null then
E.Canvas.Fill else
E.Canvas.FillStroke;
end;
EMR_CHORD: begin
NormalizeRect(PEMRChord(R)^.rclBox);
E.NeedBrushAndPen;
with PEMRChord(R)^, CenterPoint(rclBox) do
E.Canvas.ARCI(x, y, rclBox.Right-rclBox.Left, rclBox.Bottom-rclBox.Top,
ptlStart.x, ptlStart.y, ptlEnd.x, ptlEnd.y,
e.dc[e.nDC].ArcDirection = AD_CLOCKWISE,
acChoord,Position);
if pen.null then
E.Canvas.Fill else
E.Canvas.FillStroke;
end;
{$endif USE_ARC}
EMR_FILLRGN: begin
E.SelectObjectFromIndex(PEMRFillRgn(R)^.ihBrush);
E.NeedBrushAndPen;
E.FillRectangle(PRgnDataHeader(@PEMRFillRgn(R)^.RgnData[0])^.rcBound,false);
end;
EMR_POLYGON, EMR_POLYLINE, EMR_POLYGON16, EMR_POLYLINE16:
if not brush.null or not pen.null then begin
if R^.iType in [EMR_POLYGON,EMR_POLYGON16] then
E.NeedBrushAndPen else
E.NeedPen;
if R^.iType in [EMR_POLYGON, EMR_POLYLINE] then begin
E.Canvas.MoveToI(PEMRPolyLine(R)^.aptl[0].X,PEMRPolyLine(R)^.aptl[0].Y);
for i := 1 to PEMRPolyLine(R)^.cptl-1 do
E.Canvas.LineToI(PEMRPolyLine(R)^.aptl[i].X,PEMRPolyLine(R)^.aptl[i].Y);
if PEMRPolyLine(R)^.cptl>0 then
Position := PEMRPolyLine(R)^.aptl[PEMRPolyLine(R)^.cptl-1] else
Position := PEMRPolyLine(R)^.aptl[0];
end else begin
E.Canvas.MoveToI(PEMRPolyLine16(R)^.apts[0].X,PEMRPolyLine16(R)^.apts[0].Y);
if PEMRPolyLine16(R)^.cpts>0 then begin
for i := 1 to PEMRPolyLine16(R)^.cpts-1 do
E.Canvas.LineToI(PEMRPolyLine16(R)^.apts[i].X,PEMRPolyLine16(R)^.apts[i].Y);
with PEMRPolyLine16(R)^.apts[PEMRPolyLine16(R)^.cpts-1] do begin
Position.X := X;
Position.Y := Y;
end;
end else begin
Position.X := PEMRPolyLine16(R)^.apts[0].X;
Position.Y := PEMRPolyLine16(R)^.apts[0].Y;
end;
end;
Moved := false;
if R^.iType in [EMR_POLYGON,EMR_POLYGON16] then begin
E.Canvas.Closepath;
E.FlushPenBrush;
end else
if not pen.null then
E.Canvas.Stroke else // for lines
E.Canvas.NewPath;
end;
EMR_POLYPOLYGON, EMR_POLYPOLYGON16, EMR_POLYPOLYLINE, EMR_POLYPOLYLINE16:
E.PolyPoly(PEMRPolyPolygon(R), R^.iType);
EMR_POLYBEZIER: begin
if not pen.null then
E.NeedPen;
E.Canvas.MoveToI(PEMRPolyBezier(R)^.aptl[0].X,PEMRPolyBezier(R)^.aptl[0].Y);
for i := 0 to (PEMRPolyBezier(R)^.cptl div 3)-1 do
E.Canvas.CurveToCI(PEMRPolyBezier(R)^.aptl[i*3+1].X,PEMRPolyBezier(R)^.aptl[i*3+1].Y,
PEMRPolyBezier(R)^.aptl[i*3+2].X,PEMRPolyBezier(R)^.aptl[i*3+2].Y,
PEMRPolyBezier(R)^.aptl[i*3+3].X,PEMRPolyBezier(R)^.aptl[i*3+3].Y);
if PEMRPolyBezier(R)^.cptl>0 then
Position := PEMRPolyBezier(R)^.aptl[PEMRPolyBezier(R)^.cptl-1] else
Position := PEMRPolyBezier(R)^.aptl[0];
Moved := false;
if not E.Canvas.FNewPath then
if not pen.null then
E.Canvas.Stroke else
E.Canvas.NewPath;
end;
EMR_POLYBEZIER16: begin
if not pen.null then
E.NeedPen;
E.Canvas.MoveToI(PEMRPolyBezier16(R)^.apts[0].X,PEMRPolyBezier16(R)^.apts[0].Y);
if PEMRPolyBezier16(R)^.cpts>0 then begin
for i := 0 to (PEMRPolyBezier16(R)^.cpts div 3)-1 do
E.Canvas.CurveToCI(PEMRPolyBezier16(R)^.apts[i*3+1].X,PEMRPolyBezier16(R)^.apts[i*3+1].Y,
PEMRPolyBezier16(R)^.apts[i*3+2].X,PEMRPolyBezier16(R)^.apts[i*3+2].Y,
PEMRPolyBezier16(R)^.apts[i*3+3].X,PEMRPolyBezier16(R)^.apts[i*3+3].Y);
with PEMRPolyBezier16(R)^.apts[PEMRPolyBezier16(R)^.cpts-1] do begin
Position.X := X;
Position.Y := Y;
end;
end else begin
Position.X := PEMRPolyBezier16(R)^.apts[0].X;
Position.Y := PEMRPolyBezier16(R)^.apts[0].Y;
end;
Moved := false;
if not E.Canvas.FNewPath then
if not pen.null then
E.Canvas.Stroke else
E.Canvas.NewPath;
end;
EMR_POLYBEZIERTO: begin
if not pen.null then
E.NeedPen;
if not E.Canvas.FNewPath then
if not Moved then
E.Canvas.MoveToI(Position.X,Position.Y);
if PEMRPolyBezierTo(R)^.cptl>0 then begin
for i := 0 to (PEMRPolyBezierTo(R)^.cptl div 3)-1 do
E.Canvas.CurveToCI(PEMRPolyBezierTo(R)^.aptl[i*3].X,PEMRPolyBezierTo(R)^.aptl[i*3].Y,
PEMRPolyBezierTo(R)^.aptl[i*3+1].X,PEMRPolyBezierTo(R)^.aptl[i*3+1].Y,
PEMRPolyBezierTo(R)^.aptl[i*3+2].X,PEMRPolyBezierTo(R)^.aptl[i*3+2].Y);
Position := PEMRPolyBezierTo(R)^.aptl[PEMRPolyBezierTo(R)^.cptl-1];
end;
Moved := false;
if not E.Canvas.FNewPath then
if not pen.null then
E.Canvas.Stroke else
E.Canvas.NewPath;
end;
EMR_POLYBEZIERTO16: begin
if not pen.null then
E.NeedPen;
if not E.Canvas.FNewPath then
if not Moved then
E.Canvas.MoveToI(Position.X,Position.Y);
if PEMRPolyBezierTo16(R)^.cpts>0 then begin
for i := 0 to (PEMRPolyBezierTo16(R)^.cpts div 3)-1 do
E.Canvas.CurveToCI(PEMRPolyBezierTo16(R)^.apts[i*3].X,PEMRPolyBezierTo16(R)^.apts[i*3].Y,
PEMRPolyBezierTo16(R)^.apts[i*3+1].X,PEMRPolyBezierTo16(R)^.apts[i*3+1].Y,
PEMRPolyBezierTo16(R)^.apts[i*3+2].X,PEMRPolyBezierTo16(R)^.apts[i*3+2].Y);
with PEMRPolyBezierTo16(R)^.apts[PEMRPolyBezierTo16(R)^.cpts-1] do begin
Position.X := X;
Position.Y := Y;
end;
end;
Moved := false;
if not E.Canvas.FNewPath then
if not pen.null then
E.Canvas.Stroke else
E.Canvas.NewPath;
end;
EMR_POLYLINETO, EMR_POLYLINETO16: begin
if not pen.null then
E.NeedPen;
if not E.Canvas.FNewPath then begin
E.Canvas.NewPath;
if not Moved then
E.Canvas.MoveToI(Position.X,Position.Y);
end;
if R^.iType=EMR_POLYLINETO then begin
if PEMRPolyLineTo(R)^.cptl>0 then begin
for i := 0 to PEMRPolyLineTo(R)^.cptl-1 do
E.Canvas.LineToI(PEMRPolyLineTo(R)^.aptl[i].X,PEMRPolyLineTo(R)^.aptl[i].Y);
Position := PEMRPolyLineTo(R)^.aptl[PEMRPolyLineTo(R)^.cptl-1];
end;
end else // EMR_POLYLINETO16
if PEMRPolyLineTo16(R)^.cpts>0 then begin
for i := 0 to PEMRPolyLineTo16(R)^.cpts-1 do
E.Canvas.LineToI(PEMRPolyLineTo16(R)^.apts[i].X,PEMRPolyLineTo16(R)^.apts[i].Y);
with PEMRPolyLineTo16(R)^.apts[PEMRPolyLineTo16(R)^.cpts-1] do begin
Position.X := X;
Position.Y := Y;
end;
end;
Moved := false;
if not E.Canvas.FNewPath then
if not pen.null then
E.Canvas.Stroke else
E.Canvas.NewPath;
end;
EMR_POLYDRAW:
if PEMRPolyDraw(R)^.cptl>0 then begin
if not pen.null then
E.NeedPen;
polytypes := @PEMRPolyDraw(R)^.aptl[PEMRPolyDraw(R)^.cptl];
i := 0;
while i<integer(PEMRPolyDraw(R)^.cptl) do begin
case polytypes^[i] and not PT_CLOSEFIGURE of
PT_LINETO: begin
E.Canvas.LineToI(PEMRPolyDraw(R)^.aptl[i].X,PEMRPolyDraw(R)^.aptl[i].Y);
if polytypes^[i] and PT_CLOSEFIGURE<>0 then begin
E.Canvas.LineToI(Position.X, Position.Y);
Position := PEMRPolyDraw(R)^.aptl[i];
end;
end;
PT_BEZIERTO: begin
E.Canvas.CurveToCI(PEMRPolyDraw(R)^.aptl[i+1].X,PEMRPolyDraw(R)^.aptl[i+1].Y,
PEMRPolyDraw(R)^.aptl[i+2].X,PEMRPolyDraw(R)^.aptl[i+2].Y,
PEMRPolyDraw(R)^.aptl[i+3].X,PEMRPolyDraw(R)^.aptl[i+3].Y);
inc(i,3);
if polytypes^[i] and PT_CLOSEFIGURE<>0 then begin
E.Canvas.LineToI(Position.X, Position.Y);
Position := PEMRPolyDraw(R)^.aptl[i];
end;
end;
PT_MOVETO: begin
E.Canvas.MoveToI(PEMRPolyDraw(R)^.aptl[i].X,PEMRPolyDraw(R)^.aptl[i].Y);
Position := PEMRPolyDraw(R)^.aptl[i];
end;
else break; // invalid type
end;
inc(i);
end;
Position := PEMRPolyDraw(R)^.aptl[PEMRPolyDraw(R)^.cptl-1];
Moved := False;
if not E.Canvas.FNewPath then
if not pen.null then
E.Canvas.Stroke else
E.Canvas.NewPath;
end;
EMR_POLYDRAW16:
if PEMRPolyDraw16(R)^.cpts>0 then begin
if not pen.null then
E.NeedPen;
polytypes := @PEMRPolyDraw16(R)^.apts[PEMRPolyDraw16(R)^.cpts];
i := 0;
while i<integer(PEMRPolyDraw16(R)^.cpts) do begin
case polytypes^[i] and not PT_CLOSEFIGURE of
PT_LINETO: begin
E.Canvas.LineToI(PEMRPolyDraw16(R)^.apts[i].X,PEMRPolyDraw16(R)^.apts[i].Y);
if polytypes^[i] and PT_CLOSEFIGURE<>0 then begin
E.Canvas.LineToI(Position.X, Position.Y);
with PEMRPolyDraw16(R)^.apts[i] do begin
Position.X := x;
Position.Y := y;
end;
end;
end;
PT_BEZIERTO: begin
E.Canvas.CurveToCI(PEMRPolyDraw16(R)^.apts[i+1].X,PEMRPolyDraw16(R)^.apts[i+1].Y,
PEMRPolyDraw16(R)^.apts[i+2].X,PEMRPolyDraw16(R)^.apts[i+2].Y,
PEMRPolyDraw16(R)^.apts[i+3].X,PEMRPolyDraw16(R)^.apts[i+3].Y);
inc(i,3);
if polytypes^[i] and PT_CLOSEFIGURE<>0 then begin
E.Canvas.LineToI(Position.X, Position.Y);
with PEMRPolyDraw16(R)^.apts[i] do begin
Position.X := x;
Position.Y := y;
end;
end;
end;
PT_MOVETO: begin
E.Canvas.MoveToI(PEMRPolyDraw16(R)^.apts[i].X,PEMRPolyDraw16(R)^.apts[i].Y);
with PEMRPolyDraw16(R)^.apts[i] do begin
Position.X := x;
Position.Y := y;
end;
end;
else break; // invalid type
end;
inc(i);
end;
with PEMRPolyDraw16(R)^.apts[PEMRPolyDraw16(R)^.cpts-1] do begin
Position.X := x;
Position.Y := y;
end;
Moved := false;
if not E.Canvas.FNewPath then
if not pen.null then
E.Canvas.Stroke else
E.Canvas.NewPath;
end;
EMR_BITBLT: begin
with PEMRBitBlt(R)^ do // only handle RGB bitmaps (no palette)
if (offBmiSrc<>0) and (offBitsSrc<>0) then begin
E.DrawBitmap(xSrc,ySrc,cxDest,cyDest,xDest,yDest,cxDest,cyDest,iUsageSrc,
pointer(PtrUInt(R)+offBmiSrc),pointer(PtrUInt(R)+offBitsSrc),
@PEMRBitBlt(R)^.rclBounds, @PEMRBitBlt(R)^.xformSrc, PEMRBitBlt(R)^.dwRop);
end else
case PEMRBitBlt(R)^.dwRop of // we only handle PATCOPY = fillrect
PATCOPY:
with PEMRBitBlt(R)^ do
E.FillRectangle(Rect(xDest,yDest,xDest+cxDest,yDest+cyDest),true);
end;
end;
EMR_STRETCHBLT: begin
with PEMRStretchBlt(R)^ do // only handle RGB bitmaps (no palette)
if (offBmiSrc<>0) and (offBitsSrc<>0) then begin
E.DrawBitmap(xSrc,ySrc,cxSrc,cySrc,xDest,yDest,cxDest,cyDest,iUsageSrc,
pointer(PtrUInt(R)+offBmiSrc),pointer(PtrUInt(R)+offBitsSrc),
@PEMRStretchBlt(R)^.rclBounds, @PEMRStretchBlt(R)^.xformSrc, PEMRStretchBlt(R)^.dwRop);
end else
case PEMRStretchBlt(R)^.dwRop of // we only handle PATCOPY = fillrect
PATCOPY:
with PEMRStretchBlt(R)^ do
E.FillRectangle(Rect(xDest,yDest,xDest+cxDest,yDest+cyDest),true);
end;
end;
EMR_STRETCHDIBITS:
with PEMRStretchDIBits(R)^ do // only handle RGB bitmaps (no palette)
if (offBmiSrc<>0) and (offBitsSrc<>0) then begin
if WorldTransform.eM22 < 0 then
with PBitmapInfo(PtrUInt(R)+offBmiSrc)^ do
bmiHeader.biHeight := -bmiHeader.biHeight;
E.DrawBitmap(xSrc,ySrc,cxSrc,cySrc,xDest,yDest,cxDest,cyDest,iUsageSrc,
pointer(PtrUInt(R)+offBmiSrc),pointer(PtrUInt(R)+offBitsSrc),
@PEMRStretchDIBits(R)^.rclBounds, nil, PEMRStretchDIBits(R)^.dwRop);
end;
EMR_TRANSPARENTBLT:
with PEMRTransparentBLT(R)^ do // only handle RGB bitmaps (no palette)
if (offBmiSrc<>0) and (offBitsSrc<>0) then
E.DrawBitmap(xSrc,ySrc,cxSrc,cySrc,xDest,yDest,cxDest,cyDest,iUsageSrc,
pointer(PtrUInt(R)+offBmiSrc),pointer(PtrUInt(R)+offBitsSrc),
@PEMRTransparentBLT(R)^.rclBounds, @PEMRTransparentBLT(R)^.xformSrc,
SRCCOPY, PEMRTransparentBLT(R)^.dwRop); // dwRop stores the transparent color
EMR_GDICOMMENT:
with PEMRGDICOMMENT(R)^ do
if cbData>1 then
E.HandleComment(TPdfGDIComment(Data[0]),PAnsiChar(@Data)+1,cbData-1);
EMR_MODIFYWORLDTRANSFORM:
with PEMRModifyWorldTransform(R)^ do
E.ScaleMatrix(@PEMRModifyWorldTransform(R)^.xform, iMode);
EMR_EXTCREATEPEN: // approx. - fast solution
with PEMRExtCreatePen(R)^ do
if ihPen-1<cardinal(length(E.Obj)) then
with E.obj[ihPen-1] do begin
kind := OBJ_PEN;
PenColor := elp.elpColor;
PenWidth := elp.elpWidth;
PenStyle := elp.elpPenStyle and (PS_STYLE_MASK or PS_ENDCAP_MASK);
end;
EMR_SETMITERLIMIT:
if PEMRSetMiterLimit(R)^.eMiterLimit>0.1 then
E.Canvas.SetMiterLimit(PEMRSetMiterLimit(R)^.eMiterLimit);
EMR_SETMETARGN:
E.SetMetaRgn;
EMR_EXTSELECTCLIPRGN:
E.ExtSelectClipRgn(@PEMRExtSelectClipRgn(R)^.RgnData[0],PEMRExtSelectClipRgn(R)^.iMode);
EMR_INTERSECTCLIPRECT:
ClipRgn := E.IntersectClipRect(E.Canvas.BoxI(PEMRIntersectClipRect(r)^.rclClip,true),ClipRgn);
EMR_SETMAPMODE:
MappingMode := PEMRSetMapMode(R)^.iMode;
EMR_BEGINPATH: begin
E.Canvas.NewPath;
if not Moved then begin
E.Canvas.MoveToI(Position.X,Position.Y);
Moved := true;
end;
end;
EMR_ENDPATH:
E.Canvas.fNewPath := false;
EMR_ABORTPATH: begin
E.Canvas.NewPath;
E.Canvas.fNewPath := false;
end;
EMR_CLOSEFIGURE:
E.Canvas.ClosePath;
EMR_FILLPATH: begin
if not brush.Null then begin
E.FillColor := brush.color;
E.Canvas.Fill;
end;
E.Canvas.NewPath;
E.Canvas.fNewPath := false;
end;
EMR_STROKEPATH: begin
if not pen.null then begin
E.NeedPen;
E.Canvas.Stroke;
end;
E.Canvas.NewPath;
E.Canvas.fNewPath := false;
end;
EMR_STROKEANDFILLPATH: begin
if not brush.Null then begin
E.NeedPen;
E.FillColor := brush.color;
if not pen.null then
if PolyFillMode=ALTERNATE then
E.Canvas.EofillStroke else
E.Canvas.FillStroke
else if PolyFillMode=ALTERNATE then
E.Canvas.EoFill else
E.Canvas.Fill
end else
if not pen.null then begin
E.NeedPen;
E.Canvas.Stroke;
end;
E.Canvas.NewPath;
E.Canvas.fNewPath := False;
end;
EMR_SETPOLYFILLMODE:
PolyFillMode := PEMRSetPolyFillMode(R)^.iMode;
EMR_GRADIENTFILL:
E.GradientFill(PEMGradientFill(R));
EMR_SETSTRETCHBLTMODE:
StretchBltMode := PEMRSetStretchBltMode(R)^.iMode;
EMR_SETARCDIRECTION:
ArcDirection := PEMRSetArcDirection(R)^.iArcDirection;
EMR_SETPIXELV: begin
// prepare pixel size and color
if pen.width<>1 then begin
E.fPenWidth := E.Canvas.GetWorldFactorX * E.Canvas.FDevScaleX;
E.Canvas.SetLineWidth(E.fPenWidth * E.Canvas.FFactorX);
end;
if PEMRSetPixelV(R)^.crColor<>Cardinal(pen.color) then
E.Canvas.SetRGBStrokeColor(PEMRSetPixelV(R)^.crColor);
// draw point
Position := Point(PEMRSetPixelV(R)^.ptlPixel.X, PEMRSetPixelV(R)^.ptlPixel.Y);
E.Canvas.PointI(Position.X,Position.Y);
E.Canvas.Stroke;
Moved := false;
// roll back pixel size and color
if pen.width<>1 then begin
E.fPenWidth := pen.width * E.Canvas.GetWorldFactorX * E.Canvas.FDevScaleX;
E.Canvas.SetLineWidth(E.fPenWidth * E.Canvas.FFactorX);
end;
if PEMRSetPixelV(R)^.crColor<>Cardinal(pen.color) then
E.Canvas.SetRGBStrokeColor(pen.color);
end;
// TBD
EMR_SMALLTEXTOUT,
EMR_SETROP2,
EMR_ALPHADIBBLEND,
EMR_SETBRUSHORGEX,
EMR_SETICMMODE,
EMR_SELECTPALETTE,
EMR_CREATEPALETTE,
EMR_SETPALETTEENTRIES,
EMR_RESIZEPALETTE,
EMR_REALIZEPALETTE,
EMR_EOF: ; //do nothing
else
R^.iType := R^.iType; // for debug purpose (breakpoint)
end;
case R^.iType of
EMR_RESTOREDC,
EMR_SETWINDOWEXTEX,
EMR_SETWINDOWORGEX,
EMR_SETVIEWPORTEXTEX,
EMR_SETVIEWPORTORGEX,
EMR_SETMAPMODE:
E.ScaleMatrix(nil, MWT_SET); //recalc new transformation
end;
end;
procedure TPdfCanvas.RenderMetaFile(MF: TMetaFile; ScaleX, ScaleY, XOff, YOff: single;
TextPositioning: TPdfCanvasRenderMetaFileTextPositioning;
KerningHScaleBottom, KerningHScaleTop: single;
TextClipping: TPdfCanvasRenderMetaFileTextClipping);
var E: TPdfEnum;
R: TRect;
begin
R.Left := 0;
R.Top := 0;
R.Right := MF.Width;
R.Bottom := MF.Height;
if ScaleY=0 then
ScaleY := ScaleX; // if ScaleY is ommited -> assume symetric coordinates
E := TPdfEnum.Create(self);
try
FOffsetXDef := XOff;
FOffsetYDef := YOff;
FDevScaleX := ScaleX * FFactor;
FDevScaleY := ScaleY * FFactor;
FEmfBounds := R; // keep device rect
fUseMetaFileTextPositioning := TextPositioning;
fUseMetaFileTextClipping := TextClipping;
fKerningHScaleBottom := KerningHScaleBottom;
fKerningHScaleTop := KerningHScaleTop;
if FDoc.FPrinterPxPerInch.X=0 then
FDoc.FPrinterPxPerInch := CurrentPrinterRes; // caching for major speedup
FPrinterPxPerInch := FDoc.FPrinterPxPerInch;
with E.DC[0] do begin
Int64(WinSize) := PInt64(@R.Right)^;
ViewSize := WinSize;
end;
GSave;
try
EnumEnhMetaFile(fDoc.FDC,MF.Handle,@EnumEMFFunc,E,R);
finally
GRestore;
end;
finally
E.Free;
end;
end;
{ TPdfEnum }
constructor TPdfEnum.Create(ACanvas: TPdfCanvas);
begin
Canvas := ACanvas;
// set invalid colors or style -> force paint
fFillColor := -1;
fStrokeColor := -1;
fPenStyle := -1;
fPenWidth := -1;
DC[0].brush.null := true;
fInitTransformMatrix := DefaultIdentityMatrix;
DC[0].WorldTransform := fInitTransformMatrix;
fInitMetaRgn := PdfBox(0, 0, 0, 0);
DC[0].ClipRgnNull := True;
DC[0].MappingMode := MM_TEXT;
DC[0].PolyFillMode := ALTERNATE;
DC[0].StretchBltMode := STRETCH_DELETESCANS;
end;
procedure TPdfEnum.CreateFont(aLogFont: PEMRExtCreateFontIndirect);
var HF: HFONT;
TM: TTextMetric;
Old: HGDIOBJ;
destDC: HDC;
begin
destDC := Canvas.fDoc.FDC;
HF := CreateFontIndirectW(aLogFont.elfw.elfLogFont);
Old := SelectObject(destDC,HF);
GetTextMetrics(destDC,TM);
SelectObject(destDC,Old);
DeleteObject(HF);
if aLogFont^.ihFont-1<cardinal(length(obj)) then
with obj[aLogFont^.ihFont-1] do begin
kind := OBJ_FONT;
MoveFast(aLogFont^.elfw.elfLogFont,LogFont,sizeof(LogFont));
LogFont.lfPitchAndFamily := TM.tmPitchAndFamily;
if LogFont.lfOrientation<>0 then
FontSpec.angle := LogFont.lfOrientation div 10 else // -360..+360
FontSpec.angle := LogFont.lfEscapement div 10;
FontSpec.ascent := TM.tmAscent;
FontSpec.descent := TM.tmDescent;
FontSpec.cell := TM.tmHeight-TM.tmInternalLeading;
end;
end;
procedure TPdfEnum.DrawBitmap(xs, ys, ws, hs, xd, yd, wd, hd, usage: integer;
Bmi: PBitmapInfo; bits: pointer; clipRect: PRect; xSrcTransform: PXForm; dwRop: DWord;
transparent: TPdfColorRGB = $FFFFFFFF);
var B: TBitmap;
R: TRect;
Box, ClipRc: TPdfBox;
fIntFactorX, fIntFactorY, fIntOffsetX, fIntOffsetY: Single;
begin
B := TBitmap.Create;
try
InitTransformation(xSrcTransform, fIntFactorX, fIntFactorY, fIntOffsetX, fIntOffsetY);
// create a TBitmap with (0,0,ws,hs) bounds from DIB bits and info
if Bmi^.bmiHeader.biBitCount=1 then
B.Monochrome := true else
B.PixelFormat := pf24bit;
B.Width := ws;
B.Height := hs;
StretchDIBits(B.Canvas.Handle,0, 0, ws, hs, Trunc(xs+fIntOffsetX), Trunc(ys+fIntOffsetY),
Trunc(ws*fIntFactorX), Trunc(hs*fIntFactorY), bits, Bmi^, usage, dwRop);
if transparent <> $FFFFFFFF then begin
if integer(transparent)<0 then
transparent := GetSysColor(transparent and $ff);
B.TransparentColor := transparent;
end;
// draw the bitmap on the PDF canvas
with Canvas do begin
R := Rect(xd, yd, wd+xd, hd+yd);
NormalizeRect(R);
Box := BoxI(R,true);
ClipRc := GetClipRect;
if (ClipRc.Width>0) and (ClipRc.Height>0) then
Doc.CreateOrGetImage(B, @Box, @ClipRc) else // use cliping
Doc.CreateOrGetImage(B, @Box, nil);
// Doc.CreateOrGetImage() will reuse any matching TPdfImage
// don't send bmi and bits parameters here, because of StretchDIBits above
end;
finally
B.Free;
end;
end;
// simulate gradient (not finished)
procedure TPdfEnum.GradientFill(data: PEMGradientFill);
type
PTriVertex = ^TTriVertex;
TTriVertex = packed record // circumvent some bug in older Delphi
X: Longint;
Y: Longint;
Red: word; // COLOR16 wrongly defined in Delphi 6/7 e.g.
Green: word;
Blue: word;
Alpha: word;
end;
PTriVertexArray = ^TTriVertexArray;
TTriVertexArray = array[word] of TTriVertex;
PGradientTriArray = ^TGradientTriArray;
TGradientTriArray = array[word] of TGradientTriangle;
PGradientRectArray = ^TGradientRectArray;
TGradientRectArray = array[word] of TGradientRect;
var i: Integer;
pGradientTriVertex: PTriVertexArray;
pGradientTri: PGradientTriArray;
pGradientRect: PGradientRectArray;
pt1, pt2: PTriVertex;
// Direction: TGradientDirection;
begin
if data^.nVer>0 then begin
pGradientTriVertex := @data.Ver;
case data^.ulMode of
GRADIENT_FILL_RECT_H, GRADIENT_FILL_RECT_V: begin
Canvas.NewPath;
pGradientRect := @pGradientTriVertex[data^.nVer];
{ Direction := gdHorizontal;
if data^.ulMode = GRADIENT_FILL_RECT_V then
Direction := gdVertical; }
for i := 1 to data^.nTri do
with pGradientRect[i-1] do begin
pt1 := @pGradientTriVertex[UpperLeft];
pt2 := @pGradientTriVertex[LowerRight];
Canvas.MoveToI(pt1.X, pt1.Y);
Canvas.LineToI(pt1.X, pt2.Y);
Canvas.LineToI(pt2.X, pt2.Y);
Canvas.LineToI(pt2.X, pt1.Y);
Canvas.Closepath;
Canvas.Fill;
end;
end;
GRADIENT_FILL_TRIANGLE: begin
Canvas.NewPath;
pGradientTri := @pGradientTriVertex[data^.nVer];
for i := 1 to data^.nTri do
with pGradientTri[i-1] do begin
with pGradientTriVertex[Vertex1] do begin
FillColor := RGBA(Red,Green,Blue,0); // ignore Alpha
Canvas.MoveToI(X,Y);
end;
with pGradientTriVertex[Vertex2] do Canvas.LineToI(X,Y);
with pGradientTriVertex[Vertex3] do Canvas.LineToI(X,Y);
with pGradientTriVertex[Vertex1] do Canvas.LineToI(X,Y);
//DC[nDC].Moved := Point(pt1.X, pt1.Y);
Canvas.Closepath;
Canvas.Fill;
end;
end;
end;
end;
end;
procedure TPdfEnum.PolyPoly(data: PEMRPolyPolygon; iType: Integer);
var i, j, PolyOffs, PolyFirst: DWord;
pPolyPointsArray: PPointArray;
pPolyPointsArray16: PSmallPointArray;
data16: PEMRPolyPolygon16 absolute data;
begin
NeedBrushAndPen;
if not Canvas.FNewPath then
Canvas.NewPath;
case iType of
EMR_POLYPOLYGON, EMR_POLYPOLYLINE: begin
PolyOffs := 0;
pPolyPointsArray := pointer(PtrUInt(data) + SizeOf(TEMRPolyPolyline)
- SizeOf(TPoint) + (data^.nPolys - 1)*SizeOf(DWORD));
for i := 1 to data^.nPolys do begin
PolyFirst := PolyOffs;
Canvas.MoveToI(pPolyPointsArray[PolyOffs].X, pPolyPointsArray[PolyOffs].Y);
Inc(PolyOffs);
for j := 2 to data^.aPolyCounts[i-1] do begin
Canvas.LineToI(pPolyPointsArray[PolyOffs].X, pPolyPointsArray[PolyOffs].Y);
DC[nDC].Position := Point(pPolyPointsArray[PolyOffs].X, pPolyPointsArray[PolyOffs].Y);
Inc(PolyOffs);
end;
Canvas.LineToI(pPolyPointsArray[PolyFirst].X, pPolyPointsArray[PolyFirst].Y);
DC[nDC].Moved := false;
end;
end;
EMR_POLYPOLYGON16, EMR_POLYPOLYLINE16: begin
PolyOffs := 0;
pPolyPointsArray16 := pointer(PtrUInt(data16) + SizeOf(TEMRPolyPolyline16)
- SizeOf(TSmallPoint) + (data16^.nPolys - 1)*SizeOf(DWORD));
for i := 1 to data16^.nPolys do begin
PolyFirst := PolyOffs;
Canvas.MoveToI(pPolyPointsArray16[PolyOffs].X, pPolyPointsArray16[PolyOffs].Y);
Inc(PolyOffs);
for j := 2 to data16^.aPolyCounts[i-1] do begin
Canvas.LineToI(pPolyPointsArray16[PolyOffs].X, pPolyPointsArray16[PolyOffs].Y);
DC[nDC].Position := Point(pPolyPointsArray16[PolyOffs].X, pPolyPointsArray16[PolyOffs].Y);
Inc(PolyOffs);
end;
Canvas.LineToI(pPolyPointsArray16[PolyFirst].X, pPolyPointsArray16[PolyFirst].Y);
DC[nDC].Moved := false;
end;
end;
end;
if iType in [EMR_POLYPOLYLINE, EMR_POLYPOLYLINE16] then begin // stroke
if not DC[nDC].pen.null then
Canvas.Stroke else
Canvas.NewPath;
end else begin // fill
if not DC[nDC].brush.null then begin
if not DC[nDC].pen.null then
if DC[nDC].PolyFillMode=ALTERNATE then
Canvas.EofillStroke else
Canvas.FillStroke
else if DC[nDC].PolyFillMode=ALTERNATE then
Canvas.EoFill else
Canvas.Fill
end else
if not DC[nDC].pen.null then
Canvas.Stroke else
Canvas.NewPath;
end;
end;
procedure TPdfEnum.FillRectangle(const Rect: TRect; ResetNewPath: boolean);
begin
if DC[nDC].brush.null then
exit;
Canvas.NewPath;
FillColor := DC[nDC].brush.color;
with Canvas.BoxI(Rect,true) do
Canvas.Rectangle(Left,Top,Width,Height);
Canvas.Fill;
if ResetNewPath then
Canvas.FNewPath := false;
end;
procedure TPdfEnum.FlushPenBrush;
begin
with DC[nDC] do begin
if brush.null then begin
if not pen.null then
Canvas.Stroke else
Canvas.NewPath;
end else
if pen.null then
Canvas.Fill else
Canvas.FillStroke;
end;
end;
procedure TPdfEnum.SelectObjectFromIndex(iObject: integer);
begin
with DC[nDC] do begin
if iObject<0 then begin // stock object?
iObject := iObject and $7fffffff;
case iObject of
NULL_BRUSH:
brush.null := true;
WHITE_BRUSH..BLACK_BRUSH: begin
brush.color := STOCKBRUSHCOLOR[iObject];
brush.null := false;
end;
NULL_PEN: begin
if fInLined and ((pen.style<>PS_NULL) or not pen.null) then begin
fInLined := False;
if not pen.null then
Canvas.Stroke;
end;
pen.style := PS_NULL;
pen.null := true;
end;
WHITE_PEN, BLACK_PEN: begin
if fInLined and ((pen.color<>STOCKPENCOLOR[iObject]) or not pen.null) then begin
fInLined := False;
if not pen.null then
Canvas.Stroke;
end;
pen.color := STOCKPENCOLOR[iObject];
pen.null := false;
end;
end;
end else
if cardinal(iObject-1)<cardinal(length(Obj)) then // avoid GPF
with Obj[iObject-1] do
case Kind of // ignore any invalid reference
OBJ_PEN: begin
if fInLined and
((pen.color<>PenColor) or (pen.width<>PenWidth) or
(pen.style<>PenStyle)) then begin
fInLined := False;
if not pen.null then
Canvas.Stroke;
end;
pen.null := (PenWidth<0) or (PenStyle=PS_NULL); // !! 0 means as thick as possible
pen.color := PenColor;
pen.width := PenWidth;
pen.style := PenStyle;
end;
OBJ_BRUSH: begin
brush.null := BrushNull;
brush.color := BrushColor;
brush.style := BrushStyle;
end;
OBJ_FONT: begin
font.spec := FontSpec;
MoveFast(LogFont,font.LogFont,sizeof(LogFont));
end;
end;
end;
end;
procedure TPdfEnum.HandleComment(Kind: TPdfGDIComment; P: PAnsiChar; Len: integer);
var Text: RawUTF8;
W: integer;
Img: TPdfImage;
ImgName: PDFString;
ImgRect: TPdfRect;
begin
try
case Kind of
pgcOutline:
if Len>1 then begin
SetString(Text,P+1,Len-1);
Canvas.Doc.CreateOutline(UTF8ToString(Trim(Text)),PByte(P)^,
Canvas.I2Y(DC[nDC].Position.Y));
end;
pgcBookmark: begin
SetString(Text,P,Len);
Canvas.Doc.CreateBookMark(Canvas.I2Y(DC[nDC].Position.Y),Text);
end;
pgcLink,pgcLinkNoBorder:
if Len>Sizeof(TRect) then begin
SetString(Text,P+SizeOf(TRect),Len-SizeOf(TRect));
if Kind=pgcLink then
W := 1 else
W := 0;
Canvas.Doc.CreateLink(Canvas.RectI(PRect(P)^,true),Text,abSolid,W);
end;
pgcJpegDirect:
if Len>Sizeof(TRect) then begin
SetString(Text,P+SizeOf(TRect),Len-SizeOf(TRect));
ImgName := 'SynImgJpg'+PDFString(crc32cUTF8ToHex(Text));
if Canvas.Doc.GetXObject(ImgName) = nil then
begin
Img := TPdfImage.CreateJpegDirect(Canvas.Doc,UTF8ToString(Text));
Canvas.Doc.RegisterXObject(Img,ImgName);
end;
ImgRect := Canvas.RectI(PRect(P)^,true);
Canvas.DrawXObject(ImgRect.Left,ImgRect.Top,ImgRect.Right-ImgRect.Left,ImgRect.Bottom-ImgRect.Top,ImgName);
end;
end;
except
on E: Exception do ; // ignore any error (continue EMF enumeration)
end;
end;
procedure TPdfEnum.NeedBrushAndPen;
begin
if fInlined then begin
fInlined := false;
Canvas.Stroke;
end;
NeedPen;
with DC[nDC] do
if not brush.null then
FillColor := brush.color;
end;
procedure TPdfEnum.NeedPen;
begin
with DC[nDC] do
if not pen.null then begin
StrokeColor := pen.color;
if pen.style<>fPenStyle then begin
case pen.style and PS_STYLE_MASK of
PS_DASH: Canvas.SetDash([4,4]);
PS_DOT: Canvas.SetDash([1,1]);
PS_DASHDOT: Canvas.SetDash([4,1,1,1]);
PS_DASHDOTDOT: Canvas.SetDash([4,1,1,1,1,1]);
else Canvas.SetDash([]);
end;
case Pen.style and PS_ENDCAP_MASK of
PS_ENDCAP_ROUND: Canvas.SetLineCap(lcRound_End);
PS_ENDCAP_SQUARE: Canvas.SetLineCap(lcProjectingSquareEnd);
PS_ENDCAP_FLAT: Canvas.SetLineCap(lcButt_End);
end;
fPenStyle := pen.style;
end;
if pen.width * Canvas.GetWorldFactorX * Canvas.FDevScaleX<>fPenWidth then begin
if pen.width=0 then
fPenWidth := Canvas.GetWorldFactorX * Canvas.FDevScaleX else
fPenWidth := pen.width * Canvas.GetWorldFactorX * Canvas.FDevScaleX;
Canvas.SetLineWidth(fPenWidth * Canvas.FFactorX);
end;
end else begin
// pen.null need reset values
fStrokeColor := -1;
fPenWidth := -1;
fPenStyle := -1;
end;
end;
procedure TPdfEnum.RestoreDC;
begin
Assert(nDC>0);
dec(nDC);
end;
procedure TPdfEnum.SaveDC;
begin
Assert(nDC<high(DC));
DC[nDC+1] := DC[nDC];
inc(nDC);
end;
procedure TPdfEnum.ScaleMatrix(Custom: PXForm; iMode: Integer);
var ScaleXForm: XForm;
xdim, ydim: Single;
mx, my: Integer;
begin
if fInlined then begin
fInlined := false;
if not DC[nDC].pen.null then
Canvas.Stroke;
end;
with DC[nDC], Canvas do begin
FViewSize := ViewSize;
FViewOrg := ViewOrg;
FWinSize := WinSize;
FWinOrg := WinOrg;
case MappingMode of
MM_TEXT: begin
FViewSize.cx := 1;
FViewSize.cy := 1;
FWinSize.cx := 1;
FWinSize.cy := 1;
end;
MM_LOMETRIC: begin
FViewSize.cx := FPrinterPxPerInch.X;
FViewSize.cy := -FPrinterPxPerInch.Y;
FWinSize.cx := WinSize.cx*10;
FWinSize.cy := WinSize.cy*10;
end;
MM_HIMETRIC: begin
FViewSize.cx := FPrinterPxPerInch.X;
FViewSize.cy := -FPrinterPxPerInch.Y;
FWinSize.cx := WinSize.cx*100;
FWinSize.cy := WinSize.cy*100;
end;
MM_LOENGLISH: begin
FViewSize.cx := FPrinterPxPerInch.X;
FViewSize.cy := -FPrinterPxPerInch.Y;
FWinSize.cx := MulDiv(1000, WinSize.cx, 254);
FWinSize.cy := MulDiv(1000, WinSize.cy, 254);
end;
MM_HIENGLISH: begin
FViewSize.cx := FPrinterPxPerInch.X;
FViewSize.cy := -FPrinterPxPerInch.Y;
FWinSize.cx := MulDiv(10000, WinSize.cx, 254);
FWinSize.cy := MulDiv(10000, WinSize.cy, 254);
end;
MM_TWIPS: begin
FViewSize.cx := FPrinterPxPerInch.X;
FViewSize.cy := -FPrinterPxPerInch.Y;
FWinSize.cx := MulDiv(14400, WinSize.cx, 254);
FWinSize.cy := MulDiv(14400, WinSize.cy, 254);
end;
MM_ISOTROPIC: begin
FViewSize.cx := FPrinterPxPerInch.X;
FViewSize.cy := -FPrinterPxPerInch.Y;
FWinSize.cx := WinSize.cx*10;
FWinSize.cy := WinSize.cy*10;
xdim := Abs(FViewSize.cx * WinSize.cx / (FPrinterPxPerInch.X * FWinSize.cx));
ydim := Abs(FViewSize.cy * WinSize.cy / (FPrinterPxPerInch.Y * FWinSize.cy));
if (xdim > ydim) then begin
if FViewSize.cx>=0 then
mx := 1 else
mx := -1;
FViewSize.cx := Trunc(FViewSize.cx * ydim / xdim + 0.5);
if FViewSize.cx=0 then
FViewSize.cx := mx;
end else begin
if FViewSize.cy>=0 then
my := 1 else
my := -1;
FViewSize.cy := Trunc(FViewSize.cy * xdim / ydim + 0.5);
if FViewSize.cy=0 then
FViewSize.cy := my;
end;
end;
MM_ANISOTROPIC:
; // TBD
end;
if FWinSize.cx=0 then // avoid EZeroDivide
FFactorX := 1.0 else
FFactorX := Abs(FViewSize.cx / FWinSize.cx);
if FWinSize.cy=0 then // avoid EZeroDivide
FFactorY := 1.0 else
FFactorY := Abs(FViewSize.cy / FWinSize.cy);
if Custom<>nil then begin
// S.eM11=FFactorX S.eM12=0 S.eM21=0 S.eM22=FFactorY multiplied by Custom^
case iMode of
MWT_IDENTITY: // reset identity matrix
WorldTransform := DefaultIdentityMatrix;
MWT_LEFTMULTIPLY:
WorldTransform := CombineTransform(Custom^, WorldTransform);
MWT_RIGHTMULTIPLY:
WorldTransform := CombineTransform(WorldTransform, Custom^);
MWT_SET:
WorldTransform := Custom^;
end;
end;
// use transformation
ScaleXForm := WorldTransform;
if (ScaleXForm.eM11 > 0) and
(ScaleXForm.eM22 > 0) and
(ScaleXForm.eM12 = 0) and
(ScaleXForm.eM21 = 0) then
begin // Scale
FWorldFactorX := ScaleXForm.eM11;
FWorldFactorY := ScaleXForm.eM22;
FWorldOffsetX := WorldTransform.eDx;
FWorldOffsetY := WorldTransform.eDy;
end
else
if (ScaleXForm.eM22 = ScaleXForm.eM11) and
(ScaleXForm.eM21 = -ScaleXForm.eM12) then
begin // Rotate
FAngle := ArcSin(ScaleXForm.eM12) * c180divPI;
FWorldCos := ScaleXForm.eM11;
FWorldSin := ScaleXForm.eM12;
end
else
if (ScaleXForm.eM11 = 0) and
(ScaleXForm.eM22 = 0) and
((ScaleXForm.eM12 <> 0) or
(ScaleXForm.eM21 <> 0)) then
begin //Shear
end
else
if ((ScaleXForm.eM11 < 0) or
(ScaleXForm.eM22 < 0)) and
(ScaleXForm.eM12 = 0) and
(ScaleXForm.eM21 = 0) then
begin //Reflection
end;
end;
end;
procedure TPdfEnum.InitMetaRgn(ClientRect: TRect);
begin
fInitMetaRgn := Canvas.BoxI(ClientRect,true);
DC[nDC].ClipRgnNull := True;
DC[nDC].MetaRgn := fInitMetaRgn;
end;
procedure TPdfEnum.SetMetaRgn;
begin
try
with DC[nDC] do
if not ClipRgnNull then begin
MetaRgn := IntersectClipRect(ClipRgn, MetaRgn);
FillCharFast(ClipRgn,sizeof(ClipRgn),0);
ClipRgnNull := True;
end;
except
on E: Exception do ; // ignore any error (continue EMF enumeration)
end;
end;
function TPdfEnum.IntersectClipRect(const ClpRect: TPdfBox; const CurrRect: TPdfBox): TPdfBox;
begin
Result := CurrRect;
if (ClpRect.Width<>0) or (ClpRect.Height<>0) then begin // ignore null clipping area
if ClpRect.Left > Result.Left then
Result.Left := ClpRect.Left;
if ClpRect.Top > Result.Top then
Result.Top := ClpRect.Top;
if (ClpRect.Left+ClpRect.Width) < (Result.Left+Result.Width) then
Result.Width := (ClpRect.Left+ClpRect.Width) - Result.Left;
if (ClpRect.Top + ClpRect.Height) < (Result.Top + Result.Height) then
Result.Height := (ClpRect.Top + ClpRect.Height) - Result.Top;
// fix rect
if Result.Width<0 then
Result.Width := 0;
if Result.Height<0 then
Result.Height := 0;
end;
end;
procedure TPdfEnum.ExtSelectClipRgn(data: PRgnDataHeader; iMode: DWord);
var ExtClip: TRect;
begin
try
ExtClip := data^.rcBound;
with DC[nDC] do
case iMode of
RGN_COPY: begin
ClipRgn := MetaRgn;
ClipRgnNull := False;
end;
end;
except
on E: Exception do ; // ignore any error (continue EMF enumeration)
end;
end;
function TPdfEnum.GetClipRect: TPdfBox;
begin // get current clip area
with DC[nDC] do
if ClipRgnNull then
Result := MetaRgn else
Result := ClipRgn;
end;
procedure TPdfEnum.SetFillColor(const Value: integer);
begin
if fFillColor=Value then
exit;
Canvas.SetRGBFillColor(Value);
fFillColor := Value;
end;
procedure TPdfEnum.SetStrokeColor(const Value: integer);
begin
if fStrokeColor=Value then
exit;
Canvas.SetRGBStrokeColor(Value);
fStrokeColor := Value;
end;
function DXTextWidth(DX: PIntegerArray; n: Integer): integer;
var i: integer;
begin
result := 0;
for i := 0 to n-1 do
inc(result,DX^[i]);
end;
procedure TPdfEnum.TextOut(var R: TEMRExtTextOut);
var nspace,i: integer;
cur: cardinal;
wW, measW, W,H,hscale: Single;
DX: PIntegerArray; // not handled during drawing yet
Posi: TPoint;
AWidth, ASize, PosX, PosY: single;
APDFFont: TPDFFont;
tmp: array of WideChar; // R.emrtext is not #0 terminated -> use tmp[]
tmpChar: array[0..1] of WideChar;
a, acos, asin, fscaleX, fscaleY: single;
AUseDX, WithClip, bOpaque: Boolean;
ClipRect: TPdfBox;
ASignX, ASignY: Integer;
backRect: TRect;
Positioning: TPdfCanvasRenderMetaFileTextPositioning;
{$ifdef USE_UNISCRIBE}
ADC: HDC;
AnExtent: TSize;
{$endif}
procedure DrawLine(var P: TPoint; aH: Single);
var tmp: TPdfEnumStatePen;
begin
with DC[nDC] do begin
tmp := Pen;
pen.color := font.color;
pen.width := aSize / 15 / Canvas.GetWorldFactorX / Canvas.FDevScaleX;
pen.style := PS_SOLID;
pen.null := False;
NeedPen;
if font.spec.angle=0 then begin
// P = textout original coords
// (-W,-H) = delta to text start pos (at baseline)
// wW = text width
// aH = delta H for drawed line (from baseline)
Canvas.MoveToI(P.X -W,(P.Y-(H-aH))); // deltax := -W deltaY := (-H+aH)
Canvas.LineToI(P.X -W+wW,(P.Y-(H-aH)));// deltax := -W+wW deltaY := (-H+aH)
end else begin
// rotation pattern :
// rdx = deltax * acos + deltay * asin
// rdy = deltay * acos - deltax * asin
Canvas.MoveToI( P.X+( (-W) *acos +(-H+aH)*asin ),
P.Y+((-H+aH)*acos -(-W) *asin ) );
Canvas.LineToI( P.X+((-W+wW) *acos +(-H+aH)*asin ),
P.Y+((-H+aH) *acos -(-W+wW)*asin ) );
end;
Canvas.Stroke;
Pen := tmp;
NeedPen;
end;
end;
begin
if R.emrtext.nChars>0 then
with DC[nDC] do begin
SetLength(tmp,R.emrtext.nChars+1); // faster than WideString for our purpose
MoveFast(pointer(PtrUInt(@R)+R.emrtext.offString)^,tmp[0],R.emrtext.nChars*2);
ASignY := 1;
ASignX := 1;
if (Canvas.FWorldFactorY) < 0 then
ASignY := -1;
if (Canvas.FWorldFactorX) < 0 then
ASignX := -1;
fscaleY := Abs(Canvas.fFactorY * Canvas.GetWorldFactorY * Canvas.FDevScaleY);
fscaleX := Abs(Canvas.fFactorX * Canvas.GetWorldFactorX * Canvas.FDevScaleX);
// guess the font size
if font.LogFont.lfHeight<0 then
ASize := Abs(font.LogFont.lfHeight)*fscaleY else
ASize := Abs(font.spec.cell)*fscaleY;
// ensure this font is selected (very fast if was already selected)
APDFFont := Canvas.SetFont(Canvas.FDoc.FDC, font.LogFont, ASize);
// calculate coordinates
Positioning := Canvas.fUseMetaFileTextPositioning;
if (R.emrtext.fOptions and ETO_GLYPH_INDEX<>0) then
measW := 0 else begin
AWidth := 0;
{$ifdef USE_UNISCRIBE}
if Assigned(APDFFont) and Canvas.fDoc.UseUniScribe and
APDFFont.InheritsFrom(TPdfFontTrueType) then begin
ADC := Canvas.FDoc.GetDCWithFont(TPdfFontTrueType(APDFFont));
if GetTextExtentPoint32W(ADC,Pointer(tmp),R.emrtext.nChars,AnExtent) then
AWidth := (AnExtent.cX * Canvas.FPage.FFontSize) / 1000;
end;
{$endif}
if AWidth=0 then
AWidth := Canvas.UnicodeTextWidth(Pointer(tmp));
measW := Round(AWidth / fscaleX);
end;
AUseDX := R.emrtext.offDx > 0;
{$ifdef USE_UNISCRIBE}
if Canvas.fDoc.UseUniScribe then
AUseDX := AUseDX and (R.emrtext.fOptions and ETO_GLYPH_INDEX <> 0);
{$endif}
if AUseDX then begin
DX := pointer(PtrUInt(@R)+R.emrtext.offDx);
W := DXTextWidth(DX, R.emrText.nChars);
if W<R.rclBounds.Right-R.rclBounds.Left then // offDX=0 or within box
DX := nil;
end else
DX := nil;
if DX=nil then begin
W := measW;
if Positioning=tpExactTextCharacterPositining then
Positioning := tpSetTextJustification; // exact position expects DX
end;
nspace := 0;
hscale := 100;
if measW<>0 then begin
for i := 0 to R.emrtext.nChars-1 do
if tmp[i]=' ' then
inc(nspace);
if (Positioning=tpSetTextJustification) and
((nspace=0) or ((W-measW)<nspace)) then
Positioning := tpKerningFromAveragePosition;
if (Positioning=tpExactTextCharacterPositining) and (font.spec.angle<>0) then
Positioning := tpKerningFromAveragePosition;
case Positioning of
tpSetTextJustification:
// we should have had a SetTextJustification() call -> modify word space
with Canvas do
SetWordSpace(((W-measW) * fscaleX)/nspace);
tpKerningFromAveragePosition: begin
// check if DX[] width differs from PDF width
hscale := (W*100) / measW;
// implement some global kerning if needed (allow hysteresis around 100%)
if (hscale<Canvas.fKerningHScaleBottom) or
(hscale>Canvas.fKerningHScaleTop) then begin
if font.spec.angle=0 then
Canvas.SetHorizontalScaling(hscale) else
hscale := 100;
end else
hscale := 100;
end;
tpExactTextCharacterPositining:
tmpChar[1] := #0;
end;
end else
Positioning := tpSetTextJustification;
wW := W; // right x
// H Align Mask = TA_CENTER or TA_RIGHT or TA_LEFT = TA_CENTER
if (font.Align and TA_CENTER)=TA_CENTER then
W := W/2 // center x
else if (font.Align and TA_CENTER)=TA_LEFT then
W := 0; // left x
// V Align mask = TA_BASELINE or TA_BOTTOM or TA_TOP = TA_BASELINE
if (font.Align and TA_BASELINE) = TA_BASELINE then
// always zero ?
H := Abs(font.LogFont.lfHeight) - Abs(font.spec.cell) // center y
else if (font.Align and TA_BASELINE)= TA_BOTTOM then
H := Abs(font.spec.descent) // bottom y
else
// H := - Abs(font.spec.cell); // top
// needs - vertical coords of baseline from top
H := -abs(font.spec.ascent); // top
if ASignY<0 then //inverted coordinates
H := Abs(font.LogFont.lfHeight)+H;
if ASignX<0 then
W := W+wW;
if (font.align and TA_UPDATECP)=TA_UPDATECP then
Posi := Position else
Posi := R.emrtext.ptlReference;
// detect clipping
if Canvas.fUseMetaFileTextClipping<>tcNeverClip then begin
with R.emrtext.rcl do
WithClip := (Right>Left) and (Bottom>Top);
if WithClip then
ClipRect := Canvas.BoxI(R.emrtext.rcl,true) else begin
if Canvas.fUseMetaFileTextClipping=tcClipExplicit then
with R.rclBounds do
WithClip := (Right>Left) and (Bottom>Top);
if WithClip then
ClipRect := Canvas.BoxI(R.rclBounds,true) else begin
WithClip := not ClipRgnNull and
(Canvas.fUseMetaFileTextClipping=tcAlwaysClip);
if WithClip then
ClipRect := GetClipRect;
end;
end;
end else
WithClip := False;
bOpaque := not brush.null and (brush.Color<>clWhite) and
((R.emrtext.fOptions and ETO_OPAQUE<>0) or
((font.BkMode=OPAQUE) and (font.BkColor=brush.color)));
if bOpaque then
if WithClip then
backRect := R.emrtext.rcl else begin
backRect.TopLeft := Posi;
backRect.BottomRight := Posi;
inc(backRect.Right,Trunc(wW));
inc(backRect.Bottom,Abs(font.LogFont.lfHeight));
end;
NormalizeRect(backRect);
if WithClip then begin
Canvas.GSave;
Canvas.NewPath;
Canvas.Rectangle(ClipRect.Left,ClipRect.Top,ClipRect.Width,ClipRect.Height);
Canvas.ClosePath;
Canvas.Clip;
if bOpaque then begin
FillRectangle(backRect,false);
bOpaque := False; //do not handle more
end else
Canvas.NewPath;
Canvas.fNewPath := False;
end;
// draw background (if any)
if bOpaque then
// don't handle BkMode, since global to the page, but only specific text
// don't handle rotation here, since should not be used much
FillRectangle(backRect,true);
// draw text
FillColor := font.color;
{$ifdef USE_UNISCRIBE}
Canvas.RightToLeftText := (R.emrtext.fOptions and ETO_RTLREADING)<>0;
{$endif}
Canvas.BeginText;
if font.spec.angle<>0 then begin
a := font.spec.angle*cPIdiv180;
acos := cos(a);
asin := sin(a);
PosX := 0;
PosY := 0;
Canvas.SetTextMatrix(acos, asin, -asin, acos,
Canvas.I2X(Posi.X-Round(W*acos+H*asin)),
Canvas.I2Y(Posi.Y-Round(H*acos-W*asin)));
end else
if (WorldTransform.eM11 = WorldTransform.eM22) and
(WorldTransform.eM12 = -WorldTransform.eM21) and
not SameValue(ArcCos(WorldTransform.eM11), 0, 0.0001) then
begin
PosX := 0;
PosY := 0;
if SameValue(ArcCos(WorldTransform.eM11), 0, 0.0001) or //0deg
SameValue(ArcCos(WorldTransform.eM11), cPI, 0.0001) then //180deg
Canvas.SetTextMatrix(WorldTransform.eM11, WorldTransform.eM12, WorldTransform.eM21, WorldTransform.eM22,
Canvas.I2X(posi.X * WorldTransform.eM11 + posi.Y * WorldTransform.eM21 + WorldTransform.eDx),
Canvas.I2y(posi.X * WorldTransform.eM12 + posi.Y * WorldTransform.eM22 + WorldTransform.eDy))
else
Canvas.SetTextMatrix(-WorldTransform.eM11, -WorldTransform.eM12, -WorldTransform.eM21, -WorldTransform.eM22,
Canvas.I2X(posi.X * WorldTransform.eM11 + posi.Y * WorldTransform.eM21 + WorldTransform.eDx),
Canvas.I2y(posi.X * WorldTransform.eM12 + posi.Y * WorldTransform.eM22 + WorldTransform.eDy));
end
else
begin
acos := 0;
asin := 0;
if Canvas.fViewSize.cx>0 then
PosX := Posi.X-W else // zero point left
PosX := Posi.X+W; // right
if Canvas.fViewSize.cy>0 then
PosY := Posi.Y-H else // zero point beyond
PosY := Posi.Y+H; // above
Canvas.MoveTextPoint(Canvas.I2X(PosX),Canvas.I2Y(PosY));
end;
if (R.emrtext.fOptions and ETO_GLYPH_INDEX)<>0 then
Canvas.ShowGlyph(pointer(tmp),R.emrtext.nChars) else
if Positioning=tpExactTextCharacterPositining then begin
cur := 0;
repeat
tmpChar[0] := tmp[cur];
Canvas.ShowText(@tmpChar,false);
if cur=R.emrtext.nChars-1 then
break;
PosX := PosX+DX^[cur];
Canvas.EndText;
Canvas.BeginText;
Canvas.MoveTextPoint(Canvas.I2X(PosX),Canvas.I2Y(PosY));
inc(cur);
until false;
end else
Canvas.ShowText(pointer(tmp));
Canvas.EndText;
case Positioning of
tpSetTextJustification:
if nspace>0 then
Canvas.SetWordSpace(0);
tpKerningFromAveragePosition:
if hscale<>100 then
Canvas.SetHorizontalScaling(100); //reset hor. scaling
end;
// handle underline or strike out styles (direct draw PDF lines on canvas)
if font.LogFont.lfUnderline<>0 then
DrawLine(Posi, aSize / 8 / Canvas.GetWorldFactorX / Canvas.FDevScaleX);
if font.LogFont.lfStrikeOut<>0 then
DrawLine(Posi, - aSize / 3 / Canvas.GetWorldFactorX / Canvas.FDevScaleX);
// end any pending clipped TextRect() region
if WithClip then begin
Canvas.GRestore;
fFillColor := -1; // force set drawing color
end;
if not Canvas.FNewPath then begin
if WithClip then
if not DC[nDC].ClipRgnNull then begin
ClipRect := GetClipRect;
Canvas.GSave;
Canvas.Rectangle(ClipRect.Left, ClipRect.Top, ClipRect.Width, ClipRect.Height);
Canvas.Clip;
Canvas.GRestore;
Canvas.NewPath;
Canvas.fNewPath := False;
end;
end else
Canvas.fNewPath := False;
if (font.align and TA_UPDATECP)=TA_UPDATECP then begin
Position.X := Posi.X+Trunc(wW);
Position.Y := Posi.Y;
end;
end;
end;
{ TPdfForm }
constructor TPdfForm.Create(aDoc: TPdfDocumentGDI; aMetaFile: TMetafile);
var P: TPdfPageGDI;
FResources: TPdfDictionary;
W,H: integer;
oldPage: TPdfPage;
begin
inherited Create(aDoc,true);
W := aMetaFile.Width;
H := aMetaFile.Height;
P := TPdfPageGDI.Create(nil);
try
FResources := TPdfDictionary.Create(aDoc.FXref);
FFontList := TPdfDictionary.Create(nil);
FResources.AddItem('Font',FFontList);
FResources.AddItem('ProcSet',TPdfArray.CreateNames(nil,['PDF','Text','ImageC']));
with aDoc.FCanvas do begin
oldPage := FPage;
FPage := P;
try
FPageFontList := FFontList;
FContents := self;
FPage.SetPageHeight(H);
FFactor := 1;
RenderMetaFile(aMetaFile);
finally
if oldPage<>nil then
SetPage(oldPage);
end;
end;
FAttributes.AddItem('Type','XObject');
FAttributes.AddItem('Subtype','Form');
FAttributes.AddItem('BBox',TPdfArray.Create(nil,[0,0,W,H]));
FAttributes.AddItem('Matrix',TPdfRawText.Create('[1 0 0 1 0 0]'));
FAttributes.AddItem('Resources',FResources);
finally
P.Free;
end;
end;
{$endif USE_METAFILE}
{$ifdef USE_BITMAP}
{ TPdfImage }
constructor TPdfImage.Create(aDoc: TPdfDocument; aImage: TGraphic; DontAddToFXref: boolean);
var B: TBitmap;
PInc, y: integer;
Pal: PDFString;
Pals: array of TPaletteEntry;
CA: TPdfArray;
TransparentColor: TPdfColorRGB;
procedure NeedBitmap(PF: TPixelFormat);
begin
B := TBitmap.Create; // create a temp bitmap (pixelformat may change)
B.PixelFormat := PF;
B.Width := fPixelWidth;
B.Height := fPixelHeight;
B.Canvas.Draw(0,0,aImage);
end;
procedure WritePal(P: PAnsiChar; Pal: PPaletteEntry);
var i: integer;
begin
P^ := '<'; inc(P);
for i := 0 to 255 do
with Pal^ do begin
P[0] := HexChars[peRed shr 4];
P[1] := HexChars[peRed and $F];
P[2] := HexChars[peGreen shr 4];
P[3] := HexChars[peGreen and $F];
P[4] := HexChars[peBlue shr 4];
P[5] := HexChars[peBlue and $F];
P[6] := ' ';
inc(P,7);
inc(Pal);
end;
P^ := '>';
end;
begin
inherited Create(aDoc,DontAddToFXref);
fPixelWidth := aImage.Width;
fPixelHeight := aImage.Height;
FAttributes.AddItem('Type','XObject');
FAttributes.AddItem('Subtype','Image');
if aImage.InheritsFrom(TJpegImage) then begin
FAttributes.AddItem('ColorSpace','DeviceRGB');
FFilter := 'DCTDecode';
FWriter.Save; // flush to allow direct access to fDestStream
with TJpegImage(aImage) do begin
if aDoc.ForceJPEGCompression<>0 then
CompressionQuality := aDoc.ForceJPEGCompression;
{$ifdef USE_SYNGDIPLUS}
if aDoc.ForceJPEGCompression=0 then // recompression only if necessary
SaveInternalToStream(FWriter.fDestStream) else
{$endif}
SaveToStream(FWriter.fDestStream); // with CompressionQuality recompress
end;
FWriter.fDestStreamPosition := FWriter.fDestStream.Seek(0,soCurrent);
end else begin
if aImage.InheritsFrom(TBitmap) then
B := TBitmap(aImage) else
NeedBitmap(pf24bit);
try
case B.PixelFormat of
pf1bit, pf4bit, pf8bit: begin
if B.PixelFormat<>pf8bit then
NeedBitmap(pf8bit);
SetLength(Pals,256);
if GetPaletteEntries(B.Palette,0,256,Pals[0])<>256 then
raise EPdfInvalidValue.Create('TPdfImage');
SetLength(Pal,7*256+2);
WritePal(pointer(Pal),pointer(Pals));
CA := TPdfArray.Create(nil);
CA.AddItem(TPdfName.Create('Indexed'));
CA.AddItem(TPdfName.Create('DeviceRGB'));
CA.AddItem(TPdfNumber.Create(255));
CA.AddItem(TPdfRawText.Create(Pal));
FAttributes.AddItem('ColorSpace',CA);
for y := 0 to fPixelHeight-1 do
FWriter.Add(PAnsiChar(B.ScanLine[y]),fPixelWidth);
end;
else begin
FAttributes.AddItem('ColorSpace','DeviceRGB');
if not (B.PixelFormat in [pf24bit,pf32bit]) then
NeedBitmap(pf24bit);
if B.PixelFormat=pf24bit then
PInc := 3 else
PInc := 4;
for y := 0 to fPixelHeight-1 do
FWriter.AddRGB(B.ScanLine[y],PInc,fPixelWidth);
if (PInc=3) and (B.TransparentMode=tmFixed) then begin
// [ min1 max1 ... minn maxn ]
TransparentColor := B.TransparentColor;
FAttributes.AddItem('Mask',TPdfArray.CreateReals(nil,
[(TransparentColor and $ff), (TransparentColor and $ff),
(TransparentColor shr 8 and $ff), (TransparentColor shr 8 and $ff),
(TransparentColor shr 16 and $ff), (TransparentColor shr 16 and $ff)]));
end;
end;
end;
finally
if B<>aImage then
B.Free;
end;
end;
FAttributes.AddItem('Width',fPixelWidth);
FAttributes.AddItem('Height',fPixelHeight);
FAttributes.AddItem('BitsPerComponent',8);
end;
constructor TPdfImage.CreateJpegDirect(aDoc: TPdfDocument;
const aJpegFileName: TFileName; DontAddToFXref: boolean);
var MS: THeapMemoryStream;
begin
MS := THeapMemoryStream.Create;
try
MS.LoadFromFile(aJpegFileName);
CreateJpegDirect(aDoc,MS,DontAddToFXRef);
finally
MS.Free;
end;
end;
function GetJpegSize(jpeg: TMemoryStream; out width, height, BitDepth: integer): boolean;
var n: integer;
b: byte;
w: Word;
begin
result := false;
n := jpeg.Size-8;
jpeg.Position := 0;
if n<=0 then
exit;
jpeg.Read(w,2);
if w<>$D8FF then
exit; // invalid format
jpeg.Read(b,1);
while (jpeg.Position<n) and (b=$FF) do begin
jpeg.Read(b,1);
case b of
$C0..$C3: begin
jpeg.Seek(3,soCurrent);
jpeg.Read(w,2);
height := swap(w);
jpeg.Read(w,2);
width := swap(w);
jpeg.Read(b,1);
BitDepth := b*8;
Result := true; // JPEG format OK
exit;
end;
$FF:
jpeg.Read(b,1);
$D0..$D9, $01: begin
jpeg.Seek(1,soCurrent);
jpeg.Read(b,1);
end;
else begin
jpeg.Read(w,2);
jpeg.Seek(swap(w)-2, soCurrent);
jpeg.Read(b,1);
end;
end;
end;
end;
constructor TPdfImage.CreateJpegDirect(aDoc: TPdfDocument;
aJpegFile: TMemoryStream; DontAddToFXref: boolean);
var Len, BitDepth: integer;
begin
inherited Create(aDoc,DontAddToFXref);
Len := aJpegFile.Size;
if not GetJpegSize(aJpegFile,fPixelWidth,fPixelHeight,BitDepth) then
exit; // JPEG format expected
FAttributes.AddItem('Type','XObject');
FAttributes.AddItem('Subtype','Image');
FFilter := 'DCTDecode';
FWriter.Save; // flush to allow direct access to fDestStream
FWriter.Add(aJpegFile.Memory,Len);
FWriter.fDestStreamPosition := FWriter.fDestStream.Seek(0,soCurrent);
FAttributes.AddItem('Width',fPixelWidth);
FAttributes.AddItem('Height',fPixelHeight);
case BitDepth of
8: FAttributes.AddItem('ColorSpace','DeviceGray');
24: FAttributes.AddItem('ColorSpace','DeviceRGB');
end;
FAttributes.AddItem('BitsPerComponent',8);
end;
{$endif USE_BITMAP}
{ TPdfFormWithCanvas }
constructor TPdfFormWithCanvas.Create(aDoc: TPdfDocument; W, H: Integer);
var FResources: TPdfDictionary;
begin
inherited Create(aDoc,true);
FResources := TPdfDictionary.Create(aDoc.FXref);
FFontList := TPdfDictionary.Create(nil);
FResources.AddItem('Font',FFontList);
FResources.AddItem('ProcSet',TPdfArray.CreateNames(nil,['PDF','Text','ImageC']));
FPage := TPdfPage.Create(nil);
FCanvas := TPdfCanvas.Create(aDoc);
FCanvas.FPage := FPage;
FCanvas.FPageFontList := FFontList;
FCanvas.FContents := self;
FCanvas.FFactor := 1;
FAttributes.AddItem('Type','XObject');
FAttributes.AddItem('Subtype','Form');
FAttributes.AddItem('BBox',TPdfArray.Create(nil,[0,0,W,H]));
FAttributes.AddItem('Matrix',TPdfRawText.Create('[1 0 0 1 0 0]'));
FAttributes.AddItem('Resources',FResources);
end;
destructor TPdfFormWithCanvas.Destroy;
begin
CloseCanvas;
inherited;
end;
procedure TPdfFormWithCanvas.CloseCanvas;
begin
FreeAndNil(FCanvas);
FreeAndNil(FPage);
end;
{$ifdef USE_PDFSECURITY}
{ TPdfEncryption }
constructor TPdfEncryption.Create(aLevel: TPdfEncryptionLevel;
aPermissions: TPdfEncryptionPermissions;
const aUserPassword, aOwnerPassword: string);
begin
fLevel := aLevel;
fPermissions := aPermissions;
fUserPassword := aUserPassword;
if aOwnerPassword='' then
raise EPdfInvalidOperation.CreateFmt(
'%s expect a non void owner password',[ClassName]);
fOwnerPassword := aOwnerPassword;
end;
class function TPdfEncryption.New(aLevel: TPdfEncryptionLevel;
const aUserPassword, aOwnerPassword: string;
aPermissions: TPdfEncryptionPermissions): TPdfEncryption;
begin
case aLevel of
elRC4_40, elRC4_128:
result := TPdfEncryptionRC4MD5.Create(aLevel,aPermissions,aUserPassword,aOwnerPassword);
else
result := nil;
end;
end;
procedure TPdfEncryption.AttachDocument(aDoc: TPdfDocument);
begin
fDoc := aDoc;
end;
{ TPdfEncryptionRC4MD5 }
const
// see "Algorithm 3.2 Computing an encryption key" in the PDF reference doc
PDF_PADDING: TPdfBuffer32 =
($28,$BF,$4E,$5E,$4E,$75,$8A,$41,$64,$00,$4E,$56,$FF,$FA,$01,$08,
$2E,$2E,$00,$B6,$D0,$68,$3E,$80,$2F,$0C,$A9,$FE,$64,$53,$69,$7A);
procedure TPdfEncryptionRC4MD5.AttachDocument(aDoc: TPdfDocument);
procedure Pad(const source: string; var dest: TPdfBuffer32);
var L: integer;
tmp: WinAnsiString;
begin
tmp := StringToWinAnsi(source);
L := Length(tmp);
if L>SizeOf(dest) then
L := SizeOf(dest) else
MoveFast(PDF_PADDING,dest[L],SizeOf(dest)-L);
MoveFast(pointer(tmp)^,dest,L);
end;
const HASHSIZE: array[elRC4_40..elRC4_128] of integer = (5,16);
DICT: array[elRC4_40..elRC4_128] of record V,R,L: integer end =
((V:1;R:2;L:40),(V:2;R:3;L:128));
FLAGPATTERN: array[elRC4_40..elRC4_128] of cardinal = ($FFFFFFC0,$FFFFF0C0);
FLAGBIT: array[TPdfEncryptionPermission] of byte = (2,3,4,5,8,9,10,11);
var RC4: TRC4;
MD5: TMD5;
f: TPdfEncryptionPermission;
Digest, Digest2: TMD5Digest;
i,j: integer;
own, usr: TPdfBuffer32;
begin
inherited;
// compute corresponding flags
fFlags := FLAGPATTERN[fLevel];
for f := low(f) to high(f) do
if f in fPermissions then
fFlags := fFlags or (1 shl FLAGBIT[f]);
if fDoc.fFileFormat<pdf14 then
fDoc.fFileFormat := pdf14;
// calc fOwnerPass key (Algorithm 3.3 in PDF reference doc)
Pad(fUserPassword,usr);
Pad(fOwnerPassword,own);
MD5.Full(@own,SizeOf(own),Digest);
if fLevel=elRC4_128 then
for i := 1 to 50 do
MD5.Full(@Digest,sizeof(Digest),Digest);
RC4.Init(Digest,HASHSIZE[fLevel]);
RC4.Encrypt(usr,fOwnerPass,sizeof(fOwnerPass));
if fLevel=elRC4_128 then
for i := 1 to 19 do begin
for j := 0 to high(Digest2) do
Digest2[j] := Digest[j] xor i;
RC4.Init(Digest2,sizeof(Digest2));
RC4.Encrypt(fOwnerPass,fOwnerPass,sizeof(fOwnerPass));
end;
// calc main file key (Algorithm 3.2 in PDF reference doc)
MD5.Init;
MD5.Update(usr,SizeOf(usr));
MD5.Update(fOwnerPass,sizeof(fOwnerPass));
MD5.Update(fFlags,sizeof(fFlags));
MD5.Update(aDoc.fFileID,sizeof(aDoc.fFileID));
MD5.Final(Digest);
if fLevel=elRC4_128 then
for i := 1 to 50 do
MD5.Full(@Digest,sizeof(Digest),Digest);
SetLength(fInternalKey,HASHSIZE[fLevel]);
MoveFast(Digest,fInternalKey[0],HASHSIZE[fLevel]);
// calc fUserPass content
case fLevel of
elRC4_40: begin // Algorithm 3.4 in PDF reference doc
RC4.Init(fInternalKey[0],HASHSIZE[fLevel]);
RC4.Encrypt(PDF_PADDING,fUserPass,sizeof(PDF_PADDING));
end;
elRC4_128: begin // Algorithm 3.5 in PDF reference doc
MD5.Init;
MD5.Update(PDF_PADDING,sizeof(PDF_PADDING));
MD5.Update(aDoc.fFileID,sizeof(aDoc.fFileID));
MD5.Final(Digest);
for i := 0 to 19 do begin
for j := 0 to high(Digest2) do
Digest2[j] := fInternalKey[j] xor i;
RC4.Init(Digest2,SizeOf(Digest2));
RC4.Encrypt(Digest,Digest,SizeOf(Digest));
end;
MoveFast(Digest,fUserPass,SizeOf(Digest));
MoveFast(Digest,fUserPass[SizeOf(Digest)],SizeOf(Digest));
end;
end;
// add encryption dictionary object
if aDoc.fEncryptionObject=nil then
aDoc.fEncryptionObject := TPdfDictionary.Create(aDoc.FXref);
with aDoc.fEncryptionObject, DICT[fLevel] do begin
AddItem('Filter','Standard');
AddItem('V',V);
AddItem('R',R);
AddItem('Length',L);
AddItem('P',fFlags); // expected to be written as signed integer
AddItem('O',TPdfClearText.Create(@fOwnerPass,sizeof(fOwnerPass)));
AddItem('U',TPdfClearText.Create(@fUserPass,sizeof(fUserPass)));
end;
aDoc.FTrailer.Attributes.AddItem('Encrypt',aDoc.fEncryptionObject);
end;
procedure TPdfEncryptionRC4MD5.EncodeBuffer(const BufIn; var BufOut; Count: cardinal);
// see http://www.cs.cmu.edu/~dst/Adobe/Gallery/anon21jul01-pdf-encryption.txt
// see "Algorithm 3.1 Encryption of data" in PDF Reference document
procedure ComputeNewRC4Key;
const KEYSIZE: array[elRC4_40..elRC4_128] of integer = (10,16);
var MD5: TMD5;
Digest: TMD5Digest;
begin
MD5.Init;
MD5.Update(fInternalKey[0],length(fInternalKey));
MD5.Update(fDoc.fCurrentObjectNumber,3);
MD5.Update(fDoc.fCurrentGenerationNumber,2);
MD5.Final(Digest);
fLastRC4Key.Init(Digest,KEYSIZE[fLevel]);
fLastObjectNumber := fDoc.fCurrentObjectNumber;
fLastGenerationNumber := fDoc.fCurrentGenerationNumber;
end;
var work: TRC4; // Encrypt() changes the RC4 state -> local copy for reuse
begin
if (fDoc.fCurrentObjectNumber<>fLastObjectNumber) or
(fDoc.fCurrentGenerationNumber<>fLastGenerationNumber) then
// a lot of string encodings have the same context
ComputeNewRC4Key;
work := fLastRC4Key;
work.Encrypt(BufIn,BufOut,Count); // RC4 allows in-place encryption :)
end;
{$endif USE_PDFSECURITY}
{ TPdfObjectStream }
function TPdfObjectStream.AddObject(Value: TPdfObject): integer;
begin
result := fObjectCount;
inc(fObjectCount);
if fObjectCount>length(fObject) then
SetLength(fObject,fObjectCount+15);
fObject[result].Number := Value.ObjectNumber;
fObject[result].Position := fAddingStream.Position;
Value.InternalWriteTo(fAddingStream);
end;
constructor TPdfObjectStream.Create(aDoc: TPdfDocument);
begin
inherited Create(aDoc,false);
Attributes.AddItem('Type','ObjStm');
fAddingStream := TPdfWrite.Create(ADoc,THeapMemoryStream.Create);
end;
destructor TPdfObjectStream.Destroy;
begin
fAddingStream.fDestStream.Free;
fAddingStream.Free;
inherited;
end;
procedure TPdfObjectStream.InternalWriteTo(W: TPdfWrite);
var i: integer;
begin
Attributes.AddItem('N',fObjectCount);
for i := 0 to fObjectCount-1 do
with fObject[i] do
Writer.Add(Number).Add(' ').Add(Position).Add(' ');
Attributes.AddItem('First',Writer.Position);
Writer.Add(fAddingStream.ToPDFString);
inherited;
end;
initialization
{$ifdef USE_SYNGDIPLUS}
// initialize Gdi+ if necessary (and possible, i.e. not from a dll)
if (Gdip=nil) and not IsLibrary then
Gdip := TGDIPlus.Create('gdiplus.dll');
{$endif}
finalization
if (FontSub<>0) and (FontSub<>INVALID_HANDLE_VALUE) then
FreeLibrary(FontSub);
end.