mirror of
				https://gitlab.com/freepascal.org/lazarus/lazarus.git
				synced 2025-11-04 05:39:34 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			3785 lines
		
	
	
		
			125 KiB
		
	
	
	
		
			ObjectPascal
		
	
	
	
	
	
			
		
		
	
	
			3785 lines
		
	
	
		
			125 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
 | 
						|
 | 
						|
SVG Coordinates vs FPVectorial coordinates:
 | 
						|
 | 
						|
SVG by default has [0, 0] at the top-left and coordinates grow downwards and to the right
 | 
						|
Text is drawn upwards (towards negative Y)
 | 
						|
 | 
						|
* Example of a supported SVG image:
 | 
						|
 | 
						|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
 | 
						|
<!-- Created with fpVectorial (http://wiki.lazarus.freepascal.org/fpvectorial) -->
 | 
						|
 | 
						|
<svg
 | 
						|
  xmlns:dc="http://purl.org/dc/elements/1.1/"
 | 
						|
  xmlns:cc="http://creativecommons.org/ns#"
 | 
						|
  xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
 | 
						|
  xmlns:svg="http://www.w3.org/2000/svg"
 | 
						|
  xmlns="http://www.w3.org/2000/svg"
 | 
						|
  xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
 | 
						|
  width="100mm"
 | 
						|
  height="100mm"
 | 
						|
  id="svg2"
 | 
						|
  version="1.1"
 | 
						|
  sodipodi:docname="New document 1">
 | 
						|
  <g id="layer1">
 | 
						|
  <path
 | 
						|
    style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 | 
						|
    d="m 0,283.486888731396 l 106.307583274274,-35.4358610914245 "
 | 
						|
  id="path0" />
 | 
						|
  <path
 | 
						|
    style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 | 
						|
    d="m 0,354.358610914245 l 354.358610914245,0 l 0,-354.358610914245 l -354.358610914245,0 l 0,354.358610914245 "
 | 
						|
  id="path1" />
 | 
						|
  <path
 | 
						|
    style="fill:none;stroke:#000000;stroke-width:10px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
 | 
						|
    d="m 0,354.358610914245 l 35.4358610914245,-35.4358610914245 c 0,-35.4358610914246 35.4358610914245,-35.4358610914246 35.4358610914245,0 l 35.4358610914245,35.4358610914245 "
 | 
						|
  id="path2" />
 | 
						|
  </g>
 | 
						|
</svg>
 | 
						|
}
 | 
						|
unit svgvectorialreader;
 | 
						|
 | 
						|
{$mode objfpc}{$H+}
 | 
						|
{$define SVG_MERGE_LAYER_STYLES}
 | 
						|
{$define FPVECTORIAL_SVG_SPLIT_PATHS}
 | 
						|
 | 
						|
interface
 | 
						|
 | 
						|
uses
 | 
						|
  Classes, SysUtils, math, contnrs,
 | 
						|
  fpimage, fpcanvas, laz2_xmlread, laz2_dom, fgl,
 | 
						|
  // image data formats
 | 
						|
  fpreadpng,
 | 
						|
  fpvectorial, fpvutils, lazutf8, TypInfo;
 | 
						|
 | 
						|
