bctools.pas 25 KB

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