mirror of
https://gitlab.com/freepascal.org/fpc/source.git
synced 2025-09-11 20:49:14 +02:00
Starts implementing svg writer and improves test app and pdf reader
git-svn-id: trunk@15799 -
This commit is contained in:
parent
eb2581946c
commit
6403d462d2
@ -1,10 +1,11 @@
|
||||
object Form1: TForm1
|
||||
object formCDR2SVG: TformCDR2SVG
|
||||
Left = 216
|
||||
Height = 240
|
||||
Height = 439
|
||||
Top = 192
|
||||
Width = 240
|
||||
BorderStyle = bsSingle
|
||||
Caption = 'cdr2svg'
|
||||
ClientHeight = 240
|
||||
ClientHeight = 439
|
||||
ClientWidth = 240
|
||||
LCLVersion = '0.9.29'
|
||||
object Label1: TLabel
|
||||
@ -60,21 +61,36 @@ object Form1: TForm1
|
||||
TabOrder = 1
|
||||
end
|
||||
object buttonConvert: TButton
|
||||
Left = 32
|
||||
Left = 87
|
||||
Height = 25
|
||||
Top = 200
|
||||
Width = 75
|
||||
Width = 67
|
||||
Caption = 'Convert'
|
||||
OnClick = buttonConvertClick
|
||||
TabOrder = 2
|
||||
end
|
||||
object buttonQuit: TButton
|
||||
Left = 136
|
||||
Left = 176
|
||||
Height = 25
|
||||
Top = 200
|
||||
Width = 75
|
||||
Width = 59
|
||||
Caption = 'Quit'
|
||||
OnClick = buttonQuitClick
|
||||
TabOrder = 3
|
||||
end
|
||||
object imagePreview: TImage
|
||||
Left = 8
|
||||
Height = 202
|
||||
Top = 232
|
||||
Width = 224
|
||||
end
|
||||
object buttonVisualize: TButton
|
||||
Left = 8
|
||||
Height = 25
|
||||
Top = 200
|
||||
Width = 59
|
||||
Caption = 'Visualize'
|
||||
OnClick = buttonVisualizeClick
|
||||
TabOrder = 4
|
||||
end
|
||||
end
|
||||
|
@ -6,57 +6,85 @@ interface
|
||||
|
||||
uses
|
||||
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
|
||||
EditBtn;
|
||||
EditBtn, ExtCtrls;
|
||||
|
||||
type
|
||||
|
||||
{ TForm1 }
|
||||
{ TformCDR2SVG }
|
||||
|
||||
TForm1 = class(TForm)
|
||||
TformCDR2SVG = class(TForm)
|
||||
buttonVisualize: TButton;
|
||||
buttonConvert: TButton;
|
||||
buttonQuit: TButton;
|
||||
editInput: TFileNameEdit;
|
||||
editOutput: TFileNameEdit;
|
||||
imagePreview: TImage;
|
||||
Label1: TLabel;
|
||||
Label2: TLabel;
|
||||
Label3: TLabel;
|
||||
procedure buttonConvertClick(Sender: TObject);
|
||||
procedure buttonQuitClick(Sender: TObject);
|
||||
procedure buttonVisualizeClick(Sender: TObject);
|
||||
private
|
||||
{ private declarations }
|
||||
function CheckInput(): Boolean;
|
||||
public
|
||||
{ public declarations }
|
||||
end;
|
||||
|
||||
var
|
||||
Form1: TForm1;
|
||||
formCDR2SVG: TformCDR2SVG;
|
||||
|
||||
implementation
|
||||
|
||||
uses
|
||||
fpvectorial, cdrvectorialreader, svgvectorialwriter;
|
||||
fpvectorial, cdrvectorialreader, svgvectorialwriter, pdfvectorialreader,
|
||||
fpvtocanvas;
|
||||
|
||||
{$R *.lfm}
|
||||
|
||||
{ TForm1 }
|
||||
{ TformCDR2SVG }
|
||||
|
||||
procedure TForm1.buttonQuitClick(Sender: TObject);
|
||||
procedure TformCDR2SVG.buttonQuitClick(Sender: TObject);
|
||||
begin
|
||||
Close;
|
||||
end;
|
||||
|
||||
procedure TForm1.buttonConvertClick(Sender: TObject);
|
||||
procedure TformCDR2SVG.buttonVisualizeClick(Sender: TObject);
|
||||
var
|
||||
Vec: TvVectorialDocument;
|
||||
begin
|
||||
// First check the in input
|
||||
if not CheckInput() then Exit;
|
||||
|
||||
Vec := TvVectorialDocument.Create;
|
||||
try
|
||||
Vec.ReadFromFile(editInput.FileName, vfPDF);
|
||||
imagePreview.Canvas.Brush.Color := clWhite;
|
||||
imagePreview.Canvas.FillRect(0, 0, imagePreview.Width, imagePreview.Height);
|
||||
DrawFPVectorialToCanvas(Vec, imagePreview.Canvas, 0, 0, 0.25, 0.25);
|
||||
finally
|
||||
Vec.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
function TformCDR2SVG.CheckInput(): Boolean;
|
||||
begin
|
||||
// todo...
|
||||
end;
|
||||
|
||||
procedure TformCDR2SVG.buttonConvertClick(Sender: TObject);
|
||||
var
|
||||
Vec: TvVectorialDocument;
|
||||
begin
|
||||
// First check the in input
|
||||
if not CheckInput() then Exit;
|
||||
|
||||
// Now convert
|
||||
Vec := TvVectorialDocument.Create;
|
||||
try
|
||||
Vec.ReadFromFile(editInput.FileName, vfPDF);
|
||||
Vec.WriteToFile(editOutPut.FileName, vfGCodeAvisoCNCPrototipoV5);
|
||||
Vec.WriteToFile(editOutPut.FileName, vfSVG);
|
||||
finally
|
||||
Vec.Free;
|
||||
end;
|
||||
|
@ -4,6 +4,9 @@
|
||||
<Version Value="9"/>
|
||||
<PathDelim Value="\"/>
|
||||
<General>
|
||||
<Flags>
|
||||
<AlwaysBuild Value="False"/>
|
||||
</Flags>
|
||||
<SessionStorage Value="InProjectDir"/>
|
||||
<Title Value="cdr2svg_visual"/>
|
||||
<UseXPManifest Value="True"/>
|
||||
@ -39,7 +42,7 @@
|
||||
<Unit1>
|
||||
<Filename Value="cdr2svg_mainform.pas"/>
|
||||
<IsPartOfProject Value="True"/>
|
||||
<ComponentName Value="Form1"/>
|
||||
<ComponentName Value="formCDR2SVG"/>
|
||||
<ResourceBaseClass Value="Form"/>
|
||||
<UnitName Value="cdr2svg_mainform"/>
|
||||
</Unit1>
|
||||
@ -70,7 +73,7 @@
|
||||
</Other>
|
||||
</CompilerOptions>
|
||||
<Debugging>
|
||||
<Exceptions Count="3">
|
||||
<Exceptions Count="4">
|
||||
<Item1>
|
||||
<Name Value="EAbort"/>
|
||||
</Item1>
|
||||
@ -80,6 +83,9 @@
|
||||
<Item3>
|
||||
<Name Value="EFOpenError"/>
|
||||
</Item3>
|
||||
<Item4>
|
||||
<Name Value="EConvertError"/>
|
||||
</Item4>
|
||||
</Exceptions>
|
||||
</Debugging>
|
||||
</CONFIG>
|
||||
|
@ -14,7 +14,7 @@ uses
|
||||
|
||||
begin
|
||||
Application.Initialize;
|
||||
Application.CreateForm(TForm1, Form1);
|
||||
Application.CreateForm(TformCDR2SVG, formCDR2SVG);
|
||||
Application.Run;
|
||||
end.
|
||||
|
||||
|
@ -38,6 +38,11 @@ type
|
||||
st2DLine, st2DBezier,
|
||||
st3DLine, st3DBezier);
|
||||
|
||||
{@
|
||||
The coordinates in fpvectorial are given in millimiters and
|
||||
the starting point is in the top-left corner of the document
|
||||
and it grows to the bottom and to the right.
|
||||
}
|
||||
TPathSegment = record
|
||||
SegmentType: TSegmentType;
|
||||
X, Y, Z: Double; // Z is ignored in 2D segments
|
||||
@ -617,10 +622,21 @@ begin
|
||||
end;
|
||||
end;
|
||||
|
||||
{@@
|
||||
The default stream writer just uses WriteToStrings
|
||||
}
|
||||
procedure TvCustomVectorialWriter.WriteToStream(AStream: TStream;
|
||||
AData: TvVectorialDocument);
|
||||
var
|
||||
lStringList: TStringList;
|
||||
begin
|
||||
|
||||
lStringList := TStringList.Create;
|
||||
try
|
||||
WriteToStrings(lStringList, AData);
|
||||
lStringList.SaveToStream(AStream);
|
||||
finally
|
||||
lStringList.Free;
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TvCustomVectorialWriter.WriteToStrings(AStrings: TStrings;
|
||||
|
@ -10,12 +10,12 @@ uses
|
||||
fpvectorial;
|
||||
|
||||
procedure DrawFPVectorialToCanvas(ASource: TvVectorialDocument; ADest: TFPCustomCanvas;
|
||||
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Integer = 1; AMulY: Integer = 1);
|
||||
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
|
||||
|
||||
implementation
|
||||
|
||||
procedure DrawFPVectorialToCanvas(ASource: TvVectorialDocument; ADest: TFPCustomCanvas;
|
||||
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Integer = 1; AMulY: Integer = 1);
|
||||
ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0);
|
||||
var
|
||||
i, j, k: Integer;
|
||||
PosX, PosY: Integer; // Not modified by ADestX, etc
|
||||
@ -47,8 +47,8 @@ begin
|
||||
PosX := Round(CurSegment.X);
|
||||
PosY := Round(CurSegment.Y);
|
||||
ADest.LineTo(
|
||||
ADestX + AMulX * PosX,
|
||||
ADestY + AMulY * PosY
|
||||
Round(ADestX + AMulX * PosX),
|
||||
Round(ADestY + AMulY * PosY)
|
||||
);
|
||||
end;
|
||||
{ To draw a bezier we need to divide the interval in parts and make
|
||||
@ -66,8 +66,8 @@ begin
|
||||
CurX := Round(sqr(1 - t) * (1 - t) * PosX + 3 * t * sqr(1 - t) * CurSegment.X2 + 3 * t * t * (1 - t) * CurSegment.X3 + t * t * t * CurSegment.X);
|
||||
CurY := Round(sqr(1 - t) * (1 - t) * PosY + 3 * t * sqr(1 - t) * CurSegment.Y2 + 3 * t * t * (1 - t) * CurSegment.Y3 + t * t * t * CurSegment.Y);
|
||||
ADest.LineTo(
|
||||
ADestX + AMulX * CurX,
|
||||
ADestY + AMulY * CurY);
|
||||
Round(ADestX + AMulX * CurX),
|
||||
Round(ADestY + AMulY * CurY));
|
||||
end;
|
||||
PosX := Round(CurSegment.X);
|
||||
PosY := Round(CurSegment.Y);
|
||||
|
@ -8,11 +8,16 @@ uses
|
||||
Classes, SysUtils, pdfvrlexico, fpvectorial;
|
||||
|
||||
type
|
||||
|
||||
{ AnSemantico }
|
||||
|
||||
AnSemantico = class
|
||||
public
|
||||
FPointSeparator, FCommaSeparator: TFormatSettings;
|
||||
close_path_x: String;
|
||||
close_path_y: String;
|
||||
cm_a, cm_b, cm_c, cm_d, cm_e, cm_f: Real; // coordinate spaces constants
|
||||
function StringToFloat(AStr: string): Double;
|
||||
function generate(c: Command; AData: TvVectorialDocument): String;
|
||||
function convert(x: String; y: String; Axis: Char): String;
|
||||
function startMachine(): String;
|
||||
@ -22,6 +27,14 @@ type
|
||||
|
||||
implementation
|
||||
|
||||
{ PDF doesn't seam very consistent when it comes to using commas or
|
||||
points as decimal separator, so we just try both }
|
||||
function AnSemantico.StringToFloat(AStr: string): Double;
|
||||
begin
|
||||
if Pos('.', AStr) > 0 then Result := StrToFloat(AStr, FPointSeparator)
|
||||
else Result := StrToFloat(AStr, FCommaSeparator);
|
||||
end;
|
||||
|
||||
function AnSemantico.generate(c: Command; AData: TvVectorialDocument): String;
|
||||
var
|
||||
enter_line : String;
|
||||
@ -29,6 +42,7 @@ begin
|
||||
{$ifdef FPVECTORIALDEBUG}
|
||||
WriteLn(':> AnSemantico.generate');
|
||||
{$endif}
|
||||
|
||||
enter_line:= LineEnding; //chr(13) + chr(10); // CR and LF
|
||||
|
||||
if ((c.code = cc_H_CLOSE_PATH) or (c.code = cc_hS_CLOSE_AND_END_PATH)) then // command h or s
|
||||
@ -69,7 +83,7 @@ begin
|
||||
// Correcao para programas de desenho que geram um novo inicio no
|
||||
// fim do desenho, terminamos qualquer desenho inacabado
|
||||
AData.EndPath();
|
||||
AData.StartPath(StrToFloat(c.cord_x), StrToFloat(c.cord_y));
|
||||
AData.StartPath(StringToFloat(c.cord_x), StringToFloat(c.cord_y));
|
||||
|
||||
close_path_x:=c.cord_x;
|
||||
close_path_y:=c.cord_y;
|
||||
@ -81,7 +95,7 @@ begin
|
||||
{$endif}
|
||||
// Result:='G01' + ' ' + 'X' + c.cord_x + ' ' + 'Y' + c.cord_y;
|
||||
|
||||
AData.AddLineToPath(StrToFloat(c.cord_x), StrToFloat(c.cord_y));
|
||||
AData.AddLineToPath(StringToFloat(c.cord_x), StringToFloat(c.cord_y));
|
||||
end;
|
||||
cc_h_CLOSE_PATH: // command h
|
||||
begin
|
||||
@ -90,7 +104,7 @@ begin
|
||||
{$endif}
|
||||
//Result:='G01' + ' ' + 'X' + c.cord_x + ' ' + 'Y' + c.cord_y;
|
||||
|
||||
AData.AddLineToPath(StrToFloat(c.cord_x), StrToFloat(c.cord_y));
|
||||
AData.AddLineToPath(StringToFloat(c.cord_x), StringToFloat(c.cord_y));
|
||||
end;
|
||||
cc_S_END_PATH: // command S
|
||||
begin
|
||||
@ -108,7 +122,7 @@ begin
|
||||
//Result:='G01' + ' ' + 'X' + c.cord_x + ' ' + 'Y' + c.cord_y + enter_line
|
||||
// +'G01 Z0 // Sobe a cabeça de gravação' + enter_line;
|
||||
|
||||
AData.AddLineToPath(StrToFloat(c.cord_x), StrToFloat(c.cord_y));
|
||||
AData.AddLineToPath(StringToFloat(c.cord_x), StringToFloat(c.cord_y));
|
||||
AData.EndPath();
|
||||
end;
|
||||
cc_c_BEZIER_TO_X_Y_USING_X2_Y2_AND_X3_Y3: // command c
|
||||
@ -120,9 +134,9 @@ begin
|
||||
// +'G01 Z0 // Sobe a cabeça de gravação' + enter_line;
|
||||
|
||||
AData.AddBezierToPath(
|
||||
StrToFloat(c.cord_x3), StrToFloat(c.cord_y3),
|
||||
StrToFloat(c.cord_x2), StrToFloat(c.cord_y2),
|
||||
StrToFloat(c.cord_x), StrToFloat(c.cord_y)
|
||||
StringToFloat(c.cord_x3), StringToFloat(c.cord_y3),
|
||||
StringToFloat(c.cord_x2), StringToFloat(c.cord_y2),
|
||||
StringToFloat(c.cord_x), StringToFloat(c.cord_y)
|
||||
);
|
||||
end;
|
||||
cc_CONCATENATE_MATRIX: // command cm
|
||||
@ -131,12 +145,12 @@ begin
|
||||
WriteLn(':> AnSemantico.cc_CONCATENATE_MATRIX');
|
||||
{$endif}
|
||||
|
||||
cm_a := StrToFloat(c.cord_x3);
|
||||
cm_b := StrToFloat(c.cord_y3);
|
||||
cm_c := StrToFloat(c.cord_x2);
|
||||
cm_d := StrToFloat(c.cord_y2);
|
||||
cm_e := StrToFloat(c.cord_x);
|
||||
cm_f := StrToFloat(c.cord_y);
|
||||
cm_a := StringToFloat(c.cord_x3);
|
||||
cm_b := StringToFloat(c.cord_y3);
|
||||
cm_c := StringToFloat(c.cord_x2);
|
||||
cm_d := StringToFloat(c.cord_y2);
|
||||
cm_e := StringToFloat(c.cord_x);
|
||||
cm_f := StringToFloat(c.cord_y);
|
||||
end;
|
||||
cc_RESTORE_MATRIX: // command Q
|
||||
begin
|
||||
@ -169,13 +183,13 @@ begin
|
||||
if (Axis = 'y') then
|
||||
begin
|
||||
// y' = b * x + d * y + f
|
||||
Result:=FloatToStr((cm_b*StrToFloat(x)+cm_d*StrToFloat(y)+cm_f)*(25.40/72));
|
||||
Result:=FloatToStr((cm_b*StringToFloat(x)+cm_d*StringToFloat(y)+cm_f)*(25.40/72));
|
||||
end
|
||||
else
|
||||
// Axis = 'x'
|
||||
begin
|
||||
// x' = a * x + c * y + e
|
||||
Result:=FloatToStr((cm_a*StrToFloat(x)+cm_c*StrToFloat(y)+cm_e)*(25.40/72));
|
||||
Result:=FloatToStr((cm_a*StringToFloat(x)+cm_c*StringToFloat(y)+cm_e)*(25.40/72));
|
||||
end;
|
||||
end;
|
||||
|
||||
@ -209,12 +223,21 @@ end;
|
||||
constructor AnSemantico.Create;
|
||||
begin
|
||||
inherited Create;
|
||||
|
||||
cm_a:=1;
|
||||
cm_b:=0;
|
||||
cm_c:=0;
|
||||
cm_d:=1;
|
||||
cm_e:=0;
|
||||
cm_f:=0;
|
||||
|
||||
// Format seetings to convert a string to a float
|
||||
FPointSeparator := DefaultFormatSettings;
|
||||
FPointSeparator.DecimalSeparator := '.';
|
||||
FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
|
||||
FCommaSeparator := DefaultFormatSettings;
|
||||
FCommaSeparator.DecimalSeparator := ',';
|
||||
FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator
|
||||
end;
|
||||
|
||||
end.
|
||||
|
@ -20,19 +20,102 @@ type
|
||||
{ TvSVGVectorialWriter }
|
||||
|
||||
TvSVGVectorialWriter = class(TvCustomVectorialWriter)
|
||||
private
|
||||
FPointSeparator, FCommaSeparator: TFormatSettings;
|
||||
procedure WriteDocumentSize(AStrings: TStrings; AData: TvVectorialDocument);
|
||||
procedure WriteDocumentName(AStrings: TStrings; AData: TvVectorialDocument);
|
||||
procedure WritePaths(AStrings: TStrings; AData: TvVectorialDocument);
|
||||
public
|
||||
{ General reading methods }
|
||||
procedure WriteToStream(AStream: TStream; AData: TvVectorialDocument); virtual;
|
||||
procedure WriteToStrings(AStrings: TStrings; AData: TvVectorialDocument); override;
|
||||
end;
|
||||
|
||||
implementation
|
||||
|
||||
{ TvSVGVectorialWriter }
|
||||
|
||||
procedure TvSVGVectorialWriter.WriteToStream(AStream: TStream;
|
||||
procedure TvSVGVectorialWriter.WriteDocumentSize(AStrings: TStrings; AData: TvVectorialDocument);
|
||||
begin
|
||||
AStrings.Add(' width="744.09448819"');
|
||||
AStrings.Add(' height="1052.3622047"');
|
||||
end;
|
||||
|
||||
procedure TvSVGVectorialWriter.WriteDocumentName(AStrings: TStrings; AData: TvVectorialDocument);
|
||||
begin
|
||||
AStrings.Add(' sodipodi:docname="New document 1">');
|
||||
end;
|
||||
|
||||
{@@
|
||||
SVG Coordinate system measures things in millimiters.
|
||||
The initial point is in the bottom-left corner of the document and it grows
|
||||
to the top and to the right.
|
||||
|
||||
SVG uses commas "," to separate the X,Y coordinates, so it always uses points
|
||||
"." as decimal separators and uses no thousand separators
|
||||
}
|
||||
procedure TvSVGVectorialWriter.WritePaths(AStrings: TStrings; AData: TvVectorialDocument);
|
||||
var
|
||||
i, j: Integer;
|
||||
PathStr: string;
|
||||
lPath: TPath;
|
||||
PtX, PtY: double;
|
||||
begin
|
||||
for i := 0 to AData.GetPathCount() - 1 do
|
||||
begin
|
||||
PathStr := 'm ';
|
||||
lPath := AData.GetPath(i);
|
||||
for j := 0 to lPath.Len - 1 do
|
||||
begin
|
||||
if lPath.Points[j].SegmentType <> st2DLine then Break; // unsupported line type
|
||||
|
||||
PtX := lPath.Points[j].X;
|
||||
PtY := lPath.Points[j].Y;
|
||||
PtY := AData.Height - PtY;
|
||||
PathStr := PathStr + FloatToStr(PtX, FPointSeparator) + ','
|
||||
+ FloatToStr(PtY, FPointSeparator) + ' ';
|
||||
end;
|
||||
|
||||
AStrings.Add(' <path');
|
||||
AStrings.Add(' style="fill:none;stroke:#000000;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"');
|
||||
AStrings.Add(' d="' + PathStr + '"');
|
||||
AStrings.Add(' id="path' + IntToStr(i) + '" />');
|
||||
end;
|
||||
end;
|
||||
|
||||
procedure TvSVGVectorialWriter.WriteToStrings(AStrings: TStrings;
|
||||
AData: TvVectorialDocument);
|
||||
begin
|
||||
// Format seetings to convert a string to a float
|
||||
FPointSeparator := DefaultFormatSettings;
|
||||
FPointSeparator.DecimalSeparator := '.';
|
||||
FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
|
||||
FCommaSeparator := DefaultFormatSettings;
|
||||
FCommaSeparator.DecimalSeparator := ',';
|
||||
FCommaSeparator.ThousandSeparator := '#';// disable the thousand separator
|
||||
|
||||
// Headers
|
||||
AStrings.Add('<?xml version="1.0" encoding="UTF-8" standalone="no"?>');
|
||||
AStrings.Add('<!-- Created with fpVectorial (http://wiki.lazarus.freepascal.org/fpvectorial) -->');
|
||||
AStrings.Add('');
|
||||
AStrings.Add('<svg');
|
||||
AStrings.Add(' xmlns:dc="http://purl.org/dc/elements/1.1/"');
|
||||
AStrings.Add(' xmlns:cc="http://creativecommons.org/ns#"');
|
||||
AStrings.Add(' xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"');
|
||||
AStrings.Add(' xmlns:svg="http://www.w3.org/2000/svg"');
|
||||
AStrings.Add(' xmlns="http://www.w3.org/2000/svg"');
|
||||
AStrings.Add(' xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"');
|
||||
WriteDocumentSize(AStrings, AData);
|
||||
AStrings.Add(' id="svg2"');
|
||||
AStrings.Add(' version="1.1"');
|
||||
WriteDocumentName(AStrings, AData);
|
||||
|
||||
// Now data
|
||||
AStrings.Add(' <g id="layer1">');
|
||||
WritePaths(AStrings, AData);
|
||||
AStrings.Add(' </g>');
|
||||
|
||||
// finalization
|
||||
AStrings.Add('</svg>');
|
||||
end;
|
||||
|
||||
initialization
|
||||
|
Loading…
Reference in New Issue
Block a user