lcvectorpolyshapes.pas 48 KB

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