source: trunk/Components/RichTextLayoutUnit.pas@ 421

Last change on this file since 421 was 421, checked in by ataylor, 6 years ago

DBCS character boundary detection for cursor positioning logic.

  • Property svn:eol-style set to native
File size: 34.8 KB
Line 
1Unit RichTextLayoutUnit;
2
3// Dynamically created layout class.
4// Represents a laid out rich text document
5
6Interface
7
8Uses
9 Os2Def,
10 Classes, Forms, Messages, Graphics,
11 CanvasFontManager,
12 RichTextDocumentUnit, RichTextStyleUnit;
13
14Type
15 TLayoutLine = record
16 Text: PChar;
17 Length: longint;
18
19 Height: longint;
20 Width: longint;
21
22 MaxDescender: longint;
23 MaxTextHeight: longint; // maximum height of text, doesn't include images
24
25 LinkIndex: longint; // link index at start of line, if any
26
27 Style: TTextDrawStyle;
28
29 Wrapped: boolean;
30 end;
31
32 TLinesArray = array[ 0..0 ] of TLayoutLine;
33
34Type
35 TTextPosition =
36 (
37 tpAboveTextArea,
38 tpAboveText,
39 tpWithinText,
40 tpBelowText,
41 tpBelowTextArea
42 );
43
44
45 TRichTextLayout = class;
46
47 TLinkEvent = procedure( Sender: TRichTextLayout; Link: string ) of object;
48
49 TRichTextLayout = class
50 Protected
51 FFontManager: TCanvasFontManager;
52
53 // Text
54 FText: PChar;
55 FImages: TImageList;
56
57 FAllocatedNumLines: Longint;
58
59 FLayoutWidth: longint; // The target width for the layout. Used for centreing/right align
60 FWidth: longint; // The actual width of the text. May be wider due to unaligned
61 // parts or bitmaps or width so small individual characters don't fit.
62 FHeight: longint;
63
64 FLinks: TStringList;
65
66 FHorizontalImageScale: double;
67 FVerticalImageScale: double;
68
69 public
70 // Internal layout data
71 FLines: ^TLinesArray;
72 FNumLines: longint;
73
74 FRichTextSettings: TRichTextSettings;
75
76 Codepage: ULong; // ALT
77
78// TEMP (ALT)
79 DebugLog: TextFile;
80// TEMP (ALT)
81
82
83 // Drawing functions
84
85 Procedure PerformStyleTag( Const Tag: TTag;
86 Var Style: TTextDrawStyle;
87 const X: longint );
88
89 function GetElementWidth( Element: TTextElement ): longint;
90
91 // Queries
92
93 Function GetStartX( Style: TTextDrawStyle;
94 Line: TLayoutLine ): longint;
95
96 Procedure GetXFromOffset( const Offset: longint;
97 const LineIndex: longint;
98 Var X: longint );
99 Procedure GetOffsetFromX( const XToFind: longint;
100 const LineIndex: longint;
101 Var Offset: longint;
102 Var Link: string );
103 function FindPoint( XToFind, YToFind: longint;
104 Var LineIndex: longint;
105 Var Offset: longint;
106 Var Link: string ): TTextPosition;
107 function GetLineFromCharIndex( Index: longint ): longint;
108 function GetOffsetFromCharIndex( Index: longint;
109 Line: longint ): longint;
110 function GetLinePosition( Line: longint ): longint;
111 function GetLineFromPosition( YToFind: longint;
112 Var LineIndex: longint;
113 Var Remainder: longint ): TTextPosition;
114
115 // Layout functions
116
117 Procedure AddLineStart( Const Line: TLayoutLine );
118 Procedure CheckFontHeights( Var Line: TLayoutLine );
119 Procedure Layout;
120
121 function IsValidBitmapIndex( Index: longint ): boolean;
122
123 // property handlers
124
125 Function GetCharIndex( P: PChar ): longint;
126 Function GetTextEnd: longint;
127
128 // ALT
129 Function FindCharacterBoundary( TextPointer: PChar;
130 var Index: LongInt;
131 Advance: Boolean; ): LongInt;
132
133 Public
134 constructor Create( Text: PChar;
135 Images: TImageList;
136 RichTextSettings: TRichTextSettings;
137 FontManager: TCanvasFontManager;
138 Width: longint );
139
140 Destructor Destroy; Override;
141
142 property TextEnd: longint read GetTextEnd;
143
144 function LinkFromIndex( const CharIndexToFind: longint): string;
145
146 property Images: TImageList read FImages;
147
148 property Width: longint read FWidth;
149 property Height: longint read FHeight;
150
151 property HorizontalImageScale: double read FHorizontalImageScale;
152 property VerticalImageScale: double read FVerticalImageScale;
153
154 End;
155
156Implementation
157
158Uses
159 SysUtils,
160 PMWin,
161 BseDos,
162 Dos,
163 ClipBrd,
164 Printers,
165 ACLUtility,
166 ACLString,
167 ControlScrolling,
168 CharUtilsUnit;
169
170Function TRichTextLayout.GetTextEnd: longint;
171begin
172 Result := StrLen( FText );
173end;
174
175// Create a layout of the specified rich text.
176constructor TRichTextLayout.Create( Text: PChar;
177 Images: TImageList;
178 RichTextSettings: TRichTextSettings;
179 FontManager: TCanvasFontManager;
180 Width: longint );
181var
182 DefaultFontSpec: TFontSpec;
183 CpSize: ULong; // ALT
184Begin
185 Inherited Create;
186
187 FRichTextSettings := RichTextSettings;
188
189{
190AssignFile( DebugLog, 'parse.txt');
191ReWrite( DebugLog );
192WriteLn( DebugLog, 'Opened log for writing.');
193}
194
195 FImages := Images;
196
197 FText := Text;
198
199 FAllocatedNumLines := 10;
200 GetMem( FLines, FAllocatedNumLines * sizeof( TLayoutLine ) );
201 FNumLines := 0;
202
203 FLinks := TStringList.Create;
204 FLinks.Duplicates := dupIgnore;
205
206 FFontManager := FontManager;
207
208 FLayoutWidth := Width
209 * FontWidthPrecisionFactor;
210
211 FHorizontalImageScale := FFontManager.Canvas.HorizontalResolution
212 / Screen.Canvas.HorizontalResolution;
213 FVerticalImageScale := FFontManager.Canvas.VerticalResolution
214 / Screen.Canvas.VerticalResolution;
215
216 // use normal font for default font when specified fonts can't be found
217 SibylFontToFontSpec( RichTextSettings.NormalFont,
218 DefaultFontSpec );
219 FFontManager.DefaultFontSpec := DefaultFontSpec;
220
221 DosQueryCp( sizeof( Codepage ), Codepage, CpSize ); // ALT
222
223 Layout;
224End;
225
226Destructor TRichTextLayout.Destroy;
227Begin
228 FreeMem( Flines, FAllocatedNumLines * sizeof( TLayoutLine ) );
229 FLinks.Destroy;
230
231{
232// TEMP (ALT)
233WriteLn( DebugLog, '=============================');
234CloseFile( DebugLog );
235// TEMP (ALT)
236}
237
238 Inherited Destroy;
239End;
240
241Procedure TRichTextLayout.AddLineStart( Const Line: TLayoutLine );
242var
243 NewAllocation: longint;
244begin
245 if FNumLines >= FAllocatedNumLines then
246 begin
247 // reallocate the array twice the size
248 NewAllocation := FAllocatedNumLines * 2;
249 FLines := ReAllocMem( FLines,
250 FAllocatedNumLines * sizeof( TLayoutLine ),
251 NewAllocation * sizeof( TLayoutLine ) );
252 FAllocatedNumLines := NewAllocation;
253 end;
254 FLines^[ FNumLines ] := Line;
255 inc( FNumLines );
256end;
257
258Procedure TRichTextLayout.PerformStyleTag( Const Tag: TTag;
259 Var Style: TTextDrawStyle;
260 const X: longint );
261begin
262 ApplyStyleTag( Tag,
263 Style,
264 FFontManager,
265 FRichTextSettings,
266 X );
267end;
268
269// Check the current font specifications and see if the
270// give line needs updating for max height/descender
271Procedure TRichTextLayout.CheckFontHeights( Var Line: TLayoutLine );
272var
273 FontHeight: longint;
274 Descender: longint;
275begin
276 FontHeight := FFontManager.CharHeight;
277 Descender := FFontManager.CharDescender;
278
279 if FontHeight > Line.Height then
280 Line.Height := FontHeight;
281
282 if FontHeight > Line.MaxTextHeight then
283 Line.MaxTextHeight := FontHeight;
284
285 if Descender > Line.MaxDescender then
286 Line.MaxDescender := Descender;
287end;
288
289function TRichTextLayout.IsValidBitmapIndex( Index: longint ): boolean;
290begin
291 if FImages = nil then
292 Result := false
293 else if FImages.Count = 0 then
294 Result := false
295 else
296 Result := Between( Index, 0, FImages.Count - 1 );
297end;
298
299// Main procedure: reads through the whole text currently stored
300// and breaks up into lines - each represented as a TLayoutLine in
301// the array FLines[ 0.. FNumLines ]
302Procedure TRichTextLayout.Layout;
303Var
304 CurrentLine: TLayoutLine;
305
306 CurrentLinkIndex: longint;
307
308 WrapX: longint; // X to wrap at
309
310 WordX: longint; // width of word so far
311 P: PChar;
312 NextP: PChar;
313 NextP2: PChar;
314 NextP3: PChar;
315
316 WordStart: PChar;
317 WordStarted: boolean; // if false, just skipping spaces..
318 WordStartX: longint; // X position of word start
319
320 LineWordsCompleted: longint; // how many words draw so far this line
321
322 CurrentElement: TTextElement;
323 NextElement: TTextElement;
324
325 CurrentCharWidth: longint;
326
327 Style: TTextDrawStyle;
328
329 DisplayedCharsSinceFontChange: boolean;
330
331 BitmapIndex: longint;
332 Bitmap: TBitmap;
333 BitmapHeight: longint;
334
335 OnBreak: boolean;
336
337 DoWrap: boolean;
338
339 InsideDBC: Boolean; // ALT
340
341 // Nested procedure
342
343 Procedure DoLine( EndPoint: PChar;
344 NextLine: PChar;
345 EndX: longint );
346 begin
347 // check if the max font
348 // height needs updating for the last string of the line
349 CheckFontHeights( CurrentLine );
350
351 inc( FHeight, CurrentLine.Height );
352
353 CurrentLine.Length := PCharPointerDiff( EndPoint, CurrentLine.Text );
354
355 CurrentLine.Width := EndX;
356
357 if CurrentLine.Width > FWidth then
358 FWidth := CurrentLine.Width;
359
360 assert( CurrentLine.Height > 0 ); // we must have set the line height!
361
362 AddLineStart( CurrentLine );
363 CurrentLine.Text := NextLine;
364 CurrentLine.Style := Style;
365 CurrentLine.Height := 0;
366 CurrentLine.MaxDescender := 0;
367 CurrentLine.MaxTextHeight := 0;
368 CurrentLine.Width := 0;
369 CurrentLine.LinkIndex := CurrentLinkIndex;
370 CurrentLine.Wrapped := false;
371
372 assert( CurrentLinkIndex >= -1 );
373 assert( CurrentLinkIndex < FLinks.Count );
374
375 WordStartX := Style.LeftMargin * FontWidthPrecisionFactor;
376 // next line
377
378 // reset words completed count
379 LineWordsCompleted := 0;
380
381 WordStarted := false;
382 end;
383
384begin
385 FNumLines := 0;
386 FWidth := 0;
387 FHeight := FRichTextSettings.Margins.Top;
388
389 Style := GetDefaultStyle( FRichTextSettings );
390
391 ApplyStyle( Style, FFontManager );
392
393 CurrentLinkIndex := -1;
394
395 P := FText; // P is the current search position
396 CurrentLine.Text := P;
397 CurrentLine.Style := Style;
398 CurrentLine.Height := 0;
399 CurrentLine.MaxDescender := 0;
400 CurrentLine.MaxTextHeight := 0;
401 CurrentLine.Width := 0;
402 CurrentLine.LinkIndex := -1;
403 CurrentLine.Wrapped := false;
404
405 WordStartX := Style.LeftMargin * FontWidthPrecisionFactor;
406 WordX := 0;
407
408 WrapX := FLayoutWidth
409 - FRichTextSettings.Margins.Right
410 * FontWidthPrecisionFactor;
411
412 LineWordsCompleted := 0;
413
414 WordStarted := false;
415 DisplayedCharsSinceFontChange := false;
416 InsideDBC := false;
417
418 repeat
419 CurrentElement := ExtractNextTextElement( P, NextP );
420 assert( NextP > P );
421 CheckSpecialElementType( CurrentElement.Character, CurrentElement.ElementType, InsideDBC, Codepage ); // ALT
422
423 OnBreak := false;
424
425 case CurrentElement.ElementType of
426 teWordBreak:
427 begin
428 CurrentCharWidth := FFontManager.CharWidth( ' ' );
429 OnBreak := true;
430 InsideDBC := false;
431 end;
432
433 teLineBreak:
434 begin
435 DoLine( P, NextP, WordStartX + WordX );
436
437 // remember start of line
438 WordStart := NextP;
439 WordX := 0;
440 InsideDBC := false;
441
442 P := NextP;
443
444 continue;
445 end;
446
447 teTextEnd:
448 begin
449 DoLine( P, NextP, WordStartX + WordX );
450 InsideDBC := false;
451
452 // end of text, done
453 break;
454 end;
455
456 teImage:
457 begin
458 BitmapHeight := 0;
459 try
460 BitmapIndex := StrToInt( CurrentElement.Tag.Arguments );
461 except
462 BitmapIndex := -1;
463 end;
464 Bitmap := nil;
465 if IsValidBitmapIndex( BitmapIndex ) then
466 begin
467 Bitmap := FImages.GetBitmapReference( BitmapIndex );
468
469 CurrentCharWidth := Bitmap.Width
470 * FontWidthPrecisionFactor
471 * FHorizontalImageScale;
472
473 WordStarted := true;
474 BitmapHeight := Bitmap.Height * FVerticalImageScale;
475 end;
476
477 end;
478
479 teText:
480 begin
481 // Normal (non-leading-space) character
482 CurrentCharWidth := FFontManager.CharWidth( CurrentElement.Character );
483 WordStarted := true;
484 InsideDBC := false;
485 end;
486
487 // ALT begins
488 //
489 teWrapChar:
490 begin
491 // This is a legal break character, but not a space (so we still display it).
492 CurrentCharWidth := FFontManager.CharWidth( CurrentElement.Character );
493
494 // Treat as the start of a new word (for the sake of wrapping).
495 WordStarted := true;
496 inc( WordStartX, WordX + CurrentCharWidth );
497 WordX := 0;
498 WordStart := NextP;
499 end;
500
501 teLeadByte:
502 begin
503 // Leading byte of a double-byte character.
504 // Get the complete character width for our wrapping calculations.
505 if ( NextP > P ) then
506 CurrentCharWidth := FFontManager.CJKTextWidth( 2, P )
507 else
508 CurrentCharWidth := FFontManager.CJKCharWidth;
509 WordStarted := true;
510 end;
511
512 teSecondByte:
513 begin
514 // Secondary byte of a double-byte character.
515 // The full character width was already assigned to the leading byte.
516 CurrentCharWidth := 0;
517
518 // We treat each double-byte character as a complete word for the sake
519 // of the wrapping algorithm.
520 inc( LineWordsCompleted );
521 WordStarted := true;
522 inc( WordStartX, WordX + CurrentCharWidth );
523 WordX := 0;
524 WordStart := NextP;
525 end;
526 //
527 // ALT ends
528
529 teStyle:
530 begin
531 InsideDBC := false;
532 case CurrentElement.Tag.TagType of
533 ttBeginLink:
534 begin
535 CurrentLinkIndex := FLinks.Add( CurrentElement.Tag.Arguments );
536 P := NextP;
537 continue;
538 end;
539
540 ttEndLink:
541 begin
542 CurrentLinkIndex := -1;
543 P := NextP;
544 continue;
545 end;
546
547 ttSetLeftMargin: // SPECIAL CASE... could affect display immediately
548 begin
549 PerformStyleTag( CurrentElement.Tag,
550 Style,
551 WordstartX + WordX );
552 if Style.LeftMargin * FontWidthPrecisionFactor < WordStartX then
553 begin
554 // we're already past the margin being set
555
556 if pos( 'breakifpast', CurrentElement.Tag.Arguments ) > 0 then
557 begin
558 // this argument means, do a line break
559 // if the margin is already past
560 // Seems unusual for most purposes, but needed for IPF rendering.
561 DoLine( P, NextP, WordStartX + WordX );
562
563 // remember start of line
564 WordStart := NextP;
565 WordX := 0;
566
567 P := NextP;
568
569 continue;
570 end;
571
572 // so ignore it for now.
573 P := NextP;
574 continue;
575 end;
576
577 // skip across to the new margin
578 CurrentCharWidth := Style.LeftMargin * FontWidthPrecisionFactor
579 - WordStartX
580 - WordX;
581 // BUT! Don't treat it as a space, because you would not
582 // expect wrapping to take place in a margin change...
583 // at least not for IPF (NewView) :)
584
585 end;
586
587 else
588 begin
589 // before processing the tag see if font height needs updating
590 if DisplayedCharsSinceFontChange then
591 CheckFontHeights( CurrentLine );
592
593 if ( CurrentElement.Tag.TagType = ttItalicOff )
594 and ( faItalic in Style.Font.Attributes ) then
595 if not FFontManager.IsFixed then
596 // end of italic; add a space
597 inc( WordX, FFontManager.CharWidth( ' ' ) );
598
599 PerformStyleTag( CurrentElement.Tag,
600 Style,
601 WordX );
602
603 DisplayedCharsSinceFontChange := false;
604 P := NextP;
605 continue; // continue loop
606 end;
607 end;
608
609 end
610
611 end;
612
613 if OnBreak then
614 begin
615 // we just processed a space
616 if WordStarted then
617 begin
618 DisplayedCharsSinceFontChange := true;
619 // remember that we have now completed a word on this line
620 inc( LineWordsCompleted );
621 WordStarted := false;
622
623 // Add the word width, and the space width,
624 // to get the start of the next word
625 inc( WordStartX, WordX + CurrentCharWidth );
626 WordX := 0;
627
628 // remember the start of the next word
629 WordStart := NextP;
630
631 P := NextP;
632
633 continue;
634 end;
635 // else - starting spaces - fall through like normal char
636 end;
637
638 // if we're still going here we have a normal char
639 // (or leading spaces)
640
641 if not Style.Wrap then
642 begin
643 // No alignment
644 // We don't care about how wide it gets
645 inc( WordX, CurrentCharWidth );
646 DisplayedCharsSinceFontChange := true;
647
648 if CurrentElement.ElementType = teImage then
649 if Bitmap <> nil then
650 if BitmapHeight > CurrentLine.Height then
651 CurrentLine.Height := BitmapHeight;
652
653 P := NextP;
654 continue;
655 end;
656
657 DoWrap := false;
658
659 // Calculate position of end of character
660 // see if char would exceed width
661 if WordStartX
662 + WordX
663 + CurrentCharWidth
664 >= WrapX then
665 begin
666 // reached right hand side before finding end of word
667 if LineWordsCompleted > 0 then
668 // always wrap after at least one word displayed
669 DoWrap := true
670
671 else if ( CurrentElement.ElementType = teWrapChar ) or
672 ( CurrentElement.ElementType = teLeadByte ) then
673 DoWrap := true // ALT
674
675 else if ( not FRichTextSettings.AtLeastOneWordBeforeWrap ) then
676 // only wrap during the first word, if the "at least 1 word" flag is not set.
677 DoWrap := true;
678
679 end;
680
681 if DoWrap then
682 begin
683 if LineWordsCompleted = 0 then
684 begin
685 // the first word did not fit on the line. so draw
686 // as much as will fit
687 if WordX = 0 then
688 begin
689 // even the first char doesn't fit,
690 // but draw it anyway (otherwise, infinite loop)
691
692 NextElement := ExtractNextTextElement( NextP, NextP2 );
693
694 // ALT
695 if InsideDBC then
696 begin
697 // we're in the middle of a double-byte character, so keep the next byte too
698 InsideDBC := false;
699 NextP := NextP2;
700 NextElement := ExtractNextTextElement( NextP2, NextP3 );
701 NextP2 := NextP3;
702 end;
703 // /ALT
704
705 if NextElement.ElementType <> teLineBreak then
706 // there is still more on the line...
707 CurrentLine.Wrapped := true
708 else
709 // the line ends after this one char or image, we can skip the line end
710 NextP := NextP2;
711
712 if CurrentElement.ElementType = teImage then
713 begin
714 // the only thing on the line is the image. so check height
715 if Bitmap <> nil then
716 if BitmapHeight > CurrentLine.Height then
717 CurrentLine.Height := BitmapHeight;
718 end;
719
720 DoLine( NextP,
721 NextP,
722 WordStartX + WordX + CurrentCharWidth );
723 WordStart := NextP;
724
725 WordX := 0;
726 end
727 else
728 begin
729 CurrentLine.Wrapped := true;
730 // at least 1 char fits
731 // so draw up to, but not including this char
732 DoLine( P,
733 P,
734 WordStartX + WordX );
735 WordStart := P;
736 WordX := CurrentCharWidth;
737 end;
738 end
739 else
740 begin
741 // Normal wrap; at least one word fitted on the line
742 CurrentLine.Wrapped := true;
743
744
745 if ( CurrentElement.ElementType = teLeadByte ) or
746 ( CurrentElement.ElementType = teWrapChar ) then // ALT
747 begin
748 // draw up to but not including this 'word' (ALT)
749 DoLine( WordStart,
750 WordStart,
751 WordStartX );
752 end
753 else
754 begin // ALT
755 // take the width of the last space of the
756 // previous word off the line width
757 DoLine( WordStart, // current line ends at start of this word
758 WordStart, // next line starts at start of this word
759 WordStartX - FFontManager.CharWidth( ' ' ) );
760 if CurrentElement.ElementType = teImage then
761 if Bitmap <> nil then
762 if BitmapHeight > CurrentLine.Height then
763 CurrentLine.Height := BitmapHeight;
764 end; // ALT
765
766 // do NOT reset WordX to zero; as we are continuing
767 // from partway thru the word on the next line.
768 inc( WordX, CurrentCharWidth );
769 end;
770 WordStarted := true; // by definition, for wrapping
771 end
772 else
773 begin
774 // Character fits.
775 inc( WordX, CurrentCharWidth );
776
777 DisplayedCharsSinceFontChange := true;
778
779 if CurrentElement.ElementType = teImage then
780 if Bitmap <> nil then
781 if BitmapHeight > CurrentLine.Height then
782 CurrentLine.Height := BitmapHeight;
783 end;
784
785 P := NextP;
786
787 until false; // loop is exited by finding end of text
788
789 inc( FHeight, FRichTextSettings.Margins.Bottom );
790End;
791
792Function TRichTextLayout.GetStartX( Style: TTextDrawStyle;
793 Line: TLayoutLine ): longint;
794var
795 SpaceOnLine: longint;
796begin
797 case Style.Alignment of
798 taLeft:
799 Result := Style.LeftMargin * FontWidthPrecisionFactor;
800
801 taRight:
802 Result := Style.LeftMargin * FontWidthPrecisionFactor
803 + FLayoutWidth
804 - Style.RightMargin * FontWidthPrecisionFactor
805 - Line.Width;
806
807 taCenter:
808 begin
809 // |<------layout width------------------>|
810 // | |
811 // |<-lm->[aaaaaaaaaaaaaaa]<-space-><-rm->|
812 // |<-----line width------> |
813 // space = layoutw-rm-linew
814 SpaceOnLine := FLayoutWidth
815 - Style.RightMargin * FontWidthPrecisionFactor
816 - Line.Width; // Note: line width includes left margin
817 Result := Style.LeftMargin * FontWidthPrecisionFactor
818 + SpaceOnLine div 2;
819 end;
820 end;
821end;
822
823Procedure TRichTextLayout.GetOffsetFromX( const XToFind: longint;
824 const LineIndex: longint;
825 Var Offset: longint;
826 Var Link: string );
827Var
828 X: longint;
829 P: PChar;
830 NextP: PChar;
831 EndP: PChar;
832 Element: TTextElement;
833 CurrentLink: string;
834 Line: TLayoutLine;
835 Style: TTextDrawStyle;
836 NewMarginX: longint;
837 StartedDrawing: boolean;
838 InsideDBC: boolean; // ALT
839begin
840 Line := FLines[ LineIndex ];
841 P := Line.Text;
842 EndP := Line.Text + Line.Length;
843
844 Style := Line.Style;
845 FFontManager.SetFont( Style.Font );
846
847 StartedDrawing := false;
848 InsideDBC := false; // ALT
849
850 Link := '';
851 if Line.LinkIndex <> -1 then
852 CurrentLink := FLinks[ Line.LinkIndex ]
853 else
854 CurrentLink := '';
855
856 while P < EndP do
857 begin
858 Element := ExtractNextTextElement( P, NextP );
859
860 // ALT - handle double-byte characters
861 if InsideDBC and ( P < EndP ) then
862 begin
863 P := NextP;
864 Element := ExtractNextTextElement( P, NextP );
865 InsideDBC := false;
866 end;
867 CheckSpecialElementType( Element.Character, Element.ElementType, InsideDBC, Codepage );
868 // ALT done
869
870 case Element.ElementType of
871 teWordBreak,
872 teText,
873 teLeadByte, // ALT
874 teWrapChar, // ALT
875 teImage:
876 begin
877 if not StartedDrawing then
878 begin
879 // we haven't yet started drawing:
880 // so work out alignment
881 X := GetStartX( Style, Line );
882
883 if X div FontWidthPrecisionFactor
884 > XToFind then
885 begin
886 // found before the start of the line
887 // don't set link
888 Offset := 0;
889 exit;
890 end;
891
892 StartedDrawing := true;
893
894 end;
895
896 // Now find out how wide the thing is
897 if (( Element.ElementType = teLeadByte ) And ( EndP > P )) then // ALT
898 inc( X, FFontManager.CJKTextWidth( 2, P ))
899 else
900 inc( X, GetElementWidth( Element ) );
901
902 if X div FontWidthPrecisionFactor
903 > XToFind then
904 begin
905 // found
906 Offset := PCharPointerDiff( P, Line.Text );
907 Link := CurrentLink;
908 exit;
909 end;
910
911 end;
912
913 teStyle:
914 case Element.Tag.TagType of
915 ttBeginLink:
916 CurrentLink := Element.Tag.Arguments;
917 ttEndLink:
918 CurrentLink := '';
919 else
920 begin
921 if ( Element.Tag.TagType = ttItalicOff )
922 and ( faItalic in Style.Font.Attributes )
923 and ( not FFontManager.IsFixed ) then
924 // end of italic; add a space
925 inc( X, FFontManager.CharWidth( ' ' ) );
926
927 PerformStyleTag( Element.Tag,
928 Style,
929 X );
930 NewMarginX := Style.LeftMargin * FontWidthPrecisionFactor;
931 if NewMarginX > X then
932 begin
933 //skip across...
934 X := NewMarginX;
935 end;
936 end;
937 end;
938 end;
939
940 P := NextP;
941 end;
942 Offset := Line.Length;
943end;
944
945Procedure TRichTextLayout.GetXFromOffset( const Offset: longint;
946 const LineIndex: longint;
947 Var X: longint );
948Var
949 P: PChar;
950 NextP: PChar;
951 EndP: PChar;
952 Element: TTextElement;
953 StartedDrawing: boolean;
954 Line: TLayoutLine;
955 Style: TTextDrawStyle;
956 NewMarginX: longint;
957 InsideDBC: Boolean; // ALT
958begin
959 Line := FLines[ LineIndex ];
960 P := Line.Text;
961 EndP := Line.Text + Line.Length;
962
963 Style := Line.Style;
964 FFontManager.SetFont( Style.Font );
965
966 StartedDrawing := false;
967 InsideDBC := false; // ALT
968
969 while P < EndP do
970 begin
971 Element := ExtractNextTextElement( P, NextP );
972 CheckSpecialElementType( Element.Character, Element.ElementType, InsideDBC, Codepage ); // ALT
973
974 case Element.ElementType of
975 teWordBreak,
976 teText,
977 teWrapChar, // ALT
978 teLeadByte, // ALT
979 teImage:
980 begin
981 if not StartedDrawing then
982 begin
983 // we haven't yet started drawing:
984 // so work out alignment
985 X := GetStartX( Style, Line );
986 StartedDrawing := true;
987 end;
988
989 if GetCharIndex( P ) - GetCharIndex( Line.Text ) >= Offset then
990 begin
991 X := X div FontWidthPrecisionFactor;
992 // found
993 exit;
994 end;
995
996 // Now find out how wide the thing is
997 if (( Element.ElementType = teLeadByte ) And ( EndP > P )) then // ALT
998 inc( X, FFontManager.CJKTextWidth( 2, P ))
999 else
1000 inc( X, GetElementWidth( Element ) );
1001
1002 end;
1003
1004 // teSecondByte: do nothing and continue to next byte (ALT)
1005
1006 teStyle:
1007 begin
1008 if ( Element.Tag.TagType = ttItalicOff )
1009 and ( faItalic in Style.Font.Attributes )
1010 and ( not FFontManager.IsFixed ) then
1011 // end of italic; add a space
1012 inc( X, FFontManager.CharWidth( ' ' ) );
1013
1014 PerformStyleTag( Element.Tag,
1015 Style,
1016 X );
1017
1018 NewMarginX := Style.LeftMargin * FontWidthPrecisionFactor;
1019 if NewMarginX > X then
1020 begin
1021 //skip across...
1022 X := NewMarginX;
1023 end;
1024 end;
1025 end;
1026
1027 P := NextP;
1028 end;
1029 // went thru the whole line without finding the point,
1030 if not StartedDrawing then
1031 X := GetStartX( Style, Line );
1032
1033 X := X div FontWidthPrecisionFactor;
1034end;
1035
1036function TRichTextLayout.GetLineFromPosition( YToFind: longint;
1037 Var LineIndex: longint;
1038 Var Remainder: longint ): TTextPosition;
1039var
1040 Y: longint;
1041 LineHeight: longint;
1042begin
1043 LineIndex := 0;
1044 Remainder := 0;
1045
1046 Y := FRichTextSettings.Margins.Top;
1047
1048 if YToFind < Y then
1049 begin
1050 Result := tpAboveText;
1051 exit;
1052 end;
1053
1054 while LineIndex < FNumLines do
1055 begin
1056 LineHeight := FLines[ LineIndex ].Height;
1057 if ( YToFind >= Y )
1058 and ( YToFind < Y + LineHeight ) then
1059 begin
1060 // YToFind is within the line
1061 Result := tpWithinText;
1062 Remainder := YToFind - Y;
1063 exit;
1064 end;
1065
1066 inc( Y, FLines[ LineIndex ].Height );
1067 inc( LineIndex );
1068 end;
1069
1070 LineIndex := FNumLines - 1;
1071 Remainder := FLines[ LineIndex ].Height;
1072
1073 Result := tpBelowText;
1074end;
1075
1076function TRichTextLayout.FindPoint( XToFind, YToFind: longint;
1077 Var LineIndex: longint;
1078 Var Offset: longint;
1079 Var Link: string ): TTextPosition;
1080var
1081 Remainder: longint;
1082begin
1083 Link := '';
1084 Result := GetLineFromPosition( YToFind,
1085 LineIndex,
1086 Remainder );
1087 case Result of
1088 tpAboveText:
1089 begin
1090 Offset := 0;
1091 exit;
1092 end;
1093
1094 tpBelowText:
1095 begin
1096 Offset := FLines[ LineIndex ].Length;
1097 exit;
1098 end;
1099 end;
1100
1101 // found the line
1102 GetOffsetFromX( XToFind,
1103 LineIndex,
1104 Offset,
1105 Link );
1106end;
1107
1108function TRichTextLayout.GetLineFromCharIndex( Index: longint ): longint;
1109var
1110 LineCharIndex: longint;
1111 LineLength: longint;
1112begin
1113 Result := 0;
1114 if Index <= 0 then
1115 exit;
1116
1117 while Result < FNumLines do
1118 begin
1119 LineCharIndex := GetCharIndex( FLines[ Result ].Text );
1120 LineLength := FLines[ Result ].Length;
1121 if LineCharIndex + LineLength
1122 > Index then
1123 begin
1124 // found
1125 exit;
1126 end;
1127 inc( Result );
1128 end;
1129 Result := FNumLines - 1;
1130end;
1131
1132function TRichTextLayout.GetOffsetFromCharIndex( Index: longint;
1133 Line: longint ): longint;
1134begin
1135 Result := Index - GetCharIndex( TLayoutLine( FLines[ Line ] ).Text );
1136end;
1137
1138function TRichTextLayout.GetElementWidth( Element: TTextElement ): longint;
1139var
1140 Bitmap: TBItmap;
1141 BitmapIndex: longint;
1142begin
1143 // Now find out how wide the thing is
1144 case Element.ElementType of
1145 teImage:
1146 begin
1147 try
1148 BitmapIndex := StrToInt( Element.Tag.Arguments );
1149 except
1150 BitmapIndex := -1;
1151 end;
1152 if IsValidBitmapIndex( BitmapIndex ) then
1153 begin
1154 Bitmap := FImages.GetBitmapReference( BitmapIndex );
1155 Result := Bitmap.Width
1156 * FontWidthPrecisionFactor
1157 * FHorizontalImageScale;
1158 end;
1159 end;
1160
1161 teText, teWordBreak, teWrapChar: // ALT
1162 Result := FFontManager.CharWidth( Element.Character );
1163
1164 teLeadByte: // ALT - should not be reached
1165 Result := FFontManager.CJKCharWidth;
1166
1167 teSecondByte: // ALT
1168 Result := 0;
1169
1170 else
1171 Assert( False ); // should never be trying to find the width of a style, etc
1172
1173 end;
1174end;
1175
1176Function TRichTextLayout.GetCharIndex( P: PChar ): longint;
1177begin
1178 Result := PCharPointerDiff( P, FText );
1179end;
1180
1181function TRichTextLayout.GetLinePosition( Line: longint ): longint;
1182begin
1183 Result := FRichTextSettings.Margins.Top;
1184 dec( line );
1185 while line >= 0 do
1186 begin
1187 inc( Result,
1188 Flines[ Line ].Height );
1189 dec( line );
1190 end;
1191end;
1192
1193function TRichTextLayout.LinkFromIndex( const CharIndexToFind: longint): string;
1194Var
1195 P: PChar;
1196 NextP: PChar;
1197 EndP: PChar;
1198 Element: TTextElement;
1199 LineIndex: longint;
1200 Line: TLayoutLine;
1201begin
1202 if FNumLines = 0 then
1203 begin
1204 Result := '';
1205 exit;
1206 end;
1207
1208 LineIndex := GetLineFromCharIndex( CharIndexToFind );
1209
1210 Line := FLines[ LineIndex ];
1211 P := Line.Text;
1212 EndP := Line.Text + Line.Length;
1213
1214 if Line.LinkIndex <> -1 then
1215 Result := FLinks[ Line.LinkIndex ]
1216 else
1217 Result := '';
1218
1219 while P < EndP do
1220 begin
1221 if GetCharIndex( P ) >= CharIndexToFind then
1222 exit;
1223
1224 Element := ExtractNextTextElement( P, NextP );
1225
1226 case Element.ElementType of
1227 teStyle:
1228 case Element.Tag.TagType of
1229 ttBeginLink:
1230 Result := Element.Tag.Arguments;
1231 ttEndLink:
1232 Result := '';
1233 end;
1234 end;
1235
1236 P := NextP;
1237 end;
1238end;
1239
1240// ALT begins
1241//
1242// Given a text position, check if it's in the middle of a double-byte
1243// character; if so, shift the position one byte forwards or backwards.
1244//
1245function TRichTextLayout.FindCharacterBoundary( TextPointer: PChar; // pointer to text
1246 var Index: LongInt; // position (byte index) within text
1247 Advance: Boolean; // whether to adjust position forward
1248 ): LongInt; // returns new offset within line
1249var
1250 P: PChar; // pointer to current character in string
1251 NextP: PChar; // pointer to the following character, if any
1252 Element: TTextElement; // element data about the current character
1253 CurrentPos: LongInt; // index of first character of line
1254 Line: LongInt; // current line number
1255 Offset: LongInt; // offset position within current line
1256 InsideDBC: boolean;
1257begin
1258 if ( Offset > 0 ) and
1259 ( Codepage in [ 932, 936, 942, 943, 949, 950, 1381, 1386 ]) then
1260 begin
1261 // Because parsing of byte types is state based, we must verify every
1262 // byte's type from the beginning of the line until we reach the target.
1263 //
1264 Line := GetLineFromCharIndex( Index );
1265 CurrentPos := GetCharIndex( FLines[ Line ].Text );
1266 P := TextPointer + CurrentPos;
1267 Offset := 0;
1268 InsideDBC := false;
1269
1270 while CurrentPos < Index do
1271 begin
1272 Element := ExtractNextTextElement( P, NextP );
1273 CheckSpecialElementType( Element.Character, Element.ElementType, InsideDBC, Codepage );
1274 CurrentPos := CurrentPos + PCharPointerDiff( NextP, P );
1275 Offset := Offset + PCharPointerDiff( NextP, P );
1276 P := NextP;
1277 end;
1278
1279 // We've reached the target position, and the current parsing state should
1280 // be correctly set. So now we can safely determine the target byte's type.
1281 //
1282 Element := ExtractNextTextElement( P, NextP );
1283 CheckSpecialElementType( Element.Character, Element.ElementType, InsideDBC, Codepage );
1284
1285 if Element.ElementType = teSecondByte then
1286 begin
1287 // If we are inside a a double byte character, shift position by one.
1288 if Advance Then
1289 Begin
1290 inc( Index );
1291 inc( Offset );
1292 End
1293 Else
1294 Begin
1295 dec( Index );
1296 dec( Offset );
1297 End
1298 end;
1299
1300 end;
1301
1302 Result := Offset;
1303end;
1304//
1305// ALT ends
1306
1307Initialization
1308End.
1309
Note: See TracBrowser for help on using the repository browser.