fSvgPath.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503
  1. unit fSvgPath;
  2. // NOTE: This demo is yet incomplete and needs finishing until v2.0 can be
  3. // released!
  4. interface
  5. {$I GR32.inc}
  6. uses
  7. Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  8. ExtCtrls, StdCtrls, GR32, GR32_Image, GR32_Polygons, GR32_Paths, GR32_Brushes;
  9. type
  10. TFrmSvgPathRenderer = class(TForm)
  11. Image32: TImage32;
  12. LblPathData: TLabel;
  13. EditSVGPath: TEdit;
  14. ShpFillColor: TShape;
  15. ShpStrokeColor: TShape;
  16. ColorDialog: TColorDialog;
  17. procedure FormCreate(Sender: TObject);
  18. procedure FormDestroy(Sender: TObject);
  19. procedure EditSVGPathChange(Sender: TObject);
  20. procedure EditSVGPathKeyPress(Sender: TObject; var Key: Char);
  21. procedure Image32Resize(Sender: TObject);
  22. procedure ShpFillColorMouseDown(Sender: TObject; Button: TMouseButton;
  23. Shift: TShiftState; X, Y: Integer);
  24. procedure ShpStrokeColorMouseDown(Sender: TObject; Button: TMouseButton;
  25. Shift: TShiftState; X, Y: Integer);
  26. private
  27. FCanvas32: TCanvas32;
  28. FFill: TSolidBrush;
  29. FStroke: TStrokeBrush;
  30. procedure RenderPath(Path: AnsiString);
  31. procedure UpdatePath;
  32. end;
  33. var
  34. FrmSvgPathRenderer: TFrmSvgPathRenderer;
  35. implementation
  36. {$IFDEF FPC}
  37. {$R *.lfm}
  38. {$ELSE}
  39. {$R *.dfm}
  40. {$ENDIF}
  41. uses
  42. Math, Types, UITypes;
  43. resourcestring
  44. RCStrNotYetImplemented = 'Not yet implemented!';
  45. RCStrUnknownFirstCommand = 'Unknown first command (%s)';
  46. RCStrUnknownCommand = 'Unknown command (%s)';
  47. procedure TFrmSvgPathRenderer.FormCreate(Sender: TObject);
  48. begin
  49. Image32.Bitmap.SetSize(Image32.Width, Image32.Height);
  50. FCanvas32 := TCanvas32.Create(Image32.Bitmap);;
  51. FFill := TSolidBrush.Create(FCanvas32.Brushes);
  52. FFill.FillColor := SetAlpha(Color32(ShpFillColor.Brush.Color), $7F);
  53. FStroke := TStrokeBrush.Create(FCanvas32.Brushes);
  54. FStroke.FillColor := Color32(ShpStrokeColor.Brush.Color);
  55. FStroke.StrokeWidth := 3;
  56. FStroke.FillMode := pfWinding;
  57. end;
  58. procedure TFrmSvgPathRenderer.FormDestroy(Sender: TObject);
  59. begin
  60. FCanvas32.Free;
  61. end;
  62. procedure TFrmSvgPathRenderer.Image32Resize(Sender: TObject);
  63. begin
  64. Image32.Bitmap.SetSize(Image32.Width, Image32.Height);
  65. EditSVGPathChange(Sender);
  66. end;
  67. procedure TFrmSvgPathRenderer.EditSVGPathKeyPress(Sender: TObject; var Key: Char);
  68. begin
  69. if Key = #13 then
  70. EditSVGPathChange(Sender);
  71. end;
  72. procedure TFrmSvgPathRenderer.EditSVGPathChange(Sender: TObject);
  73. begin
  74. UpdatePath;
  75. end;
  76. procedure TFrmSvgPathRenderer.UpdatePath;
  77. begin
  78. try
  79. if Length(EditSVGPath.Text) > 0 then
  80. RenderPath(AnsiString(EditSVGPath.Text));
  81. except
  82. on E: Exception do MessageDlg(E.Message, mtError, [mbOK], 0);
  83. end;
  84. end;
  85. procedure TFrmSvgPathRenderer.RenderPath(Path: AnsiString);
  86. type
  87. TPointF = record
  88. X, Y: Double;
  89. end;
  90. TSvgPathCommand = (pcMoveTo, pcLineTo, pcHorizontalLineTo, pcVerticalLineTo,
  91. pcCubicTo, pcSmoothCubicTo, pcQuadTo, pcSmoothQuadTo, pcArcTo, pcClosePath);
  92. const
  93. CDeg2Rad = Pi / 180;
  94. var
  95. Index: Integer;
  96. Command: TSvgPathCommand;
  97. Relative, ReadCommand: Boolean;
  98. LastPos, Current, Radius, FirstPoint: TPointF;
  99. Control: array [0..1] of TPointF;
  100. LargeArc, SweepFlag: Boolean;
  101. Angle: Double;
  102. procedure SkipWhitespaces;
  103. begin
  104. while Index <= Length(Path) do
  105. if Path[Index] in [#$20, #$9, #$D, #$A] then
  106. Inc(Index)
  107. else
  108. Break;
  109. end;
  110. procedure CommaWhitespaces;
  111. begin
  112. while Index <= Length(Path) do
  113. if Path[Index] in [#$20, #$9, #$D, #$A, ','] then
  114. Inc(Index)
  115. else
  116. Break;
  117. end;
  118. function ReadDigitSequence: string;
  119. begin
  120. while Index <= Length(Path) do
  121. begin
  122. if Path[Index] in ['0'..'9'] then
  123. Result := Result + Char(Path[Index])
  124. else
  125. Break;
  126. Inc(Index);
  127. end;
  128. end;
  129. function ReadNumber: Double;
  130. var
  131. FloatStr: string;
  132. begin
  133. if Index > Length(Path) then
  134. raise Exception.Create('No Data');
  135. if Path[Index] in ['+', '-'] then
  136. begin
  137. // actually read sign
  138. FloatStr := Path[Index];
  139. Inc(Index);
  140. end
  141. else
  142. FloatStr := '';
  143. FloatStr := FloatStr + ReadDigitSequence;
  144. if (Index < Length(Path)) and (Path[Index] = '.') then
  145. begin
  146. // fractional number
  147. Inc(Index);
  148. FloatStr := FloatStr + FormatSettings.DecimalSeparator + ReadDigitSequence;
  149. end;
  150. if (Index < Length(Path)) and (Path[Index] in ['e', 'E']) then
  151. begin
  152. FloatStr := FloatStr + 'E';
  153. Inc(Index);
  154. // eventually read exponent sign
  155. if (Index < Length(Path)) and (Path[Index] in ['+', '-']) then
  156. begin
  157. FloatStr := FloatStr + Path[Index];
  158. Inc(Index);
  159. end;
  160. FloatStr := FloatStr + ReadDigitSequence;
  161. end;
  162. Result := StrToFloat(FloatStr);
  163. end;
  164. procedure ReadPoint(var Point: TPointF);
  165. begin
  166. Point.X := ReadNumber;
  167. CommaWhitespaces;
  168. Point.Y := ReadNumber;
  169. SkipWhitespaces;
  170. end;
  171. procedure ArcEndpointToCenterParameterization;
  172. var
  173. StartAngle, DeltaAngle, MaxRadius: Double;
  174. ComplexAngle, TempStart, TempCenter, Center, Scale: TPointF;
  175. RadLen, Numr, Denr, Sig: Double;
  176. begin
  177. SinCos(Angle, ComplexAngle.X, ComplexAngle.Y);
  178. if Radius.X = Radius.Y then
  179. begin
  180. MaxRadius := Radius.X;
  181. TempStart.X := ComplexAngle.Y * (LastPos.X - Current.X) * 0.5 + ComplexAngle.X * (LastPos.Y - Current.Y) * 0.5;
  182. TempStart.Y := -ComplexAngle.X * (LastPos.X - Current.X) * 0.5 + ComplexAngle.Y * (LastPos.Y - Current.Y) * 0.5;
  183. RadLen := (Sqr(TempStart.X) + Sqr(TempStart.Y)) / Sqr(MaxRadius);
  184. if RadLen > 1 then
  185. MaxRadius := MaxRadius * Sqrt(RadLen);
  186. // compute (cx', cy')
  187. if LargeArc = SweepFlag then
  188. Sig := -1
  189. else
  190. Sig := 1;
  191. Sig := Sig * Sqrt(Sqr(MaxRadius) / (Sqr(TempStart.Y) + Sqr(TempStart.X)) - 1);
  192. if IsNaN(Sig) or (Abs(Sig) < 1E-6) then
  193. Sig := 0;
  194. TempCenter.x := Sig * TempStart.Y;
  195. TempCenter.y := -Sig * TempStart.X;
  196. Center.x := (LastPos.X + Current.X) * 0.5 + ComplexAngle.Y * TempCenter.x - ComplexAngle.X * TempCenter.y;
  197. Center.y := (LastPos.Y + Current.Y) * 0.5 + ComplexAngle.X * TempCenter.x + ComplexAngle.Y * TempCenter.y;
  198. StartAngle := ArcTan2(TempStart.y - TempCenter.y,
  199. TempStart.X - TempCenter.x);
  200. DeltaAngle := Pi + ArcTan2(TempStart.y + TempCenter.y,
  201. TempStart.X + TempCenter.x);
  202. if (SweepFlag = False) and (StartAngle > 0) then
  203. StartAngle := StartAngle - 2 * Pi;
  204. if (SweepFlag = True) and (StartAngle < 0) then
  205. StartAngle := StartAngle + 2 * Pi;
  206. end
  207. else
  208. begin
  209. TempStart.X := ComplexAngle.Y * (LastPos.X - Current.X) * 0.5 + ComplexAngle.X * (LastPos.Y - Current.Y) * 0.5;
  210. TempStart.Y := -ComplexAngle.X * (LastPos.X - Current.X) * 0.5 + ComplexAngle.Y * (LastPos.Y - Current.Y) * 0.5;
  211. RadLen := Sqr(TempStart.X) / Sqr(Radius.X) + Sqr(TempStart.Y) / Sqr(Radius.Y);
  212. if RadLen > 1 then
  213. begin
  214. Radius.X := Radius.X * Sqrt(RadLen);
  215. Radius.Y := Radius.Y * Sqrt(RadLen);
  216. end;
  217. // compute (cx', cy')
  218. Numr := Sqr(Radius.X) * (Sqr(Radius.Y) - Sqr(TempStart.Y)) - Sqr(Radius.Y) * Sqr(TempStart.X);
  219. Denr := Sqr(Radius.X) * Sqr(TempStart.Y) + Sqr(Radius.Y) * Sqr(TempStart.X);
  220. if LargeArc = SweepFlag then
  221. Sig := -1
  222. else
  223. Sig := 1;
  224. Sig := Sig * Sqrt(Numr / Denr);
  225. if IsNaN(Sig) or (Abs(Sig) < 1E-6) then
  226. Sig := 0;
  227. TempCenter.x := Sig * Radius.x * TempStart.Y / Radius.y;
  228. TempCenter.y := Sig * -Radius.y * TempStart.X / Radius.x;
  229. // compute (cx, cy) from (cx', cy')
  230. Center.x := (LastPos.X + Current.X) * 0.5 + ComplexAngle.Y * TempCenter.x - ComplexAngle.X * TempCenter.y;
  231. Center.y := (LastPos.Y + Current.Y) * 0.5 + ComplexAngle.X * TempCenter.x + ComplexAngle.Y * TempCenter.y;
  232. StartAngle := ArcTan2((TempStart.y - TempCenter.y) / Radius.y,
  233. (TempStart.X - TempCenter.x) / Radius.X);
  234. DeltaAngle := Pi + ArcTan2((TempStart.y + TempCenter.y) / Radius.y,
  235. (TempStart.X + TempCenter.x) / Radius.X);
  236. if (SweepFlag = False) and (StartAngle > 0) then
  237. StartAngle := StartAngle - 2 * Pi;
  238. if (SweepFlag = True) and (StartAngle < 0) then
  239. StartAngle := StartAngle + 2 * Pi;
  240. if Radius.X > Radius.Y then
  241. begin
  242. MaxRadius := Radius.X;
  243. Scale.x := 1;
  244. Scale.y := Radius.y / Radius.X;
  245. end
  246. else
  247. begin
  248. MaxRadius := Radius.Y;
  249. Scale.x := Radius.x / Radius.y;
  250. Scale.y := 1;
  251. end;
  252. end;
  253. end;
  254. begin
  255. FormatSettings.DecimalSeparator := '.';
  256. Image32.Bitmap.Clear($FFFFFFFF);
  257. // ignore all whitespaces ahead
  258. Index := 1;
  259. SkipWhitespaces;
  260. // check first path command is a move (absolute/relative)
  261. if Path[Index] in ['m', 'M'] then
  262. Command := pcLineTo // all subsequent coordinates are LineTo segments!
  263. else
  264. raise Exception.CreateFmt(RCStrUnknownFirstCommand, [Path[Index]]);
  265. Relative := Ord(Path[Index]) > 60;
  266. Inc(Index);
  267. SkipWhitespaces;
  268. Current.X := ReadNumber;
  269. CommaWhitespaces;
  270. SkipWhitespaces;
  271. Current.Y := ReadNumber;
  272. SkipWhitespaces;
  273. LastPos := FirstPoint;
  274. LastPos := Current;
  275. // ToDo: Evaluate 'Relative', implement subsequent LineTo commands!
  276. FCanvas32.Path.BeginPath;
  277. FCanvas32.Path.MoveTo(LastPos.X, LastPos.Y);
  278. while Index <= Length(Path) do
  279. begin
  280. ReadCommand := True;
  281. case Path[Index] of
  282. 'M', 'm': Command := pcMoveTo;
  283. 'L', 'l': Command := pcLineTo;
  284. 'H', 'h': Command := pcHorizontalLineTo;
  285. 'V', 'v': Command := pcVerticalLineTo;
  286. 'C', 'c': Command := pcCubicTo;
  287. 'Q', 'q': Command := pcQuadTo;
  288. 'S', 's': Command := pcSmoothCubicTo;
  289. 'T', 't': Command := pcSmoothQuadTo;
  290. 'A', 'a': Command := pcArcTo;
  291. 'Z', 'z': Command := pcClosePath;
  292. '0'..'9', '+', '-', '.': ReadCommand := False;
  293. else
  294. raise Exception.CreateFmt(RCStrUnknownCommand, [Path[1]]);
  295. end;
  296. if ReadCommand then
  297. begin
  298. Relative := Ord(Path[Index]) > $60;
  299. Inc(Index);
  300. SkipWhitespaces;
  301. end;
  302. case Command of
  303. pcHorizontalLineTo:
  304. begin
  305. Current.X := ReadNumber;
  306. SkipWhitespaces;
  307. end;
  308. pcVerticalLineTo:
  309. begin
  310. Current.Y := ReadNumber;
  311. SkipWhitespaces;
  312. end;
  313. pcMoveTo, pcLineTo, pcSmoothQuadTo:
  314. begin
  315. ReadPoint(Current);
  316. end;
  317. pcSmoothCubicTo, pcQuadTo:
  318. begin
  319. ReadPoint(Control[0]);
  320. ReadPoint(Current);
  321. end;
  322. pcCubicTo:
  323. begin
  324. ReadPoint(Control[0]);
  325. ReadPoint(Control[1]);
  326. ReadPoint(Current);
  327. end;
  328. pcArcTo:
  329. begin
  330. ReadPoint(Radius);
  331. Angle := ReadNumber;
  332. SkipWhitespaces;
  333. // large arc flag
  334. LargeArc := Path[Index] = '1';
  335. Inc(Index);
  336. SkipWhitespaces;
  337. // sweep-flag
  338. SweepFlag := Path[Index] = '1';
  339. Inc(Index);
  340. SkipWhitespaces;
  341. ReadPoint(Current);
  342. end;
  343. end;
  344. case Command of
  345. pcMoveTo:
  346. begin
  347. FCanvas32.Path.EndPath;
  348. FCanvas32.Path.BeginPath;
  349. if Relative then
  350. FCanvas32.Path.MoveToRelative(Current.X, Current.Y)
  351. else
  352. FCanvas32.Path.MoveTo(Current.X, Current.Y);
  353. Command := pcLineTo; // all subsequent coordinates are LineTo segments!
  354. end;
  355. pcLineTo:
  356. if Relative then
  357. FCanvas32.Path.LineToRelative(Current.X, Current.Y)
  358. else
  359. FCanvas32.Path.LineTo(Current.X, Current.Y);
  360. pcHorizontalLineTo:
  361. if Relative then
  362. FCanvas32.Path.HorizontalLineToRelative(Current.X)
  363. else
  364. FCanvas32.Path.HorizontalLineTo(Current.X);
  365. pcVerticalLineTo:
  366. if Relative then
  367. FCanvas32.Path.VerticalLineToRelative(Current.Y)
  368. else
  369. FCanvas32.Path.VerticalLineTo(Current.Y);
  370. pcSmoothQuadTo:
  371. begin
  372. if Relative then
  373. FCanvas32.Path.ConicToRelative(Current.X, Current.Y)
  374. else
  375. FCanvas32.Path.ConicTo(Current.X, Current.Y)
  376. end;
  377. pcQuadTo:
  378. begin
  379. if Relative then
  380. FCanvas32.Path.ConicToRelative(Control[0].X, Control[0].Y,
  381. Current.X, Current.Y)
  382. else
  383. FCanvas32.Path.ConicTo(Control[0].X, Control[0].Y,
  384. Current.X, Current.Y);
  385. end;
  386. pcSmoothCubicTo:
  387. if Relative then
  388. FCanvas32.Path.CurveToRelative(Control[0].X,
  389. Control[0].Y, Current.X, Current.Y)
  390. else
  391. FCanvas32.Path.CurveTo(Control[0].X,
  392. Control[0].Y, Current.X, Current.Y);
  393. // raise Exception.Create(RCStrNotYetImplemented);
  394. pcCubicTo:
  395. if Relative then
  396. FCanvas32.Path.CurveToRelative(Control[0].X, Control[0].Y,
  397. Control[1].X, Control[1].Y, Current.X, Current.Y)
  398. else
  399. FCanvas32.Path.CurveTo(Control[0].X, Control[0].Y, Control[1].X,
  400. Control[1].Y, Current.X, Current.Y);
  401. pcArcTo:
  402. begin
  403. raise Exception.Create(RCStrNotYetImplemented);
  404. // FCanvas32.Path.Arc(Radius.X, Radius.Y, );
  405. //ArcEndpointToCenterParameterization;
  406. //raise Exception.Create(RCStrNotYetImplemented);
  407. end;
  408. pcClosePath:
  409. begin
  410. FCanvas32.Path.ClosePath;
  411. Current := FirstPoint;
  412. end;
  413. end;
  414. LastPos := Current;
  415. end;
  416. FCanvas32.Path.EndPath;
  417. end;
  418. procedure TFrmSvgPathRenderer.ShpFillColorMouseDown(Sender: TObject;
  419. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  420. begin
  421. ColorDialog.Color := ShpFillColor.Brush.Color;
  422. if ColorDialog.Execute then
  423. begin
  424. ShpFillColor.Brush.Color := ColorDialog.Color;
  425. FFill.FillColor := SetAlpha(Color32(ShpFillColor.Brush.Color), $7F);
  426. UpdatePath;
  427. end;
  428. end;
  429. procedure TFrmSvgPathRenderer.ShpStrokeColorMouseDown(Sender: TObject;
  430. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  431. begin
  432. ColorDialog.Color := ShpStrokeColor.Brush.Color;
  433. if ColorDialog.Execute then
  434. begin
  435. ShpStrokeColor.Brush.Color := ColorDialog.Color;
  436. FStroke.FillColor := Color32(ShpStrokeColor.Brush.Color);
  437. UpdatePath;
  438. end;
  439. end;
  440. end.