{ 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: } 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; { 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; {