source: trunk/Packages/bgrabitmap/bgratext.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 53.3 KB
Line 
1unit BGRAText;
2
3{$mode objfpc}{$H+}
4
5interface
6
7{$IFDEF LINUX}
8 {$DEFINE LCL_RENDERER_IS_FINE}
9 {$DEFINE LCL_CLEARTYPE_RENDERER_IS_FINE}
10 {$DEFINE RENDER_TEXT_ON_TBITMAP}
11{$ENDIF}
12{$IFDEF FREEBSD}
13 {$DEFINE LCL_RENDERER_IS_FINE}
14 {$DEFINE LCL_CLEARTYPE_RENDERER_IS_FINE}
15{$ENDIF}
16{$IFDEF DARWIN}
17 {$DEFINE LCL_RENDERER_IS_FINE}
18 {$DEFINE RENDER_TEXT_ON_TBITMAP}
19{$ENDIF}
20{$IFDEF WINDOWS}
21 {$IFNDEF LEGACY_FONT_VERTICAL_OFFSET}
22 {$DEFINE FIX_FONT_VERTICAL_OFFSET}
23 {$ENDIF}
24{$ENDIF}
25
26{
27 Font rendering units : BGRAText, BGRATextFX, BGRAVectorize, BGRAFreeType
28
29 This unit provides basic text rendering functions using LCL.
30
31 Text functions use a temporary bitmap where the operating system text drawing is used.
32 Then it is scaled down (if antialiasing is activated), and colored.
33
34 These routines are rather slow, so you may use other font renderers
35 like TBGRATextEffectFontRenderer in BGRATextFX if you want to use LCL fonts,
36 or, if you have TrueType fonts files, you may use TBGRAFreeTypeFontRenderer
37 in BGRAFreeType. }
38
39uses
40 Classes, Types, SysUtils, BGRAGraphics, BGRABitmapTypes, InterfaceBase, BGRAPen, BGRAGrayscaleMask,
41 LCLVersion;
42
43type
44 TWordBreakHandler = BGRABitmapTypes.TWordBreakHandler;
45
46 { TCustomLCLFontRenderer }
47
48 TCustomLCLFontRenderer = class(TBGRACustomFontRenderer)
49 protected
50 FFont: TFont; //font parameters
51 FWordBreakHandler: TWordBreakHandler;
52 procedure UpdateFont; virtual;
53 function InternalTextSize(sUTF8: string; AShowPrefix: boolean): TSize;
54 procedure InternalTextWordBreak(ADest: TBGRACustomBitmap; ATextUTF8: string;
55 x, y, AMaxWidth: integer; AColor: TBGRAPixel; ATexture: IBGRAScanner;
56 AHorizAlign: TAlignment; AVertAlign: TTextLayout; ARightToLeft: boolean);
57 procedure InternalTextRect(ADest: TBGRACustomBitmap; ARect: TRect; x, y: integer; sUTF8: string; style: TTextStyle; c: TBGRAPixel; ATexture: IBGRAScanner);
58 procedure InternalTextOut(ADest: TBGRACustomBitmap; x, y: single; sUTF8: string; c: TBGRAPixel; texture: IBGRAScanner;
59 align: TAlignment; AShowPrefix: boolean = false; ARightToLeft: boolean = false);
60 procedure InternalTextOutEllipse(ADest: TBGRACustomBitmap; x, y, availableWidth: single; sUTF8: string; c: TBGRAPixel; texture: IBGRAScanner;
61 align: TAlignment; AShowPrefix: boolean = false; ARightToLeft: boolean = false);
62 procedure InternalSplitText(var ATextUTF8: string; AMaxWidth: integer; out ARemainsUTF8: string; out ALineEndingBreak: boolean;
63 AWordBreak: TWordBreakHandler); overload;
64 procedure InternalSplitText(var ATextUTF8: string; AMaxWidth: integer; out ARemainsUTF8: string;
65 AWordBreak: TWordBreakHandler); overload;
66 procedure DefaultWorkBreakHandler(var ABeforeUTF8, AAfterUTF8: string);
67 public
68 procedure SplitText(var ATextUTF8: string; AMaxWidth: integer; out ARemainsUTF8: string);
69 function GetFontPixelMetric: TFontPixelMetric; override;
70 procedure TextOutAngle(ADest: TBGRACustomBitmap; x, y: single; orientationTenthDegCCW: integer; sUTF8: string; c: TBGRAPixel; align: TAlignment); overload; override;
71 procedure TextOutAngle(ADest: TBGRACustomBitmap; x, y: single; orientationTenthDegCCW: integer; sUTF8: string; texture: IBGRAScanner; align: TAlignment); overload; override;
72 procedure TextOut(ADest: TBGRACustomBitmap; x, y: single; sUTF8: string; texture: IBGRAScanner; align: TAlignment); overload; override;
73 procedure TextOut(ADest: TBGRACustomBitmap; x, y: single; sUTF8: string; c: TBGRAPixel; align: TAlignment); overload; override;
74 procedure TextOut(ADest: TBGRACustomBitmap; x, y: single; sUTF8: string; texture: IBGRAScanner; align: TAlignment; ARightToLeft: boolean); overload; override;
75 procedure TextOut(ADest: TBGRACustomBitmap; x, y: single; sUTF8: string; c: TBGRAPixel; align: TAlignment; ARightToLeft: boolean); overload; override;
76 procedure TextRect(ADest: TBGRACustomBitmap; ARect: TRect; x, y: integer; sUTF8: string; style: TTextStyle; c: TBGRAPixel); overload; override;
77 procedure TextRect(ADest: TBGRACustomBitmap; ARect: TRect; x, y: integer; sUTF8: string; style: TTextStyle; texture: IBGRAScanner); overload; override;
78 procedure TextWordBreak(ADest: TBGRACustomBitmap; AText: string; x, y, AMaxWidth: integer; AColor: TBGRAPixel; AHorizAlign: TAlignment; AVertAlign: TTextLayout; ARightToLeft: boolean = false); overload;
79 procedure TextWordBreak(ADest: TBGRACustomBitmap; AText: string; x, y, AMaxWidth: integer; ATexture: IBGRAScanner; AHorizAlign: TAlignment; AVertAlign: TTextLayout; ARightToLeft: boolean = false); overload;
80 function TextSize(sUTF8: string): TSize; override;
81 function TextSizeAngle(sUTF8: string; orientationTenthDegCCW: integer): TSize; override;
82 function TextSize(sUTF8: string; AMaxWidth: integer; {%H-}ARightToLeft: boolean): TSize; override;
83 function TextFitInfo(sUTF8: string; AMaxWidth: integer): integer; override;
84 constructor Create;
85 destructor Destroy; override;
86 property OnWordBreak: TWordBreakHandler read FWordBreakHandler write FWordBreakHandler;
87 end;
88
89 { TLCLFontRenderer }
90
91 TLCLFontRenderer = class(TCustomLCLFontRenderer)
92
93 end;
94
95function CleanTextOutString(s: string): string; //this works with UTF8 strings as well
96function RemoveLineEnding(var s: string; indexByte: integer): boolean; //this works with UTF8 strings however the index is the byte index
97function RemoveLineEndingUTF8(var sUTF8: string; indexUTF8: integer): boolean;
98
99procedure BGRATextOut(bmp: TBGRACustomBitmap; Font: TFont; Quality: TBGRAFontQuality; xf, yf: single; sUTF8: string;
100 c: TBGRAPixel; tex: IBGRAScanner; align: TAlignment; CustomAntialiasingLevel: Integer = 0;
101 ShowPrefix: boolean = false; RightToLeft: boolean = false);
102
103procedure BGRATextOutAngle(bmp: TBGRACustomBitmap; Font: TFont; Quality: TBGRAFontQuality; xf, yf: single; orientationTenthDegCCW: integer;
104 sUTF8: string; c: TBGRAPixel; tex: IBGRAScanner; align: TAlignment; CustomAntialiasingLevel: Integer = 0);
105
106procedure BGRATextRect(bmp: TBGRACustomBitmap; Font: TFont; Quality: TBGRAFontQuality; ARect: TRect; xf, yf: single;
107 sUTF8: string; style: TTextStyle; c: TBGRAPixel; tex: IBGRAScanner; CustomAntialiasingLevel: Integer = 0);
108
109function BGRATextSize(Font: TFont; Quality: TBGRAFontQuality; sUTF8: string; CustomAntialiasingLevel: Integer): TSize;
110function BGRATextFitInfo(Font: TFont; Quality: TBGRAFontQuality; sUTF8: string; CustomAntialiasingLevel: Integer; AMaxWidth: integer): integer;
111function BGRAOriginalTextSize(Font: TFont; Quality: TBGRAFontQuality; sUTF8: string; CustomAntialiasingLevel: integer): TSize;
112function BGRAOriginalTextSizeEx(Font: TFont; Quality: TBGRAFontQuality; sUTF8: string; CustomAntialiasingLevel: Integer;
113 out actualAntialiasingLevel: integer; out extraVerticalMarginDueToRotation: integer): TSize;
114
115function BGRATextUnderline(ATopLeft: TPointF; AWidth: Single; AMetrics: TFontPixelMetric): ArrayOfTPointF; overload;
116function BGRATextUnderline(ATopLeft: TPointF; AWidth: Single; ABaseline, AEmHeight: single): ArrayOfTPointF; overload;
117function BGRATextStrikeOut(ATopLeft: TPointF; AWidth: Single; AMetrics: TFontPixelMetric): ArrayOfTPointF; overload;
118function BGRATextStrikeOut(ATopLeft: TPointF; AWidth: Single; ABaseline, AEmHeight, AXHeight: single): ArrayOfTPointF; overload;
119
120function GetFontHeightSign: integer;
121function FontEmHeightSign: integer;
122function FontFullHeightSign: integer;
123function LCLFontAvailable: boolean;
124function GetFineClearTypeAuto: TBGRAFontQuality;
125function FixLCLFontFullHeight({%H-}AFontName: string; AFontHeight: integer): integer;
126
127procedure BGRAFillClearTypeGrayscaleMask(dest: TBGRACustomBitmap; x,y: integer; xThird: integer; mask: TGrayscaleMask; color: TBGRAPixel; texture: IBGRAScanner = nil; RGBOrder: boolean=true);
128procedure BGRAFillClearTypeMask(dest: TBGRACustomBitmap; x,y: integer; xThird: integer; mask: TBGRACustomBitmap; color: TBGRAPixel; texture: IBGRAScanner = nil; RGBOrder: boolean=true);
129procedure BGRAFillClearTypeRGBMask(dest: TBGRACustomBitmap; x,y: integer; mask: TBGRACustomBitmap; color: TBGRAPixel; texture: IBGRAScanner = nil; KeepRGBOrder: boolean=true);
130
131const FontAntialiasingLevel = {$IFDEF LCL_RENDERER_IS_FINE}3{$ELSE}6{$ENDIF};
132const FontDefaultQuality = fqAntialiased;
133
134function GetLCLFontPixelMetric(AFont: TFont): TFontPixelMetric;
135
136var
137 BGRATextOutImproveReadabilityProc : procedure (bmp: TBGRACustomBitmap; AFont: TFont; xf,yf: single; text: string; color: TBGRAPixel; tex: IBGRAScanner; align: TAlignment; mode : TBGRATextOutImproveReadabilityMode);
138
139implementation
140
141uses GraphType, Math, BGRABlend, BGRAUTF8, BGRAUnicode, BGRATextBidi
142 {$IF lcl_fullversion >= 1070000}, lclplatformdef{$ENDIF};
143
144const MaxPixelMetricCount = 100;
145
146var
147 LCLFontDisabledValue: boolean;
148 TempBmp: TBitmap;
149 fqFineClearTypeComputed: boolean;
150 fqFineClearTypeValue: TBGRAFontQuality;
151 FontHeightSignComputed: boolean;
152 FontHeightSignValue: integer;
153 FontPixelMetricArray: array[0..MaxPixelMetricCount-1] of record
154 usage: integer;
155 name: string;
156 height: integer;
157 italic: boolean;
158 bold: boolean;
159 metric: TFontPixelMetric;
160 end;
161 FontPixelMetricCount: integer;
162
163procedure ComputeFontVerticalBounds(text: string; font: TFont; out top, bottom, totalHeight: integer);
164var
165 xb,yb: integer;
166 pmask: PBGRAPixel;
167 nbPix: array of integer;
168 nbCur: integer;
169 mean: integer;
170 mask: TBGRACustomBitmap;
171 size: TSize;
172begin
173 if not LCLFontAvailable then
174 begin
175 top := 0;
176 bottom := 0;
177 totalHeight := 0;
178 exit;
179 end;
180 size := BGRAOriginalTextSize(font,fqSystem,text,FontAntialiasingLevel);
181 mask := BGRABitmapFactory.Create(size.cx,size.cy,BGRABlack);
182 mask.Canvas.Font := font;
183 mask.Canvas.Font.Quality := fqAntialiased;
184 mask.Canvas.Font.Color := clWhite;
185 mask.Canvas.Font.Style := font.style * [fsBold,fsItalic];
186 mask.Canvas.Brush.Style := bsClear;
187 mask.Canvas.TextOut(0,0,text);
188 top := -1;
189 bottom := -1;
190 totalHeight:= mask.Height;
191
192 mean := 0;
193 setlength(nbPix, mask.Height);
194 for yb := 0 to mask.Height-1 do
195 begin
196 pmask := mask.scanline[yb];
197 nbCur := 0;
198 for xb := 0 to mask.Width-1 do
199 begin
200 if (pmask^.green > 0) then inc(nbCur);
201 inc(pmask);
202 end;
203 nbPix[yb] := nbCur;
204 inc(mean,nbCur);
205 end;
206 mean := (mean+ (mask.Height div 2)) div mask.Height;
207
208 for yb := 0 to high(nbPix) do
209 begin
210 if nbPix[yb]> mean div 3 then
211 begin
212 if top = -1 then top := yb
213 else bottom := yb+1;
214 end;
215 end;
216 mask.Free;
217end;
218
219function ComputeFontPixelMetric(AFont: TFont): TFontPixelMetric;
220begin
221 ComputeFontVerticalBounds('acemu',AFont,result.xLine,result.Baseline,result.Lineheight);
222 ComputeFontVerticalBounds('gDjSO',AFont,result.CapLine,result.DescentLine,result.Lineheight);
223 if result.xLine = -1 then result.xLine := result.CapLine else
224 if result.CapLine = -1 then result.CapLine := result.xLine;
225 if result.DescentLine = -1 then result.DescentLine := result.Baseline else
226 if result.Baseline = -1 then result.Baseline := result.DescentLine;
227 result.Defined := (result.xLine <> -1) and (result.CapLine <> -1) and (result.Baseline <> -1) and (result.DescentLine <> -1) and
228 (result.Lineheight <> -1);
229end;
230
231function ComparePixelMetric(index: integer; font: TFont): integer;
232begin
233 if (index < 0) or (index >= FontPixelMetricCount) then
234 result := 0
235 else
236 begin
237 with FontPixelMetricArray[index] do
238 if (name = font.Name) and (height = font.Height) then
239 result := 0 else
240 if (height > font.Height) then
241 result := 1 else
242 if (height < font.Height) then
243 result := -1 else
244 if name > font.Name then
245 result := 1 else
246 if name < font.Name then
247 result := -1
248 else result := 0;
249 end;
250end;
251
252procedure FindPixelMetricPos(AFont: TFont; out startPos,endPos: integer);
253var middle,iStart,iEnd: integer;
254begin
255 if FontPixelMetricCount = 0 then
256 begin
257 startPos := 0;
258 endPos := 0;
259 end;
260 iStart:= 0;
261 iEnd:= FontPixelMetricCount;
262 while iStart < iEnd do
263 begin
264 middle := (iStart+iEnd) div 2;
265 if ComparePixelMetric(middle,AFont) >= 0 then
266 iEnd := middle
267 else
268 iStart := middle+1;
269 end;
270 startPos := iStart;
271
272 iStart:= startPos;
273 iEnd:= FontPixelMetricCount;
274 while iStart < iEnd do
275 begin
276 middle := (iStart+iEnd) div 2;
277 if ComparePixelMetric(middle,AFont) <= 0 then
278 iStart := middle+1
279 else
280 iEnd := middle;
281 end;
282 endPos := iEnd;
283end;
284
285procedure RemoveOldPixelMetric;
286var sum,nb,i: integer;
287begin
288 if FontPixelMetricCount = 0 then exit;
289 sum := 0;
290 for i := 0 to FontPixelMetricCount-1 do
291 sum += FontPixelMetricArray[i].usage;
292 sum := sum div FontPixelMetricCount;
293 nb := 0;
294 for i := 0 to FontPixelMetricCount-1 do
295 begin
296 if FontPixelMetricArray[i].usage > sum then
297 begin
298 FontPixelMetricArray[nb] := FontPixelMetricArray[i];
299 inc(nb);
300 end;
301 end;
302 FontPixelMetricCount := nb;
303end;
304
305function GetLCLFontPixelMetric(AFont: TFont): TFontPixelMetric;
306var i,startPos,endPos: integer;
307 prevHeight,fixHeight: integer;
308begin
309 if (AFont.Height < -200) or (AFont.Height > 150) then
310 begin
311 prevHeight := AFont.Height;
312 if AFont.Height < 0 then
313 fixHeight := -200
314 else
315 fixHeight := 150;
316 AFont.Height := fixHeight;
317 result := GetLCLFontPixelMetric(AFont);
318 AFont.Height := prevHeight;
319
320 result.Baseline := round(result.Baseline/fixHeight*prevHeight);
321 result.CapLine := round(result.CapLine/fixHeight*prevHeight);
322 result.DescentLine := round(result.DescentLine/fixHeight*prevHeight);
323 result.Lineheight := round(result.Lineheight/fixHeight*prevHeight);
324 result.xLine := round(result.xLine/fixHeight*prevHeight);
325 exit;
326 end;
327
328 FindPixelMetricPos(AFont,startPos,endPos);
329 for i := startPos to endPos-1 do
330 if (FontPixelMetricArray[i].bold = AFont.bold) and
331 (FontPixelMetricArray[i].italic = AFont.Italic) then
332 begin
333 result := FontPixelMetricArray[i].metric;
334 inc(FontPixelMetricArray[i].usage);
335 exit;
336 end;
337 if FontPixelMetricCount = MaxPixelMetricCount then RemoveOldPixelMetric;
338 for i := FontPixelMetricCount downto endPos+1 do
339 FontPixelMetricArray[i] := FontPixelMetricArray[i-1];
340 inc(FontPixelMetricCount);
341 with FontPixelMetricArray[endPos]do
342 begin
343 italic := AFont.Italic;
344 bold := AFont.Bold;
345 usage := 1;
346 name := AFont.Name;
347 height:= AFont.Height;
348 metric := ComputeFontPixelMetric(AFont);
349 result := metric;
350 end;
351end;
352
353const DefaultFontHeightSign = -1;
354
355function BGRATextUnderline(ATopLeft: TPointF;
356 AWidth: Single; AMetrics: TFontPixelMetric): ArrayOfTPointF;
357begin
358 result := BGRATextUnderline(ATopLeft, AWidth, AMetrics.Baseline,AMetrics.Baseline-AMetrics.CapLine);
359end;
360
361function BGRATextUnderline(ATopLeft: TPointF;
362 AWidth: Single; ABaseline, AEmHeight: single): ArrayOfTPointF;
363var height,y: single;
364begin
365 height := AEmHeight*0.1;
366 y := ATopLeft.y+ABaseline+1.5*height;
367 result := ComputeWidePolylinePoints([PointF(ATopLeft.x,y),
368 PointF(ATopLeft.x+AWidth,y)],height,BGRABlack,pecFlat,pjsMiter,
369 SolidPenStyle, []);
370end;
371
372function BGRATextStrikeOut(ATopLeft: TPointF; AWidth: Single;
373 AMetrics: TFontPixelMetric): ArrayOfTPointF;
374begin
375 result := BGRATextStrikeOut(ATopLeft, AWidth, AMetrics.Baseline,AMetrics.Baseline-AMetrics.CapLine,AMetrics.Baseline-AMetrics.xLine);
376end;
377
378function BGRATextStrikeOut(ATopLeft: TPointF; AWidth: Single; ABaseline,
379 AEmHeight, AXHeight: single): ArrayOfTPointF;
380var height,y: single;
381begin
382 height := AEmHeight*0.075;
383 y := ATopLeft.y+ABaseline-AXHeight*0.5;
384 result := ComputeWidePolylinePoints([PointF(ATopLeft.x,y),
385 PointF(ATopLeft.x+AWidth,y)],height,BGRABlack,pecFlat,pjsMiter,
386 SolidPenStyle, []);
387end;
388
389function GetFontHeightSign: integer;
390var
391 HeightP1, HeightM1: integer;
392begin
393 if LCLFontDisabledValue then
394 begin
395 result := DefaultFontHeightSign;
396 exit;
397 end;
398
399 if FontHeightSignComputed then
400 begin
401 result := FontHeightSignValue;
402 exit;
403 end;
404
405 if WidgetSet.LCLPlatform = lpNoGUI then
406 begin
407 LCLFontDisabledValue:= True;
408 result := -1;
409 exit;
410 end;
411
412 try
413 if tempBmp = nil then tempBmp := TBitmap.Create;
414 tempBmp.Canvas.Font.Name := 'Arial';
415 tempBmp.Canvas.Font.Style := [];
416 tempBmp.Canvas.Font.Height := 20;
417 HeightP1 := tempBmp.Canvas.TextExtent('Hg').cy;
418 tempBmp.Canvas.Font.Height := -20;
419 HeightM1 := tempBmp.Canvas.TextExtent('Hg').cy;
420
421 if HeightP1 > HeightM1 then
422 FontHeightSignValue := 1
423 else
424 FontHeightSignValue := -1;
425 except
426 on ex: Exception do
427 begin
428 LCLFontDisabledValue := True;
429 result := -1;
430 exit;
431 end;
432 end;
433 FontHeightSignComputed := true;
434 result := FontHeightSignValue;
435end;
436
437function GetFineClearTypeAuto: TBGRAFontQuality;
438var
439 lclBmp: TBitmap;
440 bgra: TBGRACustomBitmap;
441 x,y: integer;
442begin
443 if fqFineClearTypeComputed then
444 begin
445 result:= fqFineClearTypeValue;
446 exit;
447 end;
448 result := fqFineAntialiasing;
449 if not LCLFontDisabledValue and not (WidgetSet.LCLPlatform = lpNoGUI) then
450 begin
451 lclBmp := TBitmap.Create;
452 lclBmp.Canvas.FillRect(0,0,lclBmp.Width,lclBmp.Height);
453 lclBmp.Canvas.Font.Height := -50;
454 lclBmp.Canvas.Font.Quality := fqCleartype;
455 with lclBmp.Canvas.TextExtent('/') do
456 begin
457 lclBmp.Width := cx;
458 lclBmp.Height := cy;
459 end;
460 lclBmp.Canvas.TextOut(0,0,'/');
461 bgra:= BGRABitmapFactory.Create(lclBmp);
462 x:= bgra.Width div 2;
463 for y := 0 to bgra.Height-1 do
464 with bgra.GetPixel(x,y) do
465 if (red<>blue) then
466 begin
467 if blue < red then
468 result:= fqFineClearTypeRGB
469 else
470 result:= fqFineClearTypeBGR;
471 break;
472 end else
473 if (green = 0) then break;
474 bgra.Free;
475 lclBmp.Free;
476 end;
477 fqFineClearTypeValue := result;
478 fqFineClearTypeComputed:= true;
479end;
480
481{$IFNDEF WINDOWS}
482var LCLFontFullHeightRatio : array of record
483 FontName: string;
484 Ratio: single;
485 end;
486{$ENDIF}
487
488function FixLCLFontFullHeight(AFontName: string; AFontHeight: integer): integer;
489{$IFNDEF WINDOWS}
490const TestHeight = 200;
491var
492 i: Integer;
493 ratio : single;
494 f: TFont;
495 h: LongInt;
496begin
497 if (AFontHeight = 0) or
498 (AFontHeight*FontEmHeightSign > 0) then
499 result := AFontHeight
500 else
501 begin
502 ratio := EmptySingle;
503 for i := 0 to high(LCLFontFullHeightRatio) do
504 if CompareText(AFontName, LCLFontFullHeightRatio[i].FontName)=0 then
505 begin
506 ratio := LCLFontFullHeightRatio[i].Ratio;
507 break;
508 end;
509 if ratio = EmptySingle then
510 begin
511 f := TFont.Create;
512 f.Quality := fqDefault;
513 f.Name := AFontName;
514 f.Height := FontFullHeightSign*TestHeight;
515 h := BGRATextSize(f, fqSystem, 'Hg', 1).cy;
516 if h = 0 then ratio := 1
517 else ratio := TestHeight/h;
518
519 setlength(LCLFontFullHeightRatio, length(LCLFontFullHeightRatio)+1);
520 LCLFontFullHeightRatio[high(LCLFontFullHeightRatio)].FontName:= AFontName;
521 LCLFontFullHeightRatio[high(LCLFontFullHeightRatio)].Ratio:= ratio;
522 end;
523 result := round(AFontHeight*ratio);
524 end;
525end;
526{$ELSE}
527begin
528 result := AFontHeight;
529end;
530{$ENDIF}
531
532function FontEmHeightSign: integer;
533begin
534 result := GetFontHeightSign;
535end;
536
537function FontFullHeightSign: integer;
538begin
539 result := -FontEmHeightSign;
540end;
541
542function LCLFontAvailable: boolean;
543begin
544 if not FontHeightSignComputed then GetFontHeightSign;
545 result := not LCLFontDisabledValue;
546end;
547
548procedure BGRAFillClearTypeGrayscaleMask(dest: TBGRACustomBitmap; x,
549 y: integer; xThird: integer; mask: TGrayscaleMask; color: TBGRAPixel;
550 texture: IBGRAScanner; RGBOrder: boolean);
551begin
552 BGRAGrayscaleMask.BGRAFillClearTypeGrayscaleMask(dest,x,y,xThird,mask,color,texture,RGBOrder);
553end;
554
555procedure BGRAFillClearTypeMask(dest: TBGRACustomBitmap; x,y: integer; xThird: integer; mask: TBGRACustomBitmap; color: TBGRAPixel; texture: IBGRAScanner; RGBOrder: boolean);
556begin
557 BGRABlend.BGRAFillClearTypeMask(dest,x,y,xThird,mask,color,texture,RGBOrder);
558end;
559
560procedure BGRAFillClearTypeRGBMask(dest: TBGRACustomBitmap; x, y: integer;
561 mask: TBGRACustomBitmap; color: TBGRAPixel; texture: IBGRAScanner;
562 KeepRGBOrder: boolean);
563begin
564 BGRABlend.BGRAFillClearTypeRGBMask(dest,x,y,mask,color,texture,KeepRGBOrder);
565end;
566
567function BGRAOriginalTextSizeEx(Font: TFont; Quality: TBGRAFontQuality; sUTF8: string; CustomAntialiasingLevel: Integer;
568 out actualAntialiasingLevel: integer; out extraVerticalMarginDueToRotation: integer): TSize;
569begin
570 actualAntialiasingLevel:= CustomAntialiasingLevel;
571 extraVerticalMarginDueToRotation := 0;
572 if not LCLFontAvailable then
573 result := Size(0,0)
574 else
575 begin
576 try
577 if tempBmp = nil then tempBmp := TBitmap.Create;
578 tempBmp.Canvas.Font := Font;
579 if Quality in[fqFineClearTypeBGR,fqFineClearTypeRGB,fqFineAntialiasing] then
580 begin
581 tempBmp.Canvas.Font.Height := Font.Height*CustomAntialiasingLevel;
582 end else
583 begin
584 tempBmp.Canvas.Font.Height := Font.Height;
585 actualAntialiasingLevel:= 1;
586 end;
587 Result.cx := 0;
588 Result.cy := 0;
589 tempBmp.Canvas.Font.GetTextSize(sUTF8, Result.cx, Result.cy);
590 if Font.Orientation <> 0 then
591 begin
592 tempBmp.Canvas.Font.Orientation:= 0;
593 extraVerticalMarginDueToRotation := result.cy - tempBmp.Canvas.Font.GetTextHeight(sUTF8);
594 end;
595 except
596 on ex: exception do
597 begin
598 result := Size(0,0);
599 LCLFontDisabledValue := True;
600 end;
601 end;
602
603 end;
604end;
605
606function BGRATextFitInfo(Font: TFont; Quality: TBGRAFontQuality; sUTF8: string;
607 CustomAntialiasingLevel: Integer; AMaxWidth: integer): integer;
608var
609 actualAntialiasingLevel: Integer;
610begin
611 if AMaxWidth = 0 then exit(0);
612 actualAntialiasingLevel:= CustomAntialiasingLevel;
613 if not LCLFontAvailable then
614 result := 0
615 else
616 begin
617 try
618 if tempBmp = nil then tempBmp := TBitmap.Create;
619 tempBmp.Canvas.Font := Font;
620 if Quality in[fqFineClearTypeBGR,fqFineClearTypeRGB,fqFineAntialiasing] then
621 begin
622 tempBmp.Canvas.Font.Height := Font.Height*CustomAntialiasingLevel;
623 end else
624 begin
625 tempBmp.Canvas.Font.Height := Font.Height;
626 actualAntialiasingLevel:= 1;
627 end;
628 result := tempBmp.Canvas.TextFitInfo(sUTF8, AMaxWidth*actualAntialiasingLevel);
629 except
630 on ex: exception do
631 begin
632 result := 0;
633 LCLFontDisabledValue := True;
634 end;
635 end;
636
637 end;
638end;
639
640function BGRAOriginalTextSize(Font: TFont; Quality: TBGRAFontQuality; sUTF8: string; CustomAntialiasingLevel: Integer): TSize;
641var actualAntialiasingLevel, extraMargin: integer;
642begin
643 result := BGRAOriginalTextSizeEx(Font, Quality, sUTF8, CustomAntialiasingLevel, actualAntialiasingLevel, extraMargin);
644 {$IFDEF FIX_FONT_VERTICAL_OFFSET}
645 if extraMargin > 0 then result.cy -= extraMargin;
646 {$ENDIF}
647end;
648
649function BGRATextSize(Font: TFont; Quality: TBGRAFontQuality; sUTF8: string; CustomAntialiasingLevel: Integer): TSize;
650begin
651 result := BGRAOriginalTextSize(Font, Quality, sUTF8, CustomAntialiasingLevel);
652 if Quality in[fqFineClearTypeBGR,fqFineClearTypeRGB,fqFineAntialiasing] then
653 begin
654 result.cx := ceil(Result.cx/CustomAntialiasingLevel);
655 result.cy := ceil(Result.cy/CustomAntialiasingLevel);
656 end;
657end;
658
659function RemovePrefix(sUTF8: string): string;
660var i,resLen: integer;
661begin
662 setlength(result, length(sUTF8));
663 resLen := 0;
664 i := 1;
665 while i <= length(sUTF8) do
666 begin
667 if sUTF8[i] = '&' then
668 begin // double ('&&') indicate single char '&'
669 if (i < length(sUTF8)) and (sUTF8[i+1] = '&') then
670 begin
671 inc(resLen);
672 result[resLen] := '&';
673 inc(i,2);
674 end else
675 // single indicate underline
676 inc(i);
677 end else
678 begin
679 inc(resLen);
680 result[resLen] := sUTF8[i];
681 inc(i);
682 end;
683 end;
684 setlength(result,resLen);
685end;
686
687procedure FilterOriginalText(Quality: TBGRAFontQuality; CustomAntialiasingLevel: Integer; var temp: TBGRACustomBitmap;
688 out grayscaleMask: TGrayscaleMask);
689var
690 n: integer;
691 maxAlpha: NativeUint;
692 pb: PByte;
693 multiplyX: integer;
694 resampled: TBGRACustomBitmap;
695begin
696 grayscaleMask := nil;
697 case Quality of
698 fqFineClearTypeBGR,fqFineClearTypeRGB,fqFineAntialiasing:
699 begin
700 if Quality in [fqFineClearTypeBGR,fqFineClearTypeRGB] then multiplyX:= 3 else multiplyX:= 1;
701 if (temp.Height < CustomAntialiasingLevel*8) and (temp.Height >= CustomAntialiasingLevel*3) then
702 begin
703 temp.ResampleFilter := rfSpline;
704 resampled := temp.Resample(round(temp.width/CustomAntialiasingLevel*multiplyX),round(temp.Height/CustomAntialiasingLevel),rmFineResample);
705 grayscaleMask := TGrayscaleMask.Create(resampled,cGreen);
706 FreeAndNil(resampled);
707 end else
708 grayscaleMask := TGrayscaleMask.CreateDownSample(temp, round(temp.width/CustomAntialiasingLevel*multiplyX),round(temp.Height/CustomAntialiasingLevel));
709 FreeAndNil(temp);
710
711 maxAlpha := 0;
712 pb := grayscaleMask.Data;
713 for n := grayscaleMask.NbPixels - 1 downto 0 do
714 begin
715 if Pb^ > maxAlpha then maxAlpha := Pb^;
716 Inc(pb);
717 end;
718 if (maxAlpha <> 0) and (maxAlpha <> 255) then
719 begin
720 pb := grayscaleMask.Data;
721 for n := grayscaleMask.NbPixels - 1 downto 0 do
722 begin
723 pb^:= pb^ * 255 div maxAlpha;
724 Inc(pb);
725 end;
726 end;
727 end;
728 fqSystem:
729 begin
730 grayscaleMask := TGrayscaleMask.Create(temp, cGreen);
731 FreeAndNil(temp);
732 {$IFNDEF LINUX}
733 pb := grayscaleMask.Data;
734 for n := grayscaleMask.NbPixels - 1 downto 0 do
735 begin
736 pb^:= GammaExpansionTab[pb^] shr 8;
737 Inc(pb);
738 end;
739 {$ENDIF}
740 end;
741 end;
742end;
743
744function CleanTextOutString(s: string): string;
745begin
746 result := BGRABitmapTypes.CleanTextOutString(s);
747end;
748
749function RemoveLineEnding(var s: string; indexByte: integer): boolean;
750begin
751 result := BGRABitmapTypes.RemoveLineEnding(s, indexByte);
752end;
753
754function RemoveLineEndingUTF8(var sUTF8: string; indexUTF8: integer): boolean;
755begin
756 result := BGRABitmapTypes.RemoveLineEndingUTF8(sUTF8,indexUTF8);
757end;
758
759procedure BGRAInternalRenderText(dest: TBGRACustomBitmap; Quality: TBGRAFontQuality; grayscale: TGrayscaleMask; temp: TBGRACustomBitmap;
760 x,y,xThird: integer; c: TBGRAPixel; tex: IBGRAScanner);
761begin
762 if Quality in [fqFineClearTypeBGR,fqFineClearTypeRGB,fqSystemClearType] then
763 begin
764 if grayscale <> nil then
765 BGRAFillClearTypeGrayscaleMask(dest,x,y,xThird, grayscale,c,tex,Quality=fqFineClearTypeRGB)
766 else if temp <> nil then
767 BGRAFillClearTypeRGBMask(dest,x,y, temp,c,tex);
768 end
769 else
770 begin
771 if grayscale <> nil then
772 begin
773 if tex <> nil then
774 grayscale.DrawAsAlpha(dest, x, y, tex) else
775 grayscale.DrawAsAlpha(dest, x, y, c);
776 end
777 else if temp <> nil then
778 dest.PutImage(x, y, temp, dmDrawWithTransparency);
779 end;
780end;
781
782procedure BGRATextOut(bmp: TBGRACustomBitmap; Font: TFont; Quality: TBGRAFontQuality; xf, yf: single; sUTF8: string;
783 c: TBGRAPixel; tex: IBGRAScanner; align: TAlignment; CustomAntialiasingLevel: Integer = 0;
784 ShowPrefix: boolean = false; RightToLeft: boolean = false);
785var
786 size: TSize;
787 sizeFactor, extraVerticalMargin: integer;
788 xMarginF: single;
789 style: TTextStyle;
790 noPrefix: string;
791begin
792 if not LCLFontAvailable then exit;
793
794 if CustomAntialiasingLevel = 0 then
795 CustomAntialiasingLevel:= FontAntialiasingLevel;
796
797 if Font.Orientation mod 3600 <> 0 then
798 begin
799 BGRATextOutAngle(bmp,Font,Quality,xf,yf,Font.Orientation,sUTF8,c,tex,align);
800 exit;
801 end;
802
803 {$IFDEF LCL_RENDERER_IS_FINE}
804 if (Quality in [fqFineAntialiasing, fqFineClearTypeRGB, fqFineClearTypeBGR]) and
805 (BGRATextSize(Font, fqSystem, 'Hg', 1).cy >= 13) then
806 begin
807 if Quality = fqFineAntialiasing then Quality := fqSystem;
808 {$IFDEF LCL_CLEARTYPE_RENDERER_IS_FINE}
809 if Quality = GetFineClearTypeAuto then Quality := fqSystemClearType;
810 {$ENDIF}
811 end;
812 {$ENDIF}
813
814 if ShowPrefix then
815 noPrefix := RemovePrefix(sUTF8)
816 else
817 noPrefix := sUTF8;
818
819 size := BGRAOriginalTextSizeEx(Font,Quality,noPrefix,CustomAntialiasingLevel,sizeFactor,extraVerticalMargin);
820 if (size.cx = 0) or (size.cy = 0) then
821 exit;
822
823 if (size.cy >= 144) and (Quality in[fqFineAntialiasing,fqFineClearTypeBGR,fqFineClearTypeRGB]) and (CustomAntialiasingLevel > 4) then
824 begin
825 CustomAntialiasingLevel:= 4;
826 size := BGRAOriginalTextSizeEx(Font,Quality,noPrefix,CustomAntialiasingLevel,sizeFactor,extraVerticalMargin);
827 end;
828
829 case align of
830 taLeftJustify: ;
831 taCenter: xf -= size.cx/2/sizeFactor;
832 taRightJustify: xf -= size.cx/sizeFactor;
833 end;
834
835 xMarginF := size.cy/sizeFactor;
836 fillchar({%H-}style,sizeof(style),0);
837 style.SingleLine := true;
838 style.Alignment := taLeftJustify;
839 style.Layout := tlTop;
840 style.RightToLeft := RightToLeft;
841 style.ShowPrefix := ShowPrefix;
842 BGRATextRect(bmp, Font, Quality,
843 rect(floor(xf-xMarginF), floor(yf), ceil(xf+size.cx/sizeFactor+xMarginF), ceil(yf+size.cy/sizeFactor)),
844 xf,yf, sUTF8, style, c, tex, sizeFactor);
845end;
846
847procedure BGRATextOutAngle(bmp: TBGRACustomBitmap; Font: TFont; Quality: TBGRAFontQuality; xf, yf: single;
848 orientationTenthDegCCW: integer;
849 sUTF8: string; c: TBGRAPixel; tex: IBGRAScanner; align: TAlignment; CustomAntialiasingLevel: Integer = 0);
850var
851 x,y: integer;
852 deltaX,deltaY: integer;
853 size: TSize;
854 temp: TBGRACustomBitmap;
855 TopLeft,TopRight,BottomRight,BottomLeft: TPointF;
856 Top,dy: Single;
857 Left: Single;
858 cosA,sinA: single;
859 rotBounds: TRect;
860 sizeFactor, extraVerticalMargin: integer;
861 TempFont: TFont;
862 oldOrientation: integer;
863 grayscale:TGrayscaleMask;
864 {$IFDEF RENDER_TEXT_ON_TBITMAP}
865 tempLCL: TBitmap;
866 {$ENDIF}
867
868 procedure rotBoundsAdd(pt: TPointF);
869 begin
870 if pt.x < Left then Left := pt.x;
871 if pt.y < Top then Top := pt.y;
872 if floor(pt.X) < rotBounds.Left then rotBounds.Left := floor(pt.X/sizeFactor)*sizeFactor;
873 if floor(pt.Y) < rotBounds.Top then rotBounds.Top := floor(pt.Y/sizeFactor)*sizeFactor;
874 if ceil(pt.X) > rotBounds.Right then rotBounds.Right := ceil(pt.X/sizeFactor)*sizeFactor;
875 if ceil(pt.Y) > rotBounds.Bottom then rotBounds.Bottom := ceil(pt.Y/sizeFactor)*sizeFactor;
876 end;
877
878begin
879 if not LCLFontAvailable then exit;
880
881 if CustomAntialiasingLevel = 0 then
882 CustomAntialiasingLevel:= FontAntialiasingLevel;
883
884 if orientationTenthDegCCW mod 3600 = 0 then
885 begin
886 oldOrientation := Font.Orientation;
887 Font.Orientation := 0;
888 BGRATextOut(bmp,Font,Quality,xf,yf,sUTF8,c,tex,align);
889 Font.Orientation := oldOrientation;
890 exit;
891 end;
892 TempFont := TFont.Create;
893 TempFont.Assign(Font);
894 TempFont.Orientation := orientationTenthDegCCW;
895 TempFont.Height := Font.Height;
896 size := BGRAOriginalTextSizeEx(TempFont,Quality,sUTF8,CustomAntialiasingLevel,sizeFactor, extraVerticalMargin);
897 if (size.cx = 0) or (size.cy = 0) then
898 begin
899 tempFont.Free;
900 exit;
901 end;
902 {$IFDEF FIX_FONT_VERTICAL_OFFSET}
903 if extraVerticalMargin > 0 then
904 dy := -extraVerticalMargin*0.5 -1
905 else
906 dy := 0;
907 {$ELSE}
908 dy := 0;
909 {$ENDIF}
910 tempFont.Free;
911
912 cosA := cos(orientationTenthDegCCW*Pi/1800);
913 sinA := sin(orientationTenthDegCCW*Pi/1800);
914 TopLeft := PointF(sinA*dy,cosA*dy);
915 xf += TopLeft.x/sizeFactor;
916 yf += TopLeft.y/sizeFactor;
917 TopRight := TopLeft + PointF(cosA*size.cx,-sinA*size.cx);
918 BottomRight := TopRight + PointF(sinA*size.cy,cosA*size.cy);
919 BottomLeft := TopLeft + PointF(sinA*size.cy,cosA*size.cy);
920 rotBounds := rect(0,0,0,0);
921 Top := 0;
922 Left := 0;
923 rotBoundsAdd(TopRight);
924 rotBoundsAdd(BottomRight);
925 rotBoundsAdd(BottomLeft);
926 inc(rotBounds.Right);
927 inc(rotBounds.Bottom);
928
929 xf += Left/sizeFactor;
930 yf += Top/sizeFactor;
931 case align of
932 taLeftJustify: ;
933 taCenter:
934 begin
935 xf -= TopRight.x/2/sizeFactor;
936 yf -= TopRight.y/2/sizeFactor;
937 end;
938 taRightJustify:
939 begin
940 xf -= TopRight.x/sizeFactor;
941 yf -= TopRight.y/sizeFactor;
942 end;
943 end;
944 deltaX := round((xf - floor(xf))*sizeFactor);
945 x := floor(xf);
946 deltaY := round((yf - floor(yf))*sizeFactor);
947 y := floor(yf);
948 if deltaX <> 0 then rotBounds.Right += sizeFactor;
949 if deltaY <> 0 then rotBounds.Bottom += sizeFactor;
950
951 {$IFDEF RENDER_TEXT_ON_TBITMAP}
952 tempLCL := TBitmap.Create;
953 tempLCL.Width := rotBounds.Right-rotBounds.Left;
954 tempLCL.Height := rotBounds.Bottom-rotBounds.Top;
955 tempLCL.Canvas.Brush.Color := clBlack;
956 tempLCL.Canvas.FillRect(0,0,tempLCL.Width,tempLCL.Height);
957 with tempLCL do begin
958 {$ELSE}
959 temp := bmp.NewBitmap(rotBounds.Right-rotBounds.Left,rotBounds.Bottom-rotBounds.Top, BGRABlack);
960 with temp do begin
961 {$ENDIF}
962 Canvas.Font := Font;
963 Canvas.Font.Color := clWhite;
964 Canvas.Font.Orientation := orientationTenthDegCCW;
965 Canvas.Font.Height := round(Font.Height*sizeFactor);
966 Canvas.Brush.Style := bsClear;
967 Canvas.TextOut(-rotBounds.Left+deltaX, -rotBounds.Top+deltaY, sUTF8);
968 end;
969 {$IFDEF RENDER_TEXT_ON_TBITMAP}
970 temp := BGRABitmapFactory.create(tempLCL,False);
971 tempLCL.Free;
972 {$ENDIF}
973
974 FilterOriginalText(Quality,CustomAntialiasingLevel,temp,grayscale);
975 BGRAInternalRenderText(bmp, Quality, grayscale,temp, x,y,0, c,tex);
976 temp.Free;
977 grayscale.Free;
978end;
979
980procedure BGRATextRect(bmp: TBGRACustomBitmap; Font: TFont; Quality: TBGRAFontQuality; ARect: TRect; xf, yf: single;
981 sUTF8: string; style: TTextStyle; c: TBGRAPixel; tex: IBGRAScanner; CustomAntialiasingLevel: Integer = 0);
982var
983 lim: TRect;
984 tx, ty: integer;
985 temp: TBGRACustomBitmap;
986 sizeFactor: integer;
987 cr: TRect;
988 grayscale:TGrayscaleMask;
989 {$IFDEF RENDER_TEXT_ON_TBITMAP}
990 tempLCL: TBitmap;
991 {$ENDIF}
992begin
993 if not LCLFontAvailable then exit;
994
995 if CustomAntialiasingLevel = 0 then
996 CustomAntialiasingLevel:= FontAntialiasingLevel;
997
998 cr := bmp.ClipRect;
999 if ARect.Left < cr.Left then
1000 lim.Left := cr.Left else lim.Left := ARect.Left;
1001 if ARect.Top < cr.Top then
1002 lim.Top := cr.Top else lim.Top := ARect.Top;
1003 if ARect.Right > cr.Right then
1004 lim.Right := cr.Right else lim.Right := ARect.Right;
1005 if ARect.Bottom > cr.Bottom then
1006 lim.Bottom := cr.Bottom else lim.Bottom := ARect.Bottom;
1007
1008 tx := lim.Right - lim.Left;
1009 ty := lim.Bottom - lim.Top;
1010 if (tx <= 0) or (ty <= 0) then
1011 exit;
1012
1013 {$IFDEF LCL_RENDERER_IS_FINE}
1014 if (Quality in [fqFineAntialiasing, fqFineClearTypeRGB, fqFineClearTypeBGR]) and
1015 (BGRATextSize(Font, fqSystem, 'Hg', 1).cy >= 13) then
1016 begin
1017 if Quality = fqFineAntialiasing then Quality := fqSystem;
1018 {$IFDEF LCL_CLEARTYPE_RENDERER_IS_FINE}
1019 if Quality = GetFineClearTypeAuto then Quality := fqSystemClearType;
1020 {$ENDIF}
1021 end;
1022 {$ENDIF}
1023
1024 if Quality in[fqFineAntialiasing,fqFineClearTypeBGR,fqFineClearTypeRGB] then
1025 sizeFactor := CustomAntialiasingLevel
1026 else
1027 sizeFactor := 1;
1028
1029 {$IFDEF RENDER_TEXT_ON_TBITMAP}
1030 tempLCL := TBitmap.Create;
1031 tempLCL.Width := tx*sizeFactor;
1032 tempLCL.Height := ty*sizeFactor;
1033 tempLCL.Canvas.Brush.Color := clBlack;
1034 tempLCL.Canvas.FillRect(0,0,tempLCL.Width,tempLCL.Height);
1035 with tempLCL do begin
1036 {$ELSE}
1037 temp := bmp.NewBitmap(tx*sizeFactor, ty*sizeFactor, BGRABlack);
1038 with temp do begin
1039 {$ENDIF}
1040 Canvas.Font := Font;
1041 Canvas.Font.Orientation := 0;
1042 if Quality in[fqFineAntialiasing,fqFineClearTypeBGR,fqFineClearTypeRGB] then Canvas.Font.Height := Font.Height*CustomAntialiasingLevel
1043 else Canvas.Font.Height := Font.Height;
1044 Canvas.Font.Color := clWhite;
1045 Canvas.Brush.Style := bsClear;
1046 Canvas.TextRect(rect(lim.Left-ARect.Left, lim.Top-ARect.Top,
1047 (ARect.Right-ARect.Left)*sizeFactor, (ARect.Bottom-ARect.Top)*sizeFactor),
1048 round((xf - lim.Left)*sizeFactor), round((yf - lim.Top)*sizeFactor), sUTF8, style);
1049 end;
1050 {$IFDEF RENDER_TEXT_ON_TBITMAP}
1051 temp := BGRABitmapFactory.create(tempLCL,False);
1052 tempLCL.Free;
1053 {$ENDIF}
1054
1055 FilterOriginalText(Quality,CustomAntialiasingLevel,temp,grayscale);
1056 BGRAInternalRenderText(bmp, Quality, grayscale,temp, lim.left,lim.top,0, c,tex);
1057 temp.Free;
1058 grayscale.Free;
1059end;
1060
1061{ TCustomLCLFontRenderer }
1062
1063{ Update font properties to internal TFont object }
1064procedure TCustomLCLFontRenderer.UpdateFont;
1065var fixedHeight: integer;
1066begin
1067 if FFont.Name <> FontName then
1068 FFont.Name := FontName;
1069 if FFont.Style <> FontStyle then
1070 FFont.Style := FontStyle;
1071 if FontEmHeight < 0 then
1072 fixedHeight := FixLCLFontFullHeight(FontName, FontEmHeight * FontEmHeightSign)
1073 else
1074 fixedHeight := FontEmHeight * FontEmHeightSign;
1075 if FFont.Height <> fixedHeight then
1076 FFont.Height := fixedHeight;
1077 if FFont.Orientation <> FontOrientation then
1078 FFont.Orientation := FontOrientation;
1079 if FontQuality = fqSystemClearType then
1080 FFont.Quality := fqCleartype
1081 else
1082 FFont.Quality := FontDefaultQuality;
1083end;
1084
1085function TCustomLCLFontRenderer.InternalTextSize(sUTF8: string; AShowPrefix: boolean): TSize;
1086begin
1087 if AShowPrefix then sUTF8 := RemovePrefix(sUTF8);
1088 result := BGRAText.BGRATextSize(FFont,FontQuality,sUTF8,FontAntialiasingLevel);
1089 if (result.cy >= 24) and (FontQuality in[fqFineAntialiasing,fqFineClearTypeBGR,fqFineClearTypeRGB]) then
1090 result := BGRAText.BGRATextSize(FFont,FontQuality,sUTF8,4);
1091end;
1092
1093procedure TCustomLCLFontRenderer.SplitText(var ATextUTF8: string;
1094 AMaxWidth: integer; out ARemainsUTF8: string);
1095var WordBreakHandler: TWordBreakHandler;
1096begin
1097 UpdateFont;
1098 if Assigned(FWordBreakHandler) then
1099 WordBreakHandler := FWordBreakHandler
1100 else
1101 WordBreakHandler := @DefaultWorkBreakHandler;
1102
1103 InternalSplitText(ATextUTF8, AMaxWidth, ARemainsUTF8, WordBreakHandler);
1104end;
1105
1106function TCustomLCLFontRenderer.GetFontPixelMetric: TFontPixelMetric;
1107var fxFont: TFont;
1108begin
1109 UpdateFont;
1110 if FontQuality in[fqSystem,fqSystemClearType] then
1111 result := GetLCLFontPixelMetric(FFont)
1112 else
1113 begin
1114 FxFont := TFont.Create;
1115 FxFont.Assign(FFont);
1116 FxFont.Height := fxFont.Height*FontAntialiasingLevel;
1117 Result:= GetLCLFontPixelMetric(FxFont);
1118 if Result.Baseline <> -1 then Result.Baseline:= round((Result.Baseline-1)/FontAntialiasingLevel);
1119 if Result.CapLine <> -1 then Result.CapLine:= round(Result.CapLine/FontAntialiasingLevel);
1120 if Result.DescentLine <> -1 then Result.DescentLine:= round((Result.DescentLine-1)/FontAntialiasingLevel);
1121 if Result.Lineheight <> -1 then Result.Lineheight:= round(Result.Lineheight/FontAntialiasingLevel);
1122 if Result.xLine <> -1 then Result.xLine:= round(Result.xLine/FontAntialiasingLevel);
1123 FxFont.Free;
1124 end;
1125end;
1126
1127procedure TCustomLCLFontRenderer.TextOutAngle(ADest: TBGRACustomBitmap; x, y: single; orientationTenthDegCCW: integer;
1128 sUTF8: string; c: TBGRAPixel; align: TAlignment);
1129begin
1130 UpdateFont;
1131 BGRAText.BGRATextOutAngle(ADest,FFont,FontQuality,x,y,orientationTenthDegCCW,sUTF8,c,nil,align);
1132end;
1133
1134procedure TCustomLCLFontRenderer.TextOutAngle(ADest: TBGRACustomBitmap; x, y: single; orientationTenthDegCCW: integer;
1135 sUTF8: string; texture: IBGRAScanner; align: TAlignment);
1136begin
1137 UpdateFont;
1138 BGRAText.BGRATextOutAngle(ADest,FFont,FontQuality,x,y,orientationTenthDegCCW,sUTF8,BGRAPixelTransparent,texture,align);
1139end;
1140
1141procedure TCustomLCLFontRenderer.TextOut(ADest: TBGRACustomBitmap; x, y: single; sUTF8: string;
1142 texture: IBGRAScanner; align: TAlignment);
1143begin
1144 UpdateFont;
1145 InternalTextOut(ADest, x,y, sUTF8, BGRAPixelTransparent,texture, align);
1146end;
1147
1148procedure TCustomLCLFontRenderer.TextOut(ADest: TBGRACustomBitmap; x, y: single; sUTF8: string; c: TBGRAPixel;
1149 align: TAlignment);
1150begin
1151 UpdateFont;
1152 InternalTextOut(ADest, x,y, sUTF8, c,nil, align);
1153end;
1154
1155procedure TCustomLCLFontRenderer.TextOut(ADest: TBGRACustomBitmap; x,
1156 y: single; sUTF8: string; texture: IBGRAScanner; align: TAlignment;
1157 ARightToLeft: boolean);
1158begin
1159 UpdateFont;
1160 InternalTextOut(ADest, x,y, sUTF8, BGRAPixelTransparent,texture, align,
1161 False, ARightToLeft);
1162end;
1163
1164procedure TCustomLCLFontRenderer.TextOut(ADest: TBGRACustomBitmap; x,
1165 y: single; sUTF8: string; c: TBGRAPixel; align: TAlignment;
1166 ARightToLeft: boolean);
1167begin
1168 UpdateFont;
1169 InternalTextOut(ADest, x,y, sUTF8, c,nil, align, false, ARightToLeft);
1170end;
1171
1172procedure TCustomLCLFontRenderer.TextRect(ADest: TBGRACustomBitmap; ARect: TRect; x, y: integer; sUTF8: string;
1173 style: TTextStyle; c: TBGRAPixel);
1174begin
1175 UpdateFont;
1176 InternalTextRect(ADest,ARect,x,y,sUTF8,style,c,nil);
1177end;
1178
1179procedure TCustomLCLFontRenderer.TextRect(ADest: TBGRACustomBitmap; ARect: TRect; x, y: integer; sUTF8: string;
1180 style: TTextStyle; texture: IBGRAScanner);
1181begin
1182 UpdateFont;
1183 InternalTextRect(ADest,ARect,x,y,sUTF8,style,BGRAPixelTransparent,texture);
1184end;
1185
1186procedure TCustomLCLFontRenderer.TextWordBreak(ADest: TBGRACustomBitmap;
1187 AText: string; x, y, AMaxWidth: integer; AColor: TBGRAPixel;
1188 AHorizAlign: TAlignment; AVertAlign: TTextLayout; ARightToLeft: boolean);
1189begin
1190 UpdateFont;
1191 InternalTextWordBreak(ADest,AText,x,y,AMaxWidth,AColor,nil,AHorizAlign,AVertAlign,ARightToLeft);
1192end;
1193
1194procedure TCustomLCLFontRenderer.TextWordBreak(ADest: TBGRACustomBitmap;
1195 AText: string; x, y, AMaxWidth: integer; ATexture: IBGRAScanner;
1196 AHorizAlign: TAlignment; AVertAlign: TTextLayout; ARightToLeft: boolean);
1197begin
1198 UpdateFont;
1199 InternalTextWordBreak(ADest,AText,x,y,AMaxWidth,BGRAPixelTransparent,ATexture,AHorizAlign,AVertAlign,ARightToLeft);
1200end;
1201
1202procedure TCustomLCLFontRenderer.InternalTextWordBreak(
1203 ADest: TBGRACustomBitmap; ATextUTF8: string; x, y, AMaxWidth: integer;
1204 AColor: TBGRAPixel; ATexture: IBGRAScanner; AHorizAlign: TAlignment;
1205 AVertAlign: TTextLayout; ARightToLeft: boolean);
1206var remains, part, curText,nextText: string;
1207 stepX,stepY: integer;
1208 lines: TStringList;
1209 i: integer;
1210 lineShift: single;
1211 WordBreakHandler: TWordBreakHandler;
1212 lineEndingBreak: boolean;
1213 bidiLayout: TBidiTextLayout;
1214 bidiAlign: TBidiTextAlignment;
1215begin
1216 if (ATextUTF8 = '') or (AMaxWidth <= 0) then exit;
1217
1218 if Assigned(FWordBreakHandler) then
1219 WordBreakHandler := FWordBreakHandler
1220 else
1221 WordBreakHandler := @DefaultWorkBreakHandler;
1222
1223 if ContainsBidiIsolateOrFormattingUTF8(ATextUTF8) then
1224 begin
1225 bidiLayout := TBidiTextLayout.Create(self, ATextUTF8, ARightToLeft);
1226 bidiLayout.WordBreakHandler:= WordBreakHandler;
1227 bidiLayout.AvailableWidth := AMaxWidth;
1228 case AHorizAlign of
1229 taLeftJustify: bidiAlign:= btaLeftJustify;
1230 taRightJustify: begin
1231 bidiAlign:= btaRightJustify;
1232 x -= AMaxWidth;
1233 end
1234 else
1235 begin
1236 bidiAlign:= btaCenter;
1237 x -= AMaxWidth div 2;
1238 end;
1239 end;
1240 for i := 0 to bidiLayout.ParagraphCount-1 do
1241 bidiLayout.ParagraphAlignment[i] := bidiAlign;
1242 case AVertAlign of
1243 tlBottom: bidiLayout.TopLeft := PointF(x, y - bidiLayout.TotalTextHeight);
1244 tlCenter: bidiLayout.TopLeft := PointF(x, y - bidiLayout.TotalTextHeight/2);
1245 end;
1246 if ATexture <> nil then bidiLayout.DrawText(ADest, ATexture)
1247 else bidiLayout.DrawText(ADest, AColor);
1248 bidiLayout.Free;
1249 exit;
1250 end;
1251
1252 stepX := 0;
1253 stepY := TextSize('Hg').cy;
1254
1255 lines := TStringList.Create;
1256 curText := ATextUTF8;
1257 repeat
1258 InternalSplitText(curText, AMaxWidth, remains, lineEndingBreak, WordBreakHandler);
1259 part := curText;
1260 if not lineEndingBreak then
1261 // append following direction to part
1262 case GetFirstStrongBidiClassUTF8(remains) of
1263 ubcLeftToRight: if ARightToLeft then part += UnicodeCharToUTF8($200E);
1264 ubcRightToLeft,ubcArabicLetter: if not ARightToLeft then part += UnicodeCharToUTF8($200F);
1265 end;
1266 lines.Add(part);
1267 // prefix next part with previous direction
1268 nextText := remains;
1269 if not lineEndingBreak then
1270 case GetLastStrongBidiClassUTF8(curText) of
1271 ubcLeftToRight: if ARightToLeft then nextText := UnicodeCharToUTF8($200E) + nextText;
1272 ubcRightToLeft,ubcArabicLetter: if not ARightToLeft then nextText := UnicodeCharToUTF8($200F) + nextText;
1273 end;
1274 curText := nextText;
1275 until remains = '';
1276 if AVertAlign = tlCenter then lineShift := lines.Count/2
1277 else if AVertAlign = tlBottom then lineShift := lines.Count
1278 else lineShift := 0;
1279
1280 X -= round(stepX*lineShift);
1281 Y -= round(stepY*lineShift);
1282 for i := 0 to lines.Count-1 do
1283 begin
1284 InternalTextOut(ADest,x,y,lines[i],AColor,ATexture,AHorizAlign,false,ARightToLeft);
1285 X+= stepX;
1286 Y+= stepY;
1287 end;
1288 lines.Free;
1289end;
1290
1291procedure TCustomLCLFontRenderer.InternalTextRect(ADest: TBGRACustomBitmap;
1292 ARect: TRect; x, y: integer; sUTF8: string; style: TTextStyle; c: TBGRAPixel;
1293 ATexture: IBGRAScanner);
1294var
1295 previousClip, intersected: TRect;
1296 lines: TStringList;
1297 iStart,i,h: integer;
1298 availableWidth: integer;
1299begin
1300 previousClip := ADest.ClipRect;
1301 if style.Clipping then
1302 begin
1303 intersected := rect(0,0,0,0);
1304 if not IntersectRect(intersected, previousClip, ARect) then exit;
1305 ADest.ClipRect := intersected;
1306 end;
1307 FFont.Orientation := 0;
1308 if style.SystemFont then FFont.Name := 'default';
1309
1310 if not (style.Alignment in[taCenter,taRightJustify]) then ARect.Left := x;
1311 if not (style.Layout in[tlCenter,tlBottom]) then ARect.top := y;
1312 if (ARect.Right <= ARect.Left) and style.Clipping then
1313 begin
1314 ADest.ClipRect := previousClip;
1315 exit;
1316 end;
1317 if style.Layout = tlCenter then Y := (ARect.Top+ARect.Bottom) div 2 else
1318 if style.Layout = tlBottom then Y := ARect.Bottom else
1319 Y := ARect.Top;
1320 if style.Alignment = taCenter then X := (ARect.Left+ARect.Right) div 2 else
1321 if style.Alignment = taRightJustify then X := ARect.Right else
1322 X := ARect.Left;
1323 if style.Wordbreak then
1324 begin
1325 if style.ShowPrefix then sUTF8 := RemovePrefix(sUTF8); //prefix not handled
1326 InternalTextWordBreak(ADest,sUTF8,X,Y,ARect.Right-ARect.Left,c,ATexture,
1327 style.Alignment,style.Layout,style.RightToLeft);
1328 end
1329 else
1330 begin
1331 lines := nil;
1332 iStart := 1;
1333
1334 if not style.SingleLine then
1335 begin
1336 i := iStart;
1337 while i <= length(sUTF8) do
1338 begin
1339 if sUTF8[i] in[#13,#10] then
1340 begin
1341 if not assigned(lines) then lines := TStringList.Create;
1342 lines.add(copy(sUTF8,iStart,i-iStart));
1343 if (sUTF8[i]=#13) and (i < length(sUTF8)) and (sUTF8[i+1]=#10) then inc(i);
1344 iStart := i+1
1345 end;
1346 inc(i);
1347 end;
1348 end;
1349
1350 if style.Alignment = taLeftJustify then
1351 availableWidth := ARect.Right-X
1352 else
1353 availableWidth := ARect.Right-ARect.Left;
1354 if availableWidth < 0 then availableWidth:= 0;
1355
1356 if lines = nil then //only one line
1357 begin
1358 if style.Layout = tlCenter then Y -= InternalTextSize(sUTF8,style.ShowPrefix).cy div 2;
1359 if style.Layout = tlBottom then Y -= InternalTextSize(sUTF8,style.ShowPrefix).cy;
1360 if style.EndEllipsis then
1361 InternalTextOutEllipse(ADest,X,Y,availableWidth,sUTF8,c,ATexture,style.Alignment,
1362 style.ShowPrefix,style.RightToLeft)
1363 else
1364 InternalTextOut(ADest,X,Y,sUTF8,c,ATexture,style.Alignment,
1365 style.ShowPrefix,style.RightToLeft);
1366 end else
1367 begin //multiple lines
1368 lines.add(copy(sUTF8, iStart, length(sUTF8)-iStart+1));
1369 h := InternalTextSize('Hg',False).cy;
1370 if style.Layout = tlCenter then Y -= h*lines.Count div 2;
1371 if style.Layout = tlBottom then Y -= h*lines.Count;
1372 for i := 0 to lines.Count-1 do
1373 begin
1374 if style.EndEllipsis then
1375 InternalTextOutEllipse(ADest,X,Y,availableWidth,lines[i],c,ATexture,style.Alignment,
1376 style.ShowPrefix,style.RightToLeft)
1377 else
1378 InternalTextOut(ADest,X,Y,lines[i],c,ATexture,style.Alignment,
1379 style.ShowPrefix,style.RightToLeft);
1380 inc(Y,h);
1381 end;
1382 lines.Free;
1383 end;
1384
1385 end;
1386
1387 if style.Clipping then
1388 ADest.ClipRect := previousClip;
1389end;
1390
1391procedure TCustomLCLFontRenderer.InternalTextOut(ADest: TBGRACustomBitmap; x,
1392 y: single; sUTF8: string; c: TBGRAPixel; texture: IBGRAScanner;
1393 align: TAlignment; AShowPrefix: boolean = false; ARightToLeft: boolean = false);
1394var mode : TBGRATextOutImproveReadabilityMode;
1395begin
1396 {$IFDEF LINUX}
1397 //help LCL detect the correct direction
1398 case GetFirstStrongBidiClassUTF8(sUTF8) of
1399 ubcRightToLeft, ubcArabicLetter: if not ARightToLeft then sUTF8 := UnicodeCharToUTF8($200E) + sUTF8;
1400 else
1401 begin //suppose left-to-right
1402 if ARightToLeft then sUTF8 := UnicodeCharToUTF8($200F) + sUTF8;
1403 end;
1404 end;
1405 {$ENDIF}
1406 if Assigned(BGRATextOutImproveReadabilityProc) and (FontQuality in[fqFineAntialiasing,fqFineClearTypeBGR,fqFineClearTypeRGB]) and (FFont.Orientation mod 3600 = 0) then
1407 begin
1408 case FontQuality of
1409 fqFineClearTypeBGR: mode := irClearTypeBGR;
1410 fqFineClearTypeRGB: mode := irClearTypeRGB;
1411 else
1412 mode := irNormal;
1413 end;
1414 if AShowPrefix then sUTF8 := RemovePrefix(sUTF8); //prefix not handled
1415 BGRATextOutImproveReadabilityProc(ADest,FFont,x,y,sUTF8,c,texture,align,mode);
1416 end else
1417 BGRAText.BGRATextOut(ADest,FFont,FontQuality,x,y,sUTF8,c,texture,align,
1418 0,AShowPrefix,ARightToLeft);
1419end;
1420
1421procedure TCustomLCLFontRenderer.InternalTextOutEllipse(
1422 ADest: TBGRACustomBitmap; x, y, availableWidth: single; sUTF8: string;
1423 c: TBGRAPixel; texture: IBGRAScanner; align: TAlignment;
1424 AShowPrefix: boolean; ARightToLeft: boolean);
1425var remain: string;
1426begin
1427 if InternalTextSize(sUTF8,AShowPrefix).cx > availableWidth then
1428 begin
1429 InternalSplitText(sUTF8, round(availableWidth - InternalTextSize('...',AShowPrefix).cx), remain, nil);
1430 sUTF8 += '...';
1431 end;
1432 InternalTextOut(ADest,x,y,sUTF8,c,texture,align,AShowPrefix,ARightToLeft);
1433end;
1434
1435procedure TCustomLCLFontRenderer.InternalSplitText(var ATextUTF8: string;
1436 AMaxWidth: integer; out ARemainsUTF8: string; out ALineEndingBreak: boolean; AWordBreak: TWordBreakHandler);
1437var p,skipCount, charLen: integer;
1438 zeroWidth: boolean;
1439 u: Cardinal;
1440begin
1441 ALineEndingBreak:= false;
1442 if ATextUTF8= '' then
1443 begin
1444 ARemainsUTF8 := '';
1445 exit;
1446 end;
1447 if RemoveLineEnding(ATextUTF8,1) then
1448 begin
1449 ARemainsUTF8:= ATextUTF8;
1450 ATextUTF8 := '';
1451 ALineEndingBreak:= true;
1452 exit;
1453 end;
1454
1455 if AMaxWidth <= 0 then
1456 skipCount := 0
1457 else
1458 skipCount := BGRATextFitInfo(FFont, FontQuality, ATextUTF8, FontAntialiasingLevel, AMaxWidth);
1459
1460 if skipCount <= 0 then skipCount := 1;
1461
1462 p := 1;
1463 zeroWidth := true;
1464 repeat
1465 charLen := UTF8CharacterLength(@ATextUTF8[p]);
1466 u := UTF8CodepointToUnicode(@ATextUTF8[p], charLen);
1467 if not IsZeroWidthUnicode(u) then
1468 zeroWidth:= false;
1469 inc(p, charLen); //UTF8 chars may be more than 1 byte long
1470 dec(skipCount);
1471
1472 if RemoveLineEnding(ATextUTF8,p) then
1473 begin
1474 ARemainsUTF8:= copy(ATextUTF8,p,length(ATextUTF8)-p+1);
1475 ATextUTF8 := copy(ATextUTF8,1,p-1);
1476 ALineEndingBreak:= true;
1477 exit;
1478 end;
1479 until ((skipCount <= 0) and not zeroWidth) or (p >= length(ATextUTF8)+1);
1480
1481 ARemainsUTF8:= copy(ATextUTF8,p,length(ATextUTF8)-p+1);
1482 ATextUTF8 := copy(ATextUTF8,1,p-1); //this includes the whole last UTF8 char
1483 if Assigned(AWordBreak) then AWordBreak(ATextUTF8,ARemainsUTF8);
1484end;
1485
1486procedure TCustomLCLFontRenderer.InternalSplitText(var ATextUTF8: string;
1487 AMaxWidth: integer; out ARemainsUTF8: string; AWordBreak: TWordBreakHandler);
1488var lineEndingBreak: boolean;
1489begin
1490 InternalSplitText(ATextUTF8,AMaxWidth,ARemainsUTF8,lineEndingBreak,AWordBreak);
1491end;
1492
1493procedure TCustomLCLFontRenderer.DefaultWorkBreakHandler(var ABeforeUTF8,
1494 AAfterUTF8: string);
1495begin
1496 BGRADefaultWordBreakHandler(ABeforeUTF8,AAfterUTF8);
1497end;
1498
1499function TCustomLCLFontRenderer.TextSize(sUTF8: string): TSize;
1500var oldOrientation: integer;
1501begin
1502 oldOrientation:= FontOrientation;
1503 FontOrientation:= 0;
1504 UpdateFont;
1505 result := InternalTextSize(sUTF8,False);
1506 FontOrientation:= oldOrientation;
1507end;
1508
1509function TCustomLCLFontRenderer.TextSizeAngle(sUTF8: string;
1510 orientationTenthDegCCW: integer): TSize;
1511var oldOrientation: integer;
1512begin
1513 oldOrientation:= FontOrientation;
1514 FontOrientation:= orientationTenthDegCCW;
1515 UpdateFont;
1516 result := InternalTextSize(sUTF8,False);
1517 FontOrientation:= oldOrientation;
1518end;
1519
1520function TCustomLCLFontRenderer.TextSize(sUTF8: string;
1521 AMaxWidth: integer; ARightToLeft: boolean): TSize;
1522var
1523 remains: string;
1524 h, i, w: integer;
1525 WordBreakHandler: TWordBreakHandler;
1526 layout: TBidiTextLayout;
1527begin
1528 UpdateFont;
1529
1530 if Assigned(FWordBreakHandler) then
1531 WordBreakHandler := FWordBreakHandler
1532 else
1533 WordBreakHandler := @DefaultWorkBreakHandler;
1534
1535 if ContainsBidiIsolateOrFormattingUTF8(sUTF8) then
1536 begin
1537 layout := TBidiTextLayout.Create(self, sUTF8, ARightToLeft);
1538 layout.WordBreakHandler:= WordBreakHandler;
1539 layout.AvailableWidth := AMaxWidth;
1540 for i := 0 to layout.ParagraphCount-1 do
1541 layout.ParagraphAlignment[i] := btaLeftJustify;
1542 result.cx := 0;
1543 for i := 0 to layout.PartCount-1 do
1544 begin
1545 w := ceil(layout.PartRectF[i].Right);
1546 if w > result.cx then result.cx := w;
1547 end;
1548 result.cy := ceil(layout.TotalTextHeight);
1549 layout.Free;
1550 end else
1551 begin
1552 result.cx := 0;
1553 result.cy := 0;
1554 h := InternalTextSize('Hg',False).cy;
1555 repeat
1556 InternalSplitText(sUTF8, AMaxWidth, remains, WordBreakHandler);
1557 with InternalTextSize(sUTF8, false) do
1558 if cx > result.cx then result.cx := cx;
1559 result.cy += h;
1560 sUTF8 := remains;
1561 until remains = '';
1562 end;
1563end;
1564
1565function TCustomLCLFontRenderer.TextFitInfo(sUTF8: string; AMaxWidth: integer
1566 ): integer;
1567begin
1568 UpdateFont;
1569 result := BGRATextFitInfo(FFont, FontQuality, sUTF8, FontAntialiasingLevel, AMaxWidth);
1570end;
1571
1572constructor TCustomLCLFontRenderer.Create;
1573begin
1574 FFont := TFont.Create;
1575end;
1576
1577destructor TCustomLCLFontRenderer.Destroy;
1578begin
1579 FFont.Free;
1580 inherited Destroy;
1581end;
1582
1583initialization
1584
1585 tempBmp := nil;
1586
1587finalization
1588
1589 tempBmp.Free;
1590
1591end.
1592
Note: See TracBrowser for help on using the repository browser.