fpvectorial.pas 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263
  1. {
  2. fpvectorial.pas
  3. Vector graphics document
  4. License: The same modified LGPL as the Free Pascal RTL
  5. See the file COPYING.modifiedLGPL for more details
  6. AUTHORS: Felipe Monteiro de Carvalho
  7. Pedro Sol Pegorini L de Lima
  8. }
  9. unit fpvectorial;
  10. {$ifdef fpc}
  11. {$mode delphi}
  12. {$endif}
  13. interface
  14. uses
  15. Classes, SysUtils, Math,
  16. fpcanvas, fpimage;
  17. type
  18. TvVectorialFormat = (
  19. { Multi-purpose document formats }
  20. vfPDF, vfSVG, vfCorelDrawCDR, vfWindowsMetafileWMF,
  21. { CAD formats }
  22. vfDXF,
  23. { Printing formats }
  24. vfPostScript, vfEncapsulatedPostScript,
  25. { GCode formats }
  26. vfGCodeAvisoCNCPrototipoV5, vfGCodeAvisoCNCPrototipoV6);
  27. const
  28. { Default extensions }
  29. { Multi-purpose document formats }
  30. STR_PDF_EXTENSION = '.pdf';
  31. STR_POSTSCRIPT_EXTENSION = '.ps';
  32. STR_SVG_EXTENSION = '.svg';
  33. STR_CORELDRAW_EXTENSION = '.cdr';
  34. STR_WINMETAFILE_EXTENSION = '.wmf';
  35. STR_AUTOCAD_EXCHANGE_EXTENSION = '.dxf';
  36. STR_ENCAPSULATEDPOSTSCRIPT_EXTENSION = '.eps';
  37. type
  38. { Pen, Brush and Font }
  39. TvPen = record
  40. Color: TFPColor;
  41. Style: TFPPenStyle;
  42. Width: Integer;
  43. end;
  44. TvBrush = record
  45. Color: TFPColor;
  46. Style: TFPBrushStyle;
  47. end;
  48. TvFont = record
  49. Color: TFPColor;
  50. Size: integer;
  51. Name: utf8string;
  52. {@@
  53. Font orientation is measured in degrees and uses the
  54. same direction as the LCL TFont.orientation, which is counter-clockwise.
  55. Zero is the normal, horizontal, orientation.
  56. }
  57. Orientation: Double;
  58. end;
  59. { Coordinates and polyline segments }
  60. T3DPoint = record
  61. X, Y, Z: Double;
  62. end;
  63. P3DPoint = ^T3DPoint;
  64. TSegmentType = (
  65. st2DLine, st2DLineWithPen, st2DBezier,
  66. st3DLine, st3DBezier, stMoveTo);
  67. {@@
  68. The coordinates in fpvectorial are given in millimiters and
  69. the starting point is in the bottom-left corner of the document.
  70. The X grows to the right and the Y grows to the top.
  71. }
  72. { TPathSegment }
  73. TPathSegment = class
  74. public
  75. SegmentType: TSegmentType;
  76. // Fields for linking the list
  77. Previous: TPathSegment;
  78. Next: TPathSegment;
  79. end;
  80. {@@
  81. In a 2D segment, the X and Y coordinates represent usually the
  82. final point of the segment, being that it starts where the previous
  83. segment ends. The exception is for the first segment of all, which simply
  84. holds the starting point for the drawing and should always be of the type
  85. stMoveTo.
  86. }
  87. T2DSegment = class(TPathSegment)
  88. public
  89. X, Y: Double;
  90. end;
  91. T2DSegmentWithPen = class(T2DSegment)
  92. public
  93. Pen: TvPen;
  94. end;
  95. {@@
  96. In Bezier segments, we remain using the X and Y coordinates for the ending point.
  97. The starting point is where the previous segment ended, so that the intermediary
  98. bezier control points are [X2, Y2] and [X3, Y3].
  99. }
  100. T2DBezierSegment = class(T2DSegment)
  101. public
  102. X2, Y2: Double;
  103. X3, Y3: Double;
  104. end;
  105. T3DSegment = class(TPathSegment)
  106. public
  107. {@@
  108. Coordinates of the end of the segment.
  109. For the first segment, this is the starting point.
  110. }
  111. X, Y, Z: Double;
  112. end;
  113. T3DBezierSegment = class(T3DSegment)
  114. public
  115. X2, Y2, Z2: Double;
  116. X3, Y3, Z3: Double;
  117. end;
  118. { Now all elements }
  119. {@@
  120. All elements should derive from TvEntity, regardless of whatever properties
  121. they might contain.
  122. }
  123. TvEntity = class
  124. public
  125. {@@ The global Pen for the entire entity. In the case of paths, individual
  126. elements might be able to override this setting. }
  127. Pen: TvPen;
  128. {@@ The global Brush for the entire entity. In the case of paths, individual
  129. elements might be able to override this setting. }
  130. Brush: TvBrush;
  131. constructor Create; virtual;
  132. end;
  133. TPath = class(TvEntity)
  134. Len: Integer;
  135. Points: TPathSegment; // Beginning of the double-linked list
  136. PointsEnd: TPathSegment; // End of the double-linked list
  137. CurPoint: TPathSegment; // Used in PrepareForSequentialReading and Next
  138. procedure Assign(ASource: TPath);
  139. procedure PrepareForSequentialReading;
  140. function Next(): TPathSegment;
  141. end;
  142. {@@
  143. TvText represents a text in memory.
  144. At the moment fonts are unsupported, only simple texts
  145. up to 255 chars are supported.
  146. }
  147. TvText = class(TvEntity)
  148. public
  149. X, Y, Z: Double; // Z is ignored in 2D formats
  150. Value: utf8string;
  151. Font: TvFont;
  152. end;
  153. {@@
  154. }
  155. TvCircle = class(TvEntity)
  156. public
  157. CenterX, CenterY, CenterZ, Radius: Double;
  158. end;
  159. {@@
  160. }
  161. TvCircularArc = class(TvEntity)
  162. public
  163. CenterX, CenterY, CenterZ, Radius: Double;
  164. {@@ The Angle is measured in degrees in relation to the positive X axis }
  165. StartAngle, EndAngle: Double;
  166. end;
  167. {@@
  168. }
  169. TvEllipse = class(TvEntity)
  170. public
  171. // Mandatory fields
  172. CenterX, CenterY, CenterZ, MajorHalfAxis, MinorHalfAxis: Double;
  173. {@@ The Angle is measured in degrees in relation to the positive X axis }
  174. Angle: Double;
  175. // Calculated fields
  176. BoundingRect: TRect;
  177. procedure CalculateBoundingRectangle;
  178. end;
  179. {@@
  180. The brush has no effect in this class
  181. DimensionLeft ---text--- DimensionRight
  182. | |
  183. | | BaseRight
  184. |
  185. | BaseLeft
  186. }
  187. { TvAlignedDimension }
  188. TvAlignedDimension = class(TvEntity)
  189. public
  190. // Mandatory fields
  191. BaseLeft, BaseRight, DimensionLeft, DimensionRight: T3DPoint;
  192. end;
  193. {@@
  194. Vectorial images can contain raster images inside them and this entity
  195. represents this.
  196. If the Width and Height differ from the same data in the image, then
  197. the raster image will be stretched.
  198. Note that TFPCustomImage does not implement a storage, so the property
  199. RasterImage should be filled with either a FPImage.TFPMemoryImage or with
  200. a TLazIntfImage. The property RasterImage might be nil.
  201. }
  202. TvRasterImage = class(TvEntity)
  203. public
  204. RasterImage: TFPCustomImage;
  205. Top, Left, Width, Height: Double;
  206. end;
  207. type
  208. TvCustomVectorialWriter = class;
  209. TvCustomVectorialReader = class;
  210. { TvVectorialDocument }
  211. TvVectorialDocument = class
  212. private
  213. FEntities: TFPList;
  214. FTmpPath: TPath;
  215. FTmpText: TvText;
  216. procedure RemoveCallback(data, arg: pointer);
  217. function CreateVectorialWriter(AFormat: TvVectorialFormat): TvCustomVectorialWriter;
  218. function CreateVectorialReader(AFormat: TvVectorialFormat): TvCustomVectorialReader;
  219. procedure ClearTmpPath();
  220. procedure AppendSegmentToTmpPath(ASegment: TPathSegment);
  221. public
  222. Name: string;
  223. Width, Height: Double; // in millimeters
  224. { Base methods }
  225. constructor Create;
  226. destructor Destroy; override;
  227. procedure Assign(ASource: TvVectorialDocument);
  228. procedure AssignTo(ADest: TvVectorialDocument);
  229. procedure WriteToFile(AFileName: string; AFormat: TvVectorialFormat); overload;
  230. procedure WriteToFile(AFileName: string); overload;
  231. procedure WriteToStream(AStream: TStream; AFormat: TvVectorialFormat);
  232. procedure WriteToStrings(AStrings: TStrings; AFormat: TvVectorialFormat);
  233. procedure ReadFromFile(AFileName: string; AFormat: TvVectorialFormat); overload;
  234. procedure ReadFromFile(AFileName: string); overload;
  235. procedure ReadFromStream(AStream: TStream; AFormat: TvVectorialFormat);
  236. procedure ReadFromStrings(AStrings: TStrings; AFormat: TvVectorialFormat);
  237. class function GetFormatFromExtension(AFileName: string): TvVectorialFormat;
  238. function GetDetailedFileFormat(): string;
  239. { Data reading methods }
  240. function GetPath(ANum: Cardinal): TPath;
  241. function GetPathCount: Integer;
  242. function GetEntity(ANum: Cardinal): TvEntity;
  243. function GetEntitiesCount: Integer;
  244. { Data removing methods }
  245. procedure Clear;
  246. { Data writing methods }
  247. procedure AddEntity(AEntity: TvEntity);
  248. procedure AddPathCopyMem(APath: TPath);
  249. procedure StartPath(AX, AY: Double); overload;
  250. procedure StartPath(); overload;
  251. procedure AddMoveToPath(AX, AY: Double);
  252. procedure AddLineToPath(AX, AY: Double); overload;
  253. procedure AddLineToPath(AX, AY: Double; AColor: TFPColor); overload;
  254. procedure AddLineToPath(AX, AY, AZ: Double); overload;
  255. procedure GetCurrentPathPenPos(var AX, AY: Double);
  256. procedure AddBezierToPath(AX1, AY1, AX2, AY2, AX3, AY3: Double); overload;
  257. procedure AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2, AX3, AY3, AZ3: Double); overload;
  258. procedure SetBrushColor(AColor: TFPColor);
  259. procedure SetBrushStyle(AStyle: TFPBrushStyle);
  260. procedure SetPenColor(AColor: TFPColor);
  261. procedure SetPenStyle(AStyle: TFPPenStyle);
  262. procedure SetPenWidth(AWidth: Integer);
  263. procedure EndPath();
  264. procedure AddText(AX, AY, AZ: Double; FontName: string; FontSize: integer; AText: utf8string); overload;
  265. procedure AddText(AX, AY, AZ: Double; AStr: utf8string); overload;
  266. procedure AddCircle(ACenterX, ACenterY, ACenterZ, ARadius: Double);
  267. procedure AddCircularArc(ACenterX, ACenterY, ACenterZ, ARadius, AStartAngle, AEndAngle: Double; AColor: TFPColor);
  268. procedure AddEllipse(CenterX, CenterY, CenterZ, MajorHalfAxis, MinorHalfAxis, Angle: Double);
  269. // Dimensions
  270. procedure AddAlignedDimension(BaseLeft, BaseRight, DimLeft, DimRight: T3DPoint);
  271. { properties }
  272. property PathCount: Integer read GetPathCount;
  273. property Paths[Index: Cardinal]: TPath read GetPath;
  274. end;
  275. {@@ TvVectorialReader class reference type }
  276. TvVectorialReaderClass = class of TvCustomVectorialReader;
  277. { TvCustomVectorialReader }
  278. TvCustomVectorialReader = class
  279. public
  280. { General reading methods }
  281. constructor Create; virtual;
  282. procedure ReadFromFile(AFileName: string; AData: TvVectorialDocument); virtual;
  283. procedure ReadFromStream(AStream: TStream; AData: TvVectorialDocument); virtual;
  284. procedure ReadFromStrings(AStrings: TStrings; AData: TvVectorialDocument); virtual;
  285. end;
  286. {@@ TvVectorialWriter class reference type }
  287. TvVectorialWriterClass = class of TvCustomVectorialWriter;
  288. {@@ TvCustomVectorialWriter }
  289. { TvCustomVectorialWriter }
  290. TvCustomVectorialWriter = class
  291. public
  292. { General writing methods }
  293. constructor Create; virtual;
  294. procedure WriteToFile(AFileName: string; AData: TvVectorialDocument); virtual;
  295. procedure WriteToStream(AStream: TStream; AData: TvVectorialDocument); virtual;
  296. procedure WriteToStrings(AStrings: TStrings; AData: TvVectorialDocument); virtual;
  297. end;
  298. {@@ List of registered formats }
  299. TvVectorialFormatData = record
  300. ReaderClass: TvVectorialReaderClass;
  301. WriterClass: TvVectorialWriterClass;
  302. ReaderRegistered: Boolean;
  303. WriterRegistered: Boolean;
  304. Format: TvVectorialFormat;
  305. end;
  306. var
  307. GvVectorialFormats: array of TvVectorialFormatData;
  308. procedure RegisterVectorialReader(
  309. AReaderClass: TvVectorialReaderClass;
  310. AFormat: TvVectorialFormat);
  311. procedure RegisterVectorialWriter(
  312. AWriterClass: TvVectorialWriterClass;
  313. AFormat: TvVectorialFormat);
  314. function Make2DPoint(AX, AY: Double): T3DPoint;
  315. implementation
  316. const
  317. Str_Error_Nil_Path = ' The program attempted to add a segment before creating a path';
  318. {@@
  319. Registers a new reader for a format
  320. }
  321. procedure RegisterVectorialReader(
  322. AReaderClass: TvVectorialReaderClass;
  323. AFormat: TvVectorialFormat);
  324. var
  325. i, len: Integer;
  326. FormatInTheList: Boolean;
  327. begin
  328. len := Length(GvVectorialFormats);
  329. FormatInTheList := False;
  330. { First search for the format in the list }
  331. for i := 0 to len - 1 do
  332. begin
  333. if GvVectorialFormats[i].Format = AFormat then
  334. begin
  335. if GvVectorialFormats[i].ReaderRegistered then
  336. raise Exception.Create('RegisterVectorialReader: Reader class for format ' {+ AFormat} + ' already registered.');
  337. GvVectorialFormats[i].ReaderRegistered := True;
  338. GvVectorialFormats[i].ReaderClass := AReaderClass;
  339. FormatInTheList := True;
  340. Break;
  341. end;
  342. end;
  343. { If not already in the list, then add it }
  344. if not FormatInTheList then
  345. begin
  346. SetLength(GvVectorialFormats, len + 1);
  347. GvVectorialFormats[len].ReaderClass := AReaderClass;
  348. GvVectorialFormats[len].WriterClass := nil;
  349. GvVectorialFormats[len].ReaderRegistered := True;
  350. GvVectorialFormats[len].WriterRegistered := False;
  351. GvVectorialFormats[len].Format := AFormat;
  352. end;
  353. end;
  354. {@@
  355. Registers a new writer for a format
  356. }
  357. procedure RegisterVectorialWriter(
  358. AWriterClass: TvVectorialWriterClass;
  359. AFormat: TvVectorialFormat);
  360. var
  361. i, len: Integer;
  362. FormatInTheList: Boolean;
  363. begin
  364. len := Length(GvVectorialFormats);
  365. FormatInTheList := False;
  366. { First search for the format in the list }
  367. for i := 0 to len - 1 do
  368. begin
  369. if GvVectorialFormats[i].Format = AFormat then
  370. begin
  371. if GvVectorialFormats[i].WriterRegistered then
  372. raise Exception.Create('RegisterVectorialWriter: Writer class for format ' + {AFormat +} ' already registered.');
  373. GvVectorialFormats[i].WriterRegistered := True;
  374. GvVectorialFormats[i].WriterClass := AWriterClass;
  375. FormatInTheList := True;
  376. Break;
  377. end;
  378. end;
  379. { If not already in the list, then add it }
  380. if not FormatInTheList then
  381. begin
  382. SetLength(GvVectorialFormats, len + 1);
  383. GvVectorialFormats[len].ReaderClass := nil;
  384. GvVectorialFormats[len].WriterClass := AWriterClass;
  385. GvVectorialFormats[len].ReaderRegistered := False;
  386. GvVectorialFormats[len].WriterRegistered := True;
  387. GvVectorialFormats[len].Format := AFormat;
  388. end;
  389. end;
  390. function Make2DPoint(AX, AY: Double): T3DPoint;
  391. begin
  392. Result.X := AX;
  393. Result.Y := AY;
  394. Result.Z := 0;
  395. end;
  396. { TvEntity }
  397. constructor TvEntity.Create;
  398. begin
  399. Pen.Style := psSolid;
  400. Pen.Color := colBlack;
  401. Brush.Style := bsClear;
  402. Brush.Color := colBlue;
  403. end;
  404. { TvEllipse }
  405. procedure TvEllipse.CalculateBoundingRectangle;
  406. var
  407. t, tmp: Double;
  408. begin
  409. {
  410. To calculate the bounding rectangle we can do this:
  411. Ellipse equations:You could try using the parametrized equations for an ellipse rotated at an arbitrary angle:
  412. x = CenterX + MajorHalfAxis*cos(t)*cos(Angle) - MinorHalfAxis*sin(t)*sin(Angle)
  413. y = CenterY + MinorHalfAxis*sin(t)*cos(Angle) + MajorHalfAxis*cos(t)*sin(Angle)
  414. You can then differentiate and solve for gradient = 0:
  415. 0 = dx/dt = -MajorHalfAxis*sin(t)*cos(Angle) - MinorHalfAxis*cos(t)*sin(Angle)
  416. =>
  417. tan(t) = -MinorHalfAxis*tan(Angle)/MajorHalfAxis
  418. =>
  419. t = cotang(-MinorHalfAxis*tan(Angle)/MajorHalfAxis)
  420. On the other axis:
  421. 0 = dy/dt = b*cos(t)*cos(phi) - a*sin(t)*sin(phi)
  422. =>
  423. tan(t) = b*cot(phi)/a
  424. }
  425. t := cotan(-MinorHalfAxis*tan(Angle)/MajorHalfAxis);
  426. tmp := CenterX + MajorHalfAxis*cos(t)*cos(Angle) - MinorHalfAxis*sin(t)*sin(Angle);
  427. BoundingRect.Right := Round(tmp);
  428. end;
  429. { TsWorksheet }
  430. {@@
  431. Helper method for clearing the records in a spreadsheet.
  432. }
  433. procedure TvVectorialDocument.RemoveCallback(data, arg: pointer);
  434. begin
  435. { if data <> nil then
  436. begin
  437. ldata := PObject(data);
  438. ldata^.Free;
  439. end;}
  440. end;
  441. {@@
  442. Constructor.
  443. }
  444. constructor TvVectorialDocument.Create;
  445. begin
  446. inherited Create;
  447. FEntities := TFPList.Create;
  448. FTmpPath := TPath.Create;
  449. end;
  450. {@@
  451. Destructor.
  452. }
  453. destructor TvVectorialDocument.Destroy;
  454. begin
  455. Clear;
  456. FEntities.Free;
  457. inherited Destroy;
  458. end;
  459. procedure TvVectorialDocument.Assign(ASource: TvVectorialDocument);
  460. var
  461. i: Integer;
  462. begin
  463. Clear;
  464. for i := 0 to ASource.GetEntitiesCount - 1 do
  465. Self.AddEntity(ASource.GetEntity(i));
  466. end;
  467. procedure TvVectorialDocument.AssignTo(ADest: TvVectorialDocument);
  468. begin
  469. ADest.Assign(Self);
  470. end;
  471. procedure TvVectorialDocument.AddPathCopyMem(APath: TPath);
  472. var
  473. lPath: TPath;
  474. Len: Integer;
  475. begin
  476. lPath := TPath.Create;
  477. lPath.Assign(APath);
  478. AddEntity(lPath);
  479. //WriteLn(':>TvVectorialDocument.AddPath 1 Len = ', Len);
  480. end;
  481. {@@
  482. Starts writing a Path in multiple steps.
  483. Should be followed by zero or more calls to AddPointToPath
  484. and by a call to EndPath to effectively add the data.
  485. @see StartPath, AddPointToPath
  486. }
  487. procedure TvVectorialDocument.StartPath(AX, AY: Double);
  488. var
  489. segment: T2DSegment;
  490. begin
  491. ClearTmpPath();
  492. FTmpPath.Len := 1;
  493. segment := T2DSegment.Create;
  494. segment.SegmentType := stMoveTo;
  495. segment.X := AX;
  496. segment.Y := AY;
  497. FTmpPath.Points := segment;
  498. FTmpPath.PointsEnd := segment;
  499. end;
  500. procedure TvVectorialDocument.StartPath();
  501. begin
  502. ClearTmpPath();
  503. end;
  504. procedure TvVectorialDocument.AddMoveToPath(AX, AY: Double);
  505. var
  506. segment: T2DSegment;
  507. begin
  508. segment := T2DSegment.Create;
  509. segment.SegmentType := stMoveTo;
  510. segment.X := AX;
  511. segment.Y := AY;
  512. AppendSegmentToTmpPath(segment);
  513. end;
  514. {@@
  515. Adds one more point to the end of a Path being
  516. writing in multiple steps.
  517. Does nothing if not called between StartPath and EndPath.
  518. Can be called multiple times to add multiple points.
  519. @see StartPath, EndPath
  520. }
  521. procedure TvVectorialDocument.AddLineToPath(AX, AY: Double);
  522. var
  523. segment: T2DSegment;
  524. begin
  525. segment := T2DSegment.Create;
  526. segment.SegmentType := st2DLine;
  527. segment.X := AX;
  528. segment.Y := AY;
  529. AppendSegmentToTmpPath(segment);
  530. end;
  531. procedure TvVectorialDocument.AddLineToPath(AX, AY: Double; AColor: TFPColor);
  532. var
  533. segment: T2DSegmentWithPen;
  534. begin
  535. segment := T2DSegmentWithPen.Create;
  536. segment.SegmentType := st2DLineWithPen;
  537. segment.X := AX;
  538. segment.Y := AY;
  539. segment.Pen.Color := AColor;
  540. AppendSegmentToTmpPath(segment);
  541. end;
  542. procedure TvVectorialDocument.AddLineToPath(AX, AY, AZ: Double);
  543. var
  544. segment: T3DSegment;
  545. begin
  546. segment := T3DSegment.Create;
  547. segment.SegmentType := st3DLine;
  548. segment.X := AX;
  549. segment.Y := AY;
  550. segment.Z := AZ;
  551. AppendSegmentToTmpPath(segment);
  552. end;
  553. {@@
  554. Gets the current Pen Pos in the temporary path
  555. }
  556. procedure TvVectorialDocument.GetCurrentPathPenPos(var AX, AY: Double);
  557. begin
  558. // Check if we are the first segment in the tmp path
  559. if FTmpPath.PointsEnd = nil then raise Exception.Create('[TvVectorialDocument.GetCurrentPathPenPos] One cannot obtain the Pen Pos if there are no segments in the temporary path');
  560. AX := T2DSegment(FTmpPath.PointsEnd).X;
  561. AY := T2DSegment(FTmpPath.PointsEnd).Y;
  562. end;
  563. {@@
  564. Adds a bezier element to the path. It starts where the previous element ended
  565. and it goes throw the control points [AX1, AY1] and [AX2, AY2] and ends
  566. in [AX3, AY3].
  567. }
  568. procedure TvVectorialDocument.AddBezierToPath(AX1, AY1, AX2, AY2, AX3,
  569. AY3: Double);
  570. var
  571. segment: T2DBezierSegment;
  572. begin
  573. segment := T2DBezierSegment.Create;
  574. segment.SegmentType := st2DBezier;
  575. segment.X := AX3;
  576. segment.Y := AY3;
  577. segment.X2 := AX1;
  578. segment.Y2 := AY1;
  579. segment.X3 := AX2;
  580. segment.Y3 := AY2;
  581. AppendSegmentToTmpPath(segment);
  582. end;
  583. procedure TvVectorialDocument.AddBezierToPath(AX1, AY1, AZ1, AX2, AY2, AZ2,
  584. AX3, AY3, AZ3: Double);
  585. var
  586. segment: T3DBezierSegment;
  587. begin
  588. segment := T3DBezierSegment.Create;
  589. segment.SegmentType := st3DBezier;
  590. segment.X := AX3;
  591. segment.Y := AY3;
  592. segment.Z := AZ3;
  593. segment.X2 := AX1;
  594. segment.Y2 := AY1;
  595. segment.Z2 := AZ1;
  596. segment.X3 := AX2;
  597. segment.Y3 := AY2;
  598. segment.Z3 := AZ2;
  599. AppendSegmentToTmpPath(segment);
  600. end;
  601. procedure TvVectorialDocument.SetBrushColor(AColor: TFPColor);
  602. begin
  603. FTmPPath.Brush.Color := AColor;
  604. end;
  605. procedure TvVectorialDocument.SetBrushStyle(AStyle: TFPBrushStyle);
  606. begin
  607. FTmPPath.Brush.Style := AStyle;
  608. end;
  609. procedure TvVectorialDocument.SetPenColor(AColor: TFPColor);
  610. begin
  611. FTmPPath.Pen.Color := AColor;
  612. end;
  613. procedure TvVectorialDocument.SetPenStyle(AStyle: TFPPenStyle);
  614. begin
  615. FTmPPath.Pen.Style := AStyle;
  616. end;
  617. procedure TvVectorialDocument.SetPenWidth(AWidth: Integer);
  618. begin
  619. FTmPPath.Pen.Width := AWidth;
  620. end;
  621. {@@
  622. Finishes writing a Path, which was created in multiple
  623. steps using StartPath and AddPointToPath,
  624. to the document.
  625. Does nothing if there wasn't a previous correspondent call to
  626. StartPath.
  627. @see StartPath, AddPointToPath
  628. }
  629. procedure TvVectorialDocument.EndPath();
  630. begin
  631. if FTmPPath.Len = 0 then Exit;
  632. AddPathCopyMem(FTmPPath);
  633. ClearTmpPath();
  634. end;
  635. procedure TvVectorialDocument.AddText(AX, AY, AZ: Double; FontName: string; FontSize: integer; AText: utf8string);
  636. var
  637. lText: TvText;
  638. begin
  639. lText := TvText.Create;
  640. lText.Value := AText;
  641. lText.X := AX;
  642. lText.Y := AY;
  643. lText.Z := AZ;
  644. lText.Font.Name := FontName;
  645. lText.Font.Size := FontSize;
  646. AddEntity(lText);
  647. end;
  648. procedure TvVectorialDocument.AddText(AX, AY, AZ: Double; AStr: utf8string);
  649. begin
  650. AddText(AX, AY, AZ, '', 10, AStr);
  651. end;
  652. procedure TvVectorialDocument.AddCircle(ACenterX, ACenterY, ACenterZ, ARadius: Double);
  653. var
  654. lCircle: TvCircle;
  655. begin
  656. lCircle := TvCircle.Create;
  657. lCircle.CenterX := ACenterX;
  658. lCircle.CenterY := ACenterY;
  659. lCircle.CenterZ := ACenterZ;
  660. lCircle.Radius := ARadius;
  661. AddEntity(lCircle);
  662. end;
  663. procedure TvVectorialDocument.AddCircularArc(ACenterX, ACenterY, ACenterZ,
  664. ARadius, AStartAngle, AEndAngle: Double; AColor: TFPColor);
  665. var
  666. lCircularArc: TvCircularArc;
  667. begin
  668. lCircularArc := TvCircularArc.Create;
  669. lCircularArc.CenterX := ACenterX;
  670. lCircularArc.CenterY := ACenterY;
  671. lCircularArc.CenterZ := ACenterZ;
  672. lCircularArc.Radius := ARadius;
  673. lCircularArc.StartAngle := AStartAngle;
  674. lCircularArc.EndAngle := AEndAngle;
  675. lCircularArc.Pen.Color := AColor;
  676. AddEntity(lCircularArc);
  677. end;
  678. procedure TvVectorialDocument.AddEllipse(CenterX, CenterY, CenterZ,
  679. MajorHalfAxis, MinorHalfAxis, Angle: Double);
  680. var
  681. lEllipse: TvEllipse;
  682. begin
  683. lEllipse := TvEllipse.Create;
  684. lEllipse.CenterX := CenterX;
  685. lEllipse.CenterY := CenterY;
  686. lEllipse.CenterZ := CenterZ;
  687. lEllipse.MajorHalfAxis := MajorHalfAxis;
  688. lEllipse.MinorHalfAxis := MinorHalfAxis;
  689. lEllipse.Angle := Angle;
  690. AddEntity(lEllipse);
  691. end;
  692. {@@
  693. Don't free the passed TvText because it will be added directly to the list
  694. }
  695. procedure TvVectorialDocument.AddEntity(AEntity: TvEntity);
  696. begin
  697. FEntities.Add(Pointer(AEntity));
  698. end;
  699. procedure TvVectorialDocument.AddAlignedDimension(BaseLeft, BaseRight,
  700. DimLeft, DimRight: T3DPoint);
  701. var
  702. lDim: TvAlignedDimension;
  703. begin
  704. lDim := TvAlignedDimension.Create;
  705. lDim.BaseLeft := BaseLeft;
  706. lDim.BaseRight := BaseRight;
  707. lDim.DimensionLeft := DimLeft;
  708. lDim.DimensionRight := DimRight;
  709. AddEntity(lDim);
  710. end;
  711. {@@
  712. Convenience method which creates the correct
  713. writer object for a given vector graphics document format.
  714. }
  715. function TvVectorialDocument.CreateVectorialWriter(AFormat: TvVectorialFormat): TvCustomVectorialWriter;
  716. var
  717. i: Integer;
  718. begin
  719. Result := nil;
  720. for i := 0 to Length(GvVectorialFormats) - 1 do
  721. if GvVectorialFormats[i].Format = AFormat then
  722. begin
  723. if GvVectorialFormats[i].WriterClass <> nil then
  724. Result := GvVectorialFormats[i].WriterClass.Create;
  725. Break;
  726. end;
  727. if Result = nil then raise Exception.Create('Unsupported vector graphics format.');
  728. end;
  729. {@@
  730. Convenience method which creates the correct
  731. reader object for a given vector graphics document format.
  732. }
  733. function TvVectorialDocument.CreateVectorialReader(AFormat: TvVectorialFormat): TvCustomVectorialReader;
  734. var
  735. i: Integer;
  736. begin
  737. Result := nil;
  738. for i := 0 to Length(GvVectorialFormats) - 1 do
  739. if GvVectorialFormats[i].Format = AFormat then
  740. begin
  741. if GvVectorialFormats[i].ReaderClass <> nil then
  742. Result := GvVectorialFormats[i].ReaderClass.Create;
  743. Break;
  744. end;
  745. if Result = nil then raise Exception.Create('Unsupported vector graphics format.');
  746. end;
  747. procedure TvVectorialDocument.ClearTmpPath();
  748. var
  749. segment, oldsegment: TPathSegment;
  750. begin
  751. // segment := FTmpPath.Points;
  752. // Don't free segments, because they are used when the path is added
  753. // while segment <> nil do
  754. // begin
  755. // oldsegment := segment;
  756. // segment := segment^.Next;
  757. // oldsegment^.Free;
  758. // end;
  759. FTmpPath.Points := nil;
  760. FTmpPath.PointsEnd := nil;
  761. FTmpPath.Len := 0;
  762. FTmpPath.Brush.Color := colBlue;
  763. FTmpPath.Brush.Style := bsClear;
  764. FTmpPath.Pen.Color := colBlack;
  765. FTmpPath.Pen.Style := psSolid;
  766. FTmpPath.Pen.Width := 1;
  767. end;
  768. procedure TvVectorialDocument.AppendSegmentToTmpPath(ASegment: TPathSegment);
  769. var
  770. L: Integer;
  771. begin
  772. // Check if we are the first segment in the tmp path
  773. if FTmpPath.PointsEnd = nil then
  774. begin
  775. if FTmpPath.Len <> 0 then
  776. Exception.Create('[TvVectorialDocument.AppendSegmentToTmpPath]' + Str_Error_Nil_Path);
  777. FTmpPath.Points := ASegment;
  778. FTmpPath.PointsEnd := ASegment;
  779. FTmpPath.Len := 1;
  780. Exit;
  781. end;
  782. L := FTmpPath.Len;
  783. Inc(FTmpPath.Len);
  784. // Adds the element to the end of the list
  785. FTmpPath.PointsEnd.Next := ASegment;
  786. ASegment.Previous := FTmpPath.PointsEnd;
  787. FTmpPath.PointsEnd := ASegment;
  788. end;
  789. {@@
  790. Writes the document to a file.
  791. If the file doesn't exist, it will be created.
  792. }
  793. procedure TvVectorialDocument.WriteToFile(AFileName: string; AFormat: TvVectorialFormat);
  794. var
  795. AWriter: TvCustomVectorialWriter;
  796. begin
  797. AWriter := CreateVectorialWriter(AFormat);
  798. try
  799. AWriter.WriteToFile(AFileName, Self);
  800. finally
  801. AWriter.Free;
  802. end;
  803. end;
  804. procedure TvVectorialDocument.WriteToFile(AFileName: string);
  805. var
  806. lFormat: TvVectorialFormat;
  807. begin
  808. lFormat := GetFormatFromExtension(ExtractFileExt(AFileName));
  809. WriteToFile(AFileName, lFormat);
  810. end;
  811. {@@
  812. Writes the document to a stream
  813. }
  814. procedure TvVectorialDocument.WriteToStream(AStream: TStream; AFormat: TvVectorialFormat);
  815. var
  816. AWriter: TvCustomVectorialWriter;
  817. begin
  818. AWriter := CreateVectorialWriter(AFormat);
  819. try
  820. AWriter.WriteToStream(AStream, Self);
  821. finally
  822. AWriter.Free;
  823. end;
  824. end;
  825. procedure TvVectorialDocument.WriteToStrings(AStrings: TStrings;
  826. AFormat: TvVectorialFormat);
  827. var
  828. AWriter: TvCustomVectorialWriter;
  829. begin
  830. AWriter := CreateVectorialWriter(AFormat);
  831. try
  832. AWriter.WriteToStrings(AStrings, Self);
  833. finally
  834. AWriter.Free;
  835. end;
  836. end;
  837. {@@
  838. Reads the document from a file.
  839. Any current contents will be removed.
  840. }
  841. procedure TvVectorialDocument.ReadFromFile(AFileName: string;
  842. AFormat: TvVectorialFormat);
  843. var
  844. AReader: TvCustomVectorialReader;
  845. begin
  846. Self.Clear;
  847. AReader := CreateVectorialReader(AFormat);
  848. try
  849. AReader.ReadFromFile(AFileName, Self);
  850. finally
  851. AReader.Free;
  852. end;
  853. end;
  854. {@@
  855. Reads the document from a file. A variant that auto-detects the format from the extension.
  856. }
  857. procedure TvVectorialDocument.ReadFromFile(AFileName: string);
  858. var
  859. lFormat: TvVectorialFormat;
  860. begin
  861. lFormat := GetFormatFromExtension(ExtractFileExt(AFileName));
  862. ReadFromFile(AFileName, lFormat);
  863. end;
  864. {@@
  865. Reads the document from a stream.
  866. Any current contents will be removed.
  867. }
  868. procedure TvVectorialDocument.ReadFromStream(AStream: TStream;
  869. AFormat: TvVectorialFormat);
  870. var
  871. AReader: TvCustomVectorialReader;
  872. begin
  873. Self.Clear;
  874. AReader := CreateVectorialReader(AFormat);
  875. try
  876. AReader.ReadFromStream(AStream, Self);
  877. finally
  878. AReader.Free;
  879. end;
  880. end;
  881. procedure TvVectorialDocument.ReadFromStrings(AStrings: TStrings;
  882. AFormat: TvVectorialFormat);
  883. var
  884. AReader: TvCustomVectorialReader;
  885. begin
  886. Self.Clear;
  887. AReader := CreateVectorialReader(AFormat);
  888. try
  889. AReader.ReadFromStrings(AStrings, Self);
  890. finally
  891. AReader.Free;
  892. end;
  893. end;
  894. class function TvVectorialDocument.GetFormatFromExtension(AFileName: string
  895. ): TvVectorialFormat;
  896. var
  897. lExt: string;
  898. begin
  899. lExt := ExtractFileExt(AFileName);
  900. if AnsiCompareText(lExt, STR_PDF_EXTENSION) = 0 then Result := vfPDF
  901. else if AnsiCompareText(lExt, STR_POSTSCRIPT_EXTENSION) = 0 then Result := vfPostScript
  902. else if AnsiCompareText(lExt, STR_SVG_EXTENSION) = 0 then Result := vfSVG
  903. else if AnsiCompareText(lExt, STR_CORELDRAW_EXTENSION) = 0 then Result := vfCorelDrawCDR
  904. else if AnsiCompareText(lExt, STR_WINMETAFILE_EXTENSION) = 0 then Result := vfWindowsMetafileWMF
  905. else if AnsiCompareText(lExt, STR_AUTOCAD_EXCHANGE_EXTENSION) = 0 then Result := vfDXF
  906. else if AnsiCompareText(lExt, STR_ENCAPSULATEDPOSTSCRIPT_EXTENSION) = 0 then Result := vfEncapsulatedPostScript
  907. else
  908. raise Exception.Create('TvVectorialDocument.GetFormatFromExtension: The extension (' + lExt + ') doesn''t match any supported formats.');
  909. end;
  910. function TvVectorialDocument.GetDetailedFileFormat(): string;
  911. begin
  912. end;
  913. function TvVectorialDocument.GetPath(ANum: Cardinal): TPath;
  914. var
  915. i: Integer;
  916. Index: Integer = - 1;
  917. begin
  918. Result := nil;
  919. if ANum >= FEntities.Count then raise Exception.Create('TvVectorialDocument.GetPath: Path number out of bounds');
  920. for i := 0 to FEntities.Count - 1 do
  921. begin
  922. if TvEntity(FEntities.Items[i]) is TPath then
  923. begin
  924. Inc(Index);
  925. if Index = ANum then Result := TPath(FEntities.Items[i]);
  926. end;
  927. end;
  928. end;
  929. function TvVectorialDocument.GetPathCount: Integer;
  930. var
  931. i: Integer;
  932. begin
  933. Result := 0;
  934. for i := 0 to FEntities.Count - 1 do
  935. if TvEntity(FEntities.Items[i]) is TPath then Inc(Result);
  936. end;
  937. function TvVectorialDocument.GetEntity(ANum: Cardinal): TvEntity;
  938. begin
  939. if ANum >= FEntities.Count then raise Exception.Create('TvVectorialDocument.GetEntity: Entity number out of bounds');
  940. if FEntities.Items[ANum] = nil then raise Exception.Create('TvVectorialDocument.GetEntity: Invalid Entity number');
  941. Result := TvEntity(FEntities.Items[ANum]);
  942. end;
  943. function TvVectorialDocument.GetEntitiesCount: Integer;
  944. begin
  945. Result := FEntities.Count;
  946. end;
  947. {@@
  948. Clears all data in the document
  949. }
  950. procedure TvVectorialDocument.Clear;
  951. begin
  952. FEntities.Clear();
  953. end;
  954. { TvCustomVectorialReader }
  955. constructor TvCustomVectorialReader.Create;
  956. begin
  957. inherited Create;
  958. end;
  959. procedure TvCustomVectorialReader.ReadFromFile(AFileName: string; AData: TvVectorialDocument);
  960. var
  961. FileStream: TFileStream;
  962. begin
  963. FileStream := TFileStream.Create(AFileName, fmOpenRead or fmShareDenyNone);
  964. try
  965. ReadFromStream(FileStream, AData);
  966. finally
  967. FileStream.Free;
  968. end;
  969. end;
  970. procedure TvCustomVectorialReader.ReadFromStream(AStream: TStream;
  971. AData: TvVectorialDocument);
  972. var
  973. AStringStream: TStringStream;
  974. AStrings: TStringList;
  975. begin
  976. AStringStream := TStringStream.Create('');
  977. AStrings := TStringList.Create;
  978. try
  979. AStringStream.CopyFrom(AStream, AStream.Size);
  980. AStringStream.Seek(0, soFromBeginning);
  981. AStrings.Text := AStringStream.DataString;
  982. ReadFromStrings(AStrings, AData);
  983. finally
  984. AStringStream.Free;
  985. AStrings.Free;
  986. end;
  987. end;
  988. procedure TvCustomVectorialReader.ReadFromStrings(AStrings: TStrings;
  989. AData: TvVectorialDocument);
  990. var
  991. AStringStream: TStringStream;
  992. begin
  993. AStringStream := TStringStream.Create('');
  994. try
  995. AStringStream.WriteString(AStrings.Text);
  996. AStringStream.Seek(0, soFromBeginning);
  997. ReadFromStream(AStringStream, AData);
  998. finally
  999. AStringStream.Free;
  1000. end;
  1001. end;
  1002. { TsCustomSpreadWriter }
  1003. constructor TvCustomVectorialWriter.Create;
  1004. begin
  1005. inherited Create;
  1006. end;
  1007. {@@
  1008. Default file writting method.
  1009. Opens the file and calls WriteToStream
  1010. @param AFileName The output file name.
  1011. If the file already exists it will be replaced.
  1012. @param AData The Workbook to be saved.
  1013. @see TsWorkbook
  1014. }
  1015. procedure TvCustomVectorialWriter.WriteToFile(AFileName: string; AData: TvVectorialDocument);
  1016. var
  1017. OutputFile: TFileStream;
  1018. begin
  1019. OutputFile := TFileStream.Create(AFileName, fmCreate or fmOpenWrite);
  1020. try
  1021. WriteToStream(OutputFile, AData);
  1022. finally
  1023. OutputFile.Free;
  1024. end;
  1025. end;
  1026. {@@
  1027. The default stream writer just uses WriteToStrings
  1028. }
  1029. procedure TvCustomVectorialWriter.WriteToStream(AStream: TStream;
  1030. AData: TvVectorialDocument);
  1031. var
  1032. lStringList: TStringList;
  1033. begin
  1034. lStringList := TStringList.Create;
  1035. try
  1036. WriteToStrings(lStringList, AData);
  1037. lStringList.SaveToStream(AStream);
  1038. finally
  1039. lStringList.Free;
  1040. end;
  1041. end;
  1042. procedure TvCustomVectorialWriter.WriteToStrings(AStrings: TStrings;
  1043. AData: TvVectorialDocument);
  1044. begin
  1045. end;
  1046. { TPath }
  1047. procedure TPath.Assign(ASource: TPath);
  1048. begin
  1049. Len := ASource.Len;
  1050. Points := ASource.Points;
  1051. PointsEnd := ASource.PointsEnd;
  1052. CurPoint := ASource.CurPoint;
  1053. Pen := ASource.Pen;
  1054. Brush := ASource.Brush;
  1055. end;
  1056. procedure TPath.PrepareForSequentialReading;
  1057. begin
  1058. CurPoint := nil;
  1059. end;
  1060. function TPath.Next(): TPathSegment;
  1061. begin
  1062. if CurPoint = nil then Result := Points
  1063. else Result := CurPoint.Next;
  1064. CurPoint := Result;
  1065. end;
  1066. finalization
  1067. SetLength(GvVectorialFormats, 0);
  1068. end.