fpvectorial-svg: Adds support for paths with multiple path closures, by adding them all to an entity with subentities.

git-svn-id: trunk@55385 -
This commit is contained in:
sekelsenmat 2017-06-22 04:10:39 +00:00
parent aba8fa6c61
commit 7dff93e5af

View File

@ -10,6 +10,39 @@ SVG Coordinates vs FPVectorial coordinates:
SVG by default has [0, 0] at the top-left and coordinates grow downwards and to the right
Text is drawn upwards (towards negative Y)
* Example of a supported SVG image:
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with fpVectorial (http://wiki.lazarus.freepascal.org/fpvectorial) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
width="100mm"
height="100mm"
id="svg2"
version="1.1"
sodipodi:docname="New document 1">
<g id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0,283.486888731396 l 106.307583274274,-35.4358610914245 "
id="path0" />
<path
style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0,354.358610914245 l 354.358610914245,0 l 0,-354.358610914245 l -354.358610914245,0 l 0,354.358610914245 "
id="path1" />
<path
style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0,354.358610914245 l 35.4358610914245,-35.4358610914245 c 0,-35.4358610914246 35.4358610914245,-35.4358610914246 35.4358610914245,0 l 35.4358610914245,35.4358610914245 "
id="path2" />
</g>
</svg>
}
unit svgvectorialreader;
@ -101,6 +134,20 @@ type
TSVGUnit = (suPX, suMM, suPT {Points});
{ TvSVGPathList }
TvSVGPathList = class(TFPObjectList)
public
// parsing temporary info
IsFirstPathMove: Boolean;
LastPathClosed: Boolean;
CurTokenIndex: Integer;
CurX, CurY: Double;
Data: TvVectorialPage;
Doc: TvVectorialDocument;
function GetPath(AIndex: Integer): TPath;
end;
{ TvSVGVectorialReader }
TvSVGVectorialReader = class(TvCustomVectorialReader)
@ -156,8 +203,8 @@ type
procedure ReadLayerFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
function ReadLineFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
function ReadPathFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
procedure ReadPathFromString(AStr: string; AData: TvVectorialPage; ADoc: TvVectorialDocument);
procedure ReadNextPathCommand(ACurTokenType: TSVGTokenType; var i: Integer; var AIsFirstPathMove: Boolean; var CurX, CurY: Double; AData: TvVectorialPage; ADoc: TvVectorialDocument);
function ReadPathFromString(AStr: string; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvSVGPathList;
procedure ReadNextPathCommand(ACurTokenType: TSVGTokenType; APaths: TvSVGPathList; var CurX, CurY: Double);
procedure ReadPointsFromString(AStr: string; AData: TvVectorialPage; ADoc: TvVectorialDocument; AClosePath: Boolean);
function ReadPolyFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
function ReadRectFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
@ -478,39 +525,12 @@ begin
+ Format('(%f) ', [Tokens.Items[i].Value]);
end;
{ Example of a supported SVG image:
{ TvSVGPathList }
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with fpVectorial (http://wiki.lazarus.freepascal.org/fpvectorial) -->
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
width="100mm"
height="100mm"
id="svg2"
version="1.1"
sodipodi:docname="New document 1">
<g id="layer1">
<path
style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0,283.486888731396 l 106.307583274274,-35.4358610914245 "
id="path0" />
<path
style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0,354.358610914245 l 354.358610914245,0 l 0,-354.358610914245 l -354.358610914245,0 l 0,354.358610914245 "
id="path1" />
<path
style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
d="m 0,354.358610914245 l 35.4358610914245,-35.4358610914245 c 0,-35.4358610914246 35.4358610914245,-35.4358610914246 35.4358610914245,0 l 35.4358610914245,35.4358610914245 "
id="path2" />
</g>
</svg>
}
function TvSVGPathList.GetPath(AIndex: Integer): TPath;
begin
Result := TPath(Self.Items[AIndex]);
end;
{ TvSVGVectorialReader }
@ -2261,9 +2281,11 @@ function TvSVGVectorialReader.ReadPathFromNode(ANode: TDOMNode;
AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
var
lNodeName, lDStr: WideString;
i: Integer;
lPath: TPath;
i, j: Integer;
lCurPath: TPath;
lPaths: TvSVGPathList;
begin
Result := nil;
for i := 0 to ANode.Attributes.Length - 1 do
begin
lNodeName := ANode.Attributes.Item[i].NodeName;
@ -2271,67 +2293,84 @@ begin
lDStr := ANode.Attributes.Item[i].NodeValue;
end;
AData.StartPath();
Inc(FPathNumber);
FSVGPathTokenizer.ExtraDebugStr := Format(' [TvSVGVectorialReader.ReadPathFromNode] path#(1-based)=%d', [FPathNumber]);
ReadPathFromString(UTF8Encode(lDStr), AData, ADoc);
lPaths := ReadPathFromString(UTF8Encode(lDStr), AData, ADoc);
FSVGPathTokenizer.ExtraDebugStr := '';
lPath := AData.EndPath(True);
Result := lPath;
// Add default SVG pen/brush
lPath.Pen.Style := psClear;
lPath.Brush.Color := colBlack;
lPath.Brush.Style := bsClear;
// Apply the layer style
ApplyLayerStyles(AData, lPath);
// Add the pen/brush/name
for i := 0 to ANode.Attributes.Length - 1 do
for j := 0 to lPaths.Count-1 do
begin
lNodeName := ANode.Attributes.Item[i].NodeName;
if lNodeName = 'id' then
lPath.Name := ANode.Attributes.Item[i].NodeValue
else if lNodeName = 'style' then
ReadSVGStyle(AData, ANode.Attributes.Item[i].NodeValue, lPath)
else if IsAttributeFromStyle(lNodeName) then
lCurPath := lPaths.GetPath(j);
// Add default SVG pen/brush
lCurPath.Pen.Style := psClear;
lCurPath.Brush.Color := colBlack;
lCurPath.Brush.Style := bsClear;
// Apply the layer style
ApplyLayerStyles(AData, lCurPath);
// Add the pen/brush/name
for i := 0 to ANode.Attributes.Length - 1 do
begin
ReadSVGPenStyleWithKeyAndValue(lNodeName,
ANode.Attributes.Item[i].NodeValue, lPath);
ReadSVGBrushStyleWithKeyAndValue(lNodeName,
ANode.Attributes.Item[i].NodeValue, lPath);
ReadSVGGeneralStyleWithKeyAndValue(AData, lNodeName,
ANode.Attributes.Item[i].NodeValue, lPath);
lNodeName := ANode.Attributes.Item[i].NodeName;
if lNodeName = 'id' then
lCurPath.Name := ANode.Attributes.Item[i].NodeValue
else if lNodeName = 'style' then
ReadSVGStyle(AData, ANode.Attributes.Item[i].NodeValue, lCurPath)
else if IsAttributeFromStyle(lNodeName) then
begin
ReadSVGPenStyleWithKeyAndValue(lNodeName,
ANode.Attributes.Item[i].NodeValue, lCurPath);
ReadSVGBrushStyleWithKeyAndValue(lNodeName,
ANode.Attributes.Item[i].NodeValue, lCurPath);
ReadSVGGeneralStyleWithKeyAndValue(AData, lNodeName,
ANode.Attributes.Item[i].NodeValue, lCurPath);
end;
end;
end;
if lPaths.Count = 1 then
begin
Result := lPaths.GetPath(0);
end
else
begin
Result := TvEntityWithSubEntities.Create(nil);
for j := 0 to lPaths.Count-1 do
begin
lCurPath := lPaths.GetPath(j);
TvEntityWithSubEntities(Result).AddEntity(lCurPath);
end;
end;
end;
// Documentation: http://www.w3.org/TR/SVG/paths.html
procedure TvSVGVectorialReader.ReadPathFromString(AStr: string;
AData: TvVectorialPage; ADoc: TvVectorialDocument);
function TvSVGVectorialReader.ReadPathFromString(AStr: string;
AData: TvVectorialPage; ADoc: TvVectorialDocument): TvSVGPathList;
var
i: Integer;
X, Y, X2, Y2, X3, Y3: Double;
CurX, CurY: Double;
lCurTokenType, lLastCommandToken: TSVGTokenType;
lDebugStr: String;
lTmpTokenType: TSVGTokenType;
lIsFirstPathMove: Boolean;
lIsFirstPathMove, lLastPathClosed: Boolean;
begin
Result := TvSVGPathList.Create;
FSVGPathTokenizer.ClearTokens;
FSVGPathTokenizer.TokenizePathString(AStr);
//lDebugStr := FSVGPathTokenizer.DebugOutTokensAsString();
CurX := 0;
CurY := 0;
lIsFirstPathMove := true;
Result.IsFirstPathMove := true;
Result.Data := AData;
Result.Doc := ADoc;
lLastCommandToken := sttFloatValue;
AData.StartPath();
i := 0;
while i < FSVGPathTokenizer.Tokens.Count do
Result.CurTokenIndex := 0;
while Result.CurTokenIndex < FSVGPathTokenizer.Tokens.Count do
begin
lCurTokenType := FSVGPathTokenizer.Tokens.Items[i].TokenType;
Result.LastPathClosed := False;
lCurTokenType := FSVGPathTokenizer.Tokens.Items[Result.CurTokenIndex].TokenType;
if not (lCurTokenType = sttFloatValue) then
begin
lLastCommandToken := lCurTokenType;
ReadNextPathCommand(lCurTokenType, i, lIsFirstPathMove, CurX, CurY, AData, ADoc);
ReadNextPathCommand(lCurTokenType, Result, Result.CurX, Result.CurY);
end
// In this case we are getting a command without a starting letter
// It is a copy of the last one, or something related to it
@ -2341,15 +2380,16 @@ begin
if lLastCommandToken = sttMoveTo then lTmpTokenType := sttLineTo;
if lLastCommandToken = sttRelativeMoveTo then lTmpTokenType := sttRelativeLineTo;
// For bezier I checked that a sttBezierTo upon repetition expects a sttBezierTo
Dec(i);// because there is no command token in this command
ReadNextPathCommand(lTmpTokenType, i, lIsFirstPathMove, CurX, CurY, AData, ADoc);
Dec(Result.CurTokenIndex);// because there is no command token in this command
ReadNextPathCommand(lTmpTokenType, Result, Result.CurX, Result.CurY);
end;
end;
if not Result.LastPathClosed then
Result.Add(AData.EndPath(True));
end;
procedure TvSVGVectorialReader.ReadNextPathCommand(ACurTokenType: TSVGTokenType;
var i: Integer; var AIsFirstPathMove: Boolean; var CurX, CurY: Double; AData: TvVectorialPage;
ADoc: TvVectorialDocument);
APaths: TvSVGPathList; var CurX, CurY: Double);
var
X, Y, X2, Y2, X3, Y3, XQ, YQ, Xnew, Ynew, cx, cy, phi, tmp: Double;
LargeArcFlag, SweepFlag, LeftmostEllipse, ClockwiseArc: Boolean;
@ -2358,8 +2398,12 @@ var
lToken5Before, lToken7Before: TSVGTokenType;
lCorrectPreviousToken: Boolean;
lPrevRelative, lCurRelative: Boolean;
AData: TvVectorialPage;
i: Integer;
begin
lCurTokenType := ACurTokenType;
AData := APaths.Data;
i := APaths.CurTokenIndex;
// --------------
// Moves
// --------------
@ -2384,17 +2428,17 @@ begin
CurX := X;
CurY := Y;
end;
AData.AddMoveToPath(CurX, CurY);
APaths.Data.AddMoveToPath(CurX, CurY);
// Since there may be several subpolygons we must store the start point
// to close the subpolygon correctly later.
if AIsFirstPathMove then
if APaths.IsFirstPathMove then
begin
FPathStart.X := CurX;
FPathStart.Y := CurY;
AIsFirstPathMove := false;
FPathStart.X := APaths.CurX;
FPathStart.Y := APaths.CurY;
APaths.IsFirstPathMove := false;
end;
Inc(i, 3);
Inc(APaths.CurTokenIndex, 3);
end
// --------------
// Close Path
@ -2404,9 +2448,12 @@ begin
// Repeat the first point of the subpolygon
CurX := FPathStart.X;
CurY := FPathStart.Y;
AData.AddLineToPath(CurX, CurY);
APaths.Data.AddLineToPath(CurX, CurY);
APaths.LastPathClosed := True;
APaths.Add(AData.EndPath(True));
AData.StartPath();
Inc(i, 1);
Inc(APaths.CurTokenIndex, 1);
end
// --------------
// Lines
@ -2419,34 +2466,36 @@ begin
X := FSVGPathTokenizer.Tokens.Items[i+1].Value;
Y := FSVGPathTokenizer.Tokens.Items[i+2].Value;
if lCurTokenType = sttLineTo then
ConvertSVGCoordinatesToFPVCoordinates(AData, X,Y, CurX,CurY)
ConvertSVGCoordinatesToFPVCoordinates(APaths.Data, X,Y, CurX,CurY)
else
begin
ConvertSVGDeltaToFPVDelta(AData, X,Y, X,Y);
ConvertSVGDeltaToFPVDelta(APaths.Data, X,Y, X,Y);
CurX := CurX + X;
CurY := CurY + Y;
end;
inc(i, 3);
end else
if lCurTokenType in [sttHorzLineTo, sttVertLineTo] then
inc(APaths.CurTokenIndex, 3);
end
else if lCurTokenType in [sttHorzLineTo, sttVertLineTo] then
begin
tmp := FSVGPathTokenizer.Tokens.Items[i+1].Value;
ConvertSVGCoordinatesToFPVCoordinates(AData, tmp, tmp, X, Y);
ConvertSVGCoordinatesToFPVCoordinates(APaths.Data, tmp, tmp, X, Y);
if lCurTokenType = sttHorzLineTo then
CurX := X else
CurX := X
else
CurY := Y;
inc(i, 2);
inc(APaths.CurTokenIndex, 2);
end else
if lCurTokenType in [sttRelativeHorzLineTo, sttRelativeVertLineTo] then
begin
tmp := FSVGPathTokenizer.Tokens.Items[i+1].Value;
ConvertSVGDeltaToFPVDelta(AData, tmp, tmp, X, Y);
ConvertSVGDeltaToFPVDelta(APaths.Data, tmp, tmp, X, Y);
if lCurTokenType = sttRelativeHorzLineTo then
CurX := CurX + X else
CurX := CurX + X
else
CurY := CurY + Y;
inc(i, 2);
inc(APaths.CurTokenIndex, 2);
end;
AData.AddLineToPath(CurX, CurY);
APaths.Data.AddLineToPath(CurX, CurY);
end
// --------------
// Cubic Bezier
@ -2558,15 +2607,17 @@ begin
else
begin
if lPrevRelative then
AData.AddBezierToPath(X2 + CurX, Y2 + CurY, X3, Y3, X, Y) else
AData.AddBezierToPath(X2 + CurX, Y2 + CurY, X3, Y3, X, Y)
else
AData.AddBezierToPath(X2, Y2, X3, Y3, X, Y);
CurX := X;
CurY := Y;
end;
if lCurTokenType in [sttBezierTo, sttRelativeBezierTo] then
Inc(i, 7) else
Inc(i, 5);
Inc(APaths.CurTokenIndex, 7)
else
Inc(APaths.CurTokenIndex, 5);
end
// --------------
// Quadratic Bezier
@ -2581,8 +2632,8 @@ begin
// Careful that absolute coordinates require using ConvertSVGCoordinatesToFPVCoordinates
if lCurTokenType in [sttRelativeQuadraticBezierTo] then
begin
ConvertSVGDeltaToFPVDelta(AData, XQ, YQ, XQ, YQ);
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
ConvertSVGDeltaToFPVDelta(APaths.Data, XQ, YQ, XQ, YQ);
ConvertSVGDeltaToFPVDelta(APaths.Data, X, Y, X, Y);
XQ := XQ + CurX;
YQ := YQ + CurY;
@ -2591,8 +2642,8 @@ begin
end
else
begin
ConvertSVGCoordinatesToFPVCoordinates(AData, XQ, YQ, XQ, YQ);
ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y);
ConvertSVGCoordinatesToFPVCoordinates(APaths.Data, XQ, YQ, XQ, YQ);
ConvertSVGCoordinatesToFPVCoordinates(APaths.Data, X, Y, X, Y);
end;
// Convert quadratic to cubic bezier
@ -2606,11 +2657,11 @@ begin
X3 := X + (2/3) * (XQ-X);
Y3 := Y + (2/3) * (YQ-Y);
AData.AddBezierToPath(X2, Y2, X3, Y3, X, Y);
APaths.Data.AddBezierToPath(X2, Y2, X3, Y3, X, Y);
CurX := X;
CurY := Y;
Inc(i, 5);
Inc(APaths.CurTokenIndex, 5);
end
// --------------
// Elliptical arcs
@ -2699,11 +2750,11 @@ begin
end;
}
Inc(i, 8);
Inc(APaths.CurTokenIndex, 8);
end
else
begin
Inc(i);
Inc(APaths.CurTokenIndex);
end;
end;
@ -2875,6 +2926,7 @@ var
lAttrName: DOMString;
lCurNode: TDOMNode;
begin
Result := nil;
lBlock := TvBlock.Create(nil);
// pre-load attribute reader, to get the block name
@ -2949,13 +3001,12 @@ var
procedure ReadTextSpans(ACurNode: TDOMNode);
var
i,j: Integer;
j: Integer;
lCurNode: TDOMNode;
lTextStr: string;
lText: TvText;
lCText: TvCurvedText;
lInsertedEntity, lInsertedSubEntity: TvEntity;
s: String;
begin
lCurNode := ACurNode.FirstChild;
while lCurNode <> nil do