fpvectorial: Fix writing of rotated rectangles and ellipses to wmf. Fix corners of rotated rounded rectangles.

This commit is contained in:
wp_xyz 2023-01-23 19:07:51 +01:00
parent 03193dbf6d
commit ebcffd6ca1
3 changed files with 107 additions and 45 deletions

View File

@ -553,8 +553,6 @@ type
{ TvEntityWithPen }
TvEntityWithPen = class(TvNamedEntity)
protected
function CreatePath: TPath; virtual;
public
{@@ The global Pen for the entire entity. In the case of paths, individual
elements might be able to override this setting. }
@ -563,6 +561,7 @@ type
procedure ApplyPenToCanvas(constref ARenderInfo: TvRenderInfo); overload;
procedure ApplyPenToCanvas(constref ARenderInfo: TvRenderInfo; APen: TvPen); overload;
procedure AssignPen(APen: TvPen);
function CreatePath: TPath; virtual;
procedure Render(var ARenderInfo: TvRenderInfo; ADoDraw: Boolean = True); override;
end;
@ -732,11 +731,10 @@ type
{ TvCircle }
TvCircle = class(TvEntityWithPenAndBrush)
protected
function CreatePath: TPath; override;
public
Radius: Double;
procedure CalculateBoundingBox(constref ARenderInfo: TvRenderInfo; out ALeft, ATop, ARight, ABottom: Double); override;
function CreatePath: TPath; override;
procedure Render(var ARenderInfo: TvRenderInfo; ADoDraw: Boolean = True); override;
procedure Rotate(AAngle: Double; ABase: T3DPoint); override; // Angle in radians, >0 counter-clockwise
end;
@ -760,8 +758,6 @@ type
{ TvEllipse }
TvEllipse = class(TvEntityWithPenAndBrush)
protected
function CreatePath: TPath; override;
public
// Mandatory fields
HorzHalfAxis: Double; // This half-axis is the horizontal one when Angle=0
@ -769,6 +765,7 @@ type
{@@ The Angle is measured in radians in relation to the positive X axis and
counter-clockwise direction. }
Angle: Double;
function CreatePath: TPath; override;
function GetLineIntersectionPoints(ACoord: Double; ACoordIsX: Boolean): TDoubleDynArray; override;
function TryToSelect(APos: TPoint; var ASubpart: Cardinal; ASnapFlexibility: Integer = 5): TvFindEntityResult; override;
procedure CalculateBoundingBox(constref ARenderInfo: TvRenderInfo; out ALeft, ATop, ARight, ABottom: Double); override;
@ -780,8 +777,6 @@ type
{ The point (X,Y) refers to the left/top corner of the rectangle! }
TvRectangle = class(TvEntityWithPenBrushAndFont)
protected
function CreatePath: TPath; override;
public
// A text displayed in the center of the square, usually empty
Text: string;
@ -793,6 +788,7 @@ type
// Center of rotation is (X,Y).
Angle: Double;
procedure CalculateBoundingBox(constref ARenderInfo: TvRenderInfo; out ALeft, ATop, ARight, ABottom: Double); override;
function CreatePath: TPath; override;
procedure Render(var ARenderInfo: TvRenderInfo; ADoDraw: Boolean = True); override;
procedure Rotate(AAngle: Double; ABase: T3DPoint); override;
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
@ -6348,7 +6344,10 @@ end;
function TvRectangle.CreatePath: TPath;
var
pts: T3dPointsArray = nil;
cc: T3dPointsArray = nil;
ctr: T3dPoint;
refPt: T3dPoint;
shift: T3dPoint;
j: Integer;
phi, lYAdj: Double;
begin
@ -6357,45 +6356,72 @@ begin
if (RX > 0) and (RY > 0) then
begin
SetLength(pts, 9);
pts[0] := Make3dPoint(X, Y+lYAdj*RY); { 1 2 }
pts[1] := Make3dPoint(X+RX, Y); { 0,8 3 }
pts[2] := Make3dPoint(X+CX-RX, Y); { }
pts[3] := Make3dPoint(X+CX, Y+lYAdj*RY); { }
pts[4] := Make3dPoint(X+CX, Y+lYAdj*(CY-RY)); { 7 4 }
pts[5] := Make3dPoint(X+CX-RX, Y+lYAdj*CY); { 6 5 }
pts[6] := Make3dPoint(X+RX, Y+lYAdj*CY);
pts[7] := Make3dPoint(X, Y+lYAdj*(CY-RY));
pts[8] := Make3dPoint(X, Y+lYAdj*RY);
pts[0] := Make3dPoint(X, Y+lYAdj*RY); { 1 2 }
pts[1] := Make3dPoint(X+RX, Y); { 0,8 3 }
pts[2] := Make3dPoint(X+CX-RX, Y); { }
pts[3] := Make3dPoint(X+CX, Y+lYAdj*RY); { }
pts[4] := Make3dPoint(X+CX, Y+lYAdj*(CY-RY)); { 7 4 }
pts[5] := Make3dPoint(X+CX-RX, Y+lYAdj*CY); { 6 5 }
pts[6] := Make3dPoint(X+RX, Y+lYAdj*CY);
pts[7] := Make3dPoint(X, Y+lYAdj*(CY-RY));
pts[8] := Make3dPoint(X, Y+lYAdj*RY);
SetLength(cc, 4); // centers of the corner circles
cc[0] := Make3dPoint(pts[1].x, pts[0].y);
cc[1] := Make3dPoint(pts[2].x, pts[3].y);
cc[2] := Make3dPoint(pts[5].x, pts[4].y);
cc[3] := Make3dPoint(pts[6].x, pts[7].y);
end
else
begin
SetLength(pts, 5); { 0,4 1 }
pts[0] := Make3dPoint(X, Y); { }
pts[0] := Make3dPoint(X, Y); { }
pts[1] := Make3dPoint(X+CX, Y); { }
pts[2] := Make3dPoint(X+CX, Y+lYAdj*CY); { }
pts[3] := Make3dPoint(X, Y+lYAdj*CY); { }
pts[4] := Make3dPoint(X, Y); { 3 2 }
pts[3] := Make3dPoint(X, Y+lYAdj*CY); { }
pts[4] := Make3dPoint(X, Y); { 3 2 }
end;
ctr := Make3DPoint(X, Y); // Rotation center
phi := -Angle; // Angle must be inverted due to sign convention in Rotate3DPointInXY
// We first rotate around the center of the rectangle and then move the
// rectangle points by the difference vector between the new and old top/left
// corner point.
refPt := Make3dPoint(X, Y); // Top/left point
ctr := Make3DPoint(X+CX/2, Y+CY/2*lYAdj); // Rotation center = center of rect
phi := -Angle; // Angle must be inverted due to sign convention in Rotate3DPointInXY
// Perform the rotation
for j:=0 to High(pts) do
pts[j] := Rotate3DPointInXY(pts[j], ctr, phi);
for j := 0 to High(cc) do
cc[j] := Rotate3DPointInXY(cc[j], ctr, phi);
// Perform the translation so that top/left corner is back at its original position.
shift := Make3dPoint(refPt.x - pts[0].x, refPt.y - pts[0].y);
for j := 0 to High(pts) do
pts[j] := Offset3dPoint(pts[j], shift);
for j := 0 to High(cc) do
cc[j] := Offset3dPoint(cc[j], shift);
// Now create the path from the rotated points
Result := TPath.Create(FPage);
if (RX > 0) and (RY > 0) then
begin
Result.AppendMoveToSegment(pts[0].x, pts[0].y);
Result.AppendEllipticalArcWithCenter(RX, RY, phi, pts[1].x, pts[1].y,
pts[1].x, pts[0].y, true);
cc[0].x, cc[0].y, true);
// pts[1].x, pts[0].y, true);
Result.AppendLineToSegment(pts[2].x, pts[2].y);
Result.AppendEllipticalArcWithCenter(RX, RY, phi, pts[3].x, pts[3].y,
pts[2].x, pts[3].y, true);
cc[1].x, cc[1].y, true);
// pts[2].x, pts[3].y, true);
Result.AppendLineToSegment(pts[4].x, pts[4].y);
Result.AppendEllipticalArcWithCenter(RX, RY, phi, pts[5].x, pts[5].y,
pts[5].x, pts[4].y, true);
cc[2].x, cc[2].y, true);
//pts[5].x, pts[4].y, true);
Result.AppendLineToSegment(pts[6].x, pts[6].y);
Result.AppendEllipticalArcWithCenter(RX, RY, phi, pts[7].x, pts[7].y,
pts[6].x, pts[7].y, true);
cc[3].x, cc[3].y, true);
// pts[6].x, pts[7].y, true);
Result.AppendLineToSegment(pts[8].x, pts[8].y);
end else
begin

