source: trunk/Packages/bgrabitmap/bgratextbidi.pas

Last change on this file was 2, checked in by chronos, 5 years ago
File size: 67.3 KB
Line 
1unit BGRATextBidi;
2
3{$mode objfpc}{$H+}
4
5interface
6
7uses
8 Classes, SysUtils, BGRABitmapTypes, BGRAUTF8, BGRAUnicode, BGRATransform;
9
10type
11 TBidiCaretPos = record
12 PartIndex: integer;
13
14 Top, Bottom: TPointF;
15 RightToLeft: boolean;
16
17 PreviousTop, PreviousBottom: TPointF;
18 PreviousRightToLeft: boolean;
19 end;
20
21 { TBidiTextLayout }
22
23 TBidiTextLayout = class
24 private
25 FAvailableHeight: single;
26 FAvailableWidth: single;
27 FParagraphSpacingAbove: single;
28 FParagraphSpacingBelow: single;
29 FTopLeft: TPointF;
30 FMatrix, FMatrixInverse: TAffineMatrix;
31 FText: string;
32 FCharCount: integer;
33 FTabSize: Single;
34 FWordBreakHandler: TWordBreakHandler;
35 function GetBrokenLineAffineBox(AIndex: integer): TAffineBox;
36 function GetBrokenLineCount: integer;
37 function GetBrokenLineEndCaret(AIndex: integer): TBidiCaretPos;
38 function GetBrokenLineEndIndex(AIndex: integer): integer;
39 function GetBrokenLineParagraphIndex(AIndex: integer): integer;
40 function GetBrokenLineRectF(AIndex: integer): TRectF;
41 function GetBrokenLineRightToLeft(AIndex: integer): boolean;
42 function GetBrokenLineStartCaret(AIndex: integer): TBidiCaretPos;
43 function GetBrokenLineStartIndex(AIndex: integer): integer;
44 function GetMatrix: TAffineMatrix;
45 function GetMatrixInverse: TAffineMatrix;
46 function GetParagraphAffineBox(AIndex: integer): TAffineBox;
47 function GetParagraphAlignment(AIndex: integer): TBidiTextAlignment;
48 function GetParagraphEndIndex(AIndex: integer): integer;
49 function GetParagraphEndIndexBeforeParagraphSeparator(AIndex: integer): integer;
50 function GetParagraphRectF(AIndex: integer): TRectF;
51 function GetParagraphRightToLeft(AIndex: integer): boolean;
52 function GetParagraphStartIndex(AIndex: integer): integer;
53 function GetPartAffineBox(AIndex: integer): TAffineBox;
54 function GetPartBrokenLineIndex(AIndex: integer): integer;
55 function GetPartCount: integer;
56 function GetPartEndIndex(AIndex: integer): integer;
57 function GetPartRectF(AIndex: integer): TRectF;
58 function GetPartRightToLeft(AIndex: integer): boolean;
59 function GetPartStartIndex(AIndex: integer): integer;
60 function GetTotalTextHeight: single;
61 function GetUnicodeChar(APosition0: integer): cardinal;
62 function GetUTF8Char(APosition0: integer): string4;
63 procedure SetAvailableHeight(AValue: single);
64 procedure SetAvailableWidth(AValue: single);
65 procedure SetFontBidiMode(AValue: TFontBidiMode);
66 procedure SetFontRenderer(AValue: TBGRACustomFontRenderer);
67 procedure SetParagraphAlignment(AIndex: integer; AValue: TBidiTextAlignment);
68 procedure SetParagraphSpacingAbove(AValue: single);
69 procedure SetParagraphSpacingBelow(AValue: single);
70 procedure SetTabSize(AValue: single);
71 procedure SetTopLeft(AValue: TPointF);
72 procedure ComputeMatrix;
73 protected
74 FBidi: TBidiUTF8Array;
75 FRenderer: TBGRACustomFontRenderer;
76 FLineHeight: single;
77
78 FParagraph: array of record
79 firstUnbrokenLineIndex: integer;
80 rectF: TRectF;
81 alignment: TBidiTextAlignment;
82 rtl: boolean;
83 end;
84 FParagraphCount: integer;
85
86 FUnbrokenLine: array of record
87 startIndex: integer;
88 firstBrokenLineIndex: integer;
89 paragraphIndex: integer;
90 end;
91 FUnbrokenLineCount: integer;
92
93 FBrokenLine: array of record
94 unbrokenLineIndex: integer;
95 startIndex, endIndex: integer;
96 bidiLevel: byte;
97 rectF: TRectF;
98 firstPartIndex, lastPartIndexPlusOne: integer;
99 end;
100 FBrokenLineCount: integer;
101
102 FPart: array of record
103 startIndex, endIndex: integer;
104 bidiLevel: byte;
105 rectF: TRectF;
106 posCorrection: TPointF;
107 sUTF8: string;
108 brokenLineIndex: integer;
109 end;
110 FPartCount: integer;
111 FLayoutComputed: boolean;
112 FColor: TBGRAPixel;
113 FTexture: IBGRAScanner;
114 FFontBidiMode: TFontBidiMode;
115
116 function TextSizeBidiOverride(sUTF8: string; ARightToLeft: boolean): TPointF;
117 function TextSizeBidiOverrideSplit(AStartIndex, AEndIndex: integer; ARightToLeft: boolean; ASplitIndex: integer): TPointF;
118 function TextFitInfoBidiOverride(sUTF8: string; AWidth: single; ARightToLeft: boolean): integer;
119 function GetFontFullHeight: single;
120 function GetFontBaseline: single;
121 function GetFontOrientation: single;
122 procedure TextOutBidiOverride(ADest: TBGRACustomBitmap; x, y: single; sUTF8: string; ARightToLeft: boolean);
123 function AddOverrideIfNecessary(var sUTF8: string; ARightToLeft: boolean): boolean;
124
125 procedure AddPart(AStartIndex, AEndIndex: integer; ABidiLevel: byte; ARectF: TRectF; APosCorrection: TPointF; ASUTF8: string; ABrokenLineIndex: integer);
126 function GetPartStartCaret(APartIndex: integer): TBidiCaretPos;
127 function GetPartEndCaret(APartIndex: integer): TBidiCaretPos;
128
129 procedure AnalyzeLineStart(ADefaultRTL: boolean);
130 function GetSameLevelString(startIndex,endIndex: integer): string; overload;
131 function GetSameLevelString(startIndex,endIndex: integer; out nonRemovedCount: integer): string; overload;
132 procedure LevelSize(AMaxWidth: single; startIndex, endIndex: integer; bidiLevel: byte; out ASplitIndex: integer; out AWidth, AHeight: single);
133 procedure ComputeLevelLayout(APos: TPointF; startIndex,
134 endIndex: integer; bidiLevel: byte; fullHeight, baseLine: single; brokenLineIndex: integer;
135 out AWidth: single);
136 procedure Init; virtual;
137 procedure ComputeLayout; virtual;
138 procedure NeedLayout;
139 procedure InternalDrawText(ADest: TBGRACustomBitmap);
140 procedure AnalyzeText;
141 public
142 constructor Create(AFontRenderer: TBGRACustomFontRenderer; sUTF8: string); overload;
143 constructor Create(AFontRenderer: TBGRACustomFontRenderer; sUTF8: string; ARightToLeft: boolean); overload;
144 constructor Create(AFontRenderer: TBGRACustomFontRenderer; sUTF8: string; AFontBidiMode: TFontBidiMode); overload;
145 procedure SetLayout(ARect: TRectF);
146 procedure InvalidateLayout;
147
148 procedure DrawText(ADest: TBGRACustomBitmap); overload;
149 procedure DrawText(ADest: TBGRACustomBitmap; AColor: TBGRAPixel); overload;
150 procedure DrawText(ADest: TBGRACustomBitmap; ATexture: IBGRAScanner); overload;
151 procedure DrawCaret(ADest: TBGRACustomBitmap; ACharIndex: integer; AMainColor, ASecondaryColor: TBGRAPixel);
152 procedure DrawSelection(ADest: TBGRACustomBitmap; AStartIndex, AEndIndex: integer; AColor: TBGRAPixel);
153
154 function GetCaret(ACharIndex: integer): TBidiCaretPos;
155 function GetCharIndexAt(APosition: TPointF): integer;
156 function GetTextEnveloppe(AStartIndex, AEndIndex: integer; APixelCenteredCoordinates: boolean = true): ArrayOfTPointF;
157 function GetParagraphAt(ACharIndex: Integer): integer;
158
159 function InsertText(ATextUTF8: string; APosition: integer): integer;
160 function InsertLineSeparator(APosition: integer): integer;
161 function DeleteText(APosition, ACount: integer): integer;
162 function DeleteTextBefore(APosition, ACount: integer): integer;
163 function CopyText(APosition, ACount: integer): string;
164 function CopyTextBefore(APosition, ACount: integer): string;
165 function IncludeNonSpacingChars(APosition, ACount: integer): integer;
166 function IncludeNonSpacingCharsBefore(APosition, ACount: integer): integer;
167
168 property CharCount: integer read FCharCount;
169 property UTF8Char[APosition0: integer]: string4 read GetUTF8Char;
170 property UnicodeChar[APosition0: integer]: cardinal read GetUnicodeChar;
171
172 property BrokenLineCount: integer read GetBrokenLineCount;
173 property BrokenLineParagraphIndex[AIndex: integer]: integer read GetBrokenLineParagraphIndex;
174 property BrokenLineStartIndex[AIndex: integer]: integer read GetBrokenLineStartIndex;
175 property BrokenLineEndIndex[AIndex: integer]: integer read GetBrokenLineEndIndex;
176 property BrokenLineRectF[AIndex: integer]: TRectF read GetBrokenLineRectF;
177 property BrokenLineAffineBox[AIndex: integer]: TAffineBox read GetBrokenLineAffineBox;
178 property BrokenLineRightToLeft[AIndex: integer]: boolean read GetBrokenLineRightToLeft;
179 property BrokenLineStartCaret[AIndex: integer]: TBidiCaretPos read GetBrokenLineStartCaret;
180 property BrokenLineEndCaret[AIndex: integer]: TBidiCaretPos read GetBrokenLineEndCaret;
181
182 property PartCount: integer read GetPartCount;
183 property PartStartIndex[AIndex: integer]: integer read GetPartStartIndex;
184 property PartEndIndex[AIndex: integer]: integer read GetPartEndIndex;
185 property PartBrokenLineIndex[AIndex: integer]: integer read GetPartBrokenLineIndex;
186 property PartStartCaret[AIndex: integer]: TBidiCaretPos read GetPartStartCaret;
187 property PartEndCaret[AIndex: integer]: TBidiCaretPos read GetPartEndCaret;
188 property PartRectF[AIndex: integer]: TRectF read GetPartRectF;
189 property PartAffineBox[AIndex: integer]: TAffineBox read GetPartAffineBox;
190 property PartRightToLeft[AIndex: integer]: boolean read GetPartRightToLeft;
191
192 property TopLeft: TPointF read FTopLeft write SetTopLeft;
193 property AvailableWidth: single read FAvailableWidth write SetAvailableWidth;
194 property AvailableHeight: single read FAvailableHeight write SetAvailableHeight;
195 property TabSize: single read FTabSize write SetTabSize;
196 property ParagraphSpacingAbove: single read FParagraphSpacingAbove write SetParagraphSpacingAbove;
197 property ParagraphSpacingBelow: single read FParagraphSpacingBelow write SetParagraphSpacingBelow;
198 property ParagraphRectF[AIndex: integer]: TRectF read GetParagraphRectF;
199 property ParagraphAffineBox[AIndex: integer]: TAffineBox read GetParagraphAffineBox;
200 property ParagraphAlignment[AIndex: integer]: TBidiTextAlignment read GetParagraphAlignment write SetParagraphAlignment;
201 property ParagraphStartIndex[AIndex: integer]: integer read GetParagraphStartIndex;
202 property ParagraphEndIndex[AIndex: integer]: integer read GetParagraphEndIndex;
203 property ParagraphEndIndexBeforeParagraphSeparator[AIndex: integer]: integer read GetParagraphEndIndexBeforeParagraphSeparator;
204 property ParagraphRightToLeft[AIndex: integer]: boolean read GetParagraphRightToLeft;
205 property ParagraphCount: integer read FParagraphCount;
206
207 property TotalTextHeight: single read GetTotalTextHeight;
208
209 property Matrix: TAffineMatrix read GetMatrix;
210 property MatrixInverse: TAffineMatrix read GetMatrixInverse;
211 property TextUTF8: string read FText;
212 property WordBreakHandler: TWordBreakHandler read FWordBreakHandler write FWordBreakHandler;
213
214 property FontRenderer: TBGRACustomFontRenderer read FRenderer write SetFontRenderer;
215 property FontBidiMode: TFontBidiMode read FFontBidiMode write SetFontBidiMode;
216 end;
217
218implementation
219
220uses math;
221
222{ TBidiTextLayout }
223
224function TBidiTextLayout.GetBrokenLineAffineBox(AIndex: integer): TAffineBox;
225begin
226 NeedLayout;
227 if (AIndex < 0) or (AIndex >= FBrokenLineCount) then
228 raise ERangeError.Create('Invalid index');
229 result := Matrix*TAffineBox.AffineBox(FBrokenLine[AIndex].rectF);
230end;
231
232function TBidiTextLayout.GetBrokenLineCount: integer;
233begin
234 NeedLayout;
235 result := FBrokenLineCount;
236end;
237
238function TBidiTextLayout.GetBrokenLineEndCaret(AIndex: integer): TBidiCaretPos;
239begin
240 NeedLayout;
241 with BrokenLineAffineBox[AIndex] do
242 begin
243 if BrokenLineRightToLeft[AIndex] then
244 result.Top := TopLeft
245 else
246 result.Top := TopRight;
247 result.Bottom := result.Top + (BottomLeft-TopLeft);
248 result.RightToLeft := odd(FBrokenLine[AIndex].bidiLevel);
249 result.PartIndex := -1;
250 result.PreviousTop := EmptyPointF;
251 result.PreviousBottom := EmptyPointF;
252 result.PreviousRightToLeft := result.RightToLeft;
253 end;
254end;
255
256function TBidiTextLayout.GetBrokenLineEndIndex(AIndex: integer): integer;
257begin
258 NeedLayout;
259 if (AIndex < 0) or (AIndex >= FBrokenLineCount) then
260 raise ERangeError.Create('Invalid index');
261 result := FBrokenLine[AIndex].endIndex;
262end;
263
264function TBidiTextLayout.GetBrokenLineParagraphIndex(AIndex: integer): integer;
265begin
266 NeedLayout;
267 if (AIndex < 0) or (AIndex >= FBrokenLineCount) then
268 raise ERangeError.Create('Invalid index');
269 result := FUnbrokenLine[FBrokenLine[AIndex].unbrokenLineIndex].paragraphIndex;
270end;
271
272function TBidiTextLayout.GetBrokenLineRectF(AIndex: integer): TRectF;
273begin
274 NeedLayout;
275 if (AIndex < 0) or (AIndex >= FBrokenLineCount) then
276 raise ERangeError.Create('Invalid index');
277 result := FBrokenLine[AIndex].rectF;
278end;
279
280function TBidiTextLayout.GetBrokenLineRightToLeft(AIndex: integer): boolean;
281begin
282 NeedLayout;
283 if (AIndex < 0) or (AIndex >= FBrokenLineCount) then
284 raise ERangeError.Create('Invalid index');
285 result := odd(FBrokenLine[AIndex].bidiLevel);
286end;
287
288function TBidiTextLayout.GetBrokenLineStartCaret(AIndex: integer): TBidiCaretPos;
289begin
290 NeedLayout;
291 with BrokenLineAffineBox[AIndex] do
292 begin
293 if BrokenLineRightToLeft[AIndex] then
294 result.Top := TopRight
295 else
296 result.Top := TopLeft;
297 result.Bottom := result.Top + (BottomLeft-TopLeft);
298 result.RightToLeft := odd(FBrokenLine[AIndex].bidiLevel);
299 result.PartIndex:= -1;
300 result.PreviousTop := EmptyPointF;
301 result.PreviousBottom := EmptyPointF;
302 result.PreviousRightToLeft := result.RightToLeft;
303 end;
304end;
305
306function TBidiTextLayout.GetBrokenLineStartIndex(AIndex: integer): integer;
307begin
308 NeedLayout;
309 if (AIndex < 0) or (AIndex >= FBrokenLineCount) then
310 raise ERangeError.Create('Invalid index');
311 result := FBrokenLine[AIndex].startIndex;
312end;
313
314function TBidiTextLayout.GetMatrix: TAffineMatrix;
315begin
316 NeedLayout;
317 result := FMatrix;
318end;
319
320function TBidiTextLayout.GetMatrixInverse: TAffineMatrix;
321begin
322 NeedLayout;
323 result := FMatrixInverse;
324end;
325
326function TBidiTextLayout.GetParagraphAffineBox(AIndex: integer): TAffineBox;
327begin
328 NeedLayout;
329 if (AIndex < 0) or (AIndex >= FParagraphCount) then
330 raise ERangeError.Create('Invalid index');
331 result := Matrix*TAffineBox.AffineBox(FParagraph[AIndex].rectF);
332end;
333
334function TBidiTextLayout.GetParagraphAlignment(AIndex: integer): TBidiTextAlignment;
335begin
336 if (AIndex < 0) or (AIndex >= FParagraphCount) then
337 raise ERangeError.Create('Invalid index');
338 result := FParagraph[AIndex].alignment;
339end;
340
341function TBidiTextLayout.GetParagraphEndIndex(AIndex: integer): integer;
342begin
343 if (AIndex < 0) or (AIndex >= FParagraphCount) then
344 raise ERangeError.Create('Invalid index');
345 result := FUnbrokenLine[FParagraph[AIndex].firstUnbrokenLineIndex+1].startIndex;
346end;
347
348function TBidiTextLayout.GetParagraphEndIndexBeforeParagraphSeparator(AIndex: integer): integer;
349var
350 u: LongWord;
351begin
352 result := GetParagraphEndIndex(AIndex);
353 u := UnicodeChar[result-1];
354 if (result>0) and IsUnicodeParagraphSeparator(u) then
355 begin
356 dec(result);
357 if IsUnicodeCrLf(u) and (result>0) and IsUnicodeCrLf(UnicodeChar[result-1]) and
358 (UnicodeChar[result-1] <> u) then dec(result);
359 end;
360end;
361
362function TBidiTextLayout.GetParagraphRectF(AIndex: integer): TRectF;
363begin
364 NeedLayout;
365 if (AIndex < 0) or (AIndex >= FParagraphCount) then
366 raise ERangeError.Create('Invalid index');
367 result := FParagraph[AIndex].rectF;
368end;
369
370function TBidiTextLayout.GetParagraphRightToLeft(AIndex: integer): boolean;
371begin
372 if (AIndex < 0) or (AIndex >= FParagraphCount) then
373 raise ERangeError.Create('Invalid index');
374 result := FParagraph[AIndex].rtl;
375end;
376
377function TBidiTextLayout.GetParagraphStartIndex(AIndex: integer): integer;
378begin
379 if (AIndex < 0) or (AIndex >= FParagraphCount) then
380 raise ERangeError.Create('Invalid index');
381 result := FUnbrokenLine[FParagraph[AIndex].firstUnbrokenLineIndex].startIndex;
382end;
383
384function TBidiTextLayout.GetPartAffineBox(AIndex: integer): TAffineBox;
385begin
386 NeedLayout;
387 if (AIndex < 0) or (AIndex >= FPartCount) then
388 raise ERangeError.Create('Invalid index');
389 result := Matrix*TAffineBox.AffineBox(FPart[AIndex].rectF);
390end;
391
392function TBidiTextLayout.GetPartBrokenLineIndex(AIndex: integer): integer;
393begin
394 NeedLayout;
395 if (AIndex < 0) or (AIndex >= FPartCount) then
396 raise ERangeError.Create('Invalid index');
397 result := FPart[AIndex].brokenLineIndex;
398end;
399
400function TBidiTextLayout.GetPartCount: integer;
401begin
402 NeedLayout;
403 result := FPartCount;
404end;
405
406function TBidiTextLayout.GetPartEndIndex(AIndex: integer): integer;
407begin
408 NeedLayout;
409 if (AIndex < 0) or (AIndex >= FPartCount) then
410 raise ERangeError.Create('Invalid index');
411 result := FPart[AIndex].endIndex;
412end;
413
414function TBidiTextLayout.GetPartRectF(AIndex: integer): TRectF;
415begin
416 NeedLayout;
417 if (AIndex < 0) or (AIndex >= FPartCount) then
418 raise ERangeError.Create('Invalid index');
419 result := FPart[AIndex].rectF;
420end;
421
422function TBidiTextLayout.GetPartRightToLeft(AIndex: integer): boolean;
423begin
424 NeedLayout;
425 if (AIndex < 0) or (AIndex >= FPartCount) then
426 raise ERangeError.Create('Invalid index');
427 result := odd(FPart[AIndex].bidiLevel);
428end;
429
430function TBidiTextLayout.GetPartStartIndex(AIndex: integer): integer;
431begin
432 NeedLayout;
433 if (AIndex < 0) or (AIndex >= FPartCount) then
434 raise ERangeError.Create('Invalid index');
435 result := FPart[AIndex].startIndex;
436end;
437
438function TBidiTextLayout.GetTotalTextHeight: single;
439begin
440 NeedLayout;
441 result := FParagraph[FParagraphCount-1].rectF.Bottom - FParagraph[0].rectF.Top;
442end;
443
444function TBidiTextLayout.GetUnicodeChar(APosition0: integer): cardinal;
445var p : PChar;
446 charLen: Integer;
447begin
448 if (APosition0 < 0) or (APosition0 >= CharCount) then
449 raise ERangeError.Create('Invalid position');
450 p := @FText[FBidi[APosition0].Offset+1];
451 charLen := UTF8CharacterLength(p);
452 result := UTF8CodepointToUnicode(p, charLen);
453end;
454
455function TBidiTextLayout.GetUTF8Char(APosition0: integer): string4;
456begin
457 if (APosition0 < 0) or (APosition0 >= CharCount) then
458 raise ERangeError.Create('Invalid position');
459
460 result := copy(FText, FBidi[APosition0].Offset+1, FBidi[APosition0+1].Offset-FBidi[APosition0].Offset);
461end;
462
463procedure TBidiTextLayout.SetAvailableHeight(AValue: single);
464begin
465 if FAvailableHeight=AValue then Exit;
466 FAvailableHeight:=AValue;
467 FLayoutComputed:= false;
468end;
469
470procedure TBidiTextLayout.SetAvailableWidth(AValue: single);
471begin
472 if FAvailableWidth=AValue then Exit;
473 FAvailableWidth:=AValue;
474 FLayoutComputed:= false;
475end;
476
477procedure TBidiTextLayout.SetFontBidiMode(AValue: TFontBidiMode);
478begin
479 if FFontBidiMode=AValue then Exit;
480 FFontBidiMode:=AValue;
481 AnalyzeText;
482end;
483
484procedure TBidiTextLayout.SetFontRenderer(AValue: TBGRACustomFontRenderer);
485begin
486 if FRenderer=AValue then Exit;
487 FRenderer:=AValue;
488 FLayoutComputed:= false;
489end;
490
491procedure TBidiTextLayout.SetParagraphAlignment(AIndex: integer;
492 AValue: TBidiTextAlignment);
493begin
494 if (AIndex < 0) or (AIndex >= FParagraphCount) then
495 raise ERangeError.Create('Invalid index');
496 FParagraph[AIndex].alignment := AValue;
497 FLayoutComputed:= false;
498end;
499
500procedure TBidiTextLayout.SetParagraphSpacingAbove(AValue: single);
501begin
502 if FParagraphSpacingAbove=AValue then Exit;
503 FParagraphSpacingAbove:=AValue;
504 FLayoutComputed:= false;
505end;
506
507procedure TBidiTextLayout.SetParagraphSpacingBelow(AValue: single);
508begin
509 if FParagraphSpacingBelow=AValue then Exit;
510 FParagraphSpacingBelow:=AValue;
511 FLayoutComputed:= false;
512end;
513
514procedure TBidiTextLayout.SetTabSize(AValue: single);
515begin
516 if FTabSize=AValue then Exit;
517 FTabSize:=AValue;
518 FLayoutComputed:= false;
519end;
520
521procedure TBidiTextLayout.SetTopLeft(AValue: TPointF);
522begin
523 if FTopLeft=AValue then Exit;
524 FTopLeft:=AValue;
525 if FLayoutComputed then ComputeMatrix;
526end;
527
528procedure TBidiTextLayout.ComputeMatrix;
529begin
530 FMatrix := AffineMatrixTranslation(FTopLeft.x, FTopLeft.y)*AffineMatrixRotationDeg(-GetFontOrientation);
531 FMatrixInverse := AffineMatrixInverse(FMatrix);
532end;
533
534function TBidiTextLayout.TextSizeBidiOverride(sUTF8: string;
535 ARightToLeft: boolean): TPointF;
536begin
537 AddOverrideIfNecessary(sUTF8, ARightToLeft);
538
539 with FRenderer.TextSizeAngle(sUTF8, FRenderer.FontOrientation) do
540 result := PointF(cx, cy);
541end;
542
543function TBidiTextLayout.TextSizeBidiOverrideSplit(AStartIndex, AEndIndex: integer;
544 ARightToLeft: boolean; ASplitIndex: integer): TPointF;
545var nextIndex, prevIndex: integer;
546 s: String;
547 extraS: string4;
548 extraW, combW: Single;
549 charClass: TUnicodeBidiClass;
550begin
551 if ASplitIndex = 0 then
552 begin
553 s := copy(FText, FBidi[AStartIndex].Offset+1, FBidi[AEndIndex].Offset-FBidi[AStartIndex].Offset);
554 result := TextSizeBidiOverride(s, ARightToLeft);
555 result.x := 0;
556 exit;
557 end;
558
559 s := copy(FText, FBidi[AStartIndex].Offset+1, FBidi[ASplitIndex].Offset-FBidi[AStartIndex].Offset);
560 result := TextSizeBidiOverride(s, ARightToLeft);
561
562 nextIndex := ASplitIndex;
563 //check if there might be a ligature
564 if (nextIndex < AEndIndex) and (GetUnicodeBidiClass(GetUnicodeChar(nextIndex)) in [ubcRightToLeft,ubcArabicLetter,ubcLeftToRight,ubcArabicNumber,ubcEuropeanNumber]) then
565 begin
566 inc(nextIndex);
567 //find previous letter
568 prevIndex := ASplitIndex-1;
569 while (prevIndex > AStartIndex) and (GetUnicodeBidiClass(GetUnicodeChar(prevIndex)) = ubcNonSpacingMark) do dec(prevIndex);
570 charClass := GetUnicodeBidiClass(GetUnicodeChar(prevIndex));
571 //arabic ligatures are asymmetric in size so use the tatweel to measure the actual size
572 if charClass = ubcArabicLetter then
573 begin
574 //measure tatweel size
575 extraS := UnicodeCharToUTF8(UNICODE_ARABIC_TATWEEL);
576 extraW := TextSizeBidiOverride(extraS, ARightToLeft).x;
577 combW := TextSizeBidiOverride(s+extraS, ARightToLeft).x;
578 result.x := combW - extraW; //subtract the size of the tatweel (which itself is not included in the ligature)
579 end else
580 // otherwise, assume that the ligature is symmetric so subtract half of the ligature size
581 begin
582 //measure the next char on its own
583 while (nextIndex < AEndIndex) and (GetUnicodeBidiClass(GetUnicodeChar(nextIndex)) = ubcNonSpacingMark) do inc(nextIndex);
584 extraS := copy(FText, FBidi[ASplitIndex].Offset+1, FBidi[nextIndex].Offset-FBidi[ASplitIndex].Offset);
585 extraW := TextSizeBidiOverride(extraS, ARightToLeft).x;
586
587 combW := TextSizeBidiOverride(s+extraS, ARightToLeft).x;
588 if combW < result.x then result.x := combW
589 else result.x -= (result.x+extraW - combW) * 0.5;
590 end;
591 end;
592end;
593
594function TBidiTextLayout.TextFitInfoBidiOverride(sUTF8: string; AWidth: single;
595 ARightToLeft: boolean): integer;
596var
597 over: Boolean;
598begin
599 over := AddOverrideIfNecessary(sUTF8, ARightToLeft);
600
601 result := FRenderer.TextFitInfo(sUTF8, round(AWidth));
602 if over then dec(result);
603end;
604
605function TBidiTextLayout.GetFontFullHeight: single;
606begin
607 result := FRenderer.TextSizeAngle('Hg', FRenderer.FontOrientation).cy;
608end;
609
610function TBidiTextLayout.GetFontBaseline: single;
611begin
612 result := FRenderer.GetFontPixelMetric.Baseline;
613end;
614
615function TBidiTextLayout.GetFontOrientation: single;
616begin
617 result := FRenderer.FontOrientation*0.1;
618end;
619
620procedure TBidiTextLayout.TextOutBidiOverride(ADest: TBGRACustomBitmap; x, y: single; sUTF8: string; ARightToLeft: boolean);
621begin
622 if sUTF8 = #9 then exit;
623 AddOverrideIfNecessary(sUTF8, ARightToLeft);
624
625 if FTexture <> nil then
626 FRenderer.TextOut(ADest, x,y, sUTF8, FTexture, taLeftJustify, ARightToLeft)
627 else
628 FRenderer.TextOut(ADest, x,y, sUTF8, FColor, taLeftJustify, ARightToLeft);
629end;
630
631function TBidiTextLayout.AddOverrideIfNecessary(var sUTF8: string;
632 ARightToLeft: boolean): boolean;
633var
634 p: PChar;
635 pEnd: Pointer;
636 add, hasStrong: boolean;
637 charLen: Integer;
638 u: Cardinal;
639 curBidi: TUnicodeBidiClass;
640 isSpacing: boolean;
641begin
642 if sUTF8 = '' then exit(false);
643 isSpacing:= true;
644 p := @sUTF8[1];
645 pEnd := p + length(sUTF8);
646 add := false;
647 hasStrong := false;
648 while p < pEnd do
649 begin
650 charLen := UTF8CharacterLength(p);
651 if (charLen = 0) or (p+charLen > pEnd) then break;
652 u := UTF8CodepointToUnicode(p, charLen);
653 curBidi := GetUnicodeBidiClass(u);
654 if curBidi <> ubcWhiteSpace then isSpacing:= false;
655 if curBidi in[ubcLeftToRight,ubcRightToLeft,ubcArabicLetter] then
656 hasStrong := true;
657 if (curBidi = ubcLeftToRight) and ARightToLeft then
658 begin
659 add := true;
660 break;
661 end else
662 if (curBidi in[ubcRightToLeft,ubcArabicLetter]) and not ARightToLeft then
663 begin
664 add := true;
665 break;
666 end;
667 inc(p,charLen);
668 end;
669 if not hasStrong and ARightToLeft and not isSpacing then add := true;
670 if add then
671 begin
672 if ARightToLeft then
673 sUTF8 := UnicodeCharToUTF8(UNICODE_RIGHT_TO_LEFT_OVERRIDE)+ sUTF8
674 else
675 sUTF8 := UnicodeCharToUTF8(UNICODE_LEFT_TO_RIGHT_OVERRIDE)+ sUTF8;
676 exit(true);
677 end
678 else exit(false);
679end;
680
681procedure TBidiTextLayout.AddPart(AStartIndex, AEndIndex: integer;
682 ABidiLevel: byte; ARectF: TRectF; APosCorrection: TPointF; ASUTF8: string;
683 ABrokenLineIndex: integer);
684begin
685 if FPartCount >= length(FPart) then
686 setlength(FPart, length(FPart)*2+8);
687
688 with FPart[FPartCount] do
689 begin
690 startIndex:= AStartIndex;
691 endIndex:= AEndIndex;
692 bidiLevel := ABidiLevel;
693 rectF := ARectF;
694 posCorrection := APosCorrection;
695 sUTF8:= ASUTF8;
696 brokenLineIndex:= ABrokenLineIndex;
697 end;
698 inc(FPartCount)
699end;
700
701procedure TBidiTextLayout.AnalyzeLineStart(ADefaultRTL: boolean);
702var
703 lineIndex, i: Integer;
704 curParaIndex: integer;
705begin
706 FUnbrokenLineCount := 1;
707 FParagraphCount := 1;
708 for i := 0 to high(FBidi)-1 do
709 begin
710 if FBidi[i].BidiInfo.IsEndOfLine or FBidi[i].BidiInfo.IsEndOfParagraph then
711 begin
712 if FBidi[i].BidiInfo.IsEndOfParagraph then FParagraphCount += 1;
713 FUnbrokenLineCount += 1;
714 end;
715 end;
716
717 curParaIndex := 0;
718 lineIndex := 0;
719 setlength(FParagraph, FParagraphCount+1);
720 FParagraph[curParaIndex].firstUnbrokenLineIndex:= lineIndex;
721 FParagraph[curParaIndex].rectF:= rectF(0,0,0,0);
722 FParagraph[curParaIndex].rtl := ADefaultRTL;
723 setlength(FUnbrokenLine, FUnbrokenLineCount+1);
724 FUnbrokenLine[lineIndex].startIndex := 0;
725 FUnbrokenLine[lineIndex].paragraphIndex := curParaIndex;
726 inc(lineIndex);
727 for i := 0 to high(FBidi)-1 do
728 begin
729 FParagraph[curParaIndex].rtl := odd(FBidi[i].BidiInfo.ParagraphBidiLevel);
730 if FBidi[i].BidiInfo.IsEndOfLine or FBidi[i].BidiInfo.IsEndOfParagraph then
731 begin
732 if FBidi[i].BidiInfo.IsEndOfParagraph then
733 begin
734 curParaIndex += 1;
735 FParagraph[curParaIndex].firstUnbrokenLineIndex:= lineIndex;
736 FParagraph[curParaIndex].rectF := rectF(0,0,0,0);
737 FParagraph[curParaIndex].rtl := ADefaultRTL;
738 end;
739 FUnbrokenLine[lineIndex].startIndex := i+1;
740 FUnbrokenLine[lineIndex].paragraphIndex := curParaIndex;
741 inc(lineIndex);
742 end;
743 end;
744 FParagraph[curParaIndex+1].firstUnbrokenLineIndex:= lineIndex;
745 FParagraph[curParaIndex+1].rectF:= rectF(0,0,0,0);
746 FParagraph[curParaIndex+1].rtl := ADefaultRTL;
747 FUnbrokenLine[lineIndex].startIndex := length(FBidi);
748 FUnbrokenLine[lineIndex].paragraphIndex:= curParaIndex+1;
749
750 for i := 0 to high(FParagraph) do
751 FParagraph[i].alignment:= btaNatural;
752
753 setlength(FBidi, length(FBidi)+1);
754 FBidi[High(FBidi)].Offset := length(FText);
755end;
756
757function TBidiTextLayout.GetSameLevelString(startIndex, endIndex: integer): string;
758var
759 nonRemovedCount: integer;
760begin
761 result := GetSameLevelString(startIndex,endIndex,nonRemovedCount);
762end;
763
764function TBidiTextLayout.GetSameLevelString(startIndex, endIndex: integer; out nonRemovedCount: integer): string;
765var i, len, charLen: integer;
766begin
767 nonRemovedCount:= 0;
768 len := 0;
769 for i := startIndex to endIndex-1 do
770 if not FBidi[i].BidiInfo.IsRemoved then
771 begin
772 inc(len, FBidi[i+1].Offset - FBidi[i].Offset);
773 inc(nonRemovedCount);
774 end;
775
776 setlength(result, len);
777 len := 0;
778 for i := startIndex to endIndex-1 do
779 if not FBidi[i].BidiInfo.IsRemoved then
780 begin
781 charLen := FBidi[i+1].Offset - FBidi[i].Offset;
782 move(FText[FBidi[i].Offset+1], result[len+1], charLen);
783 inc(len, charLen);
784 end;
785end;
786
787procedure TBidiTextLayout.LevelSize(AMaxWidth: single; startIndex,
788 endIndex: integer; bidiLevel: byte; out ASplitIndex: integer; out AWidth,
789 AHeight: single);
790var
791 i: Integer;
792 subLevel: byte;
793 subStart, subSplit, fitInfo, nonRemovedCount: integer;
794 subStr: string;
795 w,h: single;
796 splitting: boolean;
797 subSize: TPointF;
798begin
799 AWidth := 0;
800 AHeight := 0;
801 ASplitIndex:= endIndex;
802
803 while (startIndex < endIndex) and FBidi[startIndex].BidiInfo.IsRemoved do inc(startIndex);
804 while (startIndex < endIndex) and FBidi[endIndex-1].BidiInfo.IsRemoved do dec(endIndex);
805 if endIndex = startIndex then exit;
806
807 i := startIndex;
808 while i < endIndex do
809 begin
810 if not FBidi[i].BidiInfo.IsRemoved then
811 begin
812 if FBidi[i].BidiInfo.BidiLevel > bidiLevel then
813 begin
814 subStart := i;
815 subLevel := FBidi[i].BidiInfo.BidiLevel;
816 inc(i);
817 while (i < endIndex) and (FBidi[i].BidiInfo.BidiLevel > bidiLevel) do
818 begin
819 if FBidi[i].BidiInfo.BidiLevel < subLevel then
820 subLevel := FBidi[i].BidiInfo.BidiLevel;
821 inc(i);
822 end;
823
824 if AMaxWidth <> EmptySingle then
825 LevelSize(AMaxWidth - AWidth, subStart, i, subLevel, subSplit, w, h)
826 else
827 LevelSize(AMaxWidth, subStart, i, subLevel, subSplit, w, h);
828 AWidth += w;
829 if h > AHeight then AHeight := h;
830
831 if subSplit < i then
832 begin
833 ASplitIndex := subSplit;
834 exit;
835 end;
836 end else
837 begin
838 subStart:= i;
839 inc(i);
840 while (i < endIndex) and (FBidi[i].BidiInfo.BidiLevel = bidiLevel) do inc(i);
841
842 subStr := GetSameLevelString(subStart,i, nonRemovedCount);
843 if AMaxWidth <> EmptySingle then
844 begin
845 fitInfo := TextFitInfoBidiOverride(subStr, AMaxWidth - AWidth, odd(bidiLevel));
846 if fitInfo < nonRemovedCount then
847 begin
848 ASplitIndex:= subStart;
849 while fitInfo > 0 do
850 begin
851 while (ASplitIndex < CharCount) and FBidi[ASplitIndex].BidiInfo.IsRemoved do
852 Inc(ASplitIndex);
853 if ASplitIndex < CharCount then inc(ASplitIndex);
854 dec(fitInfo);
855 end;
856 subStr := GetSameLevelString(subStart,ASplitIndex);
857 splitting := true;
858 end else
859 splitting := false;
860 end else
861 splitting := false;
862
863 subSize := TextSizeBidiOverride(subStr, odd(bidiLevel));
864 w := subSize.x;
865 h := subSize.y;
866 AWidth += w;
867 if h > AHeight then AHeight:= h;
868
869 if splitting then exit;
870 end;
871
872 end else
873 inc(i);
874 end;
875end;
876
877
878constructor TBidiTextLayout.Create(AFontRenderer: TBGRACustomFontRenderer; sUTF8: string);
879begin
880 Init;
881 FRenderer := AFontRenderer;
882 FText:= sUTF8;
883 FFontBidiMode:= fbmAuto;
884 AnalyzeText;
885end;
886
887constructor TBidiTextLayout.Create(AFontRenderer: TBGRACustomFontRenderer; sUTF8: string; ARightToLeft: boolean);
888begin
889 Init;
890 FRenderer := AFontRenderer;
891 FText:= sUTF8;
892 if ARightToLeft then
893 FFontBidiMode:= fbmRightToLeft
894 else
895 FFontBidiMode:= fbmLeftToRight;
896 AnalyzeText;
897end;
898
899constructor TBidiTextLayout.Create(AFontRenderer: TBGRACustomFontRenderer;
900 sUTF8: string; AFontBidiMode: TFontBidiMode);
901begin
902 Init;
903 FRenderer := AFontRenderer;
904 FText:= sUTF8;
905 FFontBidiMode:= AFontBidiMode;
906 AnalyzeText;
907end;
908
909procedure TBidiTextLayout.SetLayout(ARect: TRectF);
910begin
911 TopLeft := ARect.TopLeft;
912 AvailableWidth:= ARect.Width;
913 AvailableHeight:= ARect.Height;
914end;
915
916procedure TBidiTextLayout.InvalidateLayout;
917begin
918 FLayoutComputed:= false;
919end;
920
921procedure TBidiTextLayout.DrawText(ADest: TBGRACustomBitmap);
922begin
923 DrawText(ADest, BGRABlack);
924end;
925
926procedure TBidiTextLayout.DrawText(ADest: TBGRACustomBitmap; AColor: TBGRAPixel);
927begin
928 FColor := AColor;
929 InternalDrawText(ADest);
930end;
931
932procedure TBidiTextLayout.DrawText(ADest: TBGRACustomBitmap;
933 ATexture: IBGRAScanner);
934begin
935 FColor := BGRAWhite;
936 FTexture := ATexture;
937 InternalDrawText(ADest);
938 FTexture := nil;
939end;
940
941procedure TBidiTextLayout.ComputeLayout;
942var w,h, lineHeight, baseLine, tabPixelSize: single;
943 paraIndex, i, j, nextTabIndex, splitIndex: Integer;
944 lineStart, subStart, lineEnd: integer;
945 paraSpacingAbove, paraSpacingBelow, correctedBaseLine: single;
946 paraRTL, needNewLine: boolean;
947 partStr, remainStr: string;
948 pos: TPointF;
949 curBidiPos,endBidiPos,nextTabBidiPos, availWidth0: single;
950 tabSectionStart, tabSectionCount: integer;
951 tabSection: array of record
952 startIndex, endIndex: integer;
953 bidiPos: single;
954 end;
955 alignment: TAlignment;
956 paraBidiLevel: Byte;
957 r: TRectF;
958 u: LongWord;
959
960 procedure AddTabSection(startIndex,endIndex: integer);
961 begin
962 if tabSectionCount >= length(tabSection) then setlength(tabSection, length(tabSection)*2+4);
963 tabSection[tabSectionCount].startIndex:= startIndex;
964 tabSection[tabSectionCount].endIndex:= endIndex;
965 tabSection[tabSectionCount].bidiPos:= curBidiPos;
966 inc(tabSectionCount);
967 end;
968
969begin
970 FLineHeight:= GetFontFullHeight;
971 baseLine := GetFontBaseline;
972 ComputeMatrix;
973 FPartCount := 0;
974 FBrokenLineCount := 0;
975
976 FLayoutComputed:= true;
977
978 paraSpacingAbove := ParagraphSpacingAbove*FLineHeight;
979 paraSpacingBelow := ParagraphSpacingBelow*FLineHeight;
980 pos := PointF(0,0);
981
982 tabPixelSize := TabSize*TextSizeBidiOverride(' ',False).x;
983 tabSection := nil;
984
985 for paraIndex := 0 to FParagraphCount-1 do
986 begin
987 FParagraph[paraIndex].rectF.Top := pos.y;
988 FParagraph[paraIndex].rectF.Bottom := pos.y;
989 if FAvailableWidth <> EmptySingle then
990 begin
991 FParagraph[paraIndex].rectF.Left := 0;
992 FParagraph[paraIndex].rectF.Right := FAvailableWidth;
993 end else
994 begin
995 FParagraph[paraIndex].rectF.Left := EmptySingle;
996 FParagraph[paraIndex].rectF.Right := EmptySingle;
997 end;
998 pos.y += paraSpacingAbove;
999
1000 for i := FParagraph[paraIndex].firstUnbrokenLineIndex to FParagraph[paraIndex+1].firstUnbrokenLineIndex-1 do
1001 begin
1002 lineStart := FUnbrokenLine[i].startIndex;
1003 lineEnd := FUnbrokenLine[i+1].startIndex;
1004
1005 if lineEnd > lineStart then
1006 begin
1007 u := UnicodeChar[lineEnd-1];
1008 case u of
1009 UNICODE_LINE_SEPARATOR, UNICODE_PARAGRAPH_SEPARATOR, UNICODE_NEXT_LINE: dec(lineEnd);
1010 13,10:
1011 begin
1012 dec(lineEnd);
1013 if lineEnd > lineStart then
1014 begin
1015 u := UnicodeChar[lineEnd-1];
1016 if (u = 13) or (u = 10) then dec(lineEnd);
1017 end;
1018 end;
1019 end;
1020 end;
1021 FUnbrokenLine[i].firstBrokenLineIndex:= FBrokenLineCount;
1022
1023 subStart := lineStart;
1024 //avoid warnings
1025 splitIndex := subStart;
1026 h := 0;
1027 w := 0;
1028
1029 //break lines
1030 while subStart < lineEnd do
1031 begin
1032 //split into sections according to tabs
1033 paraBidiLevel := FBidi[lineStart].BidiInfo.ParagraphBidiLevel;
1034 tabSectionCount := 0;
1035 curBidiPos := 0;
1036 tabSectionStart := subStart;
1037 tabSectionCount := 0;
1038 lineHeight := FLineHeight;
1039
1040 while tabSectionStart < lineEnd do
1041 begin
1042 needNewLine := false;
1043 while (tabSectionStart < lineEnd) and (FText[FBidi[tabSectionStart].Offset+1] = #9) do
1044 begin
1045 if tabPixelSize = 0 then inc(tabSectionStart)
1046 else
1047 begin
1048 nextTabBidiPos := tabPixelSize* (floor(curBidiPos / tabPixelSize + 1e-6)+1);
1049 if (FAvailableWidth = EmptySingle) or (nextTabBidiPos <= FAvailableWidth) or (tabSectionStart = subStart) then
1050 begin
1051 AddTabSection(tabSectionStart, tabSectionStart+1);
1052 inc(tabSectionStart);
1053 curBidiPos := nextTabBidiPos;
1054 end else
1055 begin
1056 //if tab is last char then go to the end of the line
1057 if tabSectionStart = lineEnd-1 then
1058 begin
1059 AddTabSection(tabSectionStart, splitIndex);
1060 inc(tabSectionStart);
1061 curBidiPos := FAvailableWidth;
1062 needNewLine := true;
1063 break;
1064 end
1065 else //otherwise a new line is needed before the tab
1066 begin
1067 needNewLine := true;
1068 break;
1069 end;
1070 end;
1071 end;
1072 end;
1073 if needNewLine then
1074 begin
1075 splitIndex:= tabSectionStart;
1076 break;
1077 end;
1078
1079 nextTabIndex := tabSectionStart;
1080 while (nextTabIndex < lineEnd) and (FText[FBidi[nextTabIndex].Offset+1] <> #9) do inc(nextTabIndex);
1081 LevelSize(FAvailableWidth - curBidiPos, tabSectionStart, nextTabIndex, paraBidiLevel, splitIndex, w,h);
1082
1083 AddTabSection(tabSectionStart, splitIndex);
1084
1085 if splitIndex < nextTabIndex then
1086 begin
1087 if (tabSectionCount = 1) and (splitIndex = tabSectionStart) then
1088 begin
1089 inc(splitIndex);
1090 while (splitIndex < nextTabIndex) and (GetUnicodeBidiClass(UnicodeChar[splitIndex]) = ubcNonSpacingMark) do inc(splitIndex);
1091 end;
1092 partStr := copy(FText, FBidi[tabSectionStart].Offset+1, FBidi[splitIndex].Offset - FBidi[tabSectionStart].Offset);
1093 remainStr := copy(FText, FBidi[splitIndex].Offset+1, FBidi[nextTabIndex].Offset - FBidi[splitIndex].Offset);
1094 if tabSectionCount > 1 then partStr := ' '+partStr;
1095 if Assigned(FWordBreakHandler) then
1096 FWordBreakHandler(partStr, remainStr)
1097 else
1098 BGRADefaultWordBreakHandler(partStr, remainStr);
1099 if tabSectionCount > 1 then delete(partStr,1,1);
1100
1101 splitIndex:= tabSectionStart + UTF8Length(partStr);
1102 LevelSize(EmptySingle, tabSectionStart, splitIndex, paraBidiLevel, splitIndex, w,h);
1103
1104 if splitIndex = tabSectionStart then
1105 begin
1106 dec(tabSectionCount);
1107
1108 //tabSectionStart stay the same
1109 end
1110 else
1111 begin
1112 //otherwise the section is split
1113 tabSection[tabSectionCount-1].endIndex:= splitIndex;
1114
1115 curBidiPos += w;
1116 if h > lineHeight then lineHeight := h;
1117 tabSectionStart := splitIndex;
1118 while (tabSectionStart < nextTabIndex) and (FText[FBidi[tabSectionStart].Offset+1] = ' ') do inc(tabSectionStart);
1119 end;
1120
1121 break;
1122 end else
1123 begin
1124 curBidiPos += w;
1125 if h > lineHeight then lineHeight := h;
1126 tabSectionStart := splitIndex;
1127 end;
1128 end;
1129
1130 // add broken line info
1131 paraRTL := FParagraph[paraIndex].rtl;
1132 pos.x := 0;
1133
1134 if FAvailableWidth <> EmptySingle then
1135 begin
1136 case FParagraph[paraIndex].alignment of
1137 btaNatural: if paraRTL then alignment := taRightJustify else alignment:= taLeftJustify;
1138 btaOpposite: if paraRTL then alignment := taLeftJustify else alignment:= taRightJustify;
1139 btaLeftJustify: alignment:= taLeftJustify;
1140 btaRightJustify: alignment:= taRightJustify;
1141 else {btaCenter:} alignment:= taCenter;
1142 end;
1143 end else
1144 alignment := taLeftJustify;
1145
1146 if FBrokenLineCount >= length(FBrokenLine) then
1147 setlength(FBrokenLine, length(FBrokenLine)*2+4);
1148 FBrokenLine[FBrokenLineCount].unbrokenLineIndex := i;
1149 FBrokenLine[FBrokenLineCount].startIndex:= subStart;
1150 FBrokenLine[FBrokenLineCount].endIndex:= splitIndex;
1151 FBrokenLine[FBrokenLineCount].bidiLevel := paraBidiLevel;
1152 FBrokenLine[FBrokenLineCount].firstPartIndex:= FPartCount;
1153 if FAvailableWidth <> EmptySingle then
1154 FBrokenLine[FBrokenLineCount].rectF := RectF(0,pos.y,FAvailableWidth,pos.y+lineHeight)
1155 else
1156 begin
1157 case alignment of
1158 taRightJustify: FBrokenLine[FBrokenLineCount].rectF := RectF(-w,pos.y,0,pos.y+lineHeight);
1159 taCenter: FBrokenLine[FBrokenLineCount].rectF := RectF(-w*0.5,pos.y,w*0.5,pos.y+lineHeight);
1160 else {taLeftJustify}
1161 FBrokenLine[FBrokenLineCount].rectF := RectF(0,pos.y,w,pos.y+lineHeight);
1162 end;
1163 end;
1164 FBrokenLineCount += 1;
1165
1166 if FAvailableWidth = EmptySingle then
1167 begin
1168 if FParagraph[paraIndex].rectF.Left = EmptySingle then
1169 begin
1170 FParagraph[paraIndex].rectF.Left := FBrokenLine[FBrokenLineCount-1].rectF.left;
1171 FParagraph[paraIndex].rectF.Right := FBrokenLine[FBrokenLineCount-1].rectF.Right;
1172 end else
1173 begin
1174 if FParagraph[paraIndex].rectF.Left < FBrokenLine[FBrokenLineCount-1].rectF.left then
1175 FParagraph[paraIndex].rectF.Left := FBrokenLine[FBrokenLineCount-1].rectF.left;
1176 if FParagraph[paraIndex].rectF.Right > FBrokenLine[FBrokenLineCount-1].rectF.Right then
1177 FParagraph[paraIndex].rectF.Right := FBrokenLine[FBrokenLineCount-1].rectF.Right;
1178 end;
1179 end;
1180
1181 subStart := tabSectionStart;
1182
1183 if FAvailableWidth <> EmptySingle then
1184 availWidth0 := FAvailableWidth
1185 else
1186 availWidth0:= 0;
1187
1188 case alignment of
1189 taRightJustify:
1190 if paraRTL then
1191 pos.x := availWidth0
1192 else
1193 pos.x := availWidth0 - curBidiPos;
1194 taCenter:
1195 if paraRTL then
1196 pos.x := (availWidth0 + curBidiPos)*0.5
1197 else
1198 pos.x := (availWidth0 - curBidiPos)*0.5;
1199 else {taLeftJustify}
1200 if paraRTL then
1201 pos.x := curBidiPos
1202 else
1203 pos.x := 0;
1204 end;
1205
1206 if FLineHeight <> 0 then
1207 correctedBaseLine := baseLine*lineHeight/FLineHeight
1208 else
1209 correctedBaseLine:= 0;
1210
1211 for j := 0 to tabSectionCount-1 do
1212 begin
1213 if (tabSection[j].endIndex = tabSection[j].startIndex+1) and
1214 (FText[FBidi[tabSection[j].startIndex].Offset+1] = #9) then
1215 begin
1216 if j = tabSectionCount-1 then
1217 endBidiPos:= curBidiPos
1218 else
1219 endBidiPos:= tabSection[j+1].bidiPos;
1220
1221 if paraRTL then
1222 r := RectF(pos.x-endBidiPos, pos.y, pos.x-tabSection[j].bidiPos, pos.y+lineHeight)
1223 else
1224 r := RectF(pos.x+tabSection[j].bidiPos, pos.y, pos.x+endBidiPos, pos.y+lineHeight);
1225
1226 AddPart(tabSection[j].startIndex, tabSection[j].endIndex, paraBidiLevel, r, PointF(0,0), #9, FBrokenLineCount-1);
1227
1228 end
1229 else
1230 begin
1231 if paraRTL then
1232 ComputeLevelLayout(pos - PointF(tabSection[j].bidiPos,0), tabSection[j].startIndex, tabSection[j].endIndex,
1233 paraBidiLevel, lineHeight, correctedBaseLine, FBrokenLineCount-1, w)
1234 else
1235 ComputeLevelLayout(pos + PointF(tabSection[j].bidiPos,0), tabSection[j].startIndex, tabSection[j].endIndex,
1236 paraBidiLevel, lineHeight, correctedBaseLine, FBrokenLineCount-1, w)
1237 end;
1238 end;
1239 FBrokenLine[FBrokenLineCount-1].lastPartIndexPlusOne:= FPartCount;
1240
1241 pos.y += lineHeight;
1242 if (FAvailableHeight <> EmptySingle) and (pos.y >= FAvailableHeight) then
1243 begin
1244 FParagraph[paraIndex].rectF.Bottom := pos.y;
1245 exit;
1246 end;
1247 end;
1248 end;
1249 pos.y += paraSpacingBelow;
1250 FParagraph[paraIndex].rectF.Bottom := pos.y;
1251 end;
1252 FUnbrokenLine[FUnbrokenLineCount].firstBrokenLineIndex:= FBrokenLineCount;
1253end;
1254
1255procedure TBidiTextLayout.NeedLayout;
1256begin
1257 if not FLayoutComputed then ComputeLayout;
1258end;
1259
1260procedure TBidiTextLayout.InternalDrawText(ADest: TBGRACustomBitmap);
1261var
1262 i: Integer;
1263begin
1264 NeedLayout;
1265 for i := 0 to FPartCount-1 do
1266 with (Matrix*(FPart[i].rectF.TopLeft + FPart[i].posCorrection)) do
1267 TextOutBidiOverride(ADest, x,y, FPart[i].sUTF8, odd(FPart[i].bidiLevel));
1268end;
1269
1270procedure TBidiTextLayout.AnalyzeText;
1271begin
1272 if FFontBidiMode <> fbmAuto then
1273 FBidi:= AnalyzeBidiUTF8(FText, FFontBidiMode = fbmRightToLeft)
1274 else
1275 FBidi:= AnalyzeBidiUTF8(FText);
1276
1277 FCharCount := length(FBidi);
1278 AnalyzeLineStart(FFontBidiMode = fbmRightToLeft);
1279 FLayoutComputed:= false;
1280end;
1281
1282procedure TBidiTextLayout.DrawCaret(ADest: TBGRACustomBitmap;
1283 ACharIndex: integer; AMainColor, ASecondaryColor: TBGRAPixel);
1284
1285 procedure DrawSingleCaret(ATop,ABottom: TPointF; ARightToLeft, AShowDir: boolean; AColor: TBGRAPixel);
1286 var u,v: TPointF;
1287 triSize,len: single;
1288 begin
1289 //hinting depending on orientation
1290 if abs(ATop.x - ABottom.x) < abs(ATop.y - ABottom.y) then
1291 begin
1292 ATop.x := round(ATop.x);
1293 ABottom.x := round(ABottom.x);
1294 end
1295 else
1296 begin
1297 ATop.y := round(ATop.y);
1298 ABottom.y := round(ABottom.y);
1299 end;
1300 u := ABottom-ATop;
1301 len := VectLen(u);
1302 if len > 0 then
1303 begin
1304 u := (1/len)*u;
1305 v := PointF(u.y,-u.x);
1306 if AShowDir then
1307 begin
1308 triSize := len*0.2;
1309 if ARightToLeft then
1310 ADest.FillPolyAntialias(PointsF([ABottom, ATop, ATop - triSize*v, ATop - v + triSize*u, ABottom - v]), AColor, false)
1311 else
1312 ADest.FillPolyAntialias(PointsF([ABottom, ATop, ATop + triSize*v, ATop + triSize*u + v, ABottom + v]), AColor, False)
1313 end
1314 else
1315 begin
1316 if len > 10 then
1317 begin
1318 if ARightToLeft then
1319 ADest.FillPolyAntialias(PointsF([ABottom, ATop, ATop - 2*v, ABottom - 2*v]), AColor, False)
1320 else
1321 ADest.FillPolyAntialias(PointsF([ABottom, ATop, ATop + 2*v, ABottom + 2*v]), AColor, False)
1322 end
1323 else
1324 begin
1325 if ARightToLeft then
1326 ADest.FillPolyAntialias(PointsF([ABottom, ATop, ATop - v, ABottom - v]), AColor, False)
1327 else
1328 ADest.FillPolyAntialias(PointsF([ABottom, ATop, ATop + v, ABottom + v]), AColor, False)
1329 end;
1330 end;
1331 end else
1332 ADest.DrawPixel(round(ATop.x),round(ATop.y), AColor);
1333 end;
1334
1335var
1336 caret: TBidiCaretPos;
1337 showDir: Boolean;
1338begin
1339 NeedLayout;
1340
1341 caret := GetCaret(ACharIndex);
1342 showDir := not isEmptyPointF(caret.PreviousTop) and (caret.RightToLeft <> caret.PreviousRightToLeft);
1343 if not isEmptyPointF(caret.Top) then DrawSingleCaret(caret.Top, caret.Bottom, caret.RightToLeft, showDir, AMainColor);
1344 if not isEmptyPointF(caret.PreviousTop) then DrawSingleCaret(caret.PreviousTop, caret.PreviousBottom, caret.PreviousRightToLeft, showDir, ASecondaryColor);
1345end;
1346
1347procedure TBidiTextLayout.DrawSelection(ADest: TBGRACustomBitmap; AStartIndex,
1348 AEndIndex: integer; AColor: TBGRAPixel);
1349var
1350 env: ArrayOfTPointF;
1351begin
1352 NeedLayout;
1353
1354 if AStartIndex = AEndIndex then exit;
1355 env := GetTextEnveloppe(AStartIndex,AEndIndex, False);
1356 ADest.FillPolyAntialias(env, AColor, False);
1357end;
1358
1359function TBidiTextLayout.GetCaret(ACharIndex: integer): TBidiCaretPos;
1360var
1361 i: Integer;
1362 w: Single;
1363begin
1364 NeedLayout;
1365
1366 if (ACharIndex < 0) or (ACharIndex > CharCount) then
1367 raise ERangeError.Create('Invalid index');
1368 result.PartIndex := -1;
1369 result.Top := EmptyPointF;
1370 result.Bottom := EmptyPointF;
1371 result.RightToLeft := false;
1372 result.PreviousTop := EmptyPointF;
1373 result.PreviousBottom := EmptyPointF;
1374 result.PreviousRightToLeft := false;
1375
1376 for i := 0 to FPartCount-1 do
1377 if ACharIndex <= FPart[i].startIndex then
1378 begin
1379 result := GetPartStartCaret(i);
1380 exit;
1381 end else
1382 if (ACharIndex > FPart[i].startIndex) and (ACharIndex <= FPart[i].endIndex) then
1383 begin
1384 if (i < FPartCount-1) and (ACharIndex = FPart[i+1].startIndex) then
1385 begin
1386 result := GetPartStartCaret(i+1);
1387 exit;
1388 end else
1389 begin
1390 if i = FPart[i].endIndex then
1391 begin
1392 result := GetPartEndCaret(i);
1393 exit;
1394 end else
1395 begin
1396 w := TextSizeBidiOverrideSplit(FPart[i].startIndex, FPart[i].endIndex, odd(FPart[i].bidiLevel), ACharIndex).x;
1397
1398 if Odd(FPart[i].bidiLevel) then
1399 result.Top := PointF(FPart[i].rectF.Right - w, FPart[i].rectF.Top)
1400 else result.Top := PointF(FPart[i].rectF.Left + w, FPart[i].rectF.Top);
1401 result.Bottom := result.Top + PointF(0,FPart[i].rectF.Height);
1402 result.Top := Matrix*result.Top;
1403 result.Bottom := Matrix*result.Bottom;
1404
1405 result.RightToLeft := odd(FPart[i].bidiLevel);
1406 result.PreviousRightToLeft := result.RightToLeft;
1407 result.PartIndex := i;
1408 end;
1409 exit;
1410 end;
1411 end;
1412
1413 if (PartCount > 0) and (ACharIndex >= FPart[PartCount-1].endIndex) then
1414 result := GetPartEndCaret(PartCount-1)
1415 else
1416 if ACharIndex = 0 then
1417 begin
1418 result.Top := FTopLeft;
1419 result.Bottom := FMatrix*PointF(0,FLineHeight);
1420 result.RightToLeft := false;
1421 result.PreviousTop := EmptyPointF;
1422 result.PreviousBottom := EmptyPointF;
1423 result.PreviousRightToLeft := false;
1424 result.PartIndex := 0;
1425 end;
1426end;
1427
1428function TBidiTextLayout.GetCharIndexAt(APosition: TPointF): integer;
1429var
1430 brokenLineIndex,j, fit: Integer;
1431 u,u2: cardinal;
1432 axis, origin: TPointF;
1433 len, w, curW, newW: Single;
1434 str: String;
1435 curIndex, newIndex, paraIndex: integer;
1436 untransformedPos: TPointF;
1437begin
1438 NeedLayout;
1439 untransformedPos := FMatrixInverse*APosition;
1440
1441 for paraIndex := 0 to ParagraphCount-1 do
1442 begin
1443 if untransformedPos.Y < FParagraph[paraIndex].rectF.Bottom then
1444 begin
1445 for brokenLineIndex := FUnbrokenLine[FParagraph[paraIndex].firstUnbrokenLineIndex].firstBrokenLineIndex to
1446 FUnbrokenLine[FParagraph[paraIndex+1].firstUnbrokenLineIndex].firstBrokenLineIndex-1 do
1447 if untransformedPos.Y < FBrokenLine[brokenLineIndex].rectF.Bottom then
1448 begin
1449 if untransformedPos.Y < FBrokenLine[brokenLineIndex].rectF.Top then
1450 exit(FBrokenLine[brokenLineIndex].startIndex);
1451
1452 j := FBrokenLine[brokenLineIndex].firstPartIndex;
1453 if j < FBrokenLine[brokenLineIndex].lastPartIndexPlusOne then
1454 begin
1455 if (BrokenLineRightToLeft[brokenLineIndex] and (untransformedPos.x >= PartRectF[j].Right)) or
1456 (not BrokenLineRightToLeft[brokenLineIndex] and (untransformedPos.x < PartRectF[j].Left)) then
1457 exit(FBrokenLine[brokenLineIndex].startIndex)
1458 end;
1459
1460 for j := FBrokenLine[brokenLineIndex].firstPartIndex to FBrokenLine[brokenLineIndex].lastPartIndexPlusOne-1 do
1461 if (PartBrokenLineIndex[j] = brokenLineIndex) and PartAffineBox[j].Contains(APosition) then
1462 begin
1463 with PartAffineBox[j] do
1464 begin
1465 if PartRightToLeft[j] then
1466 begin
1467 axis := TopLeft-TopRight;
1468 origin := TopRight;
1469 end else
1470 begin
1471 axis := TopRight-TopLeft;
1472 origin := TopLeft;
1473 end;
1474 len := VectLen(axis);
1475 if len > 0 then
1476 begin
1477 w := ((APosition-origin)*axis)/len;
1478 //if there is just one char, it is the whole part
1479 if PartEndIndex[j] = PartStartIndex[j]+1 then
1480 begin
1481 if w > 0.5*len then
1482 exit(PartEndIndex[j])
1483 else
1484 exit(PartStartIndex[j]);
1485 end;
1486
1487 str := copy(FText, FBidi[PartStartIndex[j]].Offset+1, FBidi[PartEndIndex[j]].Offset - FBidi[PartStartIndex[j]].Offset);
1488 fit := TextFitInfoBidiOverride(str, w, PartRightToLeft[j]);
1489 curIndex := PartStartIndex[j]+fit;
1490 if curIndex > PartEndIndex[j] then curIndex:= PartEndIndex[j];
1491 if curIndex = 0 then curW := 0
1492 else curW := TextSizeBidiOverrideSplit(PartStartIndex[j], PartEndIndex[j], PartRightToLeft[j], curIndex).x;
1493 while (curW < w) and (curIndex < PartEndIndex[j]) do
1494 begin
1495 newIndex := curIndex+1;
1496 while (newIndex < PartEndIndex[j]) and (GetUnicodeBidiClass(GetUnicodeChar(newIndex)) = ubcNonSpacingMark) do inc(newIndex);
1497 newW := TextSizeBidiOverrideSplit(PartStartIndex[j], PartEndIndex[j], PartRightToLeft[j], newIndex).x;
1498 if newW >= w then
1499 begin
1500 if (curW+newW)*0.5 + 1 < w then curIndex := newIndex;
1501 break;
1502 end;
1503 curIndex := newIndex;
1504 end;
1505 exit(curIndex);
1506 end;
1507 end;
1508 exit(PartStartIndex[j]);
1509 end;
1510 result := BrokenLineEndIndex[brokenLineIndex];
1511 if result > BrokenLineStartIndex[brokenLineIndex] then
1512 begin
1513 u := GetUnicodeChar(result-1);
1514 if IsUnicodeParagraphSeparator(u) or (u = UNICODE_LINE_SEPARATOR) then
1515 begin
1516 dec(result);
1517 if (result > BrokenLineStartIndex[brokenLineIndex]) and (u = 13) or (u = 10) then
1518 begin
1519 u2 := GetUnicodeChar(result-1);
1520 if (u2 <> u) and ((u2 = 13) or (u2 = 10)) then dec(result);
1521 end;
1522 end;
1523 end;
1524 exit;
1525 end;
1526
1527 result := FUnbrokenLine[FParagraph[paraIndex+1].firstUnbrokenLineIndex].startIndex;
1528 while (result > FUnbrokenLine[FParagraph[paraIndex].firstUnbrokenLineIndex].startIndex) and
1529 FBidi[result-1].BidiInfo.IsEndOfParagraph do
1530 dec(result);
1531 exit();
1532 end;
1533 end;
1534
1535 exit(CharCount);
1536end;
1537
1538function TBidiTextLayout.GetTextEnveloppe(AStartIndex, AEndIndex: integer; APixelCenteredCoordinates: boolean): ArrayOfTPointF;
1539var
1540 temp, i: Integer;
1541 startCaret, endCaret, curPartStartCaret, curPartEndCaret,
1542 lineStartCaret, lineEndCaret: TBidiCaretPos;
1543 brokenLineIndex, paraIndex: integer;
1544 r: TRectF;
1545begin
1546 NeedLayout;
1547
1548 result := nil;
1549
1550 if AStartIndex > AEndIndex then
1551 begin
1552 temp := AStartIndex;
1553 AStartIndex:= AEndIndex;
1554 AEndIndex:= temp;
1555 end;
1556 startCaret := GetCaret(AStartIndex);
1557 endCaret := GetCaret(AEndIndex);
1558 if not isEmptyPointF(endCaret.PreviousTop) then
1559 begin
1560 endCaret.Top := endCaret.PreviousTop; endCaret.PreviousTop := EmptyPointF;
1561 endCaret.Bottom := endCaret.PreviousBottom; endCaret.PreviousBottom := EmptyPointF;
1562 endCaret.RightToLeft := endCaret.PreviousRightToLeft;
1563 if endCaret.PartIndex <> -1 then endCaret.PartIndex -= 1;
1564 end;
1565
1566 if startCaret.PartIndex = endCaret.PartIndex then
1567 begin
1568 if not isEmptyPointF(startCaret.Top) and not isEmptyPointF(endCaret.Top) then
1569 result := PointsF([startCaret.Top,startCaret.Bottom,endCaret.Bottom,endCaret.Top]);
1570 end else
1571 begin
1572 result := nil;
1573 for i := startCaret.PartIndex to endCaret.PartIndex do
1574 begin
1575 if i > startCaret.PartIndex then curPartStartCaret := PartStartCaret[i]
1576 else curPartStartCaret := startCaret;
1577
1578 if i < endCaret.PartIndex then curPartEndCaret := PartEndCaret[i]
1579 else curPartEndCaret := endCaret;
1580
1581 //space between paragraph
1582 if (i > startCaret.PartIndex) and (ParagraphSpacingAbove+ParagraphSpacingBelow <> 0) then
1583 begin
1584 paraIndex := BrokenLineParagraphIndex[PartBrokenLineIndex[i]];
1585 if (paraIndex > 0) and (BrokenLineParagraphIndex[PartBrokenLineIndex[i-1]] = paraIndex-1) then
1586 begin
1587 r := RectF(ParagraphRectF[paraIndex-1].Left, ParagraphRectF[paraIndex-1].Bottom - ParagraphSpacingBelow*FLineHeight,
1588 ParagraphRectF[paraIndex-1].Right, ParagraphRectF[paraIndex-1].Bottom);
1589 result := ConcatPointsF([result, Matrix*TAffineBox.AffineBox(r).AsPolygon, PointsF([EmptyPointF])]);
1590
1591 r := RectF(ParagraphRectF[paraIndex].Left, ParagraphRectF[paraIndex].Top,
1592 ParagraphRectF[paraIndex].Right, ParagraphRectF[paraIndex].Top + ParagraphSpacingAbove*FLineHeight);
1593 result := ConcatPointsF([result, Matrix*TAffineBox.AffineBox(r).AsPolygon, PointsF([EmptyPointF])]);
1594 end;
1595 end;
1596
1597 //start of lines
1598 brokenLineIndex := PartBrokenLineIndex[i];
1599 lineStartCaret := BrokenLineStartCaret[brokenLineIndex];
1600 if (i > startCaret.PartIndex) and (PartBrokenLineIndex[i-1] <> brokenLineIndex) then
1601 begin
1602 if BrokenLineRightToLeft[brokenLineIndex] = PartRightToLeft[i] then
1603 result := ConcatPointsF([result,
1604 PointsF([lineStartCaret.Top,lineStartCaret.Bottom,PartStartCaret[i].Bottom,PartStartCaret[i].Top, EmptyPointF])
1605 ])
1606 else
1607 result := ConcatPointsF([result,
1608 PointsF([lineStartCaret.Top,lineStartCaret.Bottom,PartEndCaret[i].Bottom,PartEndCaret[i].Top, EmptyPointF])
1609 ])
1610 end;
1611
1612 //text parts
1613 result := ConcatPointsF([result,
1614 PointsF([curPartStartCaret.Top,curPartStartCaret.Bottom,curPartEndCaret.Bottom,curPartEndCaret.Top, EmptyPointF])
1615 ]);
1616
1617
1618 //end of lines
1619 lineEndCaret := BrokenLineEndCaret[brokenLineIndex];
1620 if (i < endCaret.PartIndex) and (PartBrokenLineIndex[i+1] <> PartBrokenLineIndex[i]) then
1621 begin
1622 if BrokenLineRightToLeft[brokenLineIndex] = PartRightToLeft[i] then
1623 result := ConcatPointsF([result,
1624 PointsF([PartEndCaret[i].Top,PartEndCaret[i].Bottom,lineEndCaret.Bottom,lineEndCaret.Top, EmptyPointF])
1625 ])
1626 else
1627 result := ConcatPointsF([result,
1628 PointsF([PartStartCaret[i].Top,PartStartCaret[i].Bottom,lineEndCaret.Bottom,lineEndCaret.Top, EmptyPointF])
1629 ])
1630 end;
1631 end;
1632 if result <> nil then setlength(result, length(result)-1);
1633 end;
1634
1635 if APixelCenteredCoordinates then
1636 for i := 0 to high(result) do
1637 if not isEmptyPointF(result[i]) then result[i] += PointF(0.5,0.5);
1638end;
1639
1640function TBidiTextLayout.GetParagraphAt(ACharIndex: Integer): integer;
1641var
1642 i: Integer;
1643begin
1644 if ACharIndex < 0 then exit(0);
1645 for i := 1 to FParagraphCount-1 do
1646 if ACharIndex < ParagraphStartIndex[i] then
1647 exit(i-1);
1648 exit(FParagraphCount-1);
1649end;
1650
1651function TBidiTextLayout.InsertText(ATextUTF8: string; APosition: integer): integer;
1652var prevCharCount : integer;
1653begin
1654 if (APosition < 0) or (APosition > CharCount) then raise exception.Create('Position out of bounds');
1655 prevCharCount:= CharCount;
1656
1657 if APosition = CharCount then
1658 FText += ATextUTF8
1659 else
1660 Insert(ATextUTF8, FText, FBidi[APosition].Offset+1);
1661
1662 AnalyzeText;
1663 result := CharCount-prevCharCount;
1664end;
1665
1666function TBidiTextLayout.InsertLineSeparator(APosition: integer): integer;
1667begin
1668 result := InsertText(UnicodeCharToUTF8(UNICODE_LINE_SEPARATOR), APosition);
1669end;
1670
1671function TBidiTextLayout.DeleteText(APosition, ACount: integer): integer;
1672var
1673 utf8Start, utf8Count: Integer;
1674begin
1675 ACount := IncludeNonSpacingChars(APosition, ACount);
1676 if ACount = 0 then exit(0);
1677
1678 utf8Start := FBidi[APosition].Offset+1;
1679 if APosition+ACount = CharCount then
1680 utf8Count := length(FText) - FBidi[APosition].Offset
1681 else
1682 utf8Count := FBidi[APosition+ACount].Offset - FBidi[APosition].Offset;
1683
1684 Delete(FText, utf8Start, utf8Count);
1685 AnalyzeText;
1686 result := ACount;
1687end;
1688
1689function TBidiTextLayout.DeleteTextBefore(APosition, ACount: integer): integer;
1690var
1691 utf8Start, utf8Count: Integer;
1692begin
1693 ACount := IncludeNonSpacingCharsBefore(APosition, ACount);
1694 if ACount = 0 then exit(0);
1695
1696 utf8Start := FBidi[APosition-ACount].Offset+1;
1697 if APosition = CharCount then
1698 utf8Count := length(FText) - FBidi[APosition-ACount].Offset
1699 else
1700 utf8Count := FBidi[APosition].Offset - FBidi[APosition-ACount].Offset;
1701
1702 Delete(FText, utf8Start, utf8Count);
1703 AnalyzeText;
1704 result := ACount;
1705end;
1706
1707function TBidiTextLayout.CopyText(APosition, ACount: integer): string;
1708var
1709 utf8Start, utf8Count: Integer;
1710begin
1711 ACount := IncludeNonSpacingChars(APosition, ACount);
1712 if ACount = 0 then exit('');
1713
1714 utf8Start := FBidi[APosition].Offset+1;
1715 if APosition+ACount = CharCount then
1716 utf8Count := length(FText) - FBidi[APosition].Offset
1717 else
1718 utf8Count := FBidi[APosition+ACount].Offset - FBidi[APosition].Offset;
1719
1720 result := copy(FText, utf8Start, utf8Count);
1721end;
1722
1723function TBidiTextLayout.CopyTextBefore(APosition, ACount: integer): string;
1724var
1725 utf8Start, utf8Count: Integer;
1726begin
1727 ACount := IncludeNonSpacingCharsBefore(APosition, ACount);
1728 if ACount = 0 then exit('');
1729
1730 utf8Start := FBidi[APosition-ACount].Offset+1;
1731 if APosition = CharCount then
1732 utf8Count := length(FText) - FBidi[APosition-ACount].Offset
1733 else
1734 utf8Count := FBidi[APosition].Offset - FBidi[APosition-ACount].Offset;
1735
1736 result := copy(FText, utf8Start, utf8Count);
1737end;
1738
1739function TBidiTextLayout.IncludeNonSpacingChars(APosition, ACount: integer): integer;
1740var
1741 idxPara: Integer;
1742begin
1743 if (APosition < 0) or (APosition > CharCount) then raise exception.Create('Position out of bounds');
1744 if APosition+ACount > CharCount then raise exception.Create('Exceed end of text');
1745 if ACount = 0 then exit(0);
1746
1747 //delete Cr/Lf pair
1748 if IsUnicodeCrLf(UnicodeChar[APosition+ACount-1]) then
1749 begin
1750 idxPara := GetParagraphAt(APosition+ACount-1);
1751 if (ParagraphEndIndex[idxPara] = APosition+ACount+1) and
1752 IsUnicodeCrLf(UnicodeChar[APosition+ACount]) then Inc(ACount);
1753 end;
1754
1755 //delete non spacing marks after last char
1756 while (APosition+ACount < CharCount) and
1757 (GetUnicodeBidiClass(UnicodeChar[APosition+ACount])=ubcNonSpacingMark)
1758 do inc(ACount);
1759
1760 result := ACount;
1761end;
1762
1763function TBidiTextLayout.IncludeNonSpacingCharsBefore(APosition, ACount: integer): integer;
1764var
1765 idxPara: Integer;
1766begin
1767 if (APosition < 0) or (APosition > CharCount) then raise exception.Create('Position out of bounds');
1768 if APosition-ACount < 0 then raise exception.Create('Exceed start of text');
1769 if ACount = 0 then exit(0);
1770
1771 //delete Cr/Lf pair
1772 if IsUnicodeCrLf(UnicodeChar[APosition-1]) then
1773 begin
1774 idxPara := GetParagraphAt(APosition-1);
1775 if (ParagraphStartIndex[idxPara] < APosition-1) and
1776 IsUnicodeCrLf(UnicodeChar[APosition-2]) then
1777 Inc(ACount);
1778 end;
1779
1780 //delete before non spacing marks until real char
1781 idxPara := GetParagraphAt(APosition-ACount);
1782 while (APosition-ACount > ParagraphStartIndex[idxPara]) and
1783 (GetUnicodeBidiClass(UnicodeChar[APosition-ACount])=ubcNonSpacingMark) and
1784 not IsUnicodeIsolateOrFormatting(UnicodeChar[APosition-ACount-1])
1785 do inc(ACount);
1786
1787 result := ACount;
1788end;
1789
1790function TBidiTextLayout.GetPartStartCaret(APartIndex: integer): TBidiCaretPos;
1791begin
1792 if (APartIndex < 0) or (APartIndex > PartCount) then
1793 raise ERangeError.Create('Invalid index');
1794
1795 result.PartIndex := APartIndex;
1796
1797 if Odd(FPart[APartIndex].bidiLevel) then
1798 result.Top := PointF(FPart[APartIndex].rectF.Right, FPart[APartIndex].rectF.Top)
1799 else
1800 result.Top := PointF(FPart[APartIndex].rectF.Left, FPart[APartIndex].rectF.Top);
1801 result.Bottom := result.Top + PointF(0, FPart[APartIndex].rectF.Height);
1802 result.Top := Matrix*result.Top;
1803 result.Bottom := Matrix*result.Bottom;
1804
1805 result.RightToLeft := odd(FPart[APartIndex].bidiLevel);
1806
1807 if (APartIndex > 0) and (FPart[APartIndex-1].endIndex = FPart[APartIndex].startIndex) and
1808 (FBrokenLine[FPart[APartIndex-1].brokenLineIndex].unbrokenLineIndex =
1809 FBrokenLine[FPart[APartIndex].brokenLineIndex].unbrokenLineIndex) then
1810 begin
1811 if Odd(FPart[APartIndex-1].bidiLevel) then
1812 result.PreviousTop := PointF(FPart[APartIndex-1].rectF.Left, FPart[APartIndex-1].rectF.Top)
1813 else
1814 result.PreviousTop := PointF(FPart[APartIndex-1].rectF.Right, FPart[APartIndex-1].rectF.Top);
1815 result.PreviousBottom := result.PreviousTop + PointF(0, FPart[APartIndex-1].rectF.Height);
1816 result.PreviousTop := Matrix*result.PreviousTop;
1817 result.PreviousBottom := Matrix*result.PreviousBottom;
1818 result.PreviousRightToLeft := odd(FPart[APartIndex-1].bidiLevel);
1819 end else
1820 begin
1821 result.PreviousTop := EmptyPointF;
1822 result.PreviousBottom := EmptyPointF;
1823 result.PreviousRightToLeft := result.RightToLeft;
1824 end;
1825end;
1826
1827function TBidiTextLayout.GetPartEndCaret(APartIndex: integer): TBidiCaretPos;
1828begin
1829 if (APartIndex < 0) or (APartIndex > PartCount) then
1830 raise ERangeError.Create('Invalid index');
1831
1832 result.PartIndex := APartIndex;
1833
1834 if Odd(FPart[APartIndex].bidiLevel) then
1835 result.Top := PointF(FPart[APartIndex].rectF.Left, FPart[APartIndex].rectF.Top)
1836 else
1837 result.Top := PointF(FPart[APartIndex].rectF.Right, FPart[APartIndex].rectF.Top);
1838 result.Bottom := result.Top + PointF(0, FPart[APartIndex].rectF.Height);
1839 result.Top := Matrix*result.Top;
1840 result.Bottom := Matrix*result.Bottom;
1841 result.RightToLeft := odd(FPart[APartIndex].bidiLevel);
1842
1843 result.PreviousTop := EmptyPointF;
1844 result.PreviousBottom := EmptyPointF;
1845 result.PreviousRightToLeft := result.RightToLeft;
1846end;
1847
1848procedure TBidiTextLayout.ComputeLevelLayout(APos: TPointF; startIndex,
1849 endIndex: integer; bidiLevel: byte; fullHeight, baseLine: single; brokenLineIndex: integer;
1850 out AWidth: single);
1851var
1852 i: Integer;
1853 subLevel: byte;
1854 subStart, subSplit: integer;
1855 subStr: string;
1856 w,w2,h,dy: single;
1857 subSize: TPointF;
1858begin
1859 AWidth := 0;
1860
1861 while (startIndex < endIndex) and FBidi[startIndex].BidiInfo.IsRemoved do inc(startIndex);
1862 while (startIndex < endIndex) and FBidi[endIndex-1].BidiInfo.IsRemoved do dec(endIndex);
1863 if endIndex = startIndex then exit;
1864
1865 i := startIndex;
1866 while i < endIndex do
1867 begin
1868 if not FBidi[i].BidiInfo.IsRemoved then
1869 begin
1870 if FBidi[i].BidiInfo.BidiLevel > bidiLevel then
1871 begin
1872 subStart := i;
1873 subLevel := FBidi[i].BidiInfo.BidiLevel;
1874 inc(i);
1875 while (i < endIndex) and (FBidi[i].BidiInfo.BidiLevel > bidiLevel) do
1876 begin
1877 if FBidi[i].BidiInfo.BidiLevel < subLevel then
1878 subLevel := FBidi[i].BidiInfo.BidiLevel;
1879 inc(i);
1880 end;
1881
1882 if odd(bidiLevel) then
1883 begin
1884 if odd(subLevel) then
1885 begin
1886 ComputeLevelLayout(APos, subStart, i, subLevel, fullHeight, baseLine, brokenLineIndex, w);
1887 APos.x -= w;
1888 end else
1889 begin
1890 LevelSize(EmptySingle, subStart, i, subLevel, subSplit, w,h);
1891 APos.x -= w;
1892 ComputeLevelLayout(APos, subStart, subSplit, subLevel, fullHeight, baseLine, brokenLineIndex, w2);
1893 end;
1894 end else
1895 begin
1896 if odd(subLevel) then
1897 begin
1898 LevelSize(EmptySingle, subStart, i, subLevel, subSplit, w,h);
1899 APos.x += w;
1900 ComputeLevelLayout(APos, subStart, subSplit, subLevel, fullHeight, baseLine, brokenLineIndex, w2);
1901 end else
1902 begin
1903 ComputeLevelLayout(APos, subStart, i, subLevel, fullHeight, baseLine, brokenLineIndex, w);
1904 APos.x += w;
1905 end;
1906 end;
1907 AWidth += w;
1908 end else
1909 begin
1910 subStart:= i;
1911 inc(i);
1912 while (i < endIndex) and (FBidi[i].BidiInfo.BidiLevel = bidiLevel) do inc(i);
1913
1914 subStr := GetSameLevelString(subStart,i);
1915
1916 subSize := TextSizeBidiOverride(subStr, odd(bidiLevel));
1917 w := subSize.x;
1918 if (subSize.y <> fullHeight) and (fullHeight <> 0) then
1919 begin
1920 dy := baseLine * (1 - subSize.y/fullHeight);
1921 end else
1922 dy := 0;
1923 if odd(bidiLevel) then
1924 begin
1925 APos.x -= w;
1926 AddPart(subStart, i, bidiLevel,
1927 RectF(APos.x, APos.y, APos.x+w, APos.y+fullHeight), PointF(0,dy), subStr, brokenLineIndex);
1928 end else
1929 begin
1930 AddPart(subStart, i, bidiLevel,
1931 RectF(APos.x, APos.y, APos.x+w, APos.y+fullHeight), PointF(0,dy), subStr, brokenLineIndex);
1932 APos.x += w;
1933 end;
1934 AWidth += w;
1935 end;
1936
1937 end else
1938 inc(i);
1939 end;
1940end;
1941
1942procedure TBidiTextLayout.Init;
1943begin
1944 FParagraphCount:= 0;
1945 FUnbrokenLineCount:= 0;
1946 FBrokenLineCount:= 0;
1947 FPartCount:= 0;
1948 FTopLeft := PointF(0,0);
1949 FAvailableWidth:= EmptySingle;
1950 FAvailableHeight:= EmptySingle;
1951 FTabSize := 8;
1952 FParagraphSpacingAbove:= 0;
1953 FParagraphSpacingBelow:= 0;
1954 FMatrix := AffineMatrixIdentity;
1955 FLayoutComputed:= false;
1956 FColor := BGRABlack;
1957 FTexture := nil;
1958 FWordBreakHandler:= nil;
1959end;
1960
1961end.
1962
Note: See TracBrowser for help on using the repository browser.