lcvectorpolyshapes.pas 52 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit LCVectorPolyShapes;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, Types, LCVectorOriginal, BGRABitmapTypes, BGRALayerOriginal,
  7. BGRABitmap, BGRATransform, BGRAGradients, BGRAGraphics,
  8. BGRASVGShapes, BGRASVGType, BGRAUnits, BGRAPath;
  9. type
  10. TArrowKind = (akNone, akTail, akTip, akNormal, akCut, akFlipped, akFlippedCut,
  11. akTriangle, akTriangleBack1, akTriangleBack2,
  12. akHollowTriangle, akHollowTriangleBack1, akHollowTriangleBack2);
  13. const
  14. errShapeNotHandled = 'Shape not handled';
  15. ArrowKindToStr: array[TArrowKind] of string =
  16. ('none', 'tail', 'tip', 'normal', 'cut', 'flipped', 'flipped-cut',
  17. 'triangle', 'triangle-back1', 'triangle-back2',
  18. 'hollow-triangle', 'hollow-triangle-back1', 'hollow-triangle-back2');
  19. LineCapToStr: array[TPenEndCap] of string =
  20. ('round','square','flat');
  21. function StrToArrowKind(AStr: string): TArrowKind;
  22. function StrToLineCap(AStr: string): TPenEndCap;
  23. type
  24. TCustomPolypointShape = class;
  25. TCustomPolypointPoint = record
  26. coord: TPointF;
  27. editorIndex: integer;
  28. data: cardinal;
  29. end;
  30. { TCustomPolypointShapeDiff }
  31. TCustomPolypointShapeDiff = class(TVectorShapeDiff)
  32. protected
  33. FStartPoints: array of TCustomPolypointPoint;
  34. FStartClosed: boolean;
  35. FStartArrowStartKind,FStartArrowEndKind: TArrowKind;
  36. FStartArrowSize: TPointF;
  37. FStartLineCap: TPenEndCap;
  38. FEndPoints: array of TCustomPolypointPoint;
  39. FEndClosed: boolean;
  40. FEndArrowStartKind,FEndArrowEndKind: TArrowKind;
  41. FEndArrowSize: TPointF;
  42. FEndLineCap: TPenEndCap;
  43. public
  44. constructor Create(AStartShape: TVectorShape); override;
  45. procedure ComputeDiff(AEndShape: TVectorShape); override;
  46. procedure Apply(AStartShape: TVectorShape); override;
  47. procedure Unapply(AEndShape: TVectorShape); override;
  48. procedure Append(ADiff: TVectorShapeDiff); override;
  49. function IsIdentity: boolean; override;
  50. end;
  51. { TCustomPolypointShape }
  52. TCustomPolypointShape = class(TVectorShape)
  53. private
  54. FClosed: boolean;
  55. function GetHoverPoint: integer;
  56. function GetLineCap: TPenEndCap;
  57. function GetPoint(AIndex: integer): TPointF;
  58. function GetPointCount: integer;
  59. function GetValidatedPointCount: integer;
  60. procedure SetArrowEndKind(AValue: TArrowKind);
  61. procedure SetArrowSize(AValue: TPointF);
  62. procedure SetArrowStartKind(AValue: TArrowKind);
  63. procedure SetCenterPoint(AValue: TPointF);
  64. procedure SetHoverCenter(AValue: boolean);
  65. procedure SetHoverPoint(AValue: integer);
  66. procedure SetLineCap(AValue: TPenEndCap);
  67. procedure SetPoint(AIndex: integer; AValue: TPointF);
  68. protected
  69. FPoints: array of TCustomPolypointPoint;
  70. FCenterPoint: TPointF;
  71. FCenterPointEditorIndex: integer;
  72. FCurPoint: integer;
  73. FAddingPoint, FAltPressed: boolean;
  74. FMousePos: TPointF;
  75. FHoverPoint: integer;
  76. FHoverCenter: boolean;
  77. FArrowStartKind,FArrowEndKind: TArrowKind;
  78. FArrowSize: TPointF;
  79. FViewMatrix, FViewMatrixInverse, FGridMatrix: TAffineMatrix;
  80. procedure OnMovePoint({%H-}ASender: TObject; {%H-}APrevCoord, ANewCoord: TPointF; {%H-}AShift: TShiftState);
  81. procedure OnMoveCenterPoint({%H-}ASender: TObject; {%H-}APrevCoord, ANewCoord: TPointF; {%H-}AShift: TShiftState);
  82. procedure OnStartMove({%H-}ASender: TObject; APointIndex: integer; {%H-}AShift: TShiftState);
  83. function GetCurve(AMatrix: TAffineMatrix): ArrayOfTPointF; virtual;
  84. function GetPath(AMatrix: TAffineMatrix): TBGRAPath; virtual; overload;
  85. function GetPath(const APoints: array of TPointF): TBGRAPath; overload;
  86. procedure SetUsermode(AValue: TVectorShapeUsermode); override;
  87. function GetClosed: boolean; virtual;
  88. procedure SetClosed(AValue: boolean); virtual;
  89. function PointsEqual(const APoint1, APoint2: TPointF): boolean;
  90. procedure OnHoverPoint({%H-}ASender: TObject; APointIndex: integer); virtual;
  91. procedure OnClickPoint({%H-}ASender: TObject; APointIndex: integer; {%H-}AShift: TShiftState); virtual;
  92. procedure DoClickPoint({%H-}APointIndex: integer; {%H-}AShift: TShiftState); virtual;
  93. function CanMovePoints: boolean; virtual;
  94. procedure InsertPointAuto(AShift: TShiftState);
  95. function ComputeStroke(APoints: ArrayOfTPointF; AClosed: boolean;
  96. AStrokeMatrix: TAffineMatrix): ArrayOfTPointF; override;
  97. function GetLoopStartIndex: integer;
  98. function GetLoopPointCount: integer;
  99. function GetIsFollowingMouse: boolean; override;
  100. public
  101. constructor Create(AContainer: TVectorOriginal); override;
  102. procedure Clear;
  103. function AddPoint(const APoint: TPointF): integer; virtual;
  104. procedure AddPointRange(const APoints: array of TPointF);
  105. function GetPointRange: ArrayOfTPointF;
  106. function RemovePoint(AIndex: integer): boolean;
  107. procedure RemovePointRange(AFromIndex, AToIndexPlus1: integer);
  108. procedure InsertPoint(AIndex: integer; APoint: TPointF);
  109. function GetPointBounds(AMatrix: TAffineMatrix): TRectF;
  110. procedure MouseMove({%H-}Shift: TShiftState; X, Y: single; var {%H-}ACursor: TOriginalEditorCursor; var AHandled: boolean); override;
  111. procedure MouseDown(RightButton: boolean; {%H-}ClickCount: integer; {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: single; var {%H-}ACursor: TOriginalEditorCursor; var AHandled: boolean); override;
  112. procedure KeyDown({%H-}Shift: TShiftState; Key: TSpecialKey; var AHandled: boolean); override;
  113. procedure KeyUp(Shift: TShiftState; Key: TSpecialKey; var AHandled: boolean); override;
  114. procedure QuickDefine(constref APoint1,APoint2: TPointF); override;
  115. procedure LoadFromStorage(AStorage: TBGRACustomOriginalStorage); override;
  116. procedure SaveToStorage(AStorage: TBGRACustomOriginalStorage); override;
  117. procedure ConfigureCustomEditor(AEditor: TBGRAOriginalEditor); override;
  118. procedure TransformFrame(const AMatrix: TAffineMatrix); override;
  119. class function Usermodes: TVectorShapeUsermodes; override;
  120. class function DefaultArrowSize: TPointF;
  121. property Points[AIndex:integer]: TPointF read GetPoint write SetPoint;
  122. property PointCount: integer read GetPointCount;
  123. property ValidatedPointCount: integer read GetValidatedPointCount;
  124. property Closed: boolean read GetClosed write SetClosed;
  125. property HoverPoint: integer read GetHoverPoint write SetHoverPoint;
  126. property HoverCenter: boolean read FHoverCenter write SetHoverCenter;
  127. property ArrowStartKind: TArrowKind read FArrowStartKind write SetArrowStartKind;
  128. property ArrowEndKind: TArrowKind read FArrowEndKind write SetArrowEndKind;
  129. property ArrowSize: TPointF read FArrowSize write SetArrowSize;
  130. property LineCap: TPenEndCap read GetLineCap write SetLineCap;
  131. property Center: TPointF read FCenterPoint write SetCenterPoint;
  132. end;
  133. { TPolylineShape }
  134. TPolylineShape = class(TCustomPolypointShape)
  135. public
  136. class function Fields: TVectorShapeFields; override;
  137. procedure Render(ADest: TBGRABitmap; AMatrix: TAffineMatrix; ADraft: boolean); overload; override;
  138. function AppendToSVG(AContent: TSVGContent; ADefs: TSVGDefine): TSVGElement; override;
  139. function GetRenderBounds({%H-}ADestRect: TRect; AMatrix: TAffineMatrix; AOptions: TRenderBoundsOptions = []): TRectF; override;
  140. function PointInShape(APoint: TPointF): boolean; overload; override;
  141. function PointInShape(APoint: TPointF; ARadius: single): boolean; overload; override;
  142. function PointInBack(APoint: TPointF): boolean; overload; override;
  143. function PointInPen(APoint: TPointF): boolean; overload; override;
  144. function GetIsSlow(const {%H-}AMatrix: TAffineMatrix): boolean; override;
  145. class function StorageClassName: RawByteString; override;
  146. end;
  147. TCurveShape = class;
  148. { TCurveShapeDiff }
  149. TCurveShapeDiff = class(TVectorShapeDiff)
  150. protected
  151. FStartCosineAngle: single;
  152. FStartSplineStyle: TSplineStyle;
  153. FEndCosineAngle: single;
  154. FEndSplineStyle: TSplineStyle;
  155. public
  156. constructor Create(AStartShape: TVectorShape); override;
  157. procedure ComputeDiff(AEndShape: TVectorShape); override;
  158. procedure Apply(AStartShape: TVectorShape); override;
  159. procedure Unapply(AEndShape: TVectorShape); override;
  160. procedure Append(ADiff: TVectorShapeDiff); override;
  161. function IsIdentity: boolean; override;
  162. end;
  163. { TCurveShape }
  164. TCurveShape = class(TPolylineShape)
  165. private
  166. FCosineAngle: single;
  167. FSplineStyle: TSplineStyle;
  168. function GetCurveMode(AIndex: integer): TEasyBezierCurveMode;
  169. procedure SetCosineAngle(AValue: single);
  170. procedure SetCurveMode(AIndex: integer; AValue: TEasyBezierCurveMode);
  171. procedure SetSplineStyle(AValue: TSplineStyle);
  172. protected
  173. function GetCurve(AMatrix: TAffineMatrix): ArrayOfTPointF; override;
  174. function GetPath(AMatrix: TAffineMatrix): TBGRAPath; override;
  175. function CanMovePoints: boolean; override;
  176. procedure DoClickPoint(APointIndex: integer; {%H-}AShift: TShiftState); override;
  177. public
  178. class function Usermodes: TVectorShapeUsermodes; override;
  179. constructor Create(AContainer: TVectorOriginal); override;
  180. constructor CreateFrom(AContainer: TVectorOriginal; AShape: TVectorShape);
  181. class function CanCreateFrom(AShape: TVectorShape): boolean;
  182. function AddPoint(const APoint: TPointF): integer; overload; override;
  183. function AddPoint(const APoint: TPointF; AMode: TEasyBezierCurveMode): integer; overload;
  184. procedure KeyPress(UTF8Key: string; var AHandled: boolean); override;
  185. procedure LoadFromStorage(AStorage: TBGRACustomOriginalStorage); override;
  186. procedure SaveToStorage(AStorage: TBGRACustomOriginalStorage); override;
  187. class function StorageClassName: RawByteString; override;
  188. property SplineStyle: TSplineStyle read FSplineStyle write SetSplineStyle;
  189. property CurveMode[AIndex: integer]: TEasyBezierCurveMode read GetCurveMode write SetCurveMode;
  190. property CosineAngle: single read FCosineAngle write SetCosineAngle;
  191. end;
  192. procedure ApplyArrowStyle(AArrow: TBGRACustomArrow; AStart: boolean; AKind: TArrowKind; ASize: TPointF);
  193. implementation
  194. uses BGRAPen, BGRAFillInfo, math, LCVectorialFill,
  195. BGRAArrow, LCVectorRectShapes, LCResourceString;
  196. function StrToArrowKind(AStr: string): TArrowKind;
  197. var
  198. ak: TArrowKind;
  199. begin
  200. for ak := low(TArrowKind) to high(TArrowKind) do
  201. if CompareText(AStr, ArrowKindToStr[ak])=0 then exit(ak);
  202. result := akNone;
  203. end;
  204. function StrToLineCap(AStr: string): TPenEndCap;
  205. var
  206. ec: TPenEndCap;
  207. begin
  208. for ec := low(TPenEndCap) to high(TPenEndCap) do
  209. if CompareText(AStr, LineCapToStr[ec])=0 then exit(ec);
  210. result := pecRound;
  211. end;
  212. procedure ApplyArrowStyle(AArrow: TBGRACustomArrow; AStart: boolean; AKind: TArrowKind; ASize: TPointF);
  213. var backOfs: single;
  214. begin
  215. backOfs := 0;
  216. if (ASize.x = 0) or (ASize.y = 0) then AKind := akNone;
  217. if AKind in[akTriangleBack1,akHollowTriangleBack1] then backOfs := 0.25;
  218. if AKind in[akTriangleBack2,akHollowTriangleBack2] then backOfs := 0.50;
  219. case AKind of
  220. akTail: if AStart then AArrow.StartAsTail else AArrow.EndAsTail;
  221. akTip: if AStart then AArrow.StartAsTriangle else AArrow.EndAsTriangle;
  222. akNormal,akCut,akFlipped,akFlippedCut:
  223. if AStart then AArrow.StartAsClassic(AKind in[akFlipped,akFlippedCut], AKind in[akCut,akFlippedCut])
  224. else AArrow.EndAsClassic(AKind in[akFlipped,akFlippedCut], AKind in[akCut,akFlippedCut]);
  225. akTriangle,akTriangleBack1,akTriangleBack2:
  226. if AStart then AArrow.StartAsTriangle(backOfs) else AArrow.EndAsTriangle(backOfs);
  227. akHollowTriangle,akHollowTriangleBack1,akHollowTriangleBack2:
  228. if AStart then AArrow.StartAsTriangle(backOfs,False,True) else AArrow.EndAsTriangle(backOfs,False,True);
  229. else if AStart then AArrow.StartAsNone else AArrow.EndAsNone;
  230. end;
  231. if (AKind = akTip) and not ((ASize.x = 0) or (ASize.y = 0)) then
  232. ASize := ASize*(0.5/ASize.y);
  233. if AStart then AArrow.StartSize := ASize else AArrow.EndSize := ASize;
  234. end;
  235. procedure IncludePointF(var ARectF: TRectF; APointF: TPointF);
  236. begin
  237. if APointF.x < ARectF.Left then ARectF.Left := APointF.x;
  238. if APointF.x > ARectF.Right then ARectF.Right := APointF.x;
  239. if APointF.y < ARectF.Top then ARectF.Top := APointF.y;
  240. if APointF.y > ARectF.Bottom then ARectF.Bottom := APointF.y;
  241. end;
  242. function GetPointsBoundsF(const APoints: array of TPointF): TRectF;
  243. var
  244. i: Integer;
  245. firstPoint: Boolean;
  246. begin
  247. result:= EmptyRectF;
  248. firstPoint := true;
  249. for i:= 0 to high(APoints) do
  250. if not isEmptyPointF(APoints[i]) then
  251. begin
  252. if firstPoint then
  253. begin
  254. result.TopLeft := APoints[i];
  255. result.BottomRight := APoints[i];
  256. firstPoint := false;
  257. end else
  258. IncludePointF(result, APoints[i]);
  259. end;
  260. end;
  261. { TCurveShapeDiff }
  262. constructor TCurveShapeDiff.Create(AStartShape: TVectorShape);
  263. begin
  264. with (AStartShape as TCurveShape) do
  265. begin
  266. FStartCosineAngle:= FCosineAngle;
  267. FStartSplineStyle:= FSplineStyle;
  268. end;
  269. end;
  270. procedure TCurveShapeDiff.ComputeDiff(AEndShape: TVectorShape);
  271. begin
  272. with (AEndShape as TCurveShape) do
  273. begin
  274. FEndCosineAngle:= FCosineAngle;
  275. FEndSplineStyle:= FSplineStyle;
  276. end;
  277. end;
  278. procedure TCurveShapeDiff.Apply(AStartShape: TVectorShape);
  279. begin
  280. with (AStartShape as TCurveShape) do
  281. begin
  282. BeginUpdate;
  283. FCosineAngle := FEndCosineAngle;
  284. FSplineStyle := FEndSplineStyle;
  285. EndUpdate;
  286. end;
  287. end;
  288. procedure TCurveShapeDiff.Unapply(AEndShape: TVectorShape);
  289. begin
  290. with (AEndShape as TCurveShape) do
  291. begin
  292. BeginUpdate;
  293. FCosineAngle := FStartCosineAngle;
  294. FSplineStyle := FStartSplineStyle;
  295. EndUpdate;
  296. end;
  297. end;
  298. procedure TCurveShapeDiff.Append(ADiff: TVectorShapeDiff);
  299. var
  300. next: TCurveShapeDiff;
  301. begin
  302. next := ADiff as TCurveShapeDiff;
  303. FEndCosineAngle:= next.FEndCosineAngle;
  304. FEndSplineStyle:= next.FEndSplineStyle;
  305. end;
  306. function TCurveShapeDiff.IsIdentity: boolean;
  307. begin
  308. result := (FStartCosineAngle = FEndCosineAngle) and
  309. (FStartSplineStyle = FEndSplineStyle);
  310. end;
  311. { TCustomPolypointShapeDiff }
  312. constructor TCustomPolypointShapeDiff.Create(AStartShape: TVectorShape);
  313. var
  314. i: Integer;
  315. begin
  316. with (AStartShape as TCustomPolypointShape) do
  317. begin
  318. setlength(FStartPoints, length(FPoints));
  319. for i := 0 to high(FPoints) do FStartPoints[i] := FPoints[i];
  320. FStartClosed:= FClosed;
  321. FStartArrowStartKind := FArrowStartKind;
  322. FStartArrowEndKind:= FArrowEndKind;
  323. FStartArrowSize:= FArrowSize;
  324. FStartLineCap:= Stroker.LineCap;
  325. end;
  326. end;
  327. procedure TCustomPolypointShapeDiff.ComputeDiff(AEndShape: TVectorShape);
  328. var
  329. i: Integer;
  330. begin
  331. with (AEndShape as TCustomPolypointShape) do
  332. begin
  333. setlength(FEndPoints, length(FPoints));
  334. for i := 0 to high(FPoints) do FEndPoints[i] := FPoints[i];
  335. FEndClosed:= FClosed;
  336. FEndArrowStartKind := FArrowStartKind;
  337. FEndArrowEndKind:= FArrowEndKind;
  338. FEndArrowSize:= FArrowSize;
  339. FEndLineCap:= Stroker.LineCap;
  340. end;
  341. end;
  342. procedure TCustomPolypointShapeDiff.Apply(AStartShape: TVectorShape);
  343. var
  344. i: Integer;
  345. begin
  346. with (AStartShape as TCustomPolypointShape) do
  347. begin
  348. BeginUpdate;
  349. setlength(FPoints, length(FEndPoints));
  350. for i := 0 to high(FPoints) do FPoints[i] := FEndPoints[i];
  351. FClosed := FEndClosed;
  352. FArrowStartKind := FEndArrowStartKind;
  353. FArrowEndKind := FEndArrowEndKind;
  354. FArrowSize := FEndArrowSize;
  355. Stroker.LineCap:= FEndLineCap;
  356. EndUpdate;
  357. end;
  358. end;
  359. procedure TCustomPolypointShapeDiff.Unapply(AEndShape: TVectorShape);
  360. var
  361. i: Integer;
  362. begin
  363. with (AEndShape as TCustomPolypointShape) do
  364. begin
  365. BeginUpdate;
  366. setlength(FPoints, length(FStartPoints));
  367. for i := 0 to high(FPoints) do FPoints[i] := FStartPoints[i];
  368. FClosed := FStartClosed;
  369. FArrowStartKind := FStartArrowStartKind;
  370. FArrowEndKind := FStartArrowEndKind;
  371. FArrowSize := FStartArrowSize;
  372. Stroker.LineCap:= FStartLineCap;
  373. EndUpdate;
  374. end;
  375. end;
  376. procedure TCustomPolypointShapeDiff.Append(ADiff: TVectorShapeDiff);
  377. var
  378. next: TCustomPolypointShapeDiff;
  379. i: Integer;
  380. begin
  381. next := ADiff as TCustomPolypointShapeDiff;
  382. setlength(FEndPoints, length(next.FEndPoints));
  383. for i := 0 to high(FEndPoints) do FEndPoints[i] := next.FEndPoints[i];
  384. FEndClosed := next.FEndClosed;
  385. FEndArrowStartKind := next.FEndArrowStartKind;
  386. FEndArrowEndKind := next.FEndArrowEndKind;
  387. FEndArrowSize := next.FEndArrowSize;
  388. FEndLineCap:= next.FEndLineCap;
  389. end;
  390. function TCustomPolypointShapeDiff.IsIdentity: boolean;
  391. var
  392. i: Integer;
  393. begin
  394. result := (length(FStartPoints) = length(FEndPoints)) and
  395. (FStartClosed = FEndClosed) and
  396. (FStartArrowStartKind = FEndArrowStartKind) and
  397. (FStartArrowEndKind = FEndArrowEndKind) and
  398. (FStartArrowSize = FEndArrowSize) and
  399. (FStartLineCap = FEndLineCap);
  400. if result then
  401. begin
  402. for i := 0 to high(FStartPoints) do
  403. if (FStartPoints[i].coord<>FEndPoints[i].coord) or
  404. (FStartPoints[i].data<>FEndPoints[i].data) then
  405. begin
  406. result := false;
  407. break;
  408. end;
  409. end;
  410. end;
  411. { TCustomPolypointShape }
  412. function TCustomPolypointShape.GetClosed: boolean;
  413. begin
  414. result := FClosed;
  415. end;
  416. function TCustomPolypointShape.GetPoint(AIndex: integer): TPointF;
  417. begin
  418. if (AIndex < 0) or (AIndex >= length(FPoints)) then
  419. raise ERangeError.Create(rsIndexOutOfBounds);
  420. result := FPoints[AIndex].coord;
  421. end;
  422. function TCustomPolypointShape.GetLineCap: TPenEndCap;
  423. begin
  424. result := Stroker.LineCap;
  425. end;
  426. function TCustomPolypointShape.GetHoverPoint: integer;
  427. begin
  428. if (FHoverPoint >= 0) and (FHoverPoint < PointCount) and
  429. not Points[FHoverPoint].IsEmpty then
  430. result := FHoverPoint else result := -1;
  431. end;
  432. function TCustomPolypointShape.GetPointCount: integer;
  433. begin
  434. result:= length(FPoints);
  435. end;
  436. function TCustomPolypointShape.GetValidatedPointCount: integer;
  437. begin
  438. if (PointCount > 1) and FAddingPoint then
  439. result := PointCount - 1
  440. else
  441. result := PointCount;
  442. end;
  443. procedure TCustomPolypointShape.SetArrowEndKind(AValue: TArrowKind);
  444. begin
  445. if FArrowEndKind=AValue then Exit;
  446. BeginUpdate(TCustomPolypointShapeDiff);
  447. FArrowEndKind:=AValue;
  448. EndUpdate;
  449. end;
  450. procedure TCustomPolypointShape.SetArrowSize(AValue: TPointF);
  451. begin
  452. if FArrowSize=AValue then Exit;
  453. BeginUpdate(TCustomPolypointShapeDiff);
  454. FArrowSize:=AValue;
  455. EndUpdate;
  456. end;
  457. procedure TCustomPolypointShape.SetArrowStartKind(AValue: TArrowKind);
  458. begin
  459. if FArrowStartKind=AValue then Exit;
  460. BeginUpdate(TCustomPolypointShapeDiff);
  461. FArrowStartKind:=AValue;
  462. EndUpdate;
  463. end;
  464. procedure TCustomPolypointShape.SetCenterPoint(AValue: TPointF);
  465. var
  466. i: Integer;
  467. delta: TPointF;
  468. begin
  469. if FCenterPoint=AValue then Exit;
  470. BeginUpdate(TCustomPolypointShapeDiff);
  471. delta := AValue - FCenterPoint;
  472. for i := 0 to PointCount-1 do
  473. Points[i] := Points[i]+delta;
  474. if vsfBackFill in Fields then
  475. BackFill.Transform(AffineMatrixTranslation(delta.x, delta.y));
  476. if vsfPenFill in Fields then
  477. PenFill.Transform(AffineMatrixTranslation(delta.x, delta.y));
  478. FCenterPoint:=AValue;
  479. EndUpdate;
  480. end;
  481. procedure TCustomPolypointShape.SetHoverCenter(AValue: boolean);
  482. begin
  483. if FHoverCenter=AValue then Exit;
  484. BeginEditingUpdate;
  485. if AValue then FHoverPoint := -1;
  486. FHoverCenter:=AValue;
  487. EndEditingUpdate;
  488. end;
  489. procedure TCustomPolypointShape.SetHoverPoint(AValue: integer);
  490. begin
  491. if (AValue < 0) or (AValue >= PointCount) or
  492. Points[AValue].IsEmpty then AValue := -1;
  493. if AValue <> FHoverPoint then
  494. begin
  495. BeginEditingUpdate;
  496. FHoverPoint := AValue;
  497. if AValue <> -1 then FHoverCenter:= false;
  498. EndEditingUpdate;
  499. end;
  500. end;
  501. procedure TCustomPolypointShape.SetLineCap(AValue: TPenEndCap);
  502. begin
  503. if Stroker.LineCap=AValue then Exit;
  504. BeginUpdate(TCustomPolypointShapeDiff);
  505. Stroker.LineCap:=AValue;
  506. EndUpdate;
  507. end;
  508. procedure TCustomPolypointShape.SetClosed(AValue: boolean);
  509. begin
  510. if AValue = FClosed then exit;
  511. BeginUpdate(TCustomPolypointShapeDiff);
  512. FClosed := AValue;
  513. EndUpdate;
  514. end;
  515. procedure TCustomPolypointShape.SetPoint(AIndex: integer; AValue: TPointF);
  516. begin
  517. if (AIndex < 0) or (AIndex > length(FPoints)) then
  518. raise ERangeError.Create(rsIndexOutOfBounds);
  519. BeginUpdate(TCustomPolypointShapeDiff);
  520. if AIndex = length(FPoints) then
  521. begin
  522. setlength(FPoints, length(FPoints)+1);
  523. FPoints[AIndex].coord := AValue;
  524. FPoints[AIndex].editorIndex := -1;
  525. FPoints[AIndex].data := 0;
  526. end
  527. else
  528. FPoints[AIndex].coord := AValue;
  529. EndUpdate;
  530. end;
  531. procedure TCustomPolypointShape.OnMovePoint(ASender: TObject; APrevCoord,
  532. ANewCoord: TPointF; AShift: TShiftState);
  533. begin
  534. if FCurPoint = -1 then exit;
  535. Points[FCurPoint] := ANewCoord;
  536. end;
  537. procedure TCustomPolypointShape.OnMoveCenterPoint(ASender: TObject; APrevCoord,
  538. ANewCoord: TPointF; AShift: TShiftState);
  539. begin
  540. Center := ANewCoord;
  541. end;
  542. procedure TCustomPolypointShape.OnStartMove(ASender: TObject; APointIndex: integer;
  543. AShift: TShiftState);
  544. var
  545. i: Integer;
  546. begin
  547. FCurPoint:= -1;
  548. for i:= 0 to PointCount-1 do
  549. if FPoints[i].editorIndex = APointIndex then
  550. begin
  551. FCurPoint:= i;
  552. break;
  553. end;
  554. end;
  555. function TCustomPolypointShape.GetCurve(AMatrix: TAffineMatrix): ArrayOfTPointF;
  556. var
  557. i: Integer;
  558. m: TAffineMatrix;
  559. begin
  560. result := nil;
  561. setlength(result, PointCount);
  562. m:= MatrixForPixelCentered(AMatrix);
  563. for i := 0 to PointCount-1 do
  564. result[i] := m*Points[i];
  565. end;
  566. function TCustomPolypointShape.GetPath(const APoints: array of TPointF): TBGRAPath;
  567. var p: TPointF;
  568. subPoly: boolean;
  569. begin
  570. result := TBGRAPath.Create;
  571. subPoly := true;
  572. for p in APoints do
  573. begin
  574. if isEmptyPointF(p) then
  575. begin
  576. if not result.IsEmpty and Closed then result.closePath;
  577. subPoly := true;
  578. end else
  579. begin
  580. if subPoly then
  581. begin
  582. result.moveTo(p);
  583. subPoly := false;
  584. end
  585. else
  586. result.lineTo(p);
  587. end;
  588. end;
  589. if not result.IsEmpty and Closed then result.closePath;
  590. end;
  591. function TCustomPolypointShape.GetPath(AMatrix: TAffineMatrix): TBGRAPath;
  592. var
  593. pts: array of TPointF;
  594. begin
  595. pts := GetCurve(AMatrix);
  596. result := GetPath(pts);
  597. end;
  598. class function TCustomPolypointShape.Usermodes: TVectorShapeUsermodes;
  599. begin
  600. Result:= inherited Usermodes + [vsuCreate];
  601. end;
  602. class function TCustomPolypointShape.DefaultArrowSize: TPointF;
  603. begin
  604. result := PointF(2,2);
  605. end;
  606. procedure TCustomPolypointShape.SetUsermode(AValue: TVectorShapeUsermode);
  607. var
  608. add: Boolean;
  609. begin
  610. add := AValue = vsuCreate;
  611. if add and (PointCount = 0) then exit;
  612. if FAddingPoint and not add then
  613. begin
  614. if (PointCount>1) and PointsEqual(Points[PointCount-1],Points[PointCount-2]) then
  615. RemovePoint(PointCount-1);
  616. FAddingPoint:= add;
  617. end else
  618. if not FAddingPoint and add then
  619. begin
  620. if not isEmptyPointF(FMousePos) then
  621. AddPoint(FMousePos)
  622. else
  623. AddPoint(Points[PointCount-1]);
  624. FAddingPoint:= add;
  625. end;
  626. inherited SetUsermode(AValue);
  627. end;
  628. function TCustomPolypointShape.PointsEqual(const APoint1, APoint2: TPointF
  629. ): boolean;
  630. begin
  631. if isEmptyPointF(APoint1) then
  632. exit(isEmptyPointF(APoint2))
  633. else
  634. if isEmptyPointF(APoint2) then exit(false)
  635. else
  636. exit((APoint1.x = APoint2.x) and (APoint1.y = APoint2.y));
  637. end;
  638. procedure TCustomPolypointShape.OnHoverPoint(ASender: TObject;
  639. APointIndex: integer);
  640. var
  641. i, newHoverPoint: Integer;
  642. begin
  643. if APointIndex = FCenterPointEditorIndex then
  644. begin
  645. HoverCenter := true;
  646. exit;
  647. end;
  648. newHoverPoint:= -1;
  649. if APointIndex <> -1 then
  650. begin
  651. for i:= 0 to PointCount-1 do
  652. if FPoints[i].editorIndex = APointIndex then
  653. begin
  654. newHoverPoint:= i;
  655. break;
  656. end;
  657. end;
  658. HoverPoint := newHoverPoint;
  659. HoverCenter:= false;
  660. end;
  661. procedure TCustomPolypointShape.OnClickPoint(ASender: TObject;
  662. APointIndex: integer; AShift: TShiftState);
  663. var
  664. i: Integer;
  665. begin
  666. if APointIndex <> -1 then
  667. begin
  668. for i:= 0 to PointCount-1 do
  669. if FPoints[i].editorIndex = APointIndex then
  670. begin
  671. DoClickPoint(i, AShift);
  672. break;
  673. end;
  674. end;
  675. end;
  676. procedure TCustomPolypointShape.DoClickPoint(APointIndex: integer;
  677. AShift: TShiftState);
  678. var
  679. nb: Integer;
  680. begin
  681. if FAddingPoint and ((APointIndex = GetLoopStartIndex) or
  682. ((APointIndex = PointCount-2) and (ssRight in AShift))) then
  683. begin
  684. nb := GetLoopPointCount;
  685. if nb > 2 then
  686. begin
  687. BeginUpdate;
  688. RemovePoint(PointCount-1);
  689. if APointIndex < PointCount-2 then Closed := true;
  690. EndUpdate;
  691. UserMode := vsuEdit;
  692. end else
  693. begin
  694. if GetLoopStartIndex = 0 then
  695. Remove
  696. else
  697. begin
  698. BeginUpdate;
  699. while nb > 0 do
  700. begin
  701. RemovePoint(PointCount-1);
  702. dec(nb);
  703. end;
  704. RemovePoint(PointCount-1); //remove separator
  705. end;
  706. end;
  707. end;
  708. end;
  709. function TCustomPolypointShape.CanMovePoints: boolean;
  710. begin
  711. result := true;
  712. end;
  713. procedure TCustomPolypointShape.InsertPointAuto(AShift: TShiftState);
  714. var
  715. i,j, loopStart: Integer;
  716. bestSegmentIndex,bestPointIndex: integer;
  717. bestSegmentDist,bestPointDist, segmentLen, segmentPos: single;
  718. u, n, bestProjection: TPointF;
  719. segmentDist: single;
  720. isLooping: Boolean;
  721. begin
  722. if isEmptyPointF(FMousePos) then exit;
  723. for i := 0 to PointCount-1 do
  724. if (Points[i] = FMousePos) and not (FAddingPoint and (i = PointCount-1)) then exit;
  725. bestSegmentIndex := -1;
  726. bestSegmentDist := MaxSingle;
  727. bestProjection := EmptyPointF;
  728. loopStart := 0;
  729. for i := 0 to PointCount-1 do
  730. if FAddingPoint and (i >= PointCount-2) then break else
  731. begin
  732. if IsEmptyPointF(Points[i]) then
  733. begin
  734. loopStart := i+1;
  735. continue;
  736. end;
  737. isLooping := (i = PointCount-1) or IsEmptyPointF(Points[i+1]);
  738. if isLooping and not Closed then break;
  739. if isLooping then
  740. j := loopStart
  741. else j := i+1;
  742. u := Points[j] - Points[i];
  743. segmentLen := VectLen(u);
  744. if segmentLen > 0 then
  745. begin
  746. u *= 1/segmentLen;
  747. segmentPos := (FMousePos-Points[i])**u;
  748. if (segmentPos > 0) and (segmentPos< segmentLen) then
  749. begin
  750. n := PointF(u.y,-u.x);
  751. segmentDist := abs((FMousePos-Points[i])**n);
  752. if segmentDist <= bestSegmentDist then
  753. begin
  754. bestSegmentDist := segmentDist;
  755. bestSegmentIndex := i;
  756. bestProjection := Points[i]+segmentPos*u;
  757. end;
  758. end;
  759. end;
  760. end;
  761. bestPointIndex := -1;
  762. bestPointDist := MaxSingle;
  763. if not FAddingPoint then
  764. for i := 0 to PointCount-1 do
  765. if ((i = 0) or isEmptyPointF(Points[i-1])) and
  766. ((i = PointCount-1) or isEmptyPointF(Points[i+1])) then
  767. begin
  768. segmentDist := VectLen(FMousePos-Points[i]);
  769. if segmentDist < bestPointDist then
  770. begin
  771. bestPointDist := segmentDist;
  772. bestPointIndex := i;
  773. end;
  774. end;
  775. if (bestPointIndex <> -1) and ((bestSegmentIndex = -1) or (bestPointDist < bestSegmentDist)) then
  776. begin
  777. InsertPoint(bestPointIndex+1, FMousePos);
  778. HoverPoint := bestPointIndex+1;
  779. end else
  780. if bestSegmentIndex <> -1 then
  781. begin
  782. if ssShift in AShift then
  783. InsertPoint(bestSegmentIndex+1, bestProjection)
  784. else
  785. InsertPoint(bestSegmentIndex+1, FMousePos);
  786. HoverPoint:= bestSegmentIndex+1;
  787. end;
  788. end;
  789. function TCustomPolypointShape.ComputeStroke(APoints: ArrayOfTPointF;
  790. AClosed: boolean; AStrokeMatrix: TAffineMatrix): ArrayOfTPointF;
  791. begin
  792. if Stroker.Arrow = nil then
  793. begin
  794. Stroker.Arrow := TBGRAArrow.Create;
  795. Stroker.ArrowOwned:= true;
  796. end;
  797. Stroker.Arrow.LineCap:= LineCap;
  798. ApplyArrowStyle(Stroker.Arrow, true, ArrowStartKind, ArrowSize);
  799. ApplyArrowStyle(Stroker.Arrow, false, ArrowEndKind, ArrowSize);
  800. Result:=inherited ComputeStroke(APoints, AClosed, AStrokeMatrix);
  801. Stroker.Arrow.StartAsNone;
  802. Stroker.Arrow.EndAsNone;
  803. end;
  804. function TCustomPolypointShape.GetLoopStartIndex: integer;
  805. var
  806. i: Integer;
  807. begin
  808. for i := PointCount-1 downto 0 do
  809. if isEmptyPointF(Points[i]) then exit(i+1);
  810. exit(0);
  811. end;
  812. function TCustomPolypointShape.GetLoopPointCount: integer;
  813. begin
  814. result := PointCount-GetLoopStartIndex;
  815. end;
  816. function TCustomPolypointShape.GetIsFollowingMouse: boolean;
  817. begin
  818. Result:= Usermode = vsuCreate;
  819. end;
  820. constructor TCustomPolypointShape.Create(AContainer: TVectorOriginal);
  821. begin
  822. inherited Create(AContainer);
  823. FMousePos := EmptyPointF;
  824. FClosed:= false;
  825. FHoverPoint:= -1;
  826. FCenterPoint := EmptyPointF;
  827. end;
  828. procedure TCustomPolypointShape.Clear;
  829. begin
  830. RemovePointRange(0, PointCount);
  831. end;
  832. function TCustomPolypointShape.AddPoint(const APoint: TPointF): integer;
  833. begin
  834. result := PointCount;
  835. Points[result] := APoint;
  836. end;
  837. procedure TCustomPolypointShape.AddPointRange(const APoints: array of TPointF);
  838. var
  839. i: Integer;
  840. begin
  841. BeginUpdate(TCustomPolypointShapeDiff);
  842. for i := 0 to high(APoints) do
  843. AddPoint(APoints[i]);
  844. EndUpdate;
  845. end;
  846. function TCustomPolypointShape.GetPointRange: ArrayOfTPointF;
  847. var
  848. i: Integer;
  849. begin
  850. result := nil;
  851. SetLength(result, PointCount);
  852. for i := 0 to PointCount-1 do
  853. result[i] := Points[i];
  854. end;
  855. function TCustomPolypointShape.RemovePoint(AIndex: integer): boolean;
  856. begin
  857. if (AIndex < 0) or (AIndex >= PointCount) then exit(false);
  858. RemovePointRange(AIndex,AIndex+1);
  859. result := true;
  860. end;
  861. procedure TCustomPolypointShape.RemovePointRange(AFromIndex, AToIndexPlus1: integer);
  862. var
  863. i, delCount: Integer;
  864. begin
  865. if AFromIndex < 0 then AFromIndex:= 0;
  866. if AToIndexPlus1 > PointCount then AToIndexPlus1:= PointCount;
  867. if AFromIndex >= AToIndexPlus1 then exit;
  868. BeginUpdate(TCustomPolypointShapeDiff);
  869. delCount := AToIndexPlus1-AFromIndex;
  870. for i := AFromIndex to PointCount-DelCount-1 do
  871. FPoints[i] := FPoints[i+delCount];
  872. setlength(FPoints, PointCount-delCount);
  873. if (HoverPoint >= AFromIndex) and (HoverPoint < AToIndexPlus1) then HoverPoint := -1
  874. else if (HoverPoint <> -1) and (HoverPoint >= AToIndexPlus1) then HoverPoint := HoverPoint - delCount;
  875. EndUpdate;
  876. end;
  877. procedure TCustomPolypointShape.InsertPoint(AIndex: integer; APoint: TPointF);
  878. var
  879. i: Integer;
  880. begin
  881. if (AIndex < 0) or (AIndex > PointCount) then raise exception.Create(rsIndexOutOfBounds);
  882. BeginUpdate(TCustomPolypointShapeDiff);
  883. setlength(FPoints, PointCount+1);
  884. for i := PointCount-1 downto AIndex+1 do
  885. FPoints[i] := FPoints[i-1];
  886. FPoints[AIndex].coord := APoint;
  887. FPoints[AIndex].editorIndex:= -1;
  888. FPoints[AIndex].data := 0;
  889. if (HoverPoint <> -1) and (HoverPoint >= AIndex) then HoverPoint := HoverPoint + 1;
  890. EndUpdate;
  891. end;
  892. function TCustomPolypointShape.GetPointBounds(AMatrix: TAffineMatrix): TRectF;
  893. begin
  894. result := GetPointsBoundsF(GetCurve(AMatrix));
  895. end;
  896. procedure TCustomPolypointShape.MouseMove(Shift: TShiftState; X, Y: single; var
  897. ACursor: TOriginalEditorCursor; var AHandled: boolean);
  898. begin
  899. FMousePos := PointF(X,Y);
  900. if FAddingPoint then
  901. begin
  902. BeginUpdate;
  903. if (PointCount = 1) and (FMousePos <> Points[PointCount-1]) then
  904. Points[PointCount] := FMousePos
  905. else
  906. Points[PointCount-1] := FMousePos;
  907. FillFit;
  908. EndUpdate;
  909. AHandled:= true;
  910. end;
  911. end;
  912. procedure TCustomPolypointShape.MouseDown(RightButton: boolean;
  913. ClickCount: integer; Shift: TShiftState; X, Y: single; var ACursor: TOriginalEditorCursor; var
  914. AHandled: boolean);
  915. begin
  916. FMousePos := PointF(X,Y);
  917. if FAddingPoint then
  918. begin
  919. if not RightButton then
  920. begin
  921. if (PointCount>1) and not PointsEqual(FMousePos,Points[PointCount-2]) then
  922. begin
  923. BeginUpdate;
  924. Points[PointCount-1] := FMousePos;
  925. AddPoint(FMousePos);
  926. EndUpdate;
  927. end;
  928. end else
  929. Usermode := vsuEdit;
  930. AHandled:= true;
  931. end else
  932. begin
  933. if (ssShift in Shift) and (Usermode = vsuEdit) then
  934. begin
  935. BeginUpdate;
  936. AddPoint(EmptyPointF);
  937. AddPoint(FMousePos);
  938. FillFit;
  939. EndUpdate;
  940. UserMode := vsuCreate;
  941. AHandled:= true;
  942. end;
  943. end;
  944. end;
  945. procedure TCustomPolypointShape.KeyDown(Shift: TShiftState; Key: TSpecialKey;
  946. var AHandled: boolean);
  947. var
  948. nb, idx: Integer;
  949. dx, dy, d: TPointF;
  950. begin
  951. if (Key = skDelete) and (FAddingPoint or (HoverPoint <> -1)) then
  952. begin
  953. if (HoverPoint <> -1) then
  954. begin
  955. BeginUpdate(TCustomPolypointShapeDiff);
  956. idx := HoverPoint;
  957. RemovePoint(idx);
  958. if ((idx = PointCount) or IsEmptyPointF(Points[idx])) and
  959. ((idx = 0) or IsEmptyPointF(Points[idx-1])) then
  960. begin
  961. if idx < PointCount then
  962. RemovePoint(idx)
  963. else if idx > 0 then
  964. RemovePoint(idx-1);
  965. end;
  966. EndUpdate;
  967. if PointCount = 0 then self.Remove;
  968. end;
  969. AHandled:= true;
  970. end else
  971. if (Key = skBackspace) and FAddingPoint then
  972. begin
  973. nb := GetLoopPointCount;
  974. if nb > 2 then
  975. RemovePoint(PointCount-2)
  976. else
  977. begin
  978. if GetLoopStartIndex = 0 then self.Remove
  979. else
  980. begin
  981. RemovePointRange(PointCount-3, PointCount);
  982. Usermode:= vsuEdit;
  983. end;
  984. end;
  985. AHandled:= true;
  986. end else
  987. if (Key = skInsert) then
  988. begin
  989. InsertPointAuto(Shift);
  990. AHandled := true;
  991. end else
  992. if (Key in [skLeft,skUp,skRight,skDown]) and ((HoverPoint <> -1) or HoverCenter) then
  993. begin
  994. if ssCtrl in Shift then
  995. begin
  996. dx := PointF(FGridMatrix[1,1], FGridMatrix[2,1]);
  997. dy := PointF(FGridMatrix[1,2], FGridMatrix[2,2]);
  998. end else
  999. begin
  1000. dx := PointF(FViewMatrixInverse[1,1], FViewMatrixInverse[2,1]);
  1001. dy := PointF(FViewMatrixInverse[1,2], FViewMatrixInverse[2,2]);
  1002. end;
  1003. case Key of
  1004. skLeft: d := -dx;
  1005. skRight: d := dx;
  1006. skUp: d := -dy;
  1007. skDown: d := dy;
  1008. else d := PointF(0,0);
  1009. end;
  1010. if HoverCenter then
  1011. Center := Center + d
  1012. else
  1013. Points[HoverPoint] := Points[HoverPoint] + d;
  1014. AHandled := true;
  1015. end else
  1016. if Key = skAlt then
  1017. begin
  1018. BeginUpdate;
  1019. FAltPressed := true;
  1020. EndUpdate;
  1021. AHandled := true;
  1022. end
  1023. else
  1024. inherited KeyDown(Shift, Key, AHandled);
  1025. end;
  1026. procedure TCustomPolypointShape.KeyUp(Shift: TShiftState; Key: TSpecialKey;
  1027. var AHandled: boolean);
  1028. begin
  1029. if Key = skAlt then
  1030. begin
  1031. BeginUpdate;
  1032. FAltPressed := false;
  1033. EndUpdate;
  1034. AHandled := true;
  1035. end
  1036. else inherited KeyUp(Shift, Key, AHandled);
  1037. end;
  1038. procedure TCustomPolypointShape.QuickDefine(constref APoint1, APoint2: TPointF);
  1039. begin
  1040. BeginUpdate(TCustomPolypointShapeDiff);
  1041. FPoints := nil;
  1042. AddPoint(APoint1);
  1043. if not PointsEqual(APoint1,APoint2) then
  1044. AddPoint(APoint2);
  1045. EndUpdate;
  1046. FMousePos := APoint2;
  1047. end;
  1048. procedure TCustomPolypointShape.LoadFromStorage(AStorage: TBGRACustomOriginalStorage);
  1049. var
  1050. x,y: Array of Single;
  1051. i: Integer;
  1052. begin
  1053. BeginUpdate;
  1054. inherited LoadFromStorage(AStorage);
  1055. Clear;
  1056. x := AStorage.FloatArray['x'];
  1057. y := AStorage.FloatArray['y'];
  1058. setlength(FPoints, max(length(x),length(y)));
  1059. for i := 0 to high(FPoints) do
  1060. begin
  1061. FPoints[i].coord := PointF(x[i],y[i]);
  1062. FPoints[i].editorIndex := -1;
  1063. FPoints[i].data := 0;
  1064. end;
  1065. FClosed:= AStorage.Bool['closed'];
  1066. if AStorage.HasAttribute('arrow-size') then
  1067. FArrowSize := AStorage.PointF['arrow-size']
  1068. else FArrowSize := DefaultArrowSize;
  1069. FArrowStartKind:= StrToArrowKind(AStorage.RawString['arrow-start-kind']);
  1070. FArrowEndKind:= StrToArrowKind(AStorage.RawString['arrow-end-kind']);
  1071. Stroker.LineCap := StrToLineCap(AStorage.RawString['line-cap']);
  1072. EndUpdate;
  1073. end;
  1074. procedure TCustomPolypointShape.SaveToStorage(AStorage: TBGRACustomOriginalStorage);
  1075. var
  1076. x,y: Array of Single;
  1077. i: Integer;
  1078. begin
  1079. inherited SaveToStorage(AStorage);
  1080. setlength({%H-}x, PointCount);
  1081. setlength({%H-}y, PointCount);
  1082. for i:= 0 to PointCount-1 do
  1083. begin
  1084. x[i] := Points[i].x;
  1085. y[i] := Points[i].y;
  1086. end;
  1087. AStorage.FloatArray['x'] := x;
  1088. AStorage.FloatArray['y'] := y;
  1089. AStorage.Bool['closed'] := Closed;
  1090. if ArrowStartKind=akNone then AStorage.RemoveAttribute('arrow-start-kind')
  1091. else AStorage.RawString['arrow-start-kind'] := ArrowKindToStr[ArrowStartKind];
  1092. if ArrowEndKind=akNone then AStorage.RemoveAttribute('arrow-end-kind')
  1093. else AStorage.RawString['arrow-end-kind'] := ArrowKindToStr[ArrowEndKind];
  1094. if (ArrowStartKind=akNone) and (ArrowEndKind=akNone) then AStorage.RemoveAttribute('arrow-size')
  1095. else AStorage.PointF['arrow-size'] := FArrowSize;
  1096. AStorage.RawString['line-cap'] := LineCapToStr[Stroker.LineCap];
  1097. end;
  1098. procedure TCustomPolypointShape.ConfigureCustomEditor(AEditor: TBGRAOriginalEditor);
  1099. var
  1100. i, nbTotal: Integer;
  1101. begin
  1102. FViewMatrix := AEditor.Matrix;
  1103. if not IsAffineMatrixInversible(FViewMatrix) then exit;
  1104. FViewMatrixInverse := AffineMatrixInverse(FViewMatrix);
  1105. FGridMatrix := AEditor.GridMatrix;
  1106. AEditor.AddStartMoveHandler(@OnStartMove);
  1107. AEditor.AddClickPointHandler(@OnClickPoint);
  1108. AEditor.AddHoverPointHandler(@OnHoverPoint);
  1109. FCenterPoint := PointF(0,0);
  1110. nbTotal := 0;
  1111. for i:= 0 to PointCount-1 do
  1112. if isEmptyPointF(Points[i]) then
  1113. FPoints[i].editorIndex := -1
  1114. else if (FAddingPoint and (i = PointCount-1) and (GetLoopPointCount > 1)) then
  1115. begin
  1116. FPoints[i].editorIndex := -1;
  1117. FCenterPoint += Points[i];
  1118. inc(nbTotal);
  1119. end
  1120. else
  1121. begin
  1122. if CanMovePoints then
  1123. FPoints[i].editorIndex := AEditor.AddPoint(Points[i], @OnMovePoint, false)
  1124. else
  1125. FPoints[i].editorIndex := AEditor.AddFixedPoint(Points[i], false);
  1126. FCenterPoint += Points[i];
  1127. if i = HoverPoint then
  1128. AEditor.PointHighlighted[FPoints[i].editorIndex] := true;
  1129. inc(nbTotal);
  1130. end;
  1131. if nbTotal > 0 then
  1132. FCenterPoint *= 1/nbTotal
  1133. else FCenterPoint := EmptyPointF;
  1134. if ((FAddingPoint and (nbTotal > 2)) or (not FAddingPoint and (nbTotal > 1)))
  1135. and not FAltPressed then
  1136. begin
  1137. FCenterPointEditorIndex := AEditor.AddPoint(FCenterPoint, @OnMoveCenterPoint, true);
  1138. AEditor.PointHighlighted[FCenterPointEditorIndex] := HoverCenter;
  1139. end else
  1140. FCenterPointEditorIndex := -1;
  1141. end;
  1142. procedure TCustomPolypointShape.TransformFrame(const AMatrix: TAffineMatrix);
  1143. var
  1144. i: Integer;
  1145. m: TAffineMatrix;
  1146. begin
  1147. BeginUpdate(TCustomPolypointShapeDiff);
  1148. m := MatrixForPixelCentered(AMatrix);
  1149. for i := 0 to PointCount-1 do
  1150. FPoints[i].coord := m*FPoints[i].coord;
  1151. EndUpdate;
  1152. end;
  1153. { TPolylineShape }
  1154. class function TPolylineShape.Fields: TVectorShapeFields;
  1155. begin
  1156. Result:= [vsfPenFill, vsfPenWidth, vsfPenStyle, vsfJoinStyle, vsfBackFill, vsfAliased];
  1157. end;
  1158. procedure TPolylineShape.Render(ADest: TBGRABitmap; AMatrix: TAffineMatrix;
  1159. ADraft: boolean);
  1160. var
  1161. pts: array of TPointF;
  1162. backScan, penScan: TBGRACustomScanner;
  1163. begin
  1164. if not GetBackVisible and not GetPenVisible then exit;
  1165. pts := GetCurve(AMatrix);
  1166. if GetBackVisible then
  1167. begin
  1168. if BackFill.FillType = vftSolid then backScan := nil
  1169. else backScan := BackFill.CreateScanner(AMatrix, ADraft);
  1170. if ADraft or Aliased then
  1171. begin
  1172. if Assigned(backScan) then
  1173. ADest.FillPoly(pts, backScan, dmDrawWithTransparency) else
  1174. ADest.FillPoly(pts, BackFill.SolidColor, dmDrawWithTransparency);
  1175. end
  1176. else
  1177. begin
  1178. if Assigned(backScan) then
  1179. ADest.FillPolyAntialias(pts, backScan) else
  1180. ADest.FillPolyAntialias(pts, BackFill.SolidColor);
  1181. end;
  1182. backScan.Free;
  1183. end;
  1184. if GetPenVisible then
  1185. begin
  1186. if PenFill.FillType = vftSolid then penScan := nil
  1187. else penScan := PenFill.CreateScanner(AMatrix, ADraft);
  1188. pts := ComputeStroke(pts, Closed, AMatrix);
  1189. if (ADraft and (PenWidth > 4)) or Aliased then
  1190. begin
  1191. if Assigned(penScan) then
  1192. ADest.FillPoly(pts, penScan, dmDrawWithTransparency) else
  1193. ADest.FillPoly(pts, PenColor, dmDrawWithTransparency);
  1194. end
  1195. else
  1196. begin
  1197. if Assigned(penScan) then
  1198. ADest.FillPolyAntialias(pts, penScan) else
  1199. ADest.FillPolyAntialias(pts, PenColor);
  1200. end;
  1201. penScan.Free;
  1202. end;
  1203. end;
  1204. function TPolylineShape.AppendToSVG(AContent: TSVGContent; ADefs: TSVGDefine): TSVGElement;
  1205. var
  1206. p: TBGRAPath;
  1207. begin
  1208. p := GetPath(AffineMatrixIdentity);
  1209. result := AContent.AppendPath(p.SvgString);
  1210. p.Free;
  1211. ApplyStrokeStyleToSVG(result, ADefs);
  1212. if PenVisible then
  1213. result.strokeLineCapLCL := LineCap;
  1214. ApplyFillStyleToSVG(result, ADefs);
  1215. ApplyAliasingToSVG(result);
  1216. end;
  1217. function TPolylineShape.GetRenderBounds(ADestRect: TRect; AMatrix: TAffineMatrix; AOptions: TRenderBoundsOptions): TRectF;
  1218. var
  1219. pts: ArrayOfTPointF;
  1220. xMargin, yMargin: single;
  1221. fillBounds, penBounds: TRectF;
  1222. begin
  1223. if not (GetBackVisible or (rboAssumeBackFill in AOptions)) and not GetPenVisible(rboAssumePenFill in AOptions) then
  1224. result:= EmptyRectF
  1225. else
  1226. begin
  1227. pts := GetCurve(AMatrix);
  1228. if GetPenVisible(rboAssumePenFill in AOptions) then
  1229. begin
  1230. if (JoinStyle = pjsRound) and (ArrowStartKind = akNone) and (ArrowEndKind = akNone) then
  1231. begin
  1232. xMargin := (abs(AMatrix[1,1])+abs(AMatrix[1,2]))*PenWidth*0.5;
  1233. yMargin := (abs(AMatrix[2,1])+abs(AMatrix[2,2]))*PenWidth*0.5;
  1234. if LineCap = pecSquare then
  1235. begin
  1236. xMargin *= sqrt(2);
  1237. yMargin *= sqrt(2);
  1238. end;
  1239. result := GetPointsBoundsF(pts);
  1240. result.Left -= xMargin;
  1241. result.Top -= yMargin;
  1242. result.Right += xMargin;
  1243. result.Bottom += yMargin;
  1244. end else
  1245. begin
  1246. if GetBackVisible or (rboAssumeBackFill in AOptions) then fillBounds := GetPointsBoundsF(pts)
  1247. else fillBounds := EmptyRectF;
  1248. pts := ComputeStroke(pts, Closed, AMatrix);
  1249. penBounds := GetPointsBoundsF(pts);
  1250. result := fillBounds.Union(penBounds, true);
  1251. end;
  1252. end
  1253. else
  1254. result := GetPointsBoundsF(pts);
  1255. end;
  1256. result.Offset(0.5,0.5);
  1257. end;
  1258. function TPolylineShape.PointInShape(APoint: TPointF): boolean;
  1259. var
  1260. pts: ArrayOfTPointF;
  1261. begin
  1262. if not GetBackVisible and not GetPenVisible then exit(false);
  1263. pts := GetCurve(AffineMatrixIdentity);
  1264. if GetBackVisible and IsPointInPolygon(pts, APoint, true) then exit(true);
  1265. if GetPenVisible then
  1266. begin
  1267. pts := ComputeStroke(pts, Closed, AffineMatrixIdentity);
  1268. if IsPointInPolygon(pts, APoint, true) then exit(true);
  1269. end;
  1270. result := false;
  1271. end;
  1272. function TPolylineShape.PointInShape(APoint: TPointF; ARadius: single): boolean;
  1273. var
  1274. pts: ArrayOfTPointF;
  1275. begin
  1276. if not GetBackVisible and not GetPenVisible then exit(false);
  1277. pts := GetCurve(AffineMatrixIdentity);
  1278. pts := ComputeStrokeEnvelope(pts, Closed, ARadius*2);
  1279. result := IsPointInPolygon(pts, APoint, true);
  1280. end;
  1281. function TPolylineShape.PointInBack(APoint: TPointF): boolean;
  1282. var
  1283. pts: ArrayOfTPointF;
  1284. scan: TBGRACustomScanner;
  1285. begin
  1286. if GetBackVisible then
  1287. begin
  1288. pts := GetCurve(AffineMatrixIdentity);
  1289. result := IsPointInPolygon(pts, APoint, true);
  1290. if result and (BackFill.FillType = vftTexture) then
  1291. begin
  1292. scan := BackFill.CreateScanner(AffineMatrixIdentity, false);
  1293. if scan.ScanAt(APoint.X,APoint.Y).alpha = 0 then result := false;
  1294. scan.Free;
  1295. end;
  1296. end else
  1297. result := false;
  1298. end;
  1299. function TPolylineShape.PointInPen(APoint: TPointF): boolean;
  1300. var
  1301. pts: ArrayOfTPointF;
  1302. begin
  1303. if GetBackVisible then
  1304. begin
  1305. pts := GetCurve(AffineMatrixIdentity);
  1306. pts := ComputeStroke(pts, Closed, AffineMatrixIdentity);
  1307. result := IsPointInPolygon(pts, APoint, true);
  1308. end else
  1309. result := false;
  1310. end;
  1311. function TPolylineShape.GetIsSlow(const AMatrix: TAffineMatrix): boolean;
  1312. var pts: ArrayOfTPointF;
  1313. i: Integer;
  1314. ptsBounds: TRectF;
  1315. backSurface: Single;
  1316. penLength, zoomFactor, penSurface, totalSurface: single;
  1317. begin
  1318. if not GetPenVisible and not GetBackVisible or (PointCount = 0) then exit(false);
  1319. setlength({%H-}pts, PointCount);
  1320. for i := 0 to high(pts) do
  1321. pts[i] := AMatrix * Points[i];
  1322. if GetPenVisible then
  1323. begin
  1324. penLength := 0;
  1325. zoomFactor := max(VectLen(AMatrix[1,1],AMatrix[2,1]), VectLen(AMatrix[1,2],AMatrix[2,2]));
  1326. for i := 0 to high(pts) do
  1327. if (i > 0) then
  1328. begin
  1329. if pts[i-1].IsEmpty then
  1330. begin
  1331. if not pts[i].IsEmpty and (LineCap <> pecFlat) then penLength += penWidth/2*zoomFactor;
  1332. end else
  1333. if pts[i].IsEmpty then
  1334. begin
  1335. if not pts[i-1].IsEmpty and (LineCap <> pecFlat) then penLength += penWidth/2*zoomFactor;
  1336. end else
  1337. penLength += VectLen(pts[i]-pts[i-1]);
  1338. end;
  1339. penSurface := penLength*PenWidth*zoomFactor;
  1340. end else penSurface := 0;
  1341. if GetBackVisible then
  1342. begin
  1343. ptsBounds := GetPointsBoundsF(pts);
  1344. backSurface := ptsBounds.Width*ptsBounds.Height;
  1345. end else
  1346. backSurface := 0;
  1347. if GetPenVisible and GetBackVisible then totalSurface := backSurface+penSurface/2
  1348. else totalSurface := backSurface+penSurface;
  1349. Result:= (PointCount > 40) or
  1350. ((penSurface > 320*240) and PenFill.IsSlow(AMatrix)) or
  1351. ((backSurface > 320*240) and BackFill.IsSlow(AMatrix)) or
  1352. (totalSurface > 640*480);
  1353. end;
  1354. class function TPolylineShape.StorageClassName: RawByteString;
  1355. begin
  1356. result := 'polyline';
  1357. end;
  1358. { TCurveShape }
  1359. procedure TCurveShape.SetSplineStyle(AValue: TSplineStyle);
  1360. begin
  1361. if FSplineStyle=AValue then Exit;
  1362. BeginUpdate(TCurveShapeDiff);
  1363. FSplineStyle:=AValue;
  1364. EndUpdate;
  1365. end;
  1366. function TCurveShape.GetCurveMode(AIndex: integer): TEasyBezierCurveMode;
  1367. begin
  1368. if (AIndex < 0) or (AIndex >= PointCount) then exit(cmCurve);
  1369. result := TEasyBezierCurveMode(FPoints[AIndex].data);
  1370. end;
  1371. procedure TCurveShape.SetCosineAngle(AValue: single);
  1372. begin
  1373. if FCosineAngle=AValue then Exit;
  1374. BeginUpdate(TCurveShapeDiff);
  1375. FCosineAngle:=AValue;
  1376. EndUpdate;
  1377. end;
  1378. procedure TCurveShape.SetCurveMode(AIndex: integer; AValue: TEasyBezierCurveMode);
  1379. begin
  1380. if (AIndex < 0) or (AIndex >= PointCount) then exit;
  1381. if CurveMode[AIndex] = AValue then exit;
  1382. BeginUpdate(TCustomPolypointShapeDiff);
  1383. FPoints[AIndex].data := ord(AValue);
  1384. EndUpdate
  1385. end;
  1386. function TCurveShape.GetCurve(AMatrix: TAffineMatrix): ArrayOfTPointF;
  1387. var
  1388. pts: array of TPointF;
  1389. cm: array of TEasyBezierCurveMode;
  1390. i: Integer;
  1391. eb: TEasyBezierCurve;
  1392. begin
  1393. pts := inherited GetCurve(AMatrix);
  1394. if FSplineStyle = ssEasyBezier then
  1395. begin
  1396. setlength({%H-}cm, PointCount);
  1397. for i := 0 to PointCount-1 do
  1398. cm[i] := CurveMode[i];
  1399. eb := EasyBezierCurve(pts, Closed, cm, CosineAngle);
  1400. result := eb.ToPoints;
  1401. end else
  1402. begin
  1403. if Closed then result := ComputeClosedSpline(pts, FSplineStyle)
  1404. else result := ComputeOpenedSpline(pts, FSplineStyle);
  1405. end;
  1406. end;
  1407. function TCurveShape.GetPath(AMatrix: TAffineMatrix): TBGRAPath;
  1408. var
  1409. pts: array of TPointF;
  1410. cm: array of TEasyBezierCurveMode;
  1411. i: Integer;
  1412. eb: TEasyBezierCurve;
  1413. begin
  1414. pts := inherited GetCurve(AMatrix);
  1415. if FSplineStyle = ssEasyBezier then
  1416. begin
  1417. setlength({%H-}cm, PointCount);
  1418. for i := 0 to PointCount-1 do
  1419. cm[i] := CurveMode[i];
  1420. eb := EasyBezierCurve(pts, Closed, cm, CosineAngle);
  1421. result := TBGRAPath.Create;
  1422. eb.CopyToPath(result);
  1423. end else
  1424. begin
  1425. if Closed then pts := ComputeClosedSpline(pts, FSplineStyle)
  1426. else pts := ComputeOpenedSpline(pts, FSplineStyle);
  1427. result := GetPath(pts);
  1428. end;
  1429. end;
  1430. function TCurveShape.CanMovePoints: boolean;
  1431. begin
  1432. Result:= Usermode in [vsuCreate,vsuEdit];
  1433. end;
  1434. procedure TCurveShape.DoClickPoint(APointIndex: integer; AShift: TShiftState);
  1435. begin
  1436. case Usermode of
  1437. vsuCurveSetAuto: CurveMode[APointIndex] := cmAuto;
  1438. vsuCurveSetCurve: CurveMode[APointIndex] := cmCurve;
  1439. vsuCurveSetAngle: CurveMode[APointIndex] := cmAngle;
  1440. else
  1441. inherited DoClickPoint(APointIndex, AShift);
  1442. end;
  1443. end;
  1444. class function TCurveShape.Usermodes: TVectorShapeUsermodes;
  1445. begin
  1446. Result:=inherited Usermodes + [vsuCurveSetAuto, vsuCurveSetCurve, vsuCurveSetAngle];
  1447. end;
  1448. constructor TCurveShape.Create(AContainer: TVectorOriginal);
  1449. begin
  1450. inherited Create(AContainer);
  1451. FSplineStyle:= ssEasyBezier;
  1452. end;
  1453. constructor TCurveShape.CreateFrom(AContainer: TVectorOriginal;
  1454. AShape: TVectorShape);
  1455. var
  1456. r: TCustomRectShape;
  1457. u, v: TPointF;
  1458. p: TCustomPolypointShape;
  1459. i: Integer;
  1460. f: TVectorShapeFields;
  1461. sq2m1: single;
  1462. begin
  1463. Create(AContainer);
  1464. if AShape is TEllipseShape then
  1465. begin
  1466. r := AShape as TCustomRectShape;
  1467. u := r.XAxis-r.Origin;
  1468. v := r.YAxis-r.Origin;
  1469. sq2m1 := sqrt(2)-1;
  1470. AddPoint(r.Origin-v+u*sq2m1);
  1471. AddPoint(r.Origin-v*sq2m1+u);
  1472. AddPoint(r.Origin+v*sq2m1+u);
  1473. AddPoint(r.Origin+v+u*sq2m1);
  1474. AddPoint(r.Origin+v-u*sq2m1);
  1475. AddPoint(r.Origin+v*sq2m1-u);
  1476. AddPoint(r.Origin-v*sq2m1-u);
  1477. AddPoint(r.Origin-v-u*sq2m1);
  1478. Closed := true;
  1479. end else
  1480. if AShape is TRectShape then
  1481. begin
  1482. r := AShape as TCustomRectShape;
  1483. u := r.XAxis-r.Origin;
  1484. v := r.YAxis-r.Origin;
  1485. AddPoint(r.Origin-v-u, cmAngle);
  1486. AddPoint(r.Origin-v+u, cmAngle);
  1487. AddPoint(r.Origin+v+u, cmAngle);
  1488. AddPoint(r.Origin+v-u, cmAngle);
  1489. Closed := true;
  1490. end else
  1491. if (AShape is TPolylineShape) and not
  1492. (AShape is TCurveShape) then
  1493. begin
  1494. p := AShape as TCustomPolypointShape;
  1495. for i := 0 to p.PointCount-1 do
  1496. AddPoint(p.Points[i], cmAngle);
  1497. Closed := p.Closed;
  1498. end else
  1499. raise exception.Create(errShapeNotHandled);
  1500. f := AShape.Fields;
  1501. if vsfPenFill in f then PenFill.Assign(AShape.PenFill);
  1502. if vsfPenWidth in f then PenWidth := AShape.PenWidth;
  1503. if vsfPenStyle in f then PenStyle := AShape.PenStyle;
  1504. if vsfJoinStyle in f then JoinStyle := AShape.JoinStyle;
  1505. if vsfBackFill in f then BackFill.Assign(AShape.BackFill);
  1506. end;
  1507. class function TCurveShape.CanCreateFrom(AShape: TVectorShape): boolean;
  1508. begin
  1509. result := (AShape is TEllipseShape) or
  1510. (AShape is TRectShape) or
  1511. ((AShape is TPolylineShape) and not
  1512. (AShape is TCurveShape));
  1513. end;
  1514. function TCurveShape.AddPoint(const APoint: TPointF): integer;
  1515. begin
  1516. if (PointCount > 1) and (APoint = Points[PointCount-1]) then
  1517. begin
  1518. BeginUpdate;
  1519. CurveMode[PointCount-1] := CurveMode[PointCount-2];
  1520. Result:=inherited AddPoint(APoint);
  1521. EndUpdate;
  1522. end
  1523. else Result:=inherited AddPoint(APoint);
  1524. end;
  1525. function TCurveShape.AddPoint(const APoint: TPointF; AMode: TEasyBezierCurveMode): integer;
  1526. begin
  1527. result := inherited AddPoint(APoint);
  1528. CurveMode[result] := AMode;
  1529. end;
  1530. procedure TCurveShape.KeyPress(UTF8Key: string; var AHandled: boolean);
  1531. var
  1532. targetPoint: Integer;
  1533. begin
  1534. if HoverPoint<>-1 then
  1535. targetPoint := HoverPoint
  1536. else if FAddingPoint and (PointCount > 1) then
  1537. targetPoint := PointCount-2
  1538. else
  1539. targetPoint := -1;
  1540. if (targetPoint >= 0) and (targetPoint < PointCount) then
  1541. begin
  1542. if (UTF8Key = 'A') or (UTF8Key = 'a') then
  1543. begin
  1544. CurveMode[targetPoint] := cmAuto;
  1545. AHandled := true;
  1546. end else
  1547. if (UTF8Key = 'S') or (UTF8Key = 's') then
  1548. begin
  1549. CurveMode[targetPoint] := cmCurve;
  1550. AHandled:= true;
  1551. end else
  1552. if (UTF8Key = 'X') or (UTF8Key = 'x') then
  1553. begin
  1554. CurveMode[targetPoint] := cmAngle;
  1555. AHandled:= true;
  1556. end;
  1557. end;
  1558. if not AHandled then
  1559. inherited KeyPress(UTF8Key, AHandled);
  1560. end;
  1561. procedure TCurveShape.LoadFromStorage(AStorage: TBGRACustomOriginalStorage);
  1562. var
  1563. i: Integer;
  1564. cm: array of Single;
  1565. begin
  1566. BeginUpdate;
  1567. inherited LoadFromStorage(AStorage);
  1568. case AStorage.RawString['spline-style'] of
  1569. 'inside': SplineStyle := ssInside;
  1570. 'inside+ends': SplineStyle := ssInsideWithEnds;
  1571. 'crossing': SplineStyle := ssCrossing;
  1572. 'crossing+ends': SplineStyle := ssCrossingWithEnds;
  1573. 'outside': SplineStyle := ssOutside;
  1574. 'round-outside': SplineStyle := ssRoundOutside;
  1575. 'vertex-to-side': SplineStyle := ssVertexToSide;
  1576. else
  1577. {'easy-bezier'} SplineStyle := ssEasyBezier;
  1578. end;
  1579. if SplineStyle = ssEasyBezier then
  1580. begin
  1581. cm := AStorage.FloatArray['curve-mode'];
  1582. for i := 0 to min(high(cm),PointCount-1) do
  1583. case round(cm[i]) of
  1584. 1: CurveMode[i] := cmCurve;
  1585. 2: CurveMode[i] := cmAngle;
  1586. end;
  1587. if length(cm) < PointCount then
  1588. for i:= length(cm) to PointCount-1 do
  1589. CurveMode[i] := cmCurve;
  1590. end;
  1591. CosineAngle := AStorage.FloatDef['cosine-angle', EasyBezierDefaultMinimumDotProduct];
  1592. EndUpdate;
  1593. end;
  1594. procedure TCurveShape.SaveToStorage(AStorage: TBGRACustomOriginalStorage);
  1595. var s: string;
  1596. cm: array of single;
  1597. i: Integer;
  1598. begin
  1599. inherited SaveToStorage(AStorage);
  1600. case SplineStyle of
  1601. ssInside: s := 'inside';
  1602. ssInsideWithEnds: s := 'inside+ends';
  1603. ssCrossing: s := 'crossing';
  1604. ssCrossingWithEnds: s := 'crossing+ends';
  1605. ssOutside: s := 'outside';
  1606. ssRoundOutside: s := 'round-outside';
  1607. ssVertexToSide: s := 'vertex-to-side';
  1608. ssEasyBezier: s := 'easy-bezier';
  1609. else s := '';
  1610. end;
  1611. AStorage.RawString['spline-style'] := s;
  1612. if SplineStyle = ssEasyBezier then
  1613. begin
  1614. setlength({%H-}cm, PointCount);
  1615. for i := 0 to PointCount-1 do
  1616. cm[i] := ord(CurveMode[i]);
  1617. AStorage.FloatArray['curve-mode'] := cm;
  1618. end;
  1619. AStorage.Float['cosine-angle'] := CosineAngle;
  1620. end;
  1621. class function TCurveShape.StorageClassName: RawByteString;
  1622. begin
  1623. Result:= 'curve';
  1624. end;
  1625. initialization
  1626. RegisterVectorShape(TPolylineShape);
  1627. RegisterVectorShape(TCurveShape);
  1628. end.