MainUnit.pas 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  1. unit MainUnit;
  2. // NOTE: This demo is yet incomplete and needs finishing until v2.0 can be
  3. // released!
  4. interface
  5. {$include 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 := string(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 + string(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.MoveTo(LastPos.X, LastPos.Y);
  277. while Index <= Length(Path) do
  278. begin
  279. ReadCommand := True;
  280. case Path[Index] of
  281. 'M', 'm': Command := pcMoveTo;
  282. 'L', 'l': Command := pcLineTo;
  283. 'H', 'h': Command := pcHorizontalLineTo;
  284. 'V', 'v': Command := pcVerticalLineTo;
  285. 'C', 'c': Command := pcCubicTo;
  286. 'Q', 'q': Command := pcQuadTo;
  287. 'S', 's': Command := pcSmoothCubicTo;
  288. 'T', 't': Command := pcSmoothQuadTo;
  289. 'A', 'a': Command := pcArcTo;
  290. 'Z', 'z': Command := pcClosePath;
  291. '0'..'9', '+', '-', '.': ReadCommand := False;
  292. else
  293. raise Exception.CreateFmt(RCStrUnknownCommand, [Path[1]]);
  294. end;
  295. if ReadCommand then
  296. begin
  297. Relative := Ord(Path[Index]) > $60;
  298. Inc(Index);
  299. SkipWhitespaces;
  300. end;
  301. case Command of
  302. pcHorizontalLineTo:
  303. begin
  304. Current.X := ReadNumber;
  305. SkipWhitespaces;
  306. end;
  307. pcVerticalLineTo:
  308. begin
  309. Current.Y := ReadNumber;
  310. SkipWhitespaces;
  311. end;
  312. pcMoveTo, pcLineTo, pcSmoothQuadTo:
  313. begin
  314. ReadPoint(Current);
  315. end;
  316. pcSmoothCubicTo, pcQuadTo:
  317. begin
  318. ReadPoint(Control[0]);
  319. ReadPoint(Current);
  320. end;
  321. pcCubicTo:
  322. begin
  323. ReadPoint(Control[0]);
  324. ReadPoint(Control[1]);
  325. ReadPoint(Current);
  326. end;
  327. pcArcTo:
  328. begin
  329. ReadPoint(Radius);
  330. Angle := ReadNumber;
  331. SkipWhitespaces;
  332. // large arc flag
  333. LargeArc := Path[Index] = '1';
  334. Inc(Index);
  335. SkipWhitespaces;
  336. // sweep-flag
  337. SweepFlag := Path[Index] = '1';
  338. Inc(Index);
  339. SkipWhitespaces;
  340. ReadPoint(Current);
  341. end;
  342. end;
  343. case Command of
  344. pcMoveTo:
  345. begin
  346. // MoveTo performs an implicit EndPath;
  347. if Relative then
  348. FCanvas32.MoveToRelative(Current.X, Current.Y)
  349. else
  350. FCanvas32.MoveTo(Current.X, Current.Y);
  351. Command := pcLineTo; // all subsequent coordinates are LineTo segments!
  352. end;
  353. pcLineTo:
  354. if Relative then
  355. FCanvas32.LineToRelative(Current.X, Current.Y)
  356. else
  357. FCanvas32.LineTo(Current.X, Current.Y);
  358. pcHorizontalLineTo:
  359. if Relative then
  360. FCanvas32.HorizontalLineToRelative(Current.X)
  361. else
  362. FCanvas32.HorizontalLineTo(Current.X);
  363. pcVerticalLineTo:
  364. if Relative then
  365. FCanvas32.VerticalLineToRelative(Current.Y)
  366. else
  367. FCanvas32.VerticalLineTo(Current.Y);
  368. pcSmoothQuadTo:
  369. begin
  370. if Relative then
  371. FCanvas32.ConicToRelative(Current.X, Current.Y)
  372. else
  373. FCanvas32.ConicTo(Current.X, Current.Y)
  374. end;
  375. pcQuadTo:
  376. begin
  377. if Relative then
  378. FCanvas32.ConicToRelative(Control[0].X, Control[0].Y, Current.X, Current.Y)
  379. else
  380. FCanvas32.ConicTo(Control[0].X, Control[0].Y, Current.X, Current.Y);
  381. end;
  382. pcSmoothCubicTo:
  383. if Relative then
  384. FCanvas32.CurveToRelative(Control[0].X, Control[0].Y, Current.X, Current.Y)
  385. else
  386. FCanvas32.CurveTo(Control[0].X, Control[0].Y, Current.X, Current.Y);
  387. pcCubicTo:
  388. if Relative then
  389. FCanvas32.CurveToRelative(Control[0].X, Control[0].Y, Control[1].X, Control[1].Y, Current.X, Current.Y)
  390. else
  391. FCanvas32.CurveTo(Control[0].X, Control[0].Y, Control[1].X, Control[1].Y, Current.X, Current.Y);
  392. pcArcTo:
  393. begin
  394. raise Exception.Create(RCStrNotYetImplemented);
  395. // FCanvas32.Arc(Radius.X, Radius.Y, );
  396. //ArcEndpointToCenterParameterization;
  397. end;
  398. pcClosePath:
  399. begin
  400. FCanvas32.EndPath(True);
  401. Current := FirstPoint;
  402. end;
  403. end;
  404. LastPos := Current;
  405. end;
  406. FCanvas32.EndPath;
  407. end;
  408. procedure TFrmSvgPathRenderer.ShpFillColorMouseDown(Sender: TObject;
  409. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  410. begin
  411. ColorDialog.Color := ShpFillColor.Brush.Color;
  412. if ColorDialog.Execute then
  413. begin
  414. ShpFillColor.Brush.Color := ColorDialog.Color;
  415. FFill.FillColor := SetAlpha(Color32(ShpFillColor.Brush.Color), $7F);
  416. UpdatePath;
  417. end;
  418. end;
  419. procedure TFrmSvgPathRenderer.ShpStrokeColorMouseDown(Sender: TObject;
  420. Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
  421. begin
  422. ColorDialog.Color := ShpStrokeColor.Brush.Color;
  423. if ColorDialog.Execute then
  424. begin
  425. ShpStrokeColor.Brush.Color := ColorDialog.Color;
  426. FStroke.FillColor := Color32(ShpStrokeColor.Brush.Color);
  427. UpdatePath;
  428. end;
  429. end;
  430. end.