bctools.pas 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736
  1. // SPDX-License-Identifier: LGPL-3.0-linking-exception
  2. { General framework methods for rendering background, borders, text, etc.
  3. originally written in 2012 by Krzysztof Dibowski dibowski at interia.pl
  4. }
  5. {******************************* CONTRIBUTOR(S) ******************************
  6. - Edivando S. Santos Brasil | [email protected]
  7. (Compatibility with delphi VCL 11/2018)
  8. ***************************** END CONTRIBUTOR(S) *****************************}
  9. unit BCTools;
  10. {$I bgracontrols.inc}
  11. interface
  12. uses
  13. Classes, SysUtils, Types, Graphics,
  14. {$IFDEF FPC}LCLType, LCLIntf,{$ENDIF} {$IFNDEF FPC}BGRAGraphics, GraphType, FPImage, {$ENDIF}
  15. BGRABitmap, BGRABitmapTypes, bctypes, Controls, BGRAGradientScanner;
  16. function ScaleRect(ARect: TRect; AScale: Single): TRect;
  17. // This method prepare BGRABitmap for rendering BCFont type
  18. procedure AssignBCFont(AFont: TBCFont; var ATargetBGRA: TBGRABitmap);
  19. // Calculate text height and width (doesn't include wordwrap - just single line)
  20. procedure CalculateTextSize(const AText: String; AFont: TBCFont; out ANewWidth, ANewHeight: integer;
  21. AShadowMargin: boolean = true);
  22. // Calculate text height and width (handles wordwrap and end ellipsis)
  23. procedure CalculateTextSizeEx(const AText: String; AFont: TBCFont; out ANewWidth, ANewHeight: integer;
  24. AAvailableWidth: integer; AShadowMargin: boolean = false);
  25. // Determines the layout of the glyph
  26. procedure GetGlyphActualLayout(ACaption: string; AFont: TBCFont;
  27. AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; out AHorizAlign: TAlignment;
  28. out AVertAlign: TTextLayout; out AGlyphRelativeHorizAlign: TAlignment;
  29. out AGlyphRelativeVertAlign: TTextLayout; out AGlyphHorizMargin: integer;
  30. out AGlyphVertMargin: integer);
  31. // Computes the position the glyph and update rAvail with the space dedicated to text.
  32. // Specify the flag AOldPlacement to have the old (buggy) version
  33. function ComputeGlyphPosition(var rAvail: TRect;
  34. AGlyph: TBitmap; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer;
  35. ACaption: string; AFont: TBCFont; AOldPlacement: boolean = false;
  36. AGlyphScale: Single = 1): TRect; overload;
  37. function ComputeGlyphPosition(var rAvail: TRect;
  38. gw, gh: integer; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer;
  39. ACaption: string; AFont: TBCFont; AOldPlacement: boolean = false): TRect; overload;
  40. // This method correct TRect to border width. As far as border width is bigger,
  41. // BGRA drawing rectangle with offset (half border width)
  42. procedure CalculateBorderRect(ABorder: TBCBorder; var ARect: TRect);
  43. // This returns a rectangle that is inside the border outline
  44. procedure CalculateInnerRect(ABorder: TBCBorder; var ARect: TRect);
  45. // Create BGRA Gradient Scanner based on BCGradient properties
  46. function CreateGradient(AGradient: TBCGradient; ARect: TRect): TBGRAGradientScanner;
  47. // Render arrow (used by BCButton with DropDownMenu style)
  48. procedure RenderArrow(ATargetBGRA: TBGRABitmap; const ARect: TRect;
  49. ASize: Integer; ADirection: TBCArrowDirection; AColor: TColor = clBlack;
  50. AOpacity: Byte = 255);
  51. // Render customizable backgroud (used e.g. by TBCButton, TBCPanel, TBCLabel)
  52. procedure RenderBackground(const ARect: TRect; ABackground: TBCBackground;
  53. ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil; AHasNoBorder: boolean = false);
  54. procedure RenderBackgroundF(x1,y1,x2,y2: single; ABackground: TBCBackground;
  55. ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
  56. procedure RenderBackgroundAndBorder(const ARect: TRect; ABackground: TBCBackground;
  57. ATargetBGRA: TBGRABitmap; ARounding: TBCRounding; ABorder: TBCBorder; AInnerMargin: single = 0);
  58. // Render customizable border (used e.g. by TBCButton, TBCPanel, TBCLabel)
  59. procedure RenderBorder(const ARect: TRect; ABorder: TBCBorder;
  60. ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
  61. procedure RenderBorderF(x1,y1,x2,y2: single; ABorder: TBCBorder;
  62. ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
  63. // Render BCFont (used e.g. by TBCButton, TBCPanel, TBCLabel)
  64. procedure RenderText(const ARect: TRect; AFont: TBCFont;
  65. const AText: String; ATargetBGRA: TBGRABitmap; AEnabled: boolean);
  66. // Return LCL horizontal equivalent for BCAlignment
  67. function BCAlign2HAlign(AAlign: TBCAlignment): TAlignment;
  68. // Return LCL vertical equivalent for BCAlignment
  69. function BCAlign2VAlign(AAlign: TBCAlignment): TTextLayout;
  70. implementation
  71. uses BGRAPolygon, BGRAFillInfo, BGRAText, math, BGRAUTF8, LazUTF8;
  72. function ComputeGlyphPosition(var rAvail: TRect; AGlyph: TBitmap;
  73. AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; ACaption: string;
  74. AFont: TBCFont; AOldPlacement: boolean; AGlyphScale: Single): TRect;
  75. var gw, gh: integer;
  76. begin
  77. if Assigned(AGlyph) and not AGlyph.Empty then
  78. begin
  79. gw := round(AGlyph.Width * AGlyphScale);
  80. gh := round(AGlyph.Height * AGlyphScale);
  81. end else
  82. begin
  83. gw := 0;
  84. gh := 0;
  85. end;
  86. result := ComputeGlyphPosition(rAvail, gw, gh, AGlyphAlignment, AGlyphMargin, ACaption,
  87. AFont, AOldPlacement);
  88. end;
  89. procedure CalculateBorderRect(ABorder: TBCBorder; var ARect: TRect);
  90. var w: integer;
  91. begin
  92. if ABorder = nil then Exit;
  93. w := ABorder.Width div 2;
  94. Inc(ARect.Left, w);
  95. Inc(ARect.Top, w);
  96. Dec(ARect.Right, w);
  97. Dec(ARect.Bottom, w);
  98. end;
  99. procedure CalculateInnerRect(ABorder: TBCBorder; var ARect: TRect);
  100. var w: integer;
  101. begin
  102. if (ABorder = nil) or (ABorder.Style = bboNone) then Exit;
  103. w := ABorder.Width;
  104. Inc(ARect.Left, w);
  105. Inc(ARect.Top, w);
  106. Dec(ARect.Right, w);
  107. Dec(ARect.Bottom, w);
  108. end;
  109. function CreateGradient(AGradient: TBCGradient; ARect: TRect): TBGRAGradientScanner;
  110. begin
  111. Result := TBGRAGradientScanner.Create(
  112. ColorToBGRA(ColorToRGB(AGradient.StartColor), AGradient.StartColorOpacity),
  113. ColorToBGRA(ColorToRGB(AGradient.EndColor), AGradient.EndColorOpacity),
  114. AGradient.GradientType, PointF(ARect.Left + Round(
  115. ((ARect.Right - ARect.Left) / 100) * AGradient.Point1XPercent),
  116. ARect.Top + Round(((ARect.Bottom - ARect.Top) / 100) * AGradient.Point1YPercent)),
  117. PointF(ARect.Left + Round(((ARect.Right - ARect.Left) / 100) *
  118. AGradient.Point2XPercent), ARect.Top + Round(
  119. ((ARect.Bottom - ARect.Top) / 100) * AGradient.Point2YPercent)),
  120. AGradient.ColorCorrection, AGradient.Sinus);
  121. end;
  122. procedure RenderBackgroundAndBorder(const ARect: TRect;
  123. ABackground: TBCBackground; ATargetBGRA: TBGRABitmap;
  124. ARounding: TBCRounding; ABorder: TBCBorder; AInnerMargin: single);
  125. var w: single;
  126. begin
  127. if ABorder.Style = bboNone then
  128. begin
  129. w := AInnerMargin-0.5;
  130. RenderBackgroundF(ARect.Left+w, ARect.Top+w, ARect.Right-1-w,
  131. ARect.Bottom-1-w,ABackground,ATargetBGRA,ARounding);
  132. end
  133. else
  134. begin
  135. w := (ABorder.Width-1)/2+AInnerMargin;
  136. RenderBackgroundF(ARect.Left+w,ARect.Top+w,ARect.Right-1-w,ARect.Bottom-1-w,ABackground,ATargetBGRA,ARounding);
  137. RenderBorderF(ARect.Left+w,ARect.Top+w,ARect.Right-1-w,ARect.Bottom-1-w,ABorder,ATargetBGRA,ARounding);
  138. end;
  139. end;
  140. procedure RenderBorder(const ARect: TRect; ABorder: TBCBorder;
  141. ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
  142. begin
  143. RenderBorderF(ARect.Left,ARect.Top,ARect.Right-1,ARect.Bottom-1,ABorder,
  144. ATargetBGRA,ARounding);
  145. end;
  146. procedure RenderBorderF(x1,y1,x2,y2: single; ABorder: TBCBorder;
  147. ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
  148. var
  149. fiLight: TFillBorderRoundRectInfo;
  150. rx,ry: Byte;
  151. ropt: TRoundRectangleOptions;
  152. begin
  153. if (x1>x2) or (y1>y2) then exit;
  154. if ABorder.Style=bboNone then Exit;
  155. if ARounding = nil then
  156. begin
  157. rx := 0;
  158. ry := 0;
  159. ropt := [];
  160. end else
  161. begin
  162. rx := ARounding.RoundX;
  163. ry := ARounding.RoundY;
  164. ropt := ARounding.RoundOptions;
  165. end;
  166. ATargetBGRA.RoundRectAntialias(x1,y1,x2,y2,
  167. rx, ry, ColorToBGRA(ColorToRGB(ABorder.Color),ABorder.ColorOpacity),
  168. ABorder.Width, ropt);
  169. if ABorder.LightWidth > 0 then
  170. begin
  171. //compute light position
  172. fiLight := TFillBorderRoundRectInfo.Create(
  173. x1,y1,x2,y2, rx,
  174. ry, ABorder.Width + ABorder.LightWidth, ropt);
  175. //check if there is an inner position
  176. if fiLight.InnerBorder <> nil then
  177. with fiLight.InnerBorder do //fill with light
  178. ATargetBGRA.RoundRectAntialias(topleft.x, topleft.y, bottomright.x,
  179. bottomright.y, radiusx, radiusY,
  180. ColorToBGRA(ColorToRGB(ABorder.LightColor), ABorder.LightOpacity),
  181. ABorder.LightWidth, ropt);
  182. fiLight.Free;
  183. end;
  184. end;
  185. procedure RenderText(const ARect: TRect; AFont: TBCFont;
  186. const AText: String; ATargetBGRA: TBGRABitmap; AEnabled: boolean);
  187. var
  188. shd: TBGRABitmap;
  189. hal: TAlignment;
  190. val: TTextLayout;
  191. st: TTextStyle;
  192. r: TRect;
  193. c: TColor;
  194. begin
  195. if AText = '' then exit;
  196. AssignBCFont(AFont,ATargetBGRA);
  197. hal := BCAlign2HAlign(AFont.TextAlignment);
  198. val := BCAlign2VAlign(AFont.TextAlignment);
  199. FillChar({%H-}st, SizeOf({%H-}st),0);
  200. st.Wordbreak := AFont.WordBreak;
  201. st.Alignment := hal;
  202. st.Layout := val;
  203. st.SingleLine := AFont.SingleLine;
  204. st.EndEllipsis := AFont.EndEllipsis;
  205. r := ARect;
  206. r.Left += AFont.PaddingLeft;
  207. r.Right -= AFont.PaddingRight;
  208. r.Top += AFont.PaddingTop;
  209. r.Bottom -= AFont.PaddingBottom;
  210. if AFont.Shadow then
  211. begin
  212. shd := TBGRABitmap.Create(ATargetBGRA.Width,ATargetBGRA.Height,BGRAPixelTransparent);
  213. shd.FontName := ATargetBGRA.FontName;
  214. shd.FontStyle := ATargetBGRA.FontStyle;
  215. shd.FontQuality := ATargetBGRA.FontQuality;
  216. shd.FontHeight := ATargetBGRA.FontHeight;
  217. shd.TextRect(r, r.Left, r.Top, AText, st, ColorToBGRA(ColorToRGB(AFont.ShadowColor),
  218. AFont.ShadowColorOpacity));
  219. BGRAReplace(shd, shd.FilterBlurRadial(AFont.ShadowRadius, rbFast));
  220. ATargetBGRA.BlendImage(AFont.ShadowOffsetX, AFont.ShadowOffsetY,
  221. shd, boLinearBlend);
  222. shd.Free;
  223. end;
  224. if AEnabled or (AFont.DisabledColor = clNone) then
  225. c := AFont.Color else c := AFont.DisabledColor;
  226. ATargetBGRA.TextRect(r,r.Left,r.Top,AText,st,c);
  227. end;
  228. function BCAlign2HAlign(AAlign: TBCAlignment): TAlignment;
  229. begin
  230. if AAlign in [bcaCenter, bcaCenterTop, bcaCenterBottom] then
  231. Result := taCenter
  232. else if AAlign in [bcaRightCenter, bcaRightTop, bcaRightBottom] then
  233. Result := taRightJustify
  234. else
  235. Result := taLeftJustify;
  236. end;
  237. function BCAlign2VAlign(AAlign: TBCAlignment): TTextLayout;
  238. begin
  239. if AAlign in [bcaCenter, bcaLeftCenter, bcaRightCenter] then
  240. Result := tlCenter
  241. else if AAlign in [bcaCenterBottom, bcaLeftBottom, bcaRightBottom] then
  242. Result := tlBottom
  243. else
  244. Result := tlTop;
  245. end;
  246. function ScaleRect(ARect: TRect; AScale: Single): TRect;
  247. begin
  248. with ARect do
  249. result := rect(round(Left*AScale), round(Top*AScale),
  250. round(Right*AScale), round(Bottom*AScale));
  251. end;
  252. procedure AssignBCFont(AFont: TBCFont; var ATargetBGRA: TBGRABitmap);
  253. var c: TBitmap;
  254. begin
  255. // Canvas is need for calculate font height
  256. c := TBitmap.Create;
  257. c.Canvas.Font.Name := AFont.Name;
  258. c.Canvas.Font.Style := AFont.Style;
  259. case AFont.FontQuality of
  260. fqSystem: c.Canvas.Font.Quality := fqNonAntialiased;
  261. fqFineAntialiasing: c.Canvas.Font.Quality := fqAntialiased;
  262. fqFineClearTypeRGB: c.Canvas.Font.Quality := fqProof;
  263. fqSystemClearType: c.Canvas.Font.Quality := fqCleartype;
  264. end;
  265. // FontAntialias is only backward compability for FontQuality property.
  266. // FontQuality is published in TBCFont so we don't need FontAntialias anymore.
  267. //ATargetBGRA.FontAntialias := AFont.FontAntialias;
  268. {%H-}ATargetBGRA.FontStyle := AFont.Style;
  269. // If font quality is system, then we can leave default values. LCL will
  270. // handle everything (when name is "default" or height 0)
  271. if AFont.FontQuality in [fqSystem,fqSystemClearType] then
  272. begin
  273. ATargetBGRA.FontName := AFont.Name;
  274. ATargetBGRA.FontHeight := AFont.Height;
  275. end
  276. else
  277. begin
  278. // Getting real font name
  279. if SameText(AFont.Name,'default')
  280. then ATargetBGRA.FontName := string(GetFontData(c.Canvas.Font.Handle).Name)
  281. else ATargetBGRA.FontName := AFont.Name;
  282. // Calculate default height, because when font quality is <> fqSystemXXX
  283. // then if height is 0 then it is 0 for real
  284. if (AFont.Height=0) then
  285. ATargetBGRA.FontHeight := -c.Canvas.TextHeight('Bgra')
  286. else
  287. ATargetBGRA.FontHeight := AFont.Height;
  288. end;
  289. ATargetBGRA.FontQuality := AFont.FontQuality;
  290. c.Free;
  291. end;
  292. procedure CalculateTextSize(const AText: String; AFont: TBCFont; out ANewWidth,
  293. ANewHeight: integer; AShadowMargin: boolean);
  294. var
  295. s: TSize;
  296. tmp: TBGRABitmap;
  297. begin
  298. if (AText = '') or (AFont = nil) then
  299. begin
  300. ANewWidth := 0;
  301. ANewHeight := 0;
  302. Exit;
  303. end;
  304. tmp := TBGRABitmap.Create(0,0);
  305. AssignBCFont(AFont, tmp);
  306. s := tmp.TextSize(AText);
  307. tmp.Free;
  308. { shadow offset }
  309. if AShadowMargin and AFont.Shadow then
  310. begin
  311. Inc(s.cx, 2 * Abs(AFont.ShadowOffsetX) + 2 * AFont.ShadowRadius);
  312. Inc(s.cy, 2 * Abs(AFont.ShadowOffsetY) + 2 * AFont.ShadowRadius);
  313. end;
  314. inc(s.cx, AFont.PaddingLeft+Afont.PaddingRight);
  315. inc(s.cy, AFont.PaddingTop+Afont.PaddingBottom);
  316. ANewWidth := s.cx;
  317. ANewHeight := s.cy;
  318. end;
  319. procedure CalculateTextSizeEx(const AText: String; AFont: TBCFont; out
  320. ANewWidth, ANewHeight: integer; AAvailableWidth: integer; AShadowMargin: boolean);
  321. var
  322. s: TSize;
  323. tmp: TBGRABitmap;
  324. extraX,extraY, fitCount: integer;
  325. dotSize: LongInt;
  326. begin
  327. if (AText = '') or (AFont = nil) then
  328. begin
  329. ANewWidth := 0;
  330. ANewHeight := 0;
  331. Exit;
  332. end;
  333. extraX := 0;
  334. extraY := 0;
  335. { shadow offset }
  336. if AShadowMargin and AFont.Shadow then
  337. begin
  338. Inc(extraX, 2 * Abs(AFont.ShadowOffsetX) + 2 * AFont.ShadowRadius);
  339. Inc(extraY, 2 * Abs(AFont.ShadowOffsetY) + 2 * AFont.ShadowRadius);
  340. end;
  341. inc(extraX, AFont.PaddingLeft+Afont.PaddingRight);
  342. inc(extraY, AFont.PaddingTop+Afont.PaddingBottom);
  343. dec(AAvailableWidth, extraX);
  344. tmp := TBGRABitmap.Create(0,0);
  345. AssignBCFont(AFont, tmp);
  346. if AFont.WordBreak then
  347. s := tmp.TextSize(AText, AAvailableWidth)
  348. else
  349. begin
  350. s := tmp.TextSize(AText);
  351. if AFont.EndEllipsis and (s.cx > AAvailableWidth) then
  352. begin
  353. dotSize := tmp.TextSize('...').cx;
  354. fitCount := tmp.TextFitInfo(AText, AAvailableWidth-dotSize);
  355. s.cx := tmp.TextSize(UTF8Copy(AText, 1, fitCount)).cx + dotSize;
  356. end;
  357. end;
  358. tmp.Free;
  359. ANewWidth := s.cx+extraX;
  360. ANewHeight := s.cy+extraY;
  361. end;
  362. procedure GetGlyphActualLayout(ACaption: string; AFont: TBCFont;
  363. AGlyphAlignment: TBCAlignment; AGlyphMargin: integer; out AHorizAlign: TAlignment;
  364. out AVertAlign: TTextLayout; out AGlyphRelativeHorizAlign: TAlignment;
  365. out AGlyphRelativeVertAlign: TTextLayout; out AGlyphHorizMargin: integer;
  366. out AGlyphVertMargin: integer);
  367. begin
  368. if AGlyphAlignment in [bcaLeftTop,bcaLeftCenter,bcaLeftBottom] then AHorizAlign := taLeftJustify
  369. else if AGlyphAlignment in [bcaRightTop,bcaRightCenter,bcaRightBottom] then AHorizAlign:= taRightJustify
  370. else AHorizAlign:= taCenter;
  371. if AGlyphAlignment in [bcaCenter,bcaLeftCenter,bcaRightCenter] then AVertAlign := tlCenter
  372. else if AGlyphAlignment in [bcaLeftBottom,bcaCenterBottom,bcaRightBottom] then AVertAlign := tlBottom
  373. else AVertAlign := tlTop;
  374. if ACaption<>'' then
  375. begin
  376. AGlyphRelativeVertAlign:= AVertAlign;
  377. if AVertAlign <> tlCenter then
  378. AGlyphRelativeHorizAlign:= AHorizAlign else
  379. begin
  380. if AHorizAlign = taCenter then
  381. begin
  382. if IsRightToLeftUTF8(ACaption) then AGlyphRelativeHorizAlign := taRightJustify
  383. else AGlyphRelativeHorizAlign := taLeftJustify;
  384. end else
  385. AGlyphRelativeHorizAlign:= AHorizAlign;
  386. end;
  387. if AFont.TextAlignment in [bcaLeftTop,bcaLeftCenter,bcaLeftBottom] then AHorizAlign := taLeftJustify
  388. else if AFont.TextAlignment in [bcaRightTop,bcaRightCenter,bcaRightBottom] then AHorizAlign:= taRightJustify
  389. else AHorizAlign := taCenter;
  390. if AFont.TextAlignment in [bcaLeftTop,bcaCenterTop,bcaRightTop] then AVertAlign := tlTop
  391. else if AFont.TextAlignment in [bcaLeftBottom,bcaCenterBottom,bcaRightBottom] then AVertAlign:= tlBottom
  392. else AVertAlign:= tlCenter;
  393. if AGlyphRelativeVertAlign in[tlTop,tlBottom] then
  394. begin
  395. if AGlyphRelativeHorizAlign <> taCenter then AGlyphHorizMargin:= AGlyphMargin
  396. else AGlyphHorizMargin:= 0;
  397. if AGlyphRelativeVertAlign = AVertAlign then AGlyphVertMargin:= AGlyphMargin
  398. else AGlyphVertMargin:= 0;
  399. end else
  400. begin
  401. AGlyphHorizMargin:= AGlyphMargin;
  402. AGlyphVertMargin:= 0;
  403. end;
  404. end else
  405. begin
  406. case AHorizAlign of
  407. taCenter: AGlyphRelativeHorizAlign:= taCenter;
  408. taRightJustify: AGlyphRelativeHorizAlign:= taLeftJustify;
  409. else AGlyphRelativeHorizAlign:= taRightJustify;
  410. end;
  411. if AHorizAlign <> taCenter then AGlyphHorizMargin := AGlyphMargin
  412. else AGlyphHorizMargin := 0;
  413. case AVertAlign of
  414. tlCenter: AGlyphRelativeVertAlign:= tlCenter;
  415. tlBottom: AGlyphRelativeVertAlign:= tlTop;
  416. else AGlyphRelativeVertAlign:= tlBottom;
  417. end;
  418. if AVertAlign <> tlCenter then AGlyphVertMargin := AGlyphMargin
  419. else AGlyphVertMargin := 0;
  420. end;
  421. end;
  422. function ComputeGlyphPosition(var rAvail: TRect;
  423. gw, gh: integer; AGlyphAlignment: TBCAlignment; AGlyphMargin: integer;
  424. ACaption: string; AFont: TBCFont; AOldPlacement: boolean): TRect;
  425. var
  426. w, h, w2,h2, glyphHorzMargin, glyphVertMargin: integer;
  427. horizAlign, relHorizAlign: TAlignment;
  428. vertAlign, relVertAlign: TTextLayout;
  429. rText, rAll, rGlyph: TRect;
  430. l,t: integer;
  431. procedure AlignRect(var ARect: TRect; const ABounds: TRect; AHorizAlign: TAlignment;
  432. AVertAlign: TTextLayout; AHorizMargin: integer = 0; AVertMargin: integer = 0);
  433. begin
  434. case AHorizAlign of
  435. taCenter: ARect.Offset((ABounds.Left+ABounds.Right - (ARect.Right-ARect.Left)) div 2,0);
  436. taRightJustify: ARect.Offset(ABounds.Right - AHorizMargin - (ARect.Right-ARect.Left),0);
  437. else ARect.Offset(ABounds.Left + AHorizMargin,0);
  438. end;
  439. case AVertAlign of
  440. tlCenter: ARect.Offset(0, (ABounds.Top+ABounds.Bottom - (ARect.Bottom-ARect.Top)) div 2);
  441. tlBottom: ARect.Offset(0, ABounds.Bottom - AVertMargin - (ARect.Bottom-ARect.Top));
  442. else ARect.Offset(0, ABounds.Top + AVertMargin);
  443. end;
  444. end;
  445. begin
  446. if (gw = 0) or (gh = 0) then exit(EmptyRect);
  447. if AOldPlacement then
  448. begin
  449. if ACaption = '' then
  450. begin
  451. w := 0;
  452. h := 0;
  453. end else
  454. CalculateTextSize(ACaption, AFont, w, h);
  455. l := rAvail.Right - Round(((rAvail.Right - rAvail.Left) + w + gw) / 2);
  456. t := rAvail.Bottom - Round(((rAvail.Bottom - rAvail.Top) + gh) / 2);
  457. result := rect(l,t,l+gw,t+gh);
  458. Inc(rAvail.Left, l + gw + AGlyphMargin);
  459. exit;
  460. end;
  461. GetGlyphActualLayout(ACaption, AFont, AGlyphAlignment, AGlyphMargin,
  462. horizAlign, vertAlign, relHorizAlign, relVertAlign, glyphHorzMargin, glyphVertMargin);
  463. if ACaption = '' then
  464. begin
  465. rGlyph := rect(0,0,gw,gh);
  466. AlignRect(rGlyph, rAvail, horizAlign, vertAlign, glyphHorzMargin, glyphVertMargin);
  467. exit(rGlyph);
  468. end else
  469. CalculateTextSizeEx(ACaption, AFont, w, h, rAvail.Right-rAvail.Left);
  470. if relVertAlign in[tlTop,tlBottom] then
  471. begin
  472. w2 := max(w,gw+glyphHorzMargin);
  473. h2 := h+gh+glyphVertMargin;
  474. end else
  475. begin
  476. w2 := w+gw+glyphHorzMargin;
  477. if (ACaption <> '') and (w2 > rAvail.Right-rAvail.Left) then
  478. begin
  479. CalculateTextSizeEx(ACaption, AFont, w, h, rAvail.Right-rAvail.Left - (gw+glyphHorzMargin));
  480. w2 := w+gw+glyphHorzMargin;
  481. end;
  482. h2 := max(h,gh+glyphVertMargin);
  483. end;
  484. rAll := rect(0,0,w2,h2);
  485. AlignRect(rAll, rAvail, horizAlign, vertAlign);
  486. rText := rect(0,0,w,h);
  487. rGlyph := rect(0,0,gw,gh);
  488. case relVertAlign of
  489. tlTop: begin
  490. AlignRect(rGlyph, rAll, relHorizAlign, tlTop,
  491. glyphHorzMargin, glyphVertMargin);
  492. AlignRect(rText, rAll, horizAlign, tlBottom);
  493. end;
  494. tlBottom: begin
  495. AlignRect(rGlyph, rAll, relHorizAlign, tlBottom,
  496. glyphHorzMargin, glyphVertMargin);
  497. AlignRect(rText, rAll, horizAlign, tlTop);
  498. end;
  499. else begin
  500. if relHorizAlign = taRightJustify then
  501. begin
  502. AlignRect(rGlyph, rAll, taRightJustify, tlCenter,
  503. glyphHorzMargin, glyphHorzMargin);
  504. AlignRect(rText, rAll, taLeftJustify, tlCenter);
  505. end else
  506. begin
  507. AlignRect(rGlyph, rAll, taLeftJustify, tlCenter,
  508. glyphHorzMargin, glyphHorzMargin);
  509. AlignRect(rText, rAll, taRightJustify, tlCenter);
  510. end;
  511. end;
  512. end;
  513. result := rGlyph;
  514. if AFont.WordBreak and (rText.Right < rAvail.Right) then inc(rText.Right); //word-break computation may be one pixel off
  515. rAvail := rText;
  516. end;
  517. procedure RenderArrow(ATargetBGRA: TBGRABitmap; const ARect: TRect;
  518. ASize: Integer; ADirection: TBCArrowDirection; AColor: TColor; AOpacity: Byte);
  519. var
  520. p: ArrayOfTPointF;
  521. n: byte;
  522. temp: TBGRABitmap;
  523. w: Integer;
  524. begin
  525. // We can't draw outside rect
  526. w := Min(ASize, ARect.Right - ARect.Left);
  527. { Poly }
  528. SetLength(p, 3);
  529. temp := TBGRABitmap.Create(w+1, w+1,BGRAPixelTransparent);
  530. case ADirection of
  531. badDown:
  532. begin;
  533. p[0].x := 0;
  534. p[0].y := 0;
  535. p[1].x := w;
  536. p[1].y := 0;
  537. p[2].x := Round(w/2);
  538. p[2].y := w;
  539. end;
  540. badUp:
  541. begin
  542. p[0].x := Round(w/2);
  543. p[0].y := 0;
  544. p[1].x := 0;
  545. p[1].y := w;
  546. p[2].x := w;
  547. p[2].y := w;
  548. end;
  549. badLeft:
  550. begin
  551. p[0].x := 0;
  552. p[0].y := Round(w/2);
  553. p[1].x := w;
  554. p[1].y := 0;
  555. p[2].x := w;
  556. p[2].y := w;
  557. end;
  558. badRight:
  559. begin
  560. p[0].x := w;
  561. p[0].y := Round(w/2);
  562. p[1].x := 0;
  563. p[1].y := 0;
  564. p[2].x := 0;
  565. p[2].y := w;
  566. end;
  567. end;
  568. // Fill n times to get best quality
  569. for n := 1 to 6 do
  570. temp.FillPolyAntialias(p, ColorToBGRA(ColorToRGB(AColor),AOpacity));
  571. ATargetBGRA.BlendImage(
  572. ARect.Right-Round( ((ARect.Right-ARect.Left)/2) + (w/2) ),
  573. ARect.Bottom-Round( ((ARect.Bottom-ARect.Top)/2) + (w/2) ),
  574. temp,
  575. boLinearBlend
  576. );
  577. temp.Free;
  578. end;
  579. procedure RenderBackgroundF(x1,y1,x2,y2: single; ABackground: TBCBackground;
  580. ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil);
  581. var
  582. backcolor: TBGRAPixel;
  583. multi: TBGRAMultishapeFiller;
  584. back: TBGRABitmap;
  585. grect1, grect2: TRect;
  586. gra: TBGRAGradientScanner;
  587. rx,ry: Byte;
  588. ropt: TRoundRectangleOptions;
  589. begin
  590. if (x1>=x2) or (y1>=y2) then exit;
  591. if ARounding = nil then
  592. begin
  593. rx := 0;
  594. ry := 0;
  595. ropt := [];
  596. end else
  597. begin
  598. rx := ARounding.RoundX;
  599. ry := ARounding.RoundY;
  600. ropt := ARounding.RoundOptions;
  601. end;
  602. { Background color }
  603. case ABackground.Style of
  604. bbsClear: backcolor := BGRAPixelTransparent;
  605. // TODO: Why if I use some system colors like clBtnFace, clActiveCaption etc.
  606. // without ColorToRGB, I always get Black? Interface: QT
  607. bbsColor: backcolor := ColorToBGRA(ColorToRGB(ABackground.Color), ABackground.ColorOpacity);
  608. end;
  609. case ABackground.Style of
  610. bbsClear, bbsColor:
  611. { Solid background color }
  612. ATargetBGRA.FillRoundRectAntialias(x1,y1,x2,y2, rx, ry, {%H-}backcolor, ropt);
  613. bbsGradient:
  614. begin
  615. { Using multishape filler to merge background gradient and border }
  616. multi := TBGRAMultishapeFiller.Create;
  617. multi.PolygonOrder := poFirstOnTop; { Border will replace background }
  618. { Gradients }
  619. back := TBGRABitmap.Create(ATargetBGRA.Width, ATargetBGRA.Height, BGRAPixelTransparent);
  620. grect1 := rect(floor(x1),floor(y1),ceil(x2)+1,ceil(y2)+1);
  621. grect2 := grect1;
  622. { Gradient 1 }
  623. if ABackground.Gradient1EndPercent > 0 then
  624. begin
  625. grect1.Bottom := grect1.top + Round(((grect1.Bottom-grect1.Top) / 100) * ABackground.Gradient1EndPercent);
  626. gra := CreateGradient(ABackground.Gradient1, grect1);
  627. back.FillRect(grect1.Left, grect1.Top, grect1.Right, grect1.Bottom,
  628. gra, dmSet
  629. );
  630. gra.Free;
  631. end;
  632. { Gradient 2 }
  633. if ABackground.Gradient1EndPercent < 100 then
  634. begin
  635. grect2.Top := grect1.Bottom;
  636. gra := CreateGradient(ABackground.Gradient2, grect2);
  637. back.FillRect(grect2.Left, grect2.Top, grect2.Right, grect2.Bottom,
  638. gra, dmSet
  639. );
  640. gra.Free;
  641. end;
  642. multi.AddRoundRectangle(x1,y1,x2,y2, rx, ry, back, ropt);
  643. multi.Draw(ATargetBGRA);
  644. multi.Free;
  645. back.Free;
  646. end;
  647. end;
  648. end;
  649. procedure RenderBackground(const ARect: TRect; ABackground: TBCBackground;
  650. ATargetBGRA: TBGRABitmap; ARounding: TBCRounding = nil; AHasNoBorder: boolean = false);
  651. var
  652. extraSize: single;
  653. begin
  654. if AHasNoBorder then extraSize := 0.5
  655. else extraSize := 0;
  656. RenderBackgroundF(ARect.Left-extraSize, ARect.Top-extraSize, ARect.Right-1+extraSize,
  657. ARect.Bottom-1+extraSize,ABackground,ATargetBGRA,ARounding);
  658. end;
  659. end.