diff --git a/applications/fpvviewer/fpvectorialsrc/avisocncgcodereader.pas b/applications/fpvviewer/fpvectorialsrc/avisocncgcodereader.pas new file mode 100644 index 000000000..4edbadb6e --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/avisocncgcodereader.pas @@ -0,0 +1,234 @@ +{ +Reads AvisoCNC G-Code + +License: The same modified LGPL as the Free Pascal RTL + See the file COPYING.modifiedLGPL for more details + +AUTHORS: Felipe Monteiro de Carvalho + Pedro Sol Pegorini L de Lima +} +unit avisocncgcodereader; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, + fpvectorial; + +type + + { Used by tcutils.SeparateString } + T10Strings = array[0..9] of shortstring; + + { TvAvisoCNCGCodeReader } + + TvAvisoCNCGCodeReader = class(TvCustomVectorialReader) + private + LastX, LastY, LastZ: Double; + function SeparateString(AString: string; ASeparator: Char): T10Strings; + procedure ReadString(AStr: string; AData: TvVectorialDocument); + function GetCoordinate(AStr: shortstring): Integer; + function GetCoordinateValue(AStr: shortstring): Double; + public + { General reading methods } + procedure ReadFromStrings(AStrings: TStrings; AData: TvVectorialDocument); override; + end; + +implementation + +const + { Coordinate constants } + + INT_COORDINATE_NONE = 0; + INT_COORDINATE_X = 1; + INT_COORDINATE_Y = 2; + INT_COORDINATE_Z = 3; + + { GCode constants } + + STR_GCODE_LINEAR_MOVE = 'G01'; + STR_GCODE_STEPPER_MOVE = 'S01'; + STR_GCODE_2DBEZIER_MOVE = 'B02'; + STR_GCODE_3DBEZIER_MOVE = 'B03'; + STR_GCODE_DRILL_UP = 'P01'; + STR_GCODE_DRILL_DOWN = 'P02'; + +{ TvAvisoCNCGCodeReader } + +{@@ + Reads a string and separates it in substring + using ASeparator to delimite them. + + Limits: + + Number of substrings: 10 (indexed 0 to 9) + Length of each substring: 255 (they are shortstrings) +} +function TvAvisoCNCGCodeReader.SeparateString(AString: string; ASeparator: Char): T10Strings; +var + i, CurrentPart: Integer; +begin + CurrentPart := 0; + + { Clears the result } + for i := 0 to 9 do Result[i] := ''; + + { Iterates througth the string, filling strings } + for i := 1 to Length(AString) do + begin + if Copy(AString, i, 1) = ASeparator then + begin + Inc(CurrentPart); + + { Verifies if the string capacity wasn't exceeded } + if CurrentPart > 9 then Exit; + end + else + Result[CurrentPart] := Result[CurrentPart] + Copy(AString, i, 1); + end; +end; + +procedure TvAvisoCNCGCodeReader.ReadString(AStr: string; + AData: TvVectorialDocument); +var + AParams: T10Strings; + DestX, DestY, DestZ: Double; + i: Integer; +begin + {$ifdef FPVECTORIALDEBUG} + WriteLn('TvAvisoCNCGCodeReader.ReadString ', AStr); + {$endif} + AParams := SeparateString(AStr, ' '); + + { + Format may be: + G01 X3 + G01 X3 Y4 + G01 X3 Y4 Z2 + } + if AParams[0] = STR_GCODE_DRILL_UP then + begin + AData.AddLineToPath(LastX, LastY, 0); + LastZ := 0; + end + else if AParams[0] = STR_GCODE_DRILL_DOWN then + begin + AData.AddLineToPath(LastX, LastY, 50); + LastZ := 50; + end + else if AParams[0] = STR_GCODE_LINEAR_MOVE then + begin + DestX := LastX; + DestY := LastY; + DestZ := LastZ; + + for i := 1 to 3 do + begin + case GetCoordinate(AParams[i]) of + INT_COORDINATE_X: DestX := GetCoordinateValue(AParams[i]); + INT_COORDINATE_Y: DestY := GetCoordinateValue(AParams[i]); + INT_COORDINATE_Z: DestZ := GetCoordinateValue(AParams[i]); + else + // error + end; + end; + + AData.AddLineToPath(DestX, DestY, DestZ); + + LastX := DestX; + LastY := DestY; + LastZ := DestZ; + end + else if AParams[0] = STR_GCODE_2DBEZIER_MOVE then + begin + AData.AddBezierToPath( + GetCoordinateValue(AParams[1]), + GetCoordinateValue(AParams[2]), + GetCoordinateValue(AParams[3]), + GetCoordinateValue(AParams[4]), + GetCoordinateValue(AParams[5]), + GetCoordinateValue(AParams[6]) + ); + + LastX := GetCoordinateValue(AParams[5]); + LastY := GetCoordinateValue(AParams[6]); + end + else if AParams[0] = STR_GCODE_3DBEZIER_MOVE then + begin + AData.AddBezierToPath( + GetCoordinateValue(AParams[1]), + GetCoordinateValue(AParams[2]), + GetCoordinateValue(AParams[3]), + GetCoordinateValue(AParams[4]), + GetCoordinateValue(AParams[5]), + GetCoordinateValue(AParams[6]), + GetCoordinateValue(AParams[7]), + GetCoordinateValue(AParams[8]), + GetCoordinateValue(AParams[9]) + ); + + LastX := GetCoordinateValue(AParams[7]); + LastY := GetCoordinateValue(AParams[8]); + LastZ := GetCoordinateValue(AParams[9]); + end; + {else + begin + Ignore any of these codes: + + STR_GCODE_STEPPER_MOVE + + and anything else + end;} +end; + +function TvAvisoCNCGCodeReader.GetCoordinate(AStr: shortstring): Integer; +begin + Result := INT_COORDINATE_NONE; + + if AStr = '' then Exit + else if AStr[1] = 'X' then Result := INT_COORDINATE_X + else if AStr[1] = 'Y' then Result := INT_COORDINATE_Y + else if AStr[1] = 'Z' then Result := INT_COORDINATE_Z; +end; + +function TvAvisoCNCGCodeReader.GetCoordinateValue(AStr: shortstring): Double; +begin + Result := 0.0; + + if Length(AStr) <= 1 then Exit; + + Result := StrToFloat(Copy(AStr, 2, Length(AStr) - 1)); +end; + +{@@ + The information of each separate path is lost in G-Code files + Only one path uniting all of them is created when reading G-Code +} +procedure TvAvisoCNCGCodeReader.ReadFromStrings(AStrings: TStrings; + AData: TvVectorialDocument); +var + i: Integer; +begin + {$ifdef FPVECTORIALDEBUG} + WriteLn('TvAvisoCNCGCodeReader.ReadFromStrings AStrings = ', PtrInt(AStrings), ' AData = ', PtrInt(AData)); + {$endif} + + AData.StartPath(0, 0); + + for i := 0 to AStrings.Count - 1 do + ReadString(AStrings.Strings[i], AData); + + {$ifdef FPVECTORIALDEBUG} + WriteLn('AData.EndPath'); + {$endif} + AData.EndPath(); +end; + +initialization + + RegisterVectorialReader(TvAvisoCNCGCodeReader, vfGCodeAvisoCNCPrototipoV5); + +end. + diff --git a/applications/fpvviewer/fpvectorialsrc/avisocncgcodewriter.pas b/applications/fpvviewer/fpvectorialsrc/avisocncgcodewriter.pas new file mode 100644 index 000000000..cc31e608a --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/avisocncgcodewriter.pas @@ -0,0 +1,87 @@ +{ +Writes AvisoCNC G-Code + +License: The same modified LGPL as the Free Pascal RTL + See the file COPYING.modifiedLGPL for more details + +AUTHORS: Felipe Monteiro de Carvalho + Pedro Sol Pegorini L de Lima +} +unit avisocncgcodewriter; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, + fpvectorial; + +type + { TvAvisoCNCGCodeWriter } + + TvAvisoCNCGCodeWriter = class(TvCustomVectorialWriter) + public + { General reading methods } + procedure WriteToStrings(AStrings: TStrings; AData: TvVectorialDocument); override; + end; + +implementation + +{ TvGCodeVectorialWriter } + +procedure TvAvisoCNCGCodeWriter.WriteToStrings(AStrings: TStrings; + AData: TvVectorialDocument); +var + i, j: Integer; + Str: string; + APath: TPath; +begin + AStrings.Clear; + + AStrings.Add('M216 // Ligar monitor de carga'); + AStrings.Add('G28 // Ir rapidamente para posição inicial'); + AStrings.Add('G00'); + + // itera por todos os itens + for i := 0 to AData.GetPathCount - 1 do + begin + APath := AData.GetPath(i); + + // levanta a broca + AStrings.Add('P01 // Sobe a cabeça de gravação'); + // vai para o ponto inicial + AStrings.Add(Format('G01 X%f Y%f', + [APath.Points[0].X, APath.Points[0].Y])); + AStrings.Add('P02 // Abaixa a cabeça de gravação'); + + for j := 1 to APath.Len - 1 do + begin + case APath.Points[j].SegmentType of + st2DLine: AStrings.Add(Format('G01 X%f Y%f', + [APath.Points[j].X, APath.Points[j].Y])); + st3DLine: AStrings.Add(Format('G01 X%f Y%f Z%f', + [APath.Points[j].X, APath.Points[j].Y, APath.Points[j].Z])); + st2DBezier: AStrings.Add(Format('B02 X%f Y%f X%f Y%f X%f Y%f', + [APath.Points[j].X2, APath.Points[j].Y2, + APath.Points[j].X3, APath.Points[j].Y3, + APath.Points[j].X, APath.Points[j].Y])); + st3DBezier: AStrings.Add(Format('B03 X%f Y%f Z%f X%f Y%f Z%f X%f Y%f Z%f', + [APath.Points[j].X2, APath.Points[j].Y2, APath.Points[j].Z2, + APath.Points[j].X3, APath.Points[j].Y3, APath.Points[j].Z3, + APath.Points[j].X, APath.Points[j].Y, APath.Points[j].Z])); + end; + end; + end; + + AStrings.Add('P01 // Sobe a cabeça de gravação'); + AStrings.Add('M30 // Parar o programa e retornar para posição inicial'); + AStrings.Add('M215 // Desligar monitor de carga'); +end; + +initialization + + RegisterVectorialWriter(TvAvisoCNCGCodeWriter, vfGCodeAvisoCNCPrototipoV5); + +end. + diff --git a/applications/fpvviewer/fpvectorialsrc/avisozlib.pas b/applications/fpvviewer/fpvectorialsrc/avisozlib.pas new file mode 100644 index 000000000..4bef95bbf --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/avisozlib.pas @@ -0,0 +1,74 @@ +unit avisozlib; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, paszlib; + +type + Decode = class + public + procedure CHECK_ERR(err: Integer; msg: String); + procedure EXIT_ERR(const msg: String); + function test_inflate(compr: Pointer; comprLen : LongInt; + uncompr: Pointer; uncomprLen : LongInt): PChar; + constructor Create(); + end; + +implementation + +procedure Decode.CHECK_ERR(err: Integer; msg: String); +begin + if err <> Z_OK then + begin + raise Exception.Create('ERROR: ' + msg); + Halt(1); + end; +end; + +procedure Decode.EXIT_ERR(const msg: String); +begin + raise Exception.Create('ERROR: ' + msg); + Halt(1); +end; + +function Decode.test_inflate(compr: Pointer; comprLen : LongInt; + uncompr: Pointer; uncomprLen : LongInt): PChar; +var err: Integer; + d_stream: TZStream; // decompression stream +begin + StrCopy(PChar(uncompr), 'garbage'); + + d_stream.next_in := compr; + d_stream.avail_in := 0; + d_stream.next_out := uncompr; + + err := inflateInit(d_stream); + CHECK_ERR(err, 'inflateInit'); + + while (d_stream.total_out < uncomprLen) and + (d_stream.total_in < comprLen) do + begin + d_stream.avail_out := 1; // force small buffers + d_stream.avail_in := 1; + err := inflate(d_stream, Z_NO_FLUSH); + if err = Z_STREAM_END then + break; + CHECK_ERR(err, 'inflate'); + end; + + err := inflateEnd(d_stream); + CHECK_ERR(err, 'inflateEnd'); + + Result:=PChar(uncompr); +end; + +constructor Decode.Create(); +begin + inherited Create; +end; + +end. + diff --git a/applications/fpvviewer/fpvectorialsrc/cdrvectorialreader.pas b/applications/fpvviewer/fpvectorialsrc/cdrvectorialreader.pas new file mode 100644 index 000000000..d438dc0bb --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/cdrvectorialreader.pas @@ -0,0 +1,180 @@ +{ +cdrvectorialreader.pas + +Reads a Corel Draw vectorial file + +CDR file format specification obtained from: + +ADOBE SYSTEMS INCORPORATED. PDF Reference: Adobe® +Portable Document Format. San Jose, 2006. (Sixth edition). + +AUTHORS: Felipe Monteiro de Carvalho + +License: The same modified LGPL as the Free Pascal RTL + See the file COPYING.modifiedLGPL for more details +} +unit cdrvectorialreader; + +{$ifdef fpc} + {$mode delphi} +{$endif} + +interface + +uses + Classes, SysUtils, + pdfvrlexico, pdfvrsintatico, pdfvrsemantico, avisozlib, + fpvectorial; + +type + + TCDRChunk = class + Name: array[0..3] of Char; + Size: Cardinal; + ChildChunks: TFPList; + end; + + TCDRChunkClass = class of TCDRChunk; + + TvCDRInternalData = TCDRChunk; + + TCDRChunkVRSN = class(TCDRChunk) + VersionStr: string; + VersionNum: Integer; + end; + + { TvCDRVectorialReader } + + TvCDRVectorialReader = class(TvCustomVectorialReader) + private + procedure ReadVersionChunk(AStream: TStream; var AData: TCDRChunk); + function AddNewChunk(var AData: TCDRChunk; AClass: TCDRChunkClass): TCDRChunk; + public + { General reading methods } + procedure ReadFromStream(AStream: TStream; AData: TvVectorialDocument); override; + { File format exploring methods } + procedure ExploreFromFile(AFilename: string; out AData: TvCDRInternalData); + procedure ExploreFromStream(AStream: TStream; out AData: TvCDRInternalData); + end; + +implementation + +{ TvPDFVectorialReader } + +procedure TvCDRVectorialReader.ReadVersionChunk(AStream: TStream; + var AData: TCDRChunk); +var + lDWord: DWord; + lChunk: TCDRChunkVRSN absolute AData; + lVerBytes: array[0..1] of Byte; +begin + // Read the Chunk name + lDWord := AStream.ReadDWord(); + + // Read the Chunk size + lDWord := AStream.ReadDWord(); + + // Read the version + AStream.Read(lVerBytes, 2); + + if (lVerBytes[0] = $BC) and (lVerBytes[1] = $02) then + begin + lChunk.VersionNum := 7; + lChunk.VersionStr := 'CorelDraw 7'; + end + else if (lVerBytes[0] = $20) and (lVerBytes[1] = $03) then + begin + lChunk.VersionNum := 8; + lChunk.VersionStr := 'CorelDraw 8'; + end + else if (lVerBytes[0] = $21) and (lVerBytes[1] = $03) then + begin + lChunk.VersionNum := 8; + lChunk.VersionStr := 'CorelDraw 8bidi'; + end + else if (lVerBytes[0] = $84) and (lVerBytes[1] = $03) then + begin + lChunk.VersionNum := 9; + lChunk.VersionStr := 'CorelDraw 9'; + end + else if (lVerBytes[0] = $E8) and (lVerBytes[1] = $03) then + begin + lChunk.VersionNum := 10; + lChunk.VersionStr := 'CorelDraw 10'; + end + else if (lVerBytes[0] = $4C) and (lVerBytes[1] = $04) then + begin + lChunk.VersionNum := 11; + lChunk.VersionStr := 'CorelDraw 11'; + end + else if (lVerBytes[0] = $B0) and (lVerBytes[1] = $04) then + begin + lChunk.VersionNum := 12; + lChunk.VersionStr := 'CorelDraw 12'; + end + else if (lVerBytes[0] = $14) and (lVerBytes[1] = $05) then + begin + lChunk.VersionNum := 13; + lChunk.VersionStr := 'CorelDraw X3'; + end; +end; + +function TvCDRVectorialReader.AddNewChunk(var AData: TCDRChunk; AClass: TCDRChunkClass): TCDRChunk; +begin + if AData.ChildChunks = nil then AData.ChildChunks := TFPList.Create; + + Result := AClass.Create; + + AData.ChildChunks.Add(Result); +end; + +procedure TvCDRVectorialReader.ReadFromStream(AStream: TStream; + AData: TvVectorialDocument); +begin +end; + +procedure TvCDRVectorialReader.ExploreFromFile(AFilename: string; + out AData: TvCDRInternalData); +var + FileStream: TFileStream; +begin + FileStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone); + try + ExploreFromStream(FileStream, AData); + finally + FileStream.Free; + end; +end; + +procedure TvCDRVectorialReader.ExploreFromStream(AStream: TStream; + out AData: TvCDRInternalData); +var + lRIFF: array[0..3] of Char; + lDocSize, lDWord: Cardinal; + lChild: TCDRChunk; +begin + // Create the data object + AData := TCDRChunk.Create; + + // All CorelDraw files starts with "RIFF" + AStream.Read(lRIFF, 4); + if lRIFF <> 'RIFF' then + raise Exception.Create('[TvCDRVectorialReader.ExploreFromStream] The Corel Draw RIFF file marker wasn''t found.'); + + // And then 4 bytes for the document size + lDocSize := AStream.ReadDWord(); + + // And mroe 4 bytes of other stuff + lDWord := AStream.ReadDWord(); + + // Now comes the version + lChild := AddNewChunk(AData, TCDRChunkVRSN); + ReadVersionChunk(AStream, lChild); +end; + +initialization + + RegisterVectorialReader(TvCDRVectorialReader, vfCorelDrawCDR); + +end. + diff --git a/applications/fpvviewer/fpvectorialsrc/dxfvectorialreader.pas b/applications/fpvviewer/fpvectorialsrc/dxfvectorialreader.pas new file mode 100644 index 000000000..a983332b6 --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/dxfvectorialreader.pas @@ -0,0 +1,300 @@ +{ +Reads DXF files + +License: The same modified LGPL as the Free Pascal RTL + See the file COPYING.modifiedLGPL for more details + +AUTHORS: Felipe Monteiro de Carvalho + +DXF is composed by records written in ASCII with the following structure: + +0 +SECTION +section_number +SECTION_NAME + +0 +ENDSEC +0 + +after all section end there is: + +EOF + +} +unit dxfvectorialreader; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, + fpvectorial; + +type + + { Used by tcutils.SeparateString } + T10Strings = array[0..9] of shortstring; + + { TvDXFVectorialReader } + + TvDXFVectorialReader = class(TvCustomVectorialReader) + private + LineStartX, LineStartY, LineStartZ: Double; + LineEndX, LineEndY, LineEndZ: Double; + function SeparateString(AString: string; ASeparator: Char): T10Strings; + function ReadSection(AStrings: TStrings; var AIndex: Integer; AData: TvVectorialDocument): Boolean; + function ReadENTITIES(AStrings: TStrings; var AIndex: Integer; AData: TvVectorialDocument): Boolean; + function ReadENTITIES_LINE(AStrings: TStrings; var AIndex: Integer; AData: TvVectorialDocument): Boolean; + function GetCoordinate(AStr: shortstring): Integer; + function GetCoordinateValue(AStr: shortstring): Double; + public + { General reading methods } + procedure ReadFromStrings(AStrings: TStrings; AData: TvVectorialDocument); override; + end; + +implementation + +{$define FPVECTORIALDEBUG} + +const + { Coordinate constants } + + INT_COORDINATE_NONE = 0; + INT_COORDINATE_X = 1; + INT_COORDINATE_Y = 2; + INT_COORDINATE_Z = 3; + + { GCode constants } + + STR_GCODE_LINEAR_MOVE = 'G01'; + STR_GCODE_STEPPER_MOVE = 'S01'; + STR_GCODE_2DBEZIER_MOVE = 'B02'; + STR_GCODE_3DBEZIER_MOVE = 'B03'; + STR_GCODE_DRILL_UP = 'P01'; + STR_GCODE_DRILL_DOWN = 'P02'; + +{ TvAvisoCNCGCodeReader } + +{@@ + Reads a string and separates it in substring + using ASeparator to delimite them. + + Limits: + + Number of substrings: 10 (indexed 0 to 9) + Length of each substring: 255 (they are shortstrings) +} +function TvDXFVectorialReader.SeparateString(AString: string; ASeparator: Char): T10Strings; +var + i, CurrentPart: Integer; +begin + CurrentPart := 0; + + { Clears the result } + for i := 0 to 9 do Result[i] := ''; + + { Iterates througth the string, filling strings } + for i := 1 to Length(AString) do + begin + if Copy(AString, i, 1) = ASeparator then + begin + Inc(CurrentPart); + + { Verifies if the string capacity wasn't exceeded } + if CurrentPart > 9 then Exit; + end + else + Result[CurrentPart] := Result[CurrentPart] + Copy(AString, i, 1); + end; +end; + +{@@ + returns If an end of file marker was found +} +function TvDXFVectorialReader.ReadSection( + AStrings: TStrings; var AIndex: Integer; AData: TvVectorialDocument): Boolean; +var + DestX, DestY, DestZ: Double; + StrSectionNum, StrSectionName: string; + IntSectionNum, i: Integer; +begin + Result := False; + + // Check if there is minimal space for a section + if AIndex+5 > AStrings.Count then + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn('Not enough space for a section'); + {$endif} + Exit(True); + end; + + // Check of the EOF marker + StrSectionName := Trim(AStrings.Strings[AIndex+1]); + if StrSectionName = 'EOF' then + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn('EOF found'); + {$endif} + Exit(True); + end; + + // Now read and process the section name + StrSectionNum := AStrings.Strings[AIndex+2]; + IntSectionNum := StrToInt(Trim(StrSectionNum)); + StrSectionName := AStrings.Strings[AIndex+3]; + + {$ifdef FPVECTORIALDEBUG} + WriteLn('TvDXFVectorialReader.ReadSection ' + StrSectionName); + {$endif} + + if (StrSectionName = 'HEADER') or + (StrSectionName = 'CLASSES') or + (StrSectionName = 'TABLES') or + (StrSectionName = 'BLOCKS') or + (StrSectionName = 'OBJECTS') or + (StrSectionName = 'THUMBNAILIMAGE') then + begin + // We don't care about contents here, so let's just find the last section and get out of here. + for i := AIndex + 4 to AStrings.Count - 1 do + begin + if AStrings.Strings[i] = 'ENDSEC' then + begin + AIndex := i + 1; + Exit; + end; + end; + // If we reached here, the section in incomplete + raise Exception.Create('TvDXFVectorialReader.ReadSection: ENDSEC was not found in the SECTION'); + end + else if StrSectionName = 'ENTITIES' then + begin + AIndex := AIndex + 4; + while not ReadENTITIES(AStrings, AIndex, AData) do ; + end; + {else + begin + end;} +end; + +function TvDXFVectorialReader.ReadENTITIES(AStrings: TStrings; + var AIndex: Integer; AData: TvVectorialDocument): Boolean; +var + StrSectionNum, StrSectionName: string; + IntSectionNum, i: Integer; +begin + Result := False; + + // Now read and process the item name + StrSectionName := AStrings.Strings[AIndex+1]; + + {$ifdef FPVECTORIALDEBUG} + WriteLn('TvDXFVectorialReader.ReadENTITIES ', StrSectionName); + {$endif} + + if StrSectionName = 'ENDSEC' then + begin + Inc(AIndex, 2); + Exit(True); + end + else if StrSectionName = 'LINE' then + begin + // Initial values + LineStartX := 0; + LineStartY := 0; + LineStartZ := 0; + LineEndX := 0; + LineEndY := 0; + LineEndZ := 0; + + // Read the data of the line + Inc(AIndex, 2); + while not ReadENTITIES_LINE(AStrings, AIndex, AData) do ; + + // And now write it + {$ifdef FPVECTORIALDEBUG} + WriteLn(Format('Adding Line from %f,%f to %f,%f', [LineStartX, LineStartY, LineEndX, LineEndY])); + {$endif} + AData.StartPath(LineStartX, LineStartY); + AData.AddLineToPath(LineEndX, LineEndY); + AData.EndPath(); + end; +end; + +function TvDXFVectorialReader.ReadENTITIES_LINE(AStrings: TStrings; + var AIndex: Integer; AData: TvVectorialDocument): Boolean; +var + StrSectionNum, StrSectionValue: string; + IntSectionNum: Integer; + FloatSectionValue: double; +begin + Result := False; + + // Now read and process the item name + StrSectionNum := AStrings.Strings[AIndex]; + StrSectionValue := AStrings.Strings[AIndex+1]; + + if (StrSectionValue = 'LINE') or + (StrSectionValue = 'ENDSEC') then + begin + Exit(True); + end + else + begin + Inc(AIndex, 2); + + IntSectionNum := StrToInt(Trim(StrSectionNum)); + FloatSectionValue := StrToFloat(Trim(StrSectionValue)); + + case IntSectionNum of + 10: LineStartX := FloatSectionValue; + 20: LineStartY := FloatSectionValue; + 30: LineStartZ := FloatSectionValue; + 11: LineEndX := FloatSectionValue; + 21: LineEndY := FloatSectionValue; + 31: LineEndZ := FloatSectionValue; + end; + end; +end; + +function TvDXFVectorialReader.GetCoordinate(AStr: shortstring): Integer; +begin + Result := INT_COORDINATE_NONE; + + if AStr = '' then Exit + else if AStr[1] = 'X' then Result := INT_COORDINATE_X + else if AStr[1] = 'Y' then Result := INT_COORDINATE_Y + else if AStr[1] = 'Z' then Result := INT_COORDINATE_Z; +end; + +function TvDXFVectorialReader.GetCoordinateValue(AStr: shortstring): Double; +begin + Result := 0.0; + + if Length(AStr) <= 1 then Exit; + + Result := StrToFloat(Copy(AStr, 2, Length(AStr) - 1)); +end; + +{@@ + The information of each separate path is lost in G-Code files + Only one path uniting all of them is created when reading G-Code +} +procedure TvDXFVectorialReader.ReadFromStrings(AStrings: TStrings; + AData: TvVectorialDocument); +var + i: Integer; +begin + i := 0; + while i < AStrings.Count - 1 do + if ReadSection(AStrings, i, AData) then Break; +end; + +initialization + + RegisterVectorialReader(TvDXFVectorialReader, vfDXF); + +end. + diff --git a/applications/fpvviewer/fpvectorialsrc/fpvectbuildunit.pas b/applications/fpvviewer/fpvectorialsrc/fpvectbuildunit.pas new file mode 100644 index 000000000..ea47154ac --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/fpvectbuildunit.pas @@ -0,0 +1,10 @@ +unit fpvectbuildunit; + +interface +Uses + avisocncgcodereader,avisocncgcodewriter,avisozlib,fpvectorial, + fpvtocanvas,pdfvectorialreader,pdfvrlexico,pdfvrsemantico,pdfvrsintatico, + svgvectorialwriter,cdrvectorialreader; + +implementation +end. \ No newline at end of file diff --git a/applications/fpvviewer/fpvectorialsrc/fpvectorial.pas b/applications/fpvviewer/fpvectorialsrc/fpvectorial.pas new file mode 100644 index 000000000..9fd35acc0 --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/fpvectorial.pas @@ -0,0 +1,888 @@ +{ +fpvectorial.pas + +Vector graphics document + +License: The same modified LGPL as the Free Pascal RTL + See the file COPYING.modifiedLGPL for more details + +AUTHORS: Felipe Monteiro de Carvalho + Pedro Sol Pegorini L de Lima +} +unit fpvectorial; + +{$ifdef fpc} + {$mode delphi} +{$endif} + +interface + +uses + Classes, SysUtils; + +type + TvVectorialFormat = ( + { Multi-purpose document formats } + vfPDF, vfPostScript, vfSVG, vfCorelDrawCDR, vfWindowsMetafileWMF, + { CAD formats } + vfDXF, + { GCode formats } + vfGCodeAvisoCNCPrototipoV5, vfGCodeAvisoCNCPrototipoV6); + +const + { Default extensions } + { Multi-purpose document formats } + STR_PDF_EXTENSION = '.pdf'; + STR_POSTSCRIPT_EXTENSION = '.ps'; + STR_SVG_EXTENSION = '.svg'; + STR_CORELDRAW_EXTENSION = '.cdr'; + STR_WINMETAFILE_EXTENSION = '.wmf'; + +type + TSegmentType = ( + st2DLine, st2DBezier, + st3DLine, st3DBezier, stMoveTo); + + {@@ + The coordinates in fpvectorial are given in millimiters and + the starting point is in the bottom-left corner of the document. + The X grows to the right and the Y grows to the top. + } + { TPathSegment } + + TPathSegment = class + public + SegmentType: TSegmentType; + // Fields for linking the list + Previous: TPathSegment; + Next: TPathSegment; + end; + + {@@ + In a 2D segment, the X and Y coordinates represent usually the + final point of the segment, being that it starts where the previous + segment ends. The exception is for the first segment of all, which simply + holds the starting point for the drawing and should always be of the type + stMoveTo. + } + T2DSegment = class(TPathSegment) + public + X, Y: Double; + end; + + {@@ + In Bezier segments, we remain using the X and Y coordinates for the ending point. + The starting point is where the previous segment ended, so that the intermediary + bezier control points are [X2, Y2] and [X3, Y3]. + } + T2DBezierSegment = class(T2DSegment) + public + X2, Y2: Double; + X3, Y3: Double; + end; + + T3DSegment = class(TPathSegment) + public + {@@ + Coordinates of the end of the segment. + For the first segment, this is the starting point. + } + X, Y, Z: Double; + end; + + T3DBezierSegment = class(T3DSegment) + public + X2, Y2, Z2: Double; + X3, Y3, Z3: Double; + end; + + TPath = class + Len: Integer; + Points: TPathSegment; // Beginning of the double-linked list + PointsEnd: TPathSegment; // End of the double-linked list + CurPoint: TPathSegment; // Used in PrepareForSequentialReading and Next + procedure Assign(APath: TPath); + function Count(): TPathSegment; + procedure PrepareForSequentialReading; + function Next(): TPathSegment; + end; + + {@@ + TvText represents a text in memory. + + At the moment fonts are unsupported, only simple texts + up to 255 chars are supported. + } + TvText = class + public + X, Y, Z: Double; // Z is ignored in 2D formats + FontSize: integer; + FontName: utf8string; + Value: utf8string; + end; + +type + + TvCustomVectorialWriter = class; + TvCustomVectorialReader = class; + + { TvVectorialDocument } + + TvVectorialDocument = class + private + FPaths: TFPList; + FTexts: TFPList; + FTmpPath: TPath; + FTmpText: TvText; + procedure RemoveCallback(data, arg: pointer); + function CreateVectorialWriter(AFormat: TvVectorialFormat): TvCustomVectorialWriter; + function CreateVectorialReader(AFormat: TvVectorialFormat): TvCustomVectorialReader; + procedure ClearTmpPath(); + procedure AppendSegmentToTmpPath(ASegment: TPathSegment); + public + Name: string; + Width, Height: Double; // in millimeters + { Base methods } + constructor Create; + destructor Destroy; override; + 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 ReadFromStream(AStream: TStream; AFormat: TvVectorialFormat); + procedure ReadFromStrings(AStrings: TStrings; AFormat: TvVectorialFormat); + class function GetFormatFromExtension(AFileName: string): TvVectorialFormat; + function GetDetailedFileFormat(): string; + { Data reading methods } + function GetPath(ANum: Cardinal): TPath; + function GetPathCount: Integer; + function GetText(ANum: Cardinal): TvText; + function GetTextCount: Integer; + { Data removing methods } + procedure Clear; + procedure RemoveAllPaths; + procedure RemoveAllTexts; + { Data writing methods } + procedure AddPath(APath: TPath); + procedure StartPath(AX, AY: Double); + procedure AddLineToPath(AX, AY: Double); overload; + procedure AddLineToPath(AX, AY, AZ: Double); overload; + procedure AddBezierToPath(AX1, AY1, AX2, AY2, AX3, AY3: Double); overload; + procedure AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, AX3, AY3, AZ3: Double); overload; + procedure EndPath(); + procedure AddText(AX, AY, AZ: Double; FontName: string; FontSize: integer; AText: utf8string); overload; + procedure AddText(AX, AY, AZ: Double; AStr: utf8string); overload; + { properties } + property PathCount: Integer read GetPathCount; + property Paths[Index: Cardinal]: TPath read GetPath; + end; + + {@@ TvVectorialReader class reference type } + + TvVectorialReaderClass = class of TvCustomVectorialReader; + + { TvCustomVectorialReader } + + TvCustomVectorialReader = class + public + { General reading methods } + procedure ReadFromFile(AFileName: string; AData: TvVectorialDocument); virtual; + procedure ReadFromStream(AStream: TStream; AData: TvVectorialDocument); virtual; + procedure ReadFromStrings(AStrings: TStrings; AData: TvVectorialDocument); virtual; + end; + + {@@ TvVectorialWriter class reference type } + + TvVectorialWriterClass = class of TvCustomVectorialWriter; + + {@@ TvCustomVectorialWriter } + + { TvCustomVectorialWriter } + + TvCustomVectorialWriter = class + public + { General writing methods } + procedure WriteToFile(AFileName: string; AData: TvVectorialDocument); virtual; + procedure WriteToStream(AStream: TStream; AData: TvVectorialDocument); virtual; + procedure WriteToStrings(AStrings: TStrings; AData: TvVectorialDocument); virtual; + end; + + {@@ List of registered formats } + + TvVectorialFormatData = record + ReaderClass: TvVectorialReaderClass; + WriterClass: TvVectorialWriterClass; + ReaderRegistered: Boolean; + WriterRegistered: Boolean; + Format: TvVectorialFormat; + end; + +var + GvVectorialFormats: array of TvVectorialFormatData; + +procedure RegisterVectorialReader( + AReaderClass: TvVectorialReaderClass; + AFormat: TvVectorialFormat); +procedure RegisterVectorialWriter( + AWriterClass: TvVectorialWriterClass; + AFormat: TvVectorialFormat); + +implementation + +const + Str_Error_Nil_Path = ' The program attempted to add a segment before creating a path'; + +{@@ + Registers a new reader for a format +} +procedure RegisterVectorialReader( + AReaderClass: TvVectorialReaderClass; + AFormat: TvVectorialFormat); +var + i, len: Integer; + FormatInTheList: Boolean; +begin + len := Length(GvVectorialFormats); + FormatInTheList := False; + + { First search for the format in the list } + for i := 0 to len - 1 do + begin + if GvVectorialFormats[i].Format = AFormat then + begin + if GvVectorialFormats[i].ReaderRegistered then + raise Exception.Create('RegisterVectorialReader: Reader class for format ' {+ AFormat} + ' already registered.'); + + GvVectorialFormats[i].ReaderRegistered := True; + GvVectorialFormats[i].ReaderClass := AReaderClass; + + FormatInTheList := True; + Break; + end; + end; + + { If not already in the list, then add it } + if not FormatInTheList then + begin + SetLength(GvVectorialFormats, len + 1); + + GvVectorialFormats[len].ReaderClass := AReaderClass; + GvVectorialFormats[len].WriterClass := nil; + GvVectorialFormats[len].ReaderRegistered := True; + GvVectorialFormats[len].WriterRegistered := False; + GvVectorialFormats[len].Format := AFormat; + end; +end; + +{@@ + Registers a new writer for a format +} +procedure RegisterVectorialWriter( + AWriterClass: TvVectorialWriterClass; + AFormat: TvVectorialFormat); +var + i, len: Integer; + FormatInTheList: Boolean; +begin + len := Length(GvVectorialFormats); + FormatInTheList := False; + + { First search for the format in the list } + for i := 0 to len - 1 do + begin + if GvVectorialFormats[i].Format = AFormat then + begin + if GvVectorialFormats[i].WriterRegistered then + raise Exception.Create('RegisterVectorialWriter: Writer class for format ' + {AFormat +} ' already registered.'); + + GvVectorialFormats[i].WriterRegistered := True; + GvVectorialFormats[i].WriterClass := AWriterClass; + + FormatInTheList := True; + Break; + end; + end; + + { If not already in the list, then add it } + if not FormatInTheList then + begin + SetLength(GvVectorialFormats, len + 1); + + GvVectorialFormats[len].ReaderClass := nil; + GvVectorialFormats[len].WriterClass := AWriterClass; + GvVectorialFormats[len].ReaderRegistered := False; + GvVectorialFormats[len].WriterRegistered := True; + GvVectorialFormats[len].Format := AFormat; + end; +end; + +{ TsWorksheet } + +{@@ + Helper method for clearing the records in a spreadsheet. +} +procedure TvVectorialDocument.RemoveCallback(data, arg: pointer); +begin +{ if data <> nil then + begin + ldata := PObject(data); + ldata^.Free; + end;} +end; + +{@@ + Constructor. +} +constructor TvVectorialDocument.Create; +begin + inherited Create; + + FPaths := TFPList.Create; + FTexts := TFPList.Create; + FTmpPath := TPath.Create; +end; + +{@@ + Destructor. +} +destructor TvVectorialDocument.Destroy; +begin + Clear; + + FPaths.Free; + FTexts.Free; + + inherited Destroy; +end; + +{@@ + Clears the list of Vectors and releases their memory. +} +procedure TvVectorialDocument.RemoveAllPaths; +begin +// FPaths.ForEachCall(RemoveCallback, nil); + FPaths.Clear; +end; + +procedure TvVectorialDocument.RemoveAllTexts; +begin +// FTexts.ForEachCall(RemoveCallback, nil); + FTexts.Clear; +end; + +procedure TvVectorialDocument.AddPath(APath: TPath); +var + lPath: TPath; + Len: Integer; +begin + lPath := TPath.Create; + lPath.Assign(APath); + FPaths.Add(Pointer(lPath)); + //WriteLn(':>TvVectorialDocument.AddPath 1 Len = ', Len); + //WriteLn(':>TvVectorialDocument.AddPath 2'); + //WriteLn(':>TvVectorialDocument.AddPath 3'); + //WriteLn(':>TvVectorialDocument.AddPath 4'); +end; + +{@@ + Starts writing a Path in multiple steps. + Should be followed by zero or more calls to AddPointToPath + and by a call to EndPath to effectively add the data. + + @see StartPath, AddPointToPath +} +procedure TvVectorialDocument.StartPath(AX, AY: Double); +var + segment: T2DSegment; +begin + ClearTmpPath(); + + FTmpPath.Len := 1; + segment := T2DSegment.Create; + segment.SegmentType := stMoveTo; + segment.X := AX; + segment.Y := AY; + + FTmpPath.Points := segment; + FTmpPath.PointsEnd := segment; +end; + +{@@ + Adds one more point to the end of a Path being + writing in multiple steps. + + Does nothing if not called between StartPath and EndPath. + + Can be called multiple times to add multiple points. + + @see StartPath, EndPath +} +procedure TvVectorialDocument.AddLineToPath(AX, AY: Double); +var + segment: T2DSegment; +begin + segment := T2DSegment.Create; + segment.SegmentType := st2DLine; + segment.X := AX; + segment.Y := AY; + + AppendSegmentToTmpPath(segment); +end; + +procedure TvVectorialDocument.AddLineToPath(AX, AY, AZ: Double); +var + segment: T3DSegment; +begin + segment := T3DSegment.Create; + segment.SegmentType := st3DLine; + segment.X := AX; + segment.Y := AY; + segment.Z := AZ; + + AppendSegmentToTmpPath(segment); +end; + +{@@ + Adds a bezier element to the path. It starts where the previous element ended + and it goes throw the control points [AX1, AY1] and [AX2, AY2] and ends + in [AX3, AY3]. +} +procedure TvVectorialDocument.AddBezierToPath(AX1, AY1, AX2, AY2, AX3, + AY3: Double); +var + segment: T2DBezierSegment; +begin + segment := T2DBezierSegment.Create; + segment.SegmentType := st2DBezier; + segment.X := AX3; + segment.Y := AY3; + segment.X2 := AX1; + segment.Y2 := AY1; + segment.X3 := AX2; + segment.Y3 := AY2; + + AppendSegmentToTmpPath(segment); +end; + +procedure TvVectorialDocument.AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, + AX3, AY3, AZ3: Double); +var + segment: T3DBezierSegment; +begin + segment := T3DBezierSegment.Create; + segment.SegmentType := st3DBezier; + segment.X := AX3; + segment.Y := AY3; + segment.Z := AZ3; + segment.X2 := AX1; + segment.Y2 := AY1; + segment.Z2 := AZ1; + segment.X3 := AX2; + segment.Y3 := AY2; + segment.Z3 := AZ2; + + AppendSegmentToTmpPath(segment); +end; + +{@@ + Finishes writing a Path, which was created in multiple + steps using StartPath and AddPointToPath, + to the document. + + Does nothing if there wasn't a previous correspondent call to + StartPath. + + @see StartPath, AddPointToPath +} +procedure TvVectorialDocument.EndPath(); +begin + if FTmPPath.Len = 0 then Exit; + AddPath(FTmPPath); + ClearTmpPath(); +end; + +procedure TvVectorialDocument.AddText(AX, AY, AZ: Double; FontName: string; FontSize: integer; AText: utf8string); +var + lText: TvText; +begin + lText := TvText.Create; + lText.Value := AText; + lText.X := AX; + lText.Y := AY; + lText.Z := AZ; + lText.FontName := FontName; + lText.FontSize := FontSize; + FTexts.Add(lText); +end; + +procedure TvVectorialDocument.AddText(AX, AY, AZ: Double; AStr: utf8string); +begin + AddText(AX, AY, AZ, '', 10, AStr); +end; + +{@@ + Convenience method which creates the correct + writer object for a given vector graphics document format. +} +function TvVectorialDocument.CreateVectorialWriter(AFormat: TvVectorialFormat): TvCustomVectorialWriter; +var + i: Integer; +begin + Result := nil; + + for i := 0 to Length(GvVectorialFormats) - 1 do + if GvVectorialFormats[i].Format = AFormat then + begin + Result := GvVectorialFormats[i].WriterClass.Create; + + Break; + end; + + if Result = nil then raise Exception.Create('Unsuported vector graphics format.'); +end; + +{@@ + Convenience method which creates the correct + reader object for a given vector graphics document format. +} +function TvVectorialDocument.CreateVectorialReader(AFormat: TvVectorialFormat): TvCustomVectorialReader; +var + i: Integer; +begin + Result := nil; + + for i := 0 to Length(GvVectorialFormats) - 1 do + if GvVectorialFormats[i].Format = AFormat then + begin + Result := GvVectorialFormats[i].ReaderClass.Create; + + Break; + end; + + if Result = nil then raise Exception.Create('Unsuported vector graphics format.'); +end; + +procedure TvVectorialDocument.ClearTmpPath(); +var + segment, oldsegment: TPathSegment; +begin +// segment := FTmpPath.Points; +// Don't free segments, because they are used when the path is added +// while segment <> nil do +// begin +// oldsegment := segment; +// segment := segment^.Next; +// oldsegment^.Free; +// end; + + FTmpPath.Points := nil; + FTmpPath.PointsEnd := nil; + FTmpPath.Len := 0; +end; + +procedure TvVectorialDocument.AppendSegmentToTmpPath(ASegment: TPathSegment); +var + L: Integer; +begin + if FTmpPath.PointsEnd = nil then + Exception.Create('[TvVectorialDocument.AppendSegmentToTmpPath]' + Str_Error_Nil_Path); + + L := FTmpPath.Len; + Inc(FTmpPath.Len); + + // Adds the element to the end of the list + FTmpPath.PointsEnd.Next := ASegment; + ASegment.Previous := FTmpPath.PointsEnd; + FTmpPath.PointsEnd := ASegment; +end; + +{@@ + Writes the document to a file. + + If the file doesn't exist, it will be created. +} +procedure TvVectorialDocument.WriteToFile(AFileName: string; AFormat: TvVectorialFormat); +var + AWriter: TvCustomVectorialWriter; +begin + AWriter := CreateVectorialWriter(AFormat); + + try + AWriter.WriteToFile(AFileName, Self); + finally + AWriter.Free; + end; +end; + +{@@ + Writes the document to a stream +} +procedure TvVectorialDocument.WriteToStream(AStream: TStream; AFormat: TvVectorialFormat); +var + AWriter: TvCustomVectorialWriter; +begin + AWriter := CreateVectorialWriter(AFormat); + + try + AWriter.WriteToStream(AStream, Self); + finally + AWriter.Free; + end; +end; + +procedure TvVectorialDocument.WriteToStrings(AStrings: TStrings; + AFormat: TvVectorialFormat); +var + AWriter: TvCustomVectorialWriter; +begin + AWriter := CreateVectorialWriter(AFormat); + + try + AWriter.WriteToStrings(AStrings, Self); + finally + AWriter.Free; + end; +end; + +{@@ + Reads the document from a file. + + Any current contents will be removed. +} +procedure TvVectorialDocument.ReadFromFile(AFileName: string; + AFormat: TvVectorialFormat); +var + AReader: TvCustomVectorialReader; +begin + Self.Clear; + + AReader := CreateVectorialReader(AFormat); + try + AReader.ReadFromFile(AFileName, Self); + finally + AReader.Free; + end; +end; + +{@@ + Reads the document from a stream. + + Any current contents will be removed. +} +procedure TvVectorialDocument.ReadFromStream(AStream: TStream; + AFormat: TvVectorialFormat); +var + AReader: TvCustomVectorialReader; +begin + Self.Clear; + + AReader := CreateVectorialReader(AFormat); + try + AReader.ReadFromStream(AStream, Self); + finally + AReader.Free; + end; +end; + +procedure TvVectorialDocument.ReadFromStrings(AStrings: TStrings; + AFormat: TvVectorialFormat); +var + AReader: TvCustomVectorialReader; +begin + Self.Clear; + + AReader := CreateVectorialReader(AFormat); + try + AReader.ReadFromStrings(AStrings, Self); + finally + AReader.Free; + end; +end; + +class function TvVectorialDocument.GetFormatFromExtension(AFileName: string + ): TvVectorialFormat; +var + lExt: string; +begin + lExt := ExtractFileExt(AFileName); + if AnsiCompareText(lExt, STR_PDF_EXTENSION) = 0 then Result := vfPDF + else if AnsiCompareText(lExt, STR_POSTSCRIPT_EXTENSION) = 0 then Result := vfPostScript + else if AnsiCompareText(lExt, STR_SVG_EXTENSION) = 0 then Result := vfSVG + else if AnsiCompareText(lExt, STR_CORELDRAW_EXTENSION) = 0 then Result := vfCorelDrawCDR + else if AnsiCompareText(lExt, STR_WINMETAFILE_EXTENSION) = 0 then Result := vfWindowsMetafileWMF + else + raise Exception.Create('TvVectorialDocument.GetFormatFromExtension: The extension (' + lExt + ') doesn''t match any supported formats.'); +end; + +function TvVectorialDocument.GetDetailedFileFormat(): string; +begin + +end; + +function TvVectorialDocument.GetPath(ANum: Cardinal): TPath; +begin + if ANum >= FPaths.Count then raise Exception.Create('TvVectorialDocument.GetPath: Path number out of bounds'); + + if FPaths.Items[ANum] = nil then raise Exception.Create('TvVectorialDocument.GetPath: Invalid Path number'); + + Result := TPath(FPaths.Items[ANum]); +end; + +function TvVectorialDocument.GetPathCount: Integer; +begin + Result := FPaths.Count; +end; + +function TvVectorialDocument.GetText(ANum: Cardinal): TvText; +begin + if ANum >= FTexts.Count then raise Exception.Create('TvVectorialDocument.GetText: Text number out of bounds'); + + if FTexts.Items[ANum] = nil then raise Exception.Create('TvVectorialDocument.GetText: Invalid Text number'); + + Result := TvText(FTexts.Items[ANum]); +end; + +function TvVectorialDocument.GetTextCount: Integer; +begin + Result := FTexts.Count; +end; + +{@@ + Clears all data in the document +} +procedure TvVectorialDocument.Clear; +begin + RemoveAllPaths(); + RemoveAllTexts(); +end; + +{ TvCustomVectorialReader } + +procedure TvCustomVectorialReader.ReadFromFile(AFileName: string; AData: TvVectorialDocument); +var + FileStream: TFileStream; +begin + FileStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone); + try + ReadFromStream(FileStream, AData); + finally + FileStream.Free; + end; +end; + +procedure TvCustomVectorialReader.ReadFromStream(AStream: TStream; + AData: TvVectorialDocument); +var + AStringStream: TStringStream; + AStrings: TStringList; +begin + AStringStream := TStringStream.Create(''); + AStrings := TStringList.Create; + try + AStringStream.CopyFrom(AStream, AStream.Size); + AStringStream.Seek(0, soFromBeginning); + AStrings.Text := AStringStream.DataString; + ReadFromStrings(AStrings, AData); + finally + AStringStream.Free; + AStrings.Free; + end; +end; + +procedure TvCustomVectorialReader.ReadFromStrings(AStrings: TStrings; + AData: TvVectorialDocument); +var + AStringStream: TStringStream; +begin + AStringStream := TStringStream.Create(''); + try + AStringStream.WriteString(AStrings.Text); + AStringStream.Seek(0, soFromBeginning); + ReadFromStream(AStringStream, AData); + finally + AStringStream.Free; + end; +end; + +{ TsCustomSpreadWriter } + +{@@ + Default file writting method. + + Opens the file and calls WriteToStream + + @param AFileName The output file name. + If the file already exists it will be replaced. + @param AData The Workbook to be saved. + + @see TsWorkbook +} +procedure TvCustomVectorialWriter.WriteToFile(AFileName: string; AData: TvVectorialDocument); +var + OutputFile: TFileStream; +begin + OutputFile := TFileStream.Create(AFileName, fmCreate or fmOpenWrite); + try + WriteToStream(OutputFile, AData); + finally + OutputFile.Free; + 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; + AData: TvVectorialDocument); +begin + +end; + +{ TPath } + +procedure TPath.Assign(APath: TPath); +begin + Len := APath.Len; + Points := APath.Points; + PointsEnd := APath.PointsEnd; + CurPoint := APath.CurPoint; +end; + +function TPath.Count(): TPathSegment; +begin + +end; + +procedure TPath.PrepareForSequentialReading; +begin + CurPoint := nil; +end; + +function TPath.Next(): TPathSegment; +begin + if CurPoint = nil then Result := Points + else Result := CurPoint.Next; + + CurPoint := Result; +end; + +finalization + + SetLength(GvVectorialFormats, 0); + +end. + diff --git a/applications/fpvviewer/fpvectorialsrc/fpvtocanvas.pas b/applications/fpvviewer/fpvectorialsrc/fpvtocanvas.pas new file mode 100644 index 000000000..48429f093 --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/fpvtocanvas.pas @@ -0,0 +1,109 @@ +unit fpvtocanvas; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, + fpcanvas, + fpvectorial; + +procedure DrawFPVectorialToCanvas(ASource: TvVectorialDocument; ADest: TFPCustomCanvas; + ADestX: Integer = 0; ADestY: Integer = 0; AMulX: Double = 1.0; AMulY: Double = 1.0); + +implementation + +{@@ + This function draws a FPVectorial vectorial image to a TFPCustomCanvas + descendent, such as TCanvas from the LCL. + + Be careful that by default this routine does not execute coordinate transformations, + and that FPVectorial works with a start point in the bottom-left corner, with + the X growing to the right and the Y growing to the top. This will result in + an image in TFPCustomCanvas mirrored in the Y axis in relation with the document + as seen in a PDF viewer, for example. This can be easily changed with the + provided parameters. To have the standard view of an image viewer one could + use this function like this: + + DrawFPVectorialToCanvas(ASource, ADest, 0, ASource.Height, 1.0, -1.0); +} +procedure DrawFPVectorialToCanvas(ASource: TvVectorialDocument; ADest: TFPCustomCanvas; + 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 + CurSegment: TPathSegment; + Cur2DSegment: T2DSegment absolute CurSegment; + Cur2DBSegment: T2DBezierSegment absolute CurSegment; + // For bezier + CurX, CurY: Integer; // Not modified by ADestX, etc + CurveLength: Integer; + t: Double; +begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':>DrawFPVectorialToCanvas'); + {$endif} + + PosX := 0; + PosY := 0; + + ADest.MoveTo(ADestX, ADestY); + + for i := 0 to ASource.PathCount - 1 do + begin + //WriteLn('i = ', i); + ASource.Paths[i].PrepareForSequentialReading; + + for j := 0 to ASource.Paths[i].Len - 1 do + begin + //WriteLn('j = ', j); + CurSegment := TPathSegment(ASource.Paths[i].Next()); + + case CurSegment.SegmentType of + stMoveTo: + begin + ADest.MoveTo( + Round(ADestX + AMulX * Cur2DSegment.X), + Round(ADestY + AMulY * Cur2DSegment.Y) + ); + end; + st2DLine, st3DLine: + begin + ADest.LineTo( + Round(ADestX + AMulX * Cur2DSegment.X), + Round(ADestY + AMulY * Cur2DSegment.Y) + ); + end; + { To draw a bezier we need to divide the interval in parts and make + lines between this parts } + st2DBezier, st3DBezier: + begin + CurveLength := + Round(sqrt(sqr(Cur2DBSegment.X3 - PosX) + sqr(Cur2DBSegment.Y3 - PosY))) + + Round(sqrt(sqr(Cur2DBSegment.X2 - Cur2DBSegment.X3) + sqr(Cur2DBSegment.Y2 - Cur2DBSegment.Y3))) + + Round(sqrt(sqr(Cur2DBSegment.X - Cur2DBSegment.X3) + sqr(Cur2DBSegment.Y - Cur2DBSegment.Y3))); + + for k := 1 to CurveLength do + begin + t := k / CurveLength; + CurX := Round(sqr(1 - t) * (1 - t) * PosX + 3 * t * sqr(1 - t) * Cur2DBSegment.X2 + 3 * t * t * (1 - t) * Cur2DBSegment.X3 + t * t * t * Cur2DBSegment.X); + CurY := Round(sqr(1 - t) * (1 - t) * PosY + 3 * t * sqr(1 - t) * Cur2DBSegment.Y2 + 3 * t * t * (1 - t) * Cur2DBSegment.Y3 + t * t * t * Cur2DBSegment.Y); + ADest.LineTo( + Round(ADestX + AMulX * CurX), + Round(ADestY + AMulY * CurY)); + end; + PosX := Round(Cur2DBSegment.X); + PosY := Round(Cur2DBSegment.Y); + end; + end; + end; + end; + + {$ifdef FPVECTORIALDEBUG} + WriteLn(': TvPDFVectorialReader.getFirstPage'); + {$endif} + AInput2 := TMemoryStream.Create; + AInput2.Size := AInput.Size; + AInput2.CopyFrom(AInput, AInput.Size); + AInput.Seek(0, soFromBeginning); + AInput2.Seek(0, soFromBeginning); + + myAnLexicoPage := AnLexico.Create; + myAnLexicoPage.Doc := AInput; + myAnLexicoPage.bytesRemaining:= myAnLexicoPage.Doc.Size; + myAnSintaticoPage := AnSintaticoPage.Create; + + // find first page + while ((myAnSintaticoPage.pageFound <> true) and + (myAnLexicoPage.bytesRemaining > 0)) do + begin + mytoken := myAnLexicoPage.getToken(); + myAnSintaticoPage.automata(mytoken); + end; + + if (myAnSintaticoPage.pageFound = false) then + begin + raise Exception.Create('ERROR: Arquivo corrompido.'); + Halt(1); + end; + + AInput.Seek(0, soFromBeginning); + myAnLexicoContents := AnLexico.Create; + myAnLexicoContents.Doc := AInput; + myAnLexicoContents.bytesRemaining:= myAnLexicoContents.Doc.Size; + myAnSintaticoContents := AnSintaticoPageContents.Create; + + // gathering information of the first page + myAnSintaticoContents.obj1:=myAnSintaticoPage.obj1; + myAnSintaticoContents.obj2:=myAnSintaticoPage.obj2; + + //find first page contents + while ((myAnSintaticoContents.contentsFound <> true) and + (myAnLexicoContents.bytesRemaining > 0)) do + begin + mytoken := myAnLexicoContents.getToken(); + myAnSintaticoContents.automata(mytoken, AInput2); + end; + + if (myAnSintaticoContents.contentsFound = false) then + begin + raise Exception.Create('ERROR: Arquivo corrompido.'); + Halt(1); + end; + + // gathering information of the first page + myAnLexicoContents.bytesRemaining:=myAnSintaticoContents.h.page_length; + + // write file with content just from the first page + while (myAnLexicoContents.bytesRemaining > 0) do + begin + mytoken := myAnLexicoContents.getPageToken(); + WriteStringToStream(AOutput, mytoken.token_string); + end; + + Result:=myAnSintaticoContents.h; + + {$ifdef FPVECTORIALDEBUG} + WriteLn(':< TvPDFVectorialReader.getFirstPage'); + {$endif} + +// AInput2.Free; +end; + +procedure TvPDFVectorialReader.unzipPage(AInput: TStream; AOutput: TStream); +var + compr, uncompr: Pbyte; + comprLen, uncomprLen: LongInt; + myDecode: decode; + BufStr: string; +begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> TvPDFVectorialReader.unzipPage'); + {$endif} + + myDecode := Decode.Create; + + comprLen := 10000 * SizeOf(Integer); // don't overflow + uncomprLen := comprLen; + GetMem(compr, comprLen); + GetMem(uncompr, uncomprLen); + + if (compr = NIL) or (uncompr = NIL) then + myDecode.EXIT_ERR('Out of memory'); + + (* compr and uncompr are cleared to avoid reading uninitialized + * data and to ensure that uncompr compresses well. + *) + + FillChar(compr^, comprLen, 0); + FillChar(uncompr^, uncomprLen, 0); + + AInput.Read(compr^, comprLen); + + BufStr := string(myDecode.test_inflate(compr, comprLen, uncompr, uncomprLen)); + + WriteStringToStream(AOutput, BufStr); + + FreeMem(compr, comprLen); + FreeMem(uncompr, uncomprLen); + + {$ifdef FPVECTORIALDEBUG} + WriteLn(':< TvPDFVectorialReader.unzipPage'); + {$endif} +end; + +procedure TvPDFVectorialReader.translatePage(AInput: TStream; + AData: TvVectorialDocument; APageHeader: PageHeader); +var + myAnLexico: AnLexico; + myAnSintaticoCommand: AnSintaticoCommand; + myAnSemantico: AnSemantico; + mytoken: Token; + c: Command; +begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> TvPDFVectorialReader.translatePage'); + {$endif} + + // initialize data main + myAnLexico := AnLexico.Create; + myAnLexico.Doc := AInput; + myAnLexico.bytesRemaining:= myAnLexico.Doc.Size; + myAnSintaticoCommand := AnSintaticoCommand.Create; + myAnSemantico := AnSemantico.Create; + + // initialize machine + myAnSemantico.startMachine(); + + while (myAnLexico.bytesRemaining > 0) do + begin + mytoken := myAnLexico.getToken(); + c:=myAnSintaticoCommand.automata(mytoken); + if (myAnSintaticoCommand.Codigo = true) then + myAnSemantico.generate(c, AData); + end; + + // end machine + myAnSemantico.endMachine(); +end; + +procedure TvPDFVectorialReader.ReadFromStream(AStream: TStream; + AData: TvVectorialDocument); +var + APageHeader: PageHeader; + APageStream, AUnzipStream: TStream; +begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> TvPDFVectorialReader.ReadFromStream'); + {$endif} + + APageStream := TMemoryStream.Create; + AUnzipStream := TMemoryStream.Create; + + // get first page + APageHeader := getFirstPage(AStream, APageStream); + + // unzip page + if (APageHeader.flate_decode = true) then + begin + APageStream.Seek(0, soFromBeginning); + unzipPage(APageStream, AUnzipStream); + + // translate page to doc data + AUnzipStream.Seek(0, soFromBeginning); + translatePage(AUnzipStream, AData, APageHeader); + end + else + begin + // translate page to doc data + APageStream.Seek(0, soFromBeginning); + translatePage(APageStream, AData, APageHeader); + end; + + APageStream.Free; + AUnzipStream.Free; + + //ShowMessage('Sucesso!'); + {$ifdef FPVECTORIALDEBUG} + WriteLn(':< TvPDFVectorialReader.ReadFromStream'); + WriteLn('Sucesso!'); + {$endif} +end; + +{******************************************************************* +* Initialization section +* +* Registers this reader / writer on fpVectorial +* +*******************************************************************} +initialization + + RegisterVectorialReader(TvPDFVectorialReader, vfPDF); + +end. + diff --git a/applications/fpvviewer/fpvectorialsrc/pdfvrlexico.pas b/applications/fpvviewer/fpvectorialsrc/pdfvrlexico.pas new file mode 100644 index 000000000..e8542e956 --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/pdfvrlexico.pas @@ -0,0 +1,113 @@ +unit pdfvrlexico; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils; + +type + Token = record + tipo: Int64; + token_string: String; + end; + + TPDFCommandCode = (cc_NONE, cc_m_START_PATH, cc_l_ADD_LINE_TO_PATH, + cc_H_CLOSE_PATH, cc_S_END_PATH, cc_hS_CLOSE_AND_END_PATH, + cc_c_BEZIER_TO_X_Y_USING_X2_Y2_AND_X3_Y3, + cc_v_BEZIER_TO_X_Y_USING_CURRENT_POS_AND_X2_Y2, + cc_y_BEZIER_TO_X_Y_USING_X_Y_AND_X2_Y2, + cc_CONCATENATE_MATRIX,cc_RESTORE_MATRIX); + + Command = record + cord_x3: String; + cord_y3: String; + cord_x2: String; + cord_y2: String; + cord_x: String; + cord_y: String; + my_operator: String; + code: TPDFCommandCode; + end; + + PageHeader = record + page_length: Int64; + flate_decode: Boolean; + end; + + AnLexico = class + public + Doc: TStream; + bytesRemaining: Int64; + constructor Create(); + function getToken(): Token; + function getPageToken(): Token; + end; + +implementation + +function AnLexico.getToken(): Token; +var + t: Byte; + mytoken: Token; +begin + mytoken.tipo := 0; + while( bytesRemaining > 0 ) do + begin + t := Doc.ReadByte(); + bytesRemaining := bytesRemaining - 1; + // numbers or points or minus + if((((t >= 48) and (t <= 57)) or (t = 46 ) or (t = 45)) and + ((mytoken.tipo = 1) or (mytoken.tipo = 0))) then + begin + mytoken.token_string := mytoken.token_string + char(t); + mytoken.tipo:=1; + end + else if(((t >= 65) and (t <= 90)) or ((t >= 97) and (t <= 122)) // letters + or (t = 42) // * + and ((mytoken.tipo = 2) or (mytoken.tipo = 0))) then + begin + mytoken.token_string := mytoken.token_string + char(t); + mytoken.tipo:=2; + end + else // everything else + begin + if (mytoken.tipo <> 0) then + begin + // solve CorelDraw problem after "stream" + if ((t=13) and (bytesRemaining>0)) then + begin + t := Doc.ReadByte(); + bytesRemaining:=bytesRemaining-1; + end; + Result := mytoken; + Exit; + end; + end; + end; + Result := mytoken; +end; + +function AnLexico.getPageToken(): Token; +var + t: Byte; + mytoken: Token; +begin + mytoken.tipo := 0; + if (bytesRemaining > 0) then + begin + t := Doc.ReadByte; + mytoken.token_string:=char(t); + bytesRemaining := bytesRemaining - 1; + end; + Result := mytoken; +end; + +constructor AnLexico.Create(); +begin + inherited Create; +end; + +end. + diff --git a/applications/fpvviewer/fpvectorialsrc/pdfvrsemantico.pas b/applications/fpvviewer/fpvectorialsrc/pdfvrsemantico.pas new file mode 100644 index 000000000..bf2c80edf --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/pdfvrsemantico.pas @@ -0,0 +1,244 @@ +unit pdfvrsemantico; + +{$mode objfpc}{$H+} + +interface + +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; + function endMachine(): String; + constructor Create; + end; + +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; +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 + begin + c.cord_x:=close_path_x; + c.cord_y:=close_path_y; + end; + + if ((c.code <> cc_H_CLOSE_PATH) and (c.code <> cc_hS_CLOSE_AND_END_PATH)) then // close path already converted + begin + if ((c.code = cc_m_START_PATH) or (c.code = cc_l_ADD_LINE_TO_PATH)) then + begin + //WriteLn(':: anSemantico.generate convert code ', Integer(c.code)); + c.cord_x := convert(c.cord_x,c.cord_y,'x'); + c.cord_y := convert(c.cord_x,c.cord_y,'y'); + end; + if ((c.code = cc_c_BEZIER_TO_X_Y_USING_X2_Y2_AND_X3_Y3)) then + begin + //WriteLn(':: anSemantico.generate convert code ', Integer(c.code)); + c.cord_x := convert(c.cord_x,c.cord_y,'x'); + c.cord_y := convert(c.cord_x,c.cord_y,'y'); + c.cord_x2 := convert(c.cord_x2,c.cord_y2,'x'); + c.cord_y2 := convert(c.cord_x2,c.cord_y2,'y'); + c.cord_x3 := convert(c.cord_x3,c.cord_y3,'x'); + c.cord_y3 := convert(c.cord_x3,c.cord_y3,'y'); + end; + end; + + case c.code of + cc_m_START_PATH: // command m + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.generate Estado 1 EndPath StartPath'); + {$endif} + // Result:='G01' + ' ' + 'X' + c.cord_x + ' ' + 'Y' + c.cord_y + enter_line + + // 'G01 Z50 // Abaixa a cabeça de gravação'; + + // Correcao para programas de desenho que geram um novo inicio no + // fim do desenho, terminamos qualquer desenho inacabado + AData.EndPath(); + AData.StartPath(StringToFloat(c.cord_x), StringToFloat(c.cord_y)); + + close_path_x:=c.cord_x; + close_path_y:=c.cord_y; + end; + cc_l_ADD_LINE_TO_PATH: // command l + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.generate Estado 2 AddPointToPath'); + {$endif} + // Result:='G01' + ' ' + 'X' + c.cord_x + ' ' + 'Y' + c.cord_y; + + AData.AddLineToPath(StringToFloat(c.cord_x), StringToFloat(c.cord_y)); + end; + cc_h_CLOSE_PATH: // command h + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.generate Estado 3 AddPointToPath'); + {$endif} + //Result:='G01' + ' ' + 'X' + c.cord_x + ' ' + 'Y' + c.cord_y; + + AData.AddLineToPath(StringToFloat(c.cord_x), StringToFloat(c.cord_y)); + end; + cc_S_END_PATH: // command S + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.generate Estado 4 EndPath'); + {$endif} + // Result:='G01 Z0 // Sobe a cabeça de gravação' + enter_line; + AData.EndPath(); + end; + cc_hS_CLOSE_AND_END_PATH: // command s + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.generate Estado 5 AddPoint EndPath'); + {$endif} + //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(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 + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.generate Estado 6 Bezier'); + {$endif} + //Result:='G01' + ' ' + 'X' + c.cord_x + ' ' + 'Y' + c.cord_y + enter_line + // +'G01 Z0 // Sobe a cabeça de gravação' + enter_line; + + AData.AddBezierToPath( + 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 + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.cc_CONCATENATE_MATRIX'); + {$endif} + + 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 + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.cc_RESTORE_MATRIX'); + {$endif} + + cm_a:=1; + cm_b:=0; + cm_c:=0; + cm_d:=1; + cm_e:=0; + cm_f:=0; + end; + else + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.generate Estado ELSE'); + {$endif} + Result:=c.my_operator; + end; +end; + +function AnSemantico.convert(x: String; y: String; Axis: Char): String; +begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.convert'); + {$endif} + // convert from 1/72 inch to milimeters and change axis if necessary + + if (Axis = 'y') then + begin + // y' = b * x + d * y + f + 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*StringToFloat(x)+cm_c*StringToFloat(y)+cm_e)*(25.40/72)); + end; +end; + +function AnSemantico.startMachine(): String; +var + enter_line : String; +begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.startMachine'); + {$endif} + enter_line:=chr(13) + chr(10); // CR and LF + + Result:='M216 // Ligar monitor de carga' + enter_line + + 'G28 // Ir rapidamente para posição inicial' + enter_line + + 'G00' + enter_line; +end; + +function AnSemantico.endMachine(): String; +var + enter_line : String; +begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSemantico.endMachine'); + {$endif} + enter_line:=chr(13) + chr(10); // CR and LF + + Result:='M30 // Parar o programa e retornar para posição inicial' + enter_line + + 'M215 // Desligar monitor de carga' + enter_line; +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. + diff --git a/applications/fpvviewer/fpvectorialsrc/pdfvrsintatico.pas b/applications/fpvviewer/fpvectorialsrc/pdfvrsintatico.pas new file mode 100644 index 000000000..2bcb7e905 --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/pdfvrsintatico.pas @@ -0,0 +1,628 @@ +unit pdfvrsintatico; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, pdfvrlexico; + +type + AnSintaticoPage = class + public + Estado: Int64; + obj1,obj2 : String; + pageFound: Boolean; + constructor Create; + procedure automata(t: Token); + end; + + AnSintaticoPageContents = class + public + Estado: Int64; + obj1,obj2 : String; + len_obj1,len_obj2: String; + contentsFound: Boolean; + h: PageHeader; + constructor Create; + procedure automata(t: Token; Input: TStream); + end; + + AnSintaticoCommand = class + public + Estado: Int64; + Codigo: Boolean; + c: Command; + constructor Create; + function automata(t: Token):Command; + end; + + AnSintaticoLength = class + public + Estado: Int64; + len_obj1,len_obj2: String; + page_length : Int64; + lenghtFound: Boolean; + constructor Create; + procedure automata(t: Token); + end; + +implementation + +procedure AnSintaticoPage.automata(t: Token); +begin + case Estado of + 1: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPage.automata Estado 1'); + {$endif} + if(t.token_string = 'Type') then + begin + Estado := 2; + end + else + begin + Estado := 1; + end; + end; + 2: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPage.automata Estado 2'); + {$endif} + if(t.token_string = 'Page') then + begin + Estado := 3; + end + else + begin + Estado := 1; + end; + end; + 3: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPage.automata Estado 3'); + {$endif} + if(t.token_string = 'Contents') then + begin + Estado := 4; + end + else + begin + Estado := 3; + end; + end; + 4: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPage.automata Estado 4'); + {$endif} + if(t.tipo = 1) then // numbers 1 + begin + obj1:=t.token_string; + Estado := 5; + end + else + begin + raise Exception.Create('ERROR: Arquivo corrompido.'); + Halt(1); + end; + end; + 5: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPage.automata Estado 5'); + {$endif} + if(t.tipo = 1) then // numbers 2 + begin + obj2:=t.token_string; + Estado := 6; // symbolic state + pageFound := true; + end + else + begin + raise Exception.Create('ERROR: Arquivo corrompido.'); + Halt(1); + end; + end; + else + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPage.automata Estado ELSE'); + {$endif} + Estado := 1; + end; +end; + +procedure AnSintaticoPageContents.automata(t: Token; Input: TStream); +var + myAnLexicoLength: AnLexico; + myAnSintaticoLength: AnSintaticoLength; + mytokenLength: Token; +begin + case Estado of + 1: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPageContents.automata Estado 1'); + {$endif} + if(t.token_string = obj1) then + begin + Estado := 2; + end + else + begin + Estado := 1; + end; + end; + 2: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPageContents.automata Estado 2'); + {$endif} + if(t.token_string = obj2) then + begin + Estado := 3; + end + else + begin + Estado := 1; + end; + end; + 3: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPageContents.automata Estado 3'); + {$endif} + if(t.token_string = 'obj') then + begin + Estado := 4; + end + else + begin + Estado := 1; + end; + end; + 4: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPageContents.automata Estado 4'); + {$endif} + if(t.token_string = 'Length') then + begin + Estado := 5; + end + else if (t.token_string = 'Filter') then + begin + Estado := 7; + end + else + begin + Estado := 4; + end; + end; + 5: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPageContents.automata Estado 5'); + {$endif} + if(t.tipo = 1) then + begin + h.page_length := StrToInt(t.token_string); + len_obj1:=t.token_string; + Estado := 6; + end + else + begin + raise Exception.Create('ERROR: Arquivo corrompido.'); + Halt(1); + end; + end; + 6: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPageContents.automata Estado 6'); + {$endif} + if(t.token_string = 'Filter') then + begin + Estado := 7; + end + else if (t.token_string = 'stream') then + begin + contentsFound := true; + Estado := 9; // symbolic state + end + else if (t.tipo = 1) then + begin + len_obj2:=t.token_string; + myAnLexicoLength := AnLexico.Create; + myAnLexicoLength.Doc := Input; + myAnLexicoLength.bytesRemaining:= myAnLexicoLength.Doc.Size; + myAnSintaticoLength := AnSintaticoLength.Create; + + myAnSintaticoLength.len_obj1:=len_obj1; + myAnSintaticoLength.len_obj2:=len_obj2; + + while ((myAnSintaticoLength.lenghtFound <> true) and + (myAnLexicoLength.bytesRemaining > 0)) do + begin + mytokenLength := myAnLexicoLength.getToken(); + myAnSintaticoLength.automata(mytokenLength); + end; + + if (myAnSintaticoLength.lenghtFound = false) then + begin + raise Exception.Create('ERROR: Arquivo corrompido.'); + Halt(1); + end; + + h.page_length:=myAnSintaticoLength.page_length; + myAnLexicoLength.Doc.Destroy; + Estado := 6; + end + else + begin + Estado := 6; + end; + end; + 7: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPageContents.automata Estado 7'); + {$endif} + if(t.token_string = 'FlateDecode') then + begin + h.flate_decode := true; + Estado := 8; + end + else + begin + raise Exception.Create('ERROR: Encodificacao nao suportada.'); + Halt(1); + end; + end; + 8: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPageContents.automata Estado 8'); + {$endif} + if(t.token_string = 'stream') then + begin + contentsFound := true; + Estado := 9; // symbolic state + end + else if (t.token_string = 'Length') then + begin + Estado := 5; + end + else + begin + Estado := 8; + end; + end; + else + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoPageContents.automata Estado ELSE'); + {$endif} + Estado := 1; + end; +end; + +procedure AnSintaticoLength.automata(t: Token); +begin + case Estado of + 1: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoLength.automata Estado 1'); + {$endif} + if(t.token_string = len_obj1) then + begin + Estado := 2; + end + else + begin + Estado := 1; + end; + end; + 2: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoLength.automata Estado 2'); + {$endif} + if(t.token_string = len_obj2) then + begin + Estado := 3; + end + else + begin + Estado := 1; + end; + end; + 3: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoLength.automata Estado 3'); + {$endif} + if(t.token_string = 'obj') then + begin + Estado := 4; + end + else + begin + Estado := 1; + end; + end; + 4: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoLength.automata Estado 4 Length: ', StrToInt(t.token_string)); + {$endif} + if(t.tipo = 1) then + begin + page_length:=StrToInt(t.token_string); + lenghtFound:=true; + Estado := 5; // symbolic state + end + else + begin + raise Exception.Create('ERROR: Arquivo corrompido.'); + Halt(1); + end; + end; + else + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoLength.automata Estado ELSE'); + {$endif} + Estado := 1; + end; +end; + +function AnSintaticoCommand.automata(t: Token):Command; +begin + c.cord_x3 := c.cord_y3; + c.cord_y3 := c.cord_x2; + c.cord_x2 := c.cord_y2; + c.cord_y2 := c.cord_x; + c.cord_x := c.cord_y; + c.cord_y := c.my_operator; + c.my_operator := t.token_string; + c.code := cc_NONE; + + Codigo := false; + + case Estado of + 1: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoCommand.automata Estado 1'); + {$endif} + if(t.tipo = 1) then // numbers 1 + begin + Estado := 2; + end + else if( t.token_string = 'h' ) then // command h + begin + Estado := 9; // symbolic state + Estado := 1; + Codigo := true; + c.code:=cc_H_CLOSE_PATH; + Result:=c; + end + else if( t.token_string = 's' ) then // command s + begin + Estado := 10; // symbolic state + Estado := 1; + Codigo := true; + c.code:=cc_hS_CLOSE_AND_END_PATH; + Result:=c; + end + else if( t.token_string = 'S' ) then // command S + begin + Estado := 11; // symbolic state + Estado := 1; + Codigo := true; + c.code:=cc_S_END_PATH; + Result:=c; + end + else if( t.token_string = 'Q' ) then // command Q + begin + Estado := 21; // symbolic state + Estado := 1; + Codigo := true; + c.code:=cc_RESTORE_MATRIX; + Result:=c; + end + else if ((t.token_string = 'f') or (t.token_string = 'F') + or (t.token_string = 'f*') or (t.token_string = 'B') + or (t.token_string = 'B*') or (t.token_string = 'b') + or (t.token_string = 'b*') or (t.token_string = 'n')) then + begin + Estado := 12; // symbolic state + Estado := 1; + Codigo := true; + c.code:=cc_hS_CLOSE_AND_END_PATH; // ignore painting.. + Result:=c; + //raise Exception.Create('ERROR: Prenchimento nao eh suportado.'); + //Halt(1); + end + else if ((t.token_string = 'W') or (t.token_string = 'W*')) then + begin + Estado := 13; // symbolic state + raise Exception.Create('ERROR: Clipping nao eh suportado.'); + Halt(1); + end + else + begin + Estado := 1; + end; + end; + 2: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoCommand.automata Estado 2'); + {$endif} + if(t.tipo = 1) then // numbers 2 + begin + Estado := 3; + end + else + begin + Estado := 1; + end; + end; + 3: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoCommand.automata Estado 3'); + {$endif} + if(t.tipo = 1) then // numbers 3 + begin + Estado := 5; + end + else if(t.token_string = 'l') then // command l + begin + Estado := 14; // symbolic state + Estado := 1; + c.code:=cc_l_ADD_LINE_TO_PATH; + Codigo := true; + Result:=c; + end + else if(t.token_string = 'm') then // command m + begin + Estado := 15; // symbolic state + Estado := 1; + c.code:=cc_m_START_PATH; + Codigo := true; + Result:=c; + end + else + begin + Estado := 1; + end; + end; + 5: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoCommand.automata Estado 5'); + {$endif} + if(t.tipo = 1) then // numbers 4 + begin + Estado := 6; + end + else + begin + Estado := 1; + end; + end; + 6: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoCommand.automata Estado 6'); + {$endif} + if(t.tipo = 1) then // numbers 5 + begin + Estado := 7; + end + else if( t.token_string = 'v' ) then // command v + begin + Estado := 16; // symbolic state + raise Exception.Create('ERROR: Curva de bezier nao eh suportada.'); + Halt(1); + end + else if( t.token_string = 'y' ) then // command y + begin + Estado := 17; // symbolic state + raise Exception.Create('ERROR: Curva de bezier nao eh suportada.'); + Halt(1); + end + else if( t.token_string = 're' ) then // command re + begin + Estado := 18; // symbolic state + raise Exception.Create('ERROR: Comando nao suportado.'); + Halt(1); + end + else + begin + Estado := 1; + end; + end; + 7: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoCommand.automata Estado 7'); + {$endif} + if(t.tipo = 1) then // numbers 6 + begin + Estado := 8; + end + else + begin + Estado := 1; + end; + end; + 8: + begin + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoCommand.automata Estado 8'); + {$endif} + if(t.token_string = 'c') then // commmand c + begin + Estado := 19; // symbolic state + Estado := 1; + c.code:=cc_c_BEZIER_TO_X_Y_USING_X2_Y2_AND_X3_Y3; + Codigo := true; + Result:=c; + end + else if( t.token_string = 'cm' ) then // command cm + begin + Estado := 20; // symbolic state + Estado := 1; + c.code:=cc_CONCATENATE_MATRIX; + Codigo := true; + Result:=c; + end + else + begin + Estado := 1; + end; + end; + else + {$ifdef FPVECTORIALDEBUG} + WriteLn(':> AnSintaticoCommand.automata Estado ELSE'); + {$endif} + Estado := 1; + end; +end; + +constructor AnSintaticoCommand.Create; +begin + inherited Create; + Estado := 1; +end; + +constructor AnSintaticoPage.Create; +begin + inherited Create; + Estado := 1; + pageFound := false; +end; + +constructor AnSintaticoPageContents.Create; +begin + inherited Create; + Estado := 1; + contentsFound := false; + h.flate_decode := false; +end; + +constructor AnSintaticoLength.Create; +begin + inherited Create; + Estado := 1; + lenghtFound := false; +end; + +end. + diff --git a/applications/fpvviewer/fpvectorialsrc/svgvectorialwriter.pas b/applications/fpvviewer/fpvectorialsrc/svgvectorialwriter.pas new file mode 100644 index 000000000..a1ae21495 --- /dev/null +++ b/applications/fpvviewer/fpvectorialsrc/svgvectorialwriter.pas @@ -0,0 +1,245 @@ +{ +Writes 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 svgvectorialwriter; + +{$mode objfpc}{$H+} + +interface + +uses + Classes, SysUtils, math, fpvectorial; + +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); + procedure WriteTexts(AStrings: TStrings; AData: TvVectorialDocument); + procedure ConvertFPVCoordinatesToSVGCoordinates( + const AData: TvVectorialDocument; + const ASrcX, ASrcY: Double; var ADestX, ADestY: double); + public + { General reading methods } + procedure WriteToStrings(AStrings: TStrings; 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 + +{ TvSVGVectorialWriter } + +procedure TvSVGVectorialWriter.WriteDocumentSize(AStrings: TStrings; AData: TvVectorialDocument); +begin + AStrings.Add(' width="' + FloatToStr(AData.Width, FPointSeparator) + 'mm"'); + AStrings.Add(' height="' + FloatToStr(AData.Height, FPointSeparator) + 'mm"'); +end; + +procedure TvSVGVectorialWriter.WriteDocumentName(AStrings: TStrings; AData: TvVectorialDocument); +begin + AStrings.Add(' sodipodi:docname="New document 1">'); +end; + +{@@ + SVG Coordinate system measures things only in pixels, so that we have to + hardcode a DPI value for the screen, which is usually 72. + FPVectorial uses only milimeters (mm). + + The initial point in FPVectorial is in the bottom-left corner of the document + and it grows to the top and to the right. In SVG, on the other hand, the + initial point is in the top-left corner, growing to the bottom and right. + Besides that, coordinates in SVG are also lengths in comparison to the + previous point and not absolute coordinates. + + 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, OldPtX, OldPtY: double; + BezierCP1X, BezierCP1Y, BezierCP2X, BezierCP2Y: double; + segment: TPathSegment; + l2DSegment: T2DSegment absolute segment; + l2DBSegment: T2DBezierSegment absolute segment; +begin + for i := 0 to AData.GetPathCount() - 1 do + begin + OldPtX := 0; + OldPtY := 0; + + PathStr := ''; + lPath := AData.GetPath(i); + lPath.PrepareForSequentialReading; + + for j := 0 to lPath.Len - 1 do + begin + segment := TPathSegment(lPath.Next()); + + if (segment.SegmentType <> st2DLine) + and (segment.SegmentType <> stMoveTo) + and (segment.SegmentType <> st2DBezier) + then Break; // unsupported line type + + // Coordinate conversion from fpvectorial to SVG + ConvertFPVCoordinatesToSVGCoordinates( + AData, l2DSegment.X, l2DSegment.Y, PtX, PtY); + PtX := PtX - OldPtX; + PtY := PtY - OldPtY; + + if (segment.SegmentType = stMoveTo) then + begin + PathStr := PathStr + 'm ' + + FloatToStr(PtX, FPointSeparator) + ',' + + FloatToStr(PtY, FPointSeparator) + ' '; + end + else if (segment.SegmentType = st2DLine) then + begin + PathStr := PathStr + 'l ' + + FloatToStr(PtX, FPointSeparator) + ',' + + FloatToStr(PtY, FPointSeparator) + ' '; + end + else if (segment.SegmentType = st2DBezier) then + begin + // Converts all coordinates to absolute values + ConvertFPVCoordinatesToSVGCoordinates( + AData, l2DBSegment.X2, l2DBSegment.Y2, BezierCP1X, BezierCP1Y); + ConvertFPVCoordinatesToSVGCoordinates( + AData, l2DBSegment.X3, l2DBSegment.Y3, BezierCP2X, BezierCP2Y); + + // Transforms them into values relative to the initial point + BezierCP1X := BezierCP1X - OldPtX; + BezierCP1Y := BezierCP1Y - OldPtY; + BezierCP2X := BezierCP2X - OldPtX; + BezierCP2Y := BezierCP2Y - OldPtY; + + // PtX and PtY already contains the destination point + + // Now render our 2D cubic bezier + PathStr := PathStr + 'c ' + + FloatToStr(BezierCP1X, FPointSeparator) + ',' + + FloatToStr(BezierCP1Y, FPointSeparator) + ' ' + + FloatToStr(BezierCP2X, FPointSeparator) + ',' + + FloatToStr(BezierCP2Y, FPointSeparator) + ' ' + + FloatToStr(PtX, FPointSeparator) + ',' + + FloatToStr(PtY, FPointSeparator) + ' ' + ; + end; + + // Store the current position for future points + OldPtX := OldPtX + PtX; + OldPtY := OldPtY + PtY; + end; + + AStrings.Add(' '); + end; +end; + +procedure TvSVGVectorialWriter.ConvertFPVCoordinatesToSVGCoordinates( + const AData: TvVectorialDocument; const ASrcX, ASrcY: Double; var ADestX, + ADestY: double); +begin + ADestX := ASrcX / FLOAT_MILIMETERS_PER_PIXEL; + ADestY := (AData.Height - ASrcY) / FLOAT_MILIMETERS_PER_PIXEL; +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(''); + AStrings.Add(''); + AStrings.Add(''); + AStrings.Add(''); + WritePaths(AStrings, AData); + WriteTexts(AStrings, AData); + AStrings.Add(' '); + + // finalization + AStrings.Add(''); +end; + +procedure TvSVGVectorialWriter.WriteTexts(AStrings: TStrings; AData: TvVectorialDocument); +var + i, j, FontSize: Integer; + TextStr, FontName, SVGFontFamily: string; + lText: TvText; + PtX, PtY: double; +begin + for i := 0 to AData.GetTextCount() - 1 do + begin + TextStr := ''; + lText := AData.GetText(i); + + ConvertFPVCoordinatesToSVGCoordinates( + AData, lText.X, lText.Y, PtX, PtY); + + TextStr := lText.Value; + FontSize:= ceil(lText.FontSize / FLOAT_MILIMETERS_PER_PIXEL); + SVGFontFamily := 'Arial, sans-serif';//lText.FontName; + + AStrings.Add(' '); + AStrings.Add(' '); + AStrings.Add(TextStr + ''); + end; +end; + +initialization + + RegisterVectorialWriter(TvSVGVectorialWriter, vfSVG); + +end. + diff --git a/applications/fpvviewer/fpvviewer.lpi b/applications/fpvviewer/fpvviewer.lpi index ecd7f5aa2..c29f28924 100644 --- a/applications/fpvviewer/fpvviewer.lpi +++ b/applications/fpvviewer/fpvviewer.lpi @@ -3,11 +3,14 @@ + + + + - @@ -15,7 +18,7 @@ - + @@ -34,12 +37,11 @@ - + - @@ -47,194 +49,8 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -243,6 +59,7 @@ + @@ -253,6 +70,9 @@ + + +