7
{$define USE_LCL_CANVAS}
8
{$ifdef USE_LCL_CANVAS}
9
{$define USE_CANVAS_CLIP_REGION}
10
{.$define DEBUG_CANVAS_CLIP_REGION}
13
{.$define FPVECTORIAL_TOCANVAS_DEBUG}
17
Classes, SysUtils, Math,
18
{$ifdef USE_LCL_CANVAS}
19
Graphics, LCLIntf, LCLType,
23
fpvectorial, fpvutils;
25
procedure DrawFPVectorialToCanvas(ASource: TvVectorialPage;
26
ADest: TFPCustomCanvas;
27
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
28
procedure DrawFPVPathToCanvas(ASource: TvVectorialPage; CurPath: TPath;
29
ADest: TFPCustomCanvas;
30
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
31
procedure DrawFPVEntityToCanvas(ASource: TvVectorialPage; CurEntity: TvEntity;
32
ADest: TFPCustomCanvas;
33
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
34
procedure DrawFPVTextToCanvas(ASource: TvVectorialPage; CurText: TvText;
35
ADest: TFPCustomCanvas;
36
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
41
This function draws a FPVectorial vectorial image to a TFPCustomCanvas
42
descendent, such as TCanvas from the LCL.
44
Be careful that by default this routine does not execute coordinate transformations,
45
and that FPVectorial works with a start point in the bottom-left corner, with
46
the X growing to the right and the Y growing to the top. This will result in
47
an image in TFPCustomCanvas mirrored in the Y axis in relation with the document
48
as seen in a PDF viewer, for example. This can be easily changed with the
49
provided parameters. To have the standard view of an image viewer one could
50
use this function like this:
52
DrawFPVectorialToCanvas(ASource, ADest, 0, ASource.Height, 1.0, -1.0);
54
procedure DrawFPVectorialToCanvas(ASource: TvVectorialPage;
55
ADest: TFPCustomCanvas;
56
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
61
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
62
WriteLn(':>DrawFPVectorialToCanvas');
65
for i := 0 to ASource.GetEntitiesCount - 1 do
67
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
68
Write(Format('[Path] ID=%d', [i]));
71
CurEntity := ASource.GetEntity(i);
73
if CurEntity is TPath then DrawFPVPathToCanvas(ASource, TPath(CurEntity), ADest, ADestX, ADestY, AMulX, AMulY)
74
else if CurEntity is TvText then DrawFPVTextToCanvas(ASource, TvText(CurEntity), ADest, ADestX, ADestY, AMulX, AMulY)
75
else DrawFPVEntityToCanvas(ASource, CurEntity, ADest, ADestX, ADestY, AMulX, AMulY);
78
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
79
WriteLn(':<DrawFPVectorialToCanvas');
83
procedure DrawFPVPathToCanvas(ASource: TvVectorialPage; CurPath: TPath;
84
ADest: TFPCustomCanvas;
85
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
87
function CoordToCanvasX(ACoord: Double): Integer;
89
Result := Round(ADestX + AmulX * ACoord);
92
function CoordToCanvasY(ACoord: Double): Integer;
94
Result := Round(ADestY + AmulY * ACoord);
99
PosX, PosY: Double; // Not modified by ADestX, etc
100
CoordX, CoordY: Integer;
101
CurSegment: TPathSegment;
102
Cur2DSegment: T2DSegment absolute CurSegment;
103
Cur2DBSegment: T2DBezierSegment absolute CurSegment;
105
CurX, CurY: Integer; // Not modified by ADestX, etc
106
CoordX2, CoordY2, CoordX3, CoordY3, CoordX4, CoordY4: Integer;
107
CurveLength: Integer;
110
Points: array of TPoint;
112
{$ifdef USE_LCL_CANVAS}
113
ClipRegion, OldClipRegion: HRGN;
114
ACanvas: TCanvas absolute ADest;
119
ADest.Brush.Style := bsClear;
121
ADest.MoveTo(ADestX, ADestY);
123
// Set the path Pen and Brush options
124
ADest.Pen.Style := CurPath.Pen.Style;
125
ADest.Pen.Width := Round(CurPath.Pen.Width * AMulX);
126
if ADest.Pen.Width < 1 then ADest.Pen.Width := 1;
127
ADest.Pen.FPColor := CurPath.Pen.Color;
128
ADest.Brush.FPColor := CurPath.Brush.Color;
130
// Prepare the Clipping Region, if any
131
{$ifdef USE_CANVAS_CLIP_REGION}
132
if CurPath.ClipPath <> nil then
134
OldClipRegion := LCLIntf.CreateEmptyRegion();
135
GetClipRgn(ACanvas.Handle, OldClipRegion);
136
ClipRegion := ConvertPathToRegion(CurPath.ClipPath, ADestX, ADestY, AMulX, AMulY);
137
SelectClipRgn(ACanvas.Handle, ClipRegion);
138
DeleteObject(ClipRegion);
140
{$ifdef DEBUG_CANVAS_CLIP_REGION}
141
ConvertPathToPoints(CurPath.ClipPath, ADestX, ADestY, AMulX, AMulY, Points);
142
ACanvas.Polygon(Points);
148
// For solid paths, draw a polygon for the main internal area
150
if CurPath.Brush.Style <> bsClear then
152
CurPath.PrepareForSequentialReading;
154
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
155
Write(' Solid Path Internal Area');
157
ADest.Brush.Style := CurPath.Brush.Style;
159
SetLength(Points, CurPath.Len);
161
for j := 0 to CurPath.Len - 1 do
163
//WriteLn('j = ', j);
164
CurSegment := TPathSegment(CurPath.Next());
166
CoordX := CoordToCanvasX(Cur2DSegment.X);
167
CoordY := CoordToCanvasY(Cur2DSegment.Y);
169
Points[j].X := CoordX;
170
Points[j].Y := CoordY;
172
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
173
Write(Format(' P%d,%d', [CoordY, CoordY]));
177
ADest.Polygon(Points);
179
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
180
Write(' Now the details ');
185
// For other paths, draw more carefully
187
CurPath.PrepareForSequentialReading;
189
for j := 0 to CurPath.Len - 1 do
191
//WriteLn('j = ', j);
192
CurSegment := TPathSegment(CurPath.Next());
194
case CurSegment.SegmentType of
197
CoordX := CoordToCanvasX(Cur2DSegment.X);
198
CoordY := CoordToCanvasY(Cur2DSegment.Y);
199
ADest.MoveTo(CoordX, CoordY);
200
PosX := Cur2DSegment.X;
201
PosY := Cur2DSegment.Y;
202
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
203
Write(Format(' M%d,%d', [CoordY, CoordY]));
206
// This element can override temporarely the Pen
209
ADest.Pen.FPColor := T2DSegmentWithPen(Cur2DSegment).Pen.Color;
211
CoordX := CoordToCanvasX(PosX);
212
CoordY := CoordToCanvasY(PosY);
213
CoordX2 := CoordToCanvasX(Cur2DSegment.X);
214
CoordY2 := CoordToCanvasY(Cur2DSegment.Y);
215
ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
217
PosX := Cur2DSegment.X;
218
PosY := Cur2DSegment.Y;
220
ADest.Pen.FPColor := CurPath.Pen.Color;
222
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
223
Write(Format(' L%d,%d', [CoordToCanvasX(Cur2DSegment.X), CoordToCanvasY(Cur2DSegment.Y)]));
228
CoordX := CoordToCanvasX(PosX);
229
CoordY := CoordToCanvasY(PosY);
230
CoordX2 := CoordToCanvasX(Cur2DSegment.X);
231
CoordY2 := CoordToCanvasY(Cur2DSegment.Y);
232
ADest.Line(CoordX, CoordY, CoordX2, CoordY2);
233
PosX := Cur2DSegment.X;
234
PosY := Cur2DSegment.Y;
235
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
236
Write(Format(' L%d,%d', [CoordX, CoordY]));
239
{ To draw a bezier we need to divide the interval in parts and make
240
lines between this parts }
241
st2DBezier, st3DBezier:
243
CoordX := CoordToCanvasX(PosX);
244
CoordY := CoordToCanvasY(PosY);
245
CoordX2 := CoordToCanvasX(Cur2DBSegment.X2);
246
CoordY2 := CoordToCanvasY(Cur2DBSegment.Y2);
247
CoordX3 := CoordToCanvasX(Cur2DBSegment.X3);
248
CoordY3 := CoordToCanvasY(Cur2DBSegment.Y3);
249
CoordX4 := CoordToCanvasX(Cur2DBSegment.X);
250
CoordY4 := CoordToCanvasY(Cur2DBSegment.Y);
251
SetLength(Points, 0);
253
Make2DPoint(CoordX, CoordY),
254
Make2DPoint(CoordX2, CoordY2),
255
Make2DPoint(CoordX3, CoordY3),
256
Make2DPoint(CoordX4, CoordY4),
260
ADest.Brush.Style := CurPath.Brush.Style;
261
if Length(Points) >= 3 then
262
ADest.Polygon(Points);
264
PosX := Cur2DSegment.X;
265
PosY := Cur2DSegment.Y;
267
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
268
Write(Format(' ***C%d,%d %d,%d %d,%d %d,%d',
269
[CoordToCanvasX(PosX), CoordToCanvasY(PosY),
270
CoordToCanvasX(Cur2DBSegment.X2), CoordToCanvasY(Cur2DBSegment.Y2),
271
CoordToCanvasX(Cur2DBSegment.X3), CoordToCanvasY(Cur2DBSegment.Y3),
272
CoordToCanvasX(Cur2DBSegment.X), CoordToCanvasY(Cur2DBSegment.Y)]));
277
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
281
// Restores the previous Clip Region
282
{$ifdef USE_CANVAS_CLIP_REGION}
283
if CurPath.ClipPath <> nil then
285
SelectClipRgn(ACanvas.Handle, OldClipRegion); //Using OldClipRegion crashes in Qt
290
procedure DrawFPVEntityToCanvas(ASource: TvVectorialPage; CurEntity: TvEntity;
291
ADest: TFPCustomCanvas;
292
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
294
function CoordToCanvasX(ACoord: Double): Integer;
296
Result := Round(ADestX + AmulX * ACoord);
299
function CoordToCanvasY(ACoord: Double): Integer;
301
Result := Round(ADestY + AmulY * ACoord);
306
{$ifdef USE_LCL_CANVAS}
311
CurEllipse: TvEllipse;
313
CurArc: TvCircularArc;
314
FinalStartAngle, FinalEndAngle: double;
315
BoundsLeft, BoundsTop, BoundsRight, BoundsBottom,
316
IntStartAngle, IntAngleLength, IntTmp: Integer;
318
CurDim: TvAlignedDimension;
319
Points: array of TPoint;
320
UpperDim, LowerDim: T3DPoint;
322
{$ifdef USE_LCL_CANVAS}
323
ALCLDest := TCanvas(ADest);
326
if CurEntity is TvEntityWithPenAndBrush then
328
ADest.Brush.Style := (CurEntity as TvEntityWithPenAndBrush).Brush.Style;
329
ADest.Brush.FPColor := (CurEntity as TvEntityWithPenAndBrush).Brush.Color;
331
if CurEntity is TvEntityWithPen then
333
ADest.Pen.Style := (CurEntity as TvEntityWithPen).Pen.Style;
334
ADest.Pen.FPColor := (CurEntity as TvEntityWithPen).Pen.Color;
337
if CurEntity is TvCircle then
339
CurCircle := CurEntity as TvCircle;
341
CoordToCanvasX(CurCircle.X - CurCircle.Radius),
342
CoordToCanvasY(CurCircle.Y - CurCircle.Radius),
343
CoordToCanvasX(CurCircle.X + CurCircle.Radius),
344
CoordToCanvasY(CurCircle.Y + CurCircle.Radius)
347
else if CurEntity is TvCircularArc then
349
CurArc := CurEntity as TvCircularArc;
350
{$ifdef USE_LCL_CANVAS}
351
// ToDo: Consider a X axis inversion
352
// If the Y axis is inverted, then we need to mirror our angles as well
353
BoundsLeft := CoordToCanvasX(CurArc.X - CurArc.Radius);
354
BoundsTop := CoordToCanvasY(CurArc.Y - CurArc.Radius);
355
BoundsRight := CoordToCanvasX(CurArc.X + CurArc.Radius);
356
BoundsBottom := CoordToCanvasY(CurArc.Y + CurArc.Radius);
359
FinalStartAngle := CurArc.StartAngle;
360
FinalEndAngle := CurArc.EndAngle;
362
else // AMulY is negative
364
// Inverting the angles generates the correct result for Y axis inversion
365
if CurArc.EndAngle = 0 then FinalStartAngle := 0
366
else FinalStartAngle := 360 - 1* CurArc.EndAngle;
367
if CurArc.StartAngle = 0 then FinalEndAngle := 0
368
else FinalEndAngle := 360 - 1* CurArc.StartAngle;
370
IntStartAngle := Round(16*FinalStartAngle);
371
IntAngleLength := Round(16*(FinalEndAngle - FinalStartAngle));
372
// On Gtk2 and Carbon, the Left really needs to be to the Left of the Right position
373
// The same for the Top and Bottom
374
// On Windows it works fine either way
375
// On Gtk2 if the positions are inverted then the arcs are screwed up
376
// In Carbon if the positions are inverted, then the arc is inverted
377
if BoundsLeft > BoundsRight then
379
IntTmp := BoundsLeft;
380
BoundsLeft := BoundsRight;
381
BoundsRight := IntTmp;
383
if BoundsTop > BoundsBottom then
386
BoundsTop := BoundsBottom;
387
BoundsBottom := IntTmp;
389
// Arc(ALeft, ATop, ARight, ABottom, Angle16Deg, Angle16DegLength: Integer);
390
{$ifdef FPVECTORIAL_TOCANVAS_DEBUG}
391
// WriteLn(Format('Drawing Arc Center=%f,%f Radius=%f StartAngle=%f AngleLength=%f',
392
// [CurArc.CenterX, CurArc.CenterY, CurArc.Radius, IntStartAngle/16, IntAngleLength/16]));
394
ADest.Pen.FPColor := CurArc.Pen.Color;
396
BoundsLeft, BoundsTop, BoundsRight, BoundsBottom,
397
IntStartAngle, IntAngleLength
399
ADest.Pen.FPColor := colBlack;
401
// {$define FPVECTORIALDEBUG}
402
// {$ifdef FPVECTORIALDEBUG}
403
// WriteLn(Format('Drawing Arc x1y1=%d,%d x2y2=%d,%d start=%d end=%d',
404
// [BoundsLeft, BoundsTop, BoundsRight, BoundsBottom, IntStartAngle, IntAngleLength]));
406
{ ADest.TextOut(CoordToCanvasX(CurArc.CenterX), CoordToCanvasY(CurArc.CenterY),
407
Format('R=%d S=%d L=%d', [Round(CurArc.Radius*AMulX), Round(FinalStartAngle),
408
Abs(Round((FinalEndAngle - FinalStartAngle)))]));
409
ADest.Pen.Color := TColor($DDDDDD);
411
BoundsLeft, BoundsTop, BoundsRight, BoundsBottom);
412
ADest.Pen.Color := clBlack;}
415
else if CurEntity is TvAlignedDimension then
417
CurDim := CurEntity as TvAlignedDimension;
420
// vertical horizontal
424
// Which marks the dimension
425
ADest.MoveTo(CoordToCanvasX(CurDim.BaseRight.X), CoordToCanvasY(CurDim.BaseRight.Y));
426
ADest.LineTo(CoordToCanvasX(CurDim.DimensionRight.X), CoordToCanvasY(CurDim.DimensionRight.Y));
427
ADest.LineTo(CoordToCanvasX(CurDim.DimensionLeft.X), CoordToCanvasY(CurDim.DimensionLeft.Y));
428
ADest.LineTo(CoordToCanvasX(CurDim.BaseLeft.X), CoordToCanvasY(CurDim.BaseLeft.Y));
431
SetLength(Points, 3);
432
if CurDim.DimensionRight.Y = CurDim.DimensionLeft.Y then
434
ADest.Brush.FPColor := colBlack;
435
ADest.Brush.Style := bsSolid;
437
Points[0] := Point(CoordToCanvasX(CurDim.DimensionLeft.X), CoordToCanvasY(CurDim.DimensionLeft.Y));
438
Points[1] := Point(Points[0].X + 7, Points[0].Y - 3);
439
Points[2] := Point(Points[0].X + 7, Points[0].Y + 3);
440
ADest.Polygon(Points);
442
Points[0] := Point(CoordToCanvasX(CurDim.DimensionRight.X), CoordToCanvasY(CurDim.DimensionRight.Y));
443
Points[1] := Point(Points[0].X - 7, Points[0].Y - 3);
444
Points[2] := Point(Points[0].X - 7, Points[0].Y + 3);
445
ADest.Polygon(Points);
446
ADest.Brush.Style := bsClear;
448
Points[0].X := CoordToCanvasX((CurDim.DimensionLeft.X+CurDim.DimensionRight.X)/2);
449
Points[0].Y := CoordToCanvasY(CurDim.DimensionLeft.Y);
450
LowerDim.X := CurDim.DimensionRight.X-CurDim.DimensionLeft.X;
451
ADest.Font.Size := 10;
452
ADest.TextOut(Points[0].X, Points[0].Y, Format('%.1f', [LowerDim.X]));
456
ADest.Brush.FPColor := colBlack;
457
ADest.Brush.Style := bsSolid;
458
// There is no upper/lower preference for DimensionLeft/Right, so we need to check
459
if CurDim.DimensionLeft.Y > CurDim.DimensionRight.Y then
461
UpperDim := CurDim.DimensionLeft;
462
LowerDim := CurDim.DimensionRight;
466
UpperDim := CurDim.DimensionRight;
467
LowerDim := CurDim.DimensionLeft;
470
Points[0] := Point(CoordToCanvasX(UpperDim.X), CoordToCanvasY(UpperDim.Y));
471
Points[1] := Point(Points[0].X + Round(AMulX), Points[0].Y - Round(AMulY*3));
472
Points[2] := Point(Points[0].X - Round(AMulX), Points[0].Y - Round(AMulY*3));
473
ADest.Polygon(Points);
475
Points[0] := Point(CoordToCanvasX(LowerDim.X), CoordToCanvasY(LowerDim.Y));
476
Points[1] := Point(Points[0].X + Round(AMulX), Points[0].Y + Round(AMulY*3));
477
Points[2] := Point(Points[0].X - Round(AMulX), Points[0].Y + Round(AMulY*3));
478
ADest.Polygon(Points);
479
ADest.Brush.Style := bsClear;
481
Points[0].X := CoordToCanvasX(CurDim.DimensionLeft.X);
482
Points[0].Y := CoordToCanvasY((CurDim.DimensionLeft.Y+CurDim.DimensionRight.Y)/2);
483
LowerDim.Y := CurDim.DimensionRight.Y-CurDim.DimensionLeft.Y;
484
if LowerDim.Y < 0 then LowerDim.Y := -1 * LowerDim.Y;
485
ADest.Font.Size := 10;
486
ADest.TextOut(Points[0].X, Points[0].Y, Format('%.1f', [LowerDim.Y]));
488
SetLength(Points, 0);
490
ADest.TextOut(CoordToCanvasX(CurDim.BaseRight.X), CoordToCanvasY(CurDim.BaseRight.Y), 'BR');
491
ADest.TextOut(CoordToCanvasX(CurDim.DimensionRight.X), CoordToCanvasY(CurDim.DimensionRight.Y), 'DR');
492
ADest.TextOut(CoordToCanvasX(CurDim.DimensionLeft.X), CoordToCanvasY(CurDim.DimensionLeft.Y), 'DL');
493
ADest.TextOut(CoordToCanvasX(CurDim.BaseLeft.X), CoordToCanvasY(CurDim.BaseLeft.Y), 'BL');}
496
CurEntity.Render(ADest, ADestX, ADestY, AMulX, AMulY);
499
procedure DrawFPVTextToCanvas(ASource: TvVectorialPage; CurText: TvText;
500
ADest: TFPCustomCanvas;
501
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
503
function CoordToCanvasX(ACoord: Double): Integer;
505
Result := Round(ADestX + AmulX * ACoord);
508
function CoordToCanvasY(ACoord: Double): Integer;
510
Result := Round(ADestY + AmulY * ACoord);
515
{$ifdef USE_LCL_CANVAS}
521
{$ifdef USE_LCL_CANVAS}
522
ALCLDest := TCanvas(ADest);
525
ADest.Font.Size := Round(AmulX * CurText.Font.Size);
526
ADest.Pen.Style := psSolid;
527
ADest.Pen.FPColor := colBlack;
528
ADest.Brush.Style := bsClear;
529
{$ifdef USE_LCL_CANVAS}
530
ALCLDest.Font.Orientation := Round(CurText.Font.Orientation * 16);
533
// TvText supports multiple lines
534
for i := 0 to CurText.Value.Count - 1 do
536
if CurText.Font.Size = 0 then LowerDim.Y := CurText.Y - 12 * (i + 1)
537
else LowerDim.Y := CurText.Y - CurText.Font.Size * (i + 1);
539
ADest.TextOut(CoordToCanvasX(CurText.X), CoordToCanvasY(LowerDim.Y), CurText.Value.Strings[i]);