mirror of
				https://gitlab.com/freepascal.org/lazarus/lazarus.git
				synced 2025-10-31 17:21:34 +01: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.
 | |
| 
 | 
