1 {
2  *****************************************************************************
3   See the file COPYING.modifiedLGPL.txt, included in this distribution,
4   for details about the license.
5  *****************************************************************************
6 
7   Authors: Alexander Klenin
8 
9 }
10 unit TADrawerSVG;
11 
12 {$H+}
13 
14 interface
15 
16 uses
17   Graphics, Classes, FPImage, FPCanvas, EasyLazFreeType,
18   TAFonts, TAChartUtils, TADrawUtils, TAGraph;
19 
20 type
21   TSVGDrawer = class(TBasicDrawer, IChartDrawer)
22   strict private
23     FAntialiasingMode: TChartAntialiasingMode;
24     FBrushColor: TFPColor;
25     FBrushStyle: TFPBrushStyle;
26     FClippingPathId: Integer;
27     FFont: TFreeTypeFont;
28     FFontHeight: Integer;  // Height of text in pixels
29     FFontOrientation: Integer;  // angle*10 (i.e. 90° --> 900, >0 if ccs.
30     FFontColor: TFPColor;
31     FPatterns: TStrings;
32     FPen: TFPCustomPen;
33     FPrevPos: TPoint;
34     FStream: TStream;
35 
OpacityStrnull36     function OpacityStr: String;
PointsToStrnull37     function PointsToStr(
38       const APoints: array of TPoint; AStartIndex, ANumPts: Integer): String;
39 
40     procedure SetBrush(ABrush: TFPCustomBrush);
41     procedure SetFont(AFont: TFPCustomFont);
42     procedure SetPen(APen: TFPCustomPen);
43 
StyleFillnull44     function StyleFill: String;
StyleStrokenull45     function StyleStroke: String;
46 
47     procedure WriteFmt(const AFormat: String; AParams: array of const);
48     procedure WriteStr(const AString: String);
49   strict protected
SimpleTextExtentnull50     function SimpleTextExtent(const AText: String): TPoint; override;
51     procedure SimpleTextOut(AX, AY: Integer; const AText: String); override;
52 
53   public
54     constructor Create(AStream: TStream; AWriteDocType: Boolean);
55     destructor Destroy; override;
56   public
57     procedure AddToFontOrientation(ADelta: Integer);
58     procedure ClippingStart;
59     procedure ClippingStart(const AClipRect: TRect);
60     procedure ClippingStop;
61     procedure DrawingBegin(const ABoundingBox: TRect); override;
62     procedure DrawingEnd; override;
63     procedure Ellipse(AX1, AY1, AX2, AY2: Integer);
64     procedure FillRect(AX1, AY1, AX2, AY2: Integer);
GetBrushColornull65     function GetBrushColor: TChartColor;
GetFontAnglenull66     function GetFontAngle: Double; override;
GetFontColornull67     function GetFontColor: TFPColor; override;
GetFontNamenull68     function GetFontName: String; override;
GetFontSizenull69     function GetFontSize: Integer; override;
GetFontStylenull70     function GetFontStyle: TChartFontStyles; override;
71     procedure Line(AX1, AY1, AX2, AY2: Integer);
72     procedure Line(const AP1, AP2: TPoint);
73     procedure LineTo(AX, AY: Integer); override;
74     procedure MoveTo(AX, AY: Integer); override;
75     procedure Polygon(
76       const APoints: array of TPoint; AStartIndex, ANumPts: Integer); override;
77     procedure Polyline(
78       const APoints: array of TPoint; AStartIndex, ANumPts: Integer);
79     procedure PrepareSimplePen(AColor: TChartColor);
80     procedure PutImage(AX, AY: Integer; AImage: TFPCustomImage); override;
81     procedure PutPixel(AX, AY: Integer; AColor: TChartColor); override;
82     procedure RadialPie(
83       AX1, AY1, AX2, AY2: Integer;
84       AStartAngle16Deg, AAngleLength16Deg: Integer);
85     procedure Rectangle(const ARect: TRect);
86     procedure Rectangle(AX1, AY1, AX2, AY2: Integer);
87     procedure ResetFont;
88     procedure SetAntialiasingMode(AValue: TChartAntialiasingMode);
89     procedure SetBrushColor(AColor: TChartColor);
90     procedure SetBrushParams(AStyle: TFPBrushStyle; AColor: TChartColor);
91     procedure SetPenParams(AStyle: TFPPenStyle; AColor: TChartColor);
92   end;
93 
94 
95   { TSVGChartHelper }
96 
97   TSVGChartHelper = class helper for TChart
98     procedure SaveToSVGFile(const AFileName: String);
99   end;
100 
101 
102 implementation
103 
104 uses
105   Base64, FPWritePNG, Math, SysUtils, TAGeometry;
106 
107 const
108   RECT_FMT =
109     '<rect x="%d" y="%d" width="%d" height="%d" style="%s"/>';
110 var
111   fmtSettings: TFormatSettings;
112 
EscapeXMLnull113 function EscapeXML(const AText: String): String;
114 var
115   ch: Char;
116 begin
117   Result := '';
118   for ch in AText do
119     case ch of
120       '<': Result := Result + '&lt;';
121       '>': Result := Result + '&gt;';
122       '"': Result := Result + '&quot;';
123       '''':Result := Result + '&apos;';
124       '&': Result := Result + '&amp;';
125       else Result := Result + ch;
126     end;
127 end;
128 
ColorToHexnull129 function ColorToHex(AColor: TFPColor): String;
130 begin
131   if AColor = colBlack then
132     Result := 'black'
133   else if AColor = colWhite then
134     Result := 'white'
135   else
136     with AColor do
137       Result := Format('#%.2x%.2x%.2x', [red shr 8, green shr 8, blue shr 8]);
138 end;
139 
DP2Snull140 function DP2S(AValue: TDoublePoint): String;
141 begin
142   Result := Format('%g,%g', [AValue.X, AValue.Y], fmtSettings);
143 end;
144 
F2Snull145 function F2S(AValue: Double): String;
146 begin
147   Result := FloatToStr(AValue, fmtSettings);
148 end;
149 
SVGGetFontOrientationFuncnull150 function SVGGetFontOrientationFunc(AFont: TFPCustomFont): Integer;
151 begin
152   if AFont is TFont then
153     Result := (AFont as TFont).Orientation
154   else
155     Result := AFont.Orientation;
156 end;
157 
SVGChartColorToFPColornull158 function SVGChartColorToFPColor(AChartColor: TChartColor): TFPColor;
159 begin
160   Result := ChartColorToFPColor(ColorToRGB(AChartColor));
161 end;
162 
163 
164 { TSVGDrawer }
165 
166 procedure TSVGDrawer.AddToFontOrientation(ADelta: Integer);
167 begin
168   FFontOrientation += ADelta;
169 end;
170 
171 procedure TSVGDrawer.ClippingStart(const AClipRect: TRect);
172 begin
173   FClippingPathId += 1;
174   WriteFmt('<clipPath id="clip%d">', [FClippingPathId]);
175   with AClipRect do
176     WriteFmt(RECT_FMT, [Left, Top, Right - Left, Bottom - Top, '']);
177   WriteStr('</clipPath>');
178   ClippingStart;
179 end;
180 
181 procedure TSVGDrawer.ClippingStart;
182 begin
183   WriteFmt('<g clip-path="url(#clip%d)">', [FClippingPathId]);
184 end;
185 
186 procedure TSVGDrawer.ClippingStop;
187 begin
188   WriteStr('</g>');
189 end;
190 
191 constructor TSVGDrawer.Create(AStream: TStream; AWriteDocType: Boolean);
192 begin
193   inherited Create;
194   InitFonts;
195   FStream := AStream;
196   FPatterns := TStringList.Create;
197   FPen := TFPCustomPen.Create;
198   FGetFontOrientationFunc := @SVGGetFontOrientationFunc;
199   FChartColorToFPColorFunc := @SVGChartColorToFPColor;
200   if AWriteDocType then begin
201     WriteStr('<?xml version="1.0"?>');
202     WriteStr('<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN"');
203     WriteStr('"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">');
204   end;
205 end;
206 
207 destructor TSVGDrawer.Destroy;
208 begin
209   FreeAndNil(FFont);
210   FreeAndNil(FPatterns);
211   FreeAndNil(FPen);
212   inherited Destroy;
213 end;
214 
215 procedure TSVGDrawer.DrawingBegin(const ABoundingBox: TRect);
216 begin
217   FAntialiasingMode := amDontCare;
218   with ABoundingBox do
219     WriteFmt(
220       '<svg ' +
221       'xmlns="http://www.w3.org/2000/svg" ' +
222       'xmlns:xlink="http://www.w3.org/1999/xlink" ' +
223       'width="%dpx" height="%dpx" viewBox="%d %d %d %d">',
224       [Right - Left, Bottom - Top, Left, Top, Right, Bottom]);
225   FClippingPathId := 0;
226 end;
227 
228 procedure TSVGDrawer.DrawingEnd;
229 var
230   i: Integer;
231 begin
232   if FAntialiasingMode <> amDontCare then
233     WriteStr('</g>');
234   if FPatterns.Count > 0 then begin
235     WriteStr('<defs>');
236     for i := 0 to FPatterns.Count - 1 do
237       WriteFmt(
238         '<pattern id="bs%d" width="8" height="8" patternUnits="userSpaceOnUse">' +
239         '%s</pattern>',
240         [i, FPatterns[i]]);
241     WriteStr('</defs>');
242     FPatterns.Clear;
243   end;
244   WriteStr('</svg>');
245 end;
246 
247 procedure TSVGDrawer.Ellipse(AX1, AY1, AX2, AY2: Integer);
248 var
249   e: TEllipse;
250 begin
251   e.InitBoundingBox(AX1, AY1, AX2, AY2);
252   WriteFmt(
253     '<ellipse cx="%g" cy="%g" rx="%g" ry="%g" style="%s"/>',
254     [e.FC.X, e.FC.Y, e.FR.X, e.FR.Y, StyleFill + StyleStroke]);
255 end;
256 
257 procedure TSVGDrawer.FillRect(AX1, AY1, AX2, AY2: Integer);
258 begin
259   WriteFmt(RECT_FMT, [AX1, AY1, AX2 - AX1, AY2 - AY1, StyleFill]);
260 end;
261 
GetBrushColornull262 function TSVGDrawer.GetBrushColor: TChartColor;
263 begin
264   Result := FPColorToChartColor(FBrushColor);
265 end;
266 
GetFontAnglenull267 function TSVGDrawer.GetFontAngle: Double;
268 begin
269   Result := OrientToRad(FFontOrientation);
270 end;
271 
TSVGDrawer.GetFontColornull272 function TSVGDrawer.GetFontColor: TFPColor;
273 begin
274   Result := FFontColor;
275 end;
276 
TSVGDrawer.GetFontNamenull277 function TSVGDrawer.GetFontName: String;
278 begin
279   Result := FFont.Family;
280 end;
281 
GetFontSizenull282 function TSVGDrawer.GetFontSize: Integer;
283 begin
284   Result := Round(FFont.SizeInPoints);
285 end;
286 
TSVGDrawer.GetFontStylenull287 function TSVGDrawer.GetFontStyle: TChartFontStyles;
288 begin
289   Result := [];
290   if ftsBold in FFont.Style then Include(Result, cfsBold);
291   if ftsItalic in FFont.Style then Include(Result, cfsItalic);
292   if FFont.UnderlineDecoration then Include(Result, cfsUnderline);
293   if FFont.StrikeoutDecoration then Include(Result, cfsStrikeout);
294 end;
295 
296 procedure TSVGDrawer.Line(AX1, AY1, AX2, AY2: Integer);
297 begin
298   WriteFmt(
299     '<line x1="%d" y1="%d" x2="%d" y2="%d" style="%s"/>',
300     [AX1, AY1, AX2, AY2, StyleStroke]);
301 end;
302 
303 procedure TSVGDrawer.Line(const AP1, AP2: TPoint);
304 begin
305   Line(AP1.X, AP1.Y, AP2.X, AP2.Y);
306 end;
307 
308 procedure TSVGDrawer.LineTo(AX, AY: Integer);
309 begin
310   Line(FPrevPos.X, FPrevPos.Y, AX, AY);
311   FPrevPos := Point(AX, AY);
312 end;
313 
314 procedure TSVGDrawer.MoveTo(AX, AY: Integer);
315 begin
316   FPrevPos := Point(AX, AY);
317 end;
318 
OpacityStrnull319 function TSVGDrawer.OpacityStr: String;
320 begin
321   if FTransparency = 0 then
322     Result := ''
323   else
324     Result := F2S((255 - FTransparency) / 256);
325 end;
326 
PointsToStrnull327 function TSVGDrawer.PointsToStr(
328   const APoints: array of TPoint; AStartIndex, ANumPts: Integer): String;
329 var
330   i: Integer;
331 begin
332   if ANumPts < 0 then
333     ANumPts := Length(APoints) - AStartIndex;
334   Result := '';
335   for i := 0 to ANumPts - 1 do
336     with APoints[i + AStartIndex] do
337       Result += Format('%d %d ', [X, Y]);
338 end;
339 
340 procedure TSVGDrawer.Polygon(
341   const APoints: array of TPoint; AStartIndex, ANumPts: Integer);
342 begin
343   WriteFmt(
344     '<polygon points="%s" style="%s"/>',
345     [PointsToStr(APoints, AStartIndex, ANumPts), StyleFill + StyleStroke]);
346 end;
347 
348 procedure TSVGDrawer.Polyline(
349   const APoints: array of TPoint; AStartIndex, ANumPts: Integer);
350 begin
351   WriteFmt(
352     '<polyline points="%s" style="fill: none; %s"/>',
353     [PointsToStr(APoints, AStartIndex, ANumPts), StyleStroke]);
354 end;
355 
356 procedure TSVGDrawer.PrepareSimplePen(AColor: TChartColor);
357 begin
358   FPen.FPColor := FChartColorToFPColorFunc(ColorOrMono(AColor));
359   FPen.Style := psSolid;
360   FPen.Width := 1;
361 end;
362 
363 procedure TSVGDrawer.PutImage(AX, AY: Integer; AImage: TFPCustomImage);
364 var
365   s: TStringStream = nil;
366   w: TFPWriterPNG = nil;
367   b: TBase64EncodingStream = nil;
368 begin
369   s := TStringStream.Create('');
370   b := TBase64EncodingStream.Create(s);
371   w := TFPWriterPNG.Create;
372   try
373     w.Indexed := false;
374     w.UseAlpha := true;
375     AImage.SaveToStream(b, w);
376     b.Flush;
377     WriteFmt(
378       '<image x="%d" y="%d" width="%d" height="%d" ' +
379       'xlink:href="data:image/png;base64,%s"/>',
380       [AX, AY, AImage.Width, AImage.Height, s.DataString]);
381   finally
382     w.Free;
383     s.Free;
384     b.Free;
385   end;
386 end;
387 
388 procedure TSVGDrawer.PutPixel(AX, AY: Integer; AColor: TChartColor);
389 var
390   stroke: String;
391 begin
392   stroke := 'stroke:'+ColorToHex(FChartColorToFPColorFunc(ColorOrMono(AColor))) + ';stroke-width:1;';
393   WriteFmt(RECT_FMT, [AX, AY, 1, 1, stroke]);
394 end;
395 
396 procedure TSVGDrawer.RadialPie(
397   AX1, AY1, AX2, AY2: Integer; AStartAngle16Deg, AAngleLength16Deg: Integer);
398 var
399   e: TEllipse;
400   p1, p2: TDoublePoint;
401 begin
402   e.InitBoundingBox(AX1, AY1, AX2, AY2);
403   p1 := e.GetPoint(Deg16ToRad(AStartAngle16Deg));
404   p2 := e.GetPoint(Deg16ToRad(AStartAngle16Deg + AAngleLength16Deg));
405   WriteFmt(
406     '<path d="M%s L%s A%s 0 0,0 %s Z" style="%s"/>',
407     [DP2S(e.FC), DP2S(p1), DP2S(e.FR), DP2S(p2), StyleFill + StyleStroke]);
408 end;
409 
410 procedure TSVGDrawer.Rectangle(AX1, AY1, AX2, AY2: Integer);
411 begin
412   WriteFmt(
413     RECT_FMT, [AX1, AY1, AX2 - AX1, AY2 - AY1, StyleFill + StyleStroke]);
414 end;
415 
416 procedure TSVGDrawer.Rectangle(const ARect: TRect);
417 begin
418   with ARect do
419     Rectangle(Left, Top, Right, Bottom);
420 end;
421 
422 procedure TSVGDrawer.ResetFont;
423 begin
424   FFontOrientation := 0;
425 end;
426 
427 procedure TSVGDrawer.SetAntialiasingMode(AValue: TChartAntialiasingMode);
428 const
429   AM_TO_CSS: array [amOn .. amOff] of String =
430     ('geometricPrecision', 'crispEdges');
431 begin
432   if FAntialiasingMode = AValue then exit;
433   if FAntialiasingMode <> amDontCare then
434     WriteStr('</g>');
435   FAntialiasingMode := AValue;
436   if FAntialiasingMode <> amDontCare then
437     WriteFmt('<g style="shape-rendering: %s">',[AM_TO_CSS[FAntialiasingMode]]);
438 end;
439 
440 procedure TSVGDrawer.SetBrush(ABrush: TFPCustomBrush);
441 begin
442   if ABrush is TBrush then
443     FBrushColor := FChartColorToFPColorFunc(ColorOrMono(TBrush(ABrush).Color))
444   else
445     FBrushColor := FPColorOrMono(ABrush.FPColor);
446   FBrushStyle := ABrush.Style;
447 end;
448 
449 procedure TSVGDrawer.SetBrushColor(AColor: TChartColor);
450 begin
451   FBrushColor := FChartColorToFPColorFunc(ColorOrMono(AColor));
452 end;
453 
454 procedure TSVGDrawer.SetBrushParams(
455   AStyle: TFPBrushStyle; AColor: TChartColor);
456 begin
457   FBrushColor := FChartColorToFPColorFunc(ColorOrMono(AColor));
458   FBrushStyle := AStyle;
459 end;
460 
461 procedure TSVGDrawer.SetFont(AFont: TFPCustomFont);
462 var
463   style: TFreeTypeStyles;
464   fn: String;
465 begin
466   style := [];
467   if AFont.Bold then Include(style, ftsBold);
468   if AFont.Italic then Include(style, ftsItalic);
469 
470   // create a new font if not yet loaded
471   if (FFont = nil) or (FFont.Family <> AFont.Name) or(FFont.Style <> style) then
472   begin
473     FreeAndNil(FFont);
474     if SameText(AFont.Name, 'default') then
475       fn := 'Arial'   // FIXME: Find font in FontCollection!
476     else
477       fn := AFont.Name;
478     FFont := LoadFont(fn, style);
479     if FFont = nil then
480       raise Exception.CreateFmt('Font "%s" not found."', [AFont.Name]);
481   end;
482 
483   // Set the requested font attributes
484   FFont.SizeInPoints := IfThen(AFont.Size = 0, DEFAULT_FONT_SIZE, AFont.Size);
485   FFont.UnderlineDecoration := AFont.Underline;
486   FFont.StrikeoutDecoration := AFont.StrikeThrough;
487   FFont.Hinted := true;
488   FFont.Quality := grqHighQuality;
489 
490   if FMonochromeColor <> clTAColor then
491     FFontColor := FChartColorToFPColorFunc(FMonochromeColor)
492   else
493     FFontColor := AFont.FPColor;
494   FFontOrientation := FGetFontOrientationFunc(AFont);
495   FFontHeight := round(FFont.TextHeight('Tg'));
496 end;
497 
498 procedure TSVGDrawer.SetPen(APen: TFPCustomPen);
499 begin
500   if APen is TPen then
501     FPen.FPColor := FChartColorToFPColorFunc(ColorOrMono(TPen(APen).Color))
502   else
503     FPen.FPColor := FPColorOrMono(APen.FPColor);
504   FPen.Style := APen.Style;
505   FPen.Width := APen.Width;
506 end;
507 
508 procedure TSVGDrawer.SetPenParams(AStyle: TFPPenStyle; AColor: TChartColor);
509 begin
510   FPen.FPColor := FChartColorToFPColorFunc(ColorOrMono(AColor));
511   FPen.Style := AStyle;
512 end;
513 
TSVGDrawer.SimpleTextExtentnull514 function TSVGDrawer.SimpleTextExtent(const AText: String): TPoint;
515 begin
516   Result.X := Round(FFont.TextWidth(AText));
517   Result.Y := FFontHeight;
518 end;
519 
520 type
521   TFreeTypeFontOpener = class(TFreeTypeFont);
522 
523 procedure TSVGDrawer.SimpleTextOut(AX, AY: Integer; const AText: String);
524 var
525   p: TPoint;
526   stext: String;
527   sstyle: String;
528   dy: Integer;
529   phi: Double;
530 begin
531   dy := round(TFreeTypeFontOpener(FFont).GetAscent);
532   phi := OrientToRad(FFontOrientation);
533   p := RotatePoint(Point(0, dy), -phi) + Point(AX, AY);
534   stext := Format('x="%d" y="%d"', [p.X, p.Y]);
535   if FFontOrientation <> 0 then
536     stext := stext + Format(' transform="rotate(%g,%d,%d)"',
537       [-FFontOrientation*0.1, p.X, p.Y], fmtSettings);
538 
539   sstyle := Format('fill:%s; font-family:''%s''; font-size:%dpt;',
540     [ColorToHex(GetFontColor), GetFontName, round(FFont.SizeInPoints)]);
541   if (ftsBold in FFont.Style) then
542     sstyle := sstyle + ' font-weight:bold;';
543   if (ftsItalic in FFont.Style) then
544     sstyle := sstyle + ' font-style:oblique;';
545   if FFont.UnderlineDecoration and FFont.StrikeoutDecoration then
546     sstyle := sstyle + ' text-decoration:underline,line-through;'
547   else if FFont.UnderlineDecoration then
548     sstyle := sstyle + ' text-deocration:underline;'
549   else if FFont.StrikeoutDecoration then
550     sstyle := sstyle + ' text-decoration:line-through;';
551   if OpacityStr <> '' then
552     sstyle := sstyle + OpacityStr + ';';
553 
554   WriteFmt('<text %s style="%s">%s</text>', [stext, sstyle, EscapeXML(AText)]);
555 end;
556 
StyleFillnull557 function TSVGDrawer.StyleFill: String;
558 
AddPatternnull559   function AddPattern(APattern: String): String;
560   var
561     i: Integer;
562   begin
563     i := FPatterns.IndexOf(APattern);
564     if i < 0 then
565       i := FPatterns.Add(APattern);
566     Result := Format('url(#bs%d)', [i]);
567   end;
568 
569 const
570   PATTERNS: array [TFPBrushStyle] of String = (
571     '', '',
572     'M0,4 h8',              // bsHorizontal
573     'M4,0 v8',              // bsVertical
574     'M0,0 l8,8',            // bsFDiagonal
575     'M0,8 l8,-8',           // bsBDiagonal
576     'M0,4 h8 M4,0 v8',      // bsCross
577     'M0,0 l8,8 M0,8 l8,-8', // bsDiagCross
578     '', '');
579 var
580   fill: String;
581 begin
582   case FBrushStyle of
583     bsClear: exit('fill: none;');
584     bsHorizontal..bsDiagCross:
585       fill := AddPattern(Format(
586         '<path d="%s" stroke="%s"/>',
587         [PATTERNS[FBrushStyle], ColorToHex(FBrushColor)]));
588     else
589       fill := ColorToHex(FBrushColor);
590   end;
591   Result :=
592     Format('fill:%s;', [fill]) + FormatIfNotEmpty('fill-opacity:%s;', OpacityStr);
593 end;
594 
TSVGDrawer.StyleStrokenull595 function TSVGDrawer.StyleStroke: String;
596 const
597   PEN_DASHARRAY: array [TFPPenStyle] of String =
598     ('', '2,2', '1,1', '2,1,1,1', '2,1,1,1,1,1', '', '', '');
599 begin
600   if FPen.Style = psClear then
601     exit('stroke: none');
602   Result := 'stroke:' + ColorToHex(FPen.FPColor) + ';';
603   if FPen.Width <> 1 then
604     Result += 'stroke-width:' + IntToStr(FPen.Width) + ';';
605   Result +=
606     FormatIfNotEmpty('stroke-dasharray:%s;', PEN_DASHARRAY[FPen.Style]) +
607     FormatIfNotEmpty('stroke-opacity:%s;', OpacityStr);
608 end;
609 
610 procedure TSVGDrawer.WriteFmt(const AFormat: String; AParams: array of const);
611 begin
612   WriteStr(Format(AFormat, AParams, fmtSettings));
613 end;
614 
615 procedure TSVGDrawer.WriteStr(const AString: String);
616 var
617   le: String = LineEnding;
618 begin
619   FStream.WriteBuffer(AString[1], Length(AString));
620   FStream.WriteBuffer(le[1], Length(le));
621 end;
622 
623 
624 { TSVGChartHelper }
625 
626 procedure TSVGChartHelper.SaveToSVGFile(const AFileName: String);
627 var
628   fs: TFileStream;
629 begin
630   fs := TFileStream.Create(AFileName, fmCreate);
631   try
632     Draw(TSVGDrawer.Create(fs, true), Rect(0, 0, Width, Height));
633   finally
634     fs.Free;
635   end;
636 end;
637 
638 
639 initialization
640   fmtSettings := DefaultFormatSettings;
641   fmtSettings.DecimalSeparator := '.';
642 
643 end.
644 
645