{ 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, Math; 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; {@@ } TvEntity = class public end; {@@ } TvCircle = class(TvEntity) public CenterX, CenterY, CenterZ, Radius: Double; end; {@@ } TvCircularArc = class(TvEntity) public CenterX, CenterY, CenterZ, Radius: Double; {@@ The Angle is measured in degrees in relation to the positive X axis } StartAngle, EndAngle: Double; end; {@@ } { TvEllipse } TvEllipse = class(TvEntity) public // Mandatory fields CenterX, CenterY, CenterZ, MajorHalfAxis, MinorHalfAxis: Double; {@@ The Angle is measured in degrees in relation to the positive X axis } Angle: Double; // Calculated fields BoundingRect: TRect; procedure CalculateBoundingRectangle; end; type TvCustomVectorialWriter = class; TvCustomVectorialReader = class; { TvVectorialDocument } TvVectorialDocument = class private FPaths: TFPList; FTexts: TFPList; FEntities: 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; function GetEntity(ANum: Cardinal): TvEntity; function GetEntityCount: 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; procedure AddCircle(ACenterX, ACenterY, ACenterZ, ARadius: Double); procedure AddCircularArc(ACenterX, ACenterY, ACenterZ, ARadius, AStartAngle, AEndAngle: Double); procedure AddEllipse(CenterX, CenterY, CenterZ, MajorHalfAxis, MinorHalfAxis, Angle: Double); { 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 } constructor Create; virtual; 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 } constructor Create; virtual; 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; { TvEllipse } procedure TvEllipse.CalculateBoundingRectangle; var t, tmp: Double; begin { To calculate the bounding rectangle we can do this: Ellipse equations:You could try using the parametrized equations for an ellipse rotated at an arbitrary angle: x = CenterX + MajorHalfAxis*cos(t)*cos(Angle) - MinorHalfAxis*sin(t)*sin(Angle) y = CenterY + MinorHalfAxis*sin(t)*cos(Angle) + MajorHalfAxis*cos(t)*sin(Angle) You can then differentiate and solve for gradient = 0: 0 = dx/dt = -MajorHalfAxis*sin(t)*cos(Angle) - MinorHalfAxis*cos(t)*sin(Angle) => tan(t) = -MinorHalfAxis*tan(Angle)/MajorHalfAxis => t = cotang(-MinorHalfAxis*tan(Angle)/MajorHalfAxis) On the other axis: 0 = dy/dt = b*cos(t)*cos(phi) - a*sin(t)*sin(phi) => tan(t) = b*cot(phi)/a } t := cotan(-MinorHalfAxis*tan(Angle)/MajorHalfAxis); tmp := CenterX + MajorHalfAxis*cos(t)*cos(Angle) - MinorHalfAxis*sin(t)*sin(Angle); BoundingRect.Right := Round(tmp); 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; FEntities := TFPList.Create; FTmpPath := TPath.Create; end; {@@ Destructor. } destructor TvVectorialDocument.Destroy; begin Clear; FPaths.Free; FTexts.Free; FEntities.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; procedure TvVectorialDocument.AddCircle(ACenterX, ACenterY, ACenterZ, ARadius: Double); var lCircle: TvCircle; begin lCircle := TvCircle.Create; lCircle.CenterX := ACenterX; lCircle.CenterY := ACenterY; lCircle.CenterZ := ACenterZ; lCircle.Radius := ARadius; FEntities.Add(lCircle); end; procedure TvVectorialDocument.AddCircularArc(ACenterX, ACenterY, ACenterZ, ARadius, AStartAngle, AEndAngle: Double); var lCircularArc: TvCircularArc; begin lCircularArc := TvCircularArc.Create; lCircularArc.CenterX := ACenterX; lCircularArc.CenterY := ACenterY; lCircularArc.CenterZ := ACenterZ; lCircularArc.Radius := ARadius; lCircularArc.StartAngle := AStartAngle; lCircularArc.EndAngle := AEndAngle; FEntities.Add(lCircularArc); end; procedure TvVectorialDocument.AddEllipse(CenterX, CenterY, CenterZ, MajorHalfAxis, MinorHalfAxis, Angle: Double); var lEllipse: TvEllipse; begin lEllipse := TvEllipse.Create; lEllipse.CenterX := CenterX; lEllipse.CenterY := CenterY; lEllipse.CenterZ := CenterZ; lEllipse.MajorHalfAxis := MajorHalfAxis; lEllipse.MinorHalfAxis := MinorHalfAxis; lEllipse.Angle := Angle; FEntities.Add(lEllipse); 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; function TvVectorialDocument.GetEntity(ANum: Cardinal): TvEntity; begin if ANum >= FEntities.Count then raise Exception.Create('TvVectorialDocument.GetEntity: Entity number out of bounds'); if FEntities.Items[ANum] = nil then raise Exception.Create('TvVectorialDocument.GetEntity: Invalid Entity number'); Result := TvEntity(FEntities.Items[ANum]); end; function TvVectorialDocument.GetEntityCount: Integer; begin Result := FEntities.Count; end; {@@ Clears all data in the document } procedure TvVectorialDocument.Clear; begin RemoveAllPaths(); RemoveAllTexts(); end; { TvCustomVectorialReader } constructor TvCustomVectorialReader.Create; begin inherited Create; end; 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 } constructor TvCustomVectorialWriter.Create; begin inherited Create; end; {@@ 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.