unit SVG;
interface
uses
SysUtils, Types, UITypes, Classes, Graphics, Generics.Defaults,
Generics.Collections, DoublePoint;
const
SVGLengthUnits: array[0..9] of string =
('', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pt', 'pc', '%');
type
TTagKind = (tkStart, tkEnd, tkSelfClose, tkWithChild, tkOptionalWithChild,
tkWithCDATA, tkComment);
TTransform = record
strict private
FData: string;
public
class operator Multiply(const L, R: TTransform): TTransform;
class operator Implicit(const ATransform: TTransform): string;
class operator Explicit(const S: string): TTransform;
end;
function Translation(const X: Single; const Y: Single = 0.0): TTransform; overload;
function Translation(const X: TVectorD): TTransform; overload;
function Scaling(const X: Single): TTransform; overload;
function Scaling(const X, Y: Single): TTransform; overload;
function Rotation(const Theta: Single; const X: Single = 0.0;
const Y: Single = 0.0): TTransform;
function RotationDeg(const Theta: Single; const X: Single = 0.0;
const Y: Single = 0.0): TTransform;
function RotationRad(const Theta: Single; const X: Single = 0.0;
const Y: Single = 0.0): TTransform;
function SkewX(const Theta: Single): TTransform;
function SkewY(const Theta: Single): TTransform;
function Matrix(const A, B, C, D, E, F: Single): TTransform;
function Identity: TTransform;
type
TSVGBuilder = class;
TTag = record
strict private type
TContentType = (ctText, ctXML);
strict private var
FKind: TTagKind;
FName: string;
FAttribs: TArray<TPair<string, string>>;
FContent: string;
FContentType: TContentType;
FSVG: TSVGBuilder;
public
class operator Implicit(const ATag: TTag): string;
function Associate(ASVG: TSVGBuilder): TTag;
function Name(const ATagName: string): TTag;
function StartTag: TTag;
function EndTag: TTag;
function SelfClosed: TTag;
function ContentText(const AContent: string): TTag;
function ContentXML(const AContent: string): TTag;
function CDATA(const AContent: string): TTag;
function Attrib(const AName, AValue: string): TTag; overload;
function Attrib(const AName: string; const AValue: Integer): TTag; overload;
function Attrib(const AName: string; const AValue: Single): TTag; overload;
function Attrib(const AName: string; const AValue: Double): TTag; overload;
function AttribIf(const AName, AValue: string; ACondition: Boolean): TTag; overload;
function AttribIf(const AName, AValue: string): TTag; overload;
function AttribIf(const AName: string; const AValue: Integer;
ACondition: Boolean): TTag; overload;
function AttribIf(const AName: string; const AValue: Single;
ACondition: Boolean): TTag; overload;
function AttribIf(const AName: string; const AValue: Double;
ACondition: Boolean): TTag; overload;
function Attribs(AAttribs: TDictionary<string, string>): TTag; overload;
function Attribs(AAttribs: TList<TPair<string, string>>): TTag; overload;
function Attribs(AAttribs: TArray<TPair<string, string>>): TTag; overload;
procedure Append(const AOpName: string = '');
procedure AppendIf(const AOpName: string);
function ID(const AID: string): TTag;
function &Class(const AClassName: string): TTag;
function Style(const AStyle: string): TTag;
function x(const Ax: string): TTag; overload;
function x(const Ax: Integer): TTag; overload;
function x(const Ax: Single): TTag; overload;
function x(const Ax: Double): TTag; overload;
function y(const Ay: string): TTag; overload;
function y(const Ay: Integer): TTag; overload;
function y(const Ay: Single): TTag; overload;
function y(const Ay: Double): TTag; overload;
function Width(const AWidth: string): TTag; overload;
function Width(const AWidth: Integer): TTag; overload;
function Width(const AWidth: Single): TTag; overload;
function Width(const AWidth: Double): TTag; overload;
function Height(const AHeight: string): TTag; overload;
function Height(const AHeight: Integer): TTag; overload;
function Height(const AHeight: Single): TTag; overload;
function Height(const AHeight: Double): TTag; overload;
function Transform(const ATransform: TTransform): TTag;
function TransformIf(const ATransform: TTransform; ACondition: Boolean): TTag;
function Fill(const AFill: string): TTag; overload;
function Fill(const AFill: TColor): TTag; overload;
function FillIf(const AFill: string; ACondition: Boolean): TTag; overload;
function FillIf(const AFill: TColor; ACondition: Boolean): TTag; overload;
function FillOpacity(const AFillOpacity: string): TTag; overload;
function FillOpacity(const AFillOpacity: Single): TTag; overload;
function Opacity(const AOpacity: string): TTag; overload;
function Opacity(const AOpacity: Single): TTag; overload;
function Stroke(const AStroke: string): TTag; overload;
function Stroke(const AStroke: TColor): TTag; overload;
function StrokeIf(const AStroke: string; ACondition: Boolean): TTag; overload;
function StrokeIf(const AStroke: TColor; ACondition: Boolean): TTag; overload;
function StrokeWidth(const AStrokeWidth: string): TTag; overload;
function StrokeWidth(const AStrokeWidth: Integer): TTag; overload;
function StrokeWidth(const AStrokeWidth: Single): TTag; overload;
function StrokeWidth(const AStrokeWidth: Double): TTag; overload;
function StrokeWidthIf(const AStrokeWidth: string; ACondition: Boolean): TTag; overload;
function StrokeWidthIf(const AStrokeWidth: Integer; ACondition: Boolean): TTag; overload;
function StrokeWidthIf(const AStrokeWidth: Single; ACondition: Boolean): TTag; overload;
function StrokeWidthIf(const AStrokeWidth: Double; ACondition: Boolean): TTag; overload;
function StrokeWidthPx(const AStrokeWidth: Integer): TTag; overload;
function StrokeWidthPx(const AStrokeWidth: Single): TTag; overload;
function TextAnchor(const AAnchor: string): TTag; overload;
function TextAnchor(AAnchor: TAlignment): TTag; overload;
function DominantBaseline(const AAnchor: string): TTag; overload;
function DominantBaseline(AAnchor: TVerticalAlignment): TTag; overload;
function MarkerStart(const AMarker: string): TTag;
function MarkerEnd(const AMarker: string): TTag;
function MarkerMid(const AMarker: string): TTag;
function A_Begin(const ABegin: string): TTag; overload;
function A_Begin(const ABegin: Integer): TTag; overload;
function A_Begin(const ABegin: Single): TTag; overload;
function A_End(const AEnd: string): TTag; overload;
function A_End(const AEnd: Integer): TTag; overload;
function A_End(const AEnd: Single): TTag; overload;
function A_Duration(const ADuration: string): TTag; overload;
function A_Duration(const ADuration: Integer): TTag; overload;
function A_Duration(const ADuration: Single): TTag; overload;
function A_RepeatCount(const ARepeatCount: string): TTag; overload;
function A_RepeatCount(const ARepeatCount: Integer): TTag; overload;
function A_RepeatIndefinitely: TTag;
function A_RepeatDuration(const ARepeatDuration: string): TTag; overload;
function A_RepeatDuration(const ARepeatDuration: Integer): TTag; overload;
function A_RepeatDuration(const ARepeatDuration: Single): TTag; overload;
function A_Values(const AValues: string): TTag; overload;
function A_Values(const AValues: array of Integer): TTag; overload;
function A_Values(const AValues: array of Single): TTag; overload;
function A_KeyTimes(const AKeyTimes: string): TTag; overload;
function A_KeyTimes(const AKeyTimes: array of Integer): TTag; overload;
function A_KeyTimes(const AKeyTimes: array of Single): TTag; overload;
function A_From(const AFrom: string): TTag; overload;
function A_From(const AFrom: Integer): TTag; overload;
function A_From(const AFrom: Single): TTag; overload;
function A_To(const ATo: string): TTag; overload;
function A_To(const ATo: Integer): TTag; overload;
function A_To(const ATo: Single): TTag; overload;
function FontFamily(const AFamilies: string): TTag;
function FontSize(const ASize: string): TTag; overload;
function FontSize(const ASize: Integer): TTag; overload;
function FontSize(const ASize: Single): TTag; overload;
function FontStretch(const AStretch: string): TTag;
function FontStyle(const AStyle: string): TTag;
function FontItalic: TTag;
function FontOblique: TTag;
function FontVariant(const AVariant: string): TTag;
function FontWeight(const AWeight: string): TTag;
function FontBold: TTag;
function FontBolder: TTag;
function FontLighter: TTag;
function Font(AFont: TFont): TTag;
function RescalePoints(const XFactor, YFactor: Double): TTag;
function Clone: TTag;
end;
TCSSBuilder = record
private type
TRule = string;
TRuleSet = record
Selector: string;
Rules: TArray<TRule>;
end;
strict private var
RuleSets: TArray<TRuleSet>;
public
class operator Implicit(const ACSS: TCSSBuilder): string;
function RuleSet(const ASelector: string; const ARules: array of TRule): TCSSBuilder;
end;
ESVGException = class(Exception);
TTransformType = (ttTranslation, ttScaling, ttRotation, ttSkewX, ttSkewY);
TViewBox = record
Xmin,
Ymin,
Width,
Height: Single;
class operator Implicit(const AViewBox: TViewBox): string;
constructor Create(const Xmin, Ymin, Width, Height: Integer); overload;
constructor Create(const Xmin, Ymin, Width, Height: Single); overload;
constructor Create(const Xmin, Ymin, Width, Height, ScaleX, ScaleY: Single); overload;
function Xmax: Single; inline;
function Ymax: Single; inline;
function Valid: Boolean; inline;
end;
TSVGBuilder = class
strict private
type
TNodeType = (ntDefs, ntSymbol, ntMarker, ntGroup, ntSwitch, ntMetadata);
var
FSvgVersion: string;
FWidth, FHeight: string;
FStretch: Boolean;
FViewBox: TViewBox;
FLanguage, FTitle, FDescription: string;
FLineBreak: string;
FDefs: TDictionary<string, string>;
FParts: TList<string>;
FStack: TStack<TNodeType>;
FDefines: TDictionary<string, Pointer>;
FDefID: string;
FNamespaces: TDictionary<string, string>;
function GetAsXML: string;
procedure StructureError;
function GetAspectRatio: Double;
procedure EndDefinition;
private
FAbstract: Boolean;
procedure AddTag(const ATag: TTag);
public
function EndTag(const ATagName: string = ''): TTag;
function OptionalTag(const ATagName: string = ''): TTag;
function StartTag(const ATagName: string = ''): TTag;
function Tag(const ATagName: string = ''): TTag;
function XmlComment(const AComment: string = ''): TTag;
public
constructor Create(AAbstract: Boolean = False); virtual;
destructor Destroy; override;
procedure DefineNamespace(const APrefix, AURL: string);
procedure Define(const AName: string);
function Undefine(const AName: string): Boolean;
function Line(const x1, y1, x2, y2: string): TTag; overload;
function Line(const x1, y1, x2, y2: Integer): TTag; overload;
function Line(const x1, y1, x2, y2: Single): TTag; overload;
function Line(const x1, y1, x2, y2: Double): TTag; overload;
function Line(const P, Q: TPoint): TTag; overload;
function Line(const P, Q: TPointF): TTag; overload;
function Line(const P, Q: TPointD): TTag; overload;
function PolyLine(const APoints: string): TTag; overload;
function PolyLine(const APoints: array of TPoint): TTag; overload;
function PolyLine(const APoints: array of TPointF): TTag; overload;
function PolyLine(const APoints: array of TPointD): TTag; overload;
function Polygon(const APoints: string): TTag; overload;
function Polygon(const APoints: array of TPoint): TTag; overload;
function Polygon(const APoints: array of TPointF): TTag; overload;
function Polygon(const APoints: array of TPointD): TTag; overload;
function Rect(const x, y, w, h: string): TTag; overload;
function Rect(const x, y, w, h: Single): TTag; overload;
function Rect(const x, y, w, h: Double): TTag; overload;
function Rect(const R: TRect): TTag; overload;
function Rect(const R: TRectF): TTag; overload;
function Rect(const R: TRectD): TTag; overload;
function Circle(const x, y, r: string): TTag; overload;
function Circle(const x, y, r: Integer): TTag; overload;
function Circle(const x, y, r: Single): TTag; overload;
function Circle(const x, y, r: Double): TTag; overload;
function Circle(const x, y: Single; const r: string): TTag; overload;
function Circle(const x: TPoint; const r: Integer): TTag; overload;
function Circle(const x: TPointF; const r: Single): TTag; overload;
function Circle(const x: TPointD; const r: Double): TTag; overload;
function Circle(const r: Integer): TTag; overload;
function Circle(const r: Single): TTag; overload;
function Circle(const r: Double): TTag; overload;
function Ellipse(const x, y, rx, ry: string): TTag; overload;
function Ellipse(const x, y, rx, ry: Integer): TTag; overload;
function Ellipse(const x, y, rx, ry: Single): TTag; overload;
function Ellipse(const x, y, rx, ry: Double): TTag; overload;
function Ellipse(const x: TPoint; const rx, ry: Integer): TTag; overload;
function Ellipse(const x: TPointF; const rx, ry: Single): TTag; overload;
function Ellipse(const x: TPointD; const rx, ry: Double): TTag; overload;
function Ellipse2(const x, y, rx, ry: string): TTag; overload;
function Ellipse2(const x, y, rx, ry: Integer): TTag; overload;
function Ellipse2(const x, y, rx, ry: Single): TTag; overload;
function Ellipse2(const x, y, rx, ry: Double): TTag; overload;
function Ellipse2(const x: TPoint; const rx, ry: Integer): TTag; overload;
function Ellipse2(const x: TPointF; const rx, ry: Single): TTag; overload;
function Ellipse2(const x: TPointD; const rx, ry: Double): TTag; overload;
function Path(const d: string; const APathLength: Single = 0.0): TTag;
function Arc(const x, y, rx, ry, t0, t1: Double): TTag; overload;
function Arc(const X: TPointD; const rx, ry, t0, t1: Double): TTag; overload;
function Sector(const x, y, rx, ry, t0, t1: Double): TTag; overload;
function Sector(const X: TPointD; const rx, ry, t0, t1: Double): TTag; overload;
function Text(const S: string = ''): TTag; overload;
function Text(const x, y: string; const S: string = ''): TTag; overload;
function Text(const x, y: Integer; const S: string = ''): TTag; overload;
function Text(const x, y: Single; const S: string = ''): TTag; overload;
function Text(const x, y: Double; const S: string = ''): TTag; overload;
function Text(const x: TPoint; const S: string = ''): TTag; overload;
function Text(const x: TPointF; const S: string = ''): TTag; overload;
function Text(const x: TPointD; const S: string = ''): TTag; overload;
function TextPath(const APathRef: string; const S: string = '';
const AStartOffset: string = ''): TTag;
function TextSpan(const S: string = ''): TTag;
function Link(const ref: string): TTag;
function Image(const x, y, w, h, ref: string): TTag; overload;
function Image(const x, y, w, h: Integer; const ref: string): TTag; overload;
function Image(const x, y, w, h: Single; const ref: string): TTag; overload;
function Image(const x, y, w, h: Double; const ref: string): TTag; overload;
function Image(const x: TPoint; const s: TSize; const ref: string): TTag; overload;
function Image(const x: TPointF; const s: TSizeF; const ref: string): TTag; overload;
function Image(const R: TRect; const ref: string): TTag; overload;
function Image(const R: TRectF; const ref: string): TTag; overload;
function Image(const R: TRectD; const ref: string): TTag; overload;
function HasID(const ID: string): Boolean;
function BeginSymbol(const AViewBox: string = ''): TTag;
function EndSymbol: TTag;
function DefSymbol(const ID: string; const AViewBox: string = ''): TTag;
procedure EndDefSymbol;
function BeginMarker(const AViewBox, ARefX, ARefY, AMarkerWidth,
AMarkerHeight: string; const AStrokeWidth: Boolean = True): TTag; overload;
function BeginMarker(const AViewBox: string; ARefX, ARefY, AMarkerWidth,
AMarkerHeight: Integer; const AStrokeWidth: Boolean = True): TTag; overload;
function BeginMarker(const AViewBox: string; ARefX, ARefY, AMarkerWidth,
AMarkerHeight: Single; const AStrokeWidth: Boolean = True): TTag; overload;
function BeginMarker(const AViewBox: string; ARefX, ARefY, AMarkerWidth,
AMarkerHeight: Double; const AStrokeWidth: Boolean = True): TTag; overload;
function EndMarker: TTag;
function DefMarker(const ID, AViewBox, ARefX, ARefY, AMarkerWidth,
AMarkerHeight: string; const AStrokeWidth: Boolean = True): TTag; overload;
function DefMarker(const ID, AViewBox: string; ARefX, ARefY, AMarkerWidth,
AMarkerHeight: Integer; const AStrokeWidth: Boolean = True): TTag; overload;
function DefMarker(const ID, AViewBox: string; ARefX, ARefY, AMarkerWidth,
AMarkerHeight: Single; const AStrokeWidth: Boolean = True): TTag; overload;
function DefMarker(const ID, AViewBox: string; ARefX, ARefY, AMarkerWidth,
AMarkerHeight: Double; const AStrokeWidth: Boolean = True): TTag; overload;
procedure EndDefMarker;
function Use(const ref: string): TTag; overload;
function Use(const x, y, w, h, ref: string): TTag; overload;
function Use(const x, y, ref: string): TTag; overload;
function Use(const x, y, w, h: Integer; const ref: string): TTag; overload;
function Use(const x, y: Integer; const ref: string): TTag; overload;
function Use(const x, y: Single; const ref: string): TTag; overload;
function Use(const x, y: Double; const ref: string): TTag; overload;
function Use(const x, y, w, h: Single; const ref: string): TTag; overload;
function Use(const x, y, w, h: Double; const ref: string): TTag; overload;
function Use(const X: TPoint; const S: TSize; const ref: string): TTag; overload;
function Use(const X: TPoint; const ref: string): TTag; overload;
function Use(const X: TPointF; const S: TSizeF; const ref: string): TTag; overload;
function Use(const X: TPointF; const ref: string): TTag; overload;
function Use(const X: TPointD; const S: TSizeD; const ref: string): TTag; overload;
function Use(const X: TPointD; const ref: string): TTag; overload;
function Animate(const AAttributeName: string = '';
const AAttributeType: string = ''): TTag;
function AnimateMotion(const APathData: string = '';
const ARotate: string = ''): TTag;
function AnimateTransform(const AType: TTransformType): TTag;
function AnimateSet(const AAttributeName, ATo: string): TTag;
function MPath(const APathName: string): TTag;
function BeginSwitch: TTag;
function EndSwitch: TTag;
function Style(const CSS: string): TTag;
function Script(const AScript: string): TTag;
function BeginGroup: TTag;
function GroupTitle(const ATitle: string): TTag;
function GroupDescription(const ADesc: string): TTag;
function EndGroup: TTag;
function BeginDefs: TTag;
function EndDefs: TTag;
function BeginMetadata: TTag;
function EndMetadata: TTag;
function SourceComment(const AText: string): TTag;
procedure SaveToFile(const AFileName: TFileName);
property AsXML: string read GetAsXML;
property LineBreak: string read FLineBreak write FLineBreak;
property SVGVersion: string read FSvgVersion write FSvgVersion;
property Width: string read FWidth write FWidth;
property Height: string read FHeight write FHeight;
property Stretch: Boolean read FStretch write FStretch;
property AspectRatio: Double read GetAspectRatio;
property ViewBox: TViewBox read FViewBox write FViewBox;
property Language: string read FLanguage write FLanguage;
property Title: string read FTitle write FTitle;
property Description: string read FDescription write FDescription;
end;
function CSS: TCSSBuilder;
function px(const AValue: Integer): string; overload;
function px(const AValue: Single): string; overload;
function px(const AValue: Double): string; overload;
implementation
uses
Math, StrUtils, IOUtils, ASColors, ASNum;
type
PTagKind = ^TTagKind;
var
InvFS: TFormatSettings;
function AddNamespace(AAttribs: TDictionary<string, string>;
const ANamespace: string): TArray<TPair<string, string>>; overload;
begin
Result := AAttribs.ToArray;
for var i := 0 to High(Result) do
if not Result[i].Key.Contains(':') then
Result[i].Key := ANamespace + ':' + Result[i].Key;
end;
function AddNamespace(AAttribs: TList<TPair<string, string>>;
const ANamespace: string): TArray<TPair<string, string>>; overload;
begin
Result := AAttribs.ToArray;
for var i := 0 to High(Result) do
if not Result[i].Key.Contains(':') then
Result[i].Key := ANamespace + ':' + Result[i].Key;
end;
function AddNamespace(AAttribs: TArray<TPair<string, string>>;
const ANamespace: string): TArray<TPair<string, string>>; overload;
begin
Result := Copy(AAttribs);
for var i := 0 to High(Result) do
if not Result[i].Key.Contains(':') then
Result[i].Key := ANamespace + ':' + Result[i].Key;
end;
function CSS: TCSSBuilder;
begin
Result := Default(TCSSBuilder);
end;
function px(const AValue: Integer): string;
begin
Result := AValue.ToString + 'px';
end;
function px(const AValue: Single): string;
begin
Result := FormatFloat('0.###', AValue, InvFS) + 'px';
end;
function px(const AValue: Double): string;
begin
Result := FormatFloat('0.###', AValue, InvFS) + 'px';
end;
function IntegerArrayToStringArray(const AValues: array of Integer): TArray<string>;
begin
SetLength(Result, Length(AValues));
for var i := 0 to High(AValues) do
Result[i] := AValues[i].ToString;
end;
function SingleArrayToStringArray(const AValues: array of Single): TArray<string>;
begin
SetLength(Result, Length(AValues));
for var i := 0 to High(AValues) do
Result[i] := AValues[i].ToString(InvFS);
end;
function PointToString(const APoint: TPoint): string; overload;
begin
with APoint do
Result := Format('%d,%d', [X, Y])
end;
function PointToString(const APoint: TPointF): string; overload;
begin
with APoint do
Result := Format('%g,%g', [X, Y], InvFS)
end;
function PointToString(const APoint: TPointD): string; overload;
begin
with APoint do
Result := Format('%g,%g', [X, Y], InvFS)
end;
function PointArrayToStringArray(const APoints: array of TPoint): TArray<string>; overload;
begin
SetLength(Result, Length(APoints));
for var i := 0 to High(APoints) do
Result[i] := PointToString(APoints[i]);
end;
function PointArrayToStringArray(const APoints: array of TPointF): TArray<string>; overload;
begin
SetLength(Result, Length(APoints));
for var i := 0 to High(APoints) do
Result[i] := PointToString(APoints[i]);
end;
function PointArrayToStringArray(const APoints: array of TPointD): TArray<string>; overload;
begin
SetLength(Result, Length(APoints));
for var i := 0 to High(APoints) do
Result[i] := PointToString(APoints[i]);
end;
function ParsePointString(const Data: string): TPointD;
begin
var CoordinateStrings := Data.Split([',']);
if Length(CoordinateStrings) <> 2 then
raise ESVGException.Create('Invalid point string.');
if
not TryStrToFloat(CoordinateStrings[0].Trim, Result.X, InvFS)
or
not TryStrToFloat(CoordinateStrings[1].Trim, Result.Y, InvFS)
then
raise ESVGException.Create('Invalid point string.');
end;
function SVGPointArrayParse(const Data: string): TArray<TPointD>;
begin
var PointStrings := Data.Split([#32], TStringSplitOptions.ExcludeEmpty);
SetLength(Result, Length(PointStrings));
for var i := 0 to High(Result) do
Result[i] := ParsePointString(PointStrings[i]);
end;
procedure TTag.Append(const AOpName: string);
begin
if Assigned(FSVG) then
begin
FSVG.AddTag(Self);
if not AOpName.IsEmpty then
FSVG.Define(AOpName);
end;
end;
procedure TTag.AppendIf(const AOpName: string);
begin
if Assigned(FSVG) then
if FSVG.Undefine(AOpName) then
FSVG.AddTag(Self);
end;
function TTag.Associate(ASVG: TSVGBuilder): TTag;
begin
if not ASVG.FAbstract then
FSVG := ASVG;
Result := Self;
end;
function TTag.Attrib(const AName, AValue: string): TTag;
begin
for var i := Low(FAttribs) to High(FAttribs) do
if FAttribs[i].Key = AName then
begin
if AName = 'class' then
FAttribs[i].Value := FAttribs[i].Value + IfThen(not FAttribs[i].Value.IsEmpty, #32) + AValue.Replace(#32, '-')
else if AName = 'style' then
FAttribs[i].Value := FAttribs[i].Value + IfThen(not FAttribs[i].Value.IsEmpty, '; ') + AValue.TrimRight([#32, ';'])
else
FAttribs[i].Value := AValue;
Exit(Self);
end;
SetLength(FAttribs, Succ(Length(FAttribs)));
FAttribs[High(FAttribs)].Key := AName;
FAttribs[High(FAttribs)].Value := AValue;
Result := Self;
end;
function TTag.AttribIf(const AName, AValue: string; ACondition: Boolean): TTag;
begin
if ACondition then
Result := Attrib(AName, AValue)
else
Result := Self;
end;
function TTag.Attrib(const AName: string; const AValue: Integer): TTag;
begin
Result := Attrib(AName, AValue.ToString);
end;
function TTag.Attrib(const AName: string; const AValue: Single): TTag;
begin
Result := Attrib(AName, AValue.ToString(InvFS));
end;
function TTag.Attrib(const AName: string; const AValue: Double): TTag;
begin
Result := Attrib(AName, AValue.ToString(InvFS));
end;
function TTag.AttribIf(const AName: string; const AValue: Integer;
ACondition: Boolean): TTag;
begin
Result := AttribIf(AName, AValue.ToString, ACondition);
end;
function TTag.AttribIf(const AName: string; const AValue: Single;
ACondition: Boolean): TTag;
begin
Result := AttribIf(AName, AValue.ToString(InvFS), ACondition);
end;
function TTag.AttribIf(const AName: string; const AValue: Double;
ACondition: Boolean): TTag;
begin
Result := AttribIf(AName, AValue.ToString(InvFS), ACondition);
end;
function TTag.AttribIf(const AName, AValue: string): TTag;
begin
Result := AttribIf(AName, AValue, not AValue.IsEmpty);
end;
function TTag.Attribs(AAttribs: TArray<TPair<string, string>>): TTag;
begin
var i := Length(FAttribs);
SetLength(FAttribs, Length(FAttribs) + Length(AAttribs));
for var a in AAttribs do
begin
FAttribs[i].Key := a.Key;
FAttribs[i].Value := a.Value;
Inc(i);
end;
Result := Self;
end;
function TTag.Attribs(AAttribs: TList<TPair<string, string>>): TTag;
begin
var i := Length(FAttribs);
SetLength(FAttribs, Length(FAttribs) + AAttribs.Count);
for var a in AAttribs do
begin
FAttribs[i].Key := a.Key;
FAttribs[i].Value := a.Value;
Inc(i);
end;
Result := Self;
end;
function TTag.Attribs(AAttribs: TDictionary<string, string>): TTag;
begin
var i := Length(FAttribs);
SetLength(FAttribs, Length(FAttribs) + AAttribs.Count);
for var a in AAttribs do
begin
FAttribs[i].Key := a.Key;
FAttribs[i].Value := a.Value;
Inc(i);
end;
Result := Self;
end;
function TTag.A_Begin(const ABegin: string): TTag;
begin
Result := AttribIf('begin', ABegin);
end;
function TTag.A_Begin(const ABegin: Integer): TTag;
begin
Result := A_Begin(ABegin.ToString + 's');
end;
function TTag.A_Begin(const ABegin: Single): TTag;
begin
Result := A_Begin(ABegin.ToString(InvFS) + 's');
end;
function TTag.A_End(const AEnd: string): TTag;
begin
Result := AttribIf('end', AEnd);
end;
function TTag.A_End(const AEnd: Integer): TTag;
begin
Result := A_End(AEnd.ToString + 's');
end;
function TTag.A_End(const AEnd: Single): TTag;
begin
Result := A_End(AEnd.ToString(InvFS) + 's');
end;
function TTag.A_Duration(const ADuration: Single): TTag;
begin
Result := A_Duration(ADuration.ToString(InvFS) + 's');
end;
function TTag.A_Duration(const ADuration: Integer): TTag;
begin
Result := A_Duration(ADuration.ToString + 's');
end;
function TTag.A_Duration(const ADuration: string): TTag;
begin
Result := AttribIf('dur', ADuration);
end;
function TTag.A_From(const AFrom: Single): TTag;
begin
Result := A_From(AFrom.ToString(InvFS));
end;
function TTag.A_From(const AFrom: Integer): TTag;
begin
Result := A_From(AFrom.ToString);
end;
function TTag.A_From(const AFrom: string): TTag;
begin
Result := AttribIf('from', AFrom);
end;
function TTag.A_To(const ATo: Single): TTag;
begin
Result := A_To(ATo.ToString(InvFS));
end;
function TTag.A_To(const ATo: Integer): TTag;
begin
Result := A_To(ATo.ToString);
end;
function TTag.A_To(const ATo: string): TTag;
begin
Result := AttribIf('To', ATo);
end;
function TTag.A_RepeatCount(const ARepeatCount: Integer): TTag;
begin
Result := A_RepeatCount(ARepeatCount.ToString);
end;
function TTag.A_RepeatDuration(const ARepeatDuration: string): TTag;
begin
Result := AttribIf('repeatDur', ARepeatDuration);
end;
function TTag.A_RepeatDuration(const ARepeatDuration: Integer): TTag;
begin
Result := A_RepeatDuration(ARepeatDuration.ToString + 's');
end;
function TTag.A_RepeatDuration(const ARepeatDuration: Single): TTag;
begin
Result := A_RepeatDuration(ARepeatDuration.ToString(InvFS) + 's');
end;
function TTag.A_RepeatIndefinitely: TTag;
begin
Result := A_RepeatCount('indefinite');
end;
function TTag.A_Values(const AValues: array of Single): TTag;
begin
Result := A_Values(string.Join(';', SingleArrayToStringArray(AValues)));
end;
function TTag.A_Values(const AValues: array of Integer): TTag;
begin
Result := A_Values(string.Join(';', IntegerArrayToStringArray(AValues)));
end;
function TTag.A_Values(const AValues: string): TTag;
begin
Result := AttribIf('values', AValues);
end;
function TTag.A_KeyTimes(const AKeyTimes: array of Single): TTag;
begin
Result := A_KeyTimes(string.Join(';', SingleArrayToStringArray(AKeyTimes)));
end;
function TTag.A_KeyTimes(const AKeyTimes: array of Integer): TTag;
begin
Result := A_KeyTimes(string.Join(';', IntegerArrayToStringArray(AKeyTimes)));
end;
function TTag.A_KeyTimes(const AKeyTimes: string): TTag;
begin
Result := AttribIf('keyTimes', AKeyTimes);
end;
function TTag.A_RepeatCount(const ARepeatCount: string): TTag;
begin
Result := AttribIf('repeatCount', ARepeatCount);
end;
function TTag.EndTag: TTag;
begin
FKind := tkEnd;
Result := Self;
end;
function TTag.Fill(const AFill: string): TTag;
begin
Result := AttribIf('fill', AFill);
end;
function TTag.Fill(const AFill: TColor): TTag;
begin
Result := Fill(ColorToHex(AFill));
end;
function TTag.FillIf(const AFill: TColor; ACondition: Boolean): TTag;
begin
Result := AttribIf('fill', ColorToHex(AFill), ACondition);
end;
function TTag.FillIf(const AFill: string; ACondition: Boolean): TTag;
begin
Result := AttribIf('fill', AFill, ACondition);
end;
function TTag.FillOpacity(const AFillOpacity: Single): TTag;
begin
Result := FillOpacity(AFillOpacity.ToString(InvFS));
end;
function TTag.FontSize(const ASize: string): TTag;
begin
Result := AttribIf('font-size', ASize);
end;
function TTag.FontSize(const ASize: Integer): TTag;
begin
Result := FontSize(ASize.ToString);
end;
function TTag.FontStretch(const AStretch: string): TTag;
begin
Result := AttribIf('font-stretch', AStretch);
end;
function TTag.FontStyle(const AStyle: string): TTag;
begin
Result := AttribIf('font-style', AStyle);
end;
function TTag.FontVariant(const AVariant: string): TTag;
begin
Result := AttribIf('font-variant', AVariant);
end;
function TTag.FontWeight(const AWeight: string): TTag;
begin
Result := AttribIf('font-weight', AWeight);
end;
function TTag.Height(const AHeight: string): TTag;
begin
Result := AttribIf('height', AHeight);
end;
function TTag.Height(const AHeight: Integer): TTag;
begin
Result := AttribIf('height', AHeight.ToString);
end;
function TTag.Height(const AHeight: Single): TTag;
begin
Result := AttribIf('height', AHeight.ToString(InvFS));
end;
function TTag.Height(const AHeight: Double): TTag;
begin
Result := AttribIf('height', AHeight.ToString(InvFS));
end;
function TTag.Font(AFont: TFont): TTag;
begin
if AFont = nil then
Exit(Self);
Result := FontFamily(AFont.Name);
if AFont.Size > 0 then
Result := Result.FontSize(AFont.Size.ToString + 'pt')
else
Result := Result.FontSize(AFont.Height);
if AFont.Color <> clNone then
Result := Result.Fill(AFont.Color);
if fsItalic in AFont.Style then
Result := Result.FontItalic;
if fsBold in AFont.Style then
Result := Result.FontBold;
end;
function TTag.FontBold: TTag;
begin
Result := FontWeight('bold');
end;
function TTag.FontBolder: TTag;
begin
Result := FontWeight('bolder');
end;
function TTag.FontFamily(const AFamilies: string): TTag;
begin
Result := AttribIf('font-family', AFamilies);
end;
function TTag.FontItalic: TTag;
begin
Result := FontStyle('italic');
end;
function TTag.FontLighter: TTag;
begin
Result := FontWeight('lighter');
end;
function TTag.FontOblique: TTag;
begin
Result := FontStyle('oblique');
end;
function TTag.FillOpacity(const AFillOpacity: string): TTag;
begin
Result := AttribIf('fill-opacity', AFillOpacity);
end;
function XmlEscape(const S: string): string;
begin
Result := S
.Replace('''', ''')
.Replace('"', '"')
.Replace('&', '&')
.Replace('<', '<')
.Replace('>', '>')
end;
function CdataEscape(const S: string): string;
begin
Result := S.Replace(']]>', ']]]]><![CDATA[>')
end;
function TTag.ID(const AID: string): TTag;
begin
Result := AttribIf('id', AID)
end;
class operator TTag.Implicit(const ATag: TTag): string;
begin
if ATag.FKind = tkComment then
Exit('<!-- ' + ATag.FContent.Replace('--', #$2013) + ' -->');
if (ATag.FKind = tkOptionalWithChild) and ATag.FContent.IsEmpty then
Exit('');
Result := '<' + IfThen(ATag.FKind = tkEnd, '/') + ATag.FName;
if ATag.FKind <> tkEnd then
for var p in ATag.FAttribs do
Result := Result + #32 + p.Key + '="' + XmlEscape(p.Value) + '"';
case ATag.FKind of
tkStart, tkEnd:
Result := Result + '>';
tkSelfClose:
Result := Result + ' />';
tkWithChild, tkOptionalWithChild:
case ATag.FContentType of
ctText:
Result := Result + '>' + XmlEscape(ATag.FContent) + '</' + ATag.FName + '>';
ctXML:
Result := Result + '>' + ATag.FContent + '</' + ATag.FName + '>';
end;
tkWithCDATA:
Result := Result + '><![CDATA[' + CdataEscape(ATag.FContent) + ']]></' + ATag.FName + '>';
else
raise Exception.Create('Invalid tag kind.');
end;
end;
function MarkerAttribValue(const S: string): string;
begin
if S.IsEmpty or (S = 'none') or (S = 'inherit') or (S.StartsWith('url(#')) then
Result := S
else
Result := 'url(#' + S + ')'
end;
function TTag.MarkerEnd(const AMarker: string): TTag;
begin
Result := AttribIf('marker-end', MarkerAttribValue(AMarker))
end;
function TTag.MarkerMid(const AMarker: string): TTag;
begin
Result := AttribIf('marker-mid', MarkerAttribValue(AMarker))
end;
function TTag.MarkerStart(const AMarker: string): TTag;
begin
Result := AttribIf('marker-start', MarkerAttribValue(AMarker))
end;
function TTag.Name(const ATagName: string): TTag;
begin
FName := ATagName;
Result := Self;
end;
function TTag.Opacity(const AOpacity: Single): TTag;
begin
Result := Opacity(AOpacity.ToString(InvFS));
end;
function TTag.RescalePoints(const XFactor, YFactor: Double): TTag;
begin
Result := Clone;
with Result do
for var i := 0 to High(FAttribs) do
if FAttribs[i].Key = 'points' then
begin
var LPoints := SVGPointArrayParse(FAttribs[i].Value);
for var j := 0 to High(LPoints) do
begin
LPoints[j].x := XFactor * LPoints[j].x;
LPoints[j].y := YFactor * LPoints[j].y;
end;
FAttribs[i].Value := string.Join(#32, PointArrayToStringArray(LPoints));
Break;
end
else if FAttribs[i].Key = 'rx' then
FAttribs[i].Value := (StrToFloat(FAttribs[i].Value, InvFS) * XFactor).ToString(InvFS)
else if FAttribs[i].Key = 'ry' then
FAttribs[i].Value := (StrToFloat(FAttribs[i].Value, InvFS) * YFactor).ToString(InvFS)
else if (FAttribs[i].Key = 'r') and SameValue(XFactor, YFactor) then
FAttribs[i].Value := (StrToFloat(FAttribs[i].Value, InvFS) * XFactor).ToString(InvFS)
else if FAttribs[i].Key = 'r' then
begin
var Radius := StrToFloat(FAttribs[i].Value, InvFS);
FAttribs[i].Key := 'rx';
FAttribs[i].Value := (Radius * XFactor).ToString(InvFS);
FName := 'ellipse';
Exit(Attrib('ry', (Radius * YFactor).ToString(InvFS)));
end;
end;
function TTag.Opacity(const AOpacity: string): TTag;
begin
Result := AttribIf('opacity', AOpacity);
end;
function TTag.SelfClosed: TTag;
begin
FKind := tkSelfClose;
Result := Self;
end;
function TTag.StartTag: TTag;
begin
FKind := tkStart;
Result := Self;
end;
function TTag.Stroke(const AStroke: string): TTag;
begin
Result := AttribIf('stroke', AStroke);
end;
function TTag.Stroke(const AStroke: TColor): TTag;
begin
Result := Stroke(ColorToHex(AStroke));
end;
function TTag.StrokeIf(const AStroke: TColor; ACondition: Boolean): TTag;
begin
Result := StrokeIf(ColorToHex(AStroke), ACondition);
end;
function TTag.StrokeIf(const AStroke: string; ACondition: Boolean): TTag;
begin
Result := AttribIf('stroke', AStroke, ACondition);
end;
function TTag.StrokeWidth(const AStrokeWidth: Single): TTag;
begin
Result := StrokeWidth(AStrokeWidth.ToString(InvFS));
end;
function TTag.StrokeWidthPx(const AStrokeWidth: Single): TTag;
begin
Result := StrokeWidth(px(AStrokeWidth));
end;
function TTag.StrokeWidthPx(const AStrokeWidth: Integer): TTag;
begin
Result := StrokeWidth(px(AStrokeWidth));
end;
function TTag.StrokeWidth(const AStrokeWidth: Integer): TTag;
begin
Result := StrokeWidth(AStrokeWidth.ToString);
end;
function TTag.StrokeWidth(const AStrokeWidth: string): TTag;
begin
Result := AttribIf('stroke-width', AStrokeWidth);
end;
function TTag.ContentText(const AContent: string): TTag;
begin
if FKind <> tkOptionalWithChild then
FKind := tkWithChild;
FContent := AContent;
FContentType := ctText;
Result := Self;
end;
function TTag.ContentXML(const AContent: string): TTag;
begin
if FKind <> tkOptionalWithChild then
FKind := tkWithChild;
FContent := AContent;
FContentType := ctXML;
Result := Self;
end;
function TTag.DominantBaseline(AAnchor: TVerticalAlignment): TTag;
const
BaselineValues: array[TVerticalAlignment] of string
= ('text-before-edge', 'text-after-edge', 'central');
begin
Result := DominantBaseline(BaselineValues[AAnchor]);
end;
function TTag.DominantBaseline(const AAnchor: string): TTag;
begin
Result := AttribIf('dominant-baseline', AAnchor);
end;
function TTag.CDATA(const AContent: string): TTag;
begin
FKind := tkWithCDATA;
FContent := AContent;
Result := Self;
end;
function TTag.&Class(const AClassName: string): TTag;
begin
Result := AttribIf('class', AClassName)
end;
function TTag.Clone: TTag;
begin
Result := Default(TTag);
Result.FKind := Self.FKind;
Result.FName := Self.FName;
Result.FAttribs := Copy(Self.FAttribs);
Result.FContent := Self.FContent;
Result.FContentType := Self.FContentType;
Result.FSVG := Self.FSVG;
end;
function TTag.Style(const AStyle: string): TTag;
begin
Result := AttribIf('style', AStyle)
end;
function TTag.TextAnchor(const AAnchor: string): TTag;
begin
Result := AttribIf('text-anchor', AAnchor);
end;
function TTag.Transform(const ATransform: TTransform): TTag;
begin
Result := AttribIf('transform', ATransform);
end;
function TTag.TransformIf(const ATransform: TTransform;
ACondition: Boolean): TTag;
begin
Result := AttribIf('transform', ATransform, ACondition);
end;
function TTag.Width(const AWidth: string): TTag;
begin
Result := AttribIf('width', AWidth);
end;
function TTag.Width(const AWidth: Integer): TTag;
begin
Result := AttribIf('width', AWidth.ToString);
end;
function TTag.Width(const AWidth: Single): TTag;
begin
Result := AttribIf('width', AWidth.ToString(InvFS));
end;
function TTag.Width(const AWidth: Double): TTag;
begin
Result := AttribIf('width', AWidth.ToString(InvFS));
end;
function TTag.x(const Ax: string): TTag;
begin
Result := AttribIf('x', Ax);
end;
function TTag.x(const Ax: Integer): TTag;
begin
Result := AttribIf('x', Ax.ToString);
end;
function TTag.x(const Ax: Single): TTag;
begin
Result := AttribIf('x', Ax.ToString(InvFS));
end;
function TTag.x(const Ax: Double): TTag;
begin
Result := AttribIf('x', Ax.ToString(InvFS));
end;
function TTag.y(const Ay: string): TTag;
begin
Result := AttribIf('y', Ay);
end;
function TTag.y(const Ay: Integer): TTag;
begin
Result := AttribIf('y', Ay.ToString);
end;
function TTag.y(const Ay: Single): TTag;
begin
Result := AttribIf('y', Ay.ToString(InvFS));
end;
function TTag.y(const Ay: Double): TTag;
begin
Result := AttribIf('y', Ay.ToString(InvFS));
end;
function TTag.TextAnchor(AAnchor: TAlignment): TTag;
const
Anchors: array[TAlignment] of string = ('start', 'end', 'middle');
begin
Result := TextAnchor(Anchors[AAnchor]);
end;
function TTag.FontSize(const ASize: Single): TTag;
begin
Result := FontSize(ASize.ToString(InvFS));
end;
function TTag.StrokeWidth(const AStrokeWidth: Double): TTag;
begin
Result := StrokeWidth(AStrokeWidth.ToString(InvFS));
end;
function TTag.StrokeWidthIf(const AStrokeWidth: Integer;
ACondition: Boolean): TTag;
begin
Result := StrokeWidthIf(AStrokeWidth.ToString, ACondition);
end;
function TTag.StrokeWidthIf(const AStrokeWidth: string;
ACondition: Boolean): TTag;
begin
Result := AttribIf('stroke-width', AStrokeWidth, ACondition);
end;
function TTag.StrokeWidthIf(const AStrokeWidth: Single;
ACondition: Boolean): TTag;
begin
Result := StrokeWidthIf(AStrokeWidth.ToString(InvFS), ACondition);
end;
function TTag.StrokeWidthIf(const AStrokeWidth: Double;
ACondition: Boolean): TTag;
begin
Result := StrokeWidthIf(AStrokeWidth.ToString(InvFS), ACondition);
end;
function TSVGBuilder.Circle(const x, y, r: string): TTag;
begin
Result := Tag('circle')
.AttribIf('cx', x)
.AttribIf('cy', y)
.AttribIf('r', r)
end;
function TSVGBuilder.Circle(const x, y, r: Integer): TTag;
begin
Result := Circle(x.ToString, y.ToString, r.ToString);
end;
function TSVGBuilder.Circle(const x, y, r: Single): TTag;
begin
Result := Circle(x.ToString(InvFS), y.ToString(InvFS), r.ToString(InvFS));
end;
function TSVGBuilder.Circle(const x: TPoint; const r: Integer): TTag;
begin
Result := Circle(x.X, x.Y, r);
end;
function TSVGBuilder.Circle(const x, y, r: Double): TTag;
begin
Result := Circle(x.ToString(InvFS), y.ToString(InvFS), r.ToString(InvFS));
end;
constructor TSVGBuilder.Create(AAbstract: Boolean = False);
begin
FLineBreak := #13#10;
FSvgVersion := '1.1';
FWidth := '10cm';
FHeight := '10cm';
FParts := TList<string>.Create;
FStack := TStack<TNodeType>.Create;
FDefines := TDictionary<string, Pointer>.Create;
FDefs := TDictionary<string, string>.Create;
FNamespaces := TDictionary<string, string>.Create;
FAbstract := AAbstract;
end;
procedure TSVGBuilder.Define(const AName: string);
begin
FDefines.AddOrSetValue(AName, nil);
end;
procedure TSVGBuilder.DefineNamespace(const APrefix, AURL: string);
begin
if FNamespaces.ContainsKey(APrefix) then
raise ESVGException.CreateFmt('XML namespace "%s" redeclared.', [APrefix]);
FNamespaces.Add(APrefix, AURL);
end;
function TSVGBuilder.DefMarker(const ID, AViewBox, ARefX, ARefY, AMarkerWidth,
AMarkerHeight: string; const AStrokeWidth: Boolean): TTag;
begin
if not FDefID.IsEmpty then
StructureError;
FDefs.Add(ID, '');
FDefID := ID;
Result := BeginMarker(AViewBox, ARefX, ARefY, AMarkerWidth, AMarkerHeight, AStrokeWidth).ID(ID);
end;
function TSVGBuilder.DefMarker(const ID, AViewBox: string; ARefX, ARefY,
AMarkerWidth, AMarkerHeight: Integer; const AStrokeWidth: Boolean): TTag;
begin
if not FDefID.IsEmpty then
StructureError;
FDefs.Add(ID, '');
FDefID := ID;
Result := BeginMarker(AViewBox, ARefX, ARefY, AMarkerWidth, AMarkerHeight, AStrokeWidth).ID(ID);
end;
function TSVGBuilder.DefMarker(const ID, AViewBox: string; ARefX, ARefY,
AMarkerWidth, AMarkerHeight: Single; const AStrokeWidth: Boolean): TTag;
begin
if not FDefID.IsEmpty then
StructureError;
FDefs.Add(ID, '');
FDefID := ID;
Result := BeginMarker(AViewBox, ARefX, ARefY, AMarkerWidth, AMarkerHeight, AStrokeWidth).ID(ID);
end;
function TSVGBuilder.DefMarker(const ID, AViewBox: string; ARefX, ARefY,
AMarkerWidth, AMarkerHeight: Double; const AStrokeWidth: Boolean): TTag;
begin
if not FDefID.IsEmpty then
StructureError;
FDefs.Add(ID, '');
FDefID := ID;
Result := BeginMarker(AViewBox, ARefX, ARefY, AMarkerWidth, AMarkerHeight, AStrokeWidth).ID(ID);
end;
function TSVGBuilder.DefSymbol(const ID, AViewBox: string): TTag;
begin
if not FDefID.IsEmpty then
StructureError;
FDefs.Add(ID, '');
FDefID := ID;
Result := BeginSymbol(AViewBox).ID(ID);
end;
destructor TSVGBuilder.Destroy;
begin
FNamespaces.Free;
FDefs.Free;
FDefines.Free;
FStack.Free;
FParts.Free;
inherited;
end;
function TSVGBuilder.GetAspectRatio: Double;
const
Units: array[0..8] of string = ('em', 'ex', 'px', 'in', 'cm', 'mm', 'pt', 'pc', '%');
var
WUnit, HUnit: string;
W, H: string;
WValue, HValue: Double;
begin
W := Width.Trim;
for var U in Units do
if W.EndsWith(U, True) then
begin
Delete(W, W.Length - U.Length + 1, U.Length);
WUnit := U;
Break;
end;
H := Height.Trim;
for var U in Units do
if H.EndsWith(U, True) then
begin
Delete(H, H.Length - U.Length + 1, U.Length);
HUnit := U;
Break;
end;
W := W.Trim;
H := H.Trim;
if
(WUnit = HUnit) and (WUnit <> '%') and
TryStrToFloat(W, WValue, InvFS) and
TryStrToFloat(H, HValue, InvFS) and
(WValue > 0.0) and (HValue > 0.0)
then
Result := WValue / HValue
else
raise ESVGException.Create('Couldn''t determine aspect ratio.');
end;
function TSVGBuilder.GetAsXML: string;
var
Lines: TList<string>;
procedure Line(const AText: string);
begin
for var l in AText.Split([#13#10]) do
Lines.Add(l);
end;
procedure OptionalLine(const AText: string);
begin
if not AText.IsEmpty then
Lines.Add(AText);
end;
begin
if FStack.Count > 0 then
StructureError;
if not FDefID.IsEmpty then
StructureError;
Lines := TList<string>.Create;
try
Line('<?xml version="1.0" encoding="UTF-8" standalone="no"?>');
Line(
StartTag('svg')
.Attrib('xmlns', 'http://www.w3.org/2000/svg')
.Attrib('xmlns:xlink', 'http://www.w3.org/1999/xlink')
.AttribIf('version', FSvgVersion)
.AttribIf('xml:lang', FLanguage)
.AttribIf('width', FWidth)
.AttribIf('height', FHeight)
.AttribIf('preserveAspectRatio', 'none', FStretch)
.AttribIf('viewBox', FViewBox)
.Attribs(AddNamespace(FNamespaces, 'xmlns'))
);
OptionalLine(OptionalTag('title').ContentText(FTitle));
OptionalLine(OptionalTag('desc').ContentText(FDescription));
if FDefs.Count > 0 then
begin
Line(BeginDefs);
for var s in FDefs do
OptionalLine(s.Value);
Line(EndDefs);
end;
for var s in FParts do
OptionalLine(s);
Line(EndTag('svg'));
Result := string.Join(FLineBreak, Lines.ToArray);
finally
Lines.Free;
end;
if FStack.Count > 0 then
StructureError;
end;
function TSVGBuilder.GroupDescription(const ADesc: string): TTag;
begin
Result := OptionalTag('desc').ContentText(ADesc);
end;
function TSVGBuilder.GroupTitle(const ATitle: string): TTag;
begin
Result := OptionalTag('title').ContentText(ATitle);
end;
function TSVGBuilder.HasID(const ID: string): Boolean;
begin
Result := FDefs.ContainsKey(ID);
end;
function TSVGBuilder.Image(const x: TPoint; const s: TSize;
const ref: string): TTag;
begin
Result := Image(x.X, x.Y, s.Width, s.Height, ref);
end;
function TSVGBuilder.Image(const x, y, w, h: Single; const ref: string): TTag;
begin
Result := Image(x.ToString(InvFS), y.ToString(InvFS), w.ToString(InvFS),
h.ToString(InvFS), ref)
end;
function TSVGBuilder.Image(const x, y, w, h: Integer; const ref: string): TTag;
begin
Result := Image(x.ToString, y.ToString, w.ToString, h.ToString, ref)
end;
function TSVGBuilder.Image(const x, y, w, h, ref: string): TTag;
begin
Result := Tag('image')
.AttribIf('x', x)
.AttribIf('y', y)
.AttribIf('width', w)
.AttribIf('height', h)
.AttribIf('xlink:href', ref)
end;
function TSVGBuilder.Line(const x1, y1, x2, y2: Single): TTag;
begin
Result := Line(x1.ToString(InvFS), y1.ToString(InvFS),
x2.ToString(InvFS), y2.ToString(InvFS));
end;
function TSVGBuilder.Line(const x1, y1, x2, y2: Integer): TTag;
begin
Result := Line(x1.ToString, y1.ToString, x2.ToString, y2.ToString);
end;
function TSVGBuilder.Line(const x1, y1, x2, y2: string): TTag;
begin
Result := Tag('line')
.AttribIf('x1', x1)
.AttribIf('y1', y1)
.AttribIf('x2', x2)
.AttribIf('y2', y2)
end;
function TSVGBuilder.Tag(const ATagName: string = ''): TTag;
begin
Result := Default(TTag).Name(ATagName).Associate(Self).SelfClosed;
end;
function TSVGBuilder.Text(const x, y: Single; const S: string): TTag;
begin
Result := Text(x.ToString(InvFS), y.ToString(InvFS), S);
end;
function TSVGBuilder.Text(const x, y: Integer; const S: string): TTag;
begin
Result := Text(x.ToString, y.ToString, S);
end;
function TSVGBuilder.Text(const x, y, S: string): TTag;
begin
Result := Tag('text')
.AttribIf('x', x)
.AttribIf('y', y)
.ContentText(S)
end;
function TSVGBuilder.OptionalTag(const ATagName: string = ''): TTag;
begin
Result := Default(TTag).Name(ATagName).Associate(Self);
PTagKind(@Result)^ := tkOptionalWithChild;
end;
function TSVGBuilder.Polygon(const APoints: string): TTag;
begin
Result := Tag('polygon')
.AttribIf('points', APoints)
end;
function TSVGBuilder.Polygon(const APoints: array of TPoint): TTag;
begin
Result := Polygon(string.Join(#32, PointArrayToStringArray(APoints)));
end;
function TSVGBuilder.Path(const d: string; const APathLength: Single): TTag;
begin
Result := Tag('path')
.AttribIf('d', d)
.AttribIf('pathLength', APathLength.ToString(InvFS), APathLength <> 0.0)
end;
function TSVGBuilder.Polygon(const APoints: array of TPointF): TTag;
begin
Result := Polygon(string.Join(#32, PointArrayToStringArray(APoints)));
end;
function TSVGBuilder.PolyLine(const APoints: array of TPointF): TTag;
begin
Result := PolyLine(string.Join(#32, PointArrayToStringArray(APoints)));
end;
function TSVGBuilder.Rect(const R: TRectF): TTag;
begin
Result := Rect(R.Left.ToString(InvFS), R.Top.ToString(InvFS),
R.Width.ToString(InvFS), R.Height.ToString(InvFS));
end;
function TSVGBuilder.Rect(const R: TRect): TTag;
begin
Result := Rect(R.Left.ToString, R.Top.ToString, R.Width.ToString, R.Height.ToString);
end;
function TSVGBuilder.Rect(const x, y, w, h: string): TTag;
begin
Result := Tag('rect')
.AttribIf('x', x)
.AttribIf('y', y)
.AttribIf('width', w)
.AttribIf('height', h)
end;
function TSVGBuilder.PolyLine(const APoints: array of TPoint): TTag;
begin
Result := PolyLine(string.Join(#32, PointArrayToStringArray(APoints)));
end;
function TSVGBuilder.PolyLine(const APoints: string): TTag;
begin
Result := Tag('polyline')
.AttribIf('points', APoints);
end;
procedure TSVGBuilder.SaveToFile(const AFileName: TFileName);
begin
TFile.WriteAllText(AFileName, AsXML, TEncoding.UTF8);
end;
function TSVGBuilder.Script(const AScript: string): TTag;
begin
Result := Tag('script').Attrib('type', 'text/javascript')
.CDATA(FLineBreak + AScript + FLineBreak);
end;
function TSVGBuilder.Sector(const X: TPointD; const rx, ry, t0,
t1: Double): TTag;
begin
Result := Sector(X.X, X.Y, rx, ry, t0, t1);
end;
function ArcDist(a, b: Double): Double;
begin
a := rmod(a, TwoPi);
b := rmod(b, TwoPi);
if b = a then
Result := 0
else if a < b then
Result := b - a
else
Result := TwoPi - a + b;
end;
function TSVGBuilder.Sector(const x, y, rx, ry, t0, t1: Double): TTag;
begin
var P := TPointD.Create(x + rx*cos(t0), y - ry*sin(t0));
var Q := TPointD.Create(x + rx*cos(t1), y - ry*sin(t1));
var d := Format('M%g,%g L%g,%g A%g,%g 0 %d,%d %g,%g Z',
[x, y, P.X, P.Y, rx, ry, Ord(ArcDist(t0, t1) >= Pi), 0, Q.X, Q.Y], InvFS);
Result := Path(d);
end;
function TSVGBuilder.SourceComment(const AText: string): TTag;
begin
Result := XmlComment(AText);
end;
function TSVGBuilder.StartTag(const ATagName: string = ''): TTag;
begin
Result := Default(TTag).Name(ATagName).Associate(Self).StartTag;
end;
procedure TSVGBuilder.StructureError;
begin
raise ESVGException.Create('Invalid SVG structure.');
end;
function TSVGBuilder.Style(const CSS: string): TTag;
begin
Result := Tag('style').Attrib('type', 'text/css')
.CDATA(FLineBreak + CSS + FLineBreak);
end;
procedure TSVGBuilder.AddTag(const ATag: TTag);
begin
if not FDefID.IsEmpty then
begin
var s: string;
if FDefs.TryGetValue(FDefID, s) then
begin
s := s + IfThen(not s.IsEmpty, #13#10) + ATag;
FDefs[FDefID] := s;
end
else
StructureError
end
else
FParts.Add(ATag);
end;
function TSVGBuilder.Ellipse(const x, y, rx, ry: string): TTag;
begin
Result := Tag('ellipse')
.AttribIf('cx', x)
.AttribIf('cy', y)
.AttribIf('rx', rx)
.AttribIf('ry', ry)
end;
function TSVGBuilder.Ellipse(const x, y, rx, ry: Integer): TTag;
begin
Result := Ellipse(x.ToString, y.ToString,
rx.ToString, ry.ToString);
end;
function TSVGBuilder.Ellipse(const x, y, rx, ry: Single): TTag;
begin
Result := Ellipse(x.ToString(InvFS), y.ToString(InvFS),
rx.ToString(InvFS), ry.ToString(InvFS));
end;
function TSVGBuilder.Ellipse(const x: TPoint; const rx, ry: Integer): TTag;
begin
Result := Ellipse(x.X, x.Y, rx, ry);
end;
function TSVGBuilder.Ellipse(const x: TPointF; const rx, ry: Single): TTag;
begin
Result := Ellipse(x.X, x.Y, rx, ry);
end;
procedure TSVGBuilder.EndDefinition;
begin
FDefID := '';
end;
procedure TSVGBuilder.EndDefMarker;
begin
EndMarker.Append;
EndDefinition;
end;
function TSVGBuilder.EndDefs: TTag;
begin
Result := EndTag('defs');
if FStack.Pop <> ntDefs then
StructureError;
end;
procedure TSVGBuilder.EndDefSymbol;
begin
EndSymbol.Append;
EndDefinition;
end;
function TSVGBuilder.EndGroup: TTag;
begin
Result := EndTag('g');
if FStack.Pop <> ntGroup then
StructureError;
end;
function TSVGBuilder.EndMarker: TTag;
begin
Result := EndTag('marker');
if FStack.Pop <> ntMarker then
StructureError;
end;
function TSVGBuilder.EndMetadata: TTag;
begin
Result := EndTag('metadata');
if FStack.Pop <> ntMetadata then
StructureError;
end;
function TSVGBuilder.EndSwitch: TTag;
begin
Result := EndTag('switch');
if FStack.Pop <> ntSwitch then
StructureError;
end;
function TSVGBuilder.EndSymbol: TTag;
begin
Result := EndTag('symbol');
if FStack.Pop <> ntSymbol then
StructureError;
end;
function TSVGBuilder.EndTag(const ATagName: string = ''): TTag;
begin
Result := Default(TTag).Name(ATagName).Associate(Self).EndTag;
end;
function TSVGBuilder.XmlComment(const AComment: string = ''): TTag;
begin
Result := Default(TTag).Associate(Self).ContentText(AComment);
PTagKind(@Result)^ := tkComment;
end;
function TSVGBuilder.Animate(const AAttributeName, AAttributeType: string): TTag;
begin
Result := Tag('animate')
.AttribIf('attributeType', AAttributeType)
.AttribIf('attributeName', AAttributeName);
end;
function TSVGBuilder.AnimateMotion(const APathData, ARotate: string): TTag;
begin
Result := Tag('animateMotion')
.AttribIf('path', APathData)
.AttribIf('rotate', ARotate);
end;
function TSVGBuilder.AnimateSet(const AAttributeName, ATo: string): TTag;
begin
Result := Tag('set')
.AttribIf('attributeName', AAttributeName)
.AttribIf('to', ATo)
end;
function TSVGBuilder.AnimateTransform(const AType: TTransformType): TTag;
const
TransformTypes: array[TTransformType] of string = ('translate', 'scale',
'rotate', 'skewX', 'skewY');
begin
if not InRange(Ord(AType), Ord(Low(TTransformType)), Ord(High(TTransformType))) then
raise ESVGException.Create('Invalid transform type.');
Result := Tag('animateTransform')
.Attrib('attributeName', 'transform')
.Attrib('attributeType', 'XML')
.Attrib('type', TransformTypes[AType])
end;
function TSVGBuilder.Arc(const X: TPointD; const rx, ry, t0, t1: Double): TTag;
begin
Result := Arc(X.X, X.Y, rx, ry, t0, t1);
end;
function TSVGBuilder.Arc(const x, y, rx, ry, t0, t1: Double): TTag;
begin
var P := TPointD.Create(x + rx*cos(t0), y - ry*sin(t0));
var Q := TPointD.Create(x + rx*cos(t1), y - ry*sin(t1));
var d := Format('M%g,%g A%g,%g 0 %d,%d %g,%g',
[P.X, P.Y, rx, ry, Ord(ArcDist(t0, t1) >= Pi), 0, Q.X, Q.Y], InvFS);
Result := Path(d);
end;
function TSVGBuilder.BeginDefs: TTag;
begin
FStack.Push(ntDefs);
Result := StartTag('defs');
end;
function TSVGBuilder.BeginGroup: TTag;
begin
FStack.Push(ntGroup);
Result := StartTag('g')
end;
function TSVGBuilder.BeginMarker(const AViewBox: string; ARefX, ARefY,
AMarkerWidth, AMarkerHeight: Integer; const AStrokeWidth: Boolean): TTag;
begin
Result := BeginMarker(AViewBox, ARefX.ToString, ARefY.ToString,
AMarkerWidth.ToString, AMarkerHeight.ToString, AStrokeWidth)
end;
function TSVGBuilder.BeginMarker(const AViewBox, ARefX, ARefY, AMarkerWidth,
AMarkerHeight: string; const AStrokeWidth: Boolean): TTag;
const
MarkerUnits: array[Boolean] of string = ('userSpaceOnUse', 'strokeWidth');
begin
FStack.Push(ntMarker);
Result := StartTag('marker')
.AttribIf('viewBox', AViewBox)
.AttribIf('refX', ARefX)
.AttribIf('refY', ARefY)
.Attrib ('markerUnits', MarkerUnits[AStrokeWidth])
.AttribIf('markerWidth', AMarkerWidth)
.AttribIf('markerHeight', AMarkerHeight)
end;
function TSVGBuilder.BeginSwitch: TTag;
begin
FStack.Push(ntSwitch);
Result := StartTag('switch');
end;
function TSVGBuilder.BeginSymbol(const AViewBox: string): TTag;
begin
FStack.Push(ntSymbol);
Result := StartTag('symbol')
.AttribIf('viewBox', AViewBox)
end;
function TSVGBuilder.Circle(const x: TPointF; const r: Single): TTag;
begin
Result := Circle(x.X, x.Y, r);
end;
function TSVGBuilder.Circle(const x, y: Single; const r: string): TTag;
begin
Result := Circle(x.ToString(InvFS), y.ToString(InvFS), r);
end;
function TSVGBuilder.Line(const P, Q: TPoint): TTag;
begin
Result := Line(P.X, P.Y, Q.X, Q.Y);
end;
function TSVGBuilder.Line(const P, Q: TPointF): TTag;
begin
Result := Line(P.X, P.Y, Q.X, Q.Y);
end;
function TSVGBuilder.Link(const ref: string): TTag;
begin
Result := Tag('a').AttribIf('xlink:href', ref);
end;
function TSVGBuilder.MPath(const APathName: string): TTag;
begin
Result := Tag('mpath').Attrib('xlink:href', '#' + APathName)
end;
function TSVGBuilder.Text(const x: TPointF; const S: string): TTag;
begin
Result := Text(x.X, x.Y, S);
end;
function TSVGBuilder.Text(const x: TPointD; const S: string): TTag;
begin
Result := Text(x.X, x.Y, S);
end;
function TSVGBuilder.Text(const S: string): TTag;
begin
Result := Tag('text')
.ContentText(S)
end;
function TSVGBuilder.Text(const x, y: Double; const S: string): TTag;
begin
Result := Text(x.ToString(InvFS), y.ToString(InvFS), S);
end;
function TSVGBuilder.TextPath(const APathRef, S, AStartOffset: string): TTag;
begin
Result := Tag('textPath')
.AttribIf('xlink:href', '#' + APathRef, not APathRef.IsEmpty)
.AttribIf('startOffset', AStartOffset)
.ContentText(S)
end;
function TSVGBuilder.TextSpan(const S: string): TTag;
begin
Result := Tag('tspan').ContentText(S);
end;
function TSVGBuilder.Use(const x, y, w, h: Integer; const ref: string): TTag;
begin
Result := Use(x.ToString, y.ToString, w.ToString, h.ToString, ref)
end;
function TSVGBuilder.Use(const x, y, w, h, ref: string): TTag;
begin
Result := Tag('use')
.AttribIf('x', x)
.AttribIf('y', y)
.AttribIf('width', w)
.AttribIf('height', h)
.AttribIf('xlink:href', ref);
end;
function TSVGBuilder.Text(const x: TPoint; const S: string): TTag;
begin
Result := Text(x.X, x.Y, S);
end;
function TSVGBuilder.Use(const x, y, w, h: Single; const ref: string): TTag;
begin
Result := Use(x.ToString(InvFS), y.ToString(InvFS), w.ToString(InvFS),
h.ToString(InvFS), ref)
end;
function TSVGBuilder.Image(const x: TPointF; const s: TSizeF;
const ref: string): TTag;
begin
Result := Image(x.X, x.Y, s.Width, s.Height, ref);
end;
function TSVGBuilder.Image(const x, y, w, h: Double; const ref: string): TTag;
begin
Result := Image(x.ToString(InvFS), y.ToString(InvFS), w.ToString(InvFS),
h.ToString(InvFS), ref)
end;
function TSVGBuilder.BeginMarker(const AViewBox: string; ARefX, ARefY,
AMarkerWidth, AMarkerHeight: Single; const AStrokeWidth: Boolean): TTag;
begin
Result := BeginMarker(AViewBox, ARefX.ToString(InvFS), ARefY.ToString(InvFS),
AMarkerWidth.ToString(InvFS), AMarkerHeight.ToString(InvFS), AStrokeWidth)
end;
function TSVGBuilder.BeginMetadata: TTag;
begin
FStack.Push(ntMetadata);
Result := StartTag('metadata');
end;
function TSVGBuilder.Polygon(const APoints: array of TPointD): TTag;
begin
Result := Polygon(string.Join(#32, PointArrayToStringArray(APoints)));
end;
function TSVGBuilder.PolyLine(const APoints: array of TPointD): TTag;
begin
Result := PolyLine(string.Join(#32, PointArrayToStringArray(APoints)));
end;
function TSVGBuilder.Rect(const R: TRectD): TTag;
begin
Result := Rect(R.Left.ToString(InvFS), R.Top.ToString(InvFS),
R.Width.ToString(InvFS), R.Height.ToString(InvFS));
end;
function TSVGBuilder.Rect(const x, y, w, h: Single): TTag;
begin
Result := Rect(x.ToString(InvFS), y.ToString(InvFS),
w.ToString(InvFS), h.ToString(InvFS));
end;
function TSVGBuilder.Rect(const x, y, w, h: Double): TTag;
begin
Result := Rect(x.ToString(InvFS), y.ToString(InvFS),
w.ToString(InvFS), h.ToString(InvFS));
end;
function TSVGBuilder.Line(const P, Q: TPointD): TTag;
begin
Result := Line(P.X, P.Y, Q.X, Q.Y);
end;
function TSVGBuilder.Line(const x1, y1, x2, y2: Double): TTag;
begin
Result := Line(x1.ToString(InvFS), y1.ToString(InvFS),
x2.ToString(InvFS), y2.ToString(InvFS));
end;
function TSVGBuilder.Ellipse(const x: TPointD; const rx, ry: Double): TTag;
begin
Result := Ellipse(x.X, x.Y, rx, ry);
end;
function TSVGBuilder.Ellipse2(const x, y, rx, ry: Single): TTag;
begin
if SameValue(rx, ry) then
Result := Circle(x, y, rx)
else
Result := Ellipse(x, y, rx, ry);
end;
function TSVGBuilder.Ellipse2(const x, y, rx, ry: Integer): TTag;
begin
if SameValue(rx, ry) then
Result := Circle(x, y, rx)
else
Result := Ellipse(x, y, rx, ry);
end;
function TSVGBuilder.Ellipse2(const x, y, rx, ry: string): TTag;
begin
if rx = ry then
Result := Circle(x, y, rx)
else
Result := Ellipse(x, y, rx, ry);
end;
function TSVGBuilder.Ellipse2(const x: TPointF; const rx, ry: Single): TTag;
begin
if SameValue(rx, ry) then
Result := Circle(x, rx)
else
Result := Ellipse(x, rx, ry);
end;
function TSVGBuilder.Ellipse2(const x: TPoint; const rx, ry: Integer): TTag;
begin
if SameValue(rx, ry) then
Result := Circle(x, rx)
else
Result := Ellipse(x, rx, ry);
end;
function TSVGBuilder.Ellipse2(const x, y, rx, ry: Double): TTag;
begin
if SameValue(rx, ry) then
Result := Circle(x, y, rx)
else
Result := Ellipse(x, y, rx, ry);
end;
function TSVGBuilder.Ellipse(const x, y, rx, ry: Double): TTag;
begin
Result := Ellipse(x.ToString(InvFS), y.ToString(InvFS),
rx.ToString(InvFS), ry.ToString(InvFS));
end;
function TSVGBuilder.Circle(const x: TPointD; const r: Double): TTag;
begin
Result := Circle(x.X, x.Y, r);
end;
function TSVGBuilder.Undefine(const AName: string): Boolean;
begin
Result := FDefines.ContainsKey(AName);
if Result then
FDefines.Remove(AName);
end;
function TSVGBuilder.Use(const ref: string): TTag;
begin
Result := Tag('use')
.AttribIf('xlink:href', ref);
end;
function TSVGBuilder.Use(const x, y, ref: string): TTag;
begin
Result := Use(x, y, '', '', ref);
end;
function TSVGBuilder.Use(const x, y, w, h: Double; const ref: string): TTag;
begin
Result := Use(x.ToString(InvFS), y.ToString(InvFS), w.ToString(InvFS),
h.ToString(InvFS), ref)
end;
function TSVGBuilder.Image(const R: TRect; const ref: string): TTag;
begin
Result := Image(R.Left, R.Top, R.Width, R.Height, ref);
end;
function TSVGBuilder.Image(const R: TRectF; const ref: string): TTag;
begin
Result := Image(R.Left, R.Top, R.Width, R.Height, ref);
end;
function TSVGBuilder.Image(const R: TRectD; const ref: string): TTag;
begin
Result := Image(R.Left, R.Top, R.Width, R.Height, ref);
end;
function TSVGBuilder.Use(const X: TPoint; const S: TSize;
const ref: string): TTag;
begin
Result := Use(X.X, X.Y, S.Width, S.Height, ref);
end;
function TSVGBuilder.Use(const X: TPoint; const ref: string): TTag;
begin
Result := Use(X.X, X.Y, ref);
end;
function TSVGBuilder.Use(const x, y: Double; const ref: string): TTag;
begin
Result := Use(x.ToString(InvFS), y.ToString(InvFS), ref);
end;
function TSVGBuilder.Use(const x, y: Single; const ref: string): TTag;
begin
Result := Use(x.ToString(InvFS), y.ToString(InvFS), ref);
end;
function TSVGBuilder.Use(const x, y: Integer; const ref: string): TTag;
begin
Result := Use(x.ToString, y.ToString, ref)
end;
function TSVGBuilder.Use(const X: TPointF; const S: TSizeF;
const ref: string): TTag;
begin
Result := Use(X.X, X.Y, S.Width, S.Height, ref);
end;
function TSVGBuilder.Use(const X: TPointF; const ref: string): TTag;
begin
Result := Use(X.X, X.Y, ref);
end;
function TSVGBuilder.Use(const X: TPointD; const S: TSizeD;
const ref: string): TTag;
begin
Result := Use(X.X, X.Y, S.Width, S.Height, ref);
end;
function TSVGBuilder.Use(const X: TPointD; const ref: string): TTag;
begin
Result := Use(X.X, X.Y, ref);
end;
function TSVGBuilder.Ellipse2(const x: TPointD; const rx, ry: Double): TTag;
begin
if SameValue(rx, ry) then
Result := Circle(x, rx)
else
Result := Ellipse(x, rx, ry);
end;
function TSVGBuilder.Circle(const r: Integer): TTag;
begin
Result := Tag('circle')
.Attrib('r', r.ToString);
end;
function TSVGBuilder.Circle(const r: Single): TTag;
begin
Result := Tag('circle')
.Attrib('r', r.ToString(InvFS));
end;
function TSVGBuilder.Circle(const r: Double): TTag;
begin
Result := Tag('circle')
.Attrib('r', r.ToString(InvFS));
end;
function TSVGBuilder.BeginMarker(const AViewBox: string; ARefX, ARefY,
AMarkerWidth, AMarkerHeight: Double; const AStrokeWidth: Boolean): TTag;
begin
Result := BeginMarker(AViewBox, ARefX.ToString(InvFS), ARefY.ToString(InvFS),
AMarkerWidth.ToString(InvFS), AMarkerHeight.ToString(InvFS), AStrokeWidth)
end;
class operator TTransform.Explicit(const S: string): TTransform;
begin
Result.FData := S;
end;
class operator TTransform.Implicit(const ATransform: TTransform): string;
begin
Result := ATransform.FData;
end;
class operator TTransform.Multiply(const L, R: TTransform): TTransform;
begin
if L.FData.IsEmpty then
Result := R
else
Result := TTransform(L.FData + #32 + R.FData)
end;
function Matrix(const A, B, C, D, E, F: Single): TTransform;
begin
Result := TTransform(Format('matrix(%g %g %g %g %g %g)', [A, B, C, D, E, F], InvFS));
end;
function Identity: TTransform;
begin
Result := Default(TTransform);
end;
function Rotation(const Theta, X, Y: Single): TTransform;
begin
if (X <> 0.0) or (Y <> 0.0) then
Result := TTransform(Format('rotate(%g %g,%g)', [Theta, X, Y], InvFS))
else
Result := TTransform(Format('rotate(%g)', [Theta], InvFS));
end;
function RotationDeg(const Theta, X, Y: Single): TTransform;
begin
Result := Rotation(Theta, X, Y)
end;
function RotationRad(const Theta, X, Y: Single): TTransform;
begin
Result := Rotation(180 * Theta / Pi, X, Y)
end;
function Scaling(const X, Y: Single): TTransform;
begin
Result := TTransform(Format('scale(%g,%g)', [X, Y], InvFS));
end;
function SkewX(const Theta: Single): TTransform;
begin
Result := TTransform(Format('skewX(%g)', [Theta], InvFS));
end;
function SkewY(const Theta: Single): TTransform;
begin
Result := TTransform(Format('skewY(%g)', [Theta], InvFS));
end;
function Scaling(const X: Single): TTransform;
begin
Result := TTransform(Format('scale(%g)', [X], InvFS));
end;
function Translation(const X, Y: Single): TTransform;
begin
if Y <> 0.0 then
Result := TTransform(Format('translate(%g,%g)', [X, Y], InvFS))
else
Result := TTransform(Format('translate(%g)', [X], InvFS))
end;
function Translation(const X: TVectorD): TTransform; overload;
begin
Result := Translation(X.X, X.Y);
end;
constructor TViewBox.Create(const Xmin, Ymin, Width, Height: Single);
begin
Self.Xmin := Xmin;
Self.Ymin := Ymin;
Self.Width := Width;
Self.Height := Height;
end;
constructor TViewBox.Create(const Xmin, Ymin, Width, Height: Integer);
begin
Self.Xmin := Xmin;
Self.Ymin := Ymin;
Self.Width := Width;
Self.Height := Height;
end;
constructor TViewBox.Create(const Xmin, Ymin, Width, Height, ScaleX,
ScaleY: Single);
begin
Self.Xmin := Xmin * ScaleX;
Self.Ymin := Ymin * ScaleY;
Self.Width := Width * ScaleX;
Self.Height := Height * ScaleY;
end;
class operator TViewBox.Implicit(const AViewBox: TViewBox): string;
begin
Result := Format('%g %g %g %g',
[AViewBox.Xmin, AViewBox.Ymin, AViewBox.Width, AViewBox.Height], InvFS);
end;
function TViewBox.Valid: Boolean;
begin
Result := (Width > 0) and (Height > 0);
end;
function TViewBox.Xmax: Single;
begin
Result := Xmin + Width;
end;
function TViewBox.Ymax: Single;
begin
Result := Ymin + Height;
end;
class operator TCSSBuilder.Implicit(const ACSS: TCSSBuilder): string;
begin
Result := '';
for var i := 0 to High(ACSS.RuleSets) do
begin
Result := Result + IfThen(not Result.IsEmpty, #13#10) + ACSS.RuleSets[i].Selector + ' {';
for var j := 0 to High(ACSS.RuleSets[i].Rules) do
Result := Result + #13#10#32#32 + ACSS.RuleSets[i].Rules[j].TrimRight([#32, ';']) + ';';
Result := Result + #13#10 + '}';
end;
end;
function TCSSBuilder.RuleSet(const ASelector: string;
const ARules: array of TRule): TCSSBuilder;
begin
SetLength(RuleSets, Succ(Length(RuleSets)));
RuleSets[High(RuleSets)].Selector := ASelector;
SetLength(RuleSets[High(RuleSets)].Rules, Length(ARules));
for var i := Low(ARules) to High(ARules) do
RuleSets[High(RuleSets)].Rules[i] := ARules[i];
Result := Self;
end;
initialization
InvFS := TFormatSettings.Invariant;
end.