4
Vector graphics document
6
License: The same modified LGPL as the Free Pascal RTL
7
See the file COPYING.modifiedLGPL for more details
9
AUTHORS: Felipe Monteiro de Carvalho
10
Pedro Sol Pegorini L de Lima
18
{$define USE_LCL_CANVAS}
23
Classes, SysUtils, Math,
27
{$ifdef USE_LCL_CANVAS}
28
, Graphics, LCLIntf, LCLType
34
{ Multi-purpose document formats }
35
vfPDF, vfSVG, vfCorelDrawCDR, vfWindowsMetafileWMF,
38
{ Geospatial formats }
41
vfPostScript, vfEncapsulatedPostScript,
43
vfGCodeAvisoCNCPrototipoV5, vfGCodeAvisoCNCPrototipoV6,
49
{ Default extensions }
50
{ Multi-purpose document formats }
51
STR_PDF_EXTENSION = '.pdf';
52
STR_POSTSCRIPT_EXTENSION = '.ps';
53
STR_SVG_EXTENSION = '.svg';
54
STR_CORELDRAW_EXTENSION = '.cdr';
55
STR_WINMETAFILE_EXTENSION = '.wmf';
56
STR_AUTOCAD_EXCHANGE_EXTENSION = '.dxf';
57
STR_ENCAPSULATEDPOSTSCRIPT_EXTENSION = '.eps';
58
STR_LAS_EXTENSION = '.las';
59
STR_RAW_EXTENSION = '.raw';
62
TvCustomVectorialWriter = class;
63
TvCustomVectorialReader = class;
64
TvVectorialPage = class;
66
{ Pen, Brush and Font }
84
Font orientation is measured in degrees and uses the
85
same direction as the LCL TFont.orientation, which is counter-clockwise.
86
Zero is the normal, horizontal, orientation, directed to the right.
91
{ Coordinates and polyline segments }
100
st2DLine, st2DLineWithPen, st2DBezier,
101
st3DLine, st3DBezier, stMoveTo);
104
The coordinates in fpvectorial are given in millimiters and
105
the starting point is in the bottom-left corner of the document.
106
The X grows to the right and the Y grows to the top.
112
SegmentType: TSegmentType;
113
// Fields for linking the list
114
Previous: TPathSegment;
119
In a 2D segment, the X and Y coordinates represent usually the
120
final point of the segment, being that it starts where the previous
121
segment ends. The exception is for the first segment of all, which simply
122
holds the starting point for the drawing and should always be of the type
125
T2DSegment = class(TPathSegment)
130
T2DSegmentWithPen = class(T2DSegment)
136
In Bezier segments, we remain using the X and Y coordinates for the ending point.
137
The starting point is where the previous segment ended, so that the intermediary
138
bezier control points are [X2, Y2] and [X3, Y3].
140
T2DBezierSegment = class(T2DSegment)
146
T3DSegment = class(TPathSegment)
149
Coordinates of the end of the segment.
150
For the first segment, this is the starting point.
155
T3DBezierSegment = class(T3DSegment)
161
TvFindEntityResult = (vfrNotFound, vfrFound, vfrSubpartFound);
166
All elements should derive from TvEntity, regardless of whatever properties
175
constructor Create; virtual;
176
procedure CalculateBoundingBox(var ALeft, ATop, ARight, ABottom: Double); virtual;
177
procedure ExpandBoundingBox(var ALeft, ATop, ARight, ABottom: Double);
178
{@@ ASubpart is only valid if this routine returns vfrSubpartFound }
179
function TryToSelect(APos: TPoint; var ASubpart: Cardinal): TvFindEntityResult; virtual;
180
procedure Move(ADeltaX, ADeltaY: Integer); virtual;
181
procedure MoveSubpart(ADeltaX, ADeltaY: Integer; ASubpart: Cardinal); virtual;
182
procedure Render(ADest: TFPCustomCanvas; ADestX: Integer = 0;
183
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); virtual;
184
function GetNormalizedPos(APage: TvVectorialPage; ANewMin, ANewMax: Double): T3DPoint;
189
TvEntityWithPen = class(TvEntity)
191
{@@ The global Pen for the entire entity. In the case of paths, individual
192
elements might be able to override this setting. }
194
constructor Create; override;
197
{ TvEntityWithPenAndBrush }
199
TvEntityWithPenAndBrush = class(TvEntityWithPen)
201
{@@ The global Brush for the entire entity. In the case of paths, individual
202
elements might be able to override this setting. }
204
constructor Create; override;
207
TvClipMode = (vcmNonzeroWindingRule, vcmEvenOddRule);
209
TPath = class(TvEntityWithPenAndBrush)
212
Points: TPathSegment; // Beginning of the double-linked list
213
PointsEnd: TPathSegment;// End of the double-linked list
214
CurPoint: TPathSegment; // Used in PrepareForSequentialReading and Next
216
ClipMode: TvClipMode;
217
procedure Assign(ASource: TPath);
218
procedure PrepareForSequentialReading;
219
function Next(): TPathSegment;
220
procedure CalculateBoundingBox(var ALeft, ATop, ARight, ABottom: Double); override;
221
procedure AppendSegment(ASegment: TPathSegment);
225
TvText represents a text entity.
230
TvText = class(TvEntityWithPenAndBrush)
234
constructor Create; override;
235
destructor Destroy; override;
236
function TryToSelect(APos: TPoint; var ASubpart: Cardinal): TvFindEntityResult; override;
241
TvCircle = class(TvEntityWithPenAndBrush)
248
TvCircularArc = class(TvEntityWithPenAndBrush)
251
{@@ The Angle is measured in degrees in relation to the positive X axis }
252
StartAngle, EndAngle: Double;
260
TvEllipse = class(TvEntityWithPenAndBrush)
263
HorzHalfAxis: Double; // This half-axis is the horizontal one when Angle=0
264
VertHalfAxis: Double; // This half-axis is the vertical one when Angle=0
265
{@@ The Angle is measured in degrees in relation to the positive X axis }
267
procedure CalculateBoundingBox(var ALeft, ATop, ARight, ABottom: Double); override;
268
procedure Render(ADest: TFPCustomCanvas; ADestX: Integer = 0;
269
ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); override;
273
The brush has no effect in this class
275
DimensionLeft ---text--- DimensionRight
282
{ TvAlignedDimension }
284
TvAlignedDimension = class(TvEntityWithPenAndBrush)
287
BaseLeft, BaseRight, DimensionLeft, DimensionRight: T3DPoint;
291
Vectorial images can contain raster images inside them and this entity
294
If the Width and Height differ from the same data in the image, then
295
the raster image will be stretched.
297
Note that TFPCustomImage does not implement a storage, so the property
298
RasterImage should be filled with either a FPImage.TFPMemoryImage or with
299
a TLazIntfImage. The property RasterImage might be nil.
304
TvRasterImage = class(TvEntity)
306
RasterImage: TFPCustomImage;
307
Top, Left, Width, Height: Double;
308
procedure InitializeWithConvertionOf3DPointsToHeightMap(APage: TvVectorialPage; AWidth, AHeight: Integer);
313
TvPoint = class(TvEntityWithPen)
317
TvProgressEvent = procedure (APercentage: Byte) of object;
319
{ TvVectorialDocument }
321
TvVectorialDocument = class
323
FOnProgress: TvProgressEvent;
325
FCurrentPageIndex: Integer;
326
function CreateVectorialWriter(AFormat: TvVectorialFormat): TvCustomVectorialWriter;
327
function CreateVectorialReader(AFormat: TvVectorialFormat): TvCustomVectorialReader;
329
Width, Height: Double; // in millimeters
331
// User-Interface information
332
ZoomLevel: Double; // 1 = 100%
334
SelectedvElement: TvEntity;
336
constructor Create; virtual;
337
destructor Destroy; override;
338
procedure Assign(ASource: TvVectorialDocument);
339
procedure AssignTo(ADest: TvVectorialDocument);
340
procedure WriteToFile(AFileName: string; AFormat: TvVectorialFormat); overload;
341
procedure WriteToFile(AFileName: string); overload;
342
procedure WriteToStream(AStream: TStream; AFormat: TvVectorialFormat);
343
procedure WriteToStrings(AStrings: TStrings; AFormat: TvVectorialFormat);
344
procedure ReadFromFile(AFileName: string; AFormat: TvVectorialFormat); overload;
345
procedure ReadFromFile(AFileName: string); overload;
346
procedure ReadFromStream(AStream: TStream; AFormat: TvVectorialFormat);
347
procedure ReadFromStrings(AStrings: TStrings; AFormat: TvVectorialFormat);
348
class function GetFormatFromExtension(AFileName: string): TvVectorialFormat;
349
function GetDetailedFileFormat(): string;
350
procedure GuessDocumentSize();
351
procedure GuessGoodZoomLevel(AScreenSize: Integer = 500);
353
function GetPage(AIndex: Integer): TvVectorialPage;
354
function GetPageCount: Integer;
355
function GetCurrentPage: TvVectorialPage;
356
procedure SetCurrentPage(AIndex: Integer);
357
function AddPage(): TvVectorialPage;
358
{ Data removing methods }
359
procedure Clear; virtual;
361
property OnProgress: TvProgressEvent read FOnProgress write FOnprogress;
366
TvVectorialPage = class
368
FEntities: TFPList; // of TvEntity
371
//procedure RemoveCallback(data, arg: pointer);
372
procedure ClearTmpPath();
373
procedure AppendSegmentToTmpPath(ASegment: TPathSegment);
374
procedure CallbackDeleteEntity(data,arg:pointer);
376
// Document size for page-based documents
377
Width, Height: Double; // in millimeters
378
// Document size for other documents
379
MinX, MinY, MinZ, MaxX, MaxY, MaxZ: Double;
380
Owner: TvVectorialDocument;
382
constructor Create(AOwner: TvVectorialDocument); virtual;
383
destructor Destroy; override;
384
procedure Assign(ASource: TvVectorialPage);
385
{ Data reading methods }
386
function GetEntity(ANum: Cardinal): TvEntity;
387
function GetEntitiesCount: Integer;
388
function FindAndSelectEntity(Pos: TPoint): TvFindEntityResult;
389
{ Data removing methods }
390
procedure Clear; virtual;
391
function DeleteEntity(AIndex: Cardinal): Boolean;
392
function RemoveEntity(AEntity: TvEntity; AFreeAfterRemove: Boolean = True): Boolean;
393
{ Data writing methods }
394
function AddEntity(AEntity: TvEntity): Integer;
395
procedure AddPathCopyMem(APath: TPath);
396
procedure StartPath(AX, AY: Double); overload;
397
procedure StartPath(); overload;
398
procedure AddMoveToPath(AX, AY: Double);
399
procedure AddLineToPath(AX, AY: Double); overload;
400
procedure AddLineToPath(AX, AY: Double; AColor: TFPColor); overload;
401
procedure AddLineToPath(AX, AY, AZ: Double); overload;
402
procedure GetCurrentPathPenPos(var AX, AY: Double);
403
procedure AddBezierToPath(AX1, AY1, AX2, AY2, AX3, AY3: Double); overload;
404
procedure AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, AX3, AY3, AZ3: Double); overload;
405
procedure SetBrushColor(AColor: TFPColor);
406
procedure SetBrushStyle(AStyle: TFPBrushStyle);
407
procedure SetPenColor(AColor: TFPColor);
408
procedure SetPenStyle(AStyle: TFPPenStyle);
409
procedure SetPenWidth(AWidth: Integer);
410
procedure SetClipPath(AClipPath: TPath; AClipMode: TvClipMode);
412
procedure AddText(AX, AY, AZ: Double; FontName: string; FontSize: integer; AText: utf8string); overload;
413
procedure AddText(AX, AY: Double; AStr: utf8string); overload;
414
procedure AddText(AX, AY, AZ: Double; AStr: utf8string); overload;
415
procedure AddCircle(ACenterX, ACenterY, ARadius: Double);
416
procedure AddCircularArc(ACenterX, ACenterY, ARadius, AStartAngle, AEndAngle: Double; AColor: TFPColor);
417
procedure AddEllipse(CenterX, CenterY, HorzHalfAxis, VertHalfAxis, Angle: Double);
419
procedure AddAlignedDimension(BaseLeft, BaseRight, DimLeft, DimRight: T3DPoint);
421
function AddPoint(AX, AY, AZ: Double): TvPoint;
424
{@@ TvVectorialReader class reference type }
426
TvVectorialReaderClass = class of TvCustomVectorialReader;
428
{ TvCustomVectorialReader }
430
TvCustomVectorialReader = class
432
{ General reading methods }
433
constructor Create; virtual;
434
procedure ReadFromFile(AFileName: string; AData: TvVectorialDocument); virtual;
435
procedure ReadFromStream(AStream: TStream; AData: TvVectorialDocument); virtual;
436
procedure ReadFromStrings(AStrings: TStrings; AData: TvVectorialDocument); virtual;
439
{@@ TvVectorialWriter class reference type }
441
TvVectorialWriterClass = class of TvCustomVectorialWriter;
443
{@@ TvCustomVectorialWriter }
445
{ TvCustomVectorialWriter }
447
TvCustomVectorialWriter = class
449
{ General writing methods }
450
constructor Create; virtual;
451
procedure WriteToFile(AFileName: string; AData: TvVectorialDocument); virtual;
452
procedure WriteToStream(AStream: TStream; AData: TvVectorialDocument); virtual;
453
procedure WriteToStrings(AStrings: TStrings; AData: TvVectorialDocument); virtual;
456
{@@ List of registered formats }
458
TvVectorialFormatData = record
459
ReaderClass: TvVectorialReaderClass;
460
WriterClass: TvVectorialWriterClass;
461
ReaderRegistered: Boolean;
462
WriterRegistered: Boolean;
463
Format: TvVectorialFormat;
467
GvVectorialFormats: array of TvVectorialFormatData;
469
procedure RegisterVectorialReader(
470
AReaderClass: TvVectorialReaderClass;
471
AFormat: TvVectorialFormat);
472
procedure RegisterVectorialWriter(
473
AWriterClass: TvVectorialWriterClass;
474
AFormat: TvVectorialFormat);
475
function Make2DPoint(AX, AY: Double): T3DPoint;
482
Str_Error_Nil_Path = ' The program attempted to add a segment before creating a path';
485
Registers a new reader for a format
487
procedure RegisterVectorialReader(
488
AReaderClass: TvVectorialReaderClass;
489
AFormat: TvVectorialFormat);
492
FormatInTheList: Boolean;
494
len := Length(GvVectorialFormats);
495
FormatInTheList := False;
497
{ First search for the format in the list }
498
for i := 0 to len - 1 do
500
if GvVectorialFormats[i].Format = AFormat then
502
if GvVectorialFormats[i].ReaderRegistered then
503
raise Exception.Create('RegisterVectorialReader: Reader class for format ' {+ AFormat} + ' already registered.');
505
GvVectorialFormats[i].ReaderRegistered := True;
506
GvVectorialFormats[i].ReaderClass := AReaderClass;
508
FormatInTheList := True;
513
{ If not already in the list, then add it }
514
if not FormatInTheList then
516
SetLength(GvVectorialFormats, len + 1);
518
GvVectorialFormats[len].ReaderClass := AReaderClass;
519
GvVectorialFormats[len].WriterClass := nil;
520
GvVectorialFormats[len].ReaderRegistered := True;
521
GvVectorialFormats[len].WriterRegistered := False;
522
GvVectorialFormats[len].Format := AFormat;
527
Registers a new writer for a format
529
procedure RegisterVectorialWriter(
530
AWriterClass: TvVectorialWriterClass;
531
AFormat: TvVectorialFormat);
534
FormatInTheList: Boolean;
536
len := Length(GvVectorialFormats);
537
FormatInTheList := False;
539
{ First search for the format in the list }
540
for i := 0 to len - 1 do
542
if GvVectorialFormats[i].Format = AFormat then
544
if GvVectorialFormats[i].WriterRegistered then
545
raise Exception.Create('RegisterVectorialWriter: Writer class for format ' + {AFormat +} ' already registered.');
547
GvVectorialFormats[i].WriterRegistered := True;
548
GvVectorialFormats[i].WriterClass := AWriterClass;
550
FormatInTheList := True;
555
{ If not already in the list, then add it }
556
if not FormatInTheList then
558
SetLength(GvVectorialFormats, len + 1);
560
GvVectorialFormats[len].ReaderClass := nil;
561
GvVectorialFormats[len].WriterClass := AWriterClass;
562
GvVectorialFormats[len].ReaderRegistered := False;
563
GvVectorialFormats[len].WriterRegistered := True;
564
GvVectorialFormats[len].Format := AFormat;
568
function Make2DPoint(AX, AY: Double): T3DPoint;
577
procedure TvRasterImage.InitializeWithConvertionOf3DPointsToHeightMap(APage: TvVectorialPage; AWidth, AHeight: Integer);
584
PreviousCount: Integer;
586
// First setup the map and initialize it
587
if RasterImage <> nil then RasterImage.Free;
588
RasterImage := TFPMemoryImage.create(AWidth, AHeight);
590
// Now go through all points and attempt to fit them to our grid
591
for i := 0 to APage.GetEntitiesCount - 1 do
593
lEntity := APage.GetEntity(i);
594
if lEntity is TvPoint then
596
lPos.X := Round((lEntity.X - APage.MinX) * AWidth / (APage.MaxX - APage.MinX));
597
lPos.Y := Round((lEntity.Y - APage.MinY) * AHeight / (APage.MaxY - APage.MinY));
599
if lPos.X >= AWidth then lPos.X := AWidth-1;
600
if lPos.Y >= AHeight then lPos.Y := AHeight-1;
601
if lPos.X < 0 then lPos.X := 0;
602
if lPos.Y < 0 then lPos.Y := 0;
604
// Calculate the height of this point
605
PreviousValue := lValue.Red;
606
lValue.Red := Round((lEntity.Z - APage.MinZ) * $FFFF / (APage.MaxZ - APage.MinZ));
608
// And apply it as a fraction of the total number of points which fall in this square
609
// we store the number of points in the Alpha channel
610
PreviousCount := lValue.Alpha div $100;
611
lValue.Red := Round((PreviousCount * PreviousValue + lValue.Red) / (PreviousCount + 1));
613
lValue.Green := lValue.Red;
614
lValue.Blue := lValue.Red;
615
lValue.Alpha := lValue.Alpha + $100;
617
RasterImage.Colors[lPos.X, lPos.Y] := lValue;
622
constructor TvEntityWithPen.Create;
625
Pen.Style := psSolid;
626
Pen.Color := colBlack;
629
{ TvEntityWithPenAndBrush }
631
constructor TvEntityWithPenAndBrush.Create;
634
Brush.Style := bsClear;
635
Brush.Color := colBlue;
640
procedure TvVectorialPage.ClearTmpPath;
642
segment, oldsegment: TPathSegment;
644
FTmpPath.Points := nil;
645
FTmpPath.PointsEnd := nil;
647
FTmpPath.Brush.Color := colBlue;
648
FTmpPath.Brush.Style := bsClear;
649
FTmpPath.Pen.Color := colBlack;
650
FTmpPath.Pen.Style := psSolid;
651
FTmpPath.Pen.Width := 1;
654
procedure TvVectorialPage.AppendSegmentToTmpPath(ASegment: TPathSegment);
656
FTmpPath.AppendSegment(ASegment);
659
procedure TvVectorialPage.CallbackDeleteEntity(data, arg: pointer);
661
if (data <> nil) then
665
constructor TvVectorialPage.Create(AOwner: TvVectorialDocument);
669
FEntities := TFPList.Create;
670
FTmpPath := TPath.Create;
674
destructor TvVectorialPage.Destroy;
683
procedure TvVectorialPage.Assign(ASource: TvVectorialPage);
689
for i := 0 to ASource.GetEntitiesCount - 1 do
690
Self.AddEntity(ASource.GetEntity(i));
693
function TvVectorialPage.GetEntity(ANum: Cardinal): TvEntity;
695
if ANum >= FEntities.Count then raise Exception.Create('TvVectorialDocument.GetEntity: Entity number out of bounds');
697
Result := TvEntity(FEntities.Items[ANum]);
699
if Result = nil then raise Exception.Create('TvVectorialDocument.GetEntity: Invalid Entity number');
702
function TvVectorialPage.GetEntitiesCount: Integer;
704
Result := FEntities.Count;
707
function TvVectorialPage.FindAndSelectEntity(Pos: TPoint): TvFindEntityResult;
713
Result := vfrNotFound;
715
for i := 0 to GetEntitiesCount() - 1 do
717
lEntity := GetEntity(i);
719
Result := lEntity.TryToSelect(Pos, lSubpart);
721
if Result <> vfrNotFound then
723
Owner.SelectedvElement := lEntity;
729
procedure TvVectorialPage.Clear;
731
FEntities.ForEachCall(CallbackDeleteEntity, nil);
736
Returns if the entity was really deleted or false if there is no entity with this index
738
function TvVectorialPage.DeleteEntity(AIndex: Cardinal): Boolean;
743
if AIndex >= GetEntitiesCount() then Exit;;
744
lEntity := GetEntity(AIndex);
745
if lEntity = nil then Exit;
746
FEntities.Delete(AIndex);
751
function TvVectorialPage.RemoveEntity(AEntity: TvEntity; AFreeAfterRemove: Boolean = True): Boolean;
754
if AEntity = nil then Exit;
755
FEntities.Remove(AEntity);
756
if AFreeAfterRemove then AEntity.Free;
761
Adds an entity to the document and returns it's current index
763
function TvVectorialPage.AddEntity(AEntity: TvEntity): Integer;
765
Result := FEntities.Count;
766
FEntities.Add(Pointer(AEntity));
769
procedure TvVectorialPage.AddPathCopyMem(APath: TPath);
774
lPath := TPath.Create;
777
//WriteLn(':>TvVectorialDocument.AddPath 1 Len = ', Len);
781
Starts writing a Path in multiple steps.
782
Should be followed by zero or more calls to AddPointToPath
783
and by a call to EndPath to effectively add the data.
785
@see EndPath, AddPointToPath
787
procedure TvVectorialPage.StartPath(AX, AY: Double);
794
segment := T2DSegment.Create;
795
segment.SegmentType := stMoveTo;
799
FTmpPath.Points := segment;
800
FTmpPath.PointsEnd := segment;
803
procedure TvVectorialPage.StartPath;
808
procedure TvVectorialPage.AddMoveToPath(AX, AY: Double);
812
segment := T2DSegment.Create;
813
segment.SegmentType := stMoveTo;
817
AppendSegmentToTmpPath(segment);
821
Adds one more point to the end of a Path being
822
writing in multiple steps.
824
Does nothing if not called between StartPath and EndPath.
826
Can be called multiple times to add multiple points.
828
@see StartPath, EndPath
830
procedure TvVectorialPage.AddLineToPath(AX, AY: Double);
834
segment := T2DSegment.Create;
835
segment.SegmentType := st2DLine;
839
AppendSegmentToTmpPath(segment);
842
procedure TvVectorialPage.AddLineToPath(AX, AY: Double; AColor: TFPColor);
844
segment: T2DSegmentWithPen;
846
segment := T2DSegmentWithPen.Create;
847
segment.SegmentType := st2DLineWithPen;
850
segment.Pen.Color := AColor;
852
AppendSegmentToTmpPath(segment);
855
procedure TvVectorialPage.AddLineToPath(AX, AY, AZ: Double);
859
segment := T3DSegment.Create;
860
segment.SegmentType := st3DLine;
865
AppendSegmentToTmpPath(segment);
869
Gets the current Pen Pos in the temporary path
871
procedure TvVectorialPage.GetCurrentPathPenPos(var AX, AY: Double);
873
// Check if we are the first segment in the tmp path
874
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');
876
AX := T2DSegment(FTmpPath.PointsEnd).X;
877
AY := T2DSegment(FTmpPath.PointsEnd).Y;
881
Adds a bezier element to the path. It starts where the previous element ended
882
and it goes throw the control points [AX1, AY1] and [AX2, AY2] and ends
885
procedure TvVectorialPage.AddBezierToPath(AX1, AY1, AX2, AY2, AX3, AY3: Double);
887
segment: T2DBezierSegment;
889
segment := T2DBezierSegment.Create;
890
segment.SegmentType := st2DBezier;
898
AppendSegmentToTmpPath(segment);
901
procedure TvVectorialPage.AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, AX3, AY3, AZ3: Double);
903
segment: T3DBezierSegment;
905
segment := T3DBezierSegment.Create;
906
segment.SegmentType := st3DBezier;
917
AppendSegmentToTmpPath(segment);
920
procedure TvVectorialPage.SetBrushColor(AColor: TFPColor);
922
FTmPPath.Brush.Color := AColor;
925
procedure TvVectorialPage.SetBrushStyle(AStyle: TFPBrushStyle);
927
FTmPPath.Brush.Style := AStyle;
930
procedure TvVectorialPage.SetPenColor(AColor: TFPColor);
932
FTmPPath.Pen.Color := AColor;
935
procedure TvVectorialPage.SetPenStyle(AStyle: TFPPenStyle);
937
FTmPPath.Pen.Style := AStyle;
940
procedure TvVectorialPage.SetPenWidth(AWidth: Integer);
942
FTmPPath.Pen.Width := AWidth;
945
procedure TvVectorialPage.SetClipPath(AClipPath: TPath; AClipMode: TvClipMode);
947
FTmPPath.ClipPath := AClipPath;
948
FTmPPath.ClipMode := AClipMode;
952
Finishes writing a Path, which was created in multiple
953
steps using StartPath and AddPointToPath,
956
Does nothing if there wasn't a previous correspondent call to
959
@see StartPath, AddPointToPath
961
procedure TvVectorialPage.EndPath;
963
if FTmPPath.Len = 0 then Exit;
964
AddPathCopyMem(FTmPPath);
968
procedure TvVectorialPage.AddText(AX, AY, AZ: Double; FontName: string;
969
FontSize: integer; AText: utf8string);
973
lText := TvText.Create;
974
lText.Value.Text := AText;
978
lText.Font.Name := FontName;
979
lText.Font.Size := FontSize;
983
procedure TvVectorialPage.AddText(AX, AY: Double; AStr: utf8string);
985
AddText(AX, AY, 0, '', 10, AStr);
988
procedure TvVectorialPage.AddText(AX, AY, AZ: Double; AStr: utf8string);
990
AddText(AX, AY, AZ, '', 10, AStr);
993
procedure TvVectorialPage.AddCircle(ACenterX, ACenterY, ARadius: Double);
997
lCircle := TvCircle.Create;
998
lCircle.X := ACenterX;
999
lCircle.Y := ACenterY;
1000
lCircle.Radius := ARadius;
1004
procedure TvVectorialPage.AddCircularArc(ACenterX, ACenterY, ARadius,
1005
AStartAngle, AEndAngle: Double; AColor: TFPColor);
1007
lCircularArc: TvCircularArc;
1009
lCircularArc := TvCircularArc.Create;
1010
lCircularArc.X := ACenterX;
1011
lCircularArc.Y := ACenterY;
1012
lCircularArc.Radius := ARadius;
1013
lCircularArc.StartAngle := AStartAngle;
1014
lCircularArc.EndAngle := AEndAngle;
1015
lCircularArc.Pen.Color := AColor;
1016
AddEntity(lCircularArc);
1019
procedure TvVectorialPage.AddEllipse(CenterX, CenterY, HorzHalfAxis,
1020
VertHalfAxis, Angle: Double);
1022
lEllipse: TvEllipse;
1024
lEllipse := TvEllipse.Create;
1025
lEllipse.X := CenterX;
1026
lEllipse.Y := CenterY;
1027
lEllipse.HorzHalfAxis := HorzHalfAxis;
1028
lEllipse.VertHalfAxis := VertHalfAxis;
1029
lEllipse.Angle := Angle;
1030
AddEntity(lEllipse);
1034
procedure TvVectorialPage.AddAlignedDimension(BaseLeft, BaseRight, DimLeft,
1035
DimRight: T3DPoint);
1037
lDim: TvAlignedDimension;
1039
lDim := TvAlignedDimension.Create;
1040
lDim.BaseLeft := BaseLeft;
1041
lDim.BaseRight := BaseRight;
1042
lDim.DimensionLeft := DimLeft;
1043
lDim.DimensionRight := DimRight;
1047
function TvVectorialPage.AddPoint(AX, AY, AZ: Double): TvPoint;
1051
lPoint := TvPoint.Create;
1061
constructor TvText.Create;
1064
Value := TStringList.Create;
1067
destructor TvText.Destroy;
1073
function TvText.TryToSelect(APos: TPoint; var ASubpart: Cardinal): TvFindEntityResult;
1075
lProximityFactor: Integer;
1077
lProximityFactor := 5;
1078
if (APos.X > X - lProximityFactor) and (APos.X < X + lProximityFactor)
1079
and (APos.Y > Y - lProximityFactor) and (APos.Y < Y + lProximityFactor) then
1081
else Result := vfrNotFound;
1086
constructor TvEntity.Create;
1090
procedure TvEntity.CalculateBoundingBox(var ALeft, ATop, ARight, ABottom: Double);
1098
procedure TvEntity.ExpandBoundingBox(var ALeft, ATop, ARight, ABottom: Double);
1100
lLeft, lTop, lRight, lBottom: Double;
1102
CalculateBoundingBox(lLeft, lTop, lRight, lBottom);
1103
if lLeft < ALeft then ALeft := lLeft;
1104
if lTop < ATop then ATop := lTop;
1105
if lRight > ARight then ARight := lRight;
1106
if lBottom > ABottom then ABottom := lBottom;
1109
function TvEntity.TryToSelect(APos: TPoint; var ASubpart: Cardinal): TvFindEntityResult;
1111
Result := vfrNotFound;
1114
procedure TvEntity.Move(ADeltaX, ADeltaY: Integer);
1120
procedure TvEntity.MoveSubpart(ADeltaX, ADeltaY: Integer;
1121
ASubpart: Cardinal);
1126
procedure TvEntity.Render(ADest: TFPCustomCanvas; ADestX: Integer;
1127
ADestY: Integer; AMulX: Double; AMulY: Double);
1132
function TvEntity.GetNormalizedPos(APage: TvVectorialPage; ANewMin,
1133
ANewMax: Double): T3DPoint;
1135
Result.X := (X - APage.MinX) * (ANewMax - ANewMin) / (APage.MaxX - APage.MinX) + ANewMin;
1136
Result.Y := (Y - APage.MinY) * (ANewMax - ANewMin) / (APage.MaxY - APage.MinY) + ANewMin;
1137
Result.Z := (Z - APage.MinZ) * (ANewMax - ANewMin) / (APage.MaxZ - APage.MinZ) + ANewMin;
1142
procedure TvEllipse.CalculateBoundingBox(var ALeft, ATop, ARight, ABottom: Double);
1146
// First do the trivial
1147
ALeft := X - HorzHalfAxis;
1148
ARight := X + HorzHalfAxis;
1149
ATop := Y - VertHalfAxis;
1150
ABottom := Y + VertHalfAxis;
1152
To calculate the bounding rectangle we can do this:
1154
Ellipse equations:You could try using the parametrized equations for an ellipse rotated at an arbitrary angle:
1156
x = CenterX + MajorHalfAxis*cos(t)*cos(Angle) - MinorHalfAxis*sin(t)*sin(Angle)
1157
y = CenterY + MinorHalfAxis*sin(t)*cos(Angle) + MajorHalfAxis*cos(t)*sin(Angle)
1159
You can then differentiate and solve for gradient = 0:
1160
0 = dx/dt = -MajorHalfAxis*sin(t)*cos(Angle) - MinorHalfAxis*cos(t)*sin(Angle)
1162
tan(t) = -MinorHalfAxis*tan(Angle)/MajorHalfAxis
1164
t = cotang(-MinorHalfAxis*tan(Angle)/MajorHalfAxis)
1168
0 = dy/dt = b*cos(t)*cos(phi) - a*sin(t)*sin(phi)
1170
tan(t) = b*cot(phi)/a
1172
if Angle <> 0.0 then
1174
t := cotan(-VertHalfAxis*tan(Angle)/HorzHalfAxis);
1175
tmp := X + HorzHalfAxis*cos(t)*cos(Angle) - VertHalfAxis*sin(t)*sin(Angle);
1176
ARight := Round(tmp);
1180
procedure TvEllipse.Render(ADest: TFPCustomCanvas; ADestX: Integer;
1181
ADestY: Integer; AMulX: Double; AMulY: Double);
1183
function CoordToCanvasX(ACoord: Double): Integer;
1185
Result := Round(ADestX + AmulX * ACoord);
1188
function CoordToCanvasY(ACoord: Double): Integer;
1190
Result := Round(ADestY + AmulY * ACoord);
1194
PointList: array[0..6] of TPoint;
1196
dk, x1, x2, y1, y2: Integer;
1197
fx1, fy1, fx2, fy2: Double;
1198
{$ifdef USE_LCL_CANVAS}
1199
ALCLDest: TCanvas absolute ADest;
1202
CalculateBoundingBox(fx1, fy1, fx2, fy2);
1203
x1 := CoordToCanvasX(fx1);
1204
x2 := CoordToCanvasX(fx2);
1205
y1 := CoordToCanvasY(fy1);
1206
y2 := CoordToCanvasY(fy2);
1208
{$ifdef USE_LCL_CANVAS}
1211
dk := Round(0.654 * Abs(y2-y1));
1213
f.y := Round(Y - 1);
1214
PointList[0] := Rotate2DPoint(Point(x1, f.y), f, Angle) ; // Startpoint
1215
PointList[1] := Rotate2DPoint(Point(x1, f.y - dk), f, Angle);
1216
//Controlpoint of Startpoint first part
1217
PointList[2] := Rotate2DPoint(Point(x2- 1, f.y - dk), f, Angle);
1218
//Controlpoint of secondpoint first part
1219
PointList[3] := Rotate2DPoint(Point(x2 -1 , f.y), f, Angle);
1220
// Firstpoint of secondpart
1221
PointList[4] := Rotate2DPoint(Point(x2-1 , f.y + dk), f, Angle);
1222
// Controllpoint of secondpart firstpoint
1223
PointList[5] := Rotate2DPoint(Point(x1, f.y + dk), f, Angle);
1224
// Conrollpoint of secondpart endpoint
1225
PointList[6] := PointList[0]; // Endpoint of
1226
// Back to the startpoint
1227
ALCLDest.PolyBezier(Pointlist[0]);
1232
ADest.Pen.Style := psSolid;
1233
ADest.Pen.FPColor := colBlack;
1234
ADest.Ellipse(x1, y1, x2, y2);
1243
constructor TvVectorialDocument.Create;
1247
FPages := TFPList.Create;
1248
FCurrentPageIndex := -1;
1254
destructor TvVectorialDocument.Destroy;
1263
procedure TvVectorialDocument.Assign(ASource: TvVectorialDocument);
1269
// for i := 0 to ASource.GetEntitiesCount - 1 do
1270
// Self.AddEntity(ASource.GetEntity(i));
1273
procedure TvVectorialDocument.AssignTo(ADest: TvVectorialDocument);
1279
Convenience method which creates the correct
1280
writer object for a given vector graphics document format.
1282
function TvVectorialDocument.CreateVectorialWriter(AFormat: TvVectorialFormat): TvCustomVectorialWriter;
1288
for i := 0 to Length(GvVectorialFormats) - 1 do
1289
if GvVectorialFormats[i].Format = AFormat then
1291
if GvVectorialFormats[i].WriterClass <> nil then
1292
Result := GvVectorialFormats[i].WriterClass.Create;
1297
if Result = nil then raise Exception.Create('Unsupported vector graphics format.');
1301
Convenience method which creates the correct
1302
reader object for a given vector graphics document format.
1304
function TvVectorialDocument.CreateVectorialReader(AFormat: TvVectorialFormat): TvCustomVectorialReader;
1310
for i := 0 to Length(GvVectorialFormats) - 1 do
1311
if GvVectorialFormats[i].Format = AFormat then
1313
if GvVectorialFormats[i].ReaderClass <> nil then
1314
Result := GvVectorialFormats[i].ReaderClass.Create;
1319
if Result = nil then raise Exception.Create('Unsupported vector graphics format.');
1323
Writes the document to a file.
1325
If the file doesn't exist, it will be created.
1327
procedure TvVectorialDocument.WriteToFile(AFileName: string; AFormat: TvVectorialFormat);
1329
AWriter: TvCustomVectorialWriter;
1331
AWriter := CreateVectorialWriter(AFormat);
1334
AWriter.WriteToFile(AFileName, Self);
1340
procedure TvVectorialDocument.WriteToFile(AFileName: string);
1342
lFormat: TvVectorialFormat;
1344
lFormat := GetFormatFromExtension(ExtractFileExt(AFileName));
1345
WriteToFile(AFileName, lFormat);
1349
Writes the document to a stream
1351
procedure TvVectorialDocument.WriteToStream(AStream: TStream; AFormat: TvVectorialFormat);
1353
AWriter: TvCustomVectorialWriter;
1355
AWriter := CreateVectorialWriter(AFormat);
1358
AWriter.WriteToStream(AStream, Self);
1364
procedure TvVectorialDocument.WriteToStrings(AStrings: TStrings;
1365
AFormat: TvVectorialFormat);
1367
AWriter: TvCustomVectorialWriter;
1369
AWriter := CreateVectorialWriter(AFormat);
1372
AWriter.WriteToStrings(AStrings, Self);
1379
Reads the document from a file.
1381
Any current contents in this object will be removed.
1383
procedure TvVectorialDocument.ReadFromFile(AFileName: string;
1384
AFormat: TvVectorialFormat);
1386
AReader: TvCustomVectorialReader;
1390
AReader := CreateVectorialReader(AFormat);
1392
AReader.ReadFromFile(AFileName, Self);
1399
Reads the document from a file. A variant that auto-detects the format from the extension and other factors.
1401
procedure TvVectorialDocument.ReadFromFile(AFileName: string);
1403
lFormat: TvVectorialFormat;
1405
lFormat := GetFormatFromExtension(ExtractFileExt(AFileName));
1406
ReadFromFile(AFileName, lFormat);
1410
Reads the document from a stream.
1412
Any current contents in this object will be removed.
1414
procedure TvVectorialDocument.ReadFromStream(AStream: TStream;
1415
AFormat: TvVectorialFormat);
1417
AReader: TvCustomVectorialReader;
1421
AReader := CreateVectorialReader(AFormat);
1423
AReader.ReadFromStream(AStream, Self);
1429
procedure TvVectorialDocument.ReadFromStrings(AStrings: TStrings;
1430
AFormat: TvVectorialFormat);
1432
AReader: TvCustomVectorialReader;
1436
AReader := CreateVectorialReader(AFormat);
1438
AReader.ReadFromStrings(AStrings, Self);
1444
class function TvVectorialDocument.GetFormatFromExtension(AFileName: string
1445
): TvVectorialFormat;
1449
lExt := ExtractFileExt(AFileName);
1450
if AnsiCompareText(lExt, STR_PDF_EXTENSION) = 0 then Result := vfPDF
1451
else if AnsiCompareText(lExt, STR_POSTSCRIPT_EXTENSION) = 0 then Result := vfPostScript
1452
else if AnsiCompareText(lExt, STR_SVG_EXTENSION) = 0 then Result := vfSVG
1453
else if AnsiCompareText(lExt, STR_CORELDRAW_EXTENSION) = 0 then Result := vfCorelDrawCDR
1454
else if AnsiCompareText(lExt, STR_WINMETAFILE_EXTENSION) = 0 then Result := vfWindowsMetafileWMF
1455
else if AnsiCompareText(lExt, STR_AUTOCAD_EXCHANGE_EXTENSION) = 0 then Result := vfDXF
1456
else if AnsiCompareText(lExt, STR_ENCAPSULATEDPOSTSCRIPT_EXTENSION) = 0 then Result := vfEncapsulatedPostScript
1457
else if AnsiCompareText(lExt, STR_LAS_EXTENSION) = 0 then Result := vfLAS
1458
else if AnsiCompareText(lExt, STR_RAW_EXTENSION) = 0 then Result := vfRAW
1460
raise Exception.Create('TvVectorialDocument.GetFormatFromExtension: The extension (' + lExt + ') doesn''t match any supported formats.');
1463
function TvVectorialDocument.GetDetailedFileFormat(): string;
1468
procedure TvVectorialDocument.GuessDocumentSize();
1472
lLeft, lTop, lRight, lBottom: Double;
1473
CurPage: TvVectorialPage;
1480
for j := 0 to GetPageCount()-1 do
1482
CurPage := GetPage(j);
1483
for i := 0 to CurPage.GetEntitiesCount() - 1 do
1485
lEntity := CurPage.GetEntity(I);
1486
lEntity.ExpandBoundingBox(lLeft, lTop, lRight, lBottom);
1490
Width := lRight - lLeft;
1491
Height := lBottom - lTop;
1494
procedure TvVectorialDocument.GuessGoodZoomLevel(AScreenSize: Integer);
1496
ZoomLevel := AScreenSize / Height;
1499
function TvVectorialDocument.GetPage(AIndex: Integer): TvVectorialPage;
1501
Result := TvVectorialPage(FPages.Items[AIndex]);
1504
function TvVectorialDocument.GetPageCount: Integer;
1506
Result := FPages.Count;
1509
function TvVectorialDocument.GetCurrentPage: TvVectorialPage;
1511
if FCurrentPageIndex >= 0 then
1512
Result := GetPage(FCurrentPageIndex)
1517
procedure TvVectorialDocument.SetCurrentPage(AIndex: Integer);
1519
FCurrentPageIndex := AIndex;
1522
function TvVectorialDocument.AddPage: TvVectorialPage;
1524
Result := TvVectorialPage.Create(Self);
1526
if FCurrentPageIndex < 0 then FCurrentPageIndex := FPages.Count-1;
1530
Clears all data in the document
1532
procedure TvVectorialDocument.Clear;
1536
{ TvCustomVectorialReader }
1538
constructor TvCustomVectorialReader.Create;
1543
procedure TvCustomVectorialReader.ReadFromFile(AFileName: string; AData: TvVectorialDocument);
1545
FileStream: TFileStream;
1547
FileStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
1549
ReadFromStream(FileStream, AData);
1555
procedure TvCustomVectorialReader.ReadFromStream(AStream: TStream;
1556
AData: TvVectorialDocument);
1558
AStringStream: TStringStream;
1559
AStrings: TStringList;
1561
AStringStream := TStringStream.Create('');
1562
AStrings := TStringList.Create;
1564
AStringStream.CopyFrom(AStream, AStream.Size);
1565
AStringStream.Seek(0, soFromBeginning);
1566
AStrings.Text := AStringStream.DataString;
1567
ReadFromStrings(AStrings, AData);
1574
procedure TvCustomVectorialReader.ReadFromStrings(AStrings: TStrings;
1575
AData: TvVectorialDocument);
1577
AStringStream: TStringStream;
1579
AStringStream := TStringStream.Create('');
1581
AStringStream.WriteString(AStrings.Text);
1582
AStringStream.Seek(0, soFromBeginning);
1583
ReadFromStream(AStringStream, AData);
1589
{ TsCustomSpreadWriter }
1591
constructor TvCustomVectorialWriter.Create;
1597
Default file writting method.
1599
Opens the file and calls WriteToStream
1601
@param AFileName The output file name.
1602
If the file already exists it will be replaced.
1603
@param AData The Workbook to be saved.
1607
procedure TvCustomVectorialWriter.WriteToFile(AFileName: string; AData: TvVectorialDocument);
1609
OutputFile: TFileStream;
1611
OutputFile := TFileStream.Create(AFileName, fmCreate or fmOpenWrite);
1613
WriteToStream(OutputFile, AData);
1620
The default stream writer just uses WriteToStrings
1622
procedure TvCustomVectorialWriter.WriteToStream(AStream: TStream;
1623
AData: TvVectorialDocument);
1625
lStringList: TStringList;
1627
lStringList := TStringList.Create;
1629
WriteToStrings(lStringList, AData);
1630
lStringList.SaveToStream(AStream);
1636
procedure TvCustomVectorialWriter.WriteToStrings(AStrings: TStrings;
1637
AData: TvVectorialDocument);
1644
procedure TPath.Assign(ASource: TPath);
1647
Points := ASource.Points;
1648
PointsEnd := ASource.PointsEnd;
1649
CurPoint := ASource.CurPoint;
1651
Brush := ASource.Brush;
1652
ClipPath := ASource.ClipPath;
1653
ClipMode := ASource.ClipMode;
1656
procedure TPath.PrepareForSequentialReading;
1661
function TPath.Next(): TPathSegment;
1663
if CurPoint = nil then Result := Points
1664
else Result := CurPoint.Next;
1669
procedure TPath.CalculateBoundingBox(var ALeft, ATop, ARight, ABottom: Double);
1671
lSegment: TPathSegment;
1672
l2DSegment: T2DSegment;
1673
lFirstValue: Boolean = True;
1675
inherited CalculateBoundingBox(ALeft, ATop, ARight, ABottom);
1677
PrepareForSequentialReading();
1679
while lSegment <> nil do
1681
if lSegment is T2DSegment then
1683
l2DSegment := T2DSegment(lSegment);
1686
ALeft := l2DSegment.X;
1687
ATop := l2DSegment.Y;
1688
ARight := l2DSegment.X;
1689
ABottom := l2DSegment.Y;
1690
lFirstValue := False;
1694
if l2DSegment.X < ALeft then ALeft := l2DSegment.X;
1695
if l2DSegment.Y < ATop then ATop := l2DSegment.Y;
1696
if l2DSegment.X > ARight then ARight := l2DSegment.X;
1697
if l2DSegment.Y > ABottom then ABottom := l2DSegment.Y;
1705
procedure TPath.AppendSegment(ASegment: TPathSegment);
1709
// Check if we are the first segment in the tmp path
1710
if PointsEnd = nil then
1713
Exception.Create('[TPath.AppendSegment] Assertion failed Len <> 0 with PointsEnd = nil');
1716
PointsEnd := ASegment;
1724
// Adds the element to the end of the list
1725
PointsEnd.Next := ASegment;
1726
ASegment.Previous := PointsEnd;
1727
PointsEnd := ASegment;
1732
SetLength(GvVectorialFormats, 0);