lazarus/components/fpvectorial/fpvectorial.pas

3968 lines
114 KiB
ObjectPascal
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
fpvectorial.pas
Vector graphics document
License: The same modified LGPL as the Free Pascal RTL
See the file COPYING.modifiedLGPL for more details
AUTHORS: Felipe Monteiro de Carvalho
Pedro Sol Pegorini L de Lima
}
unit fpvectorial;
{$ifdef fpc}
{$mode delphi}
{$endif}
{$define USE_LCL_CANVAS}
{$ifdef USE_LCL_CANVAS}
{$define USE_CANVAS_CLIP_REGION}
{.$define DEBUG_CANVAS_CLIP_REGION}
{$endif}
{.$define FPVECTORIAL_DEBUG_DIMENSIONS}
{.$define FPVECTORIAL_TOCANVAS_DEBUG}
{.$define FPVECTORIAL_DEBUG_BLOCKS}
interface
uses
Classes, SysUtils, Math, TypInfo,
// FCL-Image
fpcanvas, fpimage,
// LCL
lazutf8
{$ifdef USE_LCL_CANVAS}
, Graphics, LCLIntf, LCLType
{$endif}
;
type
TvVectorialFormat = (
{ Multi-purpose document formats }
vfPDF, vfSVG, vfCorelDrawCDR, vfWindowsMetafileWMF,
{ CAD formats }
vfDXF,
{ Geospatial formats }
vfLAS, vfLAZ,
{ Printing formats }
vfPostScript, vfEncapsulatedPostScript,
{ GCode formats }
vfGCodeAvisoCNCPrototipoV5, vfGCodeAvisoCNCPrototipoV6,
{ Formula formats }
vfMathML,
{ Raster Image formats }
vfRAW
);
TvProgressEvent = procedure (APercentage: Byte) of object;
{@@ This routine is called to add an item of caption AStr to an item
AParent, which is a pointer to another item as returned by a previous call
of this same proc. If AParent = nil then it should add the item to the
top of the tree. In all cases this routine should return a pointer to the
newly created item.
}
TvDebugAddItemProc = function (AStr: string; AParent: Pointer): Pointer of object;
const
{ Default extensions }
{ Multi-purpose document formats }
STR_PDF_EXTENSION = '.pdf';
STR_POSTSCRIPT_EXTENSION = '.ps';
STR_SVG_EXTENSION = '.svg';
STR_CORELDRAW_EXTENSION = '.cdr';
STR_WINMETAFILE_EXTENSION = '.wmf';
STR_AUTOCAD_EXCHANGE_EXTENSION = '.dxf';
STR_ENCAPSULATEDPOSTSCRIPT_EXTENSION = '.eps';
STR_LAS_EXTENSION = '.las';
STR_LAZ_EXTENSION = '.laz';
STR_RAW_EXTENSION = '.raw';
STR_MATHML_EXTENSION = '.mathml';
STR_FPVECTORIAL_TEXT_HEIGHT_SAMPLE = 'Ćą';
type
TvCustomVectorialWriter = class;
TvCustomVectorialReader = class;
TvVectorialPage = class;
{ Pen, Brush and Font }
TvPen = record
Color: TFPColor;
Style: TFPPenStyle;
Width: Integer;
end;
TvBrush = record
Color: TFPColor;
Style: TFPBrushStyle;
end;
TvFont = record
Color: TFPColor;
Size: integer;
Name: utf8string;
{@@
Font orientation is measured in degrees and uses the
same direction as the LCL TFont.orientation, which is counter-clockwise.
Zero is the normal, horizontal, orientation, directed to the right.
}
Orientation: Double;
Bold: boolean;
Italic: boolean;
Underline: boolean;
StrikeTrough: boolean;
end;
{ Coordinates and polyline segments }
T3DPoint = record
X, Y, Z: Double;
end;
P3DPoint = ^T3DPoint;
TSegmentType = (
st2DLine, st2DLineWithPen, st2DBezier,
st3DLine, st3DBezier, stMoveTo);
{@@
The coordinates in fpvectorial are given in millimiters and
the starting point is in the bottom-left corner of the document.
The X grows to the right and the Y grows to the top.
}
{ TPathSegment }
TPathSegment = class
public
SegmentType: TSegmentType;
// Fields for linking the list
Previous: TPathSegment;
Next: TPathSegment;
procedure Move(ADeltaX, ADeltaY: Double); virtual;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; virtual;
end;
{@@
In a 2D segment, the X and Y coordinates represent usually the
final point of the segment, being that it starts where the previous
segment ends. The exception is for the first segment of all, which simply
holds the starting point for the drawing and should always be of the type
stMoveTo.
}
{ T2DSegment }
T2DSegment = class(TPathSegment)
public
X, Y: Double;
procedure Move(ADeltaX, ADeltaY: Double); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
end;
T2DSegmentWithPen = class(T2DSegment)
public
Pen: TvPen;
end;
{@@
In Bezier segments, we remain using the X and Y coordinates for the ending point.
The starting point is where the previous segment ended, so that the intermediary
bezier control points are [X2, Y2] and [X3, Y3].
}
{ T2DBezierSegment }
T2DBezierSegment = class(T2DSegment)
public
X2, Y2: Double;
X3, Y3: Double;
procedure Move(ADeltaX, ADeltaY: Double); override;
end;
{ T3DSegment }
T3DSegment = class(TPathSegment)
public
{@@
Coordinates of the end of the segment.
For the first segment, this is the starting point.
}
X, Y, Z: Double;
procedure Move(ADeltaX, ADeltaY: Double); override;
end;
{ T3DBezierSegment }
T3DBezierSegment = class(T3DSegment)
public
X2, Y2, Z2: Double;
X3, Y3, Z3: Double;
procedure Move(ADeltaX, ADeltaY: Double); override;
end;
TvFindEntityResult = (vfrNotFound, vfrFound, vfrSubpartFound);
TvRenderInfo = record
BackgroundColor: TFPColor;
AdjustPenColorToBackground: Boolean;
Selected: Boolean;
ForceRenderBlock: Boolean; // Blocks are usually invisible, but when rendering an insert, their drawing can be forced
end;
{ Now all elements }
{@@
All elements should derive from TvEntity, regardless of whatever properties
they might contain.
}
{ TvEntity }
TvEntity = class
public
X, Y, Z: Double;
constructor Create; virtual;
procedure Clear; virtual;
// in CalculateBoundingBox always remember to treat correctly the case of ADest=nil!!!
// This cased is utilized to guess the size of a document even before getting a canvas to draw at
procedure CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double); virtual;
procedure ExpandBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double); // helper to help CalculateBoundingBox
{@@ ASubpart is only valid if this routine returns vfrSubpartFound }
function TryToSelect(APos: TPoint; var ASubpart: Cardinal): TvFindEntityResult; virtual;
procedure Move(ADeltaX, ADeltaY: Double); virtual;
procedure MoveSubpart(ADeltaX, ADeltaY: Double; ASubpart: Cardinal); virtual;
function GetSubpartCount: Integer; virtual;
procedure PositionSubparts(ADest: TFPCustomCanvas; ABaseX, ABaseY: Double); virtual;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); virtual;
function AdjustColorToBackground(AColor: TFPColor; ARenderInfo: TvRenderInfo): TFPColor;
function GetNormalizedPos(APage: TvVectorialPage; ANewMin, ANewMax: Double): T3DPoint;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; virtual;
function GenerateDebugStrForFPColor(AColor: TFPColor): string;
end;
TvEntityClass = class of TvEntity;
{ TvNamedEntity }
TvNamedEntity = class(TvEntity)
public
Name: string;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
end;
{ TvEntityWithPen }
TvEntityWithPen = class(TvNamedEntity)
public
{@@ The global Pen for the entire entity. In the case of paths, individual
elements might be able to override this setting. }
Pen: TvPen;
constructor Create; override;
procedure ApplyPenToCanvas(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo);
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{ TvEntityWithPenAndBrush }
TvEntityWithPenAndBrush = class(TvEntityWithPen)
public
{@@ The global Brush for the entire entity. In the case of paths, individual
elements might be able to override this setting. }
Brush: TvBrush;
constructor Create; override;
procedure ApplyBrushToCanvas(ADest: TFPCustomCanvas);
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{ TvEntityWithPenBrushAndFont }
TvEntityWithPenBrushAndFont = class(TvEntityWithPenAndBrush)
public
Font: TvFont;
constructor Create; override;
procedure ApplyFontToCanvas(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; AMulX: Double = 1.0);
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
TvClipMode = (vcmNonzeroWindingRule, vcmEvenOddRule);
TPath = class(TvEntityWithPenAndBrush)
private
// Used to speed up sequencial access in MoveSubpart
FCurMoveSubPartIndex: Integer;
FCurMoveSubPartSegment: TPathSegment;
//
public
Len: Integer;
Points: TPathSegment; // Beginning of the double-linked list
PointsEnd: TPathSegment;// End of the double-linked list
CurPoint: TPathSegment; // Used in PrepareForSequentialReading and Next
ClipPath: TPath;
ClipMode: TvClipMode;
constructor Create; override;
destructor Destroy; override;
procedure Clear; override;
procedure Assign(ASource: TPath);
procedure PrepareForSequentialReading;
function Next(): TPathSegment;
procedure CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double); override;
procedure AppendSegment(ASegment: TPathSegment);
procedure AppendMoveToSegment(AX, AY: Double);
procedure AppendLineToSegment(AX, AY: Double);
procedure Move(ADeltaX, ADeltaY: Double); override;
procedure MoveSubpart(ADeltaX, ADeltaY: Double; ASubpart: Cardinal); override;
function MoveToSubpart(ASubpart: Cardinal): TPathSegment;
function GetSubpartCount: Integer; override;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
end;
{@@
TvText represents a text entity.
The text starts in X, Y and grows upwards, towards a bigger Y (fpvectorial coordinates)
or smaller Y (LCL coordinates).
It has the opposite direction of text in the LCL TCanvas.
}
{ TvText }
TvText = class(TvEntityWithPenBrushAndFont)
public
Value: TStringList;
constructor Create; override;
destructor Destroy; override;
function TryToSelect(APos: TPoint; var ASubpart: Cardinal): TvFindEntityResult; override;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
end;
{@@
}
{ TvCircle }
TvCircle = class(TvEntityWithPenAndBrush)
public
Radius: Double;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{@@
}
{ TvCircularArc }
TvCircularArc = class(TvEntityWithPenAndBrush)
public
Radius: Double;
{@@ The Angle is measured in degrees in relation to the positive X axis }
StartAngle, EndAngle: Double;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{@@
}
{ TvEllipse }
TvEllipse = class(TvEntityWithPenAndBrush)
public
// Mandatory fields
HorzHalfAxis: Double; // This half-axis is the horizontal one when Angle=0
VertHalfAxis: Double; // This half-axis is the vertical one when Angle=0
{@@ The Angle is measured in degrees in relation to the positive X axis }
Angle: Double;
procedure CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double); override;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{ TvRectangle }
TvRectangle = class(TvEntityWithPenAndBrush)
public
// Mandatory fields
CX, CY, CZ: Double;
// Corner rounding, zero indicates no rounding
RX, RY: Double;
procedure CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double); override;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{@@
DimensionLeft ---text--- DimensionRight
| |
| | BaseRight
|
| BaseLeft
}
{ TvAlignedDimension }
TvAlignedDimension = class(TvEntityWithPen)
public
// Mandatory fields
BaseLeft, BaseRight, DimensionLeft, DimensionRight: T3DPoint;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{@@
}
{ TvRadialDimension }
TvRadialDimension = class(TvEntityWithPen)
public
// Mandatory fields
IsDiameter: Boolean; // If false, it is a radius, if true, it is a diameter
Center, DimensionLeft, DimensionRight: T3DPoint; // Diameter uses both, Radius uses only DImensionLeft
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{@@
Vectorial images can contain raster images inside them and this entity
represents this.
If the Width and Height differ from the same data in the image, then
the raster image will be stretched.
Note that TFPCustomImage does not implement a storage, so the property
RasterImage should be filled with either a FPImage.TFPMemoryImage or with
a TLazIntfImage. The property RasterImage might be nil.
}
{ TvRasterImage }
TvRasterImage = class(TvNamedEntity)
public
RasterImage: TFPCustomImage;
Top, Left, Width, Height: Double;
procedure InitializeWithConvertionOf3DPointsToHeightMap(APage: TvVectorialPage; AWidth, AHeight: Integer);
end;
{ TvPoint }
// Keep TvPoint as small as possible in memory foot-print for LAS support
TvPoint = class(TvEntity)
public
Pen: TvPen;
{constructor Create; override;
procedure ApplyPenToCanvas(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo);
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;}
end;
{ TvArrow }
//
// The arrow look like this:
//
// A<------|B
// |
// |C
//
// A -> X,Y,Z
// B -> Base
// C -> ExtraLineBase, which exists if HasExtraLine=True
TvArrow = class(TvEntityWithPenAndBrush)
public
Base: T3DPoint;
HasExtraLine: Boolean;
ExtraLineBase: T3DPoint;
ArrowLength: Double;
ArrowBaseLength: Double;
procedure CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double); override;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{@@
The elements bellow describe a formula
The main element of a formula is TvFormula which contains a horizontal list of
the elements of the formula. Those can then have sub-elements
The formula starts in X, Y and grows downwards, towards a smaller Y
}
TvFormula = class;
TvFormulaElementKind = (
// Basic symbols
fekVariable, // Text is the text of the variable
fekEqual, // = symbol
fekSubtraction, // - symbol
fekMultiplication, // either a point . or a small x
fekSum, // + symbol
fekPlusMinus, // The +/- symbol
fekLessThan, // The < symbol
fekLessOrEqualThan, // The <= symbol
fekGreaterThan, // The > symbol
fekGreaterOrEqualThan, // The >= symbol
// More complex elements
fekFraction, // a division with Formula on the top and AdjacentFormula in the bottom
fekRoot, // A root. For example sqrt(something). Number gives the root, usually 2, and inside it goes a Formula
fekPower, // A Formula elevated to a AdjacentFormula, example: 2^5
fekSubscript, // A Formula with a subscripted element AdjacentFormula, example: Xi
fekSummation // Sum of a variable given by Text set by Formula in the bottom and going up to AdjacentFormula in the top
);
{ TvFormulaElement }
TvFormulaElement = class
public
Kind: TvFormulaElementKind;
Text: string;
Number: Double;
Formula: TvFormula;
AdjacentFormula: TvFormula;
public
Top, Left, Width, Height: Double;
function CalculateHeight(ADest: TFPCustomCanvas): Double; // in milimeters
function CalculateWidth(ADest: TFPCustomCanvas): Double; // in milimeters
function AsText: string;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); virtual;
procedure GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer); virtual;
end;
{ TvFormula }
TvFormula = class(TvEntityWithPenBrushAndFont)
private
FCurIndex: Integer;
SpacingBetweenElementsX: Integer;
procedure CallbackDeleteElement(data,arg:pointer);
protected
FElements: TFPList; // of TvFormulaElement
public
Top, Left, Width, Height: Double;
constructor Create; override;
destructor Destroy; override;
//
function GetFirstElement: TvFormulaElement;
function GetNextElement: TvFormulaElement;
procedure AddElement(AElement: TvFormulaElement);
function AddElementWithKind(AKind: TvFormulaElementKind): TvFormulaElement;
function AddElementWithKindAndText(AKind: TvFormulaElementKind; AText: string): TvFormulaElement;
procedure Clear; override;
//
function CalculateHeight(ADest: TFPCustomCanvas): Double; // in milimeters
function CalculateWidth(ADest: TFPCustomCanvas): Double; // in milimeters
procedure PositionSubparts(ADest: TFPCustomCanvas; ABaseX, ABaseY: Double); override;
procedure CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double); override;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
end;
{ TvEntityWithSubEntities }
TvEntityWithSubEntities = class(TvNamedEntity)
private
FCurIndex: Integer;
procedure CallbackDeleteElement(data,arg:pointer);
protected
FElements: TFPList; // of TvEntity
public
constructor Create; override;
destructor Destroy; override;
//
function GetFirstEntity: TvEntity;
function GetNextEntity: TvEntity;
procedure AddEntity(AEntity: TvEntity);
procedure Clear; override;
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
end;
{@@
A block is a group of other elements. It is not rendered directly into the drawing,
but instead is rendered via another item, called TvInsert
}
{ TvBlock }
TvBlock = class(TvEntityWithSubEntities)
public
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{@@
A "Insert" inserts a copy of any other element in the specified position.
Usually TvBlock entities are inserted, but any entity can be inserted.
}
{ TvInsert }
TvInsert = class(TvNamedEntity)
public
InsertEntity: TvEntity; // The entity to be inserted
procedure Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer = 0;
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
end;
{@@
Layers are groups of elements.
Layers are similar to blocks and the diference is that the layer draws
its contents, while the block doesnt, and it cannot be pasted with an TvInsert.
}
{ TvLayer }
TvLayer = class(TvEntityWithSubEntities)
public
end;
{ TvVectorialDocument }
TvVectorialDocument = class
private
FOnProgress: TvProgressEvent;
FPages: TFPList;
FCurrentPageIndex: Integer;
function CreateVectorialWriter(AFormat: TvVectorialFormat): TvCustomVectorialWriter;
function CreateVectorialReader(AFormat: TvVectorialFormat): TvCustomVectorialReader;
public
Width, Height: Double; // in millimeters
Name: string;
Encoding: string; // The encoding on which to save the file, if empty UTF-8 will be utilized. This value is filled when reading
ForcedEncodingOnRead: string; // if empty, no encoding will be forced when reading, but it can be set to a LazUtils compatible value
// User-Interface information
ZoomLevel: Double; // 1 = 100%
{ Selection fields }
SelectedElement: TvEntity;
{ Base methods }
constructor Create; virtual;
destructor Destroy; override;
procedure Assign(ASource: TvVectorialDocument);
procedure AssignTo(ADest: TvVectorialDocument);
procedure WriteToFile(AFileName: string; AFormat: TvVectorialFormat); overload;
procedure WriteToFile(AFileName: string); overload;
procedure WriteToStream(AStream: TStream; AFormat: TvVectorialFormat);
procedure WriteToStrings(AStrings: TStrings; AFormat: TvVectorialFormat);
procedure ReadFromFile(AFileName: string; AFormat: TvVectorialFormat); overload;
procedure ReadFromFile(AFileName: string); overload;
procedure ReadFromStream(AStream: TStream; AFormat: TvVectorialFormat);
procedure ReadFromStrings(AStrings: TStrings; AFormat: TvVectorialFormat);
class function GetFormatFromExtension(AFileName: string): TvVectorialFormat;
function GetDetailedFileFormat(): string;
procedure GuessDocumentSize();
procedure GuessGoodZoomLevel(AScreenSize: Integer = 500);
{ Page methods }
function GetPage(AIndex: Integer): TvVectorialPage;
function GetPageCount: Integer;
function GetCurrentPage: TvVectorialPage;
procedure SetCurrentPage(AIndex: Integer);
function AddPage(): TvVectorialPage;
{ Data removing methods }
procedure Clear; virtual;
{ Debug methods }
procedure GenerateDebugTree(ADestRoutine: TvDebugAddItemProc);
{ Events }
property OnProgress: TvProgressEvent read FOnProgress write FOnprogress;
end;
{ TvVectorialPage }
TvVectorialPage = class
private
FEntities: TFPList; // of TvEntity
FTmpPath: TPath;
FTmpText: TvText;
FCurrentLayer: TvLayer;
//procedure RemoveCallback(data, arg: pointer);
procedure ClearTmpPath();
procedure AppendSegmentToTmpPath(ASegment: TPathSegment);
procedure CallbackDeleteEntity(data,arg:pointer);
public
// Document size for page-based documents
Width, Height: Double; // in millimeters
// Document size for other documents
MinX, MinY, MinZ, MaxX, MaxY, MaxZ: Double;
//
BackgroundColor: TFPColor;
RenderInfo: TvRenderInfo; // Prepared by the reader with info on how to draw the page
//
Owner: TvVectorialDocument;
{ Base methods }
constructor Create(AOwner: TvVectorialDocument); virtual;
destructor Destroy; override;
procedure Assign(ASource: TvVectorialPage);
{ Data reading methods }
function GetEntity(ANum: Cardinal): TvEntity;
function GetEntitiesCount: Integer;
function GetLastEntity(): TvEntity;
function FindAndSelectEntity(Pos: TPoint): TvFindEntityResult;
function FindEntityWithNameAndType(AName: string; AType: TvEntityClass): TvEntity;
{ Data removing methods }
procedure Clear; virtual;
function DeleteEntity(AIndex: Cardinal): Boolean;
function RemoveEntity(AEntity: TvEntity; AFreeAfterRemove: Boolean = True): Boolean;
{ Data writing methods }
function AddEntity(AEntity: TvEntity): Integer;
function AddPathCopyMem(APath: TPath; AOnlyCreate: Boolean = False): TPath;
procedure StartPath(AX, AY: Double); overload;
procedure StartPath(); overload;
procedure AddMoveToPath(AX, AY: Double);
procedure AddLineToPath(AX, AY: Double); overload;
procedure AddLineToPath(AX, AY: Double; AColor: TFPColor); overload;
procedure AddLineToPath(AX, AY, AZ: Double); overload;
procedure GetCurrentPathPenPos(var AX, AY: Double);
procedure GetTmpPathStartPos(var AX, AY: Double);
procedure AddBezierToPath(AX1, AY1, AX2, AY2, AX3, AY3: Double); overload;
procedure AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, AX3, AY3, AZ3: Double); overload;
procedure SetBrushColor(AColor: TFPColor);
procedure SetBrushStyle(AStyle: TFPBrushStyle);
procedure SetPenColor(AColor: TFPColor);
procedure SetPenStyle(AStyle: TFPPenStyle);
procedure SetPenWidth(AWidth: Integer);
procedure SetClipPath(AClipPath: TPath; AClipMode: TvClipMode);
function EndPath(AOnlyCreate: Boolean = False): TPath;
function AddText(AX, AY, AZ: Double; FontName: string; FontSize: integer; AText: utf8string; AOnlyCreate: Boolean = False): TvText; overload;
function AddText(AX, AY: Double; AStr: utf8string; AOnlyCreate: Boolean = False): TvText; overload;
function AddText(AX, AY, AZ: Double; AStr: utf8string; AOnlyCreate: Boolean = False): TvText; overload;
function AddCircle(ACenterX, ACenterY, ARadius: Double; AOnlyCreate: Boolean = False): TvCircle;
function AddCircularArc(ACenterX, ACenterY, ARadius, AStartAngle, AEndAngle: Double; AColor: TFPColor; AOnlyCreate: Boolean = False): TvCircularArc;
function AddEllipse(CenterX, CenterY, HorzHalfAxis, VertHalfAxis, Angle: Double; AOnlyCreate: Boolean = False): TvEllipse;
function AddBlock(AName: string; AX, AY, AZ: Double): TvBlock;
function AddInsert(AX, AY, AZ: Double; AInsertEntity: TvEntity): TvInsert;
// Layers
function AddLayer(AName: string): TvLayer;
function AddLayerAndSetAsCurrent(AName: string): TvLayer;
procedure ClearLayerSelection();
// Dimensions
function AddAlignedDimension(BaseLeft, BaseRight, DimLeft, DimRight: T3DPoint; AOnlyCreate: Boolean = False): TvAlignedDimension;
function AddRadialDimension(AIsDiameter: Boolean; ACenter, ADimLeft, ADimRight: T3DPoint; AOnlyCreate: Boolean = False): TvRadialDimension;
//
function AddPoint(AX, AY, AZ: Double): TvPoint;
{ Drawing methods }
procedure PositionEntitySubparts(ADest: TFPCustomCanvas; ABaseX, ABaseY: Double);
procedure DrawBackground(ADest: TFPCustomCanvas);
procedure Render(ADest: TFPCustomCanvas;
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
{ Debug methods }
procedure GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer);
//
property Entities[AIndex: Cardinal]: TvEntity read GetEntity;
end;
{@@ TvVectorialReader class reference type }
TvVectorialReaderClass = class of TvCustomVectorialReader;
{ TvCustomVectorialReader }
TvCustomVectorialReader = class
public
{ General reading methods }
constructor Create; virtual;
procedure ReadFromFile(AFileName: string; AData: TvVectorialDocument); virtual;
procedure ReadFromStream(AStream: TStream; AData: TvVectorialDocument); virtual;
procedure ReadFromStrings(AStrings: TStrings; AData: TvVectorialDocument); virtual;
end;
{@@ TvVectorialWriter class reference type }
TvVectorialWriterClass = class of TvCustomVectorialWriter;
{@@ TvCustomVectorialWriter }
{ TvCustomVectorialWriter }
TvCustomVectorialWriter = class
public
{ General writing methods }
constructor Create; virtual;
procedure WriteToFile(AFileName: string; AData: TvVectorialDocument); virtual;
procedure WriteToStream(AStream: TStream; AData: TvVectorialDocument); virtual;
procedure WriteToStrings(AStrings: TStrings; AData: TvVectorialDocument); virtual;
end;
{@@ List of registered formats }
TvVectorialFormatData = record
ReaderClass: TvVectorialReaderClass;
WriterClass: TvVectorialWriterClass;
ReaderRegistered: Boolean;
WriterRegistered: Boolean;
Format: TvVectorialFormat;
end;
var
GvVectorialFormats: array of TvVectorialFormatData;
procedure RegisterVectorialReader(
AReaderClass: TvVectorialReaderClass;
AFormat: TvVectorialFormat);
procedure RegisterVectorialWriter(
AWriterClass: TvVectorialWriterClass;
AFormat: TvVectorialFormat);
function Make2DPoint(AX, AY: Double): T3DPoint;
implementation
uses fpvutils;
const
Str_Error_Nil_Path = ' The program attempted to add a segment before creating a path';
{@@
Registers a new reader for a format
}
procedure RegisterVectorialReader(
AReaderClass: TvVectorialReaderClass;
AFormat: TvVectorialFormat);
var
i, len: Integer;
FormatInTheList: Boolean;
begin
len := Length(GvVectorialFormats);
FormatInTheList := False;
{ First search for the format in the list }
for i := 0 to len - 1 do
begin
if GvVectorialFormats[i].Format = AFormat then
begin
if GvVectorialFormats[i].ReaderRegistered then
raise Exception.Create('RegisterVectorialReader: Reader class for format ' {+ AFormat} + ' already registered.');
GvVectorialFormats[i].ReaderRegistered := True;
GvVectorialFormats[i].ReaderClass := AReaderClass;
FormatInTheList := True;
Break;
end;
end;
{ If not already in the list, then add it }
if not FormatInTheList then
begin
SetLength(GvVectorialFormats, len + 1);
GvVectorialFormats[len].ReaderClass := AReaderClass;
GvVectorialFormats[len].WriterClass := nil;
GvVectorialFormats[len].ReaderRegistered := True;
GvVectorialFormats[len].WriterRegistered := False;
GvVectorialFormats[len].Format := AFormat;
end;
end;
{@@
Registers a new writer for a format
}
procedure RegisterVectorialWriter(
AWriterClass: TvVectorialWriterClass;
AFormat: TvVectorialFormat);
var
i, len: Integer;
FormatInTheList: Boolean;
begin
len := Length(GvVectorialFormats);
FormatInTheList := False;
{ First search for the format in the list }
for i := 0 to len - 1 do
begin
if GvVectorialFormats[i].Format = AFormat then
begin
if GvVectorialFormats[i].WriterRegistered then
raise Exception.Create('RegisterVectorialWriter: Writer class for format ' + {AFormat +} ' already registered.');
GvVectorialFormats[i].WriterRegistered := True;
GvVectorialFormats[i].WriterClass := AWriterClass;
FormatInTheList := True;
Break;
end;
end;
{ If not already in the list, then add it }
if not FormatInTheList then
begin
SetLength(GvVectorialFormats, len + 1);
GvVectorialFormats[len].ReaderClass := nil;
GvVectorialFormats[len].WriterClass := AWriterClass;
GvVectorialFormats[len].ReaderRegistered := False;
GvVectorialFormats[len].WriterRegistered := True;
GvVectorialFormats[len].Format := AFormat;
end;
end;
function Make2DPoint(AX, AY: Double): T3DPoint;
begin
Result.X := AX;
Result.Y := AY;
Result.Z := 0;
end;
{ TPathSegment }
procedure TPathSegment.Move(ADeltaX, ADeltaY: Double);
begin
end;
function TPathSegment.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc;
APageItem: Pointer): Pointer;
var
lStr: string;
begin
lStr := Format('[%s]', [Self.ClassName]);
Result := ADestRoutine(lStr, APageItem);
end;
{ T2DSegment }
procedure T2DSegment.Move(ADeltaX, ADeltaY: Double);
begin
X := X + ADeltaX;
Y := Y + ADeltaY;
end;
function T2DSegment.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc;
APageItem: Pointer): Pointer;
var
lStr: string;
begin
lStr := Format('[%s] X=%f Y=%f', [Self.ClassName, X, Y]);
Result := ADestRoutine(lStr, APageItem);
end;
{ T2DBezierSegment }
procedure T2DBezierSegment.Move(ADeltaX, ADeltaY: Double);
begin
inherited Move(ADeltaX, ADeltaY);
X2 := X2 + ADeltaX;
Y2 := Y2 + ADeltaY;
X3 := X3 + ADeltaX;
Y3 := Y3 + ADeltaY;
end;
{ T3DSegment }
procedure T3DSegment.Move(ADeltaX, ADeltaY: Double);
begin
X := X + ADeltaX;
Y := Y + ADeltaY;
end;
{ T3DBezierSegment }
procedure T3DBezierSegment.Move(ADeltaX, ADeltaY: Double);
begin
inherited Move(ADeltaX, ADeltaY);
X2 := X2 + ADeltaX;
Y2 := Y2 + ADeltaY;
X3 := X3 + ADeltaX;
Y3 := Y3 + ADeltaY;
end;
{ TvEntity }
constructor TvEntity.Create;
begin
end;
procedure TvEntity.Clear;
begin
X := 0.0;
Y := 0.0;
Z := 0.0;
end;
procedure TvEntity.CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double);
begin
ALeft := X;
ATop := Y;
ARight := X+1;
ABottom := Y+1;
end;
procedure TvEntity.ExpandBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double);
var
lLeft, lTop, lRight, lBottom: Double;
begin
CalculateBoundingBox(ADest, lLeft, lTop, lRight, lBottom);
if lLeft < ALeft then ALeft := lLeft;
if lTop < ATop then ATop := lTop;
if lRight > ARight then ARight := lRight;
if lBottom > ABottom then ABottom := lBottom;
end;
function TvEntity.TryToSelect(APos: TPoint; var ASubpart: Cardinal): TvFindEntityResult;
begin
Result := vfrNotFound;
end;
procedure TvEntity.Move(ADeltaX, ADeltaY: Double);
begin
X := X + ADeltaX;
Y := Y + ADeltaY;
end;
procedure TvEntity.MoveSubpart(ADeltaX, ADeltaY: Double; ASubpart: Cardinal);
begin
end;
function TvEntity.GetSubpartCount: Integer;
begin
Result := 0;
end;
procedure TvEntity.PositionSubparts(ADest: TFPCustomCanvas; ABaseX,
ABaseY: Double);
begin
end;
procedure TvEntity.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
begin
end;
function TvEntity.AdjustColorToBackground(AColor: TFPColor; ARenderInfo: TvRenderInfo): TFPColor;
begin
Result := AColor;
if not ARenderInfo.AdjustPenColorToBackground then Exit;
// Adjust only if the contranst is really low
if (Abs(AColor.Red - ARenderInfo.BackgroundColor.Red) <= $100) and
(Abs(AColor.Green - ARenderInfo.BackgroundColor.Green) <= $100) and
(Abs(AColor.Blue - ARenderInfo.BackgroundColor.Blue) <= $100) then
begin
if (ARenderInfo.BackgroundColor.Red <= $1000) and
(ARenderInfo.BackgroundColor.Green <= $1000) and
(ARenderInfo.BackgroundColor.Blue <= $1000) then
Result := colWhite
else Result := colBlack;
end;
end;
function TvEntity.GetNormalizedPos(APage: TvVectorialPage; ANewMin,
ANewMax: Double): T3DPoint;
begin
Result.X := (X - APage.MinX) * (ANewMax - ANewMin) / (APage.MaxX - APage.MinX) + ANewMin;
Result.Y := (Y - APage.MinY) * (ANewMax - ANewMin) / (APage.MaxY - APage.MinY) + ANewMin;
Result.Z := (Z - APage.MinZ) * (ANewMax - ANewMin) / (APage.MaxZ - APage.MinZ) + ANewMin;
end;
function TvEntity.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc;
APageItem: Pointer): Pointer;
var
lStr: string;
begin
lStr := Format('[%s] X=%f Y=%f', [Self.ClassName, X, Y]);
Result := ADestRoutine(lStr, APageItem);
end;
function TvEntity.GenerateDebugStrForFPColor(AColor: TFPColor): string;
begin
Result := IntToHex(AColor.Red div $100, 2) + IntToHex(AColor.Green div $100, 2) + IntToHex(AColor.Blue div $100, 2) + IntToHex(AColor.Alpha div $100, 2);
end;
{ TvNamedEntity }
function TvNamedEntity.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc;
APageItem: Pointer): Pointer;
var
lStr: string;
begin
lStr := Format('[%s] Name=%s X=%f Y=%f', [Self.ClassName, Name, X, Y]);
Result := ADestRoutine(lStr, APageItem);
end;
{ TvEntityWithPen }
constructor TvEntityWithPen.Create;
begin
inherited Create;
Pen.Style := psSolid;
Pen.Color := colBlack;
Pen.Width := 1;
end;
procedure TvEntityWithPen.ApplyPenToCanvas(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo);
begin
ADest.Pen.FPColor := AdjustColorToBackground(Pen.Color, ARenderInfo);
ADest.Pen.Width := 1;//Pen.Width;
ADest.Pen.Style := Pen.Style;
end;
procedure TvEntityWithPen.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
ApplyPenToCanvas(ADest, ARenderInfo);
end;
{ TvEntityWithPenAndBrush }
constructor TvEntityWithPenAndBrush.Create;
begin
inherited Create;
Brush.Style := bsClear;
Brush.Color := colBlue;
end;
procedure TvEntityWithPenAndBrush.ApplyBrushToCanvas(ADest: TFPCustomCanvas);
begin
ADest.Brush.FPColor := Brush.Color;
ADest.Brush.Style := Brush.Style;
end;
procedure TvEntityWithPenAndBrush.Render(ADest: TFPCustomCanvas;
ARenderInfo: TvRenderInfo; ADestX: Integer; ADestY: Integer; AMulX: Double; AMulY: Double);
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
ApplyBrushToCanvas(ADest);
end;
{ TvEntityWithPenBrushAndFont }
constructor TvEntityWithPenBrushAndFont.Create;
begin
inherited Create;
Font.Color := colBlack;
end;
procedure TvEntityWithPenBrushAndFont.ApplyFontToCanvas(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; AMulX: Double = 1.0);
var
i: Integer;
{$ifdef USE_LCL_CANVAS}
ALCLDest: TCanvas absolute ADest;
{$endif}
//
LowerDim: T3DPoint;
begin
ADest.Font.Size := Round(AmulX * Font.Size);
ADest.Font.Bold := Font.Bold;
ADest.Font.Italic := Font.Italic;
ADest.Font.Underline := Font.Underline;
ADest.Font.StrikeTrough := Font.StrikeTrough;
{$ifdef USE_LCL_CANVAS}
ALCLDest.Font.Orientation := Round(Font.Orientation * 16);
{$endif}
ADest.Font.FPColor := AdjustColorToBackground(Font.Color, ARenderInfo);
end;
procedure TvEntityWithPenBrushAndFont.Render(ADest: TFPCustomCanvas;
ARenderInfo: TvRenderInfo; ADestX: Integer; ADestY: Integer; AMulX: Double;
AMulY: Double);
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
ApplyFontToCanvas(ADest, ARenderInfo, AMulX);
end;
{ TPath }
constructor TPath.Create;
begin
inherited Create;
FCurMoveSubPartIndex := -1;
end;
//GM: Follow the path to cleanly release the chained list!
destructor TPath.Destroy;
begin
Clear;
inherited Destroy;
end;
procedure TPath.Clear;
var
p, pp, np: TPathSegment;
begin
p:=PointsEnd;
if (p<>nil) then
begin
np:=p.Next;
while (p<>nil) do
begin
pp:=p.Previous;
p.Next:=nil;
p.Previous:=nil;
FreeAndNil(p);
p:=pp;
end;
p:=np;
while (p<>nil) do
begin
np:=p.Next;
p.Next:=nil;
p.Previous:=nil;
FreeAndNil(p);
p:=np;
end;
end;
PointsEnd:=nil;
Points:=nil;
inherited Clear;
end;
procedure TPath.Assign(ASource: TPath);
begin
Len := ASource.Len;
Points := ASource.Points;
PointsEnd := ASource.PointsEnd;
CurPoint := ASource.CurPoint;
Pen := ASource.Pen;
Brush := ASource.Brush;
ClipPath := ASource.ClipPath;
ClipMode := ASource.ClipMode;
end;
procedure TPath.PrepareForSequentialReading;
begin
CurPoint := nil;
end;
function TPath.Next(): TPathSegment;
begin
if CurPoint = nil then Result := Points
else Result := CurPoint.Next;
CurPoint := Result;
end;
procedure TPath.CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double);
var
lSegment: TPathSegment;
l2DSegment: T2DSegment;
lFirstValue: Boolean = True;
begin
inherited CalculateBoundingBox(ADest, ALeft, ATop, ARight, ABottom);
PrepareForSequentialReading();
lSegment := Next();
while lSegment <> nil do
begin
if lSegment is T2DSegment then
begin
l2DSegment := T2DSegment(lSegment);
if lFirstValue then
begin
ALeft := l2DSegment.X;
ATop := l2DSegment.Y;
ARight := l2DSegment.X;
ABottom := l2DSegment.Y;
lFirstValue := False;
end
else
begin
if l2DSegment.X < ALeft then ALeft := l2DSegment.X;
if l2DSegment.Y < ATop then ATop := l2DSegment.Y;
if l2DSegment.X > ARight then ARight := l2DSegment.X;
if l2DSegment.Y > ABottom then ABottom := l2DSegment.Y;
end;
end;
lSegment := Next();
end;
end;
procedure TPath.AppendSegment(ASegment: TPathSegment);
var
L: Integer;
begin
// Check if we are the first segment in the tmp path
if PointsEnd = nil then
begin
if Len <> 0 then
Exception.Create('[TPath.AppendSegment] Assertion failed Len <> 0 with PointsEnd = nil');
Points := ASegment;
PointsEnd := ASegment;
Len := 1;
Exit;
end;
L := Len;
Inc(Len);
// Adds the element to the end of the list
PointsEnd.Next := ASegment;
ASegment.Previous := PointsEnd;
PointsEnd := ASegment;
end;
procedure TPath.AppendMoveToSegment(AX, AY: Double);
var
segment: T2DSegment;
begin
segment := T2DSegment.Create;
segment.SegmentType := stMoveTo;
segment.X := AX;
segment.Y := AY;
AppendSegment(segment);
end;
procedure TPath.AppendLineToSegment(AX, AY: Double);
var
segment: T2DSegment;
begin
segment := T2DSegment.Create;
segment.SegmentType := st2DLine;
segment.X := AX;
segment.Y := AY;
AppendSegment(segment);
end;
procedure TPath.Move(ADeltaX, ADeltaY: Double);
var
i: Integer;
begin
inherited Move(ADeltaX, ADeltaY);
for i := 0 to GetSubpartCount()-1 do
begin
MoveSubpart(ADeltaX, ADeltaY, i);
end;
end;
procedure TPath.MoveSubpart(ADeltaX, ADeltaY: Double; ASubpart: Cardinal);
var
lCurPart: TPathSegment;
begin
if (ASubPart < 0) or (ASubPart > Len) then
raise Exception.Create(Format('[TPath.MoveSubpart] Invalid index %d', [ASubpart]));
// Move to the subpart
lCurPart := MoveToSubpart(ASubpart);
// Do the change
lCurPart.Move(ADeltaX, ADeltaY);
end;
function TPath.MoveToSubpart(ASubpart: Cardinal): TPathSegment;
var
i: Integer;
begin
if (ASubPart < 0) or (ASubPart > Len) then
raise Exception.Create(Format('[TPath.MoveToSubpart] Invalid index %d', [ASubpart]));
// Move to the subpart
if (ASubPart = FCurMoveSubPartIndex) then
begin
Result := FCurMoveSubPartSegment;
end
else if (FCurMoveSubPartSegment <> nil) and (ASubPart = FCurMoveSubPartIndex + 1) then
begin
Result := FCurMoveSubPartSegment.Next;
FCurMoveSubPartIndex := FCurMoveSubPartIndex + 1;
FCurMoveSubPartSegment := Result;
end
else if (FCurMoveSubPartSegment <> nil) and (ASubPart = FCurMoveSubPartIndex - 1) then
begin
Result := FCurMoveSubPartSegment.Previous;
FCurMoveSubPartIndex := FCurMoveSubPartIndex - 1;
FCurMoveSubPartSegment := Result;
end
else
begin
Result := Points;
for i := 0 to ASubpart-1 do
Result := Result.Next;
FCurMoveSubPartIndex := ASubpart;
FCurMoveSubPartSegment := Result;
end;
end;
function TPath.GetSubpartCount: Integer;
begin
Result := Len;
end;
procedure TPath.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
function CoordToCanvasX(ACoord: Double): Integer;
begin
Result := Round(ADestX + AmulX * ACoord);
end;
function CoordToCanvasY(ACoord: Double): Integer;
begin
Result := Round(ADestY + AmulY * ACoord);
end;
var
j, k: Integer;
PosX, PosY: Double; // Not modified by ADestX, etc
CoordX, CoordY: Integer;
CurSegment: TPathSegment;
Cur2DSegment: T2DSegment absolute CurSegment;
Cur2DBSegment: T2DBezierSegment absolute CurSegment;
// For bezier
CurX, CurY: Integer; // Not modified by ADestX, etc
CoordX2, CoordY2, CoordX3, CoordY3, CoordX4, CoordY4: Integer;
CurveLength: Integer;
t: Double;
// For polygons
Points: array of TPoint;
// Clipping Region
{$ifdef USE_LCL_CANVAS}
ClipRegion, OldClipRegion: HRGN;
ACanvas: TCanvas absolute ADest;
{$endif}
begin
PosX := 0;
PosY := 0;
ADest.Brush.Style := bsClear;
ADest.MoveTo(ADestX, ADestY);
// Set the path Pen and Brush options
ADest.Pen.Style := Pen.Style;
ADest.Pen.Width := Round(Pen.Width * AMulX);
if ADest.Pen.Width < 1 then ADest.Pen.Width := 1;
if (Pen.Width <= 2) and (ADest.Pen.Width > 2) then ADest.Pen.Width := 2;
if (Pen.Width <= 5) and (ADest.Pen.Width > 5) then ADest.Pen.Width := 5;
ADest.Pen.FPColor := AdjustColorToBackground(Pen.Color, ARenderInfo);
ADest.Brush.FPColor := Brush.Color;
// Prepare the Clipping Region, if any
{$ifdef USE_CANVAS_CLIP_REGION}
if ClipPath <> nil then
begin
OldClipRegion := LCLIntf.CreateEmptyRegion();
GetClipRgn(ACanvas.Handle, OldClipRegion);
ClipRegion := ConvertPathToRegion(ClipPath, ADestX, ADestY, AMulX, AMulY);
SelectClipRgn(ACanvas.Handle, ClipRegion);
DeleteObject(ClipRegion);
// debug info
{$ifdef DEBUG_CANVAS_CLIP_REGION}
ConvertPathToPoints(CurPath.ClipPath, ADestX, ADestY, AMulX, AMulY, Points);
ACanvas.Polygon(Points);
{$endif}
end;
{$endif}
//
// For solid paths, draw a polygon for the main internal area
//
if Brush.Style <> bsClear then
begin
PrepareForSequentialReading;
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
Write(' Solid Path Internal Area');
{$endif}
ADest.Brush.Style := Brush.Style;
ADest.Pen.Style := psClear;
SetLength(Points, Len);
for j := 0 to Len - 1 do
begin
//WriteLn('j = ', j);
CurSegment := TPathSegment(Next());
CoordX := CoordToCanvasX(Cur2DSegment.X);
CoordY := CoordToCanvasY(Cur2DSegment.Y);
Points[j].X := CoordX;
Points[j].Y := CoordY;
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
Write(Format(' P%d,%d', [CoordY, CoordY]));
{$endif}
end;
ADest.Polygon(Points);
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
Write(' Now the details ');
{$endif}
end;
//
// For other paths, draw more carefully
//
ADest.Pen.Style := Pen.Style;
PrepareForSequentialReading;
for j := 0 to Len - 1 do
begin
//WriteLn('j = ', j);
CurSegment := TPathSegment(Next());
case CurSegment.SegmentType of
stMoveTo:
begin
CoordX := CoordToCanvasX(Cur2DSegment.X);
CoordY := CoordToCanvasY(Cur2DSegment.Y);
ADest.MoveTo(CoordX, CoordY);
PosX := Cur2DSegment.X;
PosY := Cur2DSegment.Y;
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
Write(Format(' M%d,%d', [CoordY, CoordY]));
{$endif}
end;
// This element can override temporarely the Pen
st2DLineWithPen:
begin
ADest.Pen.FPColor := AdjustColorToBackground(T2DSegmentWithPen(Cur2DSegment).Pen.Color, ARenderInfo);
CoordX := CoordToCanvasX(PosX);
CoordY := CoordToCanvasY(PosY);
CoordX2 := CoordToCanvasX(Cur2DSegment.X);
CoordY2 := CoordToCanvasY(Cur2DSegment.Y);
ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
PosX := Cur2DSegment.X;
PosY := Cur2DSegment.Y;
ADest.Pen.FPColor := Pen.Color;
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
Write(Format(' L%d,%d', [CoordToCanvasX(Cur2DSegment.X), CoordToCanvasY(Cur2DSegment.Y)]));
{$endif}
end;
st2DLine, st3DLine:
begin
CoordX := CoordToCanvasX(PosX);
CoordY := CoordToCanvasY(PosY);
CoordX2 := CoordToCanvasX(Cur2DSegment.X);
CoordY2 := CoordToCanvasY(Cur2DSegment.Y);
ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
PosX := Cur2DSegment.X;
PosY := Cur2DSegment.Y;
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
Write(Format(' L%d,%d', [CoordX, CoordY]));
{$endif}
end;
{ To draw a bezier we need to divide the interval in parts and make
lines between this parts }
st2DBezier, st3DBezier:
begin
CoordX := CoordToCanvasX(PosX);
CoordY := CoordToCanvasY(PosY);
CoordX2 := CoordToCanvasX(Cur2DBSegment.X2);
CoordY2 := CoordToCanvasY(Cur2DBSegment.Y2);
CoordX3 := CoordToCanvasX(Cur2DBSegment.X3);
CoordY3 := CoordToCanvasY(Cur2DBSegment.Y3);
CoordX4 := CoordToCanvasX(Cur2DBSegment.X);
CoordY4 := CoordToCanvasY(Cur2DBSegment.Y);
SetLength(Points, 0);
AddBezierToPoints(
Make2DPoint(CoordX, CoordY),
Make2DPoint(CoordX2, CoordY2),
Make2DPoint(CoordX3, CoordY3),
Make2DPoint(CoordX4, CoordY4),
Points
);
ADest.Brush.Style := Brush.Style;
if Length(Points) >= 3 then
ADest.Polygon(Points);
PosX := Cur2DSegment.X;
PosY := Cur2DSegment.Y;
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
Write(Format(' ***C%d,%d %d,%d %d,%d %d,%d',
[CoordToCanvasX(PosX), CoordToCanvasY(PosY),
CoordToCanvasX(Cur2DBSegment.X2), CoordToCanvasY(Cur2DBSegment.Y2),
CoordToCanvasX(Cur2DBSegment.X3), CoordToCanvasY(Cur2DBSegment.Y3),
CoordToCanvasX(Cur2DBSegment.X), CoordToCanvasY(Cur2DBSegment.Y)]));
{$endif}
end;
end;
end;
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
WriteLn('');
{$endif}
// Restores the previous Clip Region
{$ifdef USE_CANVAS_CLIP_REGION}
if ClipPath <> nil then
begin
SelectClipRgn(ACanvas.Handle, OldClipRegion); //Using OldClipRegion crashes in Qt
end;
{$endif}
end;
function TPath.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc;
APageItem: Pointer): Pointer;
var
lStr: string;
lCurPathSeg: TPathSegment;
begin
lStr := Format('[%s] Name=%s Pen.Color=%s Pen.Style=%s Brush.Color=%s Brush.Style=%s',
[Self.ClassName, Self.Name,
GenerateDebugStrForFPColor(Pen.Color),
GetEnumName(TypeInfo(TFPPenStyle), integer(Pen.Style)),
GenerateDebugStrForFPColor(Brush.Color),
GetEnumName(TypeInfo(TFPBrushStyle), integer(Brush.Style))
]);
Result := ADestRoutine(lStr, APageItem);
// Add sub-entities
PrepareForSequentialReading();
lCurPathSeg := Next();
while lCurPathSeg <> nil do
begin
lCurPathSeg.GenerateDebugTree(ADestRoutine, Result);
lCurPathSeg := Next();
end;
end;
{ TvText }
constructor TvText.Create;
begin
inherited Create;
Value := TStringList.Create;
Font.Color := colBlack;
end;
destructor TvText.Destroy;
begin
Value.Free;
inherited Destroy;
end;
function TvText.TryToSelect(APos: TPoint; var ASubpart: Cardinal): TvFindEntityResult;
var
lProximityFactor: Integer;
begin
lProximityFactor := 5;
if (APos.X > X - lProximityFactor) and (APos.X < X + lProximityFactor)
and (APos.Y > Y - lProximityFactor) and (APos.Y < Y + lProximityFactor) then
Result := vfrFound
else Result := vfrNotFound;
end;
procedure TvText.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
function CoordToCanvasX(ACoord: Double): Integer;
begin
Result := Round(ADestX + AmulX * ACoord);
end;
function CoordToCanvasY(ACoord: Double): Integer;
begin
Result := Round(ADestY + AmulY * ACoord);
end;
var
i: Integer;
//
LowerDim: T3DPoint;
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
// Don't draw anything if we have alpha=zero
if Font.Color.Alpha = 0 then Exit;
// TvText supports multiple lines
for i := 0 to Value.Count - 1 do
begin
if Font.Size = 0 then
LowerDim.Y := CoordToCanvasY(Y) + 12 * (i - Value.Count)
else
begin
LowerDim.Y := Y + Font.Size * 1.2 * (Value.Count - i);
LowerDim.Y := CoordToCanvasY(LowerDim.Y);
end;
ADest.Font.FPColor := AdjustColorToBackground(Font.Color, ARenderInfo);
ADest.TextOut(CoordToCanvasX(X), Round(LowerDim.Y), Value.Strings[i]);
end;
end;
function TvText.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc;
APageItem: Pointer): Pointer;
var
lStr: string;
begin
lStr := Format('[%s] Name=%s X=%f Y=%f Text=%s', [Self.ClassName, Name, X, Y, Value.Text]);
Result := ADestRoutine(lStr, APageItem);
end;
{ TvCircle }
procedure TvCircle.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
function CoordToCanvasX(ACoord: Double): Integer;
begin
Result := Round(ADestX + AmulX * ACoord);
end;
function CoordToCanvasY(ACoord: Double): Integer;
begin
Result := Round(ADestY + AmulY * ACoord);
end;
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
ADest.Ellipse(
CoordToCanvasX(X - Radius),
CoordToCanvasY(Y - Radius),
CoordToCanvasX(X + Radius),
CoordToCanvasY(Y + Radius)
);
end;
{ TvCircularArc }
procedure TvCircularArc.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
function CoordToCanvasX(ACoord: Double): Integer;
begin
Result := Round(ADestX + AmulX * ACoord);
end;
function CoordToCanvasY(ACoord: Double): Integer;
begin
Result := Round(ADestY + AmulY * ACoord);
end;
var
FinalStartAngle, FinalEndAngle: double;
BoundsLeft, BoundsTop, BoundsRight, BoundsBottom,
IntStartAngle, IntAngleLength, IntTmp: Integer;
{$ifdef USE_LCL_CANVAS}
ALCLDest: TCanvas absolute ADest;
{$endif}
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
{$ifdef USE_LCL_CANVAS}
// ToDo: Consider a X axis inversion
// If the Y axis is inverted, then we need to mirror our angles as well
BoundsLeft := CoordToCanvasX(X - Radius);
BoundsTop := CoordToCanvasY(Y - Radius);
BoundsRight := CoordToCanvasX(X + Radius);
BoundsBottom := CoordToCanvasY(Y + Radius);
{if AMulY > 0 then
begin}
FinalStartAngle := StartAngle;
FinalEndAngle := EndAngle;
{end
else // AMulY is negative
begin
// Inverting the angles generates the correct result for Y axis inversion
if CurArc.EndAngle = 0 then FinalStartAngle := 0
else FinalStartAngle := 360 - 1* CurArc.EndAngle;
if CurArc.StartAngle = 0 then FinalEndAngle := 0
else FinalEndAngle := 360 - 1* CurArc.StartAngle;
end;}
IntStartAngle := Round(16*FinalStartAngle);
IntAngleLength := Round(16*(FinalEndAngle - FinalStartAngle));
// On Gtk2 and Carbon, the Left really needs to be to the Left of the Right position
// The same for the Top and Bottom
// On Windows it works fine either way
// On Gtk2 if the positions are inverted then the arcs are screwed up
// In Carbon if the positions are inverted, then the arc is inverted
if BoundsLeft > BoundsRight then
begin
IntTmp := BoundsLeft;
BoundsLeft := BoundsRight;
BoundsRight := IntTmp;
end;
if BoundsTop > BoundsBottom then
begin
IntTmp := BoundsTop;
BoundsTop := BoundsBottom;
BoundsBottom := IntTmp;
end;
// Arc(ALeft, ATop, ARight, ABottom, Angle16Deg, Angle16DegLength: Integer);
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
// WriteLn(Format('Drawing Arc Center=%f,%f Radius=%f StartAngle=%f AngleLength=%f',
// [CurArc.CenterX, CurArc.CenterY, CurArc.Radius, IntStartAngle/16, IntAngleLength/16]));
{$endif}
ALCLDest.Arc(
BoundsLeft, BoundsTop, BoundsRight, BoundsBottom,
IntStartAngle, IntAngleLength
);
// Debug info
// {$define FPVECTORIALDEBUG}
// {$ifdef FPVECTORIALDEBUG}
// WriteLn(Format('Drawing Arc x1y1=%d,%d x2y2=%d,%d start=%d end=%d',
// [BoundsLeft, BoundsTop, BoundsRight, BoundsBottom, IntStartAngle, IntAngleLength]));
// {$endif}
{ ADest.TextOut(CoordToCanvasX(CurArc.CenterX), CoordToCanvasY(CurArc.CenterY),
Format('R=%d S=%d L=%d', [Round(CurArc.Radius*AMulX), Round(FinalStartAngle),
Abs(Round((FinalEndAngle - FinalStartAngle)))]));
ADest.Pen.Color := TColor($DDDDDD);
ADest.Rectangle(
BoundsLeft, BoundsTop, BoundsRight, BoundsBottom);
ADest.Pen.Color := clBlack;}
{$endif}
end;
{ TvEllipse }
procedure TvEllipse.CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight, ABottom: Double);
var
t, tmp: Double;
begin
// First do the trivial
ALeft := X - HorzHalfAxis;
ARight := X + HorzHalfAxis;
ATop := Y - VertHalfAxis;
ABottom := Y + VertHalfAxis;
{
To calculate the bounding rectangle we can do this:
Ellipse equations:You could try using the parametrized equations for an ellipse rotated at an arbitrary angle:
x = CenterX + MajorHalfAxis*cos(t)*cos(Angle) - MinorHalfAxis*sin(t)*sin(Angle)
y = CenterY + MinorHalfAxis*sin(t)*cos(Angle) + MajorHalfAxis*cos(t)*sin(Angle)
You can then differentiate and solve for gradient = 0:
0 = dx/dt = -MajorHalfAxis*sin(t)*cos(Angle) - MinorHalfAxis*cos(t)*sin(Angle)
=>
tan(t) = -MinorHalfAxis*tan(Angle)/MajorHalfAxis
=>
t = cotang(-MinorHalfAxis*tan(Angle)/MajorHalfAxis)
On the other axis:
0 = dy/dt = b*cos(t)*cos(phi) - a*sin(t)*sin(phi)
=>
tan(t) = b*cot(phi)/a
}
if Angle <> 0.0 then
begin
t := cotan(-VertHalfAxis*tan(Angle)/HorzHalfAxis);
tmp := X + HorzHalfAxis*cos(t)*cos(Angle) - VertHalfAxis*sin(t)*sin(Angle);
ARight := Round(tmp);
end;
end;
procedure TvEllipse.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
function CoordToCanvasX(ACoord: Double): Integer;
begin
Result := Round(ADestX + AmulX * ACoord);
end;
function CoordToCanvasY(ACoord: Double): Integer;
begin
Result := Round(ADestY + AmulY * ACoord);
end;
var
PointList: array[0..6] of TPoint;
f: TPoint;
dk, x1, x2, y1, y2: Integer;
fx1, fy1, fx2, fy2: Double;
{$ifdef USE_LCL_CANVAS}
ALCLDest: TCanvas absolute ADest;
{$endif}
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
CalculateBoundingBox(ADest, fx1, fy1, fx2, fy2);
x1 := CoordToCanvasX(fx1);
x2 := CoordToCanvasX(fx2);
y1 := CoordToCanvasY(fy1);
y2 := CoordToCanvasY(fy2);
{$ifdef USE_LCL_CANVAS}
if Angle <> 0 then
begin
dk := Round(0.654 * Abs(y2-y1));
f.x := Round(X);
f.y := Round(Y - 1);
PointList[0] := Rotate2DPoint(Point(x1, f.y), f, Angle) ; // Startpoint
PointList[1] := Rotate2DPoint(Point(x1, f.y - dk), f, Angle);
//Controlpoint of Startpoint first part
PointList[2] := Rotate2DPoint(Point(x2- 1, f.y - dk), f, Angle);
//Controlpoint of secondpoint first part
PointList[3] := Rotate2DPoint(Point(x2 -1 , f.y), f, Angle);
// Firstpoint of secondpart
PointList[4] := Rotate2DPoint(Point(x2-1 , f.y + dk), f, Angle);
// Controllpoint of secondpart firstpoint
PointList[5] := Rotate2DPoint(Point(x1, f.y + dk), f, Angle);
// Conrollpoint of secondpart endpoint
PointList[6] := PointList[0]; // Endpoint of
// Back to the startpoint
ALCLDest.PolyBezier(Pointlist[0]);
end
else
{$endif}
begin
ADest.Ellipse(x1, y1, x2, y2);
end;
end;
{ TvRectangle }
procedure TvRectangle.CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft,
ATop, ARight, ABottom: Double);
begin
ALeft := X;
ARight := X + CX;
ATop := Y;
ABottom := Y + CY;
end;
procedure TvRectangle.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
function CoordToCanvasX(ACoord: Double): Integer;
begin
Result := Round(ADestX + AmulX * ACoord);
end;
function CoordToCanvasY(ACoord: Double): Integer;
begin
Result := Round(ADestY + AmulY * ACoord);
end;
var
x1, x2, y1, y2: Integer;
fx1, fy1, fx2, fy2: Double;
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
CalculateBoundingBox(ADest, fx1, fy1, fx2, fy2);
x1 := CoordToCanvasX(fx1);
x2 := CoordToCanvasX(fx2);
y1 := CoordToCanvasY(fy1);
y2 := CoordToCanvasY(fy2);
{$ifdef USE_LCL_CANVAS}
if (RX = 0) and (RY = 0) then
ADest.Rectangle(x1, y1, x2, y2)
else
LCLIntf.RoundRect(TCanvas(ADest).Handle, x1, y1, x2, y2, Round(rx), Round(ry));
{$else}
ADest.Rectangle(x1, y1, x2, y2)
{$endif}
end;
{ TvAlignedDimension }
procedure TvAlignedDimension.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
function CoordToCanvasX(ACoord: Double): Integer;
begin
Result := Round(ADestX + AmulX * ACoord);
end;
function CoordToCanvasY(ACoord: Double): Integer;
begin
Result := Round(ADestY + AmulY * ACoord);
end;
var
Points: array of TPoint;
UpperDim, LowerDim: T3DPoint;
{$ifdef USE_LCL_CANVAS}
ALCLDest: TCanvas absolute ADest;
{$endif}
begin
ADest.Pen.FPColor := AdjustColorToBackground(colBlack, ARenderInfo);
ADest.Pen.Width := 1;
ADest.Pen.Style := psSolid;
//
// Draws this shape:
// horizontal vertical
// ___
// | | or ---| X cm
// | --|
// Which marks the dimension
ADest.MoveTo(CoordToCanvasX(BaseRight.X), CoordToCanvasY(BaseRight.Y));
ADest.LineTo(CoordToCanvasX(DimensionRight.X), CoordToCanvasY(DimensionRight.Y));
ADest.LineTo(CoordToCanvasX(DimensionLeft.X), CoordToCanvasY(DimensionLeft.Y));
ADest.LineTo(CoordToCanvasX(BaseLeft.X), CoordToCanvasY(BaseLeft.Y));
// Now the arrows
// horizontal
SetLength(Points, 3);
if DimensionRight.Y = DimensionLeft.Y then
begin
ADest.Brush.FPColor := AdjustColorToBackground(colBlack, ARenderInfo);
ADest.Brush.Style := bsSolid;
// Left arrow
Points[0] := Point(CoordToCanvasX(DimensionLeft.X), CoordToCanvasY(DimensionLeft.Y));
Points[1] := Point(Points[0].X + 7, Points[0].Y - 3);
Points[2] := Point(Points[0].X + 7, Points[0].Y + 3);
ADest.Polygon(Points);
// Right arrow
Points[0] := Point(CoordToCanvasX(DimensionRight.X), CoordToCanvasY(DimensionRight.Y));
Points[1] := Point(Points[0].X - 7, Points[0].Y - 3);
Points[2] := Point(Points[0].X - 7, Points[0].Y + 3);
ADest.Polygon(Points);
ADest.Brush.Style := bsClear;
// Dimension text
Points[0].X := CoordToCanvasX((DimensionLeft.X+DimensionRight.X)/2);
Points[0].Y := CoordToCanvasY(DimensionLeft.Y);
LowerDim.X := DimensionRight.X-DimensionLeft.X;
ADest.Font.Size := 10;
ADest.Font.Orientation := 0;
ADest.Font.FPColor := AdjustColorToBackground(colBlack, ARenderInfo);
ADest.TextOut(Points[0].X, Points[0].Y-Round(ADest.Font.Size*1.5), Format('%.1f', [LowerDim.X]));
end
else
begin
ADest.Brush.FPColor := AdjustColorToBackground(colBlack, ARenderInfo);
ADest.Brush.Style := bsSolid;
// There is no upper/lower preference for DimensionLeft/Right, so we need to check
if DimensionLeft.Y > DimensionRight.Y then
begin
UpperDim := DimensionLeft;
LowerDim := DimensionRight;
end
else
begin
UpperDim := DimensionRight;
LowerDim := DimensionLeft;
end;
// Upper arrow
Points[0] := Point(CoordToCanvasX(UpperDim.X), CoordToCanvasY(UpperDim.Y));
Points[1] := Point(Points[0].X + Round(AMulX), Points[0].Y - Round(AMulY*3));
Points[2] := Point(Points[0].X - Round(AMulX), Points[0].Y - Round(AMulY*3));
ADest.Polygon(Points);
// Lower arrow
Points[0] := Point(CoordToCanvasX(LowerDim.X), CoordToCanvasY(LowerDim.Y));
Points[1] := Point(Points[0].X + Round(AMulX), Points[0].Y + Round(AMulY*3));
Points[2] := Point(Points[0].X - Round(AMulX), Points[0].Y + Round(AMulY*3));
ADest.Polygon(Points);
ADest.Brush.Style := bsClear;
// Dimension text
Points[0].X := CoordToCanvasX(DimensionLeft.X);
Points[0].Y := CoordToCanvasY((DimensionLeft.Y+DimensionRight.Y)/2);
LowerDim.Y := DimensionRight.Y-DimensionLeft.Y;
if LowerDim.Y < 0 then LowerDim.Y := -1 * LowerDim.Y;
ADest.Font.Size := 10;
ADest.Font.Orientation := 900;
ADest.Font.FPColor := AdjustColorToBackground(colBlack, ARenderInfo);
ADest.TextOut(Points[0].X-Round(ADest.Font.Size*1.5), Points[0].Y, Format('%.1f', [LowerDim.Y]));
ADest.Font.Orientation := 0;
end;
SetLength(Points, 0);
{$IFDEF FPVECTORIAL_DEBUG_DIMENSIONS}
WriteLn(Format('[TvAlignedDimension.Render] BaseRightXY=%f | %f DimensionRightXY=%f | %f DimensionLeftXY=%f | %f',
[BaseRight.X, BaseRight.Y, DimensionRight.X, DimensionRight.Y, DimensionLeft.X, DimensionLeft.Y]));
{$ENDIF}
{ // Debug info
ADest.TextOut(CoordToCanvasX(CurDim.BaseRight.X), CoordToCanvasY(CurDim.BaseRight.Y), 'BR');
ADest.TextOut(CoordToCanvasX(CurDim.DimensionRight.X), CoordToCanvasY(CurDim.DimensionRight.Y), 'DR');
ADest.TextOut(CoordToCanvasX(CurDim.DimensionLeft.X), CoordToCanvasY(CurDim.DimensionLeft.Y), 'DL');
ADest.TextOut(CoordToCanvasX(CurDim.BaseLeft.X), CoordToCanvasY(CurDim.BaseLeft.Y), 'BL');}
end;
{ TvRadialDimension }
procedure TvRadialDimension.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
function CoordToCanvasX(ACoord: Double): Integer;
begin
Result := Round(ADestX + AmulX * ACoord);
end;
function CoordToCanvasY(ACoord: Double): Integer;
begin
Result := Round(ADestY + AmulY * ACoord);
end;
var
Points: array of TPoint;
lAngle, lRadius: Double;
{$ifdef USE_LCL_CANVAS}
ALCLDest: TCanvas absolute ADest;
{$endif}
begin
ADest.Pen.FPColor := AdjustColorToBackground(colBlack, ARenderInfo);
ADest.Pen.Width := 1;
ADest.Pen.Style := psSolid;
// The size of the radius of the circle
lRadius := sqrt(sqr(Center.X - DimensionLeft.X) + sqr(Center.Y - DimensionLeft.Y));
// The angle to the first dimension
lAngle := arctan((DimensionLeft.Y - Center.Y) / (DimensionLeft.X - Center.X));
// Get an arrow in the right part of the circle
SetLength(Points, 3);
ADest.Brush.FPColor := AdjustColorToBackground(colBlack, ARenderInfo);
ADest.Brush.Style := bsSolid;
Points[0] := Point(CoordToCanvasX(Center.X + lRadius), CoordToCanvasY(Center.Y));
Points[1] := Point(CoordToCanvasX(Center.X + lRadius*0.8), CoordToCanvasY(Center.Y - lRadius*0.1));
Points[2] := Point(CoordToCanvasX(Center.X + lRadius*0.8), CoordToCanvasY(Center.Y + lRadius*0.1));
// Now rotate it to the actual position
Points[0] := Rotate2DPoint(Points[0], Point(CoordToCanvasX(Center.X), CoordToCanvasY(Center.Y)), lAngle);
Points[1] := Rotate2DPoint(Points[1], Point(CoordToCanvasX(Center.X), CoordToCanvasY(Center.Y)), lAngle);
Points[2] := Rotate2DPoint(Points[2], Point(CoordToCanvasX(Center.X), CoordToCanvasY(Center.Y)), lAngle);
if not IsDiameter then
begin
// Basic line
ADest.MoveTo(CoordToCanvasX(Center.X), CoordToCanvasY(Center.Y));
ADest.LineTo(CoordToCanvasX(DimensionLeft.X), CoordToCanvasY(DimensionLeft.Y));
// Draw the arrow
ADest.Polygon(Points);
ADest.Brush.Style := bsClear;
// Dimension text
Points[0].X := CoordToCanvasX(Center.X);
Points[0].Y := CoordToCanvasY(Center.Y);
ADest.Font.Size := 10;
ADest.Font.FPColor := AdjustColorToBackground(colBlack, ARenderInfo);
ADest.TextOut(Points[0].X, Points[0].Y, Format('%.1f', [lRadius]));
end
else
begin
// Basic line
ADest.MoveTo(CoordToCanvasX(DimensionLeft.X), CoordToCanvasY(DimensionLeft.Y));
ADest.LineTo(CoordToCanvasX(DimensionRight.X), CoordToCanvasY(DimensionRight.Y));
// Draw the first arrow
ADest.Polygon(Points);
ADest.Brush.Style := bsClear;
// And the second
Points[0] := Point(CoordToCanvasX(Center.X + lRadius), CoordToCanvasY(Center.Y));
Points[1] := Point(CoordToCanvasX(Center.X + lRadius*0.8), CoordToCanvasY(Center.Y - lRadius*0.1));
Points[2] := Point(CoordToCanvasX(Center.X + lRadius*0.8), CoordToCanvasY(Center.Y + lRadius*0.1));
// Now rotate it to the actual position
Points[0] := Rotate2DPoint(Points[0], Point(CoordToCanvasX(Center.X), CoordToCanvasY(Center.Y)), lAngle + Pi);
Points[1] := Rotate2DPoint(Points[1], Point(CoordToCanvasX(Center.X), CoordToCanvasY(Center.Y)), lAngle + Pi);
Points[2] := Rotate2DPoint(Points[2], Point(CoordToCanvasX(Center.X), CoordToCanvasY(Center.Y)), lAngle + Pi);
//
ADest.Polygon(Points);
ADest.Brush.Style := bsClear;
// Dimension text
Points[0].X := CoordToCanvasX(Center.X);
Points[0].Y := CoordToCanvasY(Center.Y);
ADest.Font.Size := 10;
ADest.Font.FPColor := AdjustColorToBackground(colBlack, ARenderInfo);
ADest.TextOut(Points[0].X, Points[0].Y, Format('%.1f', [lRadius * 2]));
end;
SetLength(Points, 0);
end;
{ TvRasterImage }
procedure TvRasterImage.InitializeWithConvertionOf3DPointsToHeightMap(APage: TvVectorialPage; AWidth, AHeight: Integer);
var
lEntity: TvEntity;
i: Integer;
lPos: TPoint;
lValue: TFPColor;
PreviousValue: Word;
PreviousCount: Integer;
begin
lValue := colBlack;
// First setup the map and initialize it
if RasterImage <> nil then RasterImage.Free;
RasterImage := TFPMemoryImage.create(AWidth, AHeight);
// Now go through all points and attempt to fit them to our grid
for i := 0 to APage.GetEntitiesCount - 1 do
begin
lEntity := APage.GetEntity(i);
if lEntity is TvPoint then
begin
lPos.X := Round((lEntity.X - APage.MinX) * AWidth / (APage.MaxX - APage.MinX));
lPos.Y := Round((lEntity.Y - APage.MinY) * AHeight / (APage.MaxY - APage.MinY));
if lPos.X >= AWidth then lPos.X := AWidth-1;
if lPos.Y >= AHeight then lPos.Y := AHeight-1;
if lPos.X < 0 then lPos.X := 0;
if lPos.Y < 0 then lPos.Y := 0;
// Calculate the height of this point
PreviousValue := lValue.Red;
lValue.Red := Round((lEntity.Z - APage.MinZ) * $FFFF / (APage.MaxZ - APage.MinZ));
// And apply it as a fraction of the total number of points which fall in this square
// we store the number of points in the Alpha channel
PreviousCount := lValue.Alpha div $100;
lValue.Red := Round((PreviousCount * PreviousValue + lValue.Red) / (PreviousCount + 1));
lValue.Green := lValue.Red;
lValue.Blue := lValue.Red;
lValue.Alpha := lValue.Alpha + $100;
//lValue.alpha:=;
RasterImage.Colors[lPos.X, lPos.Y] := lValue;
end;
end;
end;
{ TvArrow }
procedure TvArrow.CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop,
ARight, ABottom: Double);
begin
end;
procedure TvArrow.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
function CoordToCanvasX(ACoord: Double): Integer;
begin
Result := Round(ADestX + AmulX * ACoord);
end;
function CoordToCanvasY(ACoord: Double): Integer;
begin
Result := Round(ADestY + AmulY * ACoord);
end;
var
lArrow, lBase, lExtraBase: TPoint;
lPointD, lPointE, lPointF: T3DPoint;
lPoints: array[0..2] of TPoint;
AlfaAngle: Double;
begin
ApplyPenToCanvas(ADest, ARenderInfo);
ApplyBrushToCanvas(ADest);
lArrow.X := CoordToCanvasX(X);
lArrow.Y := CoordToCanvasY(Y);
lBase.X := CoordToCanvasX(Base.X);
lBase.Y := CoordToCanvasY(Base.Y);
lExtraBase.X := CoordToCanvasX(ExtraLineBase.X);
lExtraBase.Y := CoordToCanvasY(ExtraLineBase.Y);
// Start with the lines
ADest.Line(lArrow, lBase);
if HasExtraLine then
ADest.Line(lBase, lExtraBase);
// Now draw the arrow head
lPoints[0].X := CoordToCanvasX(X);
lPoints[0].Y := CoordToCanvasY(Y);
//
// Here a lot of trigonometry comes to play, it is hard to explain in text, but in essence
//
// A line L is formed by the points A (Arrow head) and B (Base)
// Our smaller triangle starts at a point D in this line which has length ArrowLength counting from A
// This forms a rectangle triangle with a line paralel to the X axis
// Alfa is the angle between A and the line parallel to the X axis
//
// This brings this equations:
// AlfaAngle := arctg((B.Y - A.Y) / (B.X - A.X));
// Sin(Alfa) := (D.Y - A.Y) / ArrowLength
// Cos(Alfa) := (D.X - A.X) / ArrowLength
//
// Then at this point D we start a line perpendicular to the line L
// And with this line we progress a length of ArrowBaseLength/2
// This line, the D point and a line parallel to the Y axis for another
// rectangle triangle with the same Alfa angle at the point D
// The point at the end of the hipotenuse of this triangle is our point E
// So we have more equations:
//
// Sin(Alfa) := (E.x - D.X) / (ArrowBaseLength/2)
// Cos(Alfa) := (E.Y - D.Y) / (ArrowBaseLength/2)
//
// And the same in the opposite direction for our point F:
//
// Sin(Alfa) := (D.X - F.X) / (ArrowBaseLength/2)
// Cos(Alfa) := (D.Y - F.Y) / (ArrowBaseLength/2)
//
AlfaAngle := ArcTan((Base.Y - Y) / (Base.X - X));
lPointD.Y := Sin(AlfaAngle) * ArrowLength + Y;
lPointD.X := Cos(AlfaAngle) * ArrowLength + X;
lPointE.X := Sin(AlfaAngle) * (ArrowBaseLength/2) + lPointD.X;
lPointE.Y := Cos(AlfaAngle) * (ArrowBaseLength/2) + lPointD.Y;
lPointF.X := - Sin(AlfaAngle) * (ArrowBaseLength/2) + lPointD.X;
lPointF.Y := - Cos(AlfaAngle) * (ArrowBaseLength/2) + lPointD.Y;
lPoints[1].X := CoordToCanvasX(lPointE.X);
lPoints[1].Y := CoordToCanvasY(lPointE.Y);
lPoints[2].X := CoordToCanvasX(lPointF.X);
lPoints[2].Y := CoordToCanvasY(lPointF.Y);
ADest.Polygon(lPoints);
end;
{ TvFormulaElement }
function TvFormulaElement.CalculateHeight(ADest: TFPCustomCanvas): Double;
var
lLineHeight: Integer;
begin
if ADest <> nil then
lLineHeight := TCanvas(ADest).TextHeight(STR_FPVECTORIAL_TEXT_HEIGHT_SAMPLE) + 2
else
lLineHeight := 15;
case Kind of
//fekVariable, // Text is the text of the variable
//fekEqual, // = symbol
//fekSubtraction, // - symbol
//fekMultiplication, // either a point . or a small x
//fekSum, // + symbol
//fekPlusMinus, // The +/- symbol
fekFraction:
begin
Formula.CalculateHeight(ADest);
AdjacentFormula.CalculateHeight(ADest);
Result := Formula.Height + AdjacentFormula.Height * 1.2;
end;
fekRoot: Result := Formula.CalculateHeight(ADest) * 1.2;
fekPower: Result := lLineHeight * 1.2;
fekSummation: Result := lLineHeight * 1.5;
else
Result := lLineHeight;
end;
Height := Result;
end;
function TvFormulaElement.CalculateWidth(ADest: TFPCustomCanvas): Double;
var
lText: String;
begin
Result := 0;
lText := AsText;
if lText <> '' then
begin
if ADest = nil then Result := 10 * UTF8Length(lText)
else Result := TCanvas(ADest).TextWidth(lText);
end;
case Kind of
fekMultiplication: Result := 0;
//
fekFraction:
begin
Formula.CalculateWidth(ADest);
AdjacentFormula.CalculateWidth(ADest);
Result := Max(Formula.Width, AdjacentFormula.Width);
end;
fekRoot: Result := Formula.CalculateWidth(ADest) + 10;
fekPower, fekSubscript:
begin
Result := Formula.CalculateWidth(ADest) +
AdjacentFormula.CalculateWidth(ADest) / 2;
end;
fekSummation: Result := 8;
else
end;
Width := Result;
end;
function TvFormulaElement.AsText: string;
begin
case Kind of
fekVariable: Result := Text;
fekEqual: Result := '=';
fekSubtraction: Result := '-';
fekMultiplication: Result := 'x';
fekSum: Result := '+';
fekPlusMinus: Result := '+/-';
fekLessThan: Result := '<';
fekLessOrEqualThan: Result := '<=';
fekGreaterThan: Result := '>';
fekGreaterOrEqualThan: Result := '>=';
// More complex elements
else
Result := Format('[%s]', [GetEnumName(TypeInfo(TvFormulaElementKind), integer(Kind))]);
end;
end;
procedure TvFormulaElement.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
function CoordToCanvasX(ACoord: Double): Integer;
begin
Result := Round(ADestX + AmulX * ACoord);
end;
function CoordToCanvasY(ACoord: Double): Integer;
begin
Result := Round(ADestY + AmulY * ACoord);
end;
var
LeftC, TopC: Integer;
lPt: array[0..3] of TPoint;
lOldFontSize: Integer;
lStr: string;
begin
LeftC := CoordToCanvasX(Left);
TopC := CoordToCanvasY(Top);
case Kind of
fekVariable: ADest.TextOut(LeftC, TopC, Text);
fekEqual: ADest.TextOut(LeftC, TopC, '=');
fekSubtraction: ADest.TextOut(LeftC, TopC, '-');
fekMultiplication:
begin
// Don't draw anything, leave an empty space, it looks better
//ADest.TextOut(LeftC, TopC, 'x'); // × -> Unicode times symbol
end;
fekSum: ADest.TextOut(LeftC, TopC, '+');
fekPlusMinus:ADest.TextOut(LeftC, TopC, '±');
fekLessThan: ADest.TextOut(LeftC, TopC, '<');
fekLessOrEqualThan: ADest.TextOut(LeftC, TopC, '≤');
fekGreaterThan: ADest.TextOut(LeftC, TopC, '>');
fekGreaterOrEqualThan: ADest.TextOut(LeftC, TopC, '≥');
fekFraction:
begin
Formula.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
AdjacentFormula.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
// Division line
lPt[0].X := CoordToCanvasX(Formula.Left);
lPt[1].X := CoordToCanvasX(Formula.Left + Formula.Width);
lPt[0].Y := CoordToCanvasY(Formula.Top - Formula.Height);
lPt[1].Y := CoordToCanvasY(Formula.Top - Formula.Height);
ADest.Line(lPt[0].X, lPt[0].Y, lPt[1].X, lPt[1].Y);
end;
fekRoot:
begin
Formula.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
// Root drawing
lPt[0].X := CoordToCanvasX(Left);
lPt[0].Y := CoordToCanvasY(Top - Formula.Height * 0.7 + 5);
// diagonal down
lPt[1].X := CoordToCanvasX(Left + 5);
lPt[1].Y := CoordToCanvasY(Top - Formula.Height * 0.7);
// up
lPt[2].X := CoordToCanvasX(Left + 5);
lPt[2].Y := CoordToCanvasY(Top);
// straight right
lPt[3].X := CoordToCanvasX(Left + Formula.Width);
lPt[3].Y := CoordToCanvasY(Top);
//
ADest.Polyline(lPt);
end;
fekPower:
begin
Formula.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
// The superscripted power
lOldFontSize := ADest.Font.Size;
if lOldFontSize = 0 then ADest.Font.Size := 5
else ADest.Font.Size := lOldFontSize div 2;
AdjacentFormula.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
ADest.Font.Size := lOldFontSize;
end;
fekSubscript:
begin
Formula.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
// The subscripted item
lOldFontSize := ADest.Font.Size;
if lOldFontSize = 0 then ADest.Font.Size := 5
else ADest.Font.Size := lOldFontSize div 2;
AdjacentFormula.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
ADest.Font.Size := lOldFontSize;
end;
fekSummation:
begin
// Draw the summation symbol
lOldFontSize := ADest.Font.Size;
ADest.Font.Size := 15;
lStr := #$E2#$88#$91; // Unicode Character 'N-ARY SUMMATION' (U+2211)
ADest.TextOut(LeftC, TopC, lStr);
ADest.Font.Size := lOldFontSize;
// Draw the bottom/main formula
Formula.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
// Draw the top formula
AdjacentFormula.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
end;
end;
end;
procedure TvFormulaElement.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc;
APageItem: Pointer);
var
lDBGItem, lDBGFormula, lDBGFormulaBottom: Pointer;
lStr: string;
begin
lStr := Format('%s [%s]', [Self.AsText(), GetEnumName(TypeInfo(TvFormulaElementKind), integer(Kind))]);
lStr := lStr + Format(' Left=%f Top=%f Width=%f Height=%f', [Left, Top, Width, Height]);
lDBGItem := ADestRoutine(lStr, APageItem);
case Kind of
fekFraction, fekPower, fekSubscript, fekSummation:
begin
lDBGFormula := ADestRoutine('Main Formula', lDBGItem);
Formula.GenerateDebugTree(ADestRoutine, lDBGFormula);
if Kind in [fekPower, fekSummation] then
lDBGFormulaBottom := ADestRoutine('Top Formula', lDBGItem)
else
lDBGFormulaBottom := ADestRoutine('Bottom Formula', lDBGItem);
AdjacentFormula.GenerateDebugTree(ADestRoutine, lDBGFormulaBottom);
end;
fekRoot: Formula.GenerateDebugTree(ADestRoutine, lDBGItem);
//fekSomatory: Result := 1.5;
end;
end;
{ TvFormula }
procedure TvFormula.CallbackDeleteElement(data, arg: pointer);
begin
TvFormulaElement(data).Free;
end;
constructor TvFormula.Create;
begin
inherited Create;
FElements := TFPList.Create;
SpacingBetweenElementsX := 5;
end;
destructor TvFormula.Destroy;
begin
FElements.Free;
inherited Destroy;
end;
function TvFormula.GetFirstElement: TvFormulaElement;
begin
if FElements.Count = 0 then Exit(nil);
Result := FElements.Items[0];
FCurIndex := 1;
end;
function TvFormula.GetNextElement: TvFormulaElement;
begin
if FElements.Count <= FCurIndex then Exit(nil);
Result := FElements.Items[FCurIndex];
Inc(FCurIndex);
end;
procedure TvFormula.AddElement(AElement: TvFormulaElement);
begin
FElements.Add(AElement);
end;
function TvFormula.AddElementWithKind(AKind: TvFormulaElementKind): TvFormulaElement;
begin
Result := AddElementWithKindAndText(AKind, '');
end;
function TvFormula.AddElementWithKindAndText(AKind: TvFormulaElementKind;
AText: string): TvFormulaElement;
begin
Result := TvFormulaElement.Create;
Result.Kind := AKind;
Result.Text := AText;
AddElement(Result);
case AKind of
fekFraction, fekPower, fekSubscript, fekSummation:
begin
Result.Formula := TvFormula.Create;
Result.AdjacentFormula := TvFormula.Create;
end;
fekRoot:
begin
Result.Formula := TvFormula.Create;
end;
end;
end;
procedure TvFormula.Clear;
begin
inherited Clear;
FElements.ForEachCall(CallbackDeleteElement, nil);
FElements.Clear;
end;
function TvFormula.CalculateHeight(ADest: TFPCustomCanvas): Double;
var
lElement: TvFormulaElement;
begin
if ADest <> nil then
Result := TCanvas(ADest).TextHeight(STR_FPVECTORIAL_TEXT_HEIGHT_SAMPLE) + 2
else
Result := 15;
lElement := GetFirstElement();
while lElement <> nil do
begin
Result := Max(Result, lElement.CalculateHeight(ADest));
lElement := GetNextElement;
end;
// Cache the result
Height := Result;
end;
function TvFormula.CalculateWidth(ADest: TFPCustomCanvas): Double;
var
lElement: TvFormulaElement;
begin
Result := 0;
lElement := GetFirstElement();
while lElement <> nil do
begin
if lElement.Kind <> fekMultiplication then
Result := Result + lElement.CalculateWidth(ADest) + SpacingBetweenElementsX;
lElement := GetNextElement;
end;
// Remove an extra spacing, since it is added even to the last item
Result := Result - SpacingBetweenElementsX;
// Cache the result
Width := Result;
end;
procedure TvFormula.PositionSubparts(ADest: TFPCustomCanvas; ABaseX, ABaseY: Double);
var
lElement: TvFormulaElement;
lPosX: Double = 0;
lPosY: Double = 0;
lCentralizeFactor: Double = 0;
lCentralizeFactorAdj: Double = 0;
lMaxHeight: Double = 0;
begin
CalculateHeight(ADest);
CalculateWidth(ADest);
Left := ABaseX;
Top := ABaseY;
// Then calculate the position of each element
lElement := GetFirstElement();
if lElement = nil then Exit;
while lElement <> nil do
begin
lElement.Left := Left + lPosX;
lPosX := lPosX + lElement.Width + SpacingBetweenElementsX;
lElement.Top := Top;
lMaxHeight := Max(lMaxHeight, lElement.Height);
case lElement.Kind of
fekFraction:
begin
// Check which fraction is the largest and centralize the other one
lElement.Formula.CalculateWidth(ADest);
lElement.AdjacentFormula.CalculateWidth(ADest);
if lElement.Formula.Width > lElement.AdjacentFormula.Width then
begin
lCentralizeFactor := 0;
lCentralizeFactorAdj := lElement.Formula.Width / 2 - lElement.AdjacentFormula.Width / 2;
end
else
begin
lCentralizeFactor := lElement.AdjacentFormula.Width / 2 - lElement.Formula.Width / 2;
lCentralizeFactorAdj := 0;
end;
lElement.Formula.PositionSubparts(ADest, lElement.Left + lCentralizeFactor, lElement.Top);
lElement.AdjacentFormula.PositionSubparts(ADest, lElement.Left + lCentralizeFactorAdj, lElement.Top - lElement.Formula.Height - 3);
end;
fekRoot:
begin
// Give a factor for the root drawing
lElement.Formula.PositionSubparts(ADest, lElement.Left + 10, lElement.Top);
end;
fekPower:
begin
lElement.Formula.PositionSubparts(ADest, lElement.Left, lElement.Top);
lElement.AdjacentFormula.PositionSubparts(ADest, lElement.Left + lElement.Formula.Width, lElement.Top);
end;
fekSubscript:
begin
lElement.Formula.PositionSubparts(ADest, lElement.Left, lElement.Top);
lElement.AdjacentFormula.PositionSubparts(ADest, lElement.Left + lElement.Formula.Width, lElement.Top - lElement.Formula.Height / 2);
end;
fekSummation:
begin
// main/bottom formula
lElement.Formula.PositionSubparts(ADest, lElement.Left, lElement.Top - 30);
// top formula
lElement.AdjacentFormula.PositionSubparts(ADest, lElement.Left, lElement.Top);
end;
end;
lElement := GetNextElement();
end;
// Go back and make a second loop to
// check if there are any high elements in the same line,
// and if yes, centralize the smaller ones
lElement := GetFirstElement();
if lElement = nil then Exit;
while lElement <> nil do
begin
if lElement.Height < lMaxHeight then
begin
lElement.Top := Top - lMaxHeight / 2 + lElement.Height / 2;
end;
lElement := GetNextElement();
end;
end;
procedure TvFormula.CalculateBoundingBox(ADest: TFPCustomCanvas; var ALeft, ATop, ARight,
ABottom: Double);
begin
ALeft := X;
ATop := Y;
ARight := CalculateWidth(ADest);
if ADest = nil then ABottom := CalculateHeight(ADest) * 15
else ABottom := CalculateHeight(ADest) * TCanvas(ADest).TextHeight('Źç');
ARight := X + ARight;
ABottom := Y + ABottom;
end;
procedure TvFormula.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
var
lElement: TvFormulaElement;
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
// First position all elements
PositionSubparts(ADest, Left, Top);
// Now draw them all
lElement := GetFirstElement();
if lElement = nil then Exit;
while lElement <> nil do
begin
lElement.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
lElement := GetNextElement();
end;
end;
function TvFormula.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc;
APageItem: Pointer): Pointer;
var
lFormulaElement: TvFormulaElement;
lStr: string;
begin
lStr := Format('[%s]', [Self.ClassName]);
lStr := lStr + Format(' Left=%f Top=%f Width=%f Height=%f', [Left, Top, Width, Height]);
Result := ADestRoutine(lStr, APageItem);
lFormulaElement := GetFirstElement();
while lFormulaElement <> nil do
begin
lFormulaElement.GenerateDebugTree(ADestRoutine, Result);
lFormulaElement := GetNextElement()
end;
end;
{ TvEntityWithSubEntities }
procedure TvEntityWithSubEntities.CallbackDeleteElement(data, arg: pointer);
begin
TvEntity(data).Free;
end;
constructor TvEntityWithSubEntities.Create;
begin
inherited Create;
FElements := TFPList.Create;
end;
destructor TvEntityWithSubEntities.Destroy;
begin
FElements.Free;
inherited Destroy;
end;
function TvEntityWithSubEntities.GetFirstEntity: TvEntity;
begin
if FElements.Count = 0 then Exit(nil);
Result := FElements.Items[0];
FCurIndex := 1;
end;
function TvEntityWithSubEntities.GetNextEntity: TvEntity;
begin
if FElements.Count <= FCurIndex then Exit(nil);
Result := FElements.Items[FCurIndex];
Inc(FCurIndex);
end;
procedure TvEntityWithSubEntities.AddEntity(AEntity: TvEntity);
begin
FElements.Add(AEntity);
end;
procedure TvEntityWithSubEntities.Clear;
begin
inherited Clear;
FElements.ForEachCall(CallbackDeleteElement, nil);
FElements.Clear;
end;
procedure TvEntityWithSubEntities.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo;
ADestX: Integer; ADestY: Integer; AMulX: Double; AMulY: Double);
var
lEntity: TvEntity;
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
lEntity := GetFirstEntity();
while lEntity <> nil do
begin
{$IFDEF FPVECTORIAL_DEBUG_BLOCKS}
//WriteLn(Format('[TvInsert.Render] Name=%s Block=%s Entity=%s EntityXY=%f | %f BlockXY=%f | %f InsertXY=%f | %f',
// [Name, Block.Name, lEntity.ClassName, lEntity.X, lEntity.Y, Block.X, Block.Y, X, Y]));
{$ENDIF}
// Render
lEntity.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMuly);
lEntity := GetNextEntity();
end;
end;
function TvEntityWithSubEntities.GenerateDebugTree(
ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer;
var
lStr: string;
lCurEntity: TvEntity;
begin
lStr := Format('[%s] Name=%s', [Self.ClassName, Self.Name]);
Result := ADestRoutine(lStr, APageItem);
// Add sub-entities
lCurEntity := GetFirstEntity();
while lCurEntity <> nil do
begin
lCurEntity.GenerateDebugTree(ADestRoutine, Result);
lCurEntity := GetNextEntity();
end;
end;
{ TvInsert }
procedure TvInsert.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
var
lEntity: TvEntity;
OldForceRenderBlock: Boolean;
begin
inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
if InsertEntity = nil then Exit;
// If we are inserting a block, make sure it will render its contents
OldForceRenderBlock := ARenderInfo.ForceRenderBlock;
ARenderInfo.ForceRenderBlock := True;
// Alter the position of the elements to consider the positioning of the BLOCK and of the INSERT
InsertEntity.Move(X, Y);
// Render
InsertEntity.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMuly);
// Change them back
InsertEntity.Move(-X, -Y);
ARenderInfo.ForceRenderBlock := OldForceRenderBlock;
end;
{ TvBlock }
procedure TvBlock.Render(ADest: TFPCustomCanvas; ARenderInfo: TvRenderInfo; ADestX: Integer;
ADestY: Integer; AMulX: Double; AMulY: Double);
var
lEntity: TvEntity;
begin
// blocks are invisible by themselves
//inherited Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMulY);
if not ARenderInfo.ForceRenderBlock then Exit;
lEntity := GetFirstEntity();
while lEntity <> nil do
begin
{$IFDEF FPVECTORIAL_DEBUG_BLOCKS}
WriteLn(Format('[TvInsert.Render] Name=%s Block=%s Entity=%s EntityXY=%f | %f BlockXY=%f | %f InsertXY=%f | %f',
[Name, Block.Name, lEntity.ClassName, lEntity.X, lEntity.Y, Block.X, Block.Y, X, Y]));
{$ENDIF}
// Alter the position of the elements to consider the positioning of the BLOCK and of the INSERT
lEntity.Move(X, Y);
// Render
lEntity.Render(ADest, ARenderInfo, ADestX, ADestY, AMulX, AMuly);
// Change them back
lEntity.Move(-X, -Y);
lEntity := GetNextEntity();
end;
end;
{ TvVectorialPage }
procedure TvVectorialPage.ClearTmpPath;
var
segment, oldsegment: TPathSegment;
begin
FTmpPath.Points := nil;
FTmpPath.PointsEnd := nil;
FTmpPath.Len := 0;
FTmpPath.Brush.Color := colBlue;
FTmpPath.Brush.Style := bsClear;
FTmpPath.Pen.Color := colBlack;
FTmpPath.Pen.Style := psSolid;
FTmpPath.Pen.Width := 1;
end;
procedure TvVectorialPage.AppendSegmentToTmpPath(ASegment: TPathSegment);
begin
FTmpPath.AppendSegment(ASegment);
end;
procedure TvVectorialPage.CallbackDeleteEntity(data, arg: pointer);
begin
if (data <> nil) then
TvEntity(data).Free;
end;
constructor TvVectorialPage.Create(AOwner: TvVectorialDocument);
begin
inherited Create;
FEntities := TFPList.Create;
FTmpPath := TPath.Create;
Owner := AOwner;
Clear();
BackgroundColor := colWhite;
System.FillChar(RenderInfo, SizeOf(RenderInfo), #0);
RenderInfo.BackgroundColor := colWhite;
end;
destructor TvVectorialPage.Destroy;
begin
Clear;
if FTmpPath <> nil then
begin
FTmpPath.Free;
FTmpPath := nil;
end;
FEntities.Free;
FEntities := nil;
inherited Destroy;
end;
procedure TvVectorialPage.Assign(ASource: TvVectorialPage);
var
i: Integer;
begin
Clear;
for i := 0 to ASource.GetEntitiesCount - 1 do
Self.AddEntity(ASource.GetEntity(i));
end;
function TvVectorialPage.GetEntity(ANum: Cardinal): TvEntity;
begin
if ANum >= FEntities.Count then raise Exception.Create('TvVectorialDocument.GetEntity: Entity number out of bounds');
Result := TvEntity(FEntities.Items[ANum]);
if Result = nil then raise Exception.Create('TvVectorialDocument.GetEntity: Invalid Entity number');
end;
function TvVectorialPage.GetEntitiesCount: Integer;
begin
Result := FEntities.Count;
end;
function TvVectorialPage.GetLastEntity(): TvEntity;
begin
Result:=TvEntity(FEntities.Last);
end;
function TvVectorialPage.FindAndSelectEntity(Pos: TPoint): TvFindEntityResult;
var
lEntity: TvEntity;
i: Integer;
lSubpart: Cardinal;
begin
Result := vfrNotFound;
for i := 0 to GetEntitiesCount() - 1 do
begin
lEntity := GetEntity(i);
Result := lEntity.TryToSelect(Pos, lSubpart);
if Result <> vfrNotFound then
begin
Owner.SelectedElement := lEntity;
Exit;
end;
end;
end;
function TvVectorialPage.FindEntityWithNameAndType(AName: string;
AType: TvEntityClass): TvEntity;
var
i: Integer;
lCurEntity: TvEntity;
begin
Result := nil;
for i := 0 to GetEntitiesCount()-1 do
begin
lCurEntity := GetEntity(i);
if (lCurEntity is AType) and
(lCurEntity is TvNamedEntity) and (TvNamedEntity(lCurEntity).Name = AName) then
begin
Result := lCurEntity;
Break;
end;
end;
end;
procedure TvVectorialPage.Clear;
begin
FEntities.ForEachCall(CallbackDeleteEntity, nil);
FEntities.Clear();
ClearTmpPath();
ClearLayerSelection();
end;
{@@
Returns if the entity was really deleted or false if there is no entity with this index
}
function TvVectorialPage.DeleteEntity(AIndex: Cardinal): Boolean;
var
lEntity: TvEntity;
begin
Result := False;
if AIndex >= GetEntitiesCount() then Exit;;
lEntity := GetEntity(AIndex);
if lEntity = nil then Exit;
FEntities.Delete(AIndex);
lEntity.Free;
Result := True;
end;
function TvVectorialPage.RemoveEntity(AEntity: TvEntity; AFreeAfterRemove: Boolean = True): Boolean;
begin
Result := False;
if AEntity = nil then Exit;
FEntities.Remove(AEntity);
if AFreeAfterRemove then AEntity.Free;
Result := True;
end;
{@@
Adds an entity to the document and returns it's current index
}
function TvVectorialPage.AddEntity(AEntity: TvEntity): Integer;
begin
if FCurrentLayer = nil then
begin
Result := FEntities.Count;
FEntities.Add(Pointer(AEntity));
end
// If a layer is selected as current, add elements to it instead
else
begin
Result := FCurrentLayer.GetSubpartCount();
FCurrentLayer.AddEntity(AEntity);
end;
end;
function TvVectorialPage.AddPathCopyMem(APath: TPath; AOnlyCreate: Boolean = False): TPath;
var
lPath: TPath;
Len: Integer;
begin
lPath := TPath.Create;
lPath.Assign(APath);
Result := lPath;
if not AOnlyCreate then AddEntity(lPath);
//WriteLn(':>TvVectorialDocument.AddPath 1 Len = ', Len);
end;
{@@
Starts writing a Path in multiple steps.
Should be followed by zero or more calls to AddPointToPath
and by a call to EndPath to effectively add the data.
@see EndPath, AddPointToPath
}
procedure TvVectorialPage.StartPath(AX, AY: Double);
var
segment: T2DSegment;
begin
ClearTmpPath();
FTmpPath.Len := 1;
segment := T2DSegment.Create;
segment.SegmentType := stMoveTo;
segment.X := AX;
segment.Y := AY;
FTmpPath.Points := segment;
FTmpPath.PointsEnd := segment;
end;
procedure TvVectorialPage.StartPath;
begin
ClearTmpPath();
end;
procedure TvVectorialPage.AddMoveToPath(AX, AY: Double);
var
segment: T2DSegment;
begin
segment := T2DSegment.Create;
segment.SegmentType := stMoveTo;
segment.X := AX;
segment.Y := AY;
AppendSegmentToTmpPath(segment);
end;
{@@
Adds one more point to the end of a Path being
writing in multiple steps.
Does nothing if not called between StartPath and EndPath.
Can be called multiple times to add multiple points.
@see StartPath, EndPath
}
procedure TvVectorialPage.AddLineToPath(AX, AY: Double);
var
segment: T2DSegment;
begin
segment := T2DSegment.Create;
segment.SegmentType := st2DLine;
segment.X := AX;
segment.Y := AY;
AppendSegmentToTmpPath(segment);
end;
procedure TvVectorialPage.AddLineToPath(AX, AY: Double; AColor: TFPColor);
var
segment: T2DSegmentWithPen;
begin
segment := T2DSegmentWithPen.Create;
segment.SegmentType := st2DLineWithPen;
segment.X := AX;
segment.Y := AY;
segment.Pen.Color := AColor;
AppendSegmentToTmpPath(segment);
end;
procedure TvVectorialPage.AddLineToPath(AX, AY, AZ: Double);
var
segment: T3DSegment;
begin
segment := T3DSegment.Create;
segment.SegmentType := st3DLine;
segment.X := AX;
segment.Y := AY;
segment.Z := AZ;
AppendSegmentToTmpPath(segment);
end;
{@@
Gets the current Pen Pos in the temporary path
}
procedure TvVectorialPage.GetCurrentPathPenPos(var AX, AY: Double);
begin
// Check if we are the first segment in the tmp path
if FTmpPath.PointsEnd = nil then raise Exception.Create('[TvVectorialDocument.GetCurrentPathPenPos] One cannot obtain the Pen Pos if there are no segments in the temporary path');
AX := T2DSegment(FTmpPath.PointsEnd).X;
AY := T2DSegment(FTmpPath.PointsEnd).Y;
end;
procedure TvVectorialPage.GetTmpPathStartPos(var AX, AY: Double);
begin
AX := 0;
AY := 0;
if (FTmpPath = nil) or (FTmpPath.GetSubpartCount() <= 0) or (FTmpPath.Points = nil) then Exit;
if FTmpPath.Points is T2DSegment then
begin
AX := T2DSegment(FTmpPath.Points).X;
AY := T2DSegment(FTmpPath.Points).Y;
end;
end;
{@@
Adds a bezier element to the path. It starts where the previous element ended
and it goes throw the control points [AX1, AY1] and [AX2, AY2] and ends
in [AX3, AY3].
}
procedure TvVectorialPage.AddBezierToPath(AX1, AY1, AX2, AY2, AX3, AY3: Double);
var
segment: T2DBezierSegment;
begin
segment := T2DBezierSegment.Create;
segment.SegmentType := st2DBezier;
segment.X := AX3;
segment.Y := AY3;
segment.X2 := AX1;
segment.Y2 := AY1;
segment.X3 := AX2;
segment.Y3 := AY2;
AppendSegmentToTmpPath(segment);
end;
procedure TvVectorialPage.AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, AX3, AY3, AZ3: Double);
var
segment: T3DBezierSegment;
begin
segment := T3DBezierSegment.Create;
segment.SegmentType := st3DBezier;
segment.X := AX3;
segment.Y := AY3;
segment.Z := AZ3;
segment.X2 := AX1;
segment.Y2 := AY1;
segment.Z2 := AZ1;
segment.X3 := AX2;
segment.Y3 := AY2;
segment.Z3 := AZ2;
AppendSegmentToTmpPath(segment);
end;
procedure TvVectorialPage.SetBrushColor(AColor: TFPColor);
begin
FTmPPath.Brush.Color := AColor;
end;
procedure TvVectorialPage.SetBrushStyle(AStyle: TFPBrushStyle);
begin
FTmPPath.Brush.Style := AStyle;
end;
procedure TvVectorialPage.SetPenColor(AColor: TFPColor);
begin
FTmPPath.Pen.Color := AColor;
end;
procedure TvVectorialPage.SetPenStyle(AStyle: TFPPenStyle);
begin
FTmPPath.Pen.Style := AStyle;
end;
procedure TvVectorialPage.SetPenWidth(AWidth: Integer);
begin
FTmPPath.Pen.Width := AWidth;
end;
procedure TvVectorialPage.SetClipPath(AClipPath: TPath; AClipMode: TvClipMode);
begin
FTmPPath.ClipPath := AClipPath;
FTmPPath.ClipMode := AClipMode;
end;
{@@
Finishes writing a Path, which was created in multiple
steps using StartPath and AddPointToPath,
to the document.
Does nothing if there wasn't a previous correspondent call to
StartPath.
@see StartPath, AddPointToPath
}
function TvVectorialPage.EndPath(AOnlyCreate: Boolean = False): TPath;
begin
if FTmPPath.Len = 0 then Exit;
Result := AddPathCopyMem(FTmPPath, AOnlyCreate);
ClearTmpPath();
end;
function TvVectorialPage.AddText(AX, AY, AZ: Double; FontName: string;
FontSize: integer; AText: utf8string; AOnlyCreate: Boolean = False): TvText;
var
lText: TvText;
begin
lText := TvText.Create;
lText.Value.Text := AText;
lText.X := AX;
lText.Y := AY;
lText.Z := AZ;
lText.Font.Name := FontName;
lText.Font.Size := FontSize;
if not AOnlyCreate then AddEntity(lText);
Result := lText;
end;
function TvVectorialPage.AddText(AX, AY: Double; AStr: utf8string; AOnlyCreate: Boolean = False): TvText;
begin
Result := AddText(AX, AY, 0, '', 10, AStr, AOnlyCreate);
end;
function TvVectorialPage.AddText(AX, AY, AZ: Double; AStr: utf8string; AOnlyCreate: Boolean = False): TvText;
begin
Result := AddText(AX, AY, AZ, '', 10, AStr, AOnlyCreate);
end;
function TvVectorialPage.AddCircle(ACenterX, ACenterY, ARadius: Double; AOnlyCreate: Boolean = False): TvCircle;
var
lCircle: TvCircle;
begin
lCircle := TvCircle.Create;
lCircle.X := ACenterX;
lCircle.Y := ACenterY;
lCircle.Radius := ARadius;
Result := lCircle;
if not AOnlyCreate then AddEntity(lCircle);
end;
function TvVectorialPage.AddCircularArc(ACenterX, ACenterY, ARadius,
AStartAngle, AEndAngle: Double; AColor: TFPColor; AOnlyCreate: Boolean = False): TvCircularArc;
var
lCircularArc: TvCircularArc;
begin
lCircularArc := TvCircularArc.Create;
lCircularArc.X := ACenterX;
lCircularArc.Y := ACenterY;
lCircularArc.Radius := ARadius;
lCircularArc.StartAngle := AStartAngle;
lCircularArc.EndAngle := AEndAngle;
lCircularArc.Pen.Color := AColor;
Result := lCircularArc;
if not AOnlyCreate then AddEntity(lCircularArc);
end;
function TvVectorialPage.AddEllipse(CenterX, CenterY, HorzHalfAxis,
VertHalfAxis, Angle: Double; AOnlyCreate: Boolean = False): TvEllipse;
var
lEllipse: TvEllipse;
begin
lEllipse := TvEllipse.Create;
lEllipse.X := CenterX;
lEllipse.Y := CenterY;
lEllipse.HorzHalfAxis := HorzHalfAxis;
lEllipse.VertHalfAxis := VertHalfAxis;
lEllipse.Angle := Angle;
Result := lEllipse;
if not AOnlyCreate then AddEntity(lEllipse);
end;
function TvVectorialPage.AddBlock(AName: string; AX, AY, AZ: Double): TvBlock;
var
lBlock: TvBlock;
begin
lBlock := TvBlock.Create;
lBlock.X := AX;
lBlock.Y := AY;
lBlock.Name := AName;
AddEntity(lBlock);
Result := lBlock;
end;
function TvVectorialPage.AddInsert(AX, AY, AZ: Double; AInsertEntity: TvEntity): TvInsert;
var
lInsert: TvInsert;
begin
lInsert := TvInsert.Create;
lInsert.X := AX;
lInsert.Y := AY;
lInsert.InsertEntity := AInsertEntity;
AddEntity(lInsert);
Result := lInsert;
end;
function TvVectorialPage.AddLayer(AName: string): TvLayer;
begin
Result := TvLayer.Create;
Result.Name := AName;
AddEntity(Result);
end;
function TvVectorialPage.AddLayerAndSetAsCurrent(AName: string): TvLayer;
begin
Result := AddLayer(AName);
FCurrentLayer := Result;
end;
procedure TvVectorialPage.ClearLayerSelection;
begin
FCurrentLayer := nil;
end;
function TvVectorialPage.AddAlignedDimension(BaseLeft, BaseRight, DimLeft,
DimRight: T3DPoint; AOnlyCreate: Boolean = False): TvAlignedDimension;
var
lDim: TvAlignedDimension;
begin
lDim := TvAlignedDimension.Create;
lDim.BaseLeft := BaseLeft;
lDim.BaseRight := BaseRight;
lDim.DimensionLeft := DimLeft;
lDim.DimensionRight := DimRight;
Result := lDim;
if not AOnlyCreate then AddEntity(lDim);
end;
function TvVectorialPage.AddRadialDimension(AIsDiameter: Boolean; ACenter,
ADimLeft, ADimRight: T3DPoint; AOnlyCreate: Boolean = False): TvRadialDimension;
var
lDim: TvRadialDimension;
begin
lDim := TvRadialDimension.Create;
lDim.IsDiameter := AIsDiameter;
lDim.Center := ACenter;
lDim.DimensionLeft := ADimLeft;
lDim.DimensionRight := ADimRight;
Result := lDim;
if not AOnlyCreate then AddEntity(lDim);
end;
function TvVectorialPage.AddPoint(AX, AY, AZ: Double): TvPoint;
var
lPoint: TvPoint;
begin
lPoint := TvPoint.Create;
lPoint.X := AX;
lPoint.Y := AY;
lPoint.Z := AZ;
AddEntity(lPoint);
Result := lPoint;
end;
procedure TvVectorialPage.PositionEntitySubparts(ADest: TFPCustomCanvas; ABaseX,
ABaseY: Double);
var
i: Integer;
begin
for i := 0 to GetEntitiesCount()-1 do
GetEntity(0).PositionSubparts(ADest, ABaseX, ABaseY);
end;
procedure TvVectorialPage.DrawBackground(ADest: TFPCustomCanvas);
begin
ADest.Pen.Style := psClear;
ADest.Brush.Style := bsSolid;
ADest.Brush.FPColor := BackgroundColor;
ADest.FillRect(0, 0, ADest.Width, ADest.Height);
ADest.Pen.Style := psSolid;
end;
{@@
This function draws a FPVectorial vectorial page to a TFPCustomCanvas
descendent, such as TCanvas from the LCL.
Be careful that by default this routine does not execute coordinate transformations,
and that FPVectorial works with a start point in the bottom-left corner, with
the X growing to the right and the Y growing to the top. This will result in
an image in TFPCustomCanvas mirrored in the Y axis in relation with the document
as seen in a PDF viewer, for example. This can be easily changed with the
provided parameters. To have the standard view of an image viewer one could
use this function like this:
ASource.Render(ADest, 0, ASource.Height, 1.0, -1.0);
}
procedure TvVectorialPage.Render(ADest: TFPCustomCanvas;
ADestX: Integer; ADestY: Integer; AMulX: Double; AMulY: Double);
var
i: Integer;
CurEntity: TvEntity;
begin
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
WriteLn(':>DrawFPVectorialToCanvas');
{$endif}
for i := 0 to GetEntitiesCount - 1 do
begin
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
Write(Format('[Path] ID=%d', [i]));
{$endif}
CurEntity := GetEntity(i);
RenderInfo.BackgroundColor := BackgroundColor;
CurEntity.Render(ADest, RenderInfo, ADestX, ADestY, AMulX, AMulY);
end;
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
WriteLn(':<DrawFPVectorialToCanvas');
{$endif}
end;
procedure TvVectorialPage.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc;
APageItem: Pointer);
var
lCurEntity: TvEntity;
i: Integer;
begin
for i := 0 to FEntities.Count - 1 do
begin
lCurEntity := TvEntity(FEntities.Items[i]);
lCurEntity.GenerateDebugTree(ADestRoutine, APageItem);
end;
end;
{ TsWorksheet }
{@@
Constructor.
}
constructor TvVectorialDocument.Create;
begin
inherited Create;
FPages := TFPList.Create;
FCurrentPageIndex := -1;
end;
{@@
Destructor.
}
destructor TvVectorialDocument.Destroy;
begin
Clear;
FPages.Free;
FPages := nil;
inherited Destroy;
end;
procedure TvVectorialDocument.Assign(ASource: TvVectorialDocument);
//var
// i: Integer;
begin
// Clear;
//
// for i := 0 to ASource.GetEntitiesCount - 1 do
// Self.AddEntity(ASource.GetEntity(i));
end;
procedure TvVectorialDocument.AssignTo(ADest: TvVectorialDocument);
begin
ADest.Assign(Self);
end;
{@@
Convenience method which creates the correct
writer object for a given vector graphics document format.
}
function TvVectorialDocument.CreateVectorialWriter(AFormat: TvVectorialFormat): TvCustomVectorialWriter;
var
i: Integer;
begin
Result := nil;
for i := 0 to Length(GvVectorialFormats) - 1 do
if GvVectorialFormats[i].Format = AFormat then
begin
if GvVectorialFormats[i].WriterClass <> nil then
Result := GvVectorialFormats[i].WriterClass.Create;
Break;
end;
if Result = nil then raise Exception.Create('Unsupported vector graphics format.');
end;
{@@
Convenience method which creates the correct
reader object for a given vector graphics document format.
}
function TvVectorialDocument.CreateVectorialReader(AFormat: TvVectorialFormat): TvCustomVectorialReader;
var
i: Integer;
begin
Result := nil;
for i := 0 to Length(GvVectorialFormats) - 1 do
if GvVectorialFormats[i].Format = AFormat then
begin
if GvVectorialFormats[i].ReaderClass <> nil then
Result := GvVectorialFormats[i].ReaderClass.Create;
Break;
end;
if Result = nil then raise Exception.Create('Unsupported vector graphics format.');
end;
{@@
Writes the document to a file.
If the file doesn't exist, it will be created.
}
procedure TvVectorialDocument.WriteToFile(AFileName: string; AFormat: TvVectorialFormat);
var
AWriter: TvCustomVectorialWriter;
begin
AWriter := CreateVectorialWriter(AFormat);
try
AWriter.WriteToFile(AFileName, Self);
finally
AWriter.Free;
end;
end;
procedure TvVectorialDocument.WriteToFile(AFileName: string);
var
lFormat: TvVectorialFormat;
begin
lFormat := GetFormatFromExtension(ExtractFileExt(AFileName));
WriteToFile(AFileName, lFormat);
end;
{@@
Writes the document to a stream
}
procedure TvVectorialDocument.WriteToStream(AStream: TStream; AFormat: TvVectorialFormat);
var
AWriter: TvCustomVectorialWriter;
begin
AWriter := CreateVectorialWriter(AFormat);
try
AWriter.WriteToStream(AStream, Self);
finally
AWriter.Free;
end;
end;
procedure TvVectorialDocument.WriteToStrings(AStrings: TStrings;
AFormat: TvVectorialFormat);
var
AWriter: TvCustomVectorialWriter;
begin
AWriter := CreateVectorialWriter(AFormat);
try
AWriter.WriteToStrings(AStrings, Self);
finally
AWriter.Free;
end;
end;
{@@
Reads the document from a file.
Any current contents in this object will be removed.
}
procedure TvVectorialDocument.ReadFromFile(AFileName: string;
AFormat: TvVectorialFormat);
var
AReader: TvCustomVectorialReader;
begin
Self.Clear;
AReader := CreateVectorialReader(AFormat);
try
AReader.ReadFromFile(AFileName, Self);
finally
AReader.Free;
end;
end;
{@@
Reads the document from a file. A variant that auto-detects the format from the extension and other factors.
}
procedure TvVectorialDocument.ReadFromFile(AFileName: string);
var
lFormat: TvVectorialFormat;
begin
lFormat := GetFormatFromExtension(ExtractFileExt(AFileName));
ReadFromFile(AFileName, lFormat);
end;
{@@
Reads the document from a stream.
Any current contents in this object will be removed.
}
procedure TvVectorialDocument.ReadFromStream(AStream: TStream;
AFormat: TvVectorialFormat);
var
AReader: TvCustomVectorialReader;
begin
Self.Clear;
AReader := CreateVectorialReader(AFormat);
try
AReader.ReadFromStream(AStream, Self);
finally
AReader.Free;
end;
end;
procedure TvVectorialDocument.ReadFromStrings(AStrings: TStrings;
AFormat: TvVectorialFormat);
var
AReader: TvCustomVectorialReader;
begin
Self.Clear;
AReader := CreateVectorialReader(AFormat);
try
AReader.ReadFromStrings(AStrings, Self);
finally
AReader.Free;
end;
end;
class function TvVectorialDocument.GetFormatFromExtension(AFileName: string
): TvVectorialFormat;
var
lExt: string;
begin
lExt := ExtractFileExt(AFileName);
if AnsiCompareText(lExt, STR_PDF_EXTENSION) = 0 then Result := vfPDF
else if AnsiCompareText(lExt, STR_POSTSCRIPT_EXTENSION) = 0 then Result := vfPostScript
else if AnsiCompareText(lExt, STR_SVG_EXTENSION) = 0 then Result := vfSVG
else if AnsiCompareText(lExt, STR_CORELDRAW_EXTENSION) = 0 then Result := vfCorelDrawCDR
else if AnsiCompareText(lExt, STR_WINMETAFILE_EXTENSION) = 0 then Result := vfWindowsMetafileWMF
else if AnsiCompareText(lExt, STR_AUTOCAD_EXCHANGE_EXTENSION) = 0 then Result := vfDXF
else if AnsiCompareText(lExt, STR_ENCAPSULATEDPOSTSCRIPT_EXTENSION) = 0 then Result := vfEncapsulatedPostScript
else if AnsiCompareText(lExt, STR_LAS_EXTENSION) = 0 then Result := vfLAS
else if AnsiCompareText(lExt, STR_LAZ_EXTENSION) = 0 then Result := vfLAZ
else if AnsiCompareText(lExt, STR_RAW_EXTENSION) = 0 then Result := vfRAW
else if AnsiCompareText(lExt, STR_MATHML_EXTENSION) = 0 then Result := vfMathML
else
raise Exception.Create('TvVectorialDocument.GetFormatFromExtension: The extension (' + lExt + ') doesn''t match any supported formats.');
end;
function TvVectorialDocument.GetDetailedFileFormat(): string;
begin
end;
procedure TvVectorialDocument.GuessDocumentSize();
var
i, j: Integer;
lEntity: TvEntity;
lLeft, lTop, lRight, lBottom: Double;
CurPage: TvVectorialPage;
begin
lLeft := 0;
lTop := 0;
lRight := 0;
lBottom := 0;
for j := 0 to GetPageCount()-1 do
begin
CurPage := GetPage(j);
for i := 0 to CurPage.GetEntitiesCount() - 1 do
begin
lEntity := CurPage.GetEntity(I);
lEntity.ExpandBoundingBox(nil, lLeft, lTop, lRight, lBottom);
end;
end;
Width := lRight - lLeft;
Height := lBottom - lTop;
end;
procedure TvVectorialDocument.GuessGoodZoomLevel(AScreenSize: Integer);
begin
ZoomLevel := AScreenSize / Height;
end;
function TvVectorialDocument.GetPage(AIndex: Integer): TvVectorialPage;
begin
Result := TvVectorialPage(FPages.Items[AIndex]);
end;
function TvVectorialDocument.GetPageCount: Integer;
begin
Result := FPages.Count;
end;
function TvVectorialDocument.GetCurrentPage: TvVectorialPage;
begin
if FCurrentPageIndex >= 0 then
Result := GetPage(FCurrentPageIndex)
else
Result := nil;
end;
procedure TvVectorialDocument.SetCurrentPage(AIndex: Integer);
begin
FCurrentPageIndex := AIndex;
end;
function TvVectorialDocument.AddPage: TvVectorialPage;
begin
Result := TvVectorialPage.Create(Self);
FPages.Add(Result);
if FCurrentPageIndex < 0 then FCurrentPageIndex := FPages.Count-1;
end;
{@@
Clears all data in the document
}
// GM: Release memory for each page
procedure TvVectorialDocument.Clear;
var
i: integer;
p: TvVectorialPage;
begin
for i:=FPages.Count-1 downto 0 do
begin
p := TvVectorialPage(FPages[i]);
p.Clear;
FreeAndNil(p);
end;
FPages.Clear;
FCurrentPageIndex:=-1;
end;
procedure TvVectorialDocument.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc);
var
i: integer;
p: TvVectorialPage;
lPageItem: Pointer;
begin
for i:=0 to FPages.Count-1 do
begin
p := TvVectorialPage(FPages[i]);
lPageItem := ADestRoutine(Format('Page %d', [i]), nil);
p.GenerateDebugTree(ADestRoutine, lPageItem);
end;
end;
{ TvCustomVectorialReader }
constructor TvCustomVectorialReader.Create;
begin
inherited Create;
end;
procedure TvCustomVectorialReader.ReadFromFile(AFileName: string; AData: TvVectorialDocument);
var
FileStream: TFileStream;
begin
FileStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
try
ReadFromStream(FileStream, AData);
finally
FileStream.Free;
end;
end;
procedure TvCustomVectorialReader.ReadFromStream(AStream: TStream;
AData: TvVectorialDocument);
var
AStringStream: TStringStream;
AStrings: TStringList;
begin
AStringStream := TStringStream.Create('');
AStrings := TStringList.Create;
try
AStringStream.CopyFrom(AStream, AStream.Size);
AStringStream.Seek(0, soFromBeginning);
AStrings.Text := AStringStream.DataString;
ReadFromStrings(AStrings, AData);
finally
AStringStream.Free;
AStrings.Free;
end;
end;
procedure TvCustomVectorialReader.ReadFromStrings(AStrings: TStrings;
AData: TvVectorialDocument);
var
AStringStream: TStringStream;
begin
AStringStream := TStringStream.Create('');
try
AStringStream.WriteString(AStrings.Text);
AStringStream.Seek(0, soFromBeginning);
ReadFromStream(AStringStream, AData);
finally
AStringStream.Free;
end;
end;
{ TsCustomSpreadWriter }
constructor TvCustomVectorialWriter.Create;
begin
inherited Create;
end;
{@@
Default file writting method.
Opens the file and calls WriteToStream
@param AFileName The output file name.
If the file already exists it will be replaced.
@param AData The Workbook to be saved.
@see TsWorkbook
}
procedure TvCustomVectorialWriter.WriteToFile(AFileName: string; AData: TvVectorialDocument);
var
OutputFile: TFileStream;
begin
OutputFile := TFileStream.Create(AFileName, fmCreate or fmOpenWrite);
try
WriteToStream(OutputFile, AData);
finally
OutputFile.Free;
end;
end;
{@@
The default stream writer just uses WriteToStrings
}
procedure TvCustomVectorialWriter.WriteToStream(AStream: TStream;
AData: TvVectorialDocument);
var
lStringList: TStringList;
begin
lStringList := TStringList.Create;
try
WriteToStrings(lStringList, AData);
lStringList.SaveToStream(AStream);
finally
lStringList.Free;
end;
end;
procedure TvCustomVectorialWriter.WriteToStrings(AStrings: TStrings;
AData: TvVectorialDocument);
begin
end;
finalization
SetLength(GvVectorialFormats, 0);
end.