mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-05-04 13:43:48 +02:00
1782 lines
52 KiB
ObjectPascal
1782 lines
52 KiB
ObjectPascal
{
|
|
Reads an SVG Document
|
|
|
|
License: The same modified LGPL as the Free Pascal RTL
|
|
See the file COPYING.modifiedLGPL for more details
|
|
|
|
AUTHORS: Felipe Monteiro de Carvalho
|
|
}
|
|
unit svgvectorialreader;
|
|
|
|
{$mode objfpc}{$H+}
|
|
{$define SVG_MERGE_LAYER_STYLES}
|
|
|
|
interface
|
|
|
|
uses
|
|
Classes, SysUtils, math,
|
|
fpimage, fpcanvas, laz2_xmlread, laz2_dom, fgl,
|
|
fpvectorial, fpvutils, lazutf8, TypInfo;
|
|
|
|
type
|
|
TSVGTokenType = (
|
|
// moves
|
|
sttMoveTo, sttRelativeMoveTo,
|
|
// Close Path
|
|
sttClosePath,
|
|
// lines
|
|
sttLineTo, sttRelativeLineTo,
|
|
sttHorzLineTo, sttRelativeHorzLineTo, sttVertLineTo, sttRelativeVertLineTo,
|
|
// cubic beziers
|
|
sttBezierTo, sttRelativeBezierTo, sttSmoothBezierTo, sttRelativeSmoothBezierTo,
|
|
// quadratic beziers
|
|
sttQuadraticBezierTo, sttRelativeQuadraticBezierTo,
|
|
// Elliptic curves
|
|
sttEllipticArcTo, sttRelativeEllipticArcTo,
|
|
// numbers
|
|
sttFloatValue);
|
|
|
|
TSVGToken = class
|
|
TokenType: TSVGTokenType;
|
|
Value: Float;
|
|
end;
|
|
|
|
TSVGTokenList = specialize TFPGList<TSVGToken>;
|
|
|
|
{ TSVGPathTokenizer }
|
|
|
|
TSVGPathTokenizer = class
|
|
public
|
|
FPointSeparator, FCommaSeparator: TFormatSettings;
|
|
Tokens: TSVGTokenList;
|
|
constructor Create;
|
|
Destructor Destroy; override;
|
|
procedure AddToken(AStr: string);
|
|
procedure TokenizePathString(AStr: string);
|
|
function DebugOutTokensAsString: string;
|
|
end;
|
|
|
|
{ TvSVGVectorialReader }
|
|
|
|
TvSVGVectorialReader = class(TvCustomVectorialReader)
|
|
private
|
|
FPointSeparator, FCommaSeparator: TFormatSettings;
|
|
FSVGPathTokenizer: TSVGPathTokenizer;
|
|
FLayerStylesKeys, FLayerStylesValues: TFPList; // of TStringList;
|
|
function ReadSVGColor(AValue: string): TFPColor;
|
|
function ReadSVGStyle(AValue: string; ADestEntity: TvEntityWithPen; AUseFillAsPen: Boolean = False): TvSetPenBrushAndFontElements;
|
|
function ReadSVGStyleToStyleLists(AValue: string; AStyleKeys, AStyleValues: TStringList): TvSetPenBrushAndFontElements;
|
|
function ReadSVGPenStyleWithKeyAndValue(AKey, AValue: string; ADestEntity: TvEntityWithPen): TvSetPenBrushAndFontElements;
|
|
function ReadSVGBrushStyleWithKeyAndValue(AKey, AValue: string; ADestEntity: TvEntityWithPenAndBrush): TvSetPenBrushAndFontElements;
|
|
function ReadSVGFontStyleWithKeyAndValue(AKey, AValue: string; ADestEntity: TvEntityWithPenBrushAndFont): TvSetPenBrushAndFontElements;
|
|
function IsAttributeFromStyle(AStr: string): Boolean;
|
|
procedure ApplyLayerStyles(ADestEntity: TvEntity);
|
|
//
|
|
procedure ReadEntityFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
procedure ReadCircleFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
procedure ReadEllipseFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
procedure ReadLayerFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
procedure ReadLineFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
procedure ReadPathFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
procedure ReadPathFromString(AStr: string; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
procedure ReadNextPathCommand(ACurTokenType: TSVGTokenType; var i: Integer; var CurX, CurY: Double; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
procedure ReadPointsFromString(AStr: string; AData: TvVectorialPage; ADoc: TvVectorialDocument; AClosePath: Boolean);
|
|
procedure ReadPolyFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
procedure ReadRectFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
procedure ReadTextFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
procedure ReadUseFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
function StringWithUnitToFloat(AStr: string): Double;
|
|
procedure ConvertSVGCoordinatesToFPVCoordinates(
|
|
const AData: TvVectorialPage;
|
|
const ASrcX, ASrcY: Double; var ADestX, ADestY: Double);
|
|
procedure ConvertSVGDeltaToFPVDelta(
|
|
const AData: TvVectorialPage;
|
|
const ASrcX, ASrcY: Double; var ADestX, ADestY: Double);
|
|
public
|
|
{ General reading methods }
|
|
constructor Create; override;
|
|
Destructor Destroy; override;
|
|
procedure ReadFromStream(AStream: TStream; AData: TvVectorialDocument); override;
|
|
procedure ReadFromXML(Doc: TXMLDocument; AData: TvVectorialDocument);
|
|
end;
|
|
|
|
implementation
|
|
|
|
const
|
|
// SVG requires hardcoding a DPI value
|
|
|
|
// The Opera Browser and Inkscape use 90 DPI, so we follow that
|
|
|
|
// 1 Inch = 25.4 milimiters
|
|
// 90 inches per pixel = (1 / 90) * 25.4 = 0.2822
|
|
// FLOAT_MILIMETERS_PER_PIXEL = 0.3528; // DPI 72 = 1 / 72 inches per pixel
|
|
|
|
FLOAT_MILIMETERS_PER_PIXEL = 0.2822; // DPI 90 = 1 / 90 inches per pixel
|
|
FLOAT_PIXELS_PER_MILIMETER = 3.5433; // DPI 90 = 1 / 90 inches per pixel
|
|
|
|
{ TSVGPathTokenizer }
|
|
|
|
constructor TSVGPathTokenizer.Create;
|
|
begin
|
|
inherited Create;
|
|
|
|
FPointSeparator := DefaultFormatSettings;
|
|
FPointSeparator.DecimalSeparator := '.';
|
|
FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
|
|
|
|
Tokens := TSVGTokenList.Create;
|
|
end;
|
|
|
|
destructor TSVGPathTokenizer.Destroy;
|
|
begin
|
|
Tokens.Free;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TSVGPathTokenizer.AddToken(AStr: string);
|
|
var
|
|
lToken: TSVGToken;
|
|
lStr: string;
|
|
begin
|
|
lToken := TSVGToken.Create;
|
|
|
|
lStr := Trim(AStr);
|
|
if lStr = '' then Exit;
|
|
|
|
// Moves
|
|
if lStr[1] = 'M' then lToken.TokenType := sttMoveTo
|
|
else if lStr[1] = 'm' then lToken.TokenType := sttRelativeMoveTo
|
|
// Close Path
|
|
else if lStr[1] = 'Z' then lToken.TokenType := sttClosePath
|
|
else if lStr[1] = 'z' then lToken.TokenType := sttClosePath
|
|
// Lines
|
|
else if lStr[1] = 'L' then lToken.TokenType := sttLineTo
|
|
else if lStr[1] = 'l' then lToken.TokenType := sttRelativeLineTo
|
|
else if lStr[1] = 'H' then lToken.TokenType := sttHorzLineTo
|
|
else if lStr[1] = 'h' then lToken.TokenType := sttRelativeHorzLineTo
|
|
else if lStr[1] = 'V' then lToken.TokenType := sttVertLineTo
|
|
else if lStr[1] = 'v' then lToken.TokenType := sttRelativeVertLineTo
|
|
// cubic Bézier curve commands
|
|
else if lStr[1] = 'C' then lToken.TokenType := sttBezierTo
|
|
else if lStr[1] = 'c' then lToken.TokenType := sttRelativeBezierTo
|
|
else if lStr[1] = 'S' then lToken.TokenType := sttSmoothBezierTo
|
|
else if lStr[1] = 's' then lToken.TokenType := sttRelativeSmoothBezierTo
|
|
// quadratic beziers
|
|
else if lStr[1] = 'Q' then lToken.TokenType := sttQuadraticBezierTo
|
|
else if lStr[1] = 'q' then lToken.TokenType := sttRelativeQuadraticBezierTo
|
|
// Elliptic curves
|
|
else if lStr[1] = 'A' then lToken.TokenType := sttEllipticArcTo
|
|
else if lStr[1] = 'a' then lToken.TokenType := sttRelativeEllipticArcTo
|
|
else
|
|
begin
|
|
lToken.TokenType := sttFloatValue;
|
|
lToken.Value := StrToFloat(AStr, FPointSeparator);
|
|
end;
|
|
|
|
// Sometimes we get a command glued to a value, for example M150
|
|
if (lToken.TokenType <> sttFloatValue) and (Length(lStr) > 1) then
|
|
begin
|
|
Tokens.Add(lToken);
|
|
lToken.TokenType := sttFloatValue;
|
|
lStr := Copy(AStr, 2, Length(AStr));
|
|
lToken.Value := StrToFloat(lStr, FPointSeparator);
|
|
end;
|
|
|
|
Tokens.Add(lToken);
|
|
end;
|
|
|
|
procedure TSVGPathTokenizer.TokenizePathString(AStr: string);
|
|
const
|
|
Str_Space: Char = ' ';
|
|
Str_Comma: Char = ',';
|
|
var
|
|
i: Integer;
|
|
lTmpStr: string = '';
|
|
lState: Integer;
|
|
lFirstTmpStrChar, lCurChar: Char;
|
|
begin
|
|
lState := 0;
|
|
|
|
i := 1;
|
|
while i <= Length(AStr) do
|
|
begin
|
|
case lState of
|
|
0: // Adding to the tmp string
|
|
begin
|
|
lCurChar := AStr[i];
|
|
if lCurChar = Str_Space then
|
|
begin
|
|
lState := 1;
|
|
AddToken(lTmpStr);
|
|
lTmpStr := '';
|
|
end
|
|
else if lCurChar = Str_Comma then
|
|
begin
|
|
AddToken(lTmpStr);
|
|
lTmpStr := '';
|
|
end
|
|
else
|
|
begin
|
|
// Check for a break, from letter to number
|
|
if (Length(lTmpStr) >= 1) then
|
|
begin
|
|
lFirstTmpStrChar := lTmpStr[1];
|
|
if ((lFirstTmpStrChar in ['a'..'z', 'A'..'Z']) and not (lCurChar in ['a'..'z', 'A'..'Z'])) or
|
|
(not (lFirstTmpStrChar in ['a'..'z', 'A'..'Z']) and (lCurChar in ['a'..'z', 'A'..'Z'])) then
|
|
begin
|
|
AddToken(lTmpStr);
|
|
lTmpStr := '';
|
|
Continue;
|
|
end;
|
|
end;
|
|
|
|
lTmpStr := lTmpStr + lCurChar;
|
|
end;
|
|
|
|
Inc(i);
|
|
end;
|
|
1: // Removing spaces
|
|
begin
|
|
if AStr[i] <> Str_Space then lState := 0
|
|
else Inc(i);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// If there is a token still to be added, add it now
|
|
if (lState = 0) and (lTmpStr <> '') then AddToken(lTmpStr);
|
|
end;
|
|
|
|
function TSVGPathTokenizer.DebugOutTokensAsString: string;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
for i := 0 to Tokens.Count-1 do
|
|
Result := Result + GetEnumName(TypeInfo(TSVGTokenType), integer(Tokens.Items[i].TokenType))
|
|
+ Format('(%f) ', [Tokens.Items[i].Value]);
|
|
end;
|
|
|
|
{ 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>
|
|
}
|
|
|
|
{ TvSVGVectorialReader }
|
|
|
|
function TvSVGVectorialReader.ReadSVGColor(AValue: string): TFPColor;
|
|
var
|
|
lValue, lStr: string;
|
|
lStrings: TStringList;
|
|
i: Integer;
|
|
begin
|
|
Result := colBlack;
|
|
lValue := Trim(LowerCase(AValue));
|
|
|
|
// Support for rgb(255,255,0)
|
|
if (Length(lValue) > 3) and (Copy(lValue, 0, 3) = 'rgb') then
|
|
begin
|
|
lStrings := TStringList.Create;
|
|
try
|
|
lStr := Copy(lValue, 5, Length(lValue)-5);
|
|
lStrings.Delimiter := ',';
|
|
lStrings.DelimitedText := lStr;
|
|
if lStrings.Count = 3 then
|
|
begin
|
|
Result.Red := StrToInt(lStrings.Strings[0]) * $101;
|
|
Result.Green := StrToInt(lStrings.Strings[1]) * $101;
|
|
Result.Blue := StrToInt(lStrings.Strings[2]) * $101;
|
|
end
|
|
else
|
|
raise Exception.Create(Format('[TvSVGVectorialReader.ReadSVGColor] An unexpected number of channels was found: %d', [lStrings.Count]));
|
|
finally
|
|
lStrings.Free;
|
|
end;
|
|
Exit;
|
|
end;
|
|
|
|
// Support for RGB hex
|
|
// ex: #0000ff
|
|
// Another wierd valid variant: #000
|
|
if (Length(lValue) > 1) and (lValue[1] = '#') then
|
|
begin
|
|
lStr := Copy(lValue, 2, 2);
|
|
Result.Red := StrToInt('$'+lStr)*$101;
|
|
lStr := Copy(lValue, 4, 2);
|
|
if lStr = '' then Result.Green := 0
|
|
else Result.Green := StrToInt('$'+lStr)*$101;
|
|
lStr := Copy(lValue, 6, 2);
|
|
if lStr = '' then Result.Blue := 0
|
|
else Result.Blue := StrToInt('$'+lStr)*$101;
|
|
Exit;
|
|
end;
|
|
|
|
// Support for named colors
|
|
// List here: http://www.december.com/html/spec/colorsvghex.html
|
|
case lValue of
|
|
'black': Result := colBlack;
|
|
'navy': Result.Blue := $8080;
|
|
'darkblue':Result.Blue := $8B8B;
|
|
'mediumblue':Result.Blue := $CDCD;
|
|
'blue': Result := colBlue;
|
|
'darkgreen':Result.Green := $6464;
|
|
'green': Result.Green := $8080;
|
|
'teal':
|
|
begin
|
|
Result.Green := $8080;
|
|
Result.Blue := $8080;
|
|
end;
|
|
'darkcyan':
|
|
begin
|
|
Result.Green := $8B8B;
|
|
Result.Blue := $8B8B;
|
|
end;
|
|
'deepskyblue':
|
|
begin
|
|
Result.Green := $BFBF;
|
|
Result.Blue := $FFFF;
|
|
end;
|
|
'darkturquoise':
|
|
begin
|
|
Result.Green := $CECE;
|
|
Result.Blue := $D1D1;
|
|
end;
|
|
'mediumspringgreen':
|
|
begin
|
|
Result.Green := $FAFA;
|
|
Result.Blue := $9A9A;
|
|
end;
|
|
'lime': Result := colGreen;
|
|
'springgreen':
|
|
begin
|
|
Result.Green := $FFFF;
|
|
Result.Blue := $7F7F;
|
|
end;
|
|
'cyan': Result := colCyan;
|
|
'aqua': Result := colCyan;
|
|
'midnightblue':
|
|
begin
|
|
Result.Red := $1919;
|
|
Result.Green := $1919;
|
|
Result.Blue := $7070;
|
|
end;
|
|
'dodgerblue':
|
|
begin
|
|
Result.Red := $1E1E;
|
|
Result.Green := $9090;
|
|
Result.Blue := $FFFF;
|
|
end;
|
|
'lightseagreen':
|
|
begin
|
|
Result.Red := $2020;
|
|
Result.Green := $B2B2;
|
|
Result.Blue := $AAAA;
|
|
end;
|
|
'forestgreen':
|
|
begin
|
|
Result.Red := $2222;
|
|
Result.Green := $8B8B;
|
|
Result.Blue := $2222;
|
|
end;
|
|
'seagreen':
|
|
begin
|
|
Result.Red := $2E2E;
|
|
Result.Green := $8B8B;
|
|
Result.Blue := $5757;
|
|
end;
|
|
'darkslategray', 'darkslategrey':
|
|
begin
|
|
Result.Red := $2F2F;
|
|
Result.Green := $4F4F;
|
|
Result.Blue := $4F4F;
|
|
end;
|
|
'limegreen':
|
|
begin
|
|
Result.Red := $3232;
|
|
Result.Green := $CDCD;
|
|
Result.Blue := $3232;
|
|
end;
|
|
'mediumseagreen':
|
|
begin
|
|
Result.Red := $3C3C;
|
|
Result.Green := $CBCB;
|
|
Result.Blue := $7171;
|
|
end;
|
|
'turquoise':
|
|
begin
|
|
Result.Red := $4040;
|
|
Result.Green := $E0E0;
|
|
Result.Blue := $D0D0;
|
|
end;
|
|
'royalblue':
|
|
begin
|
|
Result.Red := $4141;
|
|
Result.Green := $6969;
|
|
Result.Blue := $E1E1;
|
|
end;
|
|
'steelblue':
|
|
begin
|
|
Result.Red := $4646;
|
|
Result.Green := $8282;
|
|
Result.Blue := $B4B4;
|
|
end;
|
|
'darkslateblue':
|
|
begin
|
|
Result.Red := $4848;
|
|
Result.Green := $3D3D;
|
|
Result.Blue := $8B8B;
|
|
end;
|
|
'mediumturquoise':
|
|
begin
|
|
Result.Red := $4848;
|
|
Result.Green := $D1D1;
|
|
Result.Blue := $CCCC;
|
|
end;
|
|
{
|
|
indigo #4B0082
|
|
darkolivegreen #556B2F cadetblue #5F9EA0
|
|
cornflowerblue #6495ED
|
|
mediumaquamarine #66CDAA dimgrey #696969
|
|
dimgray #696969
|
|
slateblue #6A5ACD olivedrab #6B8E23
|
|
slategrey #708090
|
|
slategray #708090 lightslategray(Hex3) #778899
|
|
lightslategrey(Hex3) #778899
|
|
mediumslateblue #7B68EE lawngreen #7CFC00
|
|
chartreuse #7FFF00
|
|
}
|
|
'aquamarine':
|
|
begin
|
|
Result.Red := $7F7F;
|
|
Result.Green := $FFFF;
|
|
Result.Blue := $D4D4;
|
|
end;
|
|
'maroon': Result.Red := $8080;
|
|
'purple': Result := colPurple;
|
|
'olive': Result := colOlive;
|
|
'gray', 'grey': Result := colGray;
|
|
'skyblue':
|
|
begin
|
|
Result.Red := $8787;
|
|
Result.Green := $CECE;
|
|
Result.Blue := $EBEB;
|
|
end;
|
|
'lightskyblue':
|
|
begin
|
|
Result.Red := $8787;
|
|
Result.Green := $CECE;
|
|
Result.Blue := $FAFA;
|
|
end;
|
|
'blueviolet':
|
|
begin
|
|
Result.Red := $8A8A;
|
|
Result.Green := $2B2B;
|
|
Result.Blue := $E2E2;
|
|
end;
|
|
'darkred': Result.Red := $8B8B;
|
|
'darkmagenta':
|
|
begin
|
|
Result.Red := $8B8B;
|
|
Result.Blue := $8B8B;
|
|
end;
|
|
{
|
|
saddlebrown #8B4513
|
|
darkseagreen #8FBC8F lightgreen #90EE90
|
|
mediumpurple #9370DB
|
|
darkviolet #9400D3 palegreen #98FB98
|
|
darkorchid #9932CC
|
|
yellowgreen #9ACD32 sienna #A0522D
|
|
brown #A52A2A
|
|
darkgray #A9A9A9 darkgrey #A9A9A9
|
|
lightblue #ADD8E6
|
|
greenyellow #ADFF2F paleturquoise #AFEEEE
|
|
lightsteelblue #B0C4DE
|
|
powderblue #B0E0E6 firebrick #B22222
|
|
darkgoldenrod #B8860B
|
|
mediumorchid #BA55D3 rosybrown #BC8F8F
|
|
darkkhaki #BDB76B
|
|
}
|
|
'silver': Result := colSilver;
|
|
'mediumvioletred':
|
|
begin
|
|
Result.Red := $C7C7;
|
|
Result.Green := $1515;
|
|
Result.Blue := $8585;
|
|
end;
|
|
'indianred':
|
|
begin
|
|
Result.Red := $CDCD;
|
|
Result.Green := $5C5C;
|
|
Result.Blue := $5C5C;
|
|
end;
|
|
'peru':
|
|
begin
|
|
Result.Red := $CDCD;
|
|
Result.Green := $8585;
|
|
Result.Blue := $3F3F;
|
|
end;
|
|
'chocolate':
|
|
begin
|
|
Result.Red := $D2D2;
|
|
Result.Green := $6969;
|
|
Result.Blue := $1E1E;
|
|
end;
|
|
{
|
|
tan #D2B48C
|
|
lightgray #D3D3D3 lightgrey #D3D3D3
|
|
thistle #D8BFD8
|
|
orchid #DA70D6 goldenrod #DAA520
|
|
palevioletred #DB7093
|
|
crimson #DC143C gainsboro #DCDCDC
|
|
plum #DDA0DD
|
|
burlywood #DEB887 lightcyan #E0FFFF
|
|
lavender #E6E6FA
|
|
}
|
|
'darksalmon':
|
|
begin
|
|
Result.Red := $E9E9;
|
|
Result.Green := $9696;
|
|
Result.Blue := $7A7A;
|
|
end;
|
|
'violet':
|
|
begin
|
|
Result.Red := $EEEE;
|
|
Result.Green := $8282;
|
|
Result.Blue := $EEEE;
|
|
end;
|
|
'palegoldenrod':
|
|
begin
|
|
Result.Red := $EEEE;
|
|
Result.Green := $E8E8;
|
|
Result.Blue := $AAAA;
|
|
end;
|
|
'lightcoral':
|
|
begin
|
|
Result.Red := $F0F0;
|
|
Result.Green := $8080;
|
|
Result.Blue := $8080;
|
|
end;
|
|
'khaki':
|
|
begin
|
|
Result.Red := $F0F0;
|
|
Result.Green := $E6E6;
|
|
Result.Blue := $8C8C;
|
|
end;
|
|
'aliceblue':
|
|
begin
|
|
Result.Red := $F0F0;
|
|
Result.Green := $F8F8;
|
|
Result.Blue := $FFFF;
|
|
end;
|
|
'honeydew':
|
|
begin
|
|
Result.Red := $F0F0;
|
|
Result.Green := $FFFF;
|
|
Result.Blue := $F0F0;
|
|
end;
|
|
'azure':
|
|
begin
|
|
Result.Red := $F0F0;
|
|
Result.Green := $FFFF;
|
|
Result.Blue := $FFFF;
|
|
end;
|
|
'sandybrown':
|
|
begin
|
|
Result.Red := $F4F4;
|
|
Result.Green := $A4A4;
|
|
Result.Blue := $6060;
|
|
end;
|
|
{
|
|
wheat #F5DEB3 beige #F5F5DC
|
|
whitesmoke #F5F5F5
|
|
mintcream #F5FFFA ghostwhite #F8F8FF
|
|
salmon #FA8072
|
|
antiquewhite #FAEBD7 linen #FAF0E6
|
|
lightgoldenrodyellow #FAFAD2
|
|
oldlace #FDF5E6
|
|
}
|
|
'red': Result := colRed;
|
|
'fuchsia': Result := colFuchsia;
|
|
'magenta': Result := colMagenta;
|
|
{ deeppink #FF1493
|
|
orangered #FF4500
|
|
tomato #FF6347 hotpink #FF69B4
|
|
coral #FF7F50
|
|
darkorange #FF8C00 lightsalmon #FFA07A
|
|
orange #FFA500
|
|
lightpink #FFB6C1 pink #FFC0CB
|
|
gold #FFD700
|
|
peachpuff #FFDAB9 navajowhite #FFDEAD
|
|
moccasin #FFE4B5
|
|
bisque #FFE4C4 mistyrose #FFE4E1
|
|
blanchedalmond #FFEBCD
|
|
papayawhip #FFEFD5 lavenderblush #FFF0F5
|
|
seashell #FFF5EE
|
|
cornsilk #FFF8DC lemonchiffon #FFFACD
|
|
floralwhite #FFFAF0
|
|
}
|
|
'snow':
|
|
begin
|
|
Result.Red := $FFFF;
|
|
Result.Green := $FAFA;
|
|
Result.Blue := $FAFA;
|
|
end;
|
|
'yellow': Result := colYellow;
|
|
'lightyellow':
|
|
begin
|
|
Result.Red := $FFFF;
|
|
Result.Green := $FEFE;
|
|
end;
|
|
'ivory':
|
|
begin
|
|
Result.Red := $FFFF;
|
|
Result.Green := $FFFF;
|
|
Result.Blue := $F0F0;
|
|
end;
|
|
'white': Result := colWhite;
|
|
end;
|
|
end;
|
|
|
|
// style="fill:none;stroke:black;stroke-width:3"
|
|
function TvSVGVectorialReader.ReadSVGStyle(AValue: string;
|
|
ADestEntity: TvEntityWithPen; AUseFillAsPen: Boolean = False): TvSetPenBrushAndFontElements;
|
|
var
|
|
lStr, lStyleKeyStr, lStyleValueStr: String;
|
|
lStrings: TStringList;
|
|
i, lPosEqual: Integer;
|
|
begin
|
|
Result := [];
|
|
if AValue = '' then Exit;
|
|
|
|
// Now split using ";" separator
|
|
lStrings := TStringList.Create;
|
|
try
|
|
lStrings.Delimiter := ';';
|
|
lStrings.DelimitedText := LowerCase(AValue);
|
|
for i := 0 to lStrings.Count-1 do
|
|
begin
|
|
lStr := lStrings.Strings[i];
|
|
lPosEqual := Pos(':', lStr);
|
|
lStyleKeyStr := Copy(lStr, 0, lPosEqual-1);
|
|
lStyleValueStr := Copy(lStr, lPosEqual+1, Length(lStr));
|
|
ReadSVGPenStyleWithKeyAndValue(lStyleKeyStr, lStyleValueStr, ADestEntity);
|
|
if AUseFillAsPen and (lStyleKeyStr = 'fill') then
|
|
Result := Result + ReadSVGPenStyleWithKeyAndValue('stroke', lStyleValueStr, ADestEntity)
|
|
else if ADestEntity is TvText then
|
|
Result := Result + ReadSVGFontStyleWithKeyAndValue(lStyleKeyStr, lStyleValueStr, ADestEntity as TvText)
|
|
else if ADestEntity is TvEntityWithPenAndBrush then
|
|
Result := Result + ReadSVGBrushStyleWithKeyAndValue(lStyleKeyStr, lStyleValueStr, ADestEntity as TvEntityWithPenAndBrush);
|
|
end;
|
|
finally
|
|
lStrings.Free;
|
|
end;
|
|
end;
|
|
|
|
// style="fill:none;stroke:black;stroke-width:3"
|
|
function TvSVGVectorialReader.ReadSVGStyleToStyleLists(AValue: string;
|
|
AStyleKeys, AStyleValues: TStringList): TvSetPenBrushAndFontElements;
|
|
var
|
|
lStr, lStyleKeyStr, lStyleValueStr: String;
|
|
lStrings: TStringList;
|
|
i, lPosEqual: Integer;
|
|
begin
|
|
Result := [];
|
|
if AValue = '' then Exit;
|
|
|
|
// Now split using ";" separator
|
|
lStrings := TStringList.Create;
|
|
try
|
|
lStrings.Delimiter := ';';
|
|
lStrings.DelimitedText := LowerCase(AValue);
|
|
for i := 0 to lStrings.Count-1 do
|
|
begin
|
|
lStr := lStrings.Strings[i];
|
|
lPosEqual := Pos(':', lStr);
|
|
lStyleKeyStr := Copy(lStr, 0, lPosEqual-1);
|
|
lStyleValueStr := Copy(lStr, lPosEqual+1, Length(lStr));
|
|
AStyleKeys.Add(lStyleKeyStr);
|
|
AStyleValues.Add(lStyleValueStr);
|
|
end;
|
|
finally
|
|
lStrings.Free;
|
|
end;
|
|
end;
|
|
|
|
function TvSVGVectorialReader.ReadSVGPenStyleWithKeyAndValue(AKey,
|
|
AValue: string; ADestEntity: TvEntityWithPen): TvSetPenBrushAndFontElements;
|
|
var
|
|
OldAlpha: Word;
|
|
begin
|
|
Result := [];
|
|
if AKey = 'stroke' then
|
|
begin
|
|
// We store and restore the old alpha to support the "-opacity" element
|
|
OldAlpha := ADestEntity.Pen.Color.Alpha;
|
|
if ADestEntity.Pen.Style = psClear then ADestEntity.Pen.Style := psSolid;
|
|
|
|
if AValue = 'none' then ADestEntity.Pen.Style := fpcanvas.psClear
|
|
else
|
|
begin
|
|
ADestEntity.Pen.Color := ReadSVGColor(AValue);
|
|
ADestEntity.Pen.Color.Alpha := OldAlpha;
|
|
end;
|
|
Result := Result + [spbfPenColor, spbfPenStyle];
|
|
end
|
|
else if AKey = 'stroke-width' then
|
|
begin
|
|
ADestEntity.Pen.Width := Round(StringWithUnitToFloat(AValue));
|
|
Result := Result + [spbfPenWidth];
|
|
end
|
|
else if AKey = 'stroke-opacity' then
|
|
begin
|
|
ADestEntity.Pen.Color.Alpha := StrToInt(AValue)*$101
|
|
end
|
|
else if AKey = 'stroke-linecap' then
|
|
begin
|
|
{case LowerCase(AValue) of
|
|
'butt':
|
|
'round':
|
|
'square': ADestEntity.Pen;
|
|
end;}
|
|
end;
|
|
end;
|
|
|
|
function TvSVGVectorialReader.ReadSVGBrushStyleWithKeyAndValue(AKey,
|
|
AValue: string; ADestEntity: TvEntityWithPenAndBrush): TvSetPenBrushAndFontElements;
|
|
var
|
|
OldAlpha: Word;
|
|
begin
|
|
Result := [];
|
|
if AKey = 'fill' then
|
|
begin
|
|
// We store and restore the old alpha to support the "-opacity" element
|
|
OldAlpha := ADestEntity.Brush.Color.Alpha;
|
|
if ADestEntity.Brush.Style = bsClear then ADestEntity.Brush.Style := bsSolid;
|
|
|
|
if AValue = 'none' then ADestEntity.Brush.Style := fpcanvas.bsClear
|
|
else
|
|
begin
|
|
ADestEntity.Brush.Color := ReadSVGColor(AValue);
|
|
ADestEntity.Brush.Color.Alpha := OldAlpha;
|
|
end;
|
|
|
|
Result := Result + [spbfBrushColor, spbfBrushStyle];
|
|
end
|
|
else if AKey = 'fill-opacity' then
|
|
ADestEntity.Brush.Color.Alpha := StrToInt(AValue)*$101;
|
|
end;
|
|
|
|
function TvSVGVectorialReader.ReadSVGFontStyleWithKeyAndValue(AKey,
|
|
AValue: string; ADestEntity: TvEntityWithPenBrushAndFont): TvSetPenBrushAndFontElements;
|
|
begin
|
|
Result := [];
|
|
// SVG text uses "fill" to indicate the pen color of the text, very unintuitive as
|
|
// "fill" is usually for brush in other elements
|
|
if AKey = 'fill' then
|
|
begin
|
|
ADestEntity.Font.Color := ReadSVGColor(AValue);
|
|
Result := Result + [spbfFontColor];
|
|
end
|
|
// But sometimes SVG also uses stroke! Oh no...
|
|
else if AKey = 'stroke' then
|
|
begin
|
|
ADestEntity.Font.Color := ReadSVGColor(AValue);
|
|
Result := Result + [spbfFontColor];
|
|
end
|
|
else if AKey = 'fill-opacity' then
|
|
ADestEntity.Font.Color.Alpha := StrToInt(AValue)*$101
|
|
else if AKey = 'font-size' then
|
|
begin
|
|
ADestEntity.Font.Size := Round(StringWithUnitToFloat(AValue));
|
|
Result := Result + [spbfFontSize];
|
|
end
|
|
else if AKey = 'font-family' then
|
|
ADestEntity.Font.Name := AValue
|
|
else if AKey = 'font-weight' then
|
|
begin
|
|
case LowerCase(AValue) of
|
|
'bold': ADestEntity.Font.Bold := True;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TvSVGVectorialReader.IsAttributeFromStyle(AStr: string): Boolean;
|
|
begin
|
|
Result := (AStr = 'stroke') or (AStr = 'stroke-width') or
|
|
(AStr = 'stroke-dasharray') or (AStr = 'stroke-opacity') or
|
|
(AStr = 'stroke-linecap') or
|
|
// brush
|
|
(AStr = 'fill') or (AStr = 'fill-opacity') or
|
|
// font
|
|
(AStr = 'font-size') or (AStr = 'fill-family') or
|
|
(AStr = 'font-weight');
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ApplyLayerStyles(ADestEntity: TvEntity);
|
|
var
|
|
lStringsKeys, lStringsValues: TStringList;
|
|
i, j: Integer;
|
|
begin
|
|
for i := 0 to FLayerStylesKeys.Count-1 do
|
|
begin
|
|
lStringsKeys := TStringList(FLayerStylesKeys.Items[i]);
|
|
lStringsValues := TStringList(FLayerStylesValues.Items[i]);
|
|
for j := 0 to lStringsKeys.Count-1 do
|
|
begin
|
|
if ADestEntity is TvEntityWithPen then
|
|
ReadSVGPenStyleWithKeyAndValue(lStringsKeys.Strings[j], lStringsValues.Strings[j], ADestEntity as TvEntityWithPen);
|
|
if ADestEntity is TvEntityWithPenAndBrush then
|
|
ReadSVGBrushStyleWithKeyAndValue(lStringsKeys.Strings[j], lStringsValues.Strings[j], ADestEntity as TvEntityWithPenAndBrush);
|
|
if ADestEntity is TvEntityWithPenBrushAndFont then
|
|
ReadSVGFontStyleWithKeyAndValue(lStringsKeys.Strings[j], lStringsValues.Strings[j], ADestEntity as TvEntityWithPenBrushAndFont);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadEntityFromNode(ANode: TDOMNode;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
var
|
|
lEntityName: DOMString;
|
|
begin
|
|
lEntityName := LowerCase(ANode.NodeName);
|
|
case lEntityName of
|
|
'circle': ReadCircleFromNode(ANode, AData, ADoc);
|
|
'ellipse': ReadEllipseFromNode(ANode, AData, ADoc);
|
|
'g': ReadLayerFromNode(ANode, AData, ADoc);
|
|
'line': ReadLineFromNode(ANode, AData, ADoc);
|
|
'path': ReadPathFromNode(ANode, AData, ADoc);
|
|
'polygon', 'polyline': ReadPolyFromNode(ANode, AData, ADoc);
|
|
'rect': ReadRectFromNode(ANode, AData, ADoc);
|
|
'text': ReadTextFromNode(ANode, AData, ADoc);
|
|
'use': ReadUseFromNode(ANode, AData, ADoc);
|
|
end;
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadCircleFromNode(ANode: TDOMNode;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
var
|
|
cx, cy, cr, dtmp: double;
|
|
lCircle: TvCircle;
|
|
i: Integer;
|
|
lNodeName: DOMString;
|
|
begin
|
|
cx := 0.0;
|
|
cy := 0.0;
|
|
cr := 0.0;
|
|
|
|
lCircle := TvCircle.Create;
|
|
// SVG entities start without any pen drawing, but with a black brush
|
|
lCircle.Pen.Style := psClear;
|
|
lCircle.Brush.Style := bsSolid;
|
|
lCircle.Brush.Color := colBlack;
|
|
|
|
// read the attributes
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'cx' then
|
|
cx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'cy' then
|
|
cy := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'r' then
|
|
cr := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'id' then
|
|
lCircle.Name := UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'style' then
|
|
ReadSVGStyle(ANode.Attributes.Item[i].NodeValue, lCircle)
|
|
else if IsAttributeFromStyle(lNodeName) then
|
|
begin
|
|
ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lCircle);
|
|
ReadSVGBrushStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lCircle);
|
|
end;
|
|
end;
|
|
|
|
ConvertSVGDeltaToFPVDelta(
|
|
AData, cx, cy, lCircle.X, lCircle.Y);
|
|
ConvertSVGDeltaToFPVDelta(
|
|
AData, cr, 0, lCircle.Radius, dtmp);
|
|
|
|
AData.AddEntity(lCircle);
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadEllipseFromNode(ANode: TDOMNode;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
var
|
|
cx, cy, crx, cry: double;
|
|
lEllipse: TvEllipse;
|
|
i: Integer;
|
|
lNodeName: DOMString;
|
|
begin
|
|
cx := 0.0;
|
|
cy := 0.0;
|
|
crx := 0.0;
|
|
cry := 0.0;
|
|
|
|
lEllipse := TvEllipse.Create;
|
|
// SVG entities start without any pen drawing, but with a black brush
|
|
lEllipse.Pen.Style := psClear;
|
|
lEllipse.Brush.Style := bsSolid;
|
|
lEllipse.Brush.Color := colBlack;
|
|
|
|
// read the attributes
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'cx' then
|
|
cx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'cy' then
|
|
cy := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'rx' then
|
|
crx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'ry' then
|
|
cry := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'id' then
|
|
lEllipse.Name := UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'style' then
|
|
ReadSVGStyle(ANode.Attributes.Item[i].NodeValue, lEllipse)
|
|
else if IsAttributeFromStyle(lNodeName) then
|
|
begin
|
|
ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lEllipse);
|
|
ReadSVGBrushStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lEllipse);
|
|
end;
|
|
end;
|
|
|
|
ConvertSVGDeltaToFPVDelta(
|
|
AData, cx, cy, lEllipse.X, lEllipse.Y);
|
|
ConvertSVGDeltaToFPVDelta(
|
|
AData, crx, cry, lEllipse.HorzHalfAxis, lEllipse.VertHalfAxis);
|
|
|
|
AData.AddEntity(lEllipse);
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadLayerFromNode(ANode: TDOMNode;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
var
|
|
lNodeName: DOMString;
|
|
lLayerName: string = '';
|
|
lCurNode, lLayerNameNode: TDOMNode;
|
|
lLayer, lParentLayer: TvLayer;
|
|
i: Integer;
|
|
{$ifdef SVG_MERGE_LAYER_STYLES}
|
|
lLayerStyleKeys, lLayerStyleValues: TStringList;
|
|
{$endif}
|
|
begin
|
|
// Store the style of this layer in the list
|
|
{$ifdef SVG_MERGE_LAYER_STYLES}
|
|
lLayerStyleKeys := TStringList.Create;
|
|
lLayerStyleValues := TStringList.Create;
|
|
FLayerStylesKeys.Add(lLayerStyleKeys);
|
|
FLayerStylesValues.Add(lLayerStyleValues);
|
|
{$endif}
|
|
|
|
// first attribute reader, there is a second one
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'id' then
|
|
lLayerName := UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue);
|
|
end;
|
|
|
|
lParentLayer := AData.GetCurrentLayer();
|
|
lLayer := AData.AddLayerAndSetAsCurrent(lLayerName);
|
|
|
|
// attribute reading again after getting the object
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'style' then
|
|
begin
|
|
{$ifdef SVG_MERGE_LAYER_STYLES}
|
|
ReadSVGStyleToStyleLists(ANode.Attributes.Item[i].NodeValue, lLayerStyleKeys, lLayerStyleValues);
|
|
{$else}
|
|
lLayer.SetPenBrushAndFontElements += ReadSVGStyle(ANode.Attributes.Item[i].NodeValue, lLayer)
|
|
{$endif}
|
|
end
|
|
else if IsAttributeFromStyle(lNodeName) then
|
|
begin
|
|
{$ifdef SVG_MERGE_LAYER_STYLES}
|
|
lLayerStyleKeys.Add(lNodeName);
|
|
lLayerStyleValues.Add(UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue));
|
|
{$else}
|
|
lLayer.SetPenBrushAndFontElements += ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lLayer);
|
|
lLayer.SetPenBrushAndFontElements += ReadSVGBrushStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lLayer);
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
lCurNode := ANode.FirstChild;
|
|
while Assigned(lCurNode) do
|
|
begin
|
|
ReadEntityFromNode(lCurNode, AData, ADoc);
|
|
lCurNode := lCurNode.NextSibling;
|
|
end;
|
|
|
|
{$ifdef SVG_MERGE_LAYER_STYLES}
|
|
// Now remove the style from this layer
|
|
FLayerStylesKeys.Remove(lLayerStyleKeys);
|
|
lLayerStyleKeys.Free;
|
|
FLayerStylesValues.Remove(lLayerStyleValues);
|
|
lLayerStyleValues.Free;
|
|
{$endif}
|
|
|
|
// Set the current layer to the parent node,
|
|
// or else items read next will be put as children of this layer
|
|
AData.SetCurrentLayer(lParentLayer);
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadLineFromNode(ANode: TDOMNode;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
var
|
|
x1, y1, x2, y2: double;
|
|
vx1, vy1, vx2, vy2: double;
|
|
i: Integer;
|
|
lNodeName: DOMString;
|
|
lPath: TPath;
|
|
lStyleStr, lStrokeStr, lStrokeWidthStr: DOMString;
|
|
begin
|
|
x1 := 0.0;
|
|
y1 := 0.0;
|
|
x2 := 0.0;
|
|
y2 := 0.0;
|
|
|
|
// read the attributes
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'x1' then
|
|
x1 := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'y1' then
|
|
y1 := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'x2' then
|
|
x2 := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'y2' then
|
|
y2 := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'style' then
|
|
lStyleStr := ANode.Attributes.Item[i].NodeValue
|
|
else if lNodeName = 'stroke' then
|
|
lStrokeStr := ANode.Attributes.Item[i].NodeValue
|
|
else if lNodeName = 'stroke-width' then
|
|
lStrokeWidthStr := ANode.Attributes.Item[i].NodeValue;
|
|
end;
|
|
|
|
ConvertSVGCoordinatesToFPVCoordinates(
|
|
AData, x1, y1, vx1, vy1);
|
|
ConvertSVGCoordinatesToFPVCoordinates(
|
|
AData, x2, y2, vx2, vy2);
|
|
|
|
AData.StartPath();
|
|
AData.AddMoveToPath(vx1, vy1);
|
|
AData.AddLineToPath(vx2, vy2);
|
|
lPath := AData.EndPath();
|
|
// Add the pen/brush
|
|
ReadSVGStyle(lStyleStr, lPath);
|
|
ReadSVGStyle(lStrokeStr, lPath);
|
|
ReadSVGStyle(lStrokeWidthStr, lPath);
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadPathFromNode(ANode: TDOMNode;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
var
|
|
lNodeName, lDStr: WideString;
|
|
i: Integer;
|
|
lPath: TPath;
|
|
begin
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'd' then
|
|
lDStr := ANode.Attributes.Item[i].NodeValue;
|
|
end;
|
|
|
|
AData.StartPath();
|
|
ReadPathFromString(UTF8Encode(lDStr), AData, ADoc);
|
|
lPath := AData.EndPath();
|
|
// Add default SVG pen/brush
|
|
lPath.Pen.Style := psClear;
|
|
lPath.Brush.Color := colBlack;
|
|
lPath.Brush.Style := bsSolid;
|
|
// Apply the layer style
|
|
ApplyLayerStyles(lPath);
|
|
// Add the pen/brush/name
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'id' then
|
|
lPath.Name := UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'style' then
|
|
ReadSVGStyle(ANode.Attributes.Item[i].NodeValue, lPath)
|
|
else if IsAttributeFromStyle(lNodeName) then
|
|
begin
|
|
ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
|
|
ReadSVGBrushStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// Documentation: http://www.w3.org/TR/SVG/paths.html
|
|
procedure TvSVGVectorialReader.ReadPathFromString(AStr: string;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
var
|
|
i: Integer;
|
|
X, Y, X2, Y2, X3, Y3: Double;
|
|
CurX, CurY: Double;
|
|
lCurTokenType, lLastCommandToken: TSVGTokenType;
|
|
lDebugStr: String;
|
|
lTmpTokenType: TSVGTokenType;
|
|
begin
|
|
FSVGPathTokenizer.Tokens.Clear;
|
|
FSVGPathTokenizer.TokenizePathString(AStr);
|
|
//lDebugStr := FSVGPathTokenizer.DebugOutTokensAsString();
|
|
CurX := 0;
|
|
CurY := 0;
|
|
lLastCommandToken := sttFloatValue;
|
|
|
|
i := 0;
|
|
while i < FSVGPathTokenizer.Tokens.Count do
|
|
begin
|
|
lCurTokenType := FSVGPathTokenizer.Tokens.Items[i].TokenType;
|
|
if not (lCurTokenType = sttFloatValue) then
|
|
begin
|
|
lLastCommandToken := lCurTokenType;
|
|
ReadNextPathCommand(lCurTokenType, i, CurX, CurY, AData, ADoc);
|
|
end
|
|
else
|
|
begin
|
|
lTmpTokenType := lLastCommandToken;
|
|
if lLastCommandToken = sttMoveTo then lTmpTokenType := sttLineTo;
|
|
if lLastCommandToken = sttRelativeMoveTo then lTmpTokenType := sttRelativeLineTo;
|
|
Dec(i);// because there is command token
|
|
ReadNextPathCommand(lTmpTokenType, i, CurX, CurY, AData, ADoc);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadNextPathCommand(ACurTokenType: TSVGTokenType;
|
|
var i: Integer; var CurX, CurY: Double; AData: TvVectorialPage;
|
|
ADoc: TvVectorialDocument);
|
|
var
|
|
X, Y, X2, Y2, X3, Y3: Double;
|
|
lCurTokenType: TSVGTokenType;
|
|
lDebugStr: String;
|
|
begin
|
|
lCurTokenType := ACurTokenType;
|
|
// --------------
|
|
// Moves
|
|
// --------------
|
|
if lCurTokenType in [sttMoveTo, sttRelativeMoveTo] then
|
|
begin
|
|
X := FSVGPathTokenizer.Tokens.Items[i+1].Value;
|
|
Y := FSVGPathTokenizer.Tokens.Items[i+2].Value;
|
|
ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y);
|
|
|
|
// take care of relative or absolute
|
|
if lCurTokenType = sttRelativeMoveTo then
|
|
begin
|
|
CurX := CurX + X;
|
|
CurY := CurY + Y;
|
|
end
|
|
else
|
|
begin
|
|
CurX := X;
|
|
CurY := Y;
|
|
end;
|
|
AData.AddMoveToPath(CurX, CurY);
|
|
|
|
Inc(i, 3);
|
|
end
|
|
// --------------
|
|
// Close Path
|
|
// --------------
|
|
else if lCurTokenType = sttClosePath then
|
|
begin
|
|
// Get the first point
|
|
AData.GetTmpPathStartPos(X, Y);
|
|
|
|
// And repeat it
|
|
CurX := X;
|
|
CurY := Y;
|
|
AData.AddLineToPath(CurX, CurY);
|
|
|
|
Inc(i, 3);
|
|
end
|
|
// --------------
|
|
// Lines
|
|
// --------------
|
|
else if lCurTokenType in [sttLineTo, sttRelativeLineTo, sttHorzLineTo,
|
|
sttRelativeHorzLineTo, sttVertLineTo, sttRelativeVertLineTo] then
|
|
begin
|
|
X := FSVGPathTokenizer.Tokens.Items[i+1].Value;
|
|
if not (lCurTokenType in [sttHorzLineTo, sttRelativeHorzLineTo, sttVertLineTo, sttRelativeVertLineTo]) then
|
|
Y := FSVGPathTokenizer.Tokens.Items[i+2].Value;
|
|
|
|
// "l" LineTo uses relative coordenates in SVG
|
|
if lCurTokenType in [sttRelativeLineTo, sttRelativeHorzLineTo, sttRelativeVertLineTo] then
|
|
begin
|
|
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
|
|
CurX := CurX + X;
|
|
CurY := CurY + Y;
|
|
end
|
|
else
|
|
begin
|
|
ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y);
|
|
CurX := X;
|
|
CurY := Y;
|
|
end;
|
|
|
|
// horizontal and vertical line corrections
|
|
if lCurTokenType in [sttHorzLineTo, sttRelativeHorzLineTo] then
|
|
Y := 0
|
|
else if lCurTokenType in [sttVertLineTo, sttRelativeVertLineTo] then
|
|
begin
|
|
Y := X;
|
|
X := 0;
|
|
end;
|
|
|
|
AData.AddLineToPath(CurX, CurY);
|
|
|
|
if not (lCurTokenType in [sttHorzLineTo, sttRelativeHorzLineTo, sttVertLineTo, sttRelativeVertLineTo]) then
|
|
Inc(i, 3)
|
|
else Inc(i, 2);
|
|
end
|
|
// --------------
|
|
// Cubic Bezier
|
|
// --------------
|
|
else if lCurTokenType in [sttBezierTo, sttRelativeBezierTo,
|
|
sttSmoothBezierTo, sttRelativeSmoothBezierTo] then
|
|
begin
|
|
if lCurTokenType in [sttBezierTo, sttRelativeBezierTo] then
|
|
begin
|
|
X2 := FSVGPathTokenizer.Tokens.Items[i+1].Value;
|
|
Y2 := FSVGPathTokenizer.Tokens.Items[i+2].Value;
|
|
X3 := FSVGPathTokenizer.Tokens.Items[i+3].Value;
|
|
Y3 := FSVGPathTokenizer.Tokens.Items[i+4].Value;
|
|
X := FSVGPathTokenizer.Tokens.Items[i+5].Value;
|
|
Y := FSVGPathTokenizer.Tokens.Items[i+6].Value;
|
|
end
|
|
else
|
|
begin
|
|
X2 := CurX;
|
|
Y2 := CurY;
|
|
X3 := FSVGPathTokenizer.Tokens.Items[i+1].Value;
|
|
Y3 := FSVGPathTokenizer.Tokens.Items[i+2].Value;
|
|
X := FSVGPathTokenizer.Tokens.Items[i+3].Value;
|
|
Y := FSVGPathTokenizer.Tokens.Items[i+4].Value;
|
|
end;
|
|
|
|
// Careful that absolute coordinates require using ConvertSVGCoordinatesToFPVCoordinates
|
|
if lCurTokenType in [sttRelativeBezierTo, sttRelativeSmoothBezierTo] then
|
|
begin
|
|
ConvertSVGDeltaToFPVDelta(AData, X2, Y2, X2, Y2);
|
|
ConvertSVGDeltaToFPVDelta(AData, X3, Y3, X3, Y3);
|
|
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
|
|
end
|
|
else
|
|
begin
|
|
ConvertSVGCoordinatesToFPVCoordinates(AData, X2, Y2, X2, Y2);
|
|
ConvertSVGCoordinatesToFPVCoordinates(AData, X3, Y3, X3, Y3);
|
|
ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y);
|
|
end;
|
|
|
|
if lCurTokenType = sttRelativeBezierTo then
|
|
begin
|
|
AData.AddBezierToPath(X2 + CurX, Y2 + CurY, X3 + CurX, Y3 + CurY, X + CurX, Y + CurY);
|
|
CurX := CurX + X;
|
|
CurY := CurY + Y;
|
|
end
|
|
else
|
|
begin
|
|
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);
|
|
end
|
|
// --------------
|
|
// Quadratic Bezier
|
|
// --------------
|
|
else if lCurTokenType in [sttQuadraticBezierTo, sttRelativeQuadraticBezierTo] then
|
|
begin
|
|
X2 := FSVGPathTokenizer.Tokens.Items[i+1].Value;
|
|
Y2 := FSVGPathTokenizer.Tokens.Items[i+2].Value;
|
|
X := FSVGPathTokenizer.Tokens.Items[i+3].Value;
|
|
Y := FSVGPathTokenizer.Tokens.Items[i+4].Value;
|
|
|
|
// Careful that absolute coordinates require using ConvertSVGCoordinatesToFPVCoordinates
|
|
if lCurTokenType in [sttRelativeQuadraticBezierTo] then
|
|
begin
|
|
ConvertSVGDeltaToFPVDelta(AData, X2, Y2, X2, Y2);
|
|
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
|
|
end
|
|
else
|
|
begin
|
|
ConvertSVGCoordinatesToFPVCoordinates(AData, X2, Y2, X2, Y2);
|
|
ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y);
|
|
end;
|
|
|
|
if lCurTokenType = sttRelativeQuadraticBezierTo then
|
|
begin
|
|
AData.AddBezierToPath(X2 + CurX, Y2 + CurY, X2 + CurX, Y2 + CurY, X + CurX, Y + CurY);
|
|
CurX := CurX + X;
|
|
CurY := CurY + Y;
|
|
end
|
|
else
|
|
begin
|
|
AData.AddBezierToPath(X2, Y2, X2, Y2, X, Y);
|
|
CurX := X;
|
|
CurY := Y;
|
|
end;
|
|
|
|
Inc(i, 5);
|
|
end
|
|
// --------------
|
|
// Elliptical arcs
|
|
// --------------
|
|
else if lCurTokenType in [sttEllipticArcTo, sttRelativeEllipticArcTo] then
|
|
begin
|
|
{X2 := FSVGPathTokenizer.Tokens.Items[i+1].Value;
|
|
Y2 := FSVGPathTokenizer.Tokens.Items[i+2].Value;
|
|
X := FSVGPathTokenizer.Tokens.Items[i+3].Value;
|
|
Y := FSVGPathTokenizer.Tokens.Items[i+4].Value;
|
|
|
|
// Careful that absolute coordinates require using ConvertSVGCoordinatesToFPVCoordinates
|
|
if lCurTokenType in [sttRelativeQuadraticBezierTo] then
|
|
begin
|
|
ConvertSVGDeltaToFPVDelta(AData, X2, Y2, X2, Y2);
|
|
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
|
|
end
|
|
else
|
|
begin
|
|
ConvertSVGCoordinatesToFPVCoordinates(AData, X2, Y2, X2, Y2);
|
|
ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y);
|
|
end;
|
|
|
|
if lCurTokenType = sttRelativeQuadraticBezierTo then
|
|
begin
|
|
AData.AddBezierToPath(X2 + CurX, Y2 + CurY, X2 + CurX, Y2 + CurY, X + CurX, Y + CurY);
|
|
CurX := CurX + X;
|
|
CurY := CurY + Y;
|
|
end
|
|
else
|
|
begin
|
|
AData.AddBezierToPath(X2, Y2, X2, Y2, X, Y);
|
|
CurX := X;
|
|
CurY := Y;
|
|
end; }
|
|
|
|
Inc(i, 8);
|
|
end
|
|
else
|
|
begin
|
|
Inc(i);
|
|
end;
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadPointsFromString(AStr: string;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument; AClosePath: Boolean);
|
|
var
|
|
i: Integer;
|
|
X, Y: Double;
|
|
FirstPtX, FirstPtY, CurX, CurY: Double;
|
|
begin
|
|
FSVGPathTokenizer.Tokens.Clear;
|
|
FSVGPathTokenizer.TokenizePathString(AStr);
|
|
CurX := 0;
|
|
CurY := 0;
|
|
|
|
if FSVGPathTokenizer.Tokens.Count <= 2 then
|
|
raise Exception.Create('[TvSVGVectorialReader.ReadPointsFromString] There are too few points in the element');
|
|
|
|
// The first point
|
|
CurX := FSVGPathTokenizer.Tokens.Items[0].Value;
|
|
CurY := FSVGPathTokenizer.Tokens.Items[1].Value;
|
|
ConvertSVGCoordinatesToFPVCoordinates(AData, CurX, CurY, CurX, CurY);
|
|
FirstPtX := CurX;
|
|
FirstPtY := CurY;
|
|
AData.AddMoveToPath(CurX, CurY);
|
|
|
|
// Now all other points
|
|
i := 2;
|
|
while i < FSVGPathTokenizer.Tokens.Count do
|
|
begin
|
|
X := FSVGPathTokenizer.Tokens.Items[i].Value;
|
|
Y := FSVGPathTokenizer.Tokens.Items[i+1].Value;
|
|
ConvertSVGDeltaToFPVDelta(AData, X, Y, CurX, CurY);
|
|
AData.AddLineToPath(CurX, CurY);
|
|
|
|
Inc(i, 2);
|
|
end;
|
|
|
|
// and if we want, close the path
|
|
if AClosePath then
|
|
AData.AddLineToPath(FirstPtX, FirstPtY);
|
|
end;
|
|
|
|
// polygon and polyline are very similar
|
|
procedure TvSVGVectorialReader.ReadPolyFromNode(ANode: TDOMNode;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
var
|
|
lPointsStr: string = '';
|
|
i: Integer;
|
|
lNodeName: DOMString;
|
|
lPath: TPath;
|
|
lIsPolygon: Boolean = False;
|
|
begin
|
|
lIsPolygon := LowerCase(ANode.NodeName) = 'polygon';
|
|
|
|
// first get the points
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'points' then
|
|
lPointsStr := ANode.Attributes.Item[i].NodeValue;
|
|
end;
|
|
|
|
AData.StartPath();
|
|
ReadPointsFromString(lPointsStr, AData, ADoc, lIsPolygon);
|
|
lPath := AData.EndPath();
|
|
|
|
// now read the other attributes
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'id' then
|
|
lPath.Name := UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'style' then
|
|
ReadSVGStyle(ANode.Attributes.Item[i].NodeValue, lPath)
|
|
else if IsAttributeFromStyle(lNodeName) then
|
|
begin
|
|
ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
|
|
ReadSVGBrushStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lPath);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
// <rect width="90" height="90" stroke="green" stroke-width="3" fill="yellow" filter="url(#f1)" />
|
|
procedure TvSVGVectorialReader.ReadRectFromNode(ANode: TDOMNode;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
var
|
|
lx, ly, cx, cy, lrx, lry: double;
|
|
lRect: TvRectangle;
|
|
i: Integer;
|
|
lNodeName: DOMString;
|
|
begin
|
|
lx := 0.0;
|
|
ly := 0.0;
|
|
cx := 0.0;
|
|
cy := 0.0;
|
|
lrx := 0.0;
|
|
lry := 0.0;
|
|
|
|
lRect := TvRectangle.Create;
|
|
// SVG entities start without any pen drawing, but with a black brush
|
|
lRect.Pen.Style := psClear;
|
|
lRect.Brush.Style := bsSolid;
|
|
lRect.Brush.Color := colBlack;
|
|
|
|
// read the attributes
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'x' then
|
|
lx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'y' then
|
|
ly := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'rx' then
|
|
lrx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'ry' then
|
|
lry := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'width' then
|
|
cx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'height' then
|
|
cy := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'id' then
|
|
lRect.Name := UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'style' then
|
|
ReadSVGStyle(ANode.Attributes.Item[i].NodeValue, lRect)
|
|
else if IsAttributeFromStyle(lNodeName) then
|
|
begin
|
|
ReadSVGPenStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lRect);
|
|
ReadSVGBrushStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lRect);
|
|
end;
|
|
end;
|
|
|
|
ConvertSVGDeltaToFPVDelta(
|
|
AData, lx, ly, lRect.X, lRect.Y);
|
|
ConvertSVGDeltaToFPVDelta(
|
|
AData, cx, cy, lRect.CX, lRect.CY);
|
|
ConvertSVGDeltaToFPVDelta(
|
|
AData, lrx, lry, lRect.RX, lRect.RY);
|
|
lRect.RX := Abs(lRect.RX) * 2;
|
|
lRect.RY := Abs(lRect.RY) * 2;
|
|
|
|
AData.AddEntity(lRect);
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadTextFromNode(ANode: TDOMNode;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
var
|
|
lTextStr: string = '';
|
|
lx, ly: double;
|
|
lText: TvText;
|
|
i: Integer;
|
|
lNodeName: DOMString;
|
|
lCurNode: TDOMNode;
|
|
begin
|
|
lx := 0.0;
|
|
ly := 0.0;
|
|
|
|
lText := TvText.Create;
|
|
|
|
// read the attributes
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'x' then
|
|
lx := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'y' then
|
|
ly := StringWithUnitToFloat(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'id' then
|
|
lText.Name := UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue)
|
|
else if lNodeName = 'style' then
|
|
ReadSVGStyle(ANode.Attributes.Item[i].NodeValue, lText, True)
|
|
else if IsAttributeFromStyle(lNodeName) then
|
|
ReadSVGFontStyleWithKeyAndValue(lNodeName, ANode.Attributes.Item[i].NodeValue, lText);
|
|
end;
|
|
|
|
// The text contents are inside as a child text, not as a attribute
|
|
// ex: <text x="0" y="15" fill="red" transform="rotate(30 20,40)">I love SVG</text>
|
|
if Anode.FirstChild <> nil then
|
|
lTextStr := Anode.FirstChild.NodeValue;
|
|
// Add the first line
|
|
lText.Value.Add(lTextStr);
|
|
|
|
// Set the coordinates
|
|
ConvertSVGDeltaToFPVDelta(
|
|
AData, lx, ly, lText.X, lText.Y);
|
|
|
|
// Now add other lines, which appear as <tspan ...>another line</tspan>
|
|
// Example:
|
|
// <text x="10" y="20" style="fill:red;">Several lines:
|
|
// <tspan x="10" y="45">First line</tspan>
|
|
// <tspan x="10" y="70">Second line</tspan>
|
|
// </text>
|
|
// These other lines can be positioned, so they need to appear as independent TvText elements
|
|
{ lCurNode := Anode.FirstChild;
|
|
while lCurNode <> nil do
|
|
begin
|
|
lNodeName := LowerCase(lCurNode.NodeName);
|
|
if lNodeName <> 'tspan' then Continue;
|
|
ReadTextFromNode(lCurNode, AData, ADoc);
|
|
|
|
lCurNode := lCurNode.NextSibling;
|
|
end;}
|
|
|
|
// Finalization
|
|
AData.AddEntity(lText);
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadUseFromNode(ANode: TDOMNode;
|
|
AData: TvVectorialPage; ADoc: TvVectorialDocument);
|
|
begin
|
|
|
|
end;
|
|
|
|
function TvSVGVectorialReader.StringWithUnitToFloat(AStr: string): Double;
|
|
var
|
|
UnitStr, ValueStr: string;
|
|
Len: Integer;
|
|
LastChar: Char;
|
|
begin
|
|
if AStr = '' then Exit(0.0);
|
|
|
|
// Check the unit
|
|
Len := Length(AStr);
|
|
UnitStr := Copy(AStr, Len-1, 2);
|
|
LastChar := AStr[Len];
|
|
if UnitStr = 'mm' then
|
|
begin
|
|
ValueStr := Copy(AStr, 1, Len-2);
|
|
Result := StrToFloat(ValueStr, FPointSeparator);
|
|
end
|
|
else if UnitStr = 'cm' then
|
|
begin
|
|
ValueStr := Copy(AStr, 1, Len-2);
|
|
Result := StrToFloat(ValueStr, FPointSeparator) * 10;
|
|
end
|
|
else if UnitStr = 'px' then
|
|
begin
|
|
ValueStr := Copy(AStr, 1, Len-2);
|
|
Result := StrToFloat(ValueStr, FPointSeparator);
|
|
end
|
|
else if LastChar = '%' then
|
|
begin
|
|
ValueStr := Copy(AStr, 1, Len-1);
|
|
Result := StrToInt(ValueStr);
|
|
end
|
|
else // If there is no unit, just use StrToFloat
|
|
begin
|
|
Result := StrToFloat(AStr, FPointSeparator);
|
|
end;
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ConvertSVGCoordinatesToFPVCoordinates(
|
|
const AData: TvVectorialPage; const ASrcX, ASrcY: Double;
|
|
var ADestX,ADestY: Double);
|
|
begin
|
|
ADestX := ASrcX * FLOAT_MILIMETERS_PER_PIXEL;
|
|
ADestY := AData.Height - ASrcY * FLOAT_MILIMETERS_PER_PIXEL;
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ConvertSVGDeltaToFPVDelta(
|
|
const AData: TvVectorialPage; const ASrcX, ASrcY: Double; var ADestX,
|
|
ADestY: Double);
|
|
begin
|
|
ADestX := ASrcX * FLOAT_MILIMETERS_PER_PIXEL;
|
|
ADestY := - ASrcY * FLOAT_MILIMETERS_PER_PIXEL;
|
|
end;
|
|
|
|
constructor TvSVGVectorialReader.Create;
|
|
begin
|
|
inherited Create;
|
|
|
|
FPointSeparator := DefaultFormatSettings;
|
|
FPointSeparator.DecimalSeparator := '.';
|
|
FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
|
|
|
|
FSVGPathTokenizer := TSVGPathTokenizer.Create;
|
|
FLayerStylesKeys := TFPList.Create;
|
|
FLayerStylesValues := TFPList.Create;
|
|
end;
|
|
|
|
destructor TvSVGVectorialReader.Destroy;
|
|
begin
|
|
FLayerStylesKeys.Free;
|
|
FLayerStylesValues.Free;
|
|
FSVGPathTokenizer.Free;
|
|
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadFromStream(AStream: TStream;
|
|
AData: TvVectorialDocument);
|
|
var
|
|
Doc: TXMLDocument;
|
|
begin
|
|
try
|
|
// Read in xml file from the stream
|
|
ReadXMLFile(Doc, AStream);
|
|
ReadFromXML(Doc, AData);
|
|
finally
|
|
// finally, free the document
|
|
Doc.Free;
|
|
end;
|
|
end;
|
|
|
|
procedure TvSVGVectorialReader.ReadFromXML(Doc: TXMLDocument;
|
|
AData: TvVectorialDocument);
|
|
var
|
|
lCurNode: TDOMNode;
|
|
lPage: TvVectorialPage;
|
|
{$ifdef SVG_MERGE_LAYER_STYLES}
|
|
lLayerStyleKeys, lLayerStyleValues: TStringList;
|
|
{$endif}
|
|
lNodeName: DOMString;
|
|
ANode: TDOMElement;
|
|
i: Integer;
|
|
begin
|
|
// ----------------
|
|
// Read the properties of the <svg> tag
|
|
// ----------------
|
|
AData.Width := StringWithUnitToFloat(Doc.DocumentElement.GetAttribute('width'));
|
|
AData.Height := StringWithUnitToFloat(Doc.DocumentElement.GetAttribute('height'));
|
|
{$ifdef SVG_MERGE_LAYER_STYLES}
|
|
FLayerStylesKeys.Clear;
|
|
FLayerStylesValues.Clear;
|
|
lLayerStyleKeys := TStringList.Create;
|
|
lLayerStyleValues := TStringList.Create;
|
|
FLayerStylesKeys.Add(lLayerStyleKeys);
|
|
FLayerStylesValues.Add(lLayerStyleValues);
|
|
{$endif}
|
|
ANode := Doc.DocumentElement;
|
|
for i := 0 to ANode.Attributes.Length - 1 do
|
|
begin
|
|
lNodeName := ANode.Attributes.Item[i].NodeName;
|
|
if lNodeName = 'style' then
|
|
begin
|
|
{$ifdef SVG_MERGE_LAYER_STYLES}
|
|
ReadSVGStyleToStyleLists(ANode.Attributes.Item[i].NodeValue, lLayerStyleKeys, lLayerStyleValues);
|
|
{$endif}
|
|
end
|
|
else if IsAttributeFromStyle(lNodeName) then
|
|
begin
|
|
{$ifdef SVG_MERGE_LAYER_STYLES}
|
|
lLayerStyleKeys.Add(lNodeName);
|
|
lLayerStyleValues.Add(UTF16ToUTF8(ANode.Attributes.Item[i].NodeValue));
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
// ----------------
|
|
// Now process the elements
|
|
// ----------------
|
|
lCurNode := Doc.DocumentElement.FirstChild;
|
|
lPage := AData.AddPage();
|
|
lPage.Width := AData.Width;
|
|
lPage.Height := AData.Height;
|
|
while Assigned(lCurNode) do
|
|
begin
|
|
ReadEntityFromNode(lCurNode, lPage, AData);
|
|
lCurNode := lCurNode.NextSibling;
|
|
end;
|
|
|
|
// ----------------
|
|
// Remove the memory of the styles
|
|
// ----------------
|
|
{$ifdef SVG_MERGE_LAYER_STYLES}
|
|
// Now remove the style from this layer
|
|
FLayerStylesKeys.Remove(lLayerStyleKeys);
|
|
lLayerStyleKeys.Free;
|
|
FLayerStylesValues.Remove(lLayerStyleValues);
|
|
lLayerStyleValues.Free;
|
|
{$endif}
|
|
end;
|
|
|
|
initialization
|
|
|
|
RegisterVectorialReader(TvSVGVectorialReader, vfSVG);
|
|
|
|
end.
|
|
|