View File

@ -80,6 +80,7 @@ function GetLinePolygonIntersectionPoints(ACoord: Double;
ACoordIsX: Boolean): T2DPointsArray; overload;
function GetLinePolygonIntersectionPoints(ACoord: Double;
const APoints: T2DPointsArray; ACoordIsX: Boolean): T2DPointsArray; overload;
function Offset3DPoint(P, Delta: T3DPoint): T3DPoint;
function Rotate2DPoint(P, RotCenter: TPoint; alpha:double): TPoint;
function Rotate3DPointInXY(P, RotCenter: T3DPoint; alpha:double): T3DPoint;
function SamePoint(P1, P2: T3DPoint; Epsilon: Double = 0.0): Boolean; overload;
@ -849,6 +850,14 @@ begin
list.Free;
end;
// Offset the point P by the vector Delta
function Offset3DPoint(P, Delta: T3DPoint): T3DPoint;
begin
Result.x := P.x + Delta.x;
Result.y := P.y + Delta.y;
Result.z := P.z;
end;
// Rotates a point P around RotCenter
function Rotate2DPoint(P, RotCenter: TPoint; alpha:double): TPoint;
var

View File

@ -65,6 +65,7 @@ type
FCurrBkMode: Word;
{%H-}FCurrPolyFillMode: Word;
FUseTopLeftCoordinates: Boolean; // If true, input coordinates are given in top/left coordinate system.
FPage: TvVectorialPage;
FErrMsg: TStrings;
function CalcChecksum: Word;
@ -485,16 +486,29 @@ procedure TvWMFVectorialWriter.WriteEllipse(AStream: TStream;
AEllipse: TvEllipse);
var
r: TWMFRectRecord;
path: TPath;
begin
WritePen(AStream, AEllipse.Pen);
WriteBrush(AStream, AEllipse.Brush);
r.Left := ScaleX(AEllipse.X - AEllipse.HorzHalfAxis);
r.Top := ScaleY(AEllipse.Y + AEllipse.VertHalfAxis);
r.Right := ScaleX(AEllipse.X + AEllipse.HorzHalfAxis);
r.Bottom := ScaleY(AEllipse.Y - AEllipse.VertHalfAxis);
if AEllipse.Angle = 0 then
begin
WritePen(AStream, AEllipse.Pen);
WriteBrush(AStream, AEllipse.Brush);
// WMF record header + parameters
WriteWMFRecord(AStream, META_ELLIPSE, r, SizeOf(TWMFRectRecord));
r.Left := ScaleX(AEllipse.X - AEllipse.HorzHalfAxis);
r.Top := ScaleY(AEllipse.Y + AEllipse.VertHalfAxis);
r.Right := ScaleX(AEllipse.X + AEllipse.HorzHalfAxis);
r.Bottom := ScaleY(AEllipse.Y - AEllipse.VertHalfAxis);
// WMF record header + parameters
WriteWMFRecord(AStream, META_ELLIPSE, r, SizeOf(TWMFRectRecord));
end else
begin
// Write rotated ellipse as a path
path := AEllipse.CreatePath;
path.Pen := AEllipse.Pen;
path.Brush := AEllipse.Brush;
WritePath(AStream, path);
path.Free;
end;
end;
@ -648,6 +662,7 @@ end;
procedure TvWMFVectorialWriter.WritePage(AStream: TStream;
AData: TvVectorialDocument; APage: TvVectorialPage);
begin
FPage := APage;
WriteWindowExt(AStream);
WriteWindowOrg(AStream);
WriteMapMode(AStream);
@ -838,6 +853,7 @@ procedure TvWMFVectorialWriter.WriteRectangle(AStream: TStream;
var
r: TWMFRectRecord;
p: TWMFPointRecord;
path: TPath;
begin
WritePen(AStream, ARectangle.Pen);
WriteBrush(AStream, ARectangle.Brush);
@ -846,17 +862,28 @@ begin
r.Right := ScaleX(ARectangle.X + ARectangle.CX);
r.Bottom := ScaleY(ARectangle.Y - ARectangle.CY);
// WMF record header + parameters
if (ARectangle.RX = 0) or (ARectangle.RY = 0) then
// "normal" rectangle
WriteWMFRecord(AStream, META_RECTANGLE, r, SizeOf(TWMFRectRecord))
else begin
// rounded rectangle
p.X := ScaleSizeX(ARectangle.RX);
p.Y := ScaleSizeY(ARectangle.RY);
WriteWMFRecord(AStream, META_ROUNDRECT, SizeOf(p) + SizeOf(r));
WriteWMFParams(AStream, p, SizeOf(p));
WriteWMFParams(AStream, r, SizeOf(r));
if ARectangle.Angle = 0 then
begin
// WMF record header + parameters
if (ARectangle.RX = 0) or (ARectangle.RY = 0) then
// "normal" rectangle
WriteWMFRecord(AStream, META_RECTANGLE, r, SizeOf(TWMFRectRecord))
else begin
// rounded rectangle
p.X := ScaleSizeX(ARectangle.RX);
p.Y := ScaleSizeY(ARectangle.RY);
WriteWMFRecord(AStream, META_ROUNDRECT, SizeOf(p) + SizeOf(r));
WriteWMFParams(AStream, p, SizeOf(p));
WriteWMFParams(AStream, r, SizeOf(r));
end;
end else
begin
// Write rotated rectangle as a path
path := ARectangle.CreatePath;
path.Pen := ARectangle.Pen;
path.Brush := ARectangle.Brush;
WritePath(AStream, path);
path.Free;
end;
end;