4
License: The same modified LGPL as the Free Pascal RTL
5
See the file COPYING.modifiedLGPL for more details
7
AUTHORS: Felipe Monteiro de Carvalho
9
unit svgvectorialreader;
16
Classes, SysUtils, math,
18
fpvectorial, fpvutils;
21
TSVGTokenType = (sttMoveTo, sttLineTo, sttBezierTo, sttFloatValue);
24
TokenType: TSVGTokenType;
28
TSVGTokenList = specialize TFPGList<TSVGToken>;
32
TSVGPathTokenizer = class
34
FPointSeparator, FCommaSeparator: TFormatSettings;
35
Tokens: TSVGTokenList;
37
Destructor Destroy; override;
38
procedure AddToken(AStr: string);
39
procedure TokenizePathString(AStr: string);
42
{ TvSVGVectorialReader }
44
TvSVGVectorialReader = class(TvCustomVectorialReader)
46
FPointSeparator, FCommaSeparator: TFormatSettings;
47
FSVGPathTokenizer: TSVGPathTokenizer;
48
procedure ReadPathFromNode(APath: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
49
procedure ReadPathFromString(AStr: string; AData: TvVectorialPage; ADoc: TvVectorialDocument);
50
function StringWithUnitToFloat(AStr: string): Single;
51
procedure ConvertSVGCoordinatesToFPVCoordinates(
52
const AData: TvVectorialPage;
53
const ASrcX, ASrcY: Float; var ADestX, ADestY: Float);
54
procedure ConvertSVGDeltaToFPVDelta(
55
const AData: TvVectorialPage;
56
const ASrcX, ASrcY: Float; var ADestX, ADestY: Float);
58
{ General reading methods }
59
constructor Create; override;
60
Destructor Destroy; override;
61
procedure ReadFromStream(AStream: TStream; AData: TvVectorialDocument); override;
67
// SVG requires hardcoding a DPI value
69
// The Opera Browser and Inkscape use 90 DPI, so we follow that
71
// 1 Inch = 25.4 milimiters
72
// 90 inches per pixel = (1 / 90) * 25.4 = 0.2822
73
// FLOAT_MILIMETERS_PER_PIXEL = 0.3528; // DPI 72 = 1 / 72 inches per pixel
75
FLOAT_MILIMETERS_PER_PIXEL = 0.2822; // DPI 90 = 1 / 90 inches per pixel
76
FLOAT_PIXELS_PER_MILIMETER = 3.5433; // DPI 90 = 1 / 90 inches per pixel
80
constructor TSVGPathTokenizer.Create;
84
FPointSeparator := DefaultFormatSettings;
85
FPointSeparator.DecimalSeparator := '.';
86
FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
88
Tokens := TSVGTokenList.Create;
91
destructor TSVGPathTokenizer.Destroy;
98
procedure TSVGPathTokenizer.AddToken(AStr: string);
102
lToken := TSVGToken.Create;
104
if AStr = 'm' then lToken.TokenType := sttMoveTo
105
else if AStr = 'l' then lToken.TokenType := sttLineTo
106
else if AStr = 'c' then lToken.TokenType := sttBezierTo
109
lToken.TokenType := sttFloatValue;
110
lToken.Value := StrToFloat(AStr, FPointSeparator);
116
procedure TSVGPathTokenizer.TokenizePathString(AStr: string);
118
Str_Space: Char = ' ';
119
Str_Comma: Char = ',';
129
while i <= Length(AStr) do
132
0: // Adding to the tmp string
135
if lCurChar = Str_Space then
141
else if lCurChar = Str_Comma then
147
lTmpStr := lTmpStr + lCurChar;
151
1: // Removing spaces
153
if AStr[i] <> Str_Space then lState := 0
160
{ Example of a supported SVG image:
162
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
163
<!-- Created with fpVectorial (http://wiki.lazarus.freepascal.org/fpvectorial) -->
166
xmlns:dc="http://purl.org/dc/elements/1.1/"
167
xmlns:cc="http://creativecommons.org/ns#"
168
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
169
xmlns:svg="http://www.w3.org/2000/svg"
170
xmlns="http://www.w3.org/2000/svg"
171
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
176
sodipodi:docname="New document 1">
179
style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
180
d="m 0,283.486888731396 l 106.307583274274,-35.4358610914245 "
183
style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
184
d="m 0,354.358610914245 l 354.358610914245,0 l 0,-354.358610914245 l -354.358610914245,0 l 0,354.358610914245 "
187
style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
188
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 "
194
{ TvSVGVectorialReader }
196
procedure TvSVGVectorialReader.ReadPathFromNode(APath: TDOMNode;
197
AData: TvVectorialPage; ADoc: TvVectorialDocument);
199
lNodeName, lStyleStr, lDStr: WideString;
202
for i := 0 to APath.Attributes.Length - 1 do
204
lNodeName := APath.Attributes.Item[i].NodeName;
205
if lNodeName = 'style' then
206
lStyleStr := APath.Attributes.Item[i].NodeValue
207
else if lNodeName = 'd' then
208
lDStr := APath.Attributes.Item[i].NodeValue
212
ReadPathFromString(UTF8Encode(lDStr), AData, ADoc);
216
procedure TvSVGVectorialReader.ReadPathFromString(AStr: string;
217
AData: TvVectorialPage; ADoc: TvVectorialDocument);
220
X, Y, X2, Y2, X3, Y3: Float;
223
FSVGPathTokenizer.Tokens.Clear;
224
FSVGPathTokenizer.TokenizePathString(AStr);
229
while i < FSVGPathTokenizer.Tokens.Count do
231
if FSVGPathTokenizer.Tokens.Items[i].TokenType = sttMoveTo then
233
CurX := FSVGPathTokenizer.Tokens.Items[i+1].Value;
234
CurY := FSVGPathTokenizer.Tokens.Items[i+2].Value;
235
ConvertSVGCoordinatesToFPVCoordinates(AData, CurX, CurY, CurX, CurY);
237
AData.AddMoveToPath(CurX, CurY);
241
else if FSVGPathTokenizer.Tokens.Items[i].TokenType = sttLineTo then
243
X := FSVGPathTokenizer.Tokens.Items[i+1].Value;
244
Y := FSVGPathTokenizer.Tokens.Items[i+2].Value;
245
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
247
// LineTo uses relative coordenates in SVG
251
AData.AddLineToPath(CurX, CurY);
255
else if FSVGPathTokenizer.Tokens.Items[i].TokenType = sttBezierTo then
257
X2 := FSVGPathTokenizer.Tokens.Items[i+1].Value;
258
Y2 := FSVGPathTokenizer.Tokens.Items[i+2].Value;
259
X3 := FSVGPathTokenizer.Tokens.Items[i+3].Value;
260
Y3 := FSVGPathTokenizer.Tokens.Items[i+4].Value;
261
X := FSVGPathTokenizer.Tokens.Items[i+5].Value;
262
Y := FSVGPathTokenizer.Tokens.Items[i+6].Value;
264
ConvertSVGDeltaToFPVDelta(AData, X2, Y2, X2, Y2);
265
ConvertSVGDeltaToFPVDelta(AData, X3, Y3, X3, Y3);
266
ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
268
AData.AddBezierToPath(X2 + CurX, Y2 + CurY, X3 + CurX, Y3 + CurY, X + CurX, Y + CurY);
270
// BezierTo uses relative coordenates in SVG
283
function TvSVGVectorialReader.StringWithUnitToFloat(AStr: string): Single;
285
UnitStr, ValueStr: string;
290
UnitStr := Copy(AStr, Len-1, 2);
291
if UnitStr = 'mm' then
293
ValueStr := Copy(AStr, 1, Len-2);
294
Result := StrToInt(ValueStr);
298
procedure TvSVGVectorialReader.ConvertSVGCoordinatesToFPVCoordinates(
299
const AData: TvVectorialPage; const ASrcX, ASrcY: Float;
300
var ADestX,ADestY: Float);
302
ADestX := ASrcX * FLOAT_MILIMETERS_PER_PIXEL;
303
ADestY := AData.Height - ASrcY * FLOAT_MILIMETERS_PER_PIXEL;
306
procedure TvSVGVectorialReader.ConvertSVGDeltaToFPVDelta(
307
const AData: TvVectorialPage; const ASrcX, ASrcY: Float; var ADestX,
310
ADestX := ASrcX * FLOAT_MILIMETERS_PER_PIXEL;
311
ADestY := - ASrcY * FLOAT_MILIMETERS_PER_PIXEL;
314
constructor TvSVGVectorialReader.Create;
318
FPointSeparator := DefaultFormatSettings;
319
FPointSeparator.DecimalSeparator := '.';
320
FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
322
FSVGPathTokenizer := TSVGPathTokenizer.Create;
325
destructor TvSVGVectorialReader.Destroy;
327
FSVGPathTokenizer.Free;
332
procedure TvSVGVectorialReader.ReadFromStream(AStream: TStream;
333
AData: TvVectorialDocument);
336
lFirstLayer, lCurNode: TDOMNode;
337
lPage: TvVectorialPage;
340
// Read in xml file from the stream
341
ReadXMLFile(Doc, AStream);
343
// Read the properties of the <svg> tag
344
AData.Width := StringWithUnitToFloat(Doc.DocumentElement.GetAttribute('width'));
345
AData.Height := StringWithUnitToFloat(Doc.DocumentElement.GetAttribute('height'));
347
// Now process the elements inside the first layer
348
lFirstLayer := Doc.DocumentElement.FirstChild;
349
lCurNode := lFirstLayer.FirstChild;
350
lPage := AData.AddPage();
351
lPage.Width := AData.Width;
352
lPage.Height := AData.Height;
353
while Assigned(lCurNode) do
355
ReadPathFromNode(lCurNode, lPage, AData);
356
lCurNode := lCurNode.NextSibling;
359
// finally, free the document
366
RegisterVectorialReader(TvSVGVectorialReader, vfSVG);