1 unit Unit1;
2
3 {$mode objfpc}{$H+}
4
5 interface
6
7 uses
8 Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
9 StdCtrls, Spin, ComCtrls, BGRAVirtualScreen, BGRABitmap, BGRABitmapTypes,
10 BGRATextBidi, BGRAFreeType, EasyLazFreeType, LazFreeTypeFontCollection,
11 fgl, Types;
12
13 const
14 CaretBlinkTimeMs = 500;
15 ssShortcut = {$IFDEF DARWIN}ssMeta{$ELSE}ssCtrl{$ENDIF};
16
17 type
18 TRenderedBrokenLineList = specialize TFPGObjectList<TBGRABitmap>;
19
20 { TForm1 }
21
22 TForm1 = class(TForm)
23 BGRAVirtualScreen1: TBGRAVirtualScreen;
24 CheckBox_ClearType: TCheckBox;
25 CheckBox_FreeType: TCheckBox;
26 ImageList1: TImageList;
27 Label2: TLabel;
28 Panel1: TPanel;
29 ScrollBar1: TScrollBar;
30 SpinEdit_FontSize: TSpinEdit;
31 TimerBlinkCaret: TTimer;
32 ToolBar1: TToolBar;
33 ToolButtonLeftAlign: TToolButton;
34 ToolButtonCenterAlign: TToolButton;
35 ToolButtonRightAlign: TToolButton;
36 procedure BGRAVirtualScreen1MouseDown(Sender: TObject;
37 Button: TMouseButton; {%H-}Shift: TShiftState; X, Y: Integer);
38 procedure BGRAVirtualScreen1MouseMove(Sender: TObject; Shift: TShiftState;
39 X, Y: Integer);
40 procedure BGRAVirtualScreen1MouseWheel(Sender: TObject; Shift: TShiftState;
41 WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
42 procedure BGRAVirtualScreen1Redraw(Sender: TObject; Bitmap: TBGRABitmap);
43 procedure CheckBox_ClearTypeChange(Sender: TObject);
44 procedure CheckBox_FreeTypeChange(Sender: TObject);
45 procedure FormCreate(Sender: TObject);
46 procedure FormDestroy(Sender: TObject);
47 procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
48 procedure FormKeyUp(Sender: TObject; var Key: Word; {%H-}Shift: TShiftState);
49 procedure FormKeyPress(Sender: TObject; var Key: char);
50 procedure ScrollBar1Change(Sender: TObject);
51 procedure SpinEdit_FontSizeChange(Sender: TObject);
52 procedure TimerBlinkCaretTimer(Sender: TObject);
53 procedure ToolButtonCenterAlignClick(Sender: TObject);
54 procedure ToolButtonLeftAlignClick(Sender: TObject);
55 procedure ToolButtonRightAlignClick(Sender: TObject);
56 private
57 FFontRenderer: TBGRACustomFontRenderer;
58 FTextLayout: TBidiTextLayout;
59 FRenderedParagraphs: array of TRenderedBrokenLineList;
60 FBlinkCaretTime: TDateTime;
61 FBlinkCaretState: boolean;
62 FSelStart,FSelLength: integer;
63 FSelFirstClick,FSelLastClick: integer;
64 FCurFirstParagraph,FCurLastParagraph: integer;
65 FTestText: string;
66 FInUnicode: boolean;
67 FUnicodeValue: LongWord;
GetLayoutReadynull68 function GetLayoutReady: boolean;
GetSelLastClicknull69 function GetSelLastClick: integer;
70 procedure LayoutBrokenLinesChanged({%H}ASender: TObject;
71 AParagraphIndex: integer; ASubBrokenStart, ASubBrokenChangedCountBefore,
72 ASubBrokenChangedCountAfter: integer; ASubBrokenTotalCountBefore,
73 {%H}ASubBrokenTotalCountAfter: integer);
74 procedure LayoutParagraphDeleted({%H}ASender: TObject; AParagraphIndex: integer);
75 procedure LayoutParagraphMergedWithNext(ASender: TObject;
76 AParagraphIndex: integer);
77 procedure LayoutParagraphSplit({%H}ASender: TObject; AParagraphIndex: integer;
78 ASubBrokenIndex, {%H-}ACharIndex: integer);
79 procedure SetSelLastClick(AValue: integer);
80 procedure SetSelLength(AValue: integer);
81 procedure SetSelStart(AValue: integer);
82 procedure FlushUnicode;
83 procedure DiscardRenderedBrokenLines;
84 procedure LayoutCompletelyChanged;
85 public
86 procedure UpdateCurrentParagraph;
87 procedure UpdateSelectionFromFirstLastClick;
88 procedure SetCurrentParagraphAlign(AAlign: TAlignment);
89 procedure DeleteSelection;
90 procedure InsertText(AText: string);
91 procedure ShowCaret;
92 property SelStart: integer read FSelStart write SetSelStart;
93 property SelLength: integer read FSelLength write SetSelLength;
94 property SelLastClick: integer read GetSelLastClick write SetSelLastClick;
95 property LayoutReady: boolean read GetLayoutReady;
96 end;
97
98 var
99 Form1: TForm1;
100
101 implementation
102
103 uses BGRAText, LCLType, BGRAUTF8, Clipbrd, LCLIntf, math;
104
105 {$R *.lfm}
106
107 procedure SetClipboardAsText(Value: string);
108 var
109 strStream: TStringStream;
110 begin
111 strStream := TStringStream.Create(Value);
112 Clipboard.SetFormat(PredefinedClipboardFormat(pcfText), strStream);
113 strStream.Free;
114 end;
115
116 { TForm1 }
117
118 procedure TForm1.FormCreate(Sender: TObject);
119 begin
120 FTextLayout := nil;
121 FFontRenderer := nil;
122 FSelStart:= 0;
123 FSelLength:= 0;
124 FSelFirstClick := -1;
125 FSelLastClick:= -1;
126 TimerBlinkCaret.Interval := CaretBlinkTimeMs;
127 FCurFirstParagraph:= -1;
128 FCurLastParagraph:= -1;
129
130 BGRAVirtualScreen1.OnKeyDown:= @FormKeyDown;
131 BGRAVirtualScreen1.OnKeyUp:= @FormKeyUp;
132 BGRAVirtualScreen1.OnKeyPress:= @FormKeyPress;
133 BGRAVirtualScreen1.Cursor := crIBeam;
134 BGRAVirtualScreen1.BitmapAutoScale:= false;
135
136 FTestText := 'تحتوي العربية على 28 حرفاً مكتوباً. ويرى بعض اللغويين أنه يجب إضافة حرف الهمزة إلى حروف العربية، ليصبح عدد الحروف 29. تُكتب العربية من اليمين إلى اليسار - ومثلها اللغة الفارسية والعبرية على عكس كثير من اللغات العالمية - ومن أعلى الصفحة إلى أسفلها.'+LineEnding+
137 'Arabic reversed "' + UTF8OverrideDirection('صباح الخير',false)+'". Arabic marks: "لاٍُ لٍُإ بًٍّ ةُِ ںْ رُ ٮَ بٔ".'+ LineEnding +
138 #9'Le français est une langue indo-européenne de la famille des langues romanes. Le français s''est formé en France (variété de la « langue d''oïl », qui est la langue de la partie septentrionale du pays).'+LineEnding+
139 'Glorious finds itself reversed as '+ UTF8OverrideDirection('"glorious"',True) + '. ' +
140 '"Hello!" is '+ UTF8EmbedDirection('"مرحبا!"',True) + ' in arabic.' + LineEnding +
141 'देवनागरी एक भारतीय लिपि है जिसमें अनेक भारतीय भाषाएँ तथा कई विदेशी भाषाएँ लिखी जाती हैं। यह बायें से दायें लिखी जाती है।' + LineEnding +
142 '对于汉语的分支语言,学界主要有两种观点,一种观点将汉语定义为语言,并将官话、贛語、闽语、粤语、客家语、吴语、湘语七大语言定义为一级方言.'+LineEnding+
143 'עִבְרִית היא שפה שמית, ממשפחת השפות האפרו-אסיאתיות, הידועה כשפתם של היהודים ושל השומרונים, אשר ניב מודרני שלה (עברית ישראלית) משמש כשפה הרשמית והעיקרית של מדינת ישראל.';
144
145 end;
146
147 procedure TForm1.FormDestroy(Sender: TObject);
148 begin
149 DiscardRenderedBrokenLines;
150 FTextLayout.Free;
151 FFontRenderer.Free;
152 end;
153
154 procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
155
156 procedure MoveTo(ANewPos: integer);
157 begin
158 if ssShift in Shift then
159 begin
160 if ANewPos <> -1 then
161 SelLastClick := ANewPos;
162 end else
163 begin
164 if ANewPos <> -1 then
165 SelStart := ANewPos;
166 SelLength:= 0;
167 end;
168 end;
169
170 var
171 idxPara, newPos: Integer;
172 begin
173 if not LayoutReady then exit;
174
175 if (Key = VK_U) and ([ssCtrl,ssShift] <= Shift) then
176 begin
177 FlushUnicode;
178 FInUnicode := true;
179 FUnicodeValue:= 0;
180 Key := 0;
181 end else
182 if FInUnicode then
183 begin
184 case Key of
185 VK_DELETE: begin
186 FUnicodeValue := FUnicodeValue shr 4;
187 end;
188 VK_0..VK_9: begin
189 FUnicodeValue := (FUnicodeValue shl 4) + (Key - VK_0);
190 end;
191 VK_NUMPAD0..VK_NUMPAD9: begin
192 FUnicodeValue := (FUnicodeValue shl 4) + (Key - VK_NUMPAD0);
193 end;
194 VK_A..VK_F: begin
195 FUnicodeValue := (FUnicodeValue shl 4) + (Key - VK_A + 10);
196 end;
197 else
198 FlushUnicode;
199 end;
200 if (FUnicodeValue >= $10FFF0) or
201 (FUnicodeValue >= $11000) then
202 FlushUnicode;
203 Key := 0;
204 end else
205 if KEY = VK_DELETE then
206 begin
207 if SelLength > 0 then DeleteSelection
208 else
209 begin
210 FTextLayout.DeleteText(SelStart, 1);
211 SelStart := SelStart + FTextLayout.IncludeNonSpacingChars(SelStart, 0);
212 ShowCaret;
213 end;
214 Key := 0;
215 end else
216 if (Key = VK_LEFT) or (Key = VK_RIGHT) then
217 begin
218 if (Key = VK_LEFT) xor FTextLayout.ParagraphRightToLeft[FTextLayout.GetParagraphAt(SelLastClick)] then
219 begin
220 if SelLastClick > 0 then
221 newPos := SelLastClick - FTextLayout.IncludeNonSpacingCharsBefore(SelLastClick,1)
222 else newPos := -1;
223
224 MoveTo(newPos);
225 end else
226 begin
227 if SelLastClick < FTextLayout.CharCount then
228 newPos := SelLastClick + FTextLayout.IncludeNonSpacingChars(SelLastClick,1)
229 else newPos := -1;
230
231 MoveTo(newPos);
232 end;
233 Key := 0;
234 end else
235 if (Key = VK_UP) or (Key = VK_DOWN) then
236 begin
237 if Key = VK_UP then
238 newPos := FTextLayout.FindTextAbove(SelLastClick)
239 else
240 newPos := FTextLayout.FindTextBelow(SelLastClick);
241
242 MoveTo(newPos);
243 Key := 0;
244 end else
245 if Key = VK_HOME then
246 begin
247 idxPara := FTextLayout.GetParagraphAt(SelLastClick);
248 if ssCtrl in Shift then newPos := 0 else
249 newPos := FTextLayout.ParagraphStartIndex[idxPara];
250 MoveTo(newPos);
251 Key := 0;
252 end else
253 if Key = VK_END then
254 begin
255 idxPara := FTextLayout.GetParagraphAt(SelLastClick);
256 if ssCtrl in Shift then newPos := FTextLayout.CharCount else
257 newPos := FTextLayout.ParagraphEndIndexBeforeParagraphSeparator[idxPara];
258 MoveTo(newPos);
259 Key := 0;
260 end else
261 if Key = VK_RETURN then
262 begin
263 if SelLength > 0 then DeleteSelection;
264 if ssShift in Shift then
265 begin
266 SelStart := SelStart + FTextLayout.InsertLineSeparator(SelStart);
267 end else
268 InsertText(LineEnding);
269 Key := 0;
270 end else
271 if Key = VK_TAB then
272 begin
273 if SelLength > 0 then DeleteSelection;
274 InsertText(#9);
275 Key := 0;
276 end else
277 If (Key = VK_C) and (ssShortcut in Shift) then
278 begin
279 if SelLength> 0 then
280 SetClipboardAsText(FTextLayout.CopyText(SelStart, SelLength));
281 Key := 0;
282 end else
283 If (Key = VK_X) and (ssShortcut in Shift) then
284 begin
285 if SelLength > 0 then
286 begin
287 SetClipboardAsText(FTextLayout.CopyText(SelStart, SelLength));
288 DeleteSelection;
289 end;
290 Key := 0;
291 end else
292 If (Key = VK_V) and (ssShortcut in Shift) then
293 begin
294 InsertText(Clipboard.AsText);
295 Key := 0;
296 end else
297 If (Key = VK_A) and (ssShortcut in Shift) then
298 begin
299 SelStart:= 0;
300 SelLength:= FTextLayout.CharCount;
301 Key := 0;
302 end;
303 end;
304
305 procedure TForm1.FormKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
306 begin
307 if FInUnicode then
308 begin
309 if (Key = VK_CONTROL) or (Key = VK_SHIFT) then
310 FlushUnicode;
311 end;
312 end;
313
314 procedure TForm1.FormKeyPress(Sender: TObject; var Key: char);
315 var
316 delCount: Integer;
317 begin
318 if not LayoutReady then exit;
319
320 if Key = #8 then
321 begin
322 if SelLength > 0 then DeleteSelection
323 else
324 begin
325 if SelStart > 0 then
326 begin
327 delCount := FTextLayout.DeleteTextBefore(SelStart, 1);
328 SelStart := SelStart - delCount;
329 SelStart := SelStart + FTextLayout.IncludeNonSpacingChars(SelStart, 0);
330 end;
331 end;
332 end
333 else
334 if Key = #13 then
335 InsertText(LineEnding)
336 else
337 InsertText(Key);
338 Key := #0
339 end;
340
341 procedure TForm1.ScrollBar1Change(Sender: TObject);
342 begin
343 BGRAVirtualScreen1.DiscardBitmap;
344 end;
345
346 procedure TForm1.SpinEdit_FontSizeChange(Sender: TObject);
347 begin
348 LayoutCompletelyChanged;
349 end;
350
351 procedure TForm1.TimerBlinkCaretTimer(Sender: TObject);
352 begin
353 BGRAVirtualScreen1.DiscardBitmap;
354 end;
355
356 procedure TForm1.ToolButtonCenterAlignClick(Sender: TObject);
357 begin
358 SetCurrentParagraphAlign(taCenter);
359 end;
360
361 procedure TForm1.ToolButtonLeftAlignClick(Sender: TObject);
362 begin
363 SetCurrentParagraphAlign(taLeftJustify);
364 end;
365
366 procedure TForm1.ToolButtonRightAlignClick(Sender: TObject);
367 begin
368 SetCurrentParagraphAlign(taRightJustify);
369 end;
370
TForm1.GetLayoutReadynull371 function TForm1.GetLayoutReady: boolean;
372 begin
373 result := Assigned(FTextLayout) and Assigned(FFontRenderer);
374 end;
375
GetSelLastClicknull376 function TForm1.GetSelLastClick: integer;
377 begin
378 if FSelLastClick = -1 then
379 result := FSelStart + FSelLength
380 else
381 result := FSelLastClick;
382 end;
383
384 procedure TForm1.LayoutBrokenLinesChanged(ASender: TObject;
385 AParagraphIndex: integer; ASubBrokenStart, ASubBrokenChangedCountBefore,
386 ASubBrokenChangedCountAfter: integer; ASubBrokenTotalCountBefore,
387 ASubBrokenTotalCountAfter: integer);
388 var
389 i: Integer;
390 begin
391 if (AParagraphIndex < 0) or (AParagraphIndex > high(FRenderedParagraphs)) or
392 (FRenderedParagraphs[AParagraphIndex] = nil) then exit;
393 if ASubBrokenTotalCountBefore <> FRenderedParagraphs[AParagraphIndex].Count then
394 FreeAndNil(FRenderedParagraphs[AParagraphIndex])
395 else
396 begin
397 for i := 0 to ASubBrokenChangedCountBefore-1 do
398 FRenderedParagraphs[AParagraphIndex].Delete(ASubBrokenStart);
399 for i := 0 to ASubBrokenChangedCountAfter-1 do
400 FRenderedParagraphs[AParagraphIndex].Insert(ASubBrokenStart, nil);
401 end;
402 end;
403
404 procedure TForm1.DiscardRenderedBrokenLines;
405 var
406 i: Integer;
407 begin
408 for i := 0 to high(FRenderedParagraphs) do
409 FreeAndNil(FRenderedParagraphs[i]);
410 end;
411
412 procedure TForm1.LayoutCompletelyChanged;
413 begin
414 if Assigned(FTextLayout) then FTextLayout.InvalidateLayout;
415 DiscardRenderedBrokenLines;
416 BGRAVirtualScreen1.DiscardBitmap;
417 end;
418
419 procedure TForm1.LayoutParagraphDeleted(ASender: TObject;
420 AParagraphIndex: integer);
421 var
422 i: Integer;
423 begin
424 if (AParagraphIndex >= 0) and (AParagraphIndex <= high(FRenderedParagraphs)) then
425 begin
426 FreeAndNil(FRenderedParagraphs[AParagraphIndex]);
427 for i := AParagraphIndex to high(FRenderedParagraphs)-1 do
428 FRenderedParagraphs[i] := FRenderedParagraphs[i+1];
429 setlength(FRenderedParagraphs, length(FRenderedParagraphs)-1);
430 end;
431 end;
432
433 procedure TForm1.LayoutParagraphMergedWithNext(ASender: TObject;
434 AParagraphIndex: integer);
435 var
436 i, insertIndex: Integer;
437 renderedBrokenLine: TBGRABitmap;
438 begin
439 insertIndex := FRenderedParagraphs[AParagraphIndex].Count;
440 for i := FRenderedParagraphs[AParagraphIndex+1].Count-1 downto 0 do
441 begin
442 renderedBrokenLine := FRenderedParagraphs[AParagraphIndex+1].Items[i];
443 FRenderedParagraphs[AParagraphIndex].Insert(insertIndex, renderedBrokenLine);
444 FRenderedParagraphs[AParagraphIndex+1].Extract(renderedBrokenLine);
445 end;
446 LayoutParagraphDeleted(ASender, AParagraphIndex+1);
447 end;
448
449 procedure TForm1.LayoutParagraphSplit(ASender: TObject;
450 AParagraphIndex: integer; ASubBrokenIndex, ACharIndex: integer);
451 var
452 i, j: Integer;
453 renderedBrokenLine: TBGRABitmap;
454 begin
455 if (AParagraphIndex >= 0) and (AParagraphIndex <= high(FRenderedParagraphs)) then
456 begin
457 setlength(FRenderedParagraphs, length(FRenderedParagraphs)+1);
458 for i := high(FRenderedParagraphs) downto AParagraphIndex+2 do
459 FRenderedParagraphs[i] := FRenderedParagraphs[i-1];
460 FRenderedParagraphs[AParagraphIndex+1] := TRenderedBrokenLineList.Create;
461 for j := FRenderedParagraphs[AParagraphIndex].Count-1 downto ASubBrokenIndex+1 do
462 begin
463 renderedBrokenLine := FRenderedParagraphs[AParagraphIndex].Items[j];
464 FRenderedParagraphs[AParagraphIndex+1].Insert(0, renderedBrokenLine);
465 FRenderedParagraphs[AParagraphIndex].Extract(renderedBrokenLine);
466 end;
467 end;
468 end;
469
470 procedure TForm1.SetSelLastClick(AValue: integer);
471 begin
472 if FSelFirstClick = -1 then
473 FSelFirstClick := FSelStart;
474 FSelLastClick:= AValue;
475 UpdateSelectionFromFirstLastClick;
476 end;
477
478 procedure TForm1.SetSelLength(AValue: integer);
479 begin
480 if FSelLength=AValue then Exit;
481 FSelLength:=AValue;
482 FSelFirstClick:=-1;
483 FSelLastClick:=-1;
484 ShowCaret;
485 end;
486
487 procedure TForm1.SetSelStart(AValue: integer);
488 begin
489 if FSelStart=AValue then Exit;
490 FSelStart:=AValue;
491 if FSelStart + FSelLength > FTextLayout.CharCount then
492 FSelLength := FTextLayout.CharCount - FSelStart;
493 FSelFirstClick:=-1;
494 FSelLastClick:=-1;
495 ShowCaret;
496 end;
497
498 procedure TForm1.FlushUnicode;
499 begin
500 if not FInUnicode then exit;
501 FInUnicode := false;
502 InsertText(UnicodeCharToUTF8(FUnicodeValue));
503 end;
504
505 procedure TForm1.UpdateCurrentParagraph;
506 var curAlign: TAlignment;
507 begin
508 if not LayoutReady then exit;
509
510 FCurFirstParagraph:= FTextLayout.GetParagraphAt(SelStart);
511 FCurLastParagraph:= FTextLayout.GetParagraphAt(SelStart+SelLength);
512 case FTextLayout.ParagraphAlignment[FCurFirstParagraph] of
513 btaCenter: curAlign := taCenter;
514 btaLeftJustify: curAlign := taLeftJustify;
515 btaRightJustify: curAlign:= taRightJustify;
516 btaOpposite: if FTextLayout.ParagraphRightToLeft[FCurFirstParagraph] then
517 curAlign:= taLeftJustify else curAlign:= taRightJustify;
518 else
519 if FTextLayout.ParagraphRightToLeft[FCurFirstParagraph] then
520 curAlign:= taRightJustify else curAlign:= taLeftJustify;
521 end;
522 ToolButtonLeftAlign.Down := curAlign = taLeftJustify;
523 ToolButtonCenterAlign.Down := curAlign = taCenter;
524 ToolButtonRightAlign.Down := curAlign = taRightJustify;
525 end;
526
527 procedure TForm1.UpdateSelectionFromFirstLastClick;
528 begin
529 if FSelLastClick < FSelFirstClick then
530 begin
531 FSelStart := FSelLastClick;
532 FSelLength:= FSelFirstClick-FSelLastClick;
533 end else
534 begin
535 FSelStart:= FSelFirstClick;
536 FSelLength:= FSelLastClick-FSelFirstClick;
537 end;
538 ShowCaret;
539 end;
540
541 procedure TForm1.SetCurrentParagraphAlign(AAlign: TAlignment);
542 var
543 i: Integer;
544 newAlign: TBidiTextAlignment;
545 begin
546 if LayoutReady and (FCurFirstParagraph <> -1) then
547 begin
548 for i := FCurFirstParagraph to FCurLastParagraph do
549 begin
550 case AALign of
551 taLeftJustify: if FTextLayout.ParagraphRightToLeft[i] then
552 newAlign := btaOpposite
553 else newAlign := btaNatural;
554 taRightJustify: if FTextLayout.ParagraphRightToLeft[i] then
555 newAlign := btaNatural
556 else newAlign := btaOpposite;
557 else {taCenter:} newAlign := btaCenter;
558 end;
559 FTextLayout.ParagraphAlignment[i] := newAlign;
560 end;
561
562 BGRAVirtualScreen1.DiscardBitmap;
563 end;
564 end;
565
566 procedure TForm1.DeleteSelection;
567 begin
568 if SelLength <> 0 then
569 begin
570 FTextLayout.DeleteText(SelStart, SelLength);
571 SelStart := SelStart + FTextLayout.IncludeNonSpacingChars(SelStart, 0);
572 SelLength:= 0;
573 ShowCaret;
574 end;
575 end;
576
577 procedure TForm1.InsertText(AText: string);
578 var
579 insertCount: Integer;
580 begin
581 if not LayoutReady then exit;
582 DeleteSelection;
583 insertCount := FTextLayout.InsertText(AText, SelStart);
584 SelStart := SelStart + insertCount;
585 SelStart := SelStart + FTextLayout.IncludeNonSpacingChars(SelStart, 0);
586 ShowCaret;
587 end;
588
589 procedure TForm1.ShowCaret;
590 begin
591 FBlinkCaretState := true;
592 FBlinkCaretTime:= Now;
593 BGRAVirtualScreen1.DiscardBitmap;
594 TimerBlinkCaret.Enabled := false;
595 TimerBlinkCaret.Enabled := true;
596 end;
597
598 procedure TForm1.BGRAVirtualScreen1Redraw(Sender: TObject; Bitmap: TBGRABitmap);
599 var
600 zoom, prevAvailWidth: single;
601 caretColor, selectionColor: TBGRAPixel;
602 newTime: TDateTime;
603 oldTopLeft: TPointF;
604 i: Integer;
605 startBroken, endBroken, j, countBroken: LongInt;
606 renderedBroken: TBGRABitmap;
607 renderRect: TRect;
608 begin
609 zoom := BGRAVirtualScreen1.BitmapScale * Screen.PixelsPerInch / 96;
610 if FFontRenderer = nil then
611 begin
612 if CheckBox_FreeType.Checked then
613 begin
614 FFontRenderer := TBGRAFreeTypeFontRenderer.Create;
615 FFontRenderer.FontName := 'Liberation Serif';
616 end else
617 begin
618 FFontRenderer := TLCLFontRenderer.Create;
619 FFontRenderer.FontName := {$IFDEF LINUX}'Liberation Serif'{$ELSE}'serif'{$ENDIF};
620 End;
621 end;
622 if CheckBox_ClearType.Checked then
623 begin
624 //force ClearType to RGB if disabled on the system
625 if fqFineClearType() = fqFineAntialiasing then
626 FFontRenderer.FontQuality:= fqFineClearTypeRGB
627 else
628 FFontRenderer.FontQuality:= fqSystemClearType;
629 end
630 else
631 begin
632 if CheckBox_FreeType.Checked then
633 FFontRenderer.FontQuality:= fqFineAntialiasing
634 else
635 FFontRenderer.FontQuality:= fqSystem;
636 end;
637 FFontRenderer.FontEmHeightF:= SpinEdit_FontSize.Value * zoom;
638
639 if FTextLayout = nil then
640 begin
641 FTextLayout:= TBidiTextLayout.Create(FFontRenderer, FTestText);
642 FTextLayout.ParagraphSpacingBelow:= 0.25;
643 FTextLayout.ParagraphSpacingAbove:= 0.25;
644 FTextLayout.OnParagraphDeleted:=@LayoutParagraphDeleted;
645 FTextLayout.OnParagraphMergedWithNext:=@LayoutParagraphMergedWithNext;
646 FTextLayout.OnParagraphSplit:=@LayoutParagraphSplit;
647 FTextLayout.OnBrokenLinesChanged:=@LayoutBrokenLinesChanged;
648 end else
649 FTextLayout.FontRenderer := FFontRenderer;
650
651 prevAvailWidth := FTextLayout.AvailableWidth;
652 FTextLayout.AvailableWidth := Bitmap.Width - 8*zoom;
653 FTextLayout.TopLeft := PointF(4*zoom,4*zoom);
654 if prevAvailWidth <> FTextLayout.AvailableWidth then
655 DiscardRenderedBrokenLines;
656 FTextLayout.ComputeLayoutIfNeeded;
657
658 oldTopLeft := FTextLayout.TopLeft;
659 ScrollBar1.Min:= 0;
660 ScrollBar1.Max:= round(FTextLayout.TotalTextHeight + 8*zoom);
661 ScrollBar1.PageSize:= Bitmap.Height;
662 ScrollBar1.LargeChange:= Bitmap.Height*2 div 3;
663 ScrollBar1.SmallChange:= round(FTextLayout.LineHeight);
664 if ScrollBar1.Position > max(0, ScrollBar1.Max - ScrollBar1.PageSize) then
665 ScrollBar1.Position := max(0, ScrollBar1.Max - ScrollBar1.PageSize);
666
667 caretColor := BGRA(0,0,255);
668 selectionColor := BGRA(0,0,255,128);
669
670 newTime := Now;
671 if newTime > FBlinkCaretTime + (CaretBlinkTimeMs/1000/24/60/60) then
672 begin
673 FBlinkCaretTime:= newTime;
674 FBlinkCaretState:= not FBlinkCaretState;
675 end;
676
677 FTextLayout.TopLeft := oldTopLeft + PointF(0, -ScrollBar1.Position);
678 if FBlinkCaretState and (SelLength = 0) and BGRAVirtualScreen1.Focused then
679 FTextLayout.DrawCaret(Bitmap, SelStart, BGRA(caretColor.red,caretColor.green,caretColor.blue,140), BGRA(caretColor.red,caretColor.green,caretColor.blue,100));
680
681 for i := FTextLayout.ParagraphCount to high(FRenderedParagraphs) do
682 FreeAndNil(FRenderedParagraphs[i]);
683 setlength(FRenderedParagraphs, FTextLayout.ParagraphCount);
684
685 for i := 0 to FTextLayout.ParagraphCount-1 do
686 begin
687 if FRenderedParagraphs[i] = nil then
688 FRenderedParagraphs[i] := TRenderedBrokenLineList.Create;
689 startBroken := FTextLayout.ParagraphStartBrokenLine[i];
690 endBroken := FTextLayout.ParagraphEndBrokenLine[i];
691 for j := startBroken to endBroken - 1 do
692 begin
693 if j - startBroken >= FRenderedParagraphs[i].Count then
694 FRenderedParagraphs[i].Add(nil);
695 if j - startBroken < FRenderedParagraphs[i].Count then
696 begin
697 renderRect := RectWithSize(0, round(oldTopLeft.y + FTextLayout.BrokenLineRectF[j].Top) - ScrollBar1.Position,
698 Bitmap.Width, ceil(FTextLayout.BrokenLineRectF[j].Height));
699 if renderRect.IntersectsWith(Bitmap.ClipRect) then
700 begin
701 if FRenderedParagraphs[i].Items[j - startBroken] = nil then
702 begin
703 renderedBroken := TBGRABitmap.Create(Bitmap.Width,
704 ceil(FTextLayout.BrokenLineRectF[j].Height), BGRAVirtualScreen1.Color);
705 FTextLayout.TopLeft := PointF(oldTopLeft.x, -FTextLayout.BrokenLineRectF[j].Top);
706 FTextLayout.DrawBrokenLines(renderedBroken, j, j+1);
707 FRenderedParagraphs[i].Items[j - startBroken] := renderedBroken;
708 end;
709 Bitmap.PutImage(renderRect.Left, renderRect.Top,
710 FRenderedParagraphs[i].Items[j - startBroken], dmSet);
711 end else
712 FRenderedParagraphs[i].Items[j - startBroken] := nil;
713 end;
714 end;
715 countBroken := endBroken - startBroken;
716 while FRenderedParagraphs[i].Count > countBroken do
717 FRenderedParagraphs[i].Delete(countBroken);
718 end;
719 FTextLayout.TopLeft := oldTopLeft + PointF(0, -ScrollBar1.Position);
720
721 FTextLayout.DrawSelection(Bitmap, SelStart, SelStart+SelLength, selectionColor, BGRA(0,0,192),1);
722
723 if FBlinkCaretState and (SelLength = 0) then
724 FTextLayout.DrawCaret(Bitmap, SelStart, BGRA(caretColor.red,caretColor.green,caretColor.blue,140), BGRA(caretColor.red,caretColor.green,caretColor.blue,100));
725
726 UpdateCurrentParagraph;
727 FTextLayout.TopLeft := oldTopLeft;
728 //let some time for events
729 TimerBlinkCaret.Enabled := false;
730 TimerBlinkCaret.Enabled := true;
731 end;
732
733 procedure TForm1.CheckBox_ClearTypeChange(Sender: TObject);
734 begin
735 LayoutCompletelyChanged;
736 end;
737
738 procedure TForm1.CheckBox_FreeTypeChange(Sender: TObject);
739 begin
740 LayoutCompletelyChanged;
741 FreeAndNil(FFontRenderer);
742 end;
743
744 procedure TForm1.BGRAVirtualScreen1MouseDown(Sender: TObject;
745 Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
746 var
747 index: Integer;
748 begin
749 BGRAVirtualScreen1.SetFocus;
750 if Button = mbLeft then
751 begin
752 index := FTextLayout.GetCharIndexAt(PointF(X, Y) * BGRAVirtualScreen1.BitmapScale
753 + PointF(0,ScrollBar1.Position));
754 FSelFirstClick:= index;
755 FSelLastClick:= index;
756 UpdateSelectionFromFirstLastClick;
757 end;
758 end;
759
760 procedure TForm1.BGRAVirtualScreen1MouseMove(Sender: TObject;
761 Shift: TShiftState; X, Y: Integer);
762 var
763 index: Integer;
764 begin
765 if (FSelFirstClick <> -1) and (ssLeft in Shift) then
766 begin
767 index := FTextLayout.GetCharIndexAt(PointF(X,Y) * BGRAVirtualScreen1.BitmapScale
768 + PointF(0,ScrollBar1.Position));
769 FSelLastClick:= index;
770 UpdateSelectionFromFirstLastClick;
771 end;
772 end;
773
774 procedure TForm1.BGRAVirtualScreen1MouseWheel(Sender: TObject;
775 Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint;
776 var Handled: Boolean);
777 begin
778 ScrollBar1.Position := ScrollBar1.Position - (WheelDelta * ScrollBar1.SmallChange div 120);
779 end;
780
781 initialization
782
783 EasyLazFreeType.FontCollection := TFreeTypeFontCollection.Create;
784 EasyLazFreeType.FontCollection.AddFolder(ExtractFilePath(Application.ExeName)
785 {$IFDEF DARWIN} + '../../../' {$ENDIF});
786
787 finalization
788
789 FreeAndNil(EasyLazFreeType.FontCollection);
790
791 end.
792
793