type
 | 
						|
  TDoubleArray = array of Double;
 | 
						|
 | 
						|
  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;
 | 
						|
    StrValue: string; // filled only by TokenizeFunctions
 | 
						|
  end;
 | 
						|
 | 
						|
  TSVGTokenList = specialize TFPGList<TSVGToken>;
 | 
						|
 | 
						|
  { TSVGObjectStack }
 | 
						|
 | 
						|
  TSVGObjectStack = class(TObjectStack)
 | 
						|
  public
 | 
						|
    function GetList: TList;
 | 
						|
  end;
 | 
						|
 | 
						|
  { TSVGTextSpanStyle }
 | 
						|
 | 
						|
  TSVGTextSpanStyle = class(TvStyle)
 | 
						|
  public
 | 
						|
    PositionSet: Boolean;
 | 
						|
    X, Y: Double;
 | 
						|
    function GenerateDebugTree(ADestRoutine: TvDebugAddItemProc; APageItem: Pointer): Pointer; override;
 | 
						|
  end;
 | 
						|
 | 
						|
  TvSVGVectorialReader = class;
 | 
						|
 | 
						|
  { TSVG_CSS_Style }
 | 
						|
 | 
						|
  TSVG_CSS_Style = class(TvStyle)
 | 
						|
  public
 | 
						|
    CSSName, CSSData: string;
 | 
						|
    function MatchesClass(AClassName: string): Boolean;
 | 
						|
    procedure ParseCSSData(AReader: TvSVGVectorialReader);
 | 
						|
  end;
 | 
						|
 | 
						|
  { TSVGPathTokenizer }
 | 
						|
 | 
						|
  TSVGPathTokenizer = class
 | 
						|
  protected
 | 
						|
    Tokens: TSVGTokenList;
 | 
						|
  public
 | 
						|
    FPointSeparator, FCommaSeparator: TFormatSettings;
 | 
						|
    ExtraDebugStr: string;
 | 
						|
    constructor Create;
 | 
						|
    destructor Destroy; override;
 | 
						|
    procedure AddToken(AStr: string);
 | 
						|
    procedure ClearTokens;
 | 
						|
    procedure TokenizePathString(AStr: string);
 | 
						|
    procedure TokenizeFunctions(AStr: string);
 | 
						|
    function DebugOutTokensAsString: string;
 | 
						|
  end;
 | 
						|
 | 
						|
  TSVGCoordinateKind = (sckUnknown, sckX, sckY, sckXDelta, sckYDelta, sckXSize, sckYSize);
 | 
						|
 | 
						|
  TSVGUnit = (suPX, suMM, suPT {Points});
 | 
						|
 | 
						|
  { TvSVGPathList }
 | 
						|
 | 
						|
  TvSVGPathList = class(TFPObjectList)
 | 
						|
  public
 | 
						|
    // parsing temporary info
 | 
						|
    IsFirstPathMove: Boolean;
 | 
						|
    LastPathClosed: Boolean;
 | 
						|
    CurTokenIndex: Integer;
 | 
						|
    CurX, CurY: Double;
 | 
						|
    Data: TvVectorialPage;
 | 
						|
    Doc: TvVectorialDocument;
 | 
						|
    // Path support for multiple polygons
 | 
						|
    LastPathStart: T2DPoint;
 | 
						|
    function GetPath(AIndex: Integer): TPath;
 | 
						|
  end;
 | 
						|
 | 
						|
  { TvSVGVectorialReader }
 | 
						|
 | 
						|
  TvSVGVectorialReader = class(TvCustomVectorialReader)
 | 
						|
  private
 | 
						|
    FPointSeparator: TFormatSettings;
 | 
						|
    FSVGPathTokenizer: TSVGPathTokenizer;
 | 
						|
    FLayerStylesKeys, FLayerStylesValues: TFPList; // of TStringList;
 | 
						|
    // View box adjustment
 | 
						|
    ViewBoxAdjustment: Boolean;
 | 
						|
    ViewBox_Left, ViewBox_Top, ViewBox_Width, ViewBox_Height, Page_Width, Page_Height: Double;
 | 
						|
    // Defs section
 | 
						|
    FBrushDefs: TFPList; // of TvEntityWithPenAndBrush;
 | 
						|
    FCSSDefs: TFPList; // of TSVG_CSS_Style;
 | 
						|
    // debug symbols
 | 
						|
    FPathNumber: Integer;
 | 
						|
    // BrushDefs functions
 | 
						|
    function FindBrushDef_WithName(AName: string): TvEntityWithPenAndBrush;
 | 
						|
    //
 | 
						|
    function ReadSVGColor(AValue: string): TFPColor;
 | 
						|
    function ReadSVGGradientColorStyle(AValue: String): TFPColor;
 | 
						|
    function ReadSVGStyle(AData: TvVectorialPage; AValue: string;
 | 
						|
      ADestEntity: TvEntityWithPen; ADestStyle: TvStyle = nil;
 | 
						|
      AUseFillAsPen: Boolean = False): TvSetPenBrushAndFontElements;
 | 
						|
    function ReadSVGStyleToStyleLists(AValue: string; AStyleKeys, AStyleValues: TStringList): TvSetPenBrushAndFontElements;
 | 
						|
    function ReadSVGPenStyleWithKeyAndValue(AKey, AValue: string; ADestEntity: TvEntityWithPen; ADestStyle: TvStyle = nil): TvSetPenBrushAndFontElements;
 | 
						|
    function ReadSVGBrushStyleWithKeyAndValue(AKey, AValue: string; ADestEntity: TvEntityWithPenAndBrush; ADestStyle: TvStyle = nil): TvSetPenBrushAndFontElements;
 | 
						|
    function ReadSVGFontStyleWithKeyAndValue(AKey, AValue: string; ADestEntity: TvEntityWithPenBrushAndFont; ADestStyle: TvStyle = nil): TvSetPenBrushAndFontElements;
 | 
						|
    procedure ReadSVGGeneralStyleWithKeyAndValue(AData: TvVectorialPage;
 | 
						|
        AKey, AValue: string; ADestEntity: TvEntity);
 | 
						|
    function IsAttributeFromStyle(AStr: string): Boolean;
 | 
						|
    procedure ApplyLayerStyles(AData: TvVectorialPage; ADestEntity: TvEntity);
 | 
						|
    function ReadSpaceSeparatedFloats(AInput: string; AOtherSeparators: string): TDoubleArray;
 | 
						|
    procedure ReadSVGTransformationMatrix(AMatrix: string; out AA, AB, AC, AD, AE, AF: Double);
 | 
						|
    procedure ApplyCSSClass(AData: TvVectorialPage; AValue: string;
 | 
						|
      ADestEntity: TvEntityWithPen);
 | 
						|
    function IsEntityStyleField(AFieldName: string): Boolean;
 | 
						|
    function ReadEntityStyleField(AData: TvVectorialPage; AFieldName, AFieldValue: string; ADestEntity: TvEntityWithPen; ADestStyle: TvStyle = nil;
 | 
						|
      AUseFillAsPen: Boolean = False): TvSetPenBrushAndFontElements;
 | 
						|
    //
 | 
						|
    function GetTextContentFromNode(ANode: TDOMNode): string;
 | 
						|
    procedure ReadDefs_LinearGradient(ADest: TvEntityWithPenAndBrush; ANode: TDOMNode; AData: TvVectorialPage);
 | 
						|
    procedure ReadDefs_CSS(ANode: TDOMNode);
 | 
						|
    procedure ReadDefsFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
 | 
						|
    //
 | 
						|
    function ReadEntityFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    function ReadCircleFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    function ReadEllipseFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    function ReadFrameFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    function ReadFrameTextFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    function ReadImageFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    procedure ReadLayerFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument);
 | 
						|
    function ReadLineFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    function ReadPathFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    function ReadPathFromString(AStr: string; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvSVGPathList;
 | 
						|
    procedure ReadNextPathCommand(ACurTokenType: TSVGTokenType; APaths: TvSVGPathList; var CurX, CurY: Double);
 | 
						|
    procedure ReadPointsFromString(AStr: string; AData: TvVectorialPage; ADoc: TvVectorialDocument; AClosePath: Boolean);
 | 
						|
    function ReadPolyFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    function ReadRectFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    function ReadSymbolFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    function ReadTextFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    function ReadUseFromNode(ANode: TDOMNode; AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
    //
 | 
						|
    procedure StringToPenPattern(const AStr: String; var APen: TvPen);
 | 
						|
    function  StringWithUnitToFloat(AStr: string; ACoordKind: TSVGCoordinateKind = sckUnknown;
 | 
						|
      ADefaultUnit: TSVGUnit = suPX; ATargetUnit: TSVGUnit = suPX): Double;
 | 
						|
    function  StringFloatZeroToOneToWord(AStr: string): Word;
 | 
						|
    function  StringWithPercentToFloat(AStr: String): Double;
 | 
						|
 | 
						|
    procedure ConvertSVGCoordinatesToFPVCoordinates(
 | 
						|
      const AData: TvVectorialPage;
 | 
						|
      const ASrcX, ASrcY: Double; var ADestX, ADestY: Double;
 | 
						|
      ADoViewBoxAdjust: Boolean = True);
 | 
						|
    procedure ConvertSVGDeltaToFPVDelta(
 | 
						|
      const AData: TvVectorialPage;
 | 
						|
      const ASrcX, ASrcY: Double; var ADestX, ADestY: Double;
 | 
						|
      ADoViewBoxAdjust: Boolean = True);
 | 
						|
    procedure ConvertSVGSizeToFPVSize(
 | 
						|
      const AData: TvVectorialPage;
 | 
						|
      const ASrcX, ASrcY: Double; var ADestX, ADestY: Double;
 | 
						|
      ADoViewBoxAdjust: Boolean = True);
 | 
						|
    procedure AutoDetectDocSize(var ALeft, ATop, ARight, ABottom: Double; ABaseNode: TDOMNode);
 | 
						|
    function SVGColorValueStrToWord(AStr: string): Word;
 | 
						|
 | 
						|
  public
 | 
						|
    { General reading methods }
 | 
						|
    constructor Create; override;
 | 
						|
    Destructor Destroy; override;
 | 
						|
    procedure ReadFromStream(AStream: TStream; AData: TvVectorialDocument); override;
 | 
						|
    procedure ReadFromXML(Doc: TXMLDocument; AData: TvVectorialDocument); override;
 | 
						|
    class function ReadSpaceSeparatedStrings(AInput: string; AOtherSeparators: string): TStringList;
 | 
						|
  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_MILLIMETERS_PER_PIXEL = 1; //0.2822; // DPI 90 = 1 / 90 inches per pixel => Actually I changed the value! Because otherwise it looks ugly!
 | 
						|
  FLOAT_PIXELS_PER_MILIMETER = 1 / FLOAT_MILLIMETERS_PER_PIXEL; // DPI 90 = 1 / 90 inches per pixel
 | 
						|
 | 
						|
  FLOAT_POINTS_PER_PIXEL = 0.75; // For conversion
 | 
						|
  FLOAT_PIXEL_PER_POINT = 1 / FLOAT_POINTS_PER_PIXEL; // For conversion
 | 
						|
 | 
						|
{ TSVG_CSS_Style }
 | 
						|
 | 
						|
function TSVG_CSS_Style.MatchesClass(AClassName: string): Boolean;
 | 
						|
var
 | 
						|
  lNameModified: string;
 | 
						|
begin
 | 
						|
  lNameModified := '.' + AClassName;
 | 
						|
  Result := (lNameModified = CSSName);
 | 
						|
end;
 | 
						|
 | 
						|
{
 | 
						|
<style type="text/css">
 | 
						|
 <![CDATA[
 | 
						|
  .strr3 {stroke:#C7C7A2;stroke-width:0.793701}
 | 
						|
  .str1 {stroke:#8C8C60;stroke-width:1.70079}
 | 
						|
  .strr2 {stroke:#636347;stroke-width:2.26772}
 | 
						|
  .str0 {stroke:#2B2A29;stroke-width:3.9685}
 | 
						|
  .fil1 {fill:none}
 | 
						|
  .fil0 {fill:#EEEED4}
 | 
						|
 ]]>
 | 
						|
}
 | 
						|
procedure TSVG_CSS_Style.ParseCSSData(AReader: TvSVGVectorialReader);
 | 
						|
begin
 | 
						|
  SetElements += AReader.ReadSVGStyle(nil, CSSData, nil, Self, False);
 | 
						|
end;
 | 
						|
 | 
						|
{ TSVGTextSpanStyle }
 | 
						|
 | 
						|
function TSVGTextSpanStyle.GenerateDebugTree(ADestRoutine: TvDebugAddItemProc;
 | 
						|
  APageItem: Pointer): Pointer;
 | 
						|
begin
 | 
						|
  FExtraDebugStr := '';
 | 
						|
  if PositionSet then
 | 
						|
    FExtraDebugStr := FExtraDebugStr + Format(' X=%f Y=%f', [X, Y]);
 | 
						|
  Result:=inherited GenerateDebugTree(ADestRoutine, APageItem);
 | 
						|
end;
 | 
						|
 | 
						|
{ TSVGObjectStack }
 | 
						|
 | 
						|
function TSVGObjectStack.GetList: TList;
 | 
						|
begin
 | 
						|
  Result := List;
 | 
						|
end;
 | 
						|
 | 
						|
{ 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
 | 
						|
  ClearTokens;
 | 
						|
  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;
 | 
						|
 | 
						|
  lToken := TSVGToken.Create;
 | 
						|
  // 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;
 | 
						|
    try
 | 
						|
      lToken.Value := StrToFloat(AStr, FPointSeparator);
 | 
						|
    except
 | 
						|
      on MyException: Exception do
 | 
						|
      begin
 | 
						|
        MyException.Message := MyException.Message + ExtraDebugStr;
 | 
						|
        raise MyException;
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
  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.ClearTokens;
 | 
						|
var
 | 
						|
  i: Integer;
 | 
						|
begin
 | 
						|
  for i := Tokens.Count-1 downto 0 do
 | 
						|
    Tokens[i].Free;
 | 
						|
  Tokens.Clear;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TSVGPathTokenizer.TokenizePathString(AStr: string);
 | 
						|
const
 | 
						|
  Str_Space: Char = ' ';
 | 
						|
  Str_Comma: Char = ',';
 | 
						|
  Str_Plus: Char = '+';
 | 
						|
  Str_Minus: Char = '-';
 | 
						|
  ListOfCommandLetters: set of Char = ['a'..'d', 'f'..'z', 'A'..'D', 'F'..'Z'];
 | 
						|
var
 | 
						|
  i: Integer;
 | 
						|
  lTmpStr: string = '';
 | 
						|
  lState: Integer;
 | 
						|
  lFirstTmpStrChar, lCurChar, lPrevChar: Char;
 | 
						|
begin
 | 
						|
  lState := 0;
 | 
						|
 | 
						|
  i := 1;
 | 
						|
  while i <= Length(AStr) do
 | 
						|
  begin
 | 
						|
    case lState of
 | 
						|
    0: // Adding to the tmp string
 | 
						|
    begin
 | 
						|
      if i > 0 then lPrevChar := AStr[i-1];
 | 
						|
      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 if (lCurChar in [Str_Plus, Str_Minus]) and (lPrevChar in ['0'..'9']) then
 | 
						|
      begin
 | 
						|
        AddToken(lTmpStr);
 | 
						|
        lTmpStr := lCurChar;
 | 
						|
      end
 | 
						|
      // Check for a break, from letter to number
 | 
						|
      // Note: But don't forget that we need to support 3.799e-4 !!
 | 
						|
      // So e is not a valid letter for commands here
 | 
						|
      // Note 2: Letters don't need space between them as in "zm"
 | 
						|
      else if (lCurChar in ListOfCommandLetters) then
 | 
						|
      begin
 | 
						|
        if Length(lTmpStr) > 0 then
 | 
						|
        begin
 | 
						|
          AddToken(lTmpStr);
 | 
						|
        end;
 | 
						|
        AddToken(lCurChar);
 | 
						|
        lTmpStr := '';
 | 
						|
      end
 | 
						|
      else
 | 
						|
      begin
 | 
						|
        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;
 | 
						|
 | 
						|
procedure TSVGPathTokenizer.TokenizeFunctions(AStr: string);
 | 
						|
const
 | 
						|
  Str_Space: Char = ' ';
 | 
						|
  Str_Start_Params: Char = '(';
 | 
						|
  Str_End_Params: Char = ')';
 | 
						|
  ListOfCommandLetters: set of Char = ['a'..'d', 'f'..'z', 'A'..'D', 'F'..'Z'];
 | 
						|
var
 | 
						|
  i: Integer;
 | 
						|
  lTmpStr: string = '';
 | 
						|
  lState: Integer;
 | 
						|
  lFirstTmpStrChar, lCurChar: Char;
 | 
						|
  lToken: TSVGToken;
 | 
						|
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 in [Str_Start_Params, Str_End_Params] then
 | 
						|
      begin
 | 
						|
        lState := 1;
 | 
						|
        // Add the token
 | 
						|
        lToken := TSVGToken.Create;
 | 
						|
        lToken.StrValue := lTmpStr;
 | 
						|
        Tokens.Add(lToken);
 | 
						|
        //
 | 
						|
        lTmpStr := '';
 | 
						|
      end
 | 
						|
      else
 | 
						|
      begin
 | 
						|
        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
 | 
						|
  Result := '';
 | 
						|
  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;
 | 
						|
 | 
						|
{ TvSVGPathList }
 | 
						|
 | 
						|
function TvSVGPathList.GetPath(AIndex: Integer): TPath;
 | 
						|
begin
 | 
						|
  Result := TPath(Self.Items[AIndex]);
 | 
						|
end;
 | 
						|
 | 
						|
{ TvSVGVectorialReader }
 | 
						|
 | 
						|
function TvSVGVectorialReader.FindBrushDef_WithName(AName: string): TvEntityWithPenAndBrush;
 | 
						|
var
 | 
						|
  i: Integer;
 | 
						|
begin
 | 
						|
  Result := nil;
 | 
						|
  for i := 0 to FBrushDefs.Count-1 do
 | 
						|
  begin
 | 
						|
    Result := TvEntityWithPenAndBrush(FBrushDefs.Items[i]);
 | 
						|
    if Result.Name = AName then
 | 
						|
    begin
 | 
						|
      Exit;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
  Result := nil;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadSVGColor(AValue: string): TFPColor;
 | 
						|
var
 | 
						|
  lValue, lStr: string;
 | 
						|
  lStrings: TStringList;
 | 
						|
  i: Integer;
 | 
						|
  HasAlphaChannel: Boolean = False;
 | 
						|
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
 | 
						|
      HasAlphaChannel := Copy(lValue, 0, 4) = 'rgba';
 | 
						|
      if HasAlphaChannel then lStr := Copy(lValue, 6, Length(lValue)-6)
 | 
						|
      else lStr := Copy(lValue, 5, Length(lValue)-5);
 | 
						|
      lStrings.Delimiter := ',';
 | 
						|
      lStrings.StrictDelimiter := True;
 | 
						|
      lStrings.DelimitedText := lStr;
 | 
						|
      if lStrings.Count in [3, 4] then
 | 
						|
      begin
 | 
						|
        Result.Red := SVGColorValueStrToWord(lStrings.Strings[0]);
 | 
						|
        Result.Green := SVGColorValueStrToWord(lStrings.Strings[1]);
 | 
						|
        Result.Blue := SVGColorValueStrToWord(lStrings.Strings[2]);
 | 
						|
        if (lStrings.Count = 4) and HasAlphaChannel then
 | 
						|
          Result.alpha := StringFloatZeroToOneToWord(lStrings.Strings[3]);
 | 
						|
      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[1] of
 | 
						|
    'a': case lValue of
 | 
						|
           'aliceblue'           : Result := FPColor($F0F0, $F8F8, $FFFF);
 | 
						|
           'antiquewhite'        : Result := FPColor($FAFA, $EBEB, $D7D7);
 | 
						|
           'aqua'                : Result := colCyan;
 | 
						|
           'aquamarine'          : Result := FPColor($7F7F, $FFFF, $D4D4);
 | 
						|
           'azure'               : Result := FPColor($F0F0, $FFFF, $FFFF);
 | 
						|
         end;
 | 
						|
 | 
						|
    'b': case lValue of
 | 
						|
           'beige'               : Result := FPColor($F5F5, $F5F5, $DCDC);
 | 
						|
           'bisque'              : Result := FPColor($FFFF, $E4E4, $C4C4);
 | 
						|
           'black'               : Result := colBlack;
 | 
						|
           'blanchedalmond'      : Result := FPColor($FFFF, $EBEB, $CDCD);
 | 
						|
           'blue'                : Result := colBlue;
 | 
						|
           'blueviolet'          : Result := FPColor($8A8A, $2B2B, $E2E2);
 | 
						|
           'brown'               : Result := FPColor($A5A5, $2A2A, $2A2A);
 | 
						|
           'burlywood'           : Result := FPColor($DEDE, $B8B8, $8787);
 | 
						|
         end;
 | 
						|
 | 
						|
    'c': case lValue of
 | 
						|
           'cadetblue'           : Result := FPColor($5F5F, $9E9E, $A0A0);
 | 
						|
           'chartreuse'          : Result := FPColor($7F7F, $FFFF, $0000);
 | 
						|
           'chocolate'           : Result := FPColor($D2D2, $6969, $1E1E);
 | 
						|
           'coral'               : Result := FPColor($FFFF, $7F7F, $5050);
 | 
						|
           'cornflowerblue'      : Result := FPColor($6464, $9595, $EDED);
 | 
						|
           'cornsilk'            : Result := FPColor($FFFF, $F8F8, $DCDC);
 | 
						|
           'crimson'             : Result := FPColor($DCDC, $1414, $3C3C);
 | 
						|
           'cyan'                : Result := colCyan;
 | 
						|
         end;
 | 
						|
 | 
						|
    'd': case lValue of
 | 
						|
           'darkblue'            : Result.Blue := $8B8B;
 | 
						|
           'darkcyan'            : Result := FPColor($0000, $8B8B, $8B8B);
 | 
						|
           'darkgoldenrod'       : Result := FPColor($B8B8, $8686, $0B0B);
 | 
						|
           'darkgray',
 | 
						|
           'darkgrey'            : Result := FPColor($A9A9, $A9A9, $A9A9);
 | 
						|
           'darkgreen'           : Result.Green := $6464;
 | 
						|
           'darkkhaki'           : Result := FPColor($BDBD, $B7B7, $6B6B);
 | 
						|
           'darkmagenta'         : Result := FPColor($8B8B, $0000, $8B8B);
 | 
						|
           'darkolivegreen'      : Result := FPColor($5555, $6B6B, $2F2F);
 | 
						|
           'darkorange'          : Result := FPColor($FFFF, $8C8C, $0000);
 | 
						|
           'darkorchid'          : Result := FPColor($9999, $3232, $CCCC);
 | 
						|
           'darkred'             : Result.Red := $8B8B;
 | 
						|
           'darksalmon'          : Result := FPColor($E9E9, $9696, $7A7A);
 | 
						|
           'darkseagreen'        : Result := FPColor($8F8F, $BCBC, $8F8F);
 | 
						|
           'darkslateblue'       : Result := FPColor($4848, $3D3D, $8B8B);
 | 
						|
           'darkslategray',
 | 
						|
           'darkslategrey'       : Result := FPColor($2F2F, $4F4F, $4F4F);
 | 
						|
           'darkturquoise'       : Result := FPColor($0000, $CECE, $D1D1);
 | 
						|
           'darkviolet'          : Result := FPColor($9494, $0000, $D3D3);
 | 
						|
           'deeppink'            : Result := FPColor($FFFF, $1414, $9393);
 | 
						|
           'deepskyblue'         : Result := FPColor($0000, $BFBF, $FFFF);
 | 
						|
           'dimgray',
 | 
						|
           'dimgrey'             : Result := FPColor($6969, $6969, $6969);
 | 
						|
           'dodgerblue'          : Result := FPColor($1E1E, $9090, $FFFF);
 | 
						|
         end;
 | 
						|
 | 
						|
    'f': case lValue of
 | 
						|
           'firebrick'           : Result := FPColor($B2B2, $2222, $2222);
 | 
						|
           'floralwhite'         : Result := FPColor($FFFF, $FAFA, $F0F0);
 | 
						|
           'forestgreen'         : Result := FPColor($2222, $8B8B, $2222);
 | 
						|
           'fuchsia'             : Result := colFuchsia;
 | 
						|
         end;
 | 
						|
 | 
						|
    'g': case lValue of
 | 
						|
           'gainsboro'           : Result := FPColor($DCDC, $DCDC, $DCDC);
 | 
						|
           'ghostwhite'          : Result := FPColor($F8F8, $F8F8, $FFFF);
 | 
						|
           'gold'                : Result := FPColor($FFFF, $D7D7, $0000);
 | 
						|
           'goldenrod'           : Result := FPColor($DADA, $A5A5, $2020);
 | 
						|
           'gray', 'grey'        : Result := colGray;
 | 
						|
           'green'               : Result.Green := $8080;
 | 
						|
           'greenyellow'         : Result := FPColor($ADAD, $FFFF, $2F2F);
 | 
						|
         end;
 | 
						|
 | 
						|
    'h': case lValue of
 | 
						|
           'honeydew'            : Result := FPColor($F0F0, $FFFF, $F0F0);
 | 
						|
           'hotpink'             : Result := FPColor($FFFF, $6969, $B4B4);
 | 
						|
         end;
 | 
						|
 | 
						|
    'i': case lValue of
 | 
						|
           'indianred'           : Result := FPColor($CDCD, $5C5C, $5C5C);
 | 
						|
           'indigo'              : Result := FPColor($4B4B, $0000, $8282);
 | 
						|
           'ivory'               : Result := FPColor($FFFF, $FFFF, $F0F0);
 | 
						|
         end;
 | 
						|
 | 
						|
    'k': case lValue of
 | 
						|
           'khaki'               : Result := FPColor($F0F0, $E6E6, $8C8C);
 | 
						|
         end;
 | 
						|
 | 
						|
    'l': case lValue of
 | 
						|
           'lavender'            : Result := FPColor($E6E6, $E6E6, $FAFA);
 | 
						|
           'lavenderblush'       : Result := FPColor($FFFF, $F0F0, $F5F5);
 | 
						|
           'lawngreen'           : Result := FPColor($7C7C, $FCFE, $0000);
 | 
						|
           'lemonchiffon'        : Result := FPColor($FFFF, $FAFA, $CDCD);
 | 
						|
           'lightblue'           : Result := FPColor($ADAD, $D8D8, $E6E6);
 | 
						|
           'lightcoral'          : Result := FPColor($F0F0, $8080, $8080);
 | 
						|
           'lightcyan'           : Result := FPColor($E0E0, $FFFF, $FFFF);
 | 
						|
           'lightgoldenrodyellow': Result := FPColor($FAFA, $FAFA, $D2D2);
 | 
						|
           'lightgray',
 | 
						|
           'lightgrey'           : Result := FPColor($D3D3, $D3D3, $D3D3);
 | 
						|
           'lightgreen'          : Result := FPColor($9090, $EEEE, $9090);
 | 
						|
           'lightpink'           : Result := FPColor($FFFF, $B6B6, $C1C1);
 | 
						|
           'lightsalmon'         : Result := FPColor($FFFF, $A0A0, $7A7A);
 | 
						|
           'lightseagreen'       : Result := FPColor($2020, $B2B2, $AAAA);
 | 
						|
           'lightskyblue'        : Result := FPColor($8787, $CECE, $FAFA);
 | 
						|
           'lightslategray',
 | 
						|
           'lightslategrey'      : Result := FPColor($7777, $8888, $9999);
 | 
						|
           'lightsteelblue'      : Result := FPColor($B0B0, $C4C4, $DEDE);
 | 
						|
           'lightyellow'         : Result := FPColor($FFFF, $FEFE, $0000);
 | 
						|
           'lime'                : Result := colGreen;
 | 
						|
           'limegreen'           : Result := FPColor($3232, $CDCD, $3232);
 | 
						|
           'linen'               : Result := FPColor($FAFA, $F0F0, $E6E6);
 | 
						|
         end;
 | 
						|
 | 
						|
    'm': case lValue of
 | 
						|
           'magenta'             : Result := colMagenta;
 | 
						|
           'maroon'              : Result.Red := $8080;
 | 
						|
           'mediumaquamarine'    : Result := FPColor($6666, $CDCD, $AAAA);
 | 
						|
           'mediumblue'          : Result.Blue := $CDCD;
 | 
						|
           'mediumorchid'        : Result := FPColor($BABA, $5555, $D3D3);
 | 
						|
           'mediumpurple'        : Result := FPColor($9393, $7070, $DBDB);
 | 
						|
           'mediumseagreen'      : Result := FPColor($3C3C, $CBCB, $7171);
 | 
						|
           'mediumslateblue'     : Result := FPColor($7B7B, $6868, $EEEE);
 | 
						|
           'mediumspringgreen'   : Result := FPColor($0000, $FAFA, $9A9A);
 | 
						|
           'mediumturquoise'     : Result := FPColor($4848, $D1D1, $CCCC);
 | 
						|
           'mediumvioletred'     : Result := FPColor($C7C7, $1515, $8585);
 | 
						|
           'midnightblue'        : Result := FPColor($1919, $1919, $7070);
 | 
						|
           'mintcream'           : Result := FPColor($F5F5, $FFFF, $FAFA);
 | 
						|
           'mistyrose'           : Result := FPColor($FFFF, $E4E4, $E1E1);
 | 
						|
           'moccasin'            : Result := FPColor($FFFF, $E4E4, $B5B5);
 | 
						|
         end;
 | 
						|
 | 
						|
    'n': case lValue of
 | 
						|
           'navajowhite'         : Result := FPColor($FFFF, $DEDE, $ADAD);
 | 
						|
           'navy'                : Result.Blue := $8080;
 | 
						|
         end;
 | 
						|
 | 
						|
    'o': case lValue of
 | 
						|
           'oldlace'             : Result := FPColor($FDFD, $F5F5, $E6E6);
 | 
						|
           'olive'               : Result := colOlive;
 | 
						|
           'olivedrab'           : Result := FPColor($6B6B, $8E8E, $2323);
 | 
						|
           'orange'              : Result := FPColor($FFFF, $A5A5, $0000);
 | 
						|
           'orangered'           : Result := FPColor($FFFF, $4545, $0000);
 | 
						|
           'orchid'              : Result := FPColor($DADA, $7070, $D6D6);
 | 
						|
         end;
 | 
						|
 | 
						|
    'p': case lValue of
 | 
						|
           'palegreen'           : Result := FPColor($9898, $FBFB, $9898);
 | 
						|
           'palegoldenrod'       : Result := FPColor($EEEE, $E8E8, $AAAA);
 | 
						|
           'paleturquoise'       : Result := FPColor($AFAF, $EEEE, $EEEE);
 | 
						|
           'palevioletred'       : Result := FPColor($DBDB, $7070, $9393);
 | 
						|
           'papayawhip'          : Result := FPColor($FFFF, $EFEF, $D5D5);
 | 
						|
           'peachpuff'           : Result := FPColor($FFFF, $DADA, $B9B9);
 | 
						|
           'peru'                : Result := FPColor($CDCD, $8585, $3F3F);
 | 
						|
           'pink'                : Result := FPColor($FFFF, $C0C0, $CBCB);
 | 
						|
           'plum'                : Result := FPColor($DDDD, $A0A0, $DDDD);
 | 
						|
           'powderblue'          : Result := FPColor($B0B0, $E0E0, $E6E6);
 | 
						|
           'purple'              : Result := colPurple;
 | 
						|
         end;
 | 
						|
 | 
						|
    'r': case lValue of
 | 
						|
           'red'                 : Result := colRed;
 | 
						|
           'rosybrown'           : Result := FPColor($BCBC, $8F8F, $8F8F);
 | 
						|
           'royalblue'           : Result := FPColor($4141, $6969, $E1E1);
 | 
						|
         end;
 | 
						|
 | 
						|
    's': case lValue of
 | 
						|
           'saddlebrown'         : Result := FPColor($8B8B, $4545, $1313);
 | 
						|
           'salmon'              : Result := FPColor($FAFA, $8080, $7272);
 | 
						|
           'sandybrown'          : Result := FPColor($F4F4, $A4A4, $6060);
 | 
						|
           'seagreen'            : Result := FPColor($2E2E, $8B8B, $5757);
 | 
						|
           'seashell'            : Result := FPColor($FFFF, $F5F5, $EEEE);
 | 
						|
           'sienna'              : Result := FPColor($A0A0, $5252, $2D2D);
 | 
						|
           'silver'              : Result := colSilver;
 | 
						|
           'skyblue'             : Result := FPColor($8787, $CECE, $EBEB);
 | 
						|
           'slateblue'           : Result := FPCOlor($6A6A, $5A5A, $CDCD);
 | 
						|
           'slategray',
 | 
						|
           'slategrey'           : Result := FPColor($7070, $8080, $9090);
 | 
						|
           'snow'                : Result := FPColor($FFFF, $FAFA, $FAFA);
 | 
						|
           'springgreen'         : Result := FPColor($0000, $FFFF, $7F7F);
 | 
						|
           'steelblue'           : Result := FPColor($4646, $8282, $B4B4);
 | 
						|
         end;
 | 
						|
 | 
						|
    't': case lValue of
 | 
						|
           'tan'                 : Result := FPColor($D2D2, $B4B4, $8C8C);
 | 
						|
           'teal'                : Result := FPColor($0000, $8080, $8080);
 | 
						|
           'thistle'             : Result := FPColor($D8D8, $BFBF, $D8D8);
 | 
						|
           'tomato'              : Result := FPColor($FFFF, $6363, $4747);
 | 
						|
           'turquoise'           : Result := FPColor($4040, $E0E0, $D0D0);
 | 
						|
         end;
 | 
						|
 | 
						|
    'v': case lValue of
 | 
						|
           'violet'              : Result := FPColor($EEEE, $8282, $EEEE);
 | 
						|
         end;
 | 
						|
 | 
						|
    'w': case lValue of
 | 
						|
           'wheat'               : Result := FPColor($F5F5, $DEDE, $B3B3);
 | 
						|
           'white'               : Result := colWhite;
 | 
						|
           'whitesmoke'          : Result := FPColor($F5F5, $F5F5, $F5F5);
 | 
						|
         end;
 | 
						|
 | 
						|
    'y': case lValue of
 | 
						|
           'yellow'              : Result := colYellow;
 | 
						|
           'yellowgreen'         : Result := FPColor($9A9A, $CDCD, $3232);
 | 
						|
         end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
// style="stop-color:rgb(255,255,10);stop-opacity:1.0"
 | 
						|
function TvSVGVectorialReader.ReadSVGGradientColorStyle(AValue: String): TFPColor;
 | 
						|
var
 | 
						|
  lStr, lStyleKeyStr, lStyleValueStr: String;
 | 
						|
  lStrings: TStringList;
 | 
						|
  i: Integer;
 | 
						|
  p: Integer;
 | 
						|
begin
 | 
						|
  Result := colBlack;
 | 
						|
  if AValue = '' then Exit;
 | 
						|
  lStrings := TStringList.Create;
 | 
						|
  try
 | 
						|
    lStrings.Delimiter := ';';
 | 
						|
    lStrings.StrictDelimiter := True;
 | 
						|
    lStrings.DelimitedText := LowerCase(AValue);
 | 
						|
    for i := 0 to lStrings.Count-1 do
 | 
						|
    begin
 | 
						|
      lStr := lStrings.Strings[i];
 | 
						|
      p := Pos(':', lStr);
 | 
						|
      lStyleKeyStr := Trim(Copy(lStr, 1, p-1));
 | 
						|
      lStyleValueStr := Trim(Copy(lStr, p+1, MaxInt));
 | 
						|
      if lStyleKeyStr = 'stop-color' then
 | 
						|
        Result := ReadSVGColor(lStyleValueStr)
 | 
						|
      else if lStyleKeyStr = 'stop-opacity' then
 | 
						|
        Result.Alpha := Round(StrToFloat(lStyleValueStr, FPointSeparator)*$FFFF);
 | 
						|
    end;
 | 
						|
  finally
 | 
						|
    lStrings.Free;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
// style="fill:none;stroke:black;stroke-width:3"
 | 
						|
function TvSVGVectorialReader.ReadSVGStyle(AData: TvVectorialPage; AValue: string;
 | 
						|
  ADestEntity: TvEntityWithPen; ADestStyle: TvStyle = nil;
 | 
						|
  AUseFillAsPen: Boolean = False): TvSetPenBrushAndFontElements;
 | 
						|
var
 | 
						|
  lStr, lStyleKeyStr, lStyleValueStr: String;
 | 
						|
  lStrings: TStringList;
 | 
						|
  i: Integer;
 | 
						|
begin
 | 
						|
  Result := [];
 | 
						|
  if AValue = '' then Exit;
 | 
						|
 | 
						|
  // Now split using ";" separator
 | 
						|
  lStrings := TStringList.Create;
 | 
						|
  try
 | 
						|
    lStrings.Delimiter := ';';
 | 
						|
    lStrings.StrictDelimiter := True;
 | 
						|
    lStrings.DelimitedText := AValue;
 | 
						|
    for i := 0 to lStrings.Count-1 do
 | 
						|
    begin
 | 
						|
      lStr := lStrings.Strings[i];
 | 
						|
      SeparateStringInTwo(lStr, ':', lStyleKeyStr, lStyleValueStr);
 | 
						|
      lStyleKeyStr := LowerCase(Trim(lStyleKeyStr));
 | 
						|
      lStyleValueStr := Trim(lStyleValueStr);
 | 
						|
      if ADestEntity <> nil then
 | 
						|
      begin
 | 
						|
        ReadSVGPenStyleWithKeyAndValue(lStyleKeyStr, lStyleValueStr, ADestEntity);
 | 
						|
        ReadSVGGeneralStyleWithKeyAndValue(AData, 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;
 | 
						|
      if ADestStyle <> nil then
 | 
						|
      begin
 | 
						|
        Result += ReadSVGPenStyleWithKeyAndValue(lStyleKeyStr, lStyleValueStr, nil, ADestStyle);
 | 
						|
        //Result += ReadSVGGeneralStyleWithKeyAndValue(AData, lStyleKeyStr, lStyleValueStr, nil, ADestStyle);
 | 
						|
        if AUseFillAsPen and (lStyleKeyStr = 'fill') then
 | 
						|
          Result += ReadSVGPenStyleWithKeyAndValue('stroke', lStyleValueStr, nil, ADestStyle);
 | 
						|
        Result += ReadSVGFontStyleWithKeyAndValue(lStyleKeyStr, lStyleValueStr, nil, ADestStyle);
 | 
						|
        Result += ReadSVGBrushStyleWithKeyAndValue(lStyleKeyStr, lStyleValueStr, nil, ADestStyle);
 | 
						|
      end;
 | 
						|
    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: 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];
 | 
						|
      SeparateStringInTwo(lStr, ':', lStyleKeyStr, lStyleValueStr);
 | 
						|
      AStyleKeys.Add(lStyleKeyStr);
 | 
						|
      AStyleValues.Add(lStyleValueStr);
 | 
						|
    end;
 | 
						|
  finally
 | 
						|
    lStrings.Free;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadSVGPenStyleWithKeyAndValue(AKey,
 | 
						|
  AValue: string; ADestEntity: TvEntityWithPen; ADestStyle: TvStyle = nil): TvSetPenBrushAndFontElements;
 | 
						|
var
 | 
						|
  OldAlpha: Word;
 | 
						|
  lValueInt: Int64;
 | 
						|
begin
 | 
						|
  Result := [];
 | 
						|
  if AKey = 'stroke' then
 | 
						|
  begin
 | 
						|
    // We store and restore the old alpha to support the "-opacity" element
 | 
						|
    if ADestEntity <> nil then
 | 
						|
    begin
 | 
						|
      OldAlpha := ADestEntity.Pen.Color.Alpha;
 | 
						|
      if ADestEntity.Pen.Style = psClear then ADestEntity.Pen.Style := psSolid;
 | 
						|
    end;
 | 
						|
    if ADestStyle <> nil then
 | 
						|
    begin
 | 
						|
      OldAlpha := ADestStyle.Pen.Color.Alpha;
 | 
						|
      if ADestStyle.Pen.Style = psClear then ADestStyle.Pen.Style := psSolid;
 | 
						|
    end;
 | 
						|
 | 
						|
    if AValue = 'none'  then
 | 
						|
    begin
 | 
						|
      if ADestEntity <> nil then ADestEntity.Pen.Style := fpcanvas.psClear;
 | 
						|
      if ADestStyle <> nil then ADestStyle.Pen.Style := fpcanvas.psClear;
 | 
						|
    end
 | 
						|
    else
 | 
						|
    begin
 | 
						|
      if ADestEntity <> nil then
 | 
						|
      begin
 | 
						|
        ADestEntity.Pen.Color := ReadSVGColor(AValue);
 | 
						|
        ADestEntity.Pen.Color.Alpha := OldAlpha;
 | 
						|
      end;
 | 
						|
      if ADestStyle <> nil then
 | 
						|
      begin
 | 
						|
        ADestStyle.Pen.Color := ReadSVGColor(AValue);
 | 
						|
        ADestStyle.Pen.Color.Alpha := OldAlpha;
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
    Result := Result + [spbfPenColor, spbfPenStyle];
 | 
						|
  end
 | 
						|
  else if AKey = 'stroke-width' then
 | 
						|
  begin
 | 
						|
    lValueInt := Round(StringWithUnitToFloat(AValue, sckXSize));
 | 
						|
    if ADestEntity <> nil then ADestEntity.Pen.Width := lValueInt;
 | 
						|
    if ADestStyle <> nil then ADestStyle.Pen.Width := lValueInt;
 | 
						|
    Result := Result + [spbfPenWidth];
 | 
						|
  end
 | 
						|
  else if AKey = 'stroke-opacity' then
 | 
						|
  begin
 | 
						|
    lValueInt := Round(StrToFloat(AValue, FPointSeparator)*$FFFF);
 | 
						|
    if ADestEntity <> nil then ADestEntity.Pen.Color.Alpha := lValueInt;
 | 
						|
    if ADestStyle <> nil then ADestStyle.Pen.Color.Alpha := lValueInt;
 | 
						|
  end
 | 
						|
  else if AKey = 'stroke-linecap' then
 | 
						|
  begin
 | 
						|
    {case LowerCase(AValue) of
 | 
						|
    'butt':
 | 
						|
    'round':
 | 
						|
    'square': ADestEntity.Pen;
 | 
						|
    end;}
 | 
						|
  end
 | 
						|
  else if AKey = 'stroke-dasharray' then
 | 
						|
    StringToPenPattern(AValue, ADestEntity.Pen);
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadSVGBrushStyleWithKeyAndValue(AKey,
 | 
						|
  AValue: string; ADestEntity: TvEntityWithPenAndBrush; ADestStyle: TvStyle = nil): TvSetPenBrushAndFontElements;
 | 
						|
var
 | 
						|
  OldAlpha: Word;
 | 
						|
  Len: Integer;
 | 
						|
  lDefName: String;
 | 
						|
  lCurBrush: TvEntityWithPenAndBrush;
 | 
						|
  lDestBrush: PvBrush;
 | 
						|
begin
 | 
						|
  Result := [];
 | 
						|
 | 
						|
  if ADestEntity <> nil then
 | 
						|
  begin
 | 
						|
    lDestBrush := @ADestEntity.Brush;
 | 
						|
  end
 | 
						|
  else if ADestStyle <> nil then
 | 
						|
  begin
 | 
						|
    lDestBrush := @ADestStyle;
 | 
						|
  end;
 | 
						|
  if not ((ADestEntity <> nil) xor (ADestStyle <> nil)) then
 | 
						|
    raise Exception.Create('[TvSVGVectorialReader.ReadSVGBrushStyleWithKeyAndValue] Invalid arguments');
 | 
						|
 | 
						|
  if AKey = 'fill' then
 | 
						|
  begin
 | 
						|
    // Support for fill="url(#grad2)"
 | 
						|
    lDefName := Trim(AValue);
 | 
						|
    if Copy(lDefName, 0, 3) = 'url' then
 | 
						|
    begin
 | 
						|
      lDefName := StringReplace(AValue, 'url(#', '', []);
 | 
						|
      lDefName := StringReplace(lDefName, ')', '', []);
 | 
						|
      if lDefName = '' then Exit;
 | 
						|
 | 
						|
      lCurBrush := FindBrushDef_WithName(lDefName);
 | 
						|
      if lCurBrush <> nil then
 | 
						|
      begin
 | 
						|
        lDestBrush^ := lCurBrush.Brush;
 | 
						|
        Exit;
 | 
						|
      end;
 | 
						|
      Exit;
 | 
						|
    end;
 | 
						|
 | 
						|
    // We store and restore the old alpha to support the "-opacity" element
 | 
						|
    OldAlpha := lDestBrush^.Color.Alpha;
 | 
						|
    if lDestBrush^.Style = bsClear then lDestBrush^.Style := bsSolid;
 | 
						|
 | 
						|
    if AValue = 'none'  then
 | 
						|
    begin
 | 
						|
      lDestBrush^.Style := fpcanvas.bsClear;
 | 
						|
    end
 | 
						|
    else
 | 
						|
    begin
 | 
						|
      lDestBrush^.Color := ReadSVGColor(AValue);
 | 
						|
      lDestBrush^.Color.Alpha := OldAlpha;
 | 
						|
    end;
 | 
						|
 | 
						|
    Result := Result + [spbfBrushColor, spbfBrushStyle];
 | 
						|
  end
 | 
						|
  else if AKey = 'fill-rule' then
 | 
						|
  begin
 | 
						|
    if ADestEntity <> nil then
 | 
						|
    begin
 | 
						|
      if AValue = 'evenodd' then
 | 
						|
        ADestEntity.WindingRule := vcmEvenOddRule
 | 
						|
      else if AValue = 'nonzero' then
 | 
						|
        ADestEntity.WindingRule  := vcmNonzeroWindingRule;  // to do: "inherit" missing here
 | 
						|
    end;
 | 
						|
  end
 | 
						|
  else if AKey = 'fill-opacity' then
 | 
						|
  begin
 | 
						|
    lDestBrush^.Color.Alpha := StringFloatZeroToOneToWord(AValue);
 | 
						|
    if lDestBrush^.Color.Alpha = 0 then
 | 
						|
      lDestBrush^.Style := bsClear;
 | 
						|
  end
 | 
						|
  // For linear gradient => stop-color:rgb(255,255,0);stop-opacity:1
 | 
						|
  else if AKey = 'stop-color' then
 | 
						|
  begin
 | 
						|
    Len := Length(lDestBrush^.Gradient_colors);
 | 
						|
    SetLength(lDestBrush^.Gradient_colors, Len+1);
 | 
						|
    lDestBrush^.Gradient_colors[Len].Color := ReadSVGColor(AValue);
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadSVGFontStyleWithKeyAndValue(AKey,
 | 
						|
  AValue: string; ADestEntity: TvEntityWithPenBrushAndFont; ADestStyle: TvStyle = nil): TvSetPenBrushAndFontElements;
 | 
						|
var
 | 
						|
  lLowerValue: String;
 | 
						|
  p: Integer;
 | 
						|
  fntName: String;
 | 
						|
begin
 | 
						|
  Result := [];
 | 
						|
  lLowerValue := LowerCase(AValue);
 | 
						|
  // 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
 | 
						|
    if ADestEntity <> nil then ADestEntity.Font.Color := ReadSVGColor(AValue);
 | 
						|
    if ADestStyle <> nil then ADestStyle.Font.Color := ReadSVGColor(AValue);
 | 
						|
    Result := Result + [spbfFontColor];
 | 
						|
  end
 | 
						|
  // But sometimes SVG also uses stroke! Oh no...
 | 
						|
  else if AKey = 'stroke' then
 | 
						|
  begin
 | 
						|
    if ADestEntity <> nil then ADestEntity.Font.Color := ReadSVGColor(AValue);
 | 
						|
    if lLowerValue <> 'none' then // sometimes we get a fill value, but a stroke=none after it...
 | 
						|
    begin
 | 
						|
      if ADestStyle <> nil then ADestStyle.Font.Color := ReadSVGColor(AValue);
 | 
						|
      Result := Result + [spbfFontColor];
 | 
						|
    end;
 | 
						|
    Result := Result + [spbfFontColor];
 | 
						|
  end
 | 
						|
  else if AKey = 'fill-opacity' then
 | 
						|
  begin
 | 
						|
    if ADestEntity <> nil then ADestEntity.Font.Color.Alpha := StrToInt(AValue)*$101;
 | 
						|
    if ADestStyle <> nil then ADestStyle.Font.Color.Alpha := StrToInt(AValue)*$101;
 | 
						|
    Result := Result + [spbfFontColor];
 | 
						|
  end
 | 
						|
  else if AKey = 'font-size' then
 | 
						|
  begin
 | 
						|
    if ADestEntity <> nil then ADestEntity.Font.Size := Round(StringWithUnitToFloat(AValue, sckXSize, suPT, suPT));
 | 
						|
    if ADestStyle <> nil then ADestStyle.Font.Size := Round(StringWithUnitToFloat(AValue, sckXSize, suPT, suPT));
 | 
						|
    Result := Result + [spbfFontSize];
 | 
						|
  end
 | 
						|
  else if AKey = 'font-family' then
 | 
						|
  begin
 | 
						|
    // Extract the font name
 | 
						|
    // To do: Check if font name exists in system. Use replacement fonts which
 | 
						|
    // may follow after the comma.
 | 
						|
    p := pos(',', AValue);
 | 
						|
    if p = 0 then
 | 
						|
      fntName := AValue else
 | 
						|
      fntName := trim(Copy(AValue, 1, p-1));
 | 
						|
    if ADestEntity <> nil then ADestEntity.Font.Name := fntName;
 | 
						|
    if ADestStyle <> nil then ADestStyle.Font.Name := fntName;
 | 
						|
    Result := Result + [spbfFontName];
 | 
						|
  end
 | 
						|
  else if AKey = 'font-weight' then
 | 
						|
  begin
 | 
						|
    case lLowerValue of
 | 
						|
    'bold', '700', '800', '900':
 | 
						|
    begin
 | 
						|
      if ADestEntity <> nil then ADestEntity.Font.Bold := True;
 | 
						|
      if ADestStyle <> nil then ADestStyle.Font.Bold := True;
 | 
						|
    end;
 | 
						|
    else
 | 
						|
      if ADestEntity <> nil then ADestEntity.Font.Bold := False;
 | 
						|
      if ADestStyle <> nil then ADestStyle.Font.Bold := False;
 | 
						|
    end;
 | 
						|
    Result := Result + [spbfFontBold];
 | 
						|
  end
 | 
						|
  // Other text attributes, non-font ones
 | 
						|
  else if AKey = 'text-anchor' then
 | 
						|
  begin
 | 
						|
    // Adjust according to the text-anchor, if necessary
 | 
						|
    case lLowerValue of
 | 
						|
    'start':
 | 
						|
    begin
 | 
						|
      if ADestEntity <> nil then ADestEntity.TextAnchor := vtaStart;
 | 
						|
      if ADestStyle <> nil then ADestStyle.TextAnchor := vtaStart;
 | 
						|
    end;
 | 
						|
    'middle':
 | 
						|
    begin
 | 
						|
      if ADestEntity <> nil then ADestEntity.TextAnchor := vtaMiddle;
 | 
						|
      if ADestStyle <> nil then ADestStyle.TextAnchor := vtaMiddle;
 | 
						|
    end;
 | 
						|
    'end':
 | 
						|
    begin
 | 
						|
      if ADestEntity <> nil then ADestEntity.TextAnchor := vtaEnd;
 | 
						|
      if ADestStyle <> nil then ADestStyle.TextAnchor := vtaEnd;
 | 
						|
    end;
 | 
						|
    end;
 | 
						|
    Result := Result + [spbfTextAnchor];
 | 
						|
  end;
 | 
						|
  if ADestStyle <> nil then
 | 
						|
    ADestStyle.SetElements := ADestStyle.SetElements + Result;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.ReadSVGGeneralStyleWithKeyAndValue(AData: TvVectorialPage;
 | 
						|
  AKey, AValue: string; ADestEntity: TvEntity);
 | 
						|
var
 | 
						|
  // transform
 | 
						|
  MA, MB, MC, MD, ME, MF: Double;
 | 
						|
  lMTranslateX, lMTranslateY, lMScaleX, lMScaleY, lMSkewX, lMSkewY, lMRotate: Double;
 | 
						|
  lTokenizer: TSVGPathTokenizer;
 | 
						|
  i: Integer;
 | 
						|
  lFunctionName, lParamStr: string;
 | 
						|
var
 | 
						|
  lMatrixElements: array of Double;
 | 
						|
begin
 | 
						|
  // Examples:
 | 
						|
  // transform="matrix(0.860815 0 -0 1.07602 339.302 489.171)"
 | 
						|
  // transform="scale(0.24) translate(0, 35)"
 | 
						|
  // transform="rotate(90)"
 | 
						|
  if AKey = 'transform' then
 | 
						|
  begin
 | 
						|
    lTokenizer := TSVGPathTokenizer.Create;
 | 
						|
    try
 | 
						|
      lTokenizer.TokenizeFunctions(AValue);
 | 
						|
 | 
						|
      i := 0;
 | 
						|
      while i < lTokenizer.Tokens.Count-1 do
 | 
						|
      begin
 | 
						|
        lFunctionName := Trim(lTokenizer.Tokens.Items[i].StrValue);
 | 
						|
        lParamStr := lTokenizer.Tokens.Items[i+1].StrValue;
 | 
						|
        lMatrixElements := ReadSpaceSeparatedFloats(lParamStr, ',');
 | 
						|
 | 
						|
        if lFunctionName = 'matrix' then
 | 
						|
        begin
 | 
						|
          ReadSVGTransformationMatrix(lParamStr, MA, MB, MC, MD, ME, MF);
 | 
						|
 | 
						|
          ConvertTransformationMatrixToOperations(MA, MB, MC, MD, ME, MF,
 | 
						|
            lMTranslateX, lMTranslateY, lMScaleX, lMScaleY, lMSkewX, lMSkewY, lMRotate);
 | 
						|
 | 
						|
          ConvertSVGDeltaToFPVDelta(nil,
 | 
						|
            lMTranslateX, lMTranslateY,
 | 
						|
            lMTranslateX, lMTranslateY);
 | 
						|
 | 
						|
          ADestEntity.Move(lMTranslateX, lMTranslateY);
 | 
						|
          ADestEntity.Scale(lMScaleX, lMScaleY);
 | 
						|
        end
 | 
						|
        else if lFunctionName = 'scale' then
 | 
						|
        begin
 | 
						|
          ;
 | 
						|
        end
 | 
						|
        else if lFunctionName = 'translate' then
 | 
						|
        begin
 | 
						|
          ConvertSVGDeltaToFPVDelta(nil,
 | 
						|
            lMatrixElements[0], lMatrixElements[1],
 | 
						|
            lMatrixElements[0], lMatrixElements[1]);
 | 
						|
          ADestEntity.Move(lMatrixElements[0], lMatrixElements[1]);
 | 
						|
        end
 | 
						|
        else if lFunctionName = 'rotate' then
 | 
						|
        begin
 | 
						|
          lMRotate := -DegToRad(lMatrixElements[0]);
 | 
						|
            // "-" because of orientation of svg coordinate system
 | 
						|
          lMTranslateX := 0;
 | 
						|
          lMTranslateY := 0;
 | 
						|
          if Length(lMatrixElements) > 1 then
 | 
						|
            lMTranslateX := lMatrixElements[1];
 | 
						|
          if Length(lMatrixElements) > 2 then
 | 
						|
            lMTranslateY := lMatrixElements[2];
 | 
						|
          ConvertSVGCoordinatesToFPVCoordinates(AData,
 | 
						|
            lMTranslateX, lMTranslateY, lMTranslateX, lMTranslateY);
 | 
						|
          ADestEntity.Rotate(lMRotate, Make3DPoint(lMTranslateX, lMTranslateY));
 | 
						|
        end;
 | 
						|
 | 
						|
        Inc(i, 2);
 | 
						|
      end;
 | 
						|
    finally
 | 
						|
      lTokenizer.Free;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.IsAttributeFromStyle(AStr: string): Boolean;
 | 
						|
begin
 | 
						|
  Result :=
 | 
						|
    // pen
 | 
						|
    (AStr = 'stroke') or (AStr = 'stroke-width') or
 | 
						|
    (AStr = 'stroke-dasharray') or (AStr = 'stroke-opacity') or
 | 
						|
    (AStr = 'stroke-linecap') or
 | 
						|
    // general
 | 
						|
    (AStr = 'transform') or
 | 
						|
    // brush
 | 
						|
    (AStr = 'fill') or (AStr = 'fill-opacity') or
 | 
						|
    // font
 | 
						|
    (AStr = 'font-size') or (AStr = 'font-family') or
 | 
						|
    (AStr = 'font-weight') or (AStr = 'text-anchor');
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.ApplyLayerStyles(AData: TvVectorialPage;
 | 
						|
  ADestEntity: TvEntity);
 | 
						|
var
 | 
						|
  lStringsKeys, lStringsValues: TStringList;
 | 
						|
  i, j: Integer;
 | 
						|
  lCurKey, lCurValue: String;
 | 
						|
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
 | 
						|
      lCurKey := lStringsKeys.Strings[j];
 | 
						|
      lCurValue := lStringsValues.Strings[j];
 | 
						|
      if ADestEntity is TvEntityWithPen then
 | 
						|
        ReadSVGPenStyleWithKeyAndValue(lCurKey, lCurValue, ADestEntity as TvEntityWithPen);
 | 
						|
      // Unfortunately SVG uses 'fill' for the text color =/ so we need to hack
 | 
						|
      // our way out of this ambiguity with the brush fill
 | 
						|
      if (ADestEntity is TvEntityWithPenAndBrush) and (not (ADestEntity is TvText)) then
 | 
						|
        ReadSVGBrushStyleWithKeyAndValue(lCurKey, lCurValue, ADestEntity as TvEntityWithPenAndBrush);
 | 
						|
      if ADestEntity is TvEntityWithPenBrushAndFont then
 | 
						|
        ReadSVGFontStyleWithKeyAndValue(lCurKey, lCurValue, ADestEntity as TvEntityWithPenBrushAndFont);
 | 
						|
      // transform
 | 
						|
      ReadSVGGeneralStyleWithKeyAndValue(AData, lCurKey, lCurValue, ADestEntity);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadSpaceSeparatedFloats(AInput: string;
 | 
						|
  AOtherSeparators: string): TDoubleArray;
 | 
						|
var
 | 
						|
  lStrings: TStringList;
 | 
						|
  lInputStr: string;
 | 
						|
  lMatrixElements: array of Double;
 | 
						|
  i: Integer;
 | 
						|
begin
 | 
						|
  lStrings := TStringList.Create;
 | 
						|
  try
 | 
						|
    lStrings.Delimiter := ' ';
 | 
						|
    // now other separator too
 | 
						|
    lInputStr := AInput;
 | 
						|
    for i := 1 to Length(AOtherSeparators) do
 | 
						|
    begin
 | 
						|
      lInputStr := StringReplace(lInputStr, AOtherSeparators[i], ' ', [rfReplaceAll]);
 | 
						|
    end;
 | 
						|
    //
 | 
						|
    lStrings.DelimitedText := lInputStr;
 | 
						|
    SetLength(lMatrixElements, lStrings.Count);
 | 
						|
    for i := 0 to lStrings.Count-1 do
 | 
						|
    begin
 | 
						|
      lMatrixElements[i] := StringWithUnitToFloat(lStrings.Strings[i]);
 | 
						|
    end;
 | 
						|
 | 
						|
    Result := lMatrixElements;
 | 
						|
  finally
 | 
						|
    lStrings.Free;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
class function TvSVGVectorialReader.ReadSpaceSeparatedStrings(AInput: string;
 | 
						|
  AOtherSeparators: string): TStringList;
 | 
						|
var
 | 
						|
  i: Integer;
 | 
						|
  lInputStr: String;
 | 
						|
begin
 | 
						|
  Result := TStringList.Create;
 | 
						|
  Result.Delimiter := ' ';
 | 
						|
  // now other separator too
 | 
						|
  lInputStr := AInput;
 | 
						|
  for i := 1 to Length(AOtherSeparators) do
 | 
						|
  begin
 | 
						|
    lInputStr := StringReplace(lInputStr, AOtherSeparators[i], ' ', [rfReplaceAll]);
 | 
						|
  end;
 | 
						|
  //
 | 
						|
  Result.DelimitedText := lInputStr;
 | 
						|
end;
 | 
						|
 | 
						|
// transform="matrix(0.860815 0 -0 1.07602 354.095 482.177)"=>matrix(a, b, c, d, e, f)
 | 
						|
// Looks like that some Apps separate the matrix elements with spaces
 | 
						|
// But Inkscape uses commas! transform="matrix(1.2,0,0,1.2,9.48633,-2.25781)"
 | 
						|
// See http://apike.ca/prog_svg_transform.html
 | 
						|
procedure TvSVGVectorialReader.ReadSVGTransformationMatrix(
 | 
						|
  AMatrix: string; out AA, AB, AC, AD, AE, AF: Double);
 | 
						|
var
 | 
						|
  lMatrixElements: array of Double;
 | 
						|
begin
 | 
						|
  lMatrixElements := ReadSpaceSeparatedFloats(AMatrix, ',');
 | 
						|
 | 
						|
  AA := lMatrixElements[0];
 | 
						|
  AB := lMatrixElements[1];
 | 
						|
  AC := lMatrixElements[2];
 | 
						|
  AD := lMatrixElements[3];
 | 
						|
  AE := lMatrixElements[4];
 | 
						|
  AF := lMatrixElements[5];
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.ApplyCSSClass(AData: TvVectorialPage;
 | 
						|
  AValue: string; ADestEntity: TvEntityWithPen);
 | 
						|
var
 | 
						|
  i, j: Integer;
 | 
						|
  lCurStyle: TSVG_CSS_Style;
 | 
						|
  lAllClasses: T10Strings;
 | 
						|
begin
 | 
						|
  lAllClasses := SeparateString(AValue, ' ');
 | 
						|
  for i := 0 to High(lAllClasses)-1 do
 | 
						|
  begin
 | 
						|
    if lAllClasses[i] = '' then Break;
 | 
						|
    for j := 0 to FCSSDefs.Count-1 do
 | 
						|
    begin
 | 
						|
      lCurStyle := TSVG_CSS_Style(FCSSDefs.Items[j]);
 | 
						|
      if lCurStyle.MatchesClass(lAllClasses[i]) then
 | 
						|
      begin
 | 
						|
        lCurStyle.ApplyIntoEntity(ADestEntity);
 | 
						|
        Break;
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.IsEntityStyleField(AFieldName: string): Boolean;
 | 
						|
begin
 | 
						|
  Result := (AFieldName = 'style') or (AFieldName = 'class') or
 | 
						|
    IsAttributeFromStyle(AFieldName);
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadEntityStyleField(AData: TvVectorialPage;
 | 
						|
  AFieldName, AFieldValue: string; ADestEntity: TvEntityWithPen;
 | 
						|
  ADestStyle: TvStyle; AUseFillAsPen: Boolean): TvSetPenBrushAndFontElements;
 | 
						|
begin
 | 
						|
  case AFieldName of
 | 
						|
  'style': Result := ReadSVGStyle(AData, AFieldValue, ADestEntity, ADestStyle,
 | 
						|
             AUseFillAsPen);
 | 
						|
  'class': ApplyCSSClass(AData, AFieldValue, ADestEntity);
 | 
						|
  else
 | 
						|
    ReadSVGPenStyleWithKeyAndValue(AFieldName, AFieldValue, ADestEntity);
 | 
						|
    if ADestEntity is TvEntityWithPenAndBrush then
 | 
						|
      ReadSVGBrushStyleWithKeyAndValue(AFieldName, AFieldValue, TvEntityWithPenAndBrush(ADestEntity));
 | 
						|
    ReadSVGGeneralStyleWithKeyAndValue(AData, AFieldName, AFieldValue, ADestEntity);
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.GetTextContentFromNode(ANode: TDOMNode): string;
 | 
						|
var
 | 
						|
  i: Integer;
 | 
						|
begin
 | 
						|
  Result := '';
 | 
						|
  for i := 0 to ANode.ChildNodes.Count-1 do
 | 
						|
  begin
 | 
						|
    if ANode.ChildNodes.Item[i] is TDOMText then
 | 
						|
      Result := ANode.ChildNodes.Item[i].NodeValue;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
// <linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%">
 | 
						|
procedure TvSVGVectorialReader.ReadDefs_LinearGradient(ADest: TvEntityWithPenAndBrush;
 | 
						|
  ANode: TDOMNode; AData: TvVectorialPage);
 | 
						|
var
 | 
						|
  lAttrName, lAttrValue, lNodeName: DOMString;
 | 
						|
  i, len: Integer;
 | 
						|
  lCurSubNode: TDOMNode;
 | 
						|
  lBrushEntity, lCurBrush: TvEntityWithPenAndBrush;
 | 
						|
  lGradientColor: TvGradientColor;
 | 
						|
  x1, y1, x2, y2: Double;
 | 
						|
begin
 | 
						|
  x1 := 0;
 | 
						|
  x2 := 0;
 | 
						|
  y1 := 0;
 | 
						|
  y2 := 0;
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lAttrName := lowercase(ANode.Attributes.Item[i].NodeName);
 | 
						|
    lAttrValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if lAttrName = 'id' then
 | 
						|
      ADest.Name := lAttrValue
 | 
						|
    else
 | 
						|
    if lAttrName = 'x1' then
 | 
						|
    begin
 | 
						|
      if lAttrValue[Length(lAttrValue)] = '%' then
 | 
						|
        Include(ADest.Brush.Gradient_flags, gfRelStartX);
 | 
						|
      x1 := StringWithPercentToFloat(lAttrValue);
 | 
						|
    end else
 | 
						|
    if lAttrName = 'x2' then
 | 
						|
    begin
 | 
						|
      if lAttrValue[Length(lAttrValue)] = '%' then
 | 
						|
        Include(ADest.Brush.Gradient_flags, gfRelEndX);
 | 
						|
      x2 := StringWithPercentToFloat(lAttrValue);
 | 
						|
    end else
 | 
						|
    if lAttrName = 'y1' then
 | 
						|
    begin
 | 
						|
      if lAttrValue[Length(lAttrValue)] = '%' then
 | 
						|
        Include(ADest.Brush.Gradient_flags, gfRelStartY);
 | 
						|
      y1 := StringWithPercentToFloat(lAttrValue);
 | 
						|
    end else
 | 
						|
    if lAttrName = 'y2' then
 | 
						|
    begin
 | 
						|
      if lAttrValue[Length(lAttrValue)] = '%' then
 | 
						|
        Include(ADest.Brush.Gradient_flags, gfRelEndY);
 | 
						|
      y2 := StringWithPercentToFloat(lAttrValue);
 | 
						|
    end else
 | 
						|
    if lAttrName = 'gradientunits' then
 | 
						|
    begin
 | 
						|
      lAttrValue := LowerCase(lAttrValue);
 | 
						|
      if lAttrValue = 'userspaceonuse' then
 | 
						|
        Include(ADest.Brush.Gradient_flags, gfRelToUserSpace)
 | 
						|
      else if lAttrValue = 'objectboundingbox' then
 | 
						|
        Exclude(ADest.Brush.Gradient_flags, gfRelToUserSpace);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
  ADest.Brush.Gradient_start.X := x1;
 | 
						|
  ADest.Brush.Gradient_end.X := x2;
 | 
						|
  ADest.Brush.Gradient_start.Y := y1;
 | 
						|
  ADest.Brush.Gradient_end.Y := y2;
 | 
						|
  ConvertSVGCoordinatesToFPVCoordinates(AData, x1, y1, x1, y1);
 | 
						|
  ConvertSVGCoordinatesToFPVCoordinates(AData, x2, y2, x2, y2);
 | 
						|
  if not (gfRelStartX in ADest.Brush.Gradient_flags) then
 | 
						|
    ADest.Brush.Gradient_start.X := x1;
 | 
						|
  if not (gfRelEndX in ADest.Brush.Gradient_flags) then
 | 
						|
    ADest.Brush.Gradient_end.X := x2;
 | 
						|
  if not (gfRelStartY in ADest.Brush.Gradient_flags) then
 | 
						|
    ADest.Brush.Gradient_start.Y := y1;
 | 
						|
  if not (gfRelEndY in ADest.Brush.Gradient_flags) then
 | 
						|
    ADest.Brush.Gradient_end.Y := y2;
 | 
						|
  if (ADest.Brush.Gradient_start.X = 0) and
 | 
						|
     (ADest.Brush.Gradient_start.Y = 0) and
 | 
						|
     (ADest.Brush.Gradient_end.X = 0) and
 | 
						|
     (ADest.Brush.Gradient_end.Y = 0) then
 | 
						|
  begin
 | 
						|
    ADest.Brush.Gradient_start.X := 0.0;
 | 
						|
    ADest.Brush.Gradient_start.Y := 0.0;
 | 
						|
    ADest.Brush.Gradient_end.X := 1.0;
 | 
						|
    ADest.Brush.Gradient_end.Y := 1.0;
 | 
						|
  end;
 | 
						|
  if ADest.Brush.Gradient_start.X = ADest.Brush.Gradient_end.X then
 | 
						|
    ADest.Brush.Kind := bkVerticalGradient
 | 
						|
  else if ADest.Brush.Gradient_start.Y = ADest.Brush.Gradient_end.Y then
 | 
						|
    ADest.Brush.Kind := bkHorizontalGradient
 | 
						|
  else
 | 
						|
    ADest.Brush.Kind := bkOtherLinearGradient;
 | 
						|
 | 
						|
  // <stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
 | 
						|
  // <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
 | 
						|
  lCurSubNode := ANode.FirstChild;
 | 
						|
  while Assigned(lCurSubNode) do
 | 
						|
  begin
 | 
						|
    lNodeName := LowerCase(lCurSubNode.NodeName);
 | 
						|
    if lNodeName = 'stop' then begin
 | 
						|
      for i := 0 to lCurSubNode.Attributes.Length - 1 do
 | 
						|
      begin
 | 
						|
        lAttrName := lCurSubNode.Attributes.Item[i].NodeName;
 | 
						|
        lAttrValue := lCurSubNode.Attributes.Item[i].NodeValue;
 | 
						|
        if lAttrName = 'offset' then
 | 
						|
          lGradientColor.Position := StringWithPercentToFloat(lAttrValue)
 | 
						|
          // use as fraction 0..1
 | 
						|
        else if lAttrName = 'style' then
 | 
						|
          lGradientColor.Color := ReadSVGGradientColorStyle(lAttrValue)
 | 
						|
        else if lAttrName = 'stop-color' then
 | 
						|
          lGradientColor.Color := ReadSVGColor(lAttrValue);
 | 
						|
      end;
 | 
						|
      len := Length(ADest.Brush.Gradient_colors);
 | 
						|
      SetLength(ADest.Brush.Gradient_colors, Len+1);
 | 
						|
      ADest.Brush.Gradient_colors[len] := lGradientColor;
 | 
						|
    end;
 | 
						|
    lCurSubNode := lCurSubNode.NextSibling;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.ReadDefs_CSS(ANode: TDOMNode);
 | 
						|
var
 | 
						|
  lContent, lCurName, lCurData: string;
 | 
						|
  lCurChar: AnsiChar;
 | 
						|
  i, lParserState: Integer;
 | 
						|
  lCurStyle: TSVG_CSS_Style;
 | 
						|
begin
 | 
						|
  lContent := Trim(ANode.TextContent);
 | 
						|
  lContent := StringReplace(lContent, '<![CDATA[', '', []);
 | 
						|
  lContent := StringReplace(lContent, ']]>', '', []);
 | 
						|
  lContent := Trim(lContent);
 | 
						|
  lParserState := 0;
 | 
						|
  for i := 1 to Length(lContent) do
 | 
						|
  begin
 | 
						|
    lCurChar := lContent[i];
 | 
						|
    case lParserState of
 | 
						|
    0: // filling class name
 | 
						|
    begin
 | 
						|
      if lCurChar = '{' then lParserState := 1
 | 
						|
      else lCurName += lCurChar;
 | 
						|
    end;
 | 
						|
    1: // filling class data
 | 
						|
    begin
 | 
						|
      if lCurChar = '}' then
 | 
						|
      begin
 | 
						|
        lCurStyle := TSVG_CSS_Style.Create;
 | 
						|
        lCurStyle.CSSName := Trim(lCurName);
 | 
						|
        lCurStyle.CSSData := Trim(lCurData);
 | 
						|
        FCSSDefs.Add(lCurStyle);
 | 
						|
        lCurStyle.ParseCSSData(Self);
 | 
						|
        lParserState := 0;
 | 
						|
        lCurName := '';
 | 
						|
        lCurData := '';
 | 
						|
      end
 | 
						|
      else lCurData += lCurChar;
 | 
						|
    end;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.ReadDefsFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument);
 | 
						|
var
 | 
						|
  lEntityName: DOMString;
 | 
						|
  lBlock: TvBlock;
 | 
						|
  lPreviousLayer: TvEntityWithSubEntities;
 | 
						|
  lAttrName, lAttrValue, lNodeName, lEntityValue: DOMString;
 | 
						|
  lLayerName: String;
 | 
						|
  i, len: Integer;
 | 
						|
  lCurNode, lCurSubNode: TDOMNode;
 | 
						|
  lBrushEntity, lCurBrush: TvEntityWithPenAndBrush;
 | 
						|
  lCurEntity: TvEntity;
 | 
						|
  lAttrValue_Double: Double;
 | 
						|
begin
 | 
						|
  lCurNode := ANode.FirstChild;
 | 
						|
  while Assigned(lCurNode) do
 | 
						|
  begin
 | 
						|
    lEntityName := LowerCase(lCurNode.NodeName);
 | 
						|
    lEntityValue := lCurNode.NodeValue;
 | 
						|
    case lEntityName of
 | 
						|
      'radialgradient':
 | 
						|
      begin
 | 
						|
        lBrushEntity := TvEntityWithPenAndBrush.Create(nil);
 | 
						|
 | 
						|
        // First copy everything we can from any xlink:href
 | 
						|
        for i := 0 to lCurNode.Attributes.Length - 1 do
 | 
						|
        begin
 | 
						|
          lAttrName := LowerCase(lCurNode.Attributes.Item[i].NodeName);
 | 
						|
          lAttrValue := lCurNode.Attributes.Item[i].NodeValue;
 | 
						|
          if lAttrName = 'xlink:href' then
 | 
						|
          begin
 | 
						|
            lAttrValue := StringReplace(Trim(lAttrValue), '#', '', []);
 | 
						|
            lCurBrush := FindBrushDef_WithName(lAttrValue);
 | 
						|
            if lCurBrush <> nil then
 | 
						|
              lBrushEntity.Brush := lCurBrush.Brush;
 | 
						|
          end;
 | 
						|
        end;
 | 
						|
 | 
						|
        // Now linear gradient properties
 | 
						|
        ReadDefs_LinearGradient(lBrushEntity, lCurNode, AData);
 | 
						|
 | 
						|
        // Now process our own properties
 | 
						|
 | 
						|
        lBrushEntity.Brush.Kind := bkRadialGradient;
 | 
						|
 | 
						|
        // <radialGradient id="grad1" cx="50%" cy="50%" r="50%" fx="50%" fy="50%">
 | 
						|
        // or
 | 
						|
        // <linearGradient id="linearGradient6038">
 | 
						|
        // <stop style="stop-color:#ffffff;stop-opacity:1;" offset="0" id="stop6040" />
 | 
						|
        // <stop style="stop-color:#000000;stop-opacity:0;" offset="1" id="stop6042" />
 | 
						|
        // </linearGradient>
 | 
						|
        // <radialGradient inkscape:collect="always" xlink:href="#linearGradient6038"
 | 
						|
        //  id="radialGradient3333" gradientUnits="userSpaceOnUse"
 | 
						|
        //  gradientTransform="matrix(0.05999054,1.120093,-1.0249935,0.05489716,995.9708,109.4759)"
 | 
						|
        //  cx="406.62762" cy="567.12799" fx="406.62762" fy="567.12799" r="37.15749" />
 | 
						|
        for i := 0 to lCurNode.Attributes.Length - 1 do
 | 
						|
        begin
 | 
						|
          lAttrName := lCurNode.Attributes.Item[i].NodeName;
 | 
						|
          lAttrValue := lCurNode.Attributes.Item[i].NodeValue;
 | 
						|
          case lAttrName of
 | 
						|
          'cx', 'cy', 'fx', 'fy', 'r':
 | 
						|
            lAttrValue_Double := StringWithUnitToFloat(lAttrValue, sckUnknown, suMM, suMM);
 | 
						|
          end;
 | 
						|
 | 
						|
          case lAttrName of
 | 
						|
          'id': lBrushEntity.Name := lAttrValue;
 | 
						|
          'cx': lBrushEntity.Brush.Gradient_cx := lAttrValue_Double;
 | 
						|
          'cy': lBrushEntity.Brush.Gradient_cy := lAttrValue_Double;
 | 
						|
          'r':  lBrushEntity.Brush.Gradient_r  := lAttrValue_Double;
 | 
						|
          'fx': lBrushEntity.Brush.Gradient_fx := lAttrValue_Double;
 | 
						|
          'fy': lBrushEntity.Brush.Gradient_fy := lAttrValue_Double;
 | 
						|
          end;
 | 
						|
          //lBrushEntity.Gradient_cx_Unit, Gradient_cy_Unit, Gradient_r_Unit, Gradient_fx_Unit, Gradient_fy_Unit
 | 
						|
        end;
 | 
						|
 | 
						|
        FBrushDefs.Add(lBrushEntity);
 | 
						|
      end;
 | 
						|
      {
 | 
						|
      <linearGradient id="grad1" x1="0%" y1="0%" x2="0%" y2="100%">
 | 
						|
        <stop offset="0%" style="stop-color:rgb(255,255,0);stop-opacity:1" />
 | 
						|
        <stop offset="100%" style="stop-color:rgb(255,0,0);stop-opacity:1" />
 | 
						|
      </linearGradient>
 | 
						|
      }
 | 
						|
      'lineargradient':
 | 
						|
      begin
 | 
						|
        lBrushEntity := TvEntityWithPenAndBrush.Create(nil);
 | 
						|
 | 
						|
        ReadDefs_LinearGradient(lBrushEntity, lCurNode, AData);
 | 
						|
 | 
						|
        FBrushDefs.Add(lBrushEntity);
 | 
						|
      end;
 | 
						|
      // Sometime entities are also put in the defs
 | 
						|
      'circle', 'ellipse', 'g', 'line', 'path',
 | 
						|
      'polygon', 'polyline', 'rect', 'text', 'use', 'symbol':
 | 
						|
      begin
 | 
						|
        lLayerName := '';
 | 
						|
        lBlock := TvBlock.Create(nil);
 | 
						|
 | 
						|
        // pre-load attribute reader, to get the block name
 | 
						|
        for i := 0 to lCurNode.Attributes.Length - 1 do
 | 
						|
        begin
 | 
						|
          lAttrName := lCurNode.Attributes.Item[i].NodeName;
 | 
						|
          if lAttrName = 'id' then
 | 
						|
          begin
 | 
						|
            lLayerName := lCurNode.Attributes.Item[i].NodeValue;
 | 
						|
            lBlock.Name := lLayerName;
 | 
						|
          end;
 | 
						|
        end;
 | 
						|
 | 
						|
        // Skip g without id, create instead items for his sub-items
 | 
						|
        if (lEntityName = 'g') and (lLayerName = '') then
 | 
						|
        begin
 | 
						|
          lCurSubNode := lCurNode.FirstChild;
 | 
						|
          while Assigned(lCurSubNode) do
 | 
						|
          begin
 | 
						|
            lCurEntity := ReadEntityFromNode(lCurSubNode, AData, ADoc);
 | 
						|
            if lCurEntity <> nil then
 | 
						|
              AData.AddEntity(lCurEntity);
 | 
						|
            lCurSubNode := lCurSubNode.NextSibling;
 | 
						|
          end;
 | 
						|
          lBlock.Free;
 | 
						|
          lCurNode := lCurNode.NextSibling;
 | 
						|
          Continue;
 | 
						|
        end;
 | 
						|
 | 
						|
        lPreviousLayer := AData.GetCurrentLayer();
 | 
						|
        AData.AddEntity(lBlock);
 | 
						|
        AData.SetCurrentLayer(lBlock);
 | 
						|
        //
 | 
						|
        lCurEntity := ReadEntityFromNode(lCurNode, AData, ADoc);
 | 
						|
        if lCurEntity <> nil then
 | 
						|
          AData.AddEntity(lCurEntity);
 | 
						|
        //
 | 
						|
        AData.SetCurrentLayer(lPreviousLayer);
 | 
						|
      end;
 | 
						|
      // CSS styles
 | 
						|
      'style':
 | 
						|
      begin
 | 
						|
        for i := 0 to lCurNode.Attributes.Length - 1 do
 | 
						|
        begin
 | 
						|
          lAttrName := LowerCase(lCurNode.Attributes.Item[i].NodeName);
 | 
						|
          lAttrValue := lCurNode.Attributes.Item[i].NodeValue;
 | 
						|
          if (lAttrName = 'type') and (lAttrValue = 'text/css') then
 | 
						|
          begin
 | 
						|
            ReadDefs_CSS(lCurNode);
 | 
						|
          end;
 | 
						|
        end;
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
 | 
						|
    lCurNode := lCurNode.NextSibling;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadEntityFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  lEntityName: DOMString;
 | 
						|
begin
 | 
						|
  Result := nil;
 | 
						|
  lEntityName := LowerCase(ANode.NodeName);
 | 
						|
  case lEntityName of
 | 
						|
    'circle': Result := ReadCircleFromNode(ANode, AData, ADoc);
 | 
						|
    'defs': ReadDefsFromNode(ANode, AData, ADoc);
 | 
						|
    'ellipse': Result := ReadEllipseFromNode(ANode, AData, ADoc);
 | 
						|
    'frame': Result := ReadFrameFromNode(ANode, AData, ADoc);
 | 
						|
    'g': ReadLayerFromNode(ANode, AData, ADoc);
 | 
						|
    'image': Result := ReadImageFromNode(ANode, AData, ADoc);
 | 
						|
    'line': Result := ReadLineFromNode(ANode, AData, ADoc);
 | 
						|
    'path': Result := ReadPathFromNode(ANode, AData, ADoc);
 | 
						|
    'polygon', 'polyline': Result := ReadPolyFromNode(ANode, AData, ADoc);
 | 
						|
    'rect': Result := ReadRectFromNode(ANode, AData, ADoc);
 | 
						|
    'symbol': Result := ReadSymbolFromNode(ANode, AData, ADoc);
 | 
						|
    'text': Result := ReadTextFromNode(ANode, AData, ADoc);
 | 
						|
    'use': Result := ReadUseFromNode(ANode, AData, ADoc);
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadCircleFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  cx, cy, cr, tmp: double;
 | 
						|
  lCircle: TvCircle;
 | 
						|
  i: Integer;
 | 
						|
  lNodeName, lNodeValue: DOMString;
 | 
						|
begin
 | 
						|
  cx := 0.0;
 | 
						|
  cy := 0.0;
 | 
						|
  cr := 0.0;
 | 
						|
 | 
						|
  lCircle := TvCircle.Create(nil);
 | 
						|
  // SVG entities start without any pen drawing, but with a black brush
 | 
						|
  lCircle.Pen.Style := psClear;
 | 
						|
  lCircle.Brush.Style := bsSolid;
 | 
						|
  lCircle.Brush.Color := colBlack;
 | 
						|
  // Apply the layer style
 | 
						|
  ApplyLayerStyles(AData, lCircle);
 | 
						|
 | 
						|
  // read the attributes
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if lNodeName = 'cx' then
 | 
						|
      cx := StringWithUnitToFloat(lNodeValue) //, sckX, suPX, suMM)
 | 
						|
    else if lNodeName = 'cy' then
 | 
						|
      cy := StringWithUnitToFloat(lNodeValue) //, sckY, suPX, suMM)
 | 
						|
    else if lNodeName = 'r' then
 | 
						|
      cr := StringWithUnitToFloat(lNodeValue) //, sckXSize, suPX, suMM)
 | 
						|
    else if lNodeName = 'id' then
 | 
						|
      lCircle.Name := lNodeValue;
 | 
						|
  end;
 | 
						|
 | 
						|
  ConvertSVGCoordinatesToFPVCoordinates(AData, cx, cy, cx, cy);
 | 
						|
  ConvertSVGSizeToFPVSize(AData, cr, cr, lCircle.Radius, tmp);
 | 
						|
  lCircle.X := lCircle.X + cx;
 | 
						|
  lCircle.Y := lCircle.Y + cy;
 | 
						|
 | 
						|
  // Make sure that transformations are read after geometry and position
 | 
						|
  // of cirlce is known.
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if lNodeName = 'style' then
 | 
						|
      ReadSVGStyle(AData, lNodeValue, lCircle)
 | 
						|
    else if IsAttributeFromStyle(lNodeName) then
 | 
						|
    begin
 | 
						|
      ReadSVGPenStyleWithKeyAndValue(lNodeName, lNodeValue, lCircle);
 | 
						|
      ReadSVGBrushStyleWithKeyAndValue(lNodeName, lNodeValue, lCircle);
 | 
						|
      ReadSVGGeneralStyleWithKeyAndValue(AData, lNodeName, lNodeValue, lCircle);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  Result := lCircle;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadEllipseFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  cx, cy, crx, cry: double;
 | 
						|
  lEllipse: TvEllipse;
 | 
						|
  i: Integer;
 | 
						|
  lNodeName, lNodeValue: DOMString;
 | 
						|
begin
 | 
						|
  cx := 0.0;
 | 
						|
  cy := 0.0;
 | 
						|
  crx := 0.0;
 | 
						|
  cry := 0.0;
 | 
						|
 | 
						|
  lEllipse := TvEllipse.Create(nil);
 | 
						|
  // SVG entities start without any pen drawing, but with a black brush
 | 
						|
  lEllipse.Pen.Style := psClear;
 | 
						|
  lEllipse.Brush.Style := bsSolid;
 | 
						|
  lEllipse.Brush.Color := colBlack;
 | 
						|
  // Apply the layer style
 | 
						|
  ApplyLayerStyles(AData, lEllipse);
 | 
						|
 | 
						|
  // read the attributes
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if  lNodeName = 'cx' then
 | 
						|
      cx := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'cy' then
 | 
						|
      cy := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'rx' then
 | 
						|
      crx := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'ry' then
 | 
						|
      cry := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'id' then
 | 
						|
      lEllipse.Name := ANode.Attributes.Item[i].NodeValue;
 | 
						|
  end;
 | 
						|
 | 
						|
  ConvertSVGCoordinatesToFPVCoordinates(AData, cx, cy, lEllipse.X, lEllipse.Y);
 | 
						|
  ConvertSVGSizeToFPVSize(AData, crx, cry, lEllipse.HorzHalfAxis, lEllipse.VertHalfAxis);
 | 
						|
 | 
						|
  // Make sure that transformations are read after geometry and position
 | 
						|
  // of ellipse is known.
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if lNodeName = 'style' then
 | 
						|
      ReadSVGStyle(AData, lNodeValue, lEllipse)
 | 
						|
    else if IsAttributeFromStyle(lNodeName) then
 | 
						|
    begin
 | 
						|
      ReadSVGPenStyleWithKeyAndValue(lNodeName, lNodeValue, lEllipse);
 | 
						|
      ReadSVGBrushStyleWithKeyAndValue(lNodeName, lNodeValue, lEllipse);
 | 
						|
      ReadSVGGeneralStyleWithKeyAndValue(AData, lNodeName, lNodeValue, lEllipse);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  Result := lEllipse;
 | 
						|
end;
 | 
						|
 | 
						|
{
 | 
						|
<draw:frame draw:style-name="gr5" draw:layer="layout" svg:width="9.024cm" svg:height="0.963cm"
 | 
						|
    draw:transform="rotate (-1.58737695468884) translate (2.3cm 1.197cm)">
 | 
						|
   <draw:text-box>
 | 
						|
      <text:p>Jump opposite arm and leg up</text:p>
 | 
						|
   </draw:text-box>
 | 
						|
</draw:frame>
 | 
						|
 | 
						|
<draw:frame draw:style-name="gr5" draw:layer="layout" svg:width="15.07cm" svg:height="1.115cm" svg:x="2.6cm" svg:y="26.9cm">
 | 
						|
   <draw:text-box>
 | 
						|
      <text:p>
 | 
						|
         <text:span text:style-name="T1">Back muscle movement</text:span>
 | 
						|
         <text:span text:style-name="T2">opposite</text:span>
 | 
						|
         <text:span text:style-name="T3">arm</text:span>
 | 
						|
         and
 | 
						|
         <text:span text:style-name="T3">leg</text:span>
 | 
						|
         up
 | 
						|
      </text:p>
 | 
						|
   </draw:text-box>
 | 
						|
</draw:frame>
 | 
						|
}
 | 
						|
function TvSVGVectorialReader.ReadFrameFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  lTextStr: string = '';
 | 
						|
  lx, ly: double;
 | 
						|
  lText: TvText;
 | 
						|
  i: Integer;
 | 
						|
  lNodeName, lNodeValue, lSubNodeName, lSubNodeValue: DOMString;
 | 
						|
  lCurNode, lCurSubNode: TDOMNode;
 | 
						|
begin
 | 
						|
  lx := 0.0;
 | 
						|
  ly := 0.0;
 | 
						|
  Result := nil;
 | 
						|
 | 
						|
  lText := nil;//TvText.Create(nil);
 | 
						|
 | 
						|
  // Apply the layer style
 | 
						|
  ApplyLayerStyles(AData, lText);
 | 
						|
 | 
						|
  // read the attributes
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if  lNodeName = 'svg:x' then
 | 
						|
      lx := lx + StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'svg:y' then
 | 
						|
      ly := ly + StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'draw:style-name' then
 | 
						|
      ReadSVGStyle(AData, lNodeValue, lText);
 | 
						|
  end;
 | 
						|
 | 
						|
  // Get the text contents
 | 
						|
  lCurNode := Anode.FirstChild;
 | 
						|
  while lCurNode <> nil do
 | 
						|
  begin
 | 
						|
    lNodeName := LowerCase(lCurNode.NodeName);
 | 
						|
    if lNodeName <> 'draw:text-box' then Continue;
 | 
						|
 | 
						|
    lCurSubNode := lCurNode.FirstChild;
 | 
						|
    while lCurSubNode <> nil do
 | 
						|
    begin
 | 
						|
      lSubNodeName := LowerCase(lCurSubNode.NodeName);
 | 
						|
      if lSubNodeName <> 'draw:text-box' then Continue;
 | 
						|
 | 
						|
      lText := ReadFrameTextFromNode(lCurNode, AData, ADoc) as TvText;
 | 
						|
      Break;
 | 
						|
 | 
						|
      lCurSubNode := lCurSubNode.NextSibling;
 | 
						|
    end;
 | 
						|
    if lText <> nil then Break;
 | 
						|
 | 
						|
    lCurNode := lCurNode.NextSibling;
 | 
						|
  end;
 | 
						|
 | 
						|
  if lText = nil then Exit;
 | 
						|
 | 
						|
  // Set the coordinates
 | 
						|
  ConvertSVGCoordinatesToFPVCoordinates(
 | 
						|
        AData, lx, ly, lText.X, lText.Y);
 | 
						|
 | 
						|
  // Finalization
 | 
						|
  Result := lText;
 | 
						|
end;
 | 
						|
 | 
						|
{
 | 
						|
   <text:p>Jump opposite arm and leg up</text:p>
 | 
						|
 | 
						|
   <text:p>
 | 
						|
      <text:span text:style-name="T1">Back muscle movement</text:span>
 | 
						|
      <text:span text:style-name="T2">opposite</text:span>
 | 
						|
      <text:span text:style-name="T3">arm</text:span>
 | 
						|
      and
 | 
						|
      <text:span text:style-name="T3">leg</text:span>
 | 
						|
      up
 | 
						|
   </text:p>
 | 
						|
}
 | 
						|
function TvSVGVectorialReader.ReadFrameTextFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  lTextStr: string = '';
 | 
						|
  lx, ly: double;
 | 
						|
  lText: TvText;
 | 
						|
  //i: Integer;
 | 
						|
  //lNodeName, lNodeValue: DOMString;
 | 
						|
  //lCurNode: TDOMNode;
 | 
						|
begin
 | 
						|
  lx := 0.0;
 | 
						|
  ly := 0.0;
 | 
						|
 | 
						|
  lText := TvText.Create(nil);
 | 
						|
 | 
						|
  // Apply the layer style
 | 
						|
  ApplyLayerStyles(AData, lText);
 | 
						|
 | 
						|
  {// read the attributes
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if  lNodeName = 'x' then
 | 
						|
      lx := lx + StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'y' then
 | 
						|
      ly := ly + StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'id' then
 | 
						|
      lText.Name := lNodeValue
 | 
						|
    else if lNodeName = 'style' then
 | 
						|
      ReadSVGStyle(lNodeValue, 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);
 | 
						|
 | 
						|
  // Recover the position if there was a transformation matrix
 | 
						|
  //lx := lx + lText.X;
 | 
						|
  //ly := ly + lText.Y;
 | 
						|
 | 
						|
  // Set the coordinates
 | 
						|
  ConvertSVGCoordinatesToFPVCoordinates(
 | 
						|
        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
 | 
						|
  Result := lText;
 | 
						|
end;
 | 
						|
 | 
						|
// <image width="92.5" x="0" y="0" height="76.0429"
 | 
						|
//  xlink:href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAKMAAACGCAYAA
 | 
						|
//  ACxDToF......" clip-path="url(#Clip0)" transform="matrix(1 0 0 1 0 0)"/>
 | 
						|
function TvSVGVectorialReader.ReadImageFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  lImage: TvRasterImage;
 | 
						|
  lx, ly, lw, lh: Double;
 | 
						|
  lTransformX, lTransformY, lTransformW, lTransformH: Double;
 | 
						|
  i: Integer;
 | 
						|
  lNodeName, lNodeValue: DOMString;
 | 
						|
  lImageDataParts: TStringList;
 | 
						|
  lImageDataBase64: string;
 | 
						|
  lImageData: array of Byte;
 | 
						|
  lImageDataStream: TMemoryStream;
 | 
						|
  lImageReader: TFPCustomImageReader;
 | 
						|
begin
 | 
						|
  lImage := TvRasterImage.Create(nil);
 | 
						|
  lx := 0;
 | 
						|
  ly := 0;
 | 
						|
  lw := 0;
 | 
						|
  lh := 0;
 | 
						|
  lImage.Width := 1000;
 | 
						|
  lImage.Height := 1000;
 | 
						|
 | 
						|
  // Apply the layer style
 | 
						|
  //ApplyLayerStyles(lEllipse);
 | 
						|
 | 
						|
  // read the attributes
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if  lNodeName = 'x' then
 | 
						|
      lx := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'y' then
 | 
						|
      ly := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'width' then
 | 
						|
      lw := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'height' then
 | 
						|
      lh := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'xlink:href' then
 | 
						|
    begin
 | 
						|
      lImageDataParts := ReadSpaceSeparatedStrings(lNodeValue, ':;,');
 | 
						|
      try
 | 
						|
        if (lImageDataParts.Strings[0] = 'data') and
 | 
						|
           (lImageDataParts.Strings[1] = 'image/png') and
 | 
						|
           (lImageDataParts.Strings[2] = 'base64') then
 | 
						|
        begin
 | 
						|
          lImageReader := TFPReaderPNG.Create;
 | 
						|
          lImageDataStream := TMemoryStream.Create;
 | 
						|
          try
 | 
						|
            lImageDataBase64 := lImageDataParts.Strings[3];
 | 
						|
            DecodeBase64(lImageDataBase64, lImageDataStream);
 | 
						|
            lImageDataStream.Position := 0;
 | 
						|
            lImage.CreateRGB888Image(10, 10);
 | 
						|
            lImage.RasterImage.LoadFromStream(lImageDataStream, lImageReader);
 | 
						|
          finally
 | 
						|
            lImageDataStream.Free;
 | 
						|
            lImageReader.Free;
 | 
						|
          end;
 | 
						|
        end
 | 
						|
        else
 | 
						|
          raise Exception.Create('[TvSVGVectorialReader.ReadImageFromNode] Unimplemented image format');
 | 
						|
      finally
 | 
						|
        lImageDataParts.Free;
 | 
						|
      end;
 | 
						|
    end
 | 
						|
    else if lNodeName = 'id' then
 | 
						|
      lImage.Name := lNodeValue
 | 
						|
{    else if lNodeName = 'style' then
 | 
						|
      ReadSVGStyle(lNodeValue, lImage)}
 | 
						|
    else if IsAttributeFromStyle(lNodeName) then
 | 
						|
    begin
 | 
						|
      //ReadSVGPenStyleWithKeyAndValue(lNodeName, lNodeValue, lImage);
 | 
						|
      //ReadSVGBrushStyleWithKeyAndValue(lNodeName, lNodeValue, lImage);
 | 
						|
      ReadSVGGeneralStyleWithKeyAndValue(AData, lNodeName, lNodeValue, lImage);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  // Record the transform data
 | 
						|
  lTransformX := lImage.X;
 | 
						|
  lTransformY := lImage.Y;
 | 
						|
  lTransformW := lImage.Width / 1000;
 | 
						|
  lTransformH := lImage.Height / 1000;
 | 
						|
 | 
						|
  ConvertSVGCoordinatesToFPVCoordinates(
 | 
						|
        AData, lx, ly, lImage.X, lImage.Y);
 | 
						|
  ConvertSVGDeltaToFPVDelta(
 | 
						|
        AData, lw, lh, lImage.Width, lImage.Height);
 | 
						|
 | 
						|
  // And re-apply the transform data
 | 
						|
  lImage.X := lImage.X + lTransformX;
 | 
						|
  lImage.Y := lImage.Y + lTransformY;
 | 
						|
  lImage.Width := lImage.Width * lTransformW;
 | 
						|
  lImage.Height := lImage.Height * lTransformH;
 | 
						|
  // Strange hack: No idea why it works ... but helped wmtboc
 | 
						|
  lImage.X := lImage.X - lImage.Width;
 | 
						|
  lImage.Y := lImage.Y + lImage.Height / 2;
 | 
						|
 | 
						|
  // Apply the layer style
 | 
						|
  ApplyLayerStyles(AData, lImage);
 | 
						|
 | 
						|
  Result := lImage;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.ReadLayerFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument);
 | 
						|
var
 | 
						|
  lNodeName, lNodeValue: DOMString;
 | 
						|
  lLayerName: string = '';
 | 
						|
  lCurNode, lLayerNameNode: TDOMNode;
 | 
						|
  lLayer: TvLayer;
 | 
						|
  lParentLayer: TvEntityWithSubEntities;
 | 
						|
  i: Integer;
 | 
						|
  {$ifdef SVG_MERGE_LAYER_STYLES}
 | 
						|
  lLayerStyleKeys, lLayerStyleValues: TStringList;
 | 
						|
  lCurEntity: TvEntity;
 | 
						|
  {$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 := 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;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if lNodeName = 'style' then
 | 
						|
    begin
 | 
						|
      {$ifdef SVG_MERGE_LAYER_STYLES}
 | 
						|
      ReadSVGStyleToStyleLists(lNodeValue, lLayerStyleKeys, lLayerStyleValues);
 | 
						|
      {$else}
 | 
						|
      lLayer.SetPenBrushAndFontElements += ReadSVGStyle(lNodeValue, lLayer)
 | 
						|
      {$endif}
 | 
						|
    end
 | 
						|
    else if IsAttributeFromStyle(lNodeName) then
 | 
						|
    begin
 | 
						|
      {$ifdef SVG_MERGE_LAYER_STYLES}
 | 
						|
      lLayerStyleKeys.Add(lNodeName);
 | 
						|
      lLayerStyleValues.Add(lNodeValue);
 | 
						|
      {$else}
 | 
						|
      lLayer.SetPenBrushAndFontElements += ReadSVGPenStyleWithKeyAndValue(lNodeName, lNodeValue, lLayer);
 | 
						|
      lLayer.SetPenBrushAndFontElements += ReadSVGBrushStyleWithKeyAndValue(lNodeName, lNodeValue, lLayer);
 | 
						|
      lLayer.SetPenBrushAndFontElements += ReadSVGFontStyleWithKeyAndValue(lNodeName, lNodeValue, lLayer);
 | 
						|
      {$endif}
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  lCurNode := ANode.FirstChild;
 | 
						|
  while Assigned(lCurNode) do
 | 
						|
  begin
 | 
						|
    lCurEntity := ReadEntityFromNode(lCurNode, AData, ADoc);
 | 
						|
    if lCurEntity <> nil then
 | 
						|
      AData.AddEntity(lCurEntity);
 | 
						|
    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;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadLineFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  x1, y1, x2, y2: double;
 | 
						|
  vx1, vy1, vx2, vy2: double;
 | 
						|
  i: Integer;
 | 
						|
  lNodeName: DOMString;
 | 
						|
  lPath: TPath;
 | 
						|
  lStyleStr: 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);
 | 
						|
  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(True);
 | 
						|
 | 
						|
  // Add default SVG pen/brush
 | 
						|
  lPath.Pen.Style := psClear;
 | 
						|
 | 
						|
  // Apply the layer style
 | 
						|
  ApplyLayerStyles(AData, lPath);
 | 
						|
 | 
						|
  // Add the entity styles
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    if lNodeName = 'style' then
 | 
						|
      ReadSVGStyle(AData, ANode.Attributes.Item[i].NodeValue, lPath)
 | 
						|
    else if IsAttributeFromStyle(lNodeName) then
 | 
						|
    begin
 | 
						|
      ReadSVGPenStyleWithKeyAndValue(lNodeName,
 | 
						|
        ANode.Attributes.Item[i].NodeValue, lPath);
 | 
						|
      ReadSVGGeneralStyleWithKeyAndValue(AData, lNodeName,
 | 
						|
        ANode.Attributes.Item[i].NodeValue, lPath);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
  //
 | 
						|
  Result := lPath;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadPathFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  lNodeName, lDStr: WideString;
 | 
						|
  i, j: Integer;
 | 
						|
  lCurPath: TPath;
 | 
						|
  lPaths: TvSVGPathList;
 | 
						|
begin
 | 
						|
  Result := nil;
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    if lNodeName = 'd' then
 | 
						|
      lDStr := ANode.Attributes.Item[i].NodeValue;
 | 
						|
  end;
 | 
						|
 | 
						|
  Inc(FPathNumber);
 | 
						|
  FSVGPathTokenizer.ExtraDebugStr := Format(' [TvSVGVectorialReader.ReadPathFromNode] path#(1-based)=%d', [FPathNumber]);
 | 
						|
  lPaths := ReadPathFromString(UTF8Encode(lDStr), AData, ADoc);
 | 
						|
  FSVGPathTokenizer.ExtraDebugStr := '';
 | 
						|
  for j := 0 to lPaths.Count-1 do
 | 
						|
  begin
 | 
						|
    lCurPath := lPaths.GetPath(j);
 | 
						|
 | 
						|
    // Add default SVG pen/brush
 | 
						|
    lCurPath.Pen.Style := psClear;
 | 
						|
    lCurPath.Brush.Color := colBlack;
 | 
						|
    lCurPath.Brush.Style := bsClear;
 | 
						|
    // Apply the layer style
 | 
						|
    ApplyLayerStyles(AData, lCurPath);
 | 
						|
    // name
 | 
						|
    if lPaths.Count > 1 then
 | 
						|
      lCurPath.Name := Format('[%d]', [i]);
 | 
						|
    // 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
 | 
						|
      begin
 | 
						|
        if lPaths.Count = 1 then
 | 
						|
          lCurPath.Name := ANode.Attributes.Item[i].NodeValue;
 | 
						|
      end
 | 
						|
      else if lNodeName = 'style' then
 | 
						|
        ReadSVGStyle(AData, ANode.Attributes.Item[i].NodeValue, lCurPath)
 | 
						|
      else if IsAttributeFromStyle(lNodeName) then
 | 
						|
      begin
 | 
						|
        ReadSVGPenStyleWithKeyAndValue(lNodeName,
 | 
						|
          ANode.Attributes.Item[i].NodeValue, lCurPath);
 | 
						|
        ReadSVGBrushStyleWithKeyAndValue(lNodeName,
 | 
						|
          ANode.Attributes.Item[i].NodeValue, lCurPath);
 | 
						|
        ReadSVGGeneralStyleWithKeyAndValue(AData, lNodeName,
 | 
						|
          ANode.Attributes.Item[i].NodeValue, lCurPath);
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  if lPaths.Count = 1 then
 | 
						|
  begin
 | 
						|
    Result := lPaths.GetPath(0);
 | 
						|
  end
 | 
						|
  else if lPaths.Count > 1 then
 | 
						|
  begin
 | 
						|
    Result := TvEntityWithSubEntities.Create(nil);
 | 
						|
 | 
						|
    for j := 0 to lPaths.Count-1 do
 | 
						|
    begin
 | 
						|
      lCurPath := lPaths.GetPath(j);
 | 
						|
      TvEntityWithSubEntities(Result).AddEntity(lCurPath);
 | 
						|
    end;
 | 
						|
 | 
						|
    // Add thename
 | 
						|
    for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
    begin
 | 
						|
      lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
      if lNodeName = 'id' then
 | 
						|
        TvEntityWithSubEntities(Result).Name := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
// Documentation: http://www.w3.org/TR/SVG/paths.html
 | 
						|
function TvSVGVectorialReader.ReadPathFromString(AStr: string;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvSVGPathList;
 | 
						|
var
 | 
						|
  lCurTokenType, lLastCommandToken: TSVGTokenType;
 | 
						|
  lTmpTokenType: TSVGTokenType;
 | 
						|
begin
 | 
						|
  Result := TvSVGPathList.Create;
 | 
						|
  FSVGPathTokenizer.ClearTokens;
 | 
						|
  FSVGPathTokenizer.TokenizePathString(AStr);
 | 
						|
  //lDebugStr := FSVGPathTokenizer.DebugOutTokensAsString();
 | 
						|
  Result.IsFirstPathMove := true;
 | 
						|
  Result.Data := AData;
 | 
						|
  Result.Doc := ADoc;
 | 
						|
  lLastCommandToken := sttFloatValue;
 | 
						|
  AData.StartPath();
 | 
						|
 | 
						|
  Result.CurTokenIndex := 0;
 | 
						|
  while Result.CurTokenIndex < FSVGPathTokenizer.Tokens.Count do
 | 
						|
  begin
 | 
						|
    Result.LastPathClosed := False;
 | 
						|
    lCurTokenType := FSVGPathTokenizer.Tokens.Items[Result.CurTokenIndex].TokenType;
 | 
						|
    if not (lCurTokenType = sttFloatValue) then
 | 
						|
    begin
 | 
						|
      lLastCommandToken := lCurTokenType;
 | 
						|
      ReadNextPathCommand(lCurTokenType, Result, Result.CurX, Result.CurY);
 | 
						|
    end
 | 
						|
    // In this case we are getting a command without a starting letter
 | 
						|
    // It is a copy of the last one, or something related to it
 | 
						|
    else
 | 
						|
    begin
 | 
						|
      lTmpTokenType := lLastCommandToken;
 | 
						|
      if lLastCommandToken = sttMoveTo then lTmpTokenType := sttLineTo;
 | 
						|
      if lLastCommandToken = sttRelativeMoveTo then lTmpTokenType := sttRelativeLineTo;
 | 
						|
      // For bezier I checked that a sttBezierTo upon repetition expects a sttBezierTo
 | 
						|
      Dec(Result.CurTokenIndex);// because there is no command token in this command
 | 
						|
      ReadNextPathCommand(lTmpTokenType, Result, Result.CurX, Result.CurY);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
  if not Result.LastPathClosed then
 | 
						|
    Result.Add(AData.EndPath(True));
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.ReadNextPathCommand(ACurTokenType: TSVGTokenType;
 | 
						|
  APaths: TvSVGPathList; var CurX, CurY: Double);
 | 
						|
var
 | 
						|
  X, Y, X2, Y2, X3, Y3, XQ, YQ, Xnew, Ynew, cx, cy, phi, tmp: Double;
 | 
						|
  PathEndX, PathEndY: Double;
 | 
						|
  LargeArcFlag, SweepFlag, LeftmostEllipse, ClockwiseArc: Boolean;
 | 
						|
  lCurTokenType: TSVGTokenType;
 | 
						|
  lToken5Before, lToken7Before: TSVGTokenType;
 | 
						|
  lCorrectPreviousToken: Boolean;
 | 
						|
  lPrevRelative, lCurRelative: Boolean;
 | 
						|
  AData: TvVectorialPage;
 | 
						|
  i: Integer;
 | 
						|
begin
 | 
						|
  lCurTokenType := ACurTokenType;
 | 
						|
  AData := APaths.Data;
 | 
						|
  i := APaths.CurTokenIndex;
 | 
						|
  // --------------
 | 
						|
  // Moves
 | 
						|
  // --------------
 | 
						|
  if lCurTokenType in [sttMoveTo, sttRelativeMoveTo] then
 | 
						|
  begin
 | 
						|
    // The point at which the polygon starts.
 | 
						|
    X := FSVGPathTokenizer.Tokens.Items[i+1].Value;
 | 
						|
    Y := FSVGPathTokenizer.Tokens.Items[i+2].Value;
 | 
						|
 | 
						|
    // take care of relative or absolute
 | 
						|
    // Idiotism in SVG: If the path starts with a relative move to,
 | 
						|
    // the coordinates are absolute =o source: http://www.w3.org/TR/SVG/paths.html#PathDataMovetoCommands
 | 
						|
    if (lCurTokenType = sttRelativeMoveTo) and (i > 0) 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;
 | 
						|
    APaths.Data.AddMoveToPath(CurX, CurY);
 | 
						|
    // Since there may be several subpolygons we must store the start point
 | 
						|
    // to close the subpolygon correctly later.
 | 
						|
    if APaths.IsFirstPathMove then
 | 
						|
    begin
 | 
						|
      APaths.LastPathStart.X := APaths.CurX;
 | 
						|
      APaths.LastPathStart.Y := APaths.CurY;
 | 
						|
      APaths.IsFirstPathMove := False;
 | 
						|
      APaths.LastPathClosed := False;
 | 
						|
    end;
 | 
						|
 | 
						|
    Inc(APaths.CurTokenIndex, 3);
 | 
						|
  end
 | 
						|
  // --------------
 | 
						|
  // Close Path
 | 
						|
  // --------------
 | 
						|
  else if lCurTokenType = sttClosePath then
 | 
						|
  begin
 | 
						|
    // Repeat the first point of the subpolygon
 | 
						|
    PathEndX := APaths.LastPathStart.X;
 | 
						|
    PathEndY := APaths.LastPathStart.Y;
 | 
						|
    CurX := PathEndX;
 | 
						|
    CurY := PathEndY;
 | 
						|
    APaths.LastPathStart.X := 0;
 | 
						|
    APaths.LastPathStart.Y := 0;
 | 
						|
    APaths.Data.AddLineToPath(PathEndX, PathEndY);
 | 
						|
    {$IFDEF FPVECTORIAL_SVG_SPLIT_PATHS}
 | 
						|
    APaths.LastPathClosed := True;
 | 
						|
    APaths.IsFirstPathMove := True;
 | 
						|
    APaths.Add(AData.EndPath(True));
 | 
						|
    AData.StartPath();
 | 
						|
    {$ENDIF}
 | 
						|
 | 
						|
    Inc(APaths.CurTokenIndex, 1);
 | 
						|
  end
 | 
						|
  // --------------
 | 
						|
  // Lines
 | 
						|
  // --------------
 | 
						|
  else if lCurTokenType in [sttLineTo, sttRelativeLineTo, sttHorzLineTo,
 | 
						|
    sttRelativeHorzLineTo, sttVertLineTo, sttRelativeVertLineTo] then
 | 
						|
  begin
 | 
						|
    if lCurTokenType in [sttLineTo, sttRelativeLineTo] then
 | 
						|
    begin
 | 
						|
      X := FSVGPathTokenizer.Tokens.Items[i+1].Value;
 | 
						|
      Y := FSVGPathTokenizer.Tokens.Items[i+2].Value;
 | 
						|
      if lCurTokenType = sttLineTo then
 | 
						|
        ConvertSVGCoordinatesToFPVCoordinates(APaths.Data, X,Y, CurX,CurY)
 | 
						|
      else
 | 
						|
      begin
 | 
						|
        ConvertSVGDeltaToFPVDelta(APaths.Data, X,Y, X,Y);
 | 
						|
        CurX := CurX + X;
 | 
						|
        CurY := CurY + Y;
 | 
						|
      end;
 | 
						|
      inc(APaths.CurTokenIndex, 3);
 | 
						|
    end
 | 
						|
    else if lCurTokenType in [sttHorzLineTo, sttVertLineTo] then
 | 
						|
    begin
 | 
						|
      tmp := FSVGPathTokenizer.Tokens.Items[i+1].Value;
 | 
						|
      ConvertSVGCoordinatesToFPVCoordinates(APaths.Data, tmp, tmp, X, Y);
 | 
						|
      if lCurTokenType = sttHorzLineTo then
 | 
						|
        CurX := X
 | 
						|
      else
 | 
						|
        CurY := Y;
 | 
						|
      inc(APaths.CurTokenIndex, 2);
 | 
						|
    end else
 | 
						|
    if lCurTokenType in [sttRelativeHorzLineTo, sttRelativeVertLineTo] then
 | 
						|
    begin
 | 
						|
      tmp := FSVGPathTokenizer.Tokens.Items[i+1].Value;
 | 
						|
      ConvertSVGDeltaToFPVDelta(APaths.Data, tmp, tmp, X, Y);
 | 
						|
      if lCurTokenType = sttRelativeHorzLineTo then
 | 
						|
        CurX := CurX + X
 | 
						|
      else
 | 
						|
        CurY := CurY + Y;
 | 
						|
      inc(APaths.CurTokenIndex, 2);
 | 
						|
    end;
 | 
						|
    APaths.Data.AddLineToPath(CurX, CurY);
 | 
						|
  end
 | 
						|
  // --------------
 | 
						|
  // Cubic Bezier
 | 
						|
  // --------------
 | 
						|
  else if lCurTokenType in [sttBezierTo, sttRelativeBezierTo,
 | 
						|
    sttSmoothBezierTo, sttRelativeSmoothBezierTo] then
 | 
						|
  begin
 | 
						|
    lPrevRelative := false;
 | 
						|
    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
 | 
						|
      // Calculation found here: http://stackoverflow.com/questions/5287559/calculating-control-points-for-a-shorthand-smooth-svg-path-bezier-curve
 | 
						|
      // Description here: http://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands
 | 
						|
      //
 | 
						|
      // M X0, Y0 C X1, Y1 X2, Y2 X3, Y3 S X4, Y4 X5, Y5
 | 
						|
      // Missing control points for S is:
 | 
						|
      // XR = 2*X3 - X2 and
 | 
						|
      // YR = 2*Y3 - Y2
 | 
						|
      if i >= 7 then
 | 
						|
      begin
 | 
						|
        lToken5Before := FSVGPathTokenizer.Tokens.Items[i-5].TokenType;
 | 
						|
        lToken7Before := FSVGPathTokenizer.Tokens.Items[i-7].TokenType;
 | 
						|
        lCorrectPreviousToken := lToken5Before in [sttSmoothBezierTo, sttRelativeSmoothBezierTo];
 | 
						|
        lCorrectPreviousToken := lCorrectPreviousToken or
 | 
						|
          (lToken7Before in [sttBezierTo, sttRelativeBezierTo]);
 | 
						|
        lPrevRelative := (lToken5Before = sttRelativeSmoothBezierTo) or (lToken7Before = sttRelativeBezierTo);
 | 
						|
      end;
 | 
						|
      if (i >= 7) and (lCorrectPreviousToken) then
 | 
						|
      begin
 | 
						|
        if (lCurTokenType = sttRelativeSmoothBezierTo) or lPrevRelative then
 | 
						|
        begin
 | 
						|
          X2 := FSVGPathTokenizer.Tokens.Items[i-2].Value - FSVGPathTokenizer.Tokens.Items[i-4].Value;
 | 
						|
          Y2 := FSVGPathTokenizer.Tokens.Items[i-1].Value - FSVGPathTokenizer.Tokens.Items[i-3].Value;
 | 
						|
        end
 | 
						|
        else
 | 
						|
        begin
 | 
						|
          X2 := 2*FSVGPathTokenizer.Tokens.Items[i-2].Value - FSVGPathTokenizer.Tokens.Items[i-4].Value;
 | 
						|
          Y2 := 2*FSVGPathTokenizer.Tokens.Items[i-1].Value - FSVGPathTokenizer.Tokens.Items[i-3].Value;
 | 
						|
        end;
 | 
						|
      end;
 | 
						|
      // Now the non-missing items
 | 
						|
      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
 | 
						|
    lCurRelative := lCurTokenType in [sttRelativeBezierTo, sttRelativeSmoothBezierTo];
 | 
						|
    if lPrevRelative then
 | 
						|
    begin
 | 
						|
      ConvertSVGDeltaToFPVDelta(AData, X2, Y2, X2, Y2);
 | 
						|
      if lCurRelative then
 | 
						|
      begin
 | 
						|
        ConvertSVGDeltaToFPVDelta(AData, X3, Y3, X3, Y3);
 | 
						|
        ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
 | 
						|
      end else
 | 
						|
      begin
 | 
						|
        ConvertSVGCoordinatesToFPVCoordinates(AData, X3, Y3, X3, Y3);
 | 
						|
        ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y);
 | 
						|
      end;
 | 
						|
    end else
 | 
						|
    begin
 | 
						|
      if lCurRelative 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;
 | 
						|
    end;
 | 
						|
 | 
						|
    // Covers the case where there is no valid first control point in smooth bezier
 | 
						|
    // The code is here to be after the conversions
 | 
						|
    if (lCurTokenType in [sttSmoothBezierTo, sttRelativeSmoothBezierTo]) and
 | 
						|
      ((i < 7) or (not lCorrectPreviousToken)) then
 | 
						|
    begin
 | 
						|
      if lCurTokenType = sttRelativeSmoothBezierTo then
 | 
						|
      begin
 | 
						|
        X2 := CurX;
 | 
						|
        Y2 := CurY;
 | 
						|
      end
 | 
						|
      else
 | 
						|
      begin
 | 
						|
        X2 := 0;
 | 
						|
        Y2 := 0;
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
 | 
						|
    // The final step
 | 
						|
    if lCurTokenType in [sttRelativeBezierTo, sttRelativeSmoothBezierTo] 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
 | 
						|
      if lPrevRelative then
 | 
						|
        AData.AddBezierToPath(X2 + CurX, Y2 + CurY, X3, Y3, X, Y)
 | 
						|
      else
 | 
						|
        AData.AddBezierToPath(X2, Y2, X3, Y3, X, Y);
 | 
						|
      CurX := X;
 | 
						|
      CurY := Y;
 | 
						|
    end;
 | 
						|
 | 
						|
    if lCurTokenType in [sttBezierTo, sttRelativeBezierTo] then
 | 
						|
      Inc(APaths.CurTokenIndex, 7)
 | 
						|
    else
 | 
						|
      Inc(APaths.CurTokenIndex, 5);
 | 
						|
  end
 | 
						|
  // --------------
 | 
						|
  // Quadratic Bezier
 | 
						|
  // --------------
 | 
						|
  else if lCurTokenType in [sttQuadraticBezierTo, sttRelativeQuadraticBezierTo] then
 | 
						|
  begin
 | 
						|
    XQ := FSVGPathTokenizer.Tokens.Items[i+1].Value;
 | 
						|
    YQ := 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(APaths.Data, XQ, YQ, XQ, YQ);
 | 
						|
      ConvertSVGDeltaToFPVDelta(APaths.Data, X, Y, X, Y);
 | 
						|
 | 
						|
      XQ := XQ + CurX;
 | 
						|
      YQ := YQ + CurY;
 | 
						|
      X := X + CurX;
 | 
						|
      Y := Y + CurY;
 | 
						|
    end
 | 
						|
    else
 | 
						|
    begin
 | 
						|
      ConvertSVGCoordinatesToFPVCoordinates(APaths.Data, XQ, YQ, XQ, YQ);
 | 
						|
      ConvertSVGCoordinatesToFPVCoordinates(APaths.Data, X, Y, X, Y);
 | 
						|
    end;
 | 
						|
 | 
						|
    // Convert quadratic to cubic bezier
 | 
						|
    // CP1 = QP0 + 2/3 *(QP1-QP0)
 | 
						|
    // CP2 = QP2 + 2/3 *(QP1-QP2)
 | 
						|
    // See http://stackoverflow.com/questions/3162645/convert-a-quadratic-bezier-to-a-cubic
 | 
						|
    // Just CP1=CP2=QP1 does not work! See svg/w3c/path2.svg
 | 
						|
    X2 := CurX + (2/3) * (XQ-CurX);
 | 
						|
    Y2 := CurY + (2/3) * (YQ-CurY);
 | 
						|
    //
 | 
						|
    X3 := X + (2/3) * (XQ-X);
 | 
						|
    Y3 := Y + (2/3) * (YQ-Y);
 | 
						|
 | 
						|
    APaths.Data.AddBezierToPath(X2, Y2, X3, Y3, X, Y);
 | 
						|
    CurX := X;
 | 
						|
    CurY := Y;
 | 
						|
 | 
						|
    Inc(APaths.CurTokenIndex, 5);
 | 
						|
  end
 | 
						|
  // --------------
 | 
						|
  // Elliptical arcs
 | 
						|
  // See http://www.w3.org/TR/SVG/paths.html#PathDataEllipticalArcCommands
 | 
						|
  // (rx ry x-axis-rotation large-arc-flag sweep-flag x y)+
 | 
						|
  // --------------
 | 
						|
  else if lCurTokenType in [sttEllipticArcTo, sttRelativeEllipticArcTo] then
 | 
						|
  begin
 | 
						|
    X2 := FSVGPathTokenizer.Tokens.Items[i+1].Value; // RX
 | 
						|
    Y2 := FSVGPathTokenizer.Tokens.Items[i+2].Value; // RY
 | 
						|
    phi := FSVGPathTokenizer.Tokens.Items[i+3].Value; // RotationX
 | 
						|
    phi := DegToRad(phi);  // degrees to radians conversion
 | 
						|
    LargeArcFlag := Round(FSVGPathTokenizer.Tokens.Items[i+4].Value) = 1;
 | 
						|
    SweepFlag := Round(FSVGPathTokenizer.Tokens.Items[i+5].Value) = 1;
 | 
						|
    X := FSVGPathTokenizer.Tokens.Items[i+6].Value;  // X
 | 
						|
    Y := FSVGPathTokenizer.Tokens.Items[i+7].Value;  // Y
 | 
						|
 | 
						|
    {
 | 
						|
    if lCurTokenType = sttRelativeEllipticArcTo then
 | 
						|
    begin
 | 
						|
      Xnew := CurX + X;
 | 
						|
      Ynew := CurY + Y;
 | 
						|
    end else
 | 
						|
    begin
 | 
						|
      Xnew := CurX;
 | 
						|
      Ynew := CurY;
 | 
						|
    end;
 | 
						|
 | 
						|
    CalcEllipseCenter(CurX, CurY, Xnew, Ynew, X2, Y2, phi, LargeArcFlag, SweepFlag, cx, cy, tmp);
 | 
						|
    ConvertSVGCoordinatesToFPVCoordinates(AData, cx, cy, cx, cy);
 | 
						|
     }
 | 
						|
    // non-coordinate values (radii)
 | 
						|
    ConvertSVGDeltaToFPVDelta(AData, X2, Y2, X2, Y2);
 | 
						|
    if X2 < 0 then X2 := -X2;
 | 
						|
    if Y2 < 0 then Y2 := -Y2;
 | 
						|
 | 
						|
    // Careful that absolute coordinates require using ConvertSVGCoordinatesToFPVCoordinates
 | 
						|
    if lCurTokenType in [sttRelativeEllipticArcTo] then
 | 
						|
    begin
 | 
						|
      ConvertSVGDeltaToFPVDelta(AData, X, Y, X, Y);
 | 
						|
      Xnew := CurX + X;
 | 
						|
      Ynew := CurY + Y;
 | 
						|
    end else
 | 
						|
    begin
 | 
						|
      ConvertSVGCoordinatesToFPVCoordinates(AData, X, Y, X, Y);
 | 
						|
      Xnew := X;
 | 
						|
      Ynew := Y;
 | 
						|
    end;
 | 
						|
 | 
						|
    // in svg the y axis increases downward, in fpv upward. Therefore, angles
 | 
						|
    // change their sign!
 | 
						|
    if vrfSVG_UseBottomLeftCoords in Settings.VecReaderFlags then
 | 
						|
    begin
 | 
						|
      phi := -phi;
 | 
						|
      SweepFlag := not SweepFlag;  // i.e. "clockwise" turns into "counter-clockwise"!
 | 
						|
    end;
 | 
						|
 | 
						|
    if CalcEllipseCenter(CurX, CurY, Xnew, Ynew, X2, Y2, phi, LargeArcFlag, SweepFlag, cx, cy, tmp) then
 | 
						|
      AData.AddEllipticalArcWithCenterToPath(X2*tmp, Y2*tmp, phi, Xnew, Ynew, cx, cy, SweepFlag)
 | 
						|
    else
 | 
						|
      // Use a straight segment in case of no solution existing for the ellipse center
 | 
						|
      AData.AddLineToPath(Xnew, Ynew);
 | 
						|
 | 
						|
    CurX := Xnew;
 | 
						|
    CurY := Ynew;
 | 
						|
    {
 | 
						|
    // Convert SVG flags to fpvectorial flags
 | 
						|
    LeftmostEllipse := (LargeArcFlag and (not SweepFlag))
 | 
						|
      or ((not LargeArcFlag) and SweepFlag);
 | 
						|
    if (Y > CurY) or ((Y = CurY) and (X > CurX)) then
 | 
						|
      LeftMostEllipse := not LeftMostEllipse;
 | 
						|
      // if Y = CurY then "LeftMost" is to be understood as "TopMost"
 | 
						|
    ClockwiseArc := SweepFlag;
 | 
						|
 | 
						|
    if lCurTokenType = sttRelativeEllipticArcTo then
 | 
						|
    begin
 | 
						|
      AData.AddEllipticalArcToPath(X2, Y2, X3, X + CurX, Y + CurY, LeftmostEllipse, ClockwiseArc);
 | 
						|
      CurX := CurX + X;
 | 
						|
      CurY := CurY + Y;
 | 
						|
    end
 | 
						|
    else
 | 
						|
    begin
 | 
						|
      AData.AddEllipticalArcToPath(X2, Y2, X3, X, Y, LeftmostEllipse, ClockwiseArc);
 | 
						|
      CurX := X;
 | 
						|
      CurY := Y;
 | 
						|
    end;
 | 
						|
     }
 | 
						|
 | 
						|
    Inc(APaths.CurTokenIndex, 8);
 | 
						|
  end
 | 
						|
  else
 | 
						|
  begin
 | 
						|
    Inc(APaths.CurTokenIndex);
 | 
						|
  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.ClearTokens;
 | 
						|
  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;
 | 
						|
    ConvertSVGCoordinatesToFPVCoordinates(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
 | 
						|
function TvSVGVectorialReader.ReadPolyFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  lPointsStr: string = '';
 | 
						|
  i: Integer;
 | 
						|
  lNodeName, lNodeValue: 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(True);
 | 
						|
  Result := lPath;
 | 
						|
 | 
						|
  // Add default SVG pen/brush
 | 
						|
  lPath.Pen.Style := psClear;
 | 
						|
  lPath.Brush.Style := bsClear;
 | 
						|
 | 
						|
  // Apply the layer style
 | 
						|
  ApplyLayerStyles(AData, lPath);
 | 
						|
 | 
						|
  // now read the other attributes
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if lNodeName = 'id' then
 | 
						|
      lPath.Name := lNodeValue
 | 
						|
    else if IsEntityStyleField(lNodeName) then
 | 
						|
      ReadEntityStyleField(AData, lNodeName, lNodeValue, lPath);
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
// <rect width="90" height="90" stroke="green" stroke-width="3" fill="yellow" filter="url(#f1)" />
 | 
						|
function TvSVGVectorialReader.ReadRectFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  lx, ly, cx, cy, lrx, lry: double;
 | 
						|
  lRect: TvRectangle;
 | 
						|
  i: Integer;
 | 
						|
  lNodeName: DOMString;
 | 
						|
  lNodeValue: String;
 | 
						|
begin
 | 
						|
  lx := 0.0;
 | 
						|
  ly := 0.0;
 | 
						|
  cx := 0.0;
 | 
						|
  cy := 0.0;
 | 
						|
  lrx := 0.0;
 | 
						|
  lry := 0.0;
 | 
						|
 | 
						|
  lRect := TvRectangle.Create(nil);
 | 
						|
  // SVG entities start without any pen drawing, but with a black brush
 | 
						|
  lRect.Pen.Style := psClear;
 | 
						|
  lRect.Brush.Style := bsSolid;
 | 
						|
  lRect.Brush.Color := colBlack;
 | 
						|
  // Apply the layer style
 | 
						|
  ApplyLayerStyles(AData, lRect);
 | 
						|
 | 
						|
  // read the attributes
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if  lNodeName = 'x' then
 | 
						|
      lx := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'y' then
 | 
						|
      ly := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'rx' then
 | 
						|
      lrx := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'ry' then
 | 
						|
      lry := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'width' then
 | 
						|
      cx := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'height' then
 | 
						|
      cy := StringWithUnitToFloat(lNodeValue)
 | 
						|
    else if lNodeName = 'id' then
 | 
						|
      lRect.Name := lNodeValue;
 | 
						|
  end;
 | 
						|
 | 
						|
  ConvertSVGCoordinatesToFPVCoordinates(AData, lx, ly, lRect.X, lRect.Y);
 | 
						|
  ConvertSVGSizeToFPVSize(AData, cx, cy, lRect.CX, lRect.CY);
 | 
						|
  ConvertSVGSizeToFPVSize(AData, lrx, lry, lRect.RX, lRect.RY);
 | 
						|
  lRect.RX := Abs(lRect.RX) * 2;
 | 
						|
  lRect.RY := Abs(lRect.RY) * 2;
 | 
						|
 | 
						|
  // Make sure that transformations are read after geometry and position
 | 
						|
  // of rectangle is known.
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    if lNodeName = 'style' then
 | 
						|
      ReadSVGStyle(AData, lNodeValue, lRect)
 | 
						|
    else if IsAttributeFromStyle(lNodeName) then
 | 
						|
    begin
 | 
						|
      ReadSVGPenStyleWithKeyAndValue(lNodeName,
 | 
						|
        ANode.Attributes.Item[i].NodeValue, lRect);
 | 
						|
      ReadSVGBrushStyleWithKeyAndValue(lNodeName,
 | 
						|
        ANode.Attributes.Item[i].NodeValue, lRect);
 | 
						|
      ReadSVGGeneralStyleWithKeyAndValue(AData, lNodeName,
 | 
						|
        ANode.Attributes.Item[i].NodeValue, lRect);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  Result := lRect;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.ReadSymbolFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  lBlock: TvBlock;
 | 
						|
  lLayerName: string;
 | 
						|
  lPreviousLayer: TvEntityWithSubEntities;
 | 
						|
  lCurEntity: TvEntity;
 | 
						|
  i: Integer;
 | 
						|
  lAttrName: DOMString;
 | 
						|
  lCurNode: TDOMNode;
 | 
						|
begin
 | 
						|
  Result := nil;
 | 
						|
  lBlock := TvBlock.Create(nil);
 | 
						|
 | 
						|
  // pre-load attribute reader, to get the block name
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lAttrName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    if lAttrName = 'id' then
 | 
						|
    begin
 | 
						|
      lLayerName := ANode.Attributes.Item[i].NodeValue;
 | 
						|
      lBlock.Name := lLayerName;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  lPreviousLayer := AData.GetCurrentLayer();
 | 
						|
  AData.AddEntity(lBlock);
 | 
						|
  AData.SetCurrentLayer(lBlock);
 | 
						|
  //
 | 
						|
  lCurNode := ANode.FirstChild;
 | 
						|
  while Assigned(lCurNode) do
 | 
						|
  begin
 | 
						|
    lCurEntity := ReadEntityFromNode(lCurNode, AData, ADoc);
 | 
						|
    if lCurEntity <> nil then
 | 
						|
      AData.AddEntity(lCurEntity);
 | 
						|
    lCurNode := lCurNode.NextSibling;
 | 
						|
  end;
 | 
						|
  //
 | 
						|
  AData.SetCurrentLayer(lPreviousLayer);
 | 
						|
end;
 | 
						|
 | 
						|
{
 | 
						|
 <text x="0" y="15" fill="red" transform="rotate(30 20,40)">I love SVG</text>
 | 
						|
 | 
						|
 Example with nested tspan:
 | 
						|
 | 
						|
 <text class="TextShape">
 | 
						|
   <tspan class="TextParagraph" font-family="Times New Roman, serif" font-size="917px" font-weight="400">
 | 
						|
     <tspan class="TextPosition" x="3650" y="11251">
 | 
						|
       <tspan fill="rgb(0,0,0)" stroke="none">This </tspan>
 | 
						|
       <tspan fill="rgb(197,0,11)" stroke="none">size</tspan>
 | 
						|
       <tspan fill="rgb(0,0,0)" stroke="none"> is bigger.</tspan>
 | 
						|
     </tspan>
 | 
						|
   </tspan>
 | 
						|
 </text>
 | 
						|
}
 | 
						|
function TvSVGVectorialReader.ReadTextFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  lx, ly: double;
 | 
						|
  lName: string;
 | 
						|
  lParagraph: TvParagraph;
 | 
						|
  lTextSpanStack: TSVGObjectStack; // of TSVGTextSpanStyle
 | 
						|
  lCurStyle: TSVGTextSpanStyle;
 | 
						|
  i: Integer;
 | 
						|
  lNodeName, lNodeValue, lXLink: DOMString;
 | 
						|
  lCurObject: TObject;
 | 
						|
 | 
						|
  procedure ApplyStackStylesToText(ADest: TvText);
 | 
						|
  var
 | 
						|
    j: Integer;
 | 
						|
  begin
 | 
						|
    for j := 0 to lTextSpanStack.GetList().Count-1 do
 | 
						|
    begin
 | 
						|
      lCurStyle := TSVGTextSpanStyle(lTextSpanStack.GetList().Items[j]);
 | 
						|
      lCurStyle.ApplyIntoEntity(ADest);
 | 
						|
      if lCurStyle.PositionSet then
 | 
						|
      begin
 | 
						|
        ADest.X := ADest.X + lCurStyle.X;
 | 
						|
        ADest.Y := ADest.Y + lCurStyle.Y;
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  procedure ReadTextSpans(ACurNode: TDOMNode);
 | 
						|
  var
 | 
						|
    j: Integer;
 | 
						|
    lCurNode: TDOMNode;
 | 
						|
    lTextStr: string;
 | 
						|
    lText: TvText;
 | 
						|
    lCText: TvCurvedText;
 | 
						|
    lInsertedEntity, lInsertedSubEntity: TvEntity;
 | 
						|
  begin
 | 
						|
    lCurNode := ACurNode.FirstChild;
 | 
						|
    while lCurNode <> nil do
 | 
						|
    begin
 | 
						|
      lNodeName := LowerCase(lCurNode.NodeName);
 | 
						|
      lNodeValue := lCurNode.NodeValue;
 | 
						|
 | 
						|
      if lNodeName = 'tspan' then
 | 
						|
      begin
 | 
						|
        lCurStyle := TSVGTextSpanStyle.Create;
 | 
						|
        lTextSpanStack.Push(lCurStyle);
 | 
						|
 | 
						|
        // read the attributes
 | 
						|
        for j := 0 to lCurNode.Attributes.Length - 1 do
 | 
						|
        begin
 | 
						|
          lNodeName := lCurNode.Attributes.Item[j].NodeName;
 | 
						|
          lNodeValue := lCurNode.Attributes.Item[j].NodeValue;
 | 
						|
          lNodeName := LowerCase(lNodeName);
 | 
						|
          if lNodeName = 'x' then
 | 
						|
          begin
 | 
						|
            lCurStyle.PositionSet := True;
 | 
						|
            lCurStyle.X := StringWithUnitToFloat(lNodeValue, sckX) - lParagraph.X;
 | 
						|
          end
 | 
						|
          else if lNodeName = 'y' then
 | 
						|
          begin
 | 
						|
            lCurStyle.PositionSet := True;
 | 
						|
            lCurStyle.Y := StringWithUnitToFloat(lNodeValue, sckY) - lParagraph.Y;
 | 
						|
          end
 | 
						|
          //else if lNodeName = 'id' then
 | 
						|
          //  lText.Name := lNodeValue
 | 
						|
          //else if lNodeName = 'style' then
 | 
						|
          //  ReadSVGStyle(lNodeValue, lParagraph)
 | 
						|
          else if IsAttributeFromStyle(lNodeName) then
 | 
						|
          begin
 | 
						|
            ReadSVGFontStyleWithKeyAndValue(lNodeName, lNodeValue, nil, lCurStyle);
 | 
						|
            //ReadSVGGeneralStyleWithKeyAndValue(lNodeName, lNodeValue, lText);
 | 
						|
          end;
 | 
						|
        end;
 | 
						|
 | 
						|
        // Recursion
 | 
						|
        ReadTextSpans(lCurNode);
 | 
						|
 | 
						|
        // Get rid of the current style
 | 
						|
        lCurObject := lTextSpanStack.Pop();
 | 
						|
        if lCurObject <> nil then lCurObject.Free;
 | 
						|
      end
 | 
						|
      else if lNodeName = 'textpath' then
 | 
						|
      begin
 | 
						|
        lTextStr := GetTextContentFromNode(lCurNode);
 | 
						|
        lTextStr := RemoveLineEndingsAndTrim(lTextStr);
 | 
						|
        lTextStr := Trim(lTextStr);
 | 
						|
 | 
						|
        lCText := lParagraph.AddCurvedText(lTextStr);
 | 
						|
 | 
						|
        lCText.Font.Size := 10;
 | 
						|
        lCText.Name := lName;
 | 
						|
 | 
						|
        // read the attributes
 | 
						|
        for j := 0 to lCurNode.Attributes.Length - 1 do
 | 
						|
        begin
 | 
						|
          lNodeName := lCurNode.Attributes.Item[j].NodeName;
 | 
						|
          lNodeValue := lCurNode.Attributes.Item[j].NodeValue;
 | 
						|
          lNodeName := LowerCase(lNodeName);
 | 
						|
          if lNodeName = 'xlink:href' then
 | 
						|
          begin
 | 
						|
            lXLink := lNodeValue;
 | 
						|
            lXLink := Copy(lXLink, 2, Length(lXLink));
 | 
						|
            if lXLink = '' then Continue; // nothing to insert, so give up
 | 
						|
            lInsertedEntity := AData.FindEntityWithNameAndType(lXLink, TvEntity, True);
 | 
						|
            if lInsertedEntity = nil then Continue; // nothing to insert, give up!
 | 
						|
            if lInsertedEntity is TvBlock then
 | 
						|
            begin
 | 
						|
              lInsertedSubEntity := TvBlock(lInsertedEntity).GetFirstEntity();
 | 
						|
              if not (lInsertedSubEntity is TPath) then Continue;
 | 
						|
              // Add the curvature
 | 
						|
              lCText.Path := TPath(lInsertedSubEntity as TPath);
 | 
						|
            end;
 | 
						|
          end;
 | 
						|
        end;
 | 
						|
 | 
						|
        // Apply the layer style
 | 
						|
        ApplyLayerStyles(AData, lCText);
 | 
						|
 | 
						|
        // Apply the layer style
 | 
						|
        ApplyStackStylesToText(lCText);
 | 
						|
      end
 | 
						|
      else
 | 
						|
      if lNodeValue <> '' then
 | 
						|
      begin
 | 
						|
        lText := lParagraph.AddText(lNodeValue);
 | 
						|
        lText.Font.Size := 10;
 | 
						|
        lText.Name := lName;
 | 
						|
        // Apply the layer style
 | 
						|
        ApplyLayerStyles(AData, lText);
 | 
						|
        // Apply the layer style
 | 
						|
        ApplyStackStylesToText(lText);
 | 
						|
      end;
 | 
						|
 | 
						|
      lCurNode := lCurNode.NextSibling;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
begin
 | 
						|
  lx := 0.0;
 | 
						|
  ly := 0.0;
 | 
						|
 | 
						|
  // 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>
 | 
						|
  // For simple text, but much more complex structures appear if there are
 | 
						|
  // text spans
 | 
						|
 | 
						|
  lParagraph := TvParagraph.Create(AData);
 | 
						|
  lParagraph.YPos_NeedsAdjustment_DelFirstLineBodyHeight := True;
 | 
						|
  lTextSpanStack := TSVGObjectStack.Create;
 | 
						|
  lCurStyle := TSVGTextSpanStyle.Create;
 | 
						|
  lTextSpanStack.Push(lCurStyle);
 | 
						|
 | 
						|
  // read the attributes
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if  lNodeName = 'x' then
 | 
						|
      lx := lx + StringWithUnitToFloat(lNodeValue, sckX, suPX, suMM)
 | 
						|
    else if lNodeName = 'y' then
 | 
						|
      ly := ly + StringWithUnitToFloat(lNodeValue, sckY, suPX, suMM)
 | 
						|
    else if lNodeName = 'id' then
 | 
						|
    begin
 | 
						|
      lName := lNodeValue;
 | 
						|
      lParagraph.Name := lName;
 | 
						|
    end
 | 
						|
    else if lNodeName = 'style' then
 | 
						|
      ReadSVGStyle(AData, lNodeValue, nil, lCurStyle)
 | 
						|
    else if IsAttributeFromStyle(lNodeName) then
 | 
						|
    begin
 | 
						|
      ReadSVGFontStyleWithKeyAndValue(lNodeName, lNodeValue, nil, lCurStyle);
 | 
						|
  //    ReadSVGGeneralStyleWithKeyAndValue(AData, lNodeName, lNodeValue, lParagraph);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  // Takes into account a possible transformation matrix
 | 
						|
  lx := lx + lParagraph.X;
 | 
						|
  ly := ly + lParagraph.Y;
 | 
						|
 | 
						|
  // Set the coordinates -> Don't use ConvertSVGCoordinatesToFPVCoordinates
 | 
						|
  // because StringWithUnitToFloat(..sckX..) already makes all conversions necessary
 | 
						|
  lParagraph.X := lx;
 | 
						|
  lParagraph.Y := ly;
 | 
						|
 | 
						|
  // 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
 | 
						|
  ReadTextSpans(ANode);
 | 
						|
 | 
						|
  // read the attributes
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if IsAttributeFromStyle(lNodeName) then
 | 
						|
      ReadSVGGeneralStyleWithKeyAndValue(AData, lNodeName, lNodeValue, lParagraph);
 | 
						|
  end;
 | 
						|
 | 
						|
  // Finalization
 | 
						|
  lCurObject := lTextSpanStack.Pop();
 | 
						|
  if lCurObject <> nil then lCurObject.Free;
 | 
						|
  lTextSpanStack.Free;
 | 
						|
  Result := lParagraph;
 | 
						|
end;
 | 
						|
 | 
						|
// <use xlink:href="#svgbar" transform="rotate(45)"/>
 | 
						|
// It might use another entity or use something from Defs
 | 
						|
function TvSVGVectorialReader.ReadUseFromNode(ANode: TDOMNode;
 | 
						|
  AData: TvVectorialPage; ADoc: TvVectorialDocument): TvEntity;
 | 
						|
var
 | 
						|
  lInsert: TvInsert;
 | 
						|
  lXLink: DOMString = '';
 | 
						|
  lInsertedEntity: TvEntity;
 | 
						|
  i: Integer;
 | 
						|
  lx, ly: Double;
 | 
						|
  lNodeName, lNodeValue: DOMString;
 | 
						|
begin
 | 
						|
  Result := nil;
 | 
						|
  lx := 0.0;
 | 
						|
  ly := 0.0;
 | 
						|
 | 
						|
  // read the attributes
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if lNodeName = 'xlink:href' then
 | 
						|
    begin
 | 
						|
      lXLink := lNodeValue;
 | 
						|
      lXLink := Copy(lXLink, 2, Length(lXLink));
 | 
						|
    end
 | 
						|
    else if lNodeName = 'x' then
 | 
						|
      lx := StringWithUnitToFloat(lNodeValue, sckX)
 | 
						|
    else if lNodeName = 'y' then
 | 
						|
      ly := StringWithUnitToFloat(lNodeValue, sckY);
 | 
						|
  end;
 | 
						|
 | 
						|
  if lXLink = '' then Exit; // nothing to insert, so give up
 | 
						|
 | 
						|
  lInsertedEntity := AData.FindEntityWithNameAndType(lXLink, TvEntity, True);
 | 
						|
  if lInsertedEntity = nil then Exit; // nothing to insert, give up!
 | 
						|
 | 
						|
  lInsert := TvInsert.Create(nil);
 | 
						|
  lInsert.InsertEntity := lInsertedEntity;
 | 
						|
  lInsert.x := lx;
 | 
						|
  lInsert.y := ly;
 | 
						|
 | 
						|
  // Apply the styles
 | 
						|
  // read the attributes
 | 
						|
  for i := 0 to ANode.Attributes.Length - 1 do
 | 
						|
  begin
 | 
						|
    lNodeName := ANode.Attributes.Item[i].NodeName;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if lNodeName = 'style' then
 | 
						|
    begin
 | 
						|
      ReadSVGStyle(AData, lNodeValue, lInsert);
 | 
						|
    end
 | 
						|
    else if IsAttributeFromStyle(lNodeName) then
 | 
						|
    begin
 | 
						|
      lInsert.SetElements += ReadSVGPenStyleWithKeyAndValue(lNodeName, lNodeValue, lInsert);
 | 
						|
      lInsert.SetElements += ReadSVGBrushStyleWithKeyAndValue(lNodeName, lNodeValue, lInsert);
 | 
						|
      lInsert.SetElements += ReadSVGFontStyleWithKeyAndValue(lNodeName, lNodeValue, lInsert);
 | 
						|
      ReadSVGGeneralStyleWithKeyAndValue(AData, lNodeName, lNodeValue, lInsert);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  // We need to add this hack here, otherwise the Height is added twice
 | 
						|
  // to inserted items: Once in the Insert and yet another time in the
 | 
						|
  // coordinates of the inserted item!
 | 
						|
  if vrfSVG_UseBottomLeftCoords in Settings.VecReaderFlags then
 | 
						|
    lInsert.Y := lInsert.Y - AData.Height;
 | 
						|
 | 
						|
  Result := lInsert;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.StringToPenPattern(const AStr: String;
 | 
						|
  var APen: TvPen);
 | 
						|
var
 | 
						|
  float_patt: TDoubleArray;
 | 
						|
  patt: array of LongWord;
 | 
						|
  i: Integer;
 | 
						|
begin
 | 
						|
  if AStr = 'none' then
 | 
						|
    exit;
 | 
						|
 | 
						|
  float_patt := ReadSpaceSeparatedFloats(AStr, ',');
 | 
						|
  if Length(float_patt) < 2 then
 | 
						|
    exit;
 | 
						|
 | 
						|
  SetLength(patt, Length(float_patt));
 | 
						|
  for i:=0 to High(patt) do
 | 
						|
  begin
 | 
						|
    if float_patt[i] < 0 then
 | 
						|
      raise Exception.CreateFmt('Incorrect value in stroke-dasharray "%s"', [AStr]);
 | 
						|
    patt[i] := round(float_patt[i]);
 | 
						|
  end;
 | 
						|
 | 
						|
  case Length(patt) of
 | 
						|
    2: if patt[1] = 5 then
 | 
						|
         case patt[0] of
 | 
						|
           3: begin APen.Style := psDot;  exit; end;     // stroke-dasharray: 3, 5
 | 
						|
           9: begin APen.Style := psDash; exit; end;     // stroke-dasharray: 9, 5
 | 
						|
         end;
 | 
						|
    4: if (patt[0] = 9) and (patt[1] = 5) and (patt[2] = 3) and (patt[3] = 5) then
 | 
						|
       begin                             // stroke-dasharray: 9, 5, 3, 5
 | 
						|
         APen.Style := psDashDot;
 | 
						|
         exit;
 | 
						|
       end;
 | 
						|
    6: if (patt[0] = 9) and (patt[1] = 5) and (patt[2] = 3) and (patt[3] = 5) and
 | 
						|
          (patt[4] = 3) and (patt[5] = 5)
 | 
						|
       then begin                       // stroke-dasharray: 9, 5, 3, 5, 3, 5
 | 
						|
         APen.Style := psDashDotDot;
 | 
						|
         exit;
 | 
						|
       end;
 | 
						|
  end;
 | 
						|
  APen.Style := psPattern;
 | 
						|
  APen.Pattern := patt;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.StringWithUnitToFloat(AStr: string;
 | 
						|
  ACoordKind: TSVGCoordinateKind = sckUnknown; ADefaultUnit: TSVGUnit = suPX;
 | 
						|
  ATargetUnit: TSVGUnit = suPX): Double;
 | 
						|
var
 | 
						|
  UnitStr, ValueStr: string;
 | 
						|
  Len: Integer;
 | 
						|
  LastChar: Char;
 | 
						|
  ViewPortApplied: Boolean = False;
 | 
						|
 | 
						|
  procedure DoViewBoxAdjust();
 | 
						|
  begin
 | 
						|
    if ViewBoxAdjustment then
 | 
						|
    begin
 | 
						|
      case ACoordKind of
 | 
						|
        sckX:      Result := (Result - ViewBox_Left) * Page_Width / ViewBox_Width;
 | 
						|
        sckXDelta,
 | 
						|
        sckXSize:  Result := Result * Page_Width / ViewBox_Width;
 | 
						|
      end;
 | 
						|
      if not (vrfSVG_UseBottomLeftCoords in Settings.VecReaderFlags) then
 | 
						|
      begin
 | 
						|
        case ACoordKind of
 | 
						|
          sckY:      Result := (Result - ViewBox_Top) * Page_Height / ViewBox_Height;
 | 
						|
          sckYDelta,
 | 
						|
          sckYSize:  Result := Result * Page_Height / ViewBox_Height;
 | 
						|
        end;
 | 
						|
      end
 | 
						|
      else
 | 
						|
      begin
 | 
						|
        case ACoordKind of
 | 
						|
          sckY:      Result := Page_Height - (Result - ViewBox_Top) * Page_Height / ViewBox_Height;
 | 
						|
          sckYDelta: Result := - (Result - ViewBox_Top) * Page_Height / ViewBox_Height;
 | 
						|
          sckYSize:  Result := Result * Page_Height / ViewBox_Height;
 | 
						|
        end;
 | 
						|
      end;
 | 
						|
      ViewPortApplied := True;
 | 
						|
    end
 | 
						|
    else
 | 
						|
    begin
 | 
						|
      if vrfSVG_UseBottomLeftCoords in Settings.VecReaderFlags then
 | 
						|
      begin
 | 
						|
        case ACoordKind of
 | 
						|
          sckY:      Result := Page_Height - Result;
 | 
						|
          sckYDelta: Result := - Result;
 | 
						|
        end;
 | 
						|
      end;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  procedure DoProcessMM_End();
 | 
						|
  begin
 | 
						|
    if ATargetUnit = suPX then
 | 
						|
      Result := Result / FLOAT_MILLIMETERS_PER_PIXEL;
 | 
						|
    DoViewBoxAdjust();
 | 
						|
  end;
 | 
						|
 | 
						|
  procedure DoProcessPX();
 | 
						|
  begin
 | 
						|
    Result := StrToFloat(ValueStr, FPointSeparator);
 | 
						|
    case ATargetUnit of
 | 
						|
    suMM: Result := Result * FLOAT_MILLIMETERS_PER_PIXEL;
 | 
						|
    suPT: Result := Result * FLOAT_POINTS_PER_PIXEL;
 | 
						|
    end;
 | 
						|
    DoViewBoxAdjust();
 | 
						|
  end;
 | 
						|
 | 
						|
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);
 | 
						|
    DoProcessMM_End();
 | 
						|
  end
 | 
						|
  else if UnitStr = 'cm' then
 | 
						|
  begin
 | 
						|
    ValueStr := Copy(AStr, 1, Len-2);
 | 
						|
    Result := StrToFloat(ValueStr, FPointSeparator);
 | 
						|
    Result := Result * 10;
 | 
						|
    DoProcessMM_End();
 | 
						|
  end
 | 
						|
  else if UnitStr = 'in' then
 | 
						|
  begin
 | 
						|
    ValueStr := Copy(AStr, 1, Len-2);
 | 
						|
    Result := StrToFloat(ValueStr, FPointSeparator);
 | 
						|
    Result := Result * 25.4;
 | 
						|
    DoProcessMM_End();
 | 
						|
  end
 | 
						|
  else if UnitStr = 'px' then
 | 
						|
  begin
 | 
						|
    ValueStr := Copy(AStr, 1, Len-2);
 | 
						|
    DoProcessPX();
 | 
						|
  end
 | 
						|
  else if LastChar = '%' then
 | 
						|
  begin
 | 
						|
    ValueStr := Copy(AStr, 1, Len-1);
 | 
						|
    Result := StrToInt(ValueStr);
 | 
						|
  end
 | 
						|
  else if UnitStr = 'pt' then
 | 
						|
  begin
 | 
						|
    ValueStr := Copy(AStr, 1, Len-2);
 | 
						|
    Result := StrToFloat(ValueStr, FPointSeparator);
 | 
						|
  end
 | 
						|
  // Now process default values
 | 
						|
  else if ADefaultUnit = suMM then
 | 
						|
  begin
 | 
						|
    ValueStr := AStr;
 | 
						|
    Result := StrToFloat(ValueStr, FPointSeparator);
 | 
						|
    DoProcessMM_End();
 | 
						|
  end
 | 
						|
  else if ADefaultUnit = suPX then
 | 
						|
  begin
 | 
						|
    ValueStr := AStr;
 | 
						|
    DoProcessPX();
 | 
						|
  end
 | 
						|
  else // If there is no unit and no matching default, just use StrToFloat
 | 
						|
  begin
 | 
						|
    Result := StrToFloat(AStr, FPointSeparator);
 | 
						|
    DoViewBoxAdjust();
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
function TvSVGVectorialReader.StringFloatZeroToOneToWord(AStr: string): Word;
 | 
						|
begin
 | 
						|
  Result := Round(StrToFloat(AStr, FPointSeparator) * $FFFF);
 | 
						|
end;
 | 
						|
 | 
						|
{@@ Converts a number string to a floating-point number. If the string has a
 | 
						|
    % character at its end then it is removed, and the numerical value is
 | 
						|
    divided by 100. }
 | 
						|
function TvSVGVectorialReader.StringWithPercentToFloat(AStr: String): Double;
 | 
						|
begin
 | 
						|
  if AStr[Length(AStr)] = '%' then
 | 
						|
  begin
 | 
						|
    Delete(AStr, Length(AStr), 1);
 | 
						|
    Result := 0.01 * StrToFloat(trim(AStr), FPointSeparator);
 | 
						|
  end else
 | 
						|
    Result := StrToFloat(AStr, FPointSeparator);
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.ConvertSVGCoordinatesToFPVCoordinates(
 | 
						|
  const AData: TvVectorialPage; const ASrcX, ASrcY: Double;
 | 
						|
  var ADestX,ADestY: Double; ADoViewBoxAdjust: Boolean = True);
 | 
						|
begin
 | 
						|
  ADestX := ASrcX * FLOAT_MILLIMETERS_PER_PIXEL;
 | 
						|
  if ViewBoxAdjustment and ADoViewBoxAdjust then
 | 
						|
  begin
 | 
						|
    ADestX := (ASrcX - ViewBox_Left) * Page_Width / ViewBox_Width;
 | 
						|
  end;
 | 
						|
 | 
						|
  if not (vrfSVG_UseBottomLeftCoords in Settings.VecReaderFlags) then
 | 
						|
  begin
 | 
						|
    ADestY := ASrcY * FLOAT_MILLIMETERS_PER_PIXEL;
 | 
						|
    if ViewBoxAdjustment and ADoViewBoxAdjust then
 | 
						|
    begin
 | 
						|
      ADestY := (ASrcY - ViewBox_Top) * Page_Height / ViewBox_Height;
 | 
						|
    end;
 | 
						|
  end
 | 
						|
  else
 | 
						|
  begin
 | 
						|
    ADestY := AData.Height - ASrcY * FLOAT_MILLIMETERS_PER_PIXEL;
 | 
						|
    if ViewBoxAdjustment and ADoViewBoxAdjust then
 | 
						|
    begin
 | 
						|
      ADestY := AData.Height - (ASrcY - ViewBox_Top) * Page_Height / ViewBox_Height;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.ConvertSVGDeltaToFPVDelta(
 | 
						|
  const AData: TvVectorialPage; const ASrcX, ASrcY: Double; var ADestX,
 | 
						|
  ADestY: Double; ADoViewBoxAdjust: Boolean = True);
 | 
						|
begin
 | 
						|
  ADestX := ASrcX * FLOAT_MILLIMETERS_PER_PIXEL;
 | 
						|
  if ViewBoxAdjustment and ADoViewBoxAdjust then
 | 
						|
  begin
 | 
						|
    ADestX := ASrcX * Page_Width / ViewBox_Width;
 | 
						|
  end;
 | 
						|
 | 
						|
  if not (vrfSVG_UseBottomLeftCoords in Settings.VecReaderFlags) then
 | 
						|
  begin
 | 
						|
    ADestY := ASrcY * FLOAT_MILLIMETERS_PER_PIXEL;
 | 
						|
    if ViewBoxAdjustment and ADoViewBoxAdjust then
 | 
						|
    begin
 | 
						|
      ADestY := ASrcY * Page_Height / ViewBox_Height;
 | 
						|
    end;
 | 
						|
  end
 | 
						|
  else
 | 
						|
  begin
 | 
						|
    ADestY := -ASrcY * FLOAT_MILLIMETERS_PER_PIXEL;  // fpc y coords grow upward, svg downward
 | 
						|
    if ViewBoxAdjustment and ADoViewBoxAdjust then
 | 
						|
    begin
 | 
						|
      ADestY := -ASrcY * Page_Height / ViewBox_Height;
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.ConvertSVGSizeToFPVSize(
 | 
						|
  const AData: TvVectorialPage; const ASrcX, ASrcY: Double; var ADestX,
 | 
						|
  ADestY: Double; ADoViewBoxAdjust: Boolean = True);
 | 
						|
begin
 | 
						|
  ADestX := ASrcX * FLOAT_MILLIMETERS_PER_PIXEL;
 | 
						|
  ADestY := ASrcY * FLOAT_MILLIMETERS_PER_PIXEL;
 | 
						|
  if ViewBoxAdjustment and ADoViewBoxAdjust then
 | 
						|
  begin
 | 
						|
    ADestX := ASrcX * Page_Width / ViewBox_Width;
 | 
						|
    ADestY := ASrcY * Page_Height / ViewBox_Height;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.AutoDetectDocSize(var ALeft, ATop, ARight, ABottom: Double;
 | 
						|
  ABaseNode: TDOMNode);
 | 
						|
var
 | 
						|
  i: Integer;
 | 
						|
  lCurNode: TDOMNode;
 | 
						|
  lx, ly, lcx, lcy: Double;
 | 
						|
  lNodeName, lNodeValue: DomString;
 | 
						|
begin
 | 
						|
  lx := 0;
 | 
						|
  ly := 0;
 | 
						|
  lcx := 0;
 | 
						|
  lcy := 0;
 | 
						|
  // read the attributes
 | 
						|
  if ABaseNode.Attributes <> nil then
 | 
						|
  begin
 | 
						|
    for i := 0 to ABaseNode.Attributes.Length - 1 do
 | 
						|
    begin
 | 
						|
      lNodeName := ABaseNode.Attributes.Item[i].NodeName;
 | 
						|
      lNodeValue := ABaseNode.Attributes.Item[i].NodeValue;
 | 
						|
      if (lNodeName = 'x') or (lNodeName = 'cx') then
 | 
						|
        lx := lx + StringWithUnitToFloat(lNodeValue)
 | 
						|
      else if (lNodeName = 'y') or (lNodeName = 'cy') then
 | 
						|
        ly := ly + StringWithUnitToFloat(lNodeValue)
 | 
						|
      else if lNodeName = 'width' then
 | 
						|
        lcx := StringWithUnitToFloat(lNodeValue)
 | 
						|
      else if lNodeName = 'height' then
 | 
						|
        lcy := StringWithUnitToFloat(lNodeValue);
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  // Borders guessing
 | 
						|
  if lx < ALeft then ALeft := lx;
 | 
						|
  if ly < ATop then ATop := ly;
 | 
						|
  if lx + lcx > ARight then ARight := lx + lcx;
 | 
						|
  if ly + lcy > ABottom then ABottom := ly + lcy;
 | 
						|
 | 
						|
  // iterate through children
 | 
						|
  lCurNode := ABaseNode.FirstChild;
 | 
						|
  while Assigned(lCurNode) do
 | 
						|
  begin
 | 
						|
    AutoDetectDocSize(ALeft, ATop, ARight, ABottom, lCurNode);
 | 
						|
    lCurNode := lCurNode.NextSibling;
 | 
						|
  end;
 | 
						|
end;
 | 
						|
 | 
						|
// From 0..255 to 0..$FFFF
 | 
						|
// From 0%..100% to 0..$FFFF
 | 
						|
function TvSVGVectorialReader.SVGColorValueStrToWord(AStr: string): Word;
 | 
						|
var
 | 
						|
  lStr: string;
 | 
						|
begin
 | 
						|
  lStr := Trim(AStr);
 | 
						|
  if lStr[Length(lStr)] = '%' then
 | 
						|
  begin
 | 
						|
    lStr := Copy(lStr, 1, Length(lStr)-1);
 | 
						|
    Result := Round(StrToFloat(lStr, FPointSeparator) * $FFFF / 100);
 | 
						|
  end
 | 
						|
  else Result := StrToInt(lStr) * $101;
 | 
						|
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;
 | 
						|
  FBrushDefs := TFPList.Create;
 | 
						|
  FCSSDefs := TFPList.Create;
 | 
						|
end;
 | 
						|
 | 
						|
destructor TvSVGVectorialReader.Destroy;
 | 
						|
var
 | 
						|
  i: Integer;
 | 
						|
begin
 | 
						|
  FSVGPathTokenizer.Free;
 | 
						|
  FLayerStylesKeys.Free;
 | 
						|
  FLayerStylesValues.Free;
 | 
						|
  for i:=FBrushDefs.Count-1 downto 0 do TObject(FBrushDefs[i]).Free;
 | 
						|
  FBrushDefs.Free;
 | 
						|
  for i:=FCSSDefs.Count-1 downto 0 do TObject(FCSSDefs[i]).Free;
 | 
						|
  FCSSDefs.Free;
 | 
						|
 | 
						|
  inherited Destroy;
 | 
						|
end;
 | 
						|
 | 
						|
procedure TvSVGVectorialReader.ReadFromStream(AStream: TStream;
 | 
						|
  AData: TvVectorialDocument);
 | 
						|
var
 | 
						|
  Doc: TXMLDocument = nil;
 | 
						|
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, lNodeValue: DOMString;
 | 
						|
  ANode: TDOMElement;
 | 
						|
  i: Integer;
 | 
						|
  lCurEntity: TvEntity;
 | 
						|
  lViewBox: TDoubleArray;
 | 
						|
  lStr: string;
 | 
						|
  lDocNeedsSizeAutoDetection: Boolean = True;
 | 
						|
  lx, ly, lx2, ly2: Double;
 | 
						|
begin
 | 
						|
  FPathNumber := 0;
 | 
						|
  ViewBoxAdjustment := False;
 | 
						|
 | 
						|
  // ----------------
 | 
						|
  // Read the properties of the <svg> tag
 | 
						|
  // ----------------
 | 
						|
  AData.Width := StringWithUnitToFloat(Doc.DocumentElement.GetAttribute('width'), sckX, suPX, suMM);
 | 
						|
  lStr := Doc.DocumentElement.GetAttribute('height');
 | 
						|
  if lStr <> '' then
 | 
						|
  begin
 | 
						|
    lDocNeedsSizeAutoDetection := False;
 | 
						|
    AData.Height := StringWithUnitToFloat(lStr, sckX, suPX, suMM);
 | 
						|
  end;
 | 
						|
  Page_Width := AData.Width;
 | 
						|
  Page_Height := AData.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;
 | 
						|
    lNodeValue := ANode.Attributes.Item[i].NodeValue;
 | 
						|
    if lNodeName = 'viewBox' then
 | 
						|
    begin
 | 
						|
      lViewBox := ReadSpaceSeparatedFloats(lNodeValue, '');
 | 
						|
      if lDocNeedsSizeAutoDetection then // Has only ViewBox
 | 
						|
      begin
 | 
						|
        lDocNeedsSizeAutoDetection := False;
 | 
						|
        AData.Width := lViewBox[2] - lViewBox[0];
 | 
						|
        AData.Height := lViewBox[3] - lViewBox[1];
 | 
						|
        ViewBox_Left := 0;
 | 
						|
        ViewBox_Top := 0;
 | 
						|
        ViewBox_Width := 0;
 | 
						|
        ViewBox_Height := 0;
 | 
						|
      end
 | 
						|
      else // Has both viewBox and width/height!
 | 
						|
      begin
 | 
						|
        ViewBox_Left := lViewBox[0];
 | 
						|
        ViewBox_Top := lViewBox[1];
 | 
						|
        ViewBox_Width := lViewBox[2];
 | 
						|
        ViewBox_Height := lViewBox[3];
 | 
						|
        ViewBoxAdjustment := True;
 | 
						|
      end;
 | 
						|
    end
 | 
						|
    else if lNodeName = 'style' then
 | 
						|
    begin
 | 
						|
      {$ifdef SVG_MERGE_LAYER_STYLES}
 | 
						|
      ReadSVGStyleToStyleLists(lNodeValue, lLayerStyleKeys, lLayerStyleValues);
 | 
						|
      {$endif}
 | 
						|
    end
 | 
						|
    else if IsAttributeFromStyle(lNodeName) then
 | 
						|
    begin
 | 
						|
      {$ifdef SVG_MERGE_LAYER_STYLES}
 | 
						|
      lLayerStyleKeys.Add(lNodeName);
 | 
						|
      lLayerStyleValues.Add(lNodeValue);
 | 
						|
      {$endif}
 | 
						|
    end;
 | 
						|
  end;
 | 
						|
 | 
						|
  // Auto-detect the document size of necessary
 | 
						|
  if lDocNeedsSizeAutoDetection then
 | 
						|
  begin
 | 
						|
    lx := 0;
 | 
						|
    ly := 0;
 | 
						|
    lx2 := 0;
 | 
						|
    ly2 := 0;
 | 
						|
    lCurNode := Doc.DocumentElement.FirstChild;
 | 
						|
    while Assigned(lCurNode) do
 | 
						|
    begin
 | 
						|
      AutoDetectDocSize(lx, ly, lx2, ly2, lCurNode);
 | 
						|
      lCurNode := lCurNode.NextSibling;
 | 
						|
    end;
 | 
						|
    AData.Width := lx2 - lx;
 | 
						|
    AData.Height := ly2 - ly;
 | 
						|
  end;
 | 
						|
 | 
						|
  // Make sure the latest page size is synchronized with auto-detected
 | 
						|
  // or ViewBox-only obtained size
 | 
						|
  Page_Width := AData.Width;
 | 
						|
  Page_Height := AData.Height;
 | 
						|
 | 
						|
  // ----------------
 | 
						|
  // Now process the elements
 | 
						|
  // ----------------
 | 
						|
  lCurNode := Doc.DocumentElement.FirstChild;
 | 
						|
  lPage := AData.AddPage(not (vrfSVG_UseBottomLeftCoords in Settings.VecReaderFlags));
 | 
						|
  lPage.Width := AData.Width;
 | 
						|
  lPage.Height := AData.Height;
 | 
						|
  while Assigned(lCurNode) do
 | 
						|
  begin
 | 
						|
    lNodeName := lCurNode.NodeName;
 | 
						|
    if lNodeName = 'defs' then
 | 
						|
    begin
 | 
						|
      ReadDefsFromNode(lCurNode, lPage, AData);
 | 
						|
    end
 | 
						|
    else
 | 
						|
    begin
 | 
						|
      lCurEntity := ReadEntityFromNode(lCurNode, lPage, AData);
 | 
						|
      if lCurEntity <> nil then
 | 
						|
        lPage.AddEntity(lCurEntity);
 | 
						|
    end;
 | 
						|
    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.
 | 
						|
 |