diff --git a/applications/fpvviewer/fpvectorialsrc/fpvectorial.pas b/applications/fpvviewer/fpvectorialsrc/fpvectorial.pas
index d61b28e72..0479f47d2 100644
--- a/applications/fpvviewer/fpvectorialsrc/fpvectorial.pas
+++ b/applications/fpvviewer/fpvectorialsrc/fpvectorial.pas
@@ -38,6 +38,7 @@ const
STR_SVG_EXTENSION = '.svg';
STR_CORELDRAW_EXTENSION = '.cdr';
STR_WINMETAFILE_EXTENSION = '.wmf';
+ STR_AUTOCAD_EXCHANGE_EXTENSION = '.dxf';
type
{@@ We need our own format because TFPColor is too big for our needs and TColor has no Alpha }
@@ -144,7 +145,6 @@ type
Brush: TvBrush;
constructor Create();
procedure Assign(APath: TPath);
- function Count(): TPathSegment;
procedure PrepareForSequentialReading;
function Next(): TPathSegment;
end;
@@ -243,7 +243,8 @@ type
procedure WriteToFile(AFileName: string; AFormat: TvVectorialFormat);
procedure WriteToStream(AStream: TStream; AFormat: TvVectorialFormat);
procedure WriteToStrings(AStrings: TStrings; AFormat: TvVectorialFormat);
- procedure ReadFromFile(AFileName: string; AFormat: TvVectorialFormat);
+ procedure ReadFromFile(AFileName: string; AFormat: TvVectorialFormat); overload;
+ procedure ReadFromFile(AFileName: string); overload;
procedure ReadFromStream(AStream: TStream; AFormat: TvVectorialFormat);
procedure ReadFromStrings(AStrings: TStrings; AFormat: TvVectorialFormat);
class function GetFormatFromExtension(AFileName: string): TvVectorialFormat;
@@ -261,7 +262,9 @@ type
procedure RemoveAllTexts;
{ Data writing methods }
procedure AddPath(APath: TPath);
- procedure StartPath(AX, AY: Double);
+ procedure StartPath(AX, AY: Double); overload;
+ procedure StartPath(); overload;
+ procedure AddMoveToPath(AX, AY: Double);
procedure AddLineToPath(AX, AY: Double); overload;
procedure AddLineToPath(AX, AY: Double; AColor: TvColor); overload;
procedure AddLineToPath(AX, AY, AZ: Double); overload;
@@ -551,6 +554,23 @@ begin
FTmpPath.PointsEnd := segment;
end;
+procedure TvVectorialDocument.StartPath();
+begin
+ ClearTmpPath();
+end;
+
+procedure TvVectorialDocument.AddMoveToPath(AX, AY: Double);
+var
+ segment: T2DSegment;
+begin
+ segment := T2DSegment.Create;
+ segment.SegmentType := stMoveTo;
+ segment.X := AX;
+ segment.Y := AY;
+
+ AppendSegmentToTmpPath(segment);
+end;
+
{@@
Adds one more point to the end of a Path being
writing in multiple steps.
@@ -824,8 +844,17 @@ procedure TvVectorialDocument.AppendSegmentToTmpPath(ASegment: TPathSegment);
var
L: Integer;
begin
+ // Check if we are the first segment in the tmp path
if FTmpPath.PointsEnd = nil then
- Exception.Create('[TvVectorialDocument.AppendSegmentToTmpPath]' + Str_Error_Nil_Path);
+ begin
+ if FTmpPath.Len <> 0 then
+ Exception.Create('[TvVectorialDocument.AppendSegmentToTmpPath]' + Str_Error_Nil_Path);
+
+ FTmpPath.Points := ASegment;
+ FTmpPath.PointsEnd := ASegment;
+ FTmpPath.Len := 1;
+ Exit;
+ end;
L := FTmpPath.Len;
Inc(FTmpPath.Len);
@@ -904,6 +933,22 @@ begin
end;
end;
+{@@
+ Reads the document from a file. A variant that auto-detects the format from the extension.
+}
+procedure TvVectorialDocument.ReadFromFile(AFileName: string);
+var
+ lExt: string;
+begin
+ lExt := ExtractFileExt(AFileName);
+ if lExt = STR_PDF_EXTENSION then ReadFromFile(AFileName, vfPDF)
+ else if lExt = STR_POSTSCRIPT_EXTENSION then ReadFromFile(AFileName, vfPostScript)
+ else if lExt = STR_SVG_EXTENSION then ReadFromFile(AFileName, vfSVG)
+ else if lExt = STR_CORELDRAW_EXTENSION then ReadFromFile(AFileName, vfCorelDrawCDR)
+ else if lExt = STR_WINMETAFILE_EXTENSION then ReadFromFile(AFileName, vfWindowsMetafileWMF)
+ else if lExt = STR_AUTOCAD_EXCHANGE_EXTENSION then ReadFromFile(AFileName, vfDXF);
+end;
+
{@@
Reads the document from a stream.
@@ -1134,11 +1179,6 @@ begin
Brush := APath.Brush;
end;
-function TPath.Count(): TPathSegment;
-begin
-
-end;
-
procedure TPath.PrepareForSequentialReading;
begin
CurPoint := nil;
diff --git a/applications/fpvviewer/fpvectorialsrc/fpvectorialpkg.lpk b/applications/fpvviewer/fpvectorialsrc/fpvectorialpkg.lpk
index 419c157aa..1817e7d98 100644
--- a/applications/fpvviewer/fpvectorialsrc/fpvectorialpkg.lpk
+++ b/applications/fpvviewer/fpvectorialsrc/fpvectorialpkg.lpk
@@ -5,13 +5,13 @@
-
+
-
+
@@ -64,6 +64,10 @@
+
+
+
+
diff --git a/applications/fpvviewer/fpvectorialsrc/fpvectorialpkg.pas b/applications/fpvviewer/fpvectorialsrc/fpvectorialpkg.pas
index 29d0e2a80..cf589c426 100644
--- a/applications/fpvviewer/fpvectorialsrc/fpvectorialpkg.pas
+++ b/applications/fpvviewer/fpvectorialsrc/fpvectorialpkg.pas
@@ -10,7 +10,7 @@ uses
svgvectorialwriter, pdfvrsintatico, pdfvrsemantico, pdfvrlexico,
pdfvectorialreader, fpvtocanvas, fpvectorial, fpvectbuildunit,
dxfvectorialreader, cdrvectorialreader, avisozlib, avisocncgcodewriter,
- avisocncgcodereader, LazarusPackageIntf;
+ avisocncgcodereader, svgvectorialreader, LazarusPackageIntf;
implementation
diff --git a/applications/fpvviewer/fpvectorialsrc/svgvectorialreader.pas b/applications/fpvviewer/fpvectorialsrc/svgvectorialreader.pas
new file mode 100644
index 000000000..c5e9ad129
--- /dev/null
+++ b/applications/fpvviewer/fpvectorialsrc/svgvectorialreader.pas
@@ -0,0 +1,300 @@
+{
+Reads an SVG Document
+
+License: The same modified LGPL as the Free Pascal RTL
+ See the file COPYING.modifiedLGPL for more details
+
+AUTHORS: Felipe Monteiro de Carvalho
+}
+unit svgvectorialreader;
+
+{$mode objfpc}{$H+}
+
+interface
+
+uses
+ Classes, SysUtils, math,
+ xmlread, dom, fgl,
+ fpvectorial, fpvutils;
+
+type
+ TSVGTokenType = (sttMoveTo, sttLineTo, sttBezierTo, sttFloatValue);
+
+ TSVGToken = class
+ TokenType: TSVGTokenType;
+ Value: Float;
+ end;
+
+ TSVGTokenList = specialize TFPGList;
+
+ { TSVGPathTokenizer }
+
+ TSVGPathTokenizer = class
+ public
+ FPointSeparator, FCommaSeparator: TFormatSettings;
+ Tokens: TSVGTokenList;
+ constructor Create;
+ Destructor Destroy; override;
+ procedure AddToken(AStr: string);
+ procedure TokenizePathString(AStr: string);
+ end;
+
+ { TvSVGVectorialReader }
+
+ TvSVGVectorialReader = class(TvCustomVectorialReader)
+ private
+ FPointSeparator, FCommaSeparator: TFormatSettings;
+ FSVGPathTokenizer: TSVGPathTokenizer;
+ procedure ReadPathFromNode(APath: TDOMNode; AData: TvVectorialDocument);
+ procedure ReadPathFromString(AStr: string; AData: TvVectorialDocument);
+ public
+ { General reading methods }
+ constructor Create; override;
+ Destructor Destroy; override;
+ procedure ReadFromStream(AStream: TStream; AData: TvVectorialDocument); override;
+ end;
+
+implementation
+
+const
+ // SVG requires hardcoding a DPI value
+
+ // The Opera Browser and Inkscape use 90 DPI, so we follow that
+
+ // 1 Inch = 25.4 milimiters
+ // 90 inches per pixel = (1 / 90) * 25.4 = 0.2822
+ // FLOAT_MILIMETERS_PER_PIXEL = 0.3528; // DPI 72 = 1 / 72 inches per pixel
+
+ FLOAT_MILIMETERS_PER_PIXEL = 0.2822; // DPI 90 = 1 / 90 inches per pixel
+ FLOAT_PIXELS_PER_MILIMETER = 3.5433; // DPI 90 = 1 / 90 inches per pixel
+
+{ TSVGPathTokenizer }
+
+constructor TSVGPathTokenizer.Create;
+begin
+ inherited Create;
+
+ FPointSeparator := DefaultFormatSettings;
+ FPointSeparator.DecimalSeparator := '.';
+ FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
+
+ Tokens := TSVGTokenList.Create;
+end;
+
+destructor TSVGPathTokenizer.Destroy;
+begin
+ Tokens.Free;
+
+ inherited Destroy;
+end;
+
+procedure TSVGPathTokenizer.AddToken(AStr: string);
+var
+ lToken: TSVGToken;
+begin
+ lToken := TSVGToken.Create;
+
+ if AStr = 'm' then lToken.TokenType := sttMoveTo
+ else if AStr = 'l' then lToken.TokenType := sttLineTo
+ else if AStr = 'c' then lToken.TokenType := sttBezierTo
+ else
+ begin
+ lToken.TokenType := sttFloatValue;
+ lToken.Value := StrToFloat(AStr, FPointSeparator);
+ lToken.Value := lToken.Value * FLOAT_MILIMETERS_PER_PIXEL;
+ end;
+
+ Tokens.Add(lToken);
+end;
+
+procedure TSVGPathTokenizer.TokenizePathString(AStr: string);
+const
+ Str_Space: Char = ' ';
+ Str_Comma: Char = ',';
+var
+ i: Integer;
+ lTmpStr: string;
+ lState: Integer;
+ lCurChar: Char;
+begin
+ lState := 0;
+
+ i := 1;
+ while i <= Length(AStr) do
+ begin
+ case lState of
+ 0: // Adding to the tmp string
+ begin
+ lCurChar := AStr[i];
+ if lCurChar = Str_Space then
+ begin
+ lState := 1;
+ AddToken(lTmpStr);
+ lTmpStr := '';
+ end
+ else if lCurChar = Str_Comma then
+ begin
+ AddToken(lTmpStr);
+ lTmpStr := '';
+ end
+ else
+ lTmpStr := lTmpStr + lCurChar;
+
+ Inc(i);
+ end;
+ 1: // Removing spaces
+ begin
+ if AStr[i] <> Str_Space then lState := 0
+ else Inc(i);
+ end;
+ end;
+ end;
+end;
+
+{ Example of a supported SVG image:
+
+
+
+
+
+}
+
+{ TvSVGVectorialReader }
+
+procedure TvSVGVectorialReader.ReadPathFromNode(APath: TDOMNode;
+ AData: TvVectorialDocument);
+var
+ lNodeName, lStyleStr, lDStr: WideString;
+ i: Integer;
+begin
+ for i := 0 to APath.Attributes.Length - 1 do
+ begin
+ lNodeName := APath.Attributes.Item[i].NodeName;
+ if lNodeName = 'style' then
+ lStyleStr := APath.Attributes.Item[i].NodeValue
+ else if lNodeName = 'd' then
+ lDStr := APath.Attributes.Item[i].NodeValue
+ end;
+
+ AData.StartPath();
+ ReadPathFromString(UTF8Encode(lDStr), AData);
+ AData.EndPath();
+end;
+
+procedure TvSVGVectorialReader.ReadPathFromString(AStr: string;
+ AData: TvVectorialDocument);
+var
+ i: Integer;
+ X, Y, CurX, CurY: Float;
+begin
+ FSVGPathTokenizer.Tokens.Clear;
+ FSVGPathTokenizer.TokenizePathString(AStr);
+ CurX := 0;
+ CurY := 0;
+
+ i := 0;
+ while i < FSVGPathTokenizer.Tokens.Count do
+ begin
+ if FSVGPathTokenizer.Tokens.Items[i].TokenType = sttMoveTo then
+ begin
+ CurX := FSVGPathTokenizer.Tokens.Items[i+1].Value;
+ CurY := FSVGPathTokenizer.Tokens.Items[i+2].Value;
+
+ AData.AddMoveToPath(CurX, CurY);
+
+ Inc(i, 3);
+ end
+ else if FSVGPathTokenizer.Tokens.Items[i].TokenType = sttLineTo then
+ begin
+ X := FSVGPathTokenizer.Tokens.Items[i+1].Value;
+ Y := FSVGPathTokenizer.Tokens.Items[i+2].Value;
+
+ // LineTo uses relative coordenates in SVG
+ CurX := CurX + X;
+ CurY := CurY + Y;
+
+ AData.AddLineToPath(CurX, CurY);
+
+ Inc(i, 3);
+ end
+ else
+ begin
+ Inc(i);
+ end;
+ end;
+end;
+
+constructor TvSVGVectorialReader.Create;
+begin
+ inherited Create;
+
+ FPointSeparator := DefaultFormatSettings;
+ FPointSeparator.DecimalSeparator := '.';
+ FPointSeparator.ThousandSeparator := '#';// disable the thousand separator
+
+ FSVGPathTokenizer := TSVGPathTokenizer.Create;
+end;
+
+destructor TvSVGVectorialReader.Destroy;
+begin
+ FSVGPathTokenizer.Free;
+
+ inherited Destroy;
+end;
+
+procedure TvSVGVectorialReader.ReadFromStream(AStream: TStream;
+ AData: TvVectorialDocument);
+var
+ Doc: TXMLDocument;
+ lFirstLayer, lCurNode: TDOMNode;
+begin
+ try
+ // Read in xml file from the stream
+ ReadXMLFile(Doc, AStream);
+
+ // Now process the elements inside the first layer
+ lFirstLayer := Doc.DocumentElement.FirstChild;
+ lCurNode := lFirstLayer.FirstChild;
+ while Assigned(lCurNode) do
+ begin
+ ReadPathFromNode(lCurNode, AData);
+ lCurNode := lCurNode.NextSibling;
+ end;
+ finally
+ // finally, free the document
+ Doc.Free;
+ end;
+end;
+
+initialization
+
+ RegisterVectorialReader(TvSVGVectorialReader, vfSVG);
+
+end.
+