utooltext.pas 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307
  1. // SPDX-License-Identifier: GPL-3.0-only
  2. unit UToolText;
  3. {$mode objfpc}{$H+}
  4. interface
  5. uses
  6. Classes, SysUtils, UTool, UToolVectorial, LCLType, Graphics, BGRABitmap, BGRABitmapTypes, BGRATextFX,
  7. BGRAGradients, LCVectorOriginal;
  8. type
  9. { TToolText }
  10. TToolText = class(TVectorialTool)
  11. protected
  12. FMatrix: TAffineMatrix;
  13. FPrevShadow: boolean;
  14. FPrevShadowOffset: TPoint;
  15. FPrevShadowRadius: single;
  16. function ShapeClass: TVectorShapeAny; override;
  17. function CreateShape: TVectorShape; override;
  18. function AlwaysRasterizeShape: boolean; override;
  19. procedure IncludeShadowBounds(var ARect: TRect);
  20. function GetCustomShapeBounds(ADestBounds: TRect; AMatrix: TAffineMatrix; ADraft: boolean): TRect; override;
  21. procedure DrawCustomShape(ADest: TBGRABitmap; AMatrix: TAffineMatrix; ADraft: boolean); override;
  22. procedure ShapeChange(ASender: TObject; ABounds: TRectF; ADiff: TVectorShapeDiff); override;
  23. procedure ShapeEditingChange(ASender: TObject); override;
  24. procedure AssignShapeStyle(AMatrix: TAffineMatrix; AAlwaysFit: boolean); override;
  25. procedure QuickDefineEnd; override;
  26. function RoundCoordinate(constref ptF: TPointF): TPointF; override;
  27. function DoToolKeyDown(var key: Word): TRect; override;
  28. function GetIsEditingText: boolean; override;
  29. public
  30. constructor Create(AManager: TToolManager); override;
  31. function GetContextualToolbars: TContextualToolbars; override;
  32. function ToolCommand(ACommand: TToolCommand): boolean; override;
  33. function ToolProvideCommand(ACommand: TToolCommand): boolean; override;
  34. end;
  35. implementation
  36. uses LCVectorTextShapes, BGRALayerOriginal, BGRATransform, BGRAGrayscaleMask,
  37. ugraph, math;
  38. { TToolText }
  39. function TToolText.ShapeClass: TVectorShapeAny;
  40. begin
  41. result := TTextShape;
  42. end;
  43. function TToolText.CreateShape: TVectorShape;
  44. begin
  45. Result:=inherited CreateShape;
  46. if result is TTextShape then
  47. (result as TTextShape).FontBidiMode:= Manager.TextBidiMode
  48. end;
  49. function TToolText.AlwaysRasterizeShape: boolean;
  50. begin
  51. Result:= Manager.TextShadow;
  52. end;
  53. procedure TToolText.IncludeShadowBounds(var ARect: TRect);
  54. var
  55. shadowRect: TRect;
  56. begin
  57. if Manager.TextShadow then
  58. begin
  59. shadowRect := ARect;
  60. shadowRect.Inflate(ceil(Manager.TextShadowBlurRadius),ceil(Manager.TextShadowBlurRadius));
  61. shadowRect.Offset(Manager.TextShadowOffset.X,Manager.TextShadowOffset.Y);
  62. ARect := RectUnion(ARect, shadowRect);
  63. end;
  64. end;
  65. function TToolText.GetCustomShapeBounds(ADestBounds: TRect; AMatrix: TAffineMatrix; ADraft: boolean): TRect;
  66. begin
  67. Result:= inherited GetCustomShapeBounds(ADestBounds, AMatrix, ADraft);
  68. IncludeShadowBounds(result);
  69. result.Intersect(ADestBounds);
  70. end;
  71. procedure TToolText.DrawCustomShape(ADest: TBGRABitmap; AMatrix: TAffineMatrix; ADraft: boolean);
  72. var
  73. temp: TBGRABitmap;
  74. blur, gray, grayShape: TGrayscaleMask;
  75. shapeBounds, blurBounds, r, actualShapeBounds: TRect;
  76. begin
  77. if Manager.TextShadow then
  78. begin
  79. shapeBounds := GetCustomShapeBounds(rect(0,0,ADest.Width,ADest.Height),AMatrix,ADraft);
  80. shapeBounds.Intersect(ADest.ClipRect);
  81. if (shapeBounds.Width > 0) and (shapeBounds.Height > 0) then
  82. begin
  83. temp := TBGRABitmap.Create(shapeBounds.Width,shapeBounds.Height);
  84. inherited DrawCustomShape(temp, AffineMatrixTranslation(-shapeBounds.Left,-shapeBounds.Top)*AMatrix, ADraft);
  85. actualShapeBounds := temp.GetImageBounds;
  86. if not actualShapeBounds.IsEmpty then
  87. begin
  88. actualShapeBounds.Offset(shapeBounds.Left,shapeBounds.Top);
  89. grayShape := TGrayscaleMask.Create;
  90. grayShape.CopyFrom(temp, cAlpha);
  91. blurBounds := actualShapeBounds;
  92. blurBounds.Inflate(ceil(Manager.TextShadowBlurRadius),ceil(Manager.TextShadowBlurRadius));
  93. blurBounds.Offset(Manager.TextShadowOffset.X,Manager.TextShadowOffset.Y);
  94. r := ADest.ClipRect;
  95. r.Inflate(ceil(Manager.TextShadowBlurRadius),ceil(Manager.TextShadowBlurRadius));
  96. blurBounds.Intersect(r);
  97. gray := TGrayscaleMask.Create(blurBounds.Width,blurBounds.Height);
  98. gray.PutImage(shapeBounds.Left-blurBounds.Left+Manager.TextShadowOffset.X,
  99. shapeBounds.Top-blurBounds.Top+Manager.TextShadowOffset.Y,grayShape,dmSet);
  100. grayShape.Free;
  101. blur := gray.FilterBlurRadial(Manager.TextShadowBlurRadius,Manager.TextShadowBlurRadius, rbFast) as TGrayscaleMask;
  102. gray.Free;
  103. ADest.FillMask(blurBounds.Left,blurBounds.Top,blur,BGRABlack,dmDrawWithTransparency);
  104. blur.Free;
  105. end;
  106. ADest.PutImage(shapeBounds.Left,shapeBounds.Top,temp,dmDrawWithTransparency);
  107. temp.Free;
  108. end;
  109. FPrevShadow := true;
  110. FPrevShadowRadius := Manager.TextShadowBlurRadius;
  111. FPrevShadowOffset := Manager.TextShadowOffset;
  112. end else
  113. begin
  114. inherited DrawCustomShape(ADest, AMatrix, ADraft);
  115. FPrevShadow := false;
  116. end;
  117. end;
  118. procedure TToolText.ShapeChange(ASender: TObject; ABounds: TRectF; ADiff: TVectorShapeDiff);
  119. var
  120. r: TRect;
  121. posF: TPointF;
  122. begin
  123. posF := AffineMatrixInverse(FMatrix)*(FShape as TTextShape).LightPosition;
  124. Manager.LightPosition := posF;
  125. with ABounds do r := rect(floor(Left),floor(Top),ceil(Right),ceil(Bottom));
  126. IncludeShadowBounds(r);
  127. inherited ShapeChange(ASender, RectF(r.Left,r.Top,r.Right,r.Bottom), ADiff);
  128. end;
  129. procedure TToolText.ShapeEditingChange(ASender: TObject);
  130. begin
  131. with (FShape as TTextShape) do
  132. begin
  133. Manager.TextFontStyle := FontStyle;
  134. Manager.TextBidiMode := FontBidiMode;
  135. Manager.TextAlign := ParagraphAlignment;
  136. Manager.TextVerticalAlign := VerticalAlignment;
  137. end;
  138. inherited ShapeEditingChange(ASender);
  139. end;
  140. procedure TToolText.AssignShapeStyle(AMatrix: TAffineMatrix; AAlwaysFit: boolean);
  141. var
  142. r: TRect;
  143. toolDest: TBGRABitmap;
  144. zoom: Single;
  145. begin
  146. inherited AssignShapeStyle(AMatrix, AAlwaysFit);
  147. FMatrix := AMatrix;
  148. with TTextShape(FShape) do
  149. begin
  150. zoom := (VectLen(AMatrix[1,1],AMatrix[2,1])+VectLen(AMatrix[1,2],AMatrix[2,2]))/2;
  151. FontEmHeight:= zoom*Manager.TextFontSize*Manager.Image.DPI/72;
  152. FontName:= Manager.TextFontName;
  153. FontStyle:= Manager.TextFontStyle;
  154. LightPosition := AMatrix*Manager.LightPosition;
  155. AltitudePercent:= Manager.PhongShapeAltitude;
  156. if FontBidiMode <> Manager.TextBidiMode then
  157. begin
  158. FontBidiMode:= Manager.TextBidiMode;
  159. Manager.TextAlign := ParagraphAlignment; // alignment depend on bidi mode
  160. end
  161. else
  162. ParagraphAlignment:= Manager.TextAlign;
  163. VerticalAlignment := Manager.TextVerticalAlign;
  164. PenPhong := Manager.TextPhong;
  165. end;
  166. if (Manager.TextShadow <> FPrevShadow) or
  167. (FPrevShadow and
  168. ((Manager.TextShadowBlurRadius <> FPrevShadowRadius) or
  169. (Manager.TextShadowOffset <> FPrevShadowOffset))) then
  170. begin
  171. toolDest := GetToolDrawingLayer;
  172. r:= UpdateShape(toolDest);
  173. Action.NotifyChange(toolDest, r);
  174. end;
  175. end;
  176. procedure TToolText.QuickDefineEnd;
  177. begin
  178. FShape.Usermode := vsuEditText;
  179. end;
  180. function TToolText.RoundCoordinate(constref ptF: TPointF): TPointF;
  181. begin
  182. result := PointF(floor(ptF.x)+0.5,floor(ptF.y)+0.5);
  183. end;
  184. constructor TToolText.Create(AManager: TToolManager);
  185. begin
  186. inherited Create(AManager);
  187. FMatrix := AffineMatrixIdentity;
  188. end;
  189. function TToolText.GetContextualToolbars: TContextualToolbars;
  190. begin
  191. Result:= [ctPenFill,ctText,ctOutlineFill,ctOutlineWidth,ctAliasing];
  192. if Manager.TextPhong then include(result, ctAltitude);
  193. end;
  194. function TToolText.DoToolKeyDown(var key: Word): TRect;
  195. var
  196. keyUtf8: TUTF8Char;
  197. handled: Boolean;
  198. alignBefore: TAlignment;
  199. begin
  200. if FShape is TTextShape then
  201. alignBefore := (FShape as TTextShape).ParagraphAlignment
  202. else
  203. alignBefore := taLeftJustify;
  204. if Key = VK_SPACE then
  205. begin
  206. keyUtf8:= ' ';
  207. result := ToolKeyPress(keyUtf8);
  208. if keyUtf8 <> ' ' then Key := 0;
  209. end else
  210. if (Key = VK_ESCAPE) and Assigned(FShape) then
  211. begin
  212. if FShape.Usermode = vsuEditText then
  213. begin
  214. result := EmptyRect;
  215. FShape.Usermode := vsuEdit
  216. end
  217. else
  218. result := ValidateShape;
  219. Key := 0;
  220. end else
  221. if (Key = VK_RETURN) and Assigned(FShape) then
  222. begin
  223. handled := false;
  224. result := EmptyRect;
  225. FShape.KeyDown(ShiftState, skReturn, handled);
  226. if not handled then ValidateShape;
  227. Key := 0;
  228. end else
  229. Result:=inherited DoToolKeyDown(key);
  230. if (FShape is TTextShape) and (alignBefore <> (FShape as TTextShape).ParagraphAlignment) then
  231. Manager.TextAlign := (FShape as TTextShape).ParagraphAlignment;
  232. end;
  233. function TToolText.GetIsEditingText: boolean;
  234. begin
  235. Result:= Assigned(FShape) and (FShape.Usermode = vsuEditText);
  236. end;
  237. function TToolText.ToolCommand(ACommand: TToolCommand): boolean;
  238. begin
  239. if Assigned(FShape) and (FShape.Usermode = vsuEditText) then
  240. case ACommand of
  241. tcCopy: Result:= TTextShape(FShape).CopySelection;
  242. tcCut: Result:= TTextShape(FShape).CutSelection;
  243. tcPaste: Result:= TTextShape(FShape).PasteSelection;
  244. tcDelete: Result:= TTextShape(FShape).DeleteSelection;
  245. else
  246. result := inherited ToolCommand(ACommand);
  247. end
  248. else
  249. case ACommand of
  250. tcDelete:
  251. if Assigned(FShape) then
  252. begin
  253. CancelShape;
  254. result := true;
  255. end else result := false;
  256. else result := inherited ToolCommand(ACommand);
  257. end;
  258. end;
  259. function TToolText.ToolProvideCommand(ACommand: TToolCommand): boolean;
  260. begin
  261. if Assigned(FShape) and (FShape.Usermode = vsuEditText) then
  262. case ACommand of
  263. tcCopy,tcCut,tcDelete: result := TTextShape(FShape).HasSelection;
  264. tcPaste: result := true;
  265. else
  266. result := inherited ToolProvideCommand(ACommand);
  267. end
  268. else
  269. case ACommand of
  270. tcDelete: result := Assigned(FShape);
  271. else result := inherited ToolProvideCommand(ACommand);
  272. end;
  273. end;
  274. initialization
  275. RegisterTool(ptText, TToolText);
  276. end.