mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-08-12 04:36:31 +02:00
fpvectorial: Fix writing of rotated rectangles and ellipses to wmf. Fix corners of rotated rounded rectangles.
This commit is contained in:
parent
03193dbf6d
commit
ebcffd6ca1
@ -553,8 +553,6 @@ type
|
|||||||
{ TvEntityWithPen }
|
{ TvEntityWithPen }
|
||||||
|
|
||||||
TvEntityWithPen = class(TvNamedEntity)
|
TvEntityWithPen = class(TvNamedEntity)
|
||||||
protected
|
|
||||||
function CreatePath: TPath; virtual;
|
|
||||||
public
|
public
|
||||||
{@@ The global Pen for the entire entity. In the case of paths, individual
|
{@@ The global Pen for the entire entity. In the case of paths, individual
|
||||||
elements might be able to override this setting. }
|
elements might be able to override this setting. }
|
||||||
@ -563,6 +561,7 @@ type
|
|||||||
procedure ApplyPenToCanvas(constref ARenderInfo: TvRenderInfo); overload;
|
procedure ApplyPenToCanvas(constref ARenderInfo: TvRenderInfo); overload;
|
||||||
procedure ApplyPenToCanvas(constref ARenderInfo: TvRenderInfo; APen: TvPen); overload;
|
procedure ApplyPenToCanvas(constref ARenderInfo: TvRenderInfo; APen: TvPen); overload;
|
||||||
procedure AssignPen(APen: TvPen);
|
procedure AssignPen(APen: TvPen);
|
||||||
|
function CreatePath: TPath; virtual;
|
||||||
procedure Render(var ARenderInfo: TvRenderInfo; ADoDraw: Boolean = True); override;
|
procedure Render(var ARenderInfo: TvRenderInfo; ADoDraw: Boolean = True); override;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
@ -732,11 +731,10 @@ type
|
|||||||
{ TvCircle }
|
{ TvCircle }
|
||||||
|
|
||||||
TvCircle = class(TvEntityWithPenAndBrush)
|
TvCircle = class(TvEntityWithPenAndBrush)
|
||||||
protected
|
|
||||||
function CreatePath: TPath; override;
|
|
||||||
public
|
public
|
||||||
Radius: Double;
|
Radius: Double;
|
||||||
procedure CalculateBoundingBox(constref ARenderInfo: TvRenderInfo; out ALeft, ATop, ARight, ABottom: Double); override;
|
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 Render(var ARenderInfo: TvRenderInfo; ADoDraw: Boolean = True); override;
|
||||||
procedure Rotate(AAngle: Double; ABase: T3DPoint); override; // Angle in radians, >0 counter-clockwise
|
procedure Rotate(AAngle: Double; ABase: T3DPoint); override; // Angle in radians, >0 counter-clockwise
|
||||||
end;
|
end;
|
||||||
@ -760,8 +758,6 @@ type
|
|||||||
{ TvEllipse }
|
{ TvEllipse }
|
||||||
|
|
||||||
TvEllipse = class(TvEntityWithPenAndBrush)
|
TvEllipse = class(TvEntityWithPenAndBrush)
|
||||||
protected
|
|
||||||
function CreatePath: TPath; override;
|
|
||||||
public
|
public
|
||||||
// Mandatory fields
|
// Mandatory fields
|
||||||
HorzHalfAxis: Double; // This half-axis is the horizontal one when Angle=0
|
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
|
{@@ The Angle is measured in radians in relation to the positive X axis and
|
||||||
counter-clockwise direction. }
|
counter-clockwise direction. }
|
||||||
Angle: Double;
|
Angle: Double;
|
||||||
|
function CreatePath: TPath; override;
|
||||||
function GetLineIntersectionPoints(ACoord: Double; ACoordIsX: Boolean): TDoubleDynArray; override;
|
function GetLineIntersectionPoints(ACoord: Double; ACoordIsX: Boolean): TDoubleDynArray; override;
|
||||||
function TryToSelect(APos: TPoint; var ASubpart: Cardinal; ASnapFlexibility: Integer = 5): TvFindEntityResult; 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;
|
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! }
|
{ The point (X,Y) refers to the left/top corner of the rectangle! }
|
||||||
|
|
||||||
TvRectangle = class(TvEntityWithPenBrushAndFont)
|
TvRectangle = class(TvEntityWithPenBrushAndFont)
|
||||||
protected
|
|
||||||
function CreatePath: TPath; override;
|
|
||||||
public
|
public
|
||||||
// A text displayed in the center of the square, usually empty
|
// A text displayed in the center of the square, usually empty
|
||||||
Text: string;
|
Text: string;
|
||||||
@ -793,6 +788,7 @@ type
|
|||||||
// Center of rotation is (X,Y).
|
// Center of rotation is (X,Y).
|
||||||
Angle: Double;
|
Angle: Double;
|
||||||
procedure CalculateBoundingBox(constref ARenderInfo: TvRenderInfo; out ALeft, ATop, ARight, ABottom: Double); override;
|
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 Render(var ARenderInfo: TvRenderInfo; ADoDraw: Boolean = True); override;
|
||||||
procedure Rotate(AAngle: Double; ABase: T3DPoint); override;
|
procedure Rotate(AAngle: Double; ABase: T3DPoint); override;
|
||||||
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
|
function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
|
||||||
@ -6348,7 +6344,10 @@ end;
|
|||||||
function TvRectangle.CreatePath: TPath;
|
function TvRectangle.CreatePath: TPath;
|
||||||
var
|
var
|
||||||
pts: T3dPointsArray = nil;
|
pts: T3dPointsArray = nil;
|
||||||
|
cc: T3dPointsArray = nil;
|
||||||
ctr: T3dPoint;
|
ctr: T3dPoint;
|
||||||
|
refPt: T3dPoint;
|
||||||
|
shift: T3dPoint;
|
||||||
j: Integer;
|
j: Integer;
|
||||||
phi, lYAdj: Double;
|
phi, lYAdj: Double;
|
||||||
begin
|
begin
|
||||||
@ -6357,45 +6356,72 @@ begin
|
|||||||
if (RX > 0) and (RY > 0) then
|
if (RX > 0) and (RY > 0) then
|
||||||
begin
|
begin
|
||||||
SetLength(pts, 9);
|
SetLength(pts, 9);
|
||||||
pts[0] := Make3dPoint(X, Y+lYAdj*RY); { 1 2 }
|
pts[0] := Make3dPoint(X, Y+lYAdj*RY); { 1 2 }
|
||||||
pts[1] := Make3dPoint(X+RX, Y); { 0,8 3 }
|
pts[1] := Make3dPoint(X+RX, Y); { 0,8 3 }
|
||||||
pts[2] := Make3dPoint(X+CX-RX, Y); { }
|
pts[2] := Make3dPoint(X+CX-RX, Y); { }
|
||||||
pts[3] := Make3dPoint(X+CX, Y+lYAdj*RY); { }
|
pts[3] := Make3dPoint(X+CX, Y+lYAdj*RY); { }
|
||||||
pts[4] := Make3dPoint(X+CX, Y+lYAdj*(CY-RY)); { 7 4 }
|
pts[4] := Make3dPoint(X+CX, Y+lYAdj*(CY-RY)); { 7 4 }
|
||||||
pts[5] := Make3dPoint(X+CX-RX, Y+lYAdj*CY); { 6 5 }
|
pts[5] := Make3dPoint(X+CX-RX, Y+lYAdj*CY); { 6 5 }
|
||||||
pts[6] := Make3dPoint(X+RX, Y+lYAdj*CY);
|
pts[6] := Make3dPoint(X+RX, Y+lYAdj*CY);
|
||||||
pts[7] := Make3dPoint(X, Y+lYAdj*(CY-RY));
|
pts[7] := Make3dPoint(X, Y+lYAdj*(CY-RY));
|
||||||
pts[8] := Make3dPoint(X, Y+lYAdj*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
|
end
|
||||||
else
|
else
|
||||||
begin
|
begin
|
||||||
SetLength(pts, 5); { 0,4 1 }
|
SetLength(pts, 5); { 0,4 1 }
|
||||||
pts[0] := Make3dPoint(X, Y); { }
|
pts[0] := Make3dPoint(X, Y); { }
|
||||||
pts[1] := Make3dPoint(X+CX, Y); { }
|
pts[1] := Make3dPoint(X+CX, Y); { }
|
||||||
pts[2] := Make3dPoint(X+CX, Y+lYAdj*CY); { }
|
pts[2] := Make3dPoint(X+CX, Y+lYAdj*CY); { }
|
||||||
pts[3] := Make3dPoint(X, Y+lYAdj*CY); { }
|
pts[3] := Make3dPoint(X, Y+lYAdj*CY); { }
|
||||||
pts[4] := Make3dPoint(X, Y); { 3 2 }
|
pts[4] := Make3dPoint(X, Y); { 3 2 }
|
||||||
end;
|
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
|
for j:=0 to High(pts) do
|
||||||
pts[j] := Rotate3DPointInXY(pts[j], ctr, phi);
|
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);
|
Result := TPath.Create(FPage);
|
||||||
if (RX > 0) and (RY > 0) then
|
if (RX > 0) and (RY > 0) then
|
||||||
begin
|
begin
|
||||||
Result.AppendMoveToSegment(pts[0].x, pts[0].y);
|
Result.AppendMoveToSegment(pts[0].x, pts[0].y);
|
||||||
Result.AppendEllipticalArcWithCenter(RX, RY, phi, pts[1].x, pts[1].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.AppendLineToSegment(pts[2].x, pts[2].y);
|
||||||
Result.AppendEllipticalArcWithCenter(RX, RY, phi, pts[3].x, pts[3].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.AppendLineToSegment(pts[4].x, pts[4].y);
|
||||||
Result.AppendEllipticalArcWithCenter(RX, RY, phi, pts[5].x, pts[5].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.AppendLineToSegment(pts[6].x, pts[6].y);
|
||||||
Result.AppendEllipticalArcWithCenter(RX, RY, phi, pts[7].x, pts[7].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);
|
Result.AppendLineToSegment(pts[8].x, pts[8].y);
|
||||||
end else
|
end else
|
||||||
begin
|
begin
|
||||||
|
@ -80,6 +80,7 @@ function GetLinePolygonIntersectionPoints(ACoord: Double;
|
|||||||
ACoordIsX: Boolean): T2DPointsArray; overload;
|
ACoordIsX: Boolean): T2DPointsArray; overload;
|
||||||
function GetLinePolygonIntersectionPoints(ACoord: Double;
|
function GetLinePolygonIntersectionPoints(ACoord: Double;
|
||||||
const APoints: T2DPointsArray; ACoordIsX: Boolean): T2DPointsArray; overload;
|
const APoints: T2DPointsArray; ACoordIsX: Boolean): T2DPointsArray; overload;
|
||||||
|
function Offset3DPoint(P, Delta: T3DPoint): T3DPoint;
|
||||||
function Rotate2DPoint(P, RotCenter: TPoint; alpha:double): TPoint;
|
function Rotate2DPoint(P, RotCenter: TPoint; alpha:double): TPoint;
|
||||||
function Rotate3DPointInXY(P, RotCenter: T3DPoint; alpha:double): T3DPoint;
|
function Rotate3DPointInXY(P, RotCenter: T3DPoint; alpha:double): T3DPoint;
|
||||||
function SamePoint(P1, P2: T3DPoint; Epsilon: Double = 0.0): Boolean; overload;
|
function SamePoint(P1, P2: T3DPoint; Epsilon: Double = 0.0): Boolean; overload;
|
||||||
@ -849,6 +850,14 @@ begin
|
|||||||
list.Free;
|
list.Free;
|
||||||
end;
|
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
|
// Rotates a point P around RotCenter
|
||||||
function Rotate2DPoint(P, RotCenter: TPoint; alpha:double): TPoint;
|
function Rotate2DPoint(P, RotCenter: TPoint; alpha:double): TPoint;
|
||||||
var
|
var
|
||||||
|
@ -65,6 +65,7 @@ type
|
|||||||
FCurrBkMode: Word;
|
FCurrBkMode: Word;
|
||||||
{%H-}FCurrPolyFillMode: Word;
|
{%H-}FCurrPolyFillMode: Word;
|
||||||
FUseTopLeftCoordinates: Boolean; // If true, input coordinates are given in top/left coordinate system.
|
FUseTopLeftCoordinates: Boolean; // If true, input coordinates are given in top/left coordinate system.
|
||||||
|
FPage: TvVectorialPage;
|
||||||
FErrMsg: TStrings;
|
FErrMsg: TStrings;
|
||||||
|
|
||||||
function CalcChecksum: Word;
|
function CalcChecksum: Word;
|
||||||
@ -485,16 +486,29 @@ procedure TvWMFVectorialWriter.WriteEllipse(AStream: TStream;
|
|||||||
AEllipse: TvEllipse);
|
AEllipse: TvEllipse);
|
||||||
var
|
var
|
||||||
r: TWMFRectRecord;
|
r: TWMFRectRecord;
|
||||||
|
path: TPath;
|
||||||
begin
|
begin
|
||||||
WritePen(AStream, AEllipse.Pen);
|
if AEllipse.Angle = 0 then
|
||||||
WriteBrush(AStream, AEllipse.Brush);
|
begin
|
||||||
r.Left := ScaleX(AEllipse.X - AEllipse.HorzHalfAxis);
|
WritePen(AStream, AEllipse.Pen);
|
||||||
r.Top := ScaleY(AEllipse.Y + AEllipse.VertHalfAxis);
|
WriteBrush(AStream, AEllipse.Brush);
|
||||||
r.Right := ScaleX(AEllipse.X + AEllipse.HorzHalfAxis);
|
|
||||||
r.Bottom := ScaleY(AEllipse.Y - AEllipse.VertHalfAxis);
|
|
||||||
|
|
||||||
// WMF record header + parameters
|
r.Left := ScaleX(AEllipse.X - AEllipse.HorzHalfAxis);
|
||||||
WriteWMFRecord(AStream, META_ELLIPSE, r, SizeOf(TWMFRectRecord));
|
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;
|
end;
|
||||||
|
|
||||||
|
|
||||||
@ -648,6 +662,7 @@ end;
|
|||||||
procedure TvWMFVectorialWriter.WritePage(AStream: TStream;
|
procedure TvWMFVectorialWriter.WritePage(AStream: TStream;
|
||||||
AData: TvVectorialDocument; APage: TvVectorialPage);
|
AData: TvVectorialDocument; APage: TvVectorialPage);
|
||||||
begin
|
begin
|
||||||
|
FPage := APage;
|
||||||
WriteWindowExt(AStream);
|
WriteWindowExt(AStream);
|
||||||
WriteWindowOrg(AStream);
|
WriteWindowOrg(AStream);
|
||||||
WriteMapMode(AStream);
|
WriteMapMode(AStream);
|
||||||
@ -838,6 +853,7 @@ procedure TvWMFVectorialWriter.WriteRectangle(AStream: TStream;
|
|||||||
var
|
var
|
||||||
r: TWMFRectRecord;
|
r: TWMFRectRecord;
|
||||||
p: TWMFPointRecord;
|
p: TWMFPointRecord;
|
||||||
|
path: TPath;
|
||||||
begin
|
begin
|
||||||
WritePen(AStream, ARectangle.Pen);
|
WritePen(AStream, ARectangle.Pen);
|
||||||
WriteBrush(AStream, ARectangle.Brush);
|
WriteBrush(AStream, ARectangle.Brush);
|
||||||
@ -846,17 +862,28 @@ begin
|
|||||||
r.Right := ScaleX(ARectangle.X + ARectangle.CX);
|
r.Right := ScaleX(ARectangle.X + ARectangle.CX);
|
||||||
r.Bottom := ScaleY(ARectangle.Y - ARectangle.CY);
|
r.Bottom := ScaleY(ARectangle.Y - ARectangle.CY);
|
||||||
|
|
||||||
// WMF record header + parameters
|
if ARectangle.Angle = 0 then
|
||||||
if (ARectangle.RX = 0) or (ARectangle.RY = 0) then
|
begin
|
||||||
// "normal" rectangle
|
// WMF record header + parameters
|
||||||
WriteWMFRecord(AStream, META_RECTANGLE, r, SizeOf(TWMFRectRecord))
|
if (ARectangle.RX = 0) or (ARectangle.RY = 0) then
|
||||||
else begin
|
// "normal" rectangle
|
||||||
// rounded rectangle
|
WriteWMFRecord(AStream, META_RECTANGLE, r, SizeOf(TWMFRectRecord))
|
||||||
p.X := ScaleSizeX(ARectangle.RX);
|
else begin
|
||||||
p.Y := ScaleSizeY(ARectangle.RY);
|
// rounded rectangle
|
||||||
WriteWMFRecord(AStream, META_ROUNDRECT, SizeOf(p) + SizeOf(r));
|
p.X := ScaleSizeX(ARectangle.RX);
|
||||||
WriteWMFParams(AStream, p, SizeOf(p));
|
p.Y := ScaleSizeY(ARectangle.RY);
|
||||||
WriteWMFParams(AStream, r, SizeOf(r));
|
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;
|
||||||
end;
|
end;
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user