|
@@ -0,0 +1,1861 @@
|
|
|
+unit Fresnel.TextLayouter;
|
|
|
+
|
|
|
+{$mode objfpc}
|
|
|
+{$modeswitch advancedrecords}
|
|
|
+
|
|
|
+interface
|
|
|
+
|
|
|
+uses
|
|
|
+{$IFDEF FPC_DOTTEDUNITS}
|
|
|
+ System.Classes, System.SysUtils, System.Types, System.Contnrs, fpImage, System.UITypes;
|
|
|
+{$ELSE}
|
|
|
+ Classes, SysUtils, Types, Contnrs, fpImage, System.UITypes;
|
|
|
+{$ENDIF}
|
|
|
+
|
|
|
+Const
|
|
|
+ cEllipsis : UnicodeChar = #$2026; // '…';
|
|
|
+
|
|
|
+Type
|
|
|
+ {$IF SIZEOF(CHAR)=1}
|
|
|
+ TTextString = UTF8String;
|
|
|
+ {$ELSE}
|
|
|
+ TTextString = UnicodeString;
|
|
|
+ {$ENDIF}
|
|
|
+
|
|
|
+ ETextLayout = Class(Exception);
|
|
|
+
|
|
|
+ TWordOverflow = (woTruncate, // truncate the word
|
|
|
+ woOverflow, // Allow to overflow
|
|
|
+ woAsterisk, // Replace word with * chars.
|
|
|
+ woEllipsis // Truncate word, add ... ellipsis.
|
|
|
+ );
|
|
|
+ TTextAlign = (Leading, Center, Trailing);
|
|
|
+ TStretchMode = (smDontStretch, smActualHeight, smActualHeightStretchOnly, smActualHeightShrinkOnly, smMaxHeight);
|
|
|
+ TOverlappingRangesAction = (oraError,oraFit);
|
|
|
+ TCullThreshold = 1..100;
|
|
|
+
|
|
|
+ TTextUnits = single;
|
|
|
+
|
|
|
+ { No hyphenation:
|
|
|
+
|
|
|
+ 1 5 10 15
|
|
|
+ the cat saw me
|
|
|
+ ^ ^ ^ ^
|
|
|
+ 4 split points:
|
|
|
+ offset: 3, Whitespace 2
|
|
|
+ offset: 9, Whitespace: 1
|
|
|
+ offset: 13, Whitespace: 3
|
|
|
+ offset: 18, Whitespace: 1 (#0 considered whitespace)
|
|
|
+
|
|
|
+ With hyphenation:
|
|
|
+
|
|
|
+ 1 5 10
|
|
|
+ orthography
|
|
|
+ ^ ^ ^ ^ (or–thog–ra–phy)
|
|
|
+ 4 split points:
|
|
|
+ offset 2, whitespace 0
|
|
|
+ offset 6, whitespace 0
|
|
|
+ offset 8, whitespace 0
|
|
|
+ offset 11, whitespace 1 (#0 considered whitespace)
|
|
|
+ }
|
|
|
+
|
|
|
+ { TTextSplitPoint }
|
|
|
+
|
|
|
+ TTextSplitPoint = record
|
|
|
+ // 0-based, relative to origin.
|
|
|
+ offset : SizeInt;
|
|
|
+ // number of whitespace characters at the start of the split point
|
|
|
+ whitespace : SizeInt; // when zero, it is a hyphenation point.
|
|
|
+ Constructor Create(aOffset,aWhiteSpace : SizeInt);
|
|
|
+ end;
|
|
|
+ TTextSplitPointArray = Array of TTextSplitPoint;
|
|
|
+
|
|
|
+ TTextMeasures = record
|
|
|
+ Width, Height : TTextUnits;
|
|
|
+ Descender : TTextUnits;
|
|
|
+ end;
|
|
|
+ TTextPoint = {$IFDEF FPC_DOTTEDUNITS}System.{$ENDIF}Types.TPointF;
|
|
|
+
|
|
|
+ TFontAttribute = (faBold,faItalic,faStrikeThrough);
|
|
|
+ TFontAttributes = set of TFontAttribute;
|
|
|
+
|
|
|
+ { TTextFont }
|
|
|
+
|
|
|
+ TTextFont = Class(TPersistent)
|
|
|
+ private
|
|
|
+ FOwner : TPersistent;
|
|
|
+ FAttrs: TFontAttributes;
|
|
|
+ FName: string;
|
|
|
+ FSize: Smallint;
|
|
|
+ FColor : TFPColor;
|
|
|
+ function GetColor: TColor;
|
|
|
+ procedure SetAttrs(AValue: TFontAttributes);
|
|
|
+ procedure SetColor(AValue: TColor);
|
|
|
+ procedure SetFPColor(AValue: TFPColor);
|
|
|
+ procedure SetName(AValue: string);
|
|
|
+ procedure SetSize(AValue: Smallint);
|
|
|
+ Public
|
|
|
+ procedure Assign(Source: TPersistent); override;
|
|
|
+ Constructor Create(aOwner : TPersistent); virtual;
|
|
|
+ procedure Changed;virtual;
|
|
|
+ Function Clone(aOwner : TPersistent=nil): TTextFont;
|
|
|
+ Property FPColor : TFPColor Read FColor write SetFPColor;
|
|
|
+ Published
|
|
|
+ // In name
|
|
|
+ Property Name : string Read FName Write SetName;
|
|
|
+ // In pixels
|
|
|
+ Property Size : Smallint Read FSize write SetSize;
|
|
|
+ // attributes
|
|
|
+ Property Attrs : TFontAttributes read FAttrs Write SetAttrs;
|
|
|
+ // Color. Not needed for calculations, but allows for easier management
|
|
|
+ Property Color : TColor read GetColor Write SetColor;
|
|
|
+ end;
|
|
|
+ TTextFontClass = class of TTextFont;
|
|
|
+
|
|
|
+ TTextLayouter = Class;
|
|
|
+ TTextMeasurer = class;
|
|
|
+ TTextSplitter = Class;
|
|
|
+
|
|
|
+ { TTextSplitter }
|
|
|
+
|
|
|
+ TTextSplitter = class (TObject)
|
|
|
+ private
|
|
|
+ FLayouter: TTextLayouter;
|
|
|
+ Public
|
|
|
+ Constructor Create(aLayouter : TTextLayouter); virtual;
|
|
|
+ // Get next newline, The returned position is relative to StartPos (1-based). Offset=-1 means no newline
|
|
|
+ Function GetNextNewLine(const aText : TTextString; aStartPos : SizeInt) : TTextSplitPoint; virtual;
|
|
|
+ // The returned split point is relative text origin (which is 1-based).
|
|
|
+ Function GetNextSplitPoint(const aText : TTextString; aStartPos : SizeInt; aAllowHyphen : Boolean) : TTextSplitPoint; virtual;
|
|
|
+ // Return all possible split points for a text. aStartpos is 1-Based
|
|
|
+ Function SplitText(const aText : TTextString; aStartPos : SizeInt; aAllowHyphen : Boolean) : TTextSplitPointArray; virtual;
|
|
|
+ // Return all possible lines for a text. aStartpos is 1-Based
|
|
|
+ Function SplitLines(const aText : TTextString; aStartPos : SizeInt; aAllowHyphen : Boolean) : TTextSplitPointArray; virtual;
|
|
|
+ // Layouter
|
|
|
+ Property Layouter : TTextLayouter Read FLayouter;
|
|
|
+ end;
|
|
|
+ TTextSplitterClass = Class of TTextSplitter;
|
|
|
+
|
|
|
+ { TTextMeasurer }
|
|
|
+
|
|
|
+
|
|
|
+ TTextMeasurer = class abstract (TObject)
|
|
|
+ private
|
|
|
+ FLayouter: TTextLayouter;
|
|
|
+ FWhiteSpaceWidth : TTextUnits;
|
|
|
+ public
|
|
|
+ Constructor Create(aLayouter : TTextLayouter); virtual;
|
|
|
+ // Font size in points.
|
|
|
+ Procedure SetFont(const aFontName : String; aSize : SmallInt; Attrs : TFontAttributes); virtual; abstract;
|
|
|
+ Procedure SetFont(const aFont: TTextFont);
|
|
|
+ Function MeasureText(aText : String) : TTextMeasures; virtual; abstract;
|
|
|
+ Function WhitespaceWidth : TTextUnits;
|
|
|
+ Property Layouter : TTextLayouter Read FLayouter;
|
|
|
+
|
|
|
+ end;
|
|
|
+ TTextMeasurerClass = Class of TTextMeasurer;
|
|
|
+
|
|
|
+ { TFixedSizeTextMeasurer }
|
|
|
+
|
|
|
+ TFixedSizeTextMeasurer = Class(TTextMeasurer)
|
|
|
+ private
|
|
|
+ FHeight: TTextUnits;
|
|
|
+ FWidth: TTextUnits;
|
|
|
+ FSize : SmallInt;
|
|
|
+ FAttrs : TFontAttributes;
|
|
|
+ FFontName : String;
|
|
|
+ Public
|
|
|
+ Constructor Create(aLayouter : TTextLayouter); override;
|
|
|
+ Procedure SetFont(const aFontName : String; aSize : SmallInt; aAttrs : TFontAttributes); override;
|
|
|
+ Function MeasureText(aText : String) : TTextMeasures; override;
|
|
|
+ Property CharHeight : TTextUnits Read FHeight Write FHeight;
|
|
|
+ Property CharWidth : TTextUnits Read FWidth Write FWidth;
|
|
|
+ Property Size : SmallInt Read FSize;
|
|
|
+ Property Attributes : TFontAttributes Read FAttrs;
|
|
|
+ Property FontName : String Read FFontName;
|
|
|
+ end;
|
|
|
+
|
|
|
+
|
|
|
+ { TTextBlock }
|
|
|
+
|
|
|
+ TTextBlock = Class(TObject)
|
|
|
+ private
|
|
|
+ FLayouter : TTextLayouter;
|
|
|
+ function GetText: TTextString;
|
|
|
+
|
|
|
+ public
|
|
|
+ LayoutPos: TTextPoint;
|
|
|
+ Size : TTextMeasures;
|
|
|
+ Font: TTextFont;
|
|
|
+ // Zero based
|
|
|
+ TextOffset : Integer;
|
|
|
+ TextLen : Integer;
|
|
|
+ ForceNewLine : Boolean;
|
|
|
+ Suffix : String;
|
|
|
+ Public
|
|
|
+ Constructor Create(aLayouter : TTextLayouter); overload;virtual;
|
|
|
+ Constructor Create(aLayouter : TTextLayouter; aOffset,aLen : SizeInt); overload;
|
|
|
+ // At pos is relative to the text here, zero based
|
|
|
+ function Split(atPos : integer) : TTextBlock; virtual;
|
|
|
+ procedure Assign(aBlock : TTextBlock); virtual;
|
|
|
+ function ToString : RTLString; override;
|
|
|
+ Procedure TrimTrailingWhiteSpace;
|
|
|
+ Property Text : TTextString Read GetText;
|
|
|
+ Property Layouter : TTextLayouter Read FLayouter;
|
|
|
+ Property Width : TTextUnits Read Size.Width;
|
|
|
+ Property Height : TTextUnits Read Size.Height;
|
|
|
+ Property Descender : TTextUnits Read Size.Descender;
|
|
|
+ end;
|
|
|
+ TTextBlockClass = Class of TTextBlock;
|
|
|
+
|
|
|
+ { FTextBlockList }
|
|
|
+
|
|
|
+ { TTextBlockList }
|
|
|
+
|
|
|
+ TTextBlockList = Class(TFPObjectList)
|
|
|
+ private
|
|
|
+ function GetBlock(aIndex : Integer): TTextBlock;
|
|
|
+ public
|
|
|
+ Property Block [aIndex : Integer] : TTextBlock Read GetBlock; default;
|
|
|
+ end;
|
|
|
+
|
|
|
+
|
|
|
+ { TTextRange }
|
|
|
+
|
|
|
+ TTextRange = class(TCollectionItem)
|
|
|
+ private
|
|
|
+ FCharLength: SizeInt;
|
|
|
+ FCharOffset: SizeInt;
|
|
|
+ FFont: TTextFont;
|
|
|
+ procedure SetCharLength(AValue: SizeInt);
|
|
|
+ procedure SetCharOffSet(AValue: SizeInt);
|
|
|
+ procedure SetFont(AValue: TTextFont);
|
|
|
+ Protected
|
|
|
+ function CreateTextFont: TTextFont; virtual;
|
|
|
+ Public
|
|
|
+ constructor Create(ACollection: TCollection); override;
|
|
|
+ destructor destroy; override;
|
|
|
+ procedure Assign(Source: TPersistent); override;
|
|
|
+ Procedure Changed;
|
|
|
+ function ToString : RTLString; override;
|
|
|
+ Published
|
|
|
+ // Offset is 0 based and is the offset from the first character in the text.
|
|
|
+ Property CharOffset : SizeInt Read FCharOffset Write SetCharOffSet;
|
|
|
+ // Number of characters to count from offset
|
|
|
+ Property CharLength : SizeInt Read FCharLength Write SetCharLength;
|
|
|
+ // Name of font
|
|
|
+ Property Font : TTextFont Read FFont Write SetFont;
|
|
|
+ end;
|
|
|
+ TTextRangeClass = Class of TTextRange;
|
|
|
+
|
|
|
+ { TTextRangeList }
|
|
|
+
|
|
|
+ TTextRangeList = class (TOwnedCollection)
|
|
|
+ private
|
|
|
+ function GetRange(aIndex : integer): TTextRange;
|
|
|
+ procedure SetRange(aIndex : integer; AValue: TTextRange);
|
|
|
+ Public
|
|
|
+ Function AddRange(aOffset,aCharlength : SizeInt; aFont : TTextFont) : TTextRange;
|
|
|
+ Property Ranges[aIndex : integer] : TTextRange Read GetRange Write SetRange; default;
|
|
|
+ end;
|
|
|
+
|
|
|
+ { TTextLayoutBounds }
|
|
|
+
|
|
|
+ TTextLayoutBounds = Class(TPersistent)
|
|
|
+ private
|
|
|
+ FHeight: TTextUnits;
|
|
|
+ FWidth: TTextUnits;
|
|
|
+ FLayouter : TTextLayouter;
|
|
|
+ function GetAsPoint: TTextPoint;
|
|
|
+ procedure SetAsPoint(AValue: TTextPoint);
|
|
|
+ procedure SetHeight(AValue: TTextUnits);
|
|
|
+ procedure SetWidth(AValue: TTextUnits);
|
|
|
+ protected
|
|
|
+ procedure Changed; virtual;
|
|
|
+ function GetOwner: TPersistent; override;
|
|
|
+ public
|
|
|
+ procedure Assign(Source: TPersistent); override;
|
|
|
+ Constructor Create(aLayouter : TTextLayouter);
|
|
|
+ Property AsPoint : TTextPoint Read GetAsPoint Write SetAsPoint;
|
|
|
+ Published
|
|
|
+ Property Width : TTextUnits Read FWidth Write SetWidth;
|
|
|
+ Property Height : TTextUnits Read FHeight Write SetHeight;
|
|
|
+ end;
|
|
|
+
|
|
|
+ { TTextLayouter }
|
|
|
+
|
|
|
+ TTextLayouter = class (TComponent)
|
|
|
+ Private
|
|
|
+ FAllowHyphenation: Boolean;
|
|
|
+ FCullTreshold: TCullThreshold;
|
|
|
+ FHorizontalAlign: TTextAlign;
|
|
|
+ FHyphenationChar: String;
|
|
|
+ FLineSpacing: TTextUnits;
|
|
|
+ FBounds: TTextLayoutBounds;
|
|
|
+ FMaxStretch: TTextUnits;
|
|
|
+ FOverlappingRangesAction: TOverlappingRangesAction;
|
|
|
+ FRanges: TTextRangeList;
|
|
|
+ FStretchMode: TStretchMode;
|
|
|
+ FText: string;
|
|
|
+ FTextRanges: TTextRangeList;
|
|
|
+ FVerticalAlign: TTextAlign;
|
|
|
+ FWordOverFlow: TWordOverflow;
|
|
|
+ FBlocks : TTextBlockList;
|
|
|
+ FWordWrap: Boolean;
|
|
|
+ FFont : TTextFont;
|
|
|
+ FMeasurer : TTextMeasurer;
|
|
|
+ FSplitter : TTextSplitter;
|
|
|
+ function FindLastFittingCharPos(B: TTextBlock; const aSuffix: String; out aWidth : TTextUnits): Integer;
|
|
|
+ function GetBlock(aIndex : Integer): TTextBlock;
|
|
|
+ function GetBlockCount: Integer;
|
|
|
+ function GetColor: TFPColor;
|
|
|
+ procedure SetAllowHyphenation(AValue: Boolean);
|
|
|
+ procedure SetColor(AValue: TFPColor);
|
|
|
+ procedure SetCullTreshold(AValue: TCullThreshold);
|
|
|
+ procedure SetFont(AValue: TTextFont);
|
|
|
+ procedure SetHorizontalAlign(AValue: TTextAlign);
|
|
|
+ procedure SetHyphenationChar(AValue: String);
|
|
|
+ procedure SetLineSpacing(AValue: TTextUnits);
|
|
|
+ procedure SetBounds(AValue: TTextLayoutBounds);
|
|
|
+ procedure SetMaxStretch(AValue: TTextUnits);
|
|
|
+ procedure SetRanges(AValue: TTextRangeList);
|
|
|
+ procedure SetStretchMode(AValue: TStretchMode);
|
|
|
+ procedure SetText(AValue: string);
|
|
|
+ procedure SetTextRanges(AValue: TTextRangeList);
|
|
|
+ procedure SetVerticalAlign(AValue: TTextAlign);
|
|
|
+ procedure SetWordOverFlow(AValue: TWordOverflow);
|
|
|
+ procedure SetWordWrap(AValue: Boolean);
|
|
|
+ Protected
|
|
|
+ class Function CreateMeasurer(aLayouter : TTextLayouter): TTextMeasurer; virtual;
|
|
|
+ class Function CreateSplitter(aLayouter : TTextLayouter): TTextSplitter; virtual;
|
|
|
+ class function CreateRanges(aLayouter: TTextLayouter): TTextRangeList; virtual;
|
|
|
+ class function CreateBlock(aLayouter: TTextLayouter; aOffset,aLength : SizeInt) : TTextBlock; virtual;
|
|
|
+ function FindWrapPosition(B: TTextBlock; S: String; var aPos: integer; var CurrPos: TTextPoint): Boolean;
|
|
|
+ function AddBlock(aOffset, aLength: SizeInt; aFont: TTextFont): TTextBlock; virtual;
|
|
|
+ procedure ApplyStretchMode(const ADesiredHeight: TTextUnits); virtual;
|
|
|
+ function WrapBlock(B: TTextBlock; S: String; var Idx: integer; var CurrPos: TTextPoint) : Boolean; virtual;
|
|
|
+ procedure CullTextHorizontally(B: TTextBlock);
|
|
|
+ procedure HandleRanges; virtual;
|
|
|
+ procedure HandleNewLines; virtual;
|
|
|
+ // Apply vertical text alignment
|
|
|
+ procedure ApplyVertTextAlignment;
|
|
|
+ // Apply horizontal text alignment
|
|
|
+ procedure ApplyHorzTextAlignment;
|
|
|
+ // Remove text that falls outside bounds vertically.
|
|
|
+ procedure CullTextOutOfBoundsVertically;
|
|
|
+ // Handle text that falls outside bounds horizontally, depending on WordOverFlow.
|
|
|
+ procedure CullTextOutOfBoundsHorizontally;
|
|
|
+ // Return true if a split occurred.
|
|
|
+ function WrapLayout: Boolean; virtual;
|
|
|
+ // Return True if there are multiple lines.
|
|
|
+ function NoWrapLayout: Boolean; virtual;
|
|
|
+
|
|
|
+ Property Measurer : TTextMeasurer Read FMeasurer;
|
|
|
+ Property Splitter : TTextSplitter Read FSplitter;
|
|
|
+ Public
|
|
|
+ class var _TextMeasurerClass : TTextMeasurerClass;
|
|
|
+ class var _TextSplitterClass : TTextSplitterClass;
|
|
|
+ class var _TextRangeClass : TTextRangeClass;
|
|
|
+ class var _TextBlockClass : TTextBlockClass;
|
|
|
+ Public
|
|
|
+ Constructor Create(aOwner: TComponent); override;
|
|
|
+ Destructor Destroy; override;
|
|
|
+ // Clear block list.
|
|
|
+ Procedure Reset;
|
|
|
+ // Check if ranges do not overlap.
|
|
|
+ procedure CheckRanges;
|
|
|
+ function ToString : RTLString; override;
|
|
|
+ Property TextBlocks[aIndex : Integer] : TTextBlock Read GetBlock;
|
|
|
+ Property TextBlockCount : Integer Read GetBlockCount;
|
|
|
+ function Execute : integer; virtual;
|
|
|
+ function Execute(const aText : String) : Integer;
|
|
|
+ // Color of font
|
|
|
+ Property FPColor : TFPColor Read GetColor Write SetColor;
|
|
|
+ Published
|
|
|
+ // Setting text will clear attribute
|
|
|
+ Property Text : string Read FText Write SetText;
|
|
|
+ // Various properties
|
|
|
+ Property Ranges : TTextRangeList Read FRanges Write SetRanges;
|
|
|
+ // Do wordwrap ?
|
|
|
+ Property WordWrap : Boolean Read FWordWrap Write SetWordWrap;
|
|
|
+ // What to do in case of overflow ?
|
|
|
+ Property WordOverflow : TWordOverflow Read FWordOverFlow Write SetWordOverFlow;
|
|
|
+ // Allow to stretch maximum size ?
|
|
|
+ Property StretchMode : TStretchMode Read FStretchMode Write SetStretchMode;
|
|
|
+ // When to cull letters
|
|
|
+ Property CullThreshold : TCullThreshold Read FCullTreshold Write SetCullTreshold;
|
|
|
+ // Text ranges with different properties than the main properties.
|
|
|
+ Property TextRanges : TTextRangeList Read FTextRanges Write SetTextRanges;
|
|
|
+ // Name of font
|
|
|
+ Property Font : TTextFont Read FFont Write SetFont;
|
|
|
+ // Line spacing.
|
|
|
+ Property LineSpacing : TTextUnits Read FLineSpacing Write SetLineSpacing;
|
|
|
+ // Maximum size
|
|
|
+ Property Bounds : TTextLayoutBounds Read FBounds Write SetBounds;
|
|
|
+ // Vertical alignment of text
|
|
|
+ Property VerticalAlign : TTextAlign Read FVerticalAlign Write SetVerticalAlign;
|
|
|
+ // Horizontal alignment of text
|
|
|
+ Property HorizontalAlign : TTextAlign Read FHorizontalAlign Write SetHorizontalAlign;
|
|
|
+ // Maximum size
|
|
|
+ Property MaxStretch : TTextUnits Read FMaxStretch Write SetMaxStretch;
|
|
|
+ // Allow hyphenation ?
|
|
|
+ Property AllowHyphenation : Boolean Read FAllowHyphenation Write SetAllowHyphenation;
|
|
|
+ // Hyphenation character
|
|
|
+ Property HyphenationChar : String Read FHyphenationChar Write SetHyphenationChar;
|
|
|
+ // What to do if ranges overlap ?
|
|
|
+ Property OverlappingRangesAction : TOverlappingRangesAction Read FOverlappingRangesAction Write FOverlappingRangesAction;
|
|
|
+ end;
|
|
|
+
|
|
|
+Function SplitPoint(aOffset, aSpaces : SizeInt) : TTextSplitPoint;
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+implementation
|
|
|
+
|
|
|
+resourcestring
|
|
|
+ SErrOverlappingRanges = 'Overlapping ranges: %s and %s';
|
|
|
+
|
|
|
+Function ColorToFPColor(aColor : TColor) : TFPColor;
|
|
|
+
|
|
|
+var
|
|
|
+ Rec : TColorRec absolute aColor;
|
|
|
+
|
|
|
+begin
|
|
|
+ Result.Alpha:=(Rec.A shl 8) or Rec.A;
|
|
|
+ Result.Red:=(Rec.R shl 8) or Rec.R;
|
|
|
+ Result.Green:=(Rec.G shl 8) or Rec.G;
|
|
|
+ Result.Blue:=(Rec.B shl 8) or Rec.B;
|
|
|
+end;
|
|
|
+
|
|
|
+function FPColorToColor(aColor : TFPColor) : TColor;
|
|
|
+
|
|
|
+var
|
|
|
+ aCol : TColor;
|
|
|
+ Rec : TColorRec absolute aCol;
|
|
|
+
|
|
|
+begin
|
|
|
+ Rec.A:=aColor.Alpha shr 8;
|
|
|
+ Rec.R:=aColor.Red shr 8;
|
|
|
+ Rec.G:=aColor.Green shr 8;
|
|
|
+ Rec.B:=aColor.Blue shr 8;
|
|
|
+ Result:=aCol;
|
|
|
+end;
|
|
|
+
|
|
|
+{ TTextBlock }
|
|
|
+
|
|
|
+function TTextBlock.GetText: TTextString;
|
|
|
+begin
|
|
|
+ Result:=Copy(Layouter.Text,1+TextOffset,TextLen);
|
|
|
+ If Suffix<>'' then
|
|
|
+ Result:=Result+Suffix;
|
|
|
+end;
|
|
|
+
|
|
|
+constructor TTextBlock.Create(aLayouter: TTextLayouter);
|
|
|
+begin
|
|
|
+ FLayouter:=aLayouter;
|
|
|
+end;
|
|
|
+
|
|
|
+constructor TTextBlock.Create(aLayouter: TTextLayouter; aOffset, aLen: SizeInt);
|
|
|
+begin
|
|
|
+ Create(aLayouter);
|
|
|
+ TextOffset:=aOffset;
|
|
|
+ TextLen:=aLen;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextBlock.Split(atPos: integer): TTextBlock;
|
|
|
+begin
|
|
|
+ Result:=TTextBlock.Create(Self.Layouter,Self.TextOffset+atPos,Self.TextLen-atPos);
|
|
|
+ Result.Font:=Self.Font;
|
|
|
+ Self.TextLen:=AtPos;
|
|
|
+ // Reset formatting stuff on new
|
|
|
+ Result.ForceNewLine:=False;
|
|
|
+ Result.LayoutPos:=Default(TPointF);
|
|
|
+ Result.Size.Width:=0;
|
|
|
+ Result.Size.Height:=0;
|
|
|
+ Result.Size.Descender:=0;
|
|
|
+ // and on current
|
|
|
+ Size.Width:=0;
|
|
|
+ Size.Height:=0;
|
|
|
+ Size.Descender:=0;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextBlock.Assign(aBlock: TTextBlock);
|
|
|
+begin
|
|
|
+ LayoutPos:=aBlock.LayoutPos;
|
|
|
+ Size:=aBlock.Size;
|
|
|
+ Font:=aBlock.Font;
|
|
|
+ TextOffset:=aBlock.TextOffset;
|
|
|
+ TextLen:=aBlock.TextLen;
|
|
|
+ ForceNewLine:=aBlock.ForceNewLine;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextBlock.ToString: RTLString;
|
|
|
+begin
|
|
|
+ Result:=Format('(x: %g, y: %g, w: %g, h:%g) [Off: %d, len: %d]: >>%s<< ',[LayoutPos.X,LayoutPos.Y,Size.Width,Size.Height,TextOffset,TextLen,Text]);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextBlock.TrimTrailingWhiteSpace;
|
|
|
+
|
|
|
+Const
|
|
|
+ WhiteSpace = [#0..#32];
|
|
|
+
|
|
|
+var
|
|
|
+ Len : SizeInt;
|
|
|
+
|
|
|
+begin
|
|
|
+ Len:=TextLen;
|
|
|
+ While (Len>0) and (Layouter.Text[TextOffSet+Len] in WhiteSpace) do
|
|
|
+ Dec(Len);
|
|
|
+ TextLen:=Len;
|
|
|
+end;
|
|
|
+
|
|
|
+{ TTextBlockList }
|
|
|
+
|
|
|
+function TTextBlockList.GetBlock(aIndex : Integer): TTextBlock;
|
|
|
+begin
|
|
|
+ Result:=TTextBlock(Items[aIndex]);
|
|
|
+end;
|
|
|
+
|
|
|
+{ TTextRange }
|
|
|
+
|
|
|
+procedure TTextRange.SetCharLength(AValue: SizeInt);
|
|
|
+begin
|
|
|
+ if FCharLength=AValue then Exit;
|
|
|
+ FCharLength:=AValue;
|
|
|
+ Changed;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextRange.SetCharOffSet(AValue: SizeInt);
|
|
|
+begin
|
|
|
+ if FCharOffset=AValue then Exit;
|
|
|
+ FCharOffset:=AValue;
|
|
|
+ Changed;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextRange.SetFont(AValue: TTextFont);
|
|
|
+begin
|
|
|
+ if FFont=AValue then Exit;
|
|
|
+ FFont.Assign(AValue);
|
|
|
+ Changed;
|
|
|
+end;
|
|
|
+
|
|
|
+constructor TTextRange.Create(ACollection: TCollection);
|
|
|
+begin
|
|
|
+ inherited Create(ACollection);
|
|
|
+ FFont:=CreateTextFont;
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TTextRange.destroy;
|
|
|
+begin
|
|
|
+ FreeAndNil(FFont);
|
|
|
+ inherited destroy;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextRange.CreateTextFont: TTextFont;
|
|
|
+begin
|
|
|
+ Result:=TTextFont.Create(Self);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextRange.Assign(Source: TPersistent);
|
|
|
+var
|
|
|
+ aSource: TTextRange absolute Source;
|
|
|
+begin
|
|
|
+ if Source is TTextRange then
|
|
|
+ begin
|
|
|
+ FCharOffset:=aSource.CharOffset;
|
|
|
+ FCharLength:=aSource.CharLength;
|
|
|
+ // Triggers change
|
|
|
+ Font:=aSource.Font;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ inherited Assign(Source);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextRange.Changed;
|
|
|
+begin
|
|
|
+ if Assigned(Collection) and (Collection.Owner is TTextLayouter) then
|
|
|
+ TTextLayouter(Collection.Owner).Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextRange.ToString: RTLString;
|
|
|
+begin
|
|
|
+ Result:=Format('[offset %d, len: %d]',[CharOffset,CharLength]);
|
|
|
+end;
|
|
|
+
|
|
|
+{ TTextRangeList }
|
|
|
+
|
|
|
+function TTextRangeList.GetRange(aIndex : integer): TTextRange;
|
|
|
+begin
|
|
|
+ Result:=TTextRange(Items[aIndex]);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextRangeList.SetRange(aIndex : integer; AValue: TTextRange);
|
|
|
+begin
|
|
|
+ Items[aIndex]:=aValue;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextRangeList.AddRange(aOffset, aCharlength: SizeInt; aFont: TTextFont): TTextRange;
|
|
|
+begin
|
|
|
+ Result:=add as TTextRange;
|
|
|
+ Result.CharOffset:=aOffset;
|
|
|
+ Result.CharLength:=aCharlength;
|
|
|
+ Result.Font:=aFont;
|
|
|
+end;
|
|
|
+
|
|
|
+{ TTextLayoutBounds }
|
|
|
+
|
|
|
+procedure TTextLayoutBounds.SetHeight(AValue: TTextUnits);
|
|
|
+begin
|
|
|
+ if FHeight=AValue then Exit;
|
|
|
+ FHeight:=AValue;
|
|
|
+ Changed;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayoutBounds.GetAsPoint: TTextPoint;
|
|
|
+begin
|
|
|
+ Result:=PointF(Width,Height);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayoutBounds.SetAsPoint(AValue: TTextPoint);
|
|
|
+begin
|
|
|
+ FWidth:=aValue.X;
|
|
|
+ FHeight:=aValue.Y;
|
|
|
+ Changed;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayoutBounds.SetWidth(AValue: TTextUnits);
|
|
|
+begin
|
|
|
+ if FWidth=AValue then Exit;
|
|
|
+ FWidth:=AValue;
|
|
|
+ Changed;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayoutBounds.Changed;
|
|
|
+begin
|
|
|
+ if assigned(FLayouter) then
|
|
|
+ FLayouter.Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayoutBounds.GetOwner: TPersistent;
|
|
|
+begin
|
|
|
+ Result:=FLayouter;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayoutBounds.Assign(Source: TPersistent);
|
|
|
+
|
|
|
+var
|
|
|
+ aSource: TTextLayoutBounds absolute Source;
|
|
|
+
|
|
|
+begin
|
|
|
+ if Source is TTextLayoutBounds then
|
|
|
+ begin
|
|
|
+ Width:=aSource.Width;
|
|
|
+ Height:=aSource.Height;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ inherited Assign(Source);
|
|
|
+end;
|
|
|
+
|
|
|
+constructor TTextLayoutBounds.Create(aLayouter: TTextLayouter);
|
|
|
+begin
|
|
|
+ FLayouter:=aLayouter;
|
|
|
+end;
|
|
|
+
|
|
|
+{ TTextSplitter }
|
|
|
+
|
|
|
+Function SplitPoint(aOffset, aSpaces : SizeInt) : TTextSplitPoint;
|
|
|
+
|
|
|
+begin
|
|
|
+ Result:=TTextSplitPoint.Create(aOffset,aSpaces);
|
|
|
+end;
|
|
|
+
|
|
|
+constructor TTextSplitter.Create(aLayouter: TTextLayouter);
|
|
|
+begin
|
|
|
+ FLayouter:=aLayouter;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextSplitter.GetNextNewLine(const aText: TTextString; aStartPos: SizeInt): TTextSplitPoint;
|
|
|
+
|
|
|
+Const
|
|
|
+ NewLineChars = [#10,#13];
|
|
|
+
|
|
|
+var
|
|
|
+ Len,I,Sp : Integer;
|
|
|
+
|
|
|
+begin
|
|
|
+ Len:=Length(aText);
|
|
|
+ I:=aStartPos;
|
|
|
+ While (I<=Len) and Not (aText[I] in NewLineChars) do
|
|
|
+ Inc(I);
|
|
|
+ SP:=I;
|
|
|
+ While (I<=Len) and (aText[i] in NewLineChars) do
|
|
|
+ Inc(I);
|
|
|
+ if SP>Len then
|
|
|
+ Result.Offset:=-1
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ Result.whitespace:=I-SP;
|
|
|
+ Result.offset:=SP-1;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextSplitter.GetNextSplitPoint(const aText: TTextString; aStartPos: SizeInt; aAllowHyphen: Boolean): TTextSplitPoint;
|
|
|
+
|
|
|
+Const
|
|
|
+ WhiteSpace = [0..#32];
|
|
|
+
|
|
|
+var
|
|
|
+ Len,I,Sp : Integer;
|
|
|
+
|
|
|
+begin
|
|
|
+ Len:=Length(aText);
|
|
|
+ I:=aStartPos;
|
|
|
+ While (I<=Len) and Not (aText[I] in WhiteSpace) do
|
|
|
+ Inc(I);
|
|
|
+ SP:=I;
|
|
|
+ While (I<=Len) and (aText[i] in WhiteSpace) do
|
|
|
+ Inc(I);
|
|
|
+ if I>Len then
|
|
|
+ Result.whitespace:=1
|
|
|
+ else
|
|
|
+ Result.whitespace:=I-SP;
|
|
|
+ Result.offset:=SP-1;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextSplitter.SplitText(const aText: TTextString; aStartPos: SizeInt; aAllowHyphen: Boolean): TTextSplitPointArray;
|
|
|
+
|
|
|
+var
|
|
|
+ aPos,MaxOffset,Idx,Len : SizeInt;
|
|
|
+
|
|
|
+begin
|
|
|
+ Result:=[];
|
|
|
+ Len:=Length(aText);
|
|
|
+ MaxOffset:=Len-aStartPos+1;
|
|
|
+ Idx:=0;
|
|
|
+ aPos:=0;
|
|
|
+ SetLength(Result,MaxOffset);
|
|
|
+ Repeat
|
|
|
+ Result[Idx]:=GetNextSplitPoint(aText,aStartPos+aPos,aAllowHyphen);
|
|
|
+ aPos:=Result[Idx].offset+Result[Idx].WhiteSpace;
|
|
|
+ Inc(Idx);
|
|
|
+ until (aPos>=MaxOffset);
|
|
|
+ SetLength(Result,Idx);
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextSplitter.SplitLines(const aText: TTextString; aStartPos: SizeInt; aAllowHyphen: Boolean): TTextSplitPointArray;
|
|
|
+
|
|
|
+var
|
|
|
+ aPos,MaxOffset,Idx,Len : SizeInt;
|
|
|
+ aTSP : TTextSplitPoint;
|
|
|
+
|
|
|
+begin
|
|
|
+ Result:=[];
|
|
|
+ Len:=Length(aText);
|
|
|
+ MaxOffset:=Len-aStartPos+1;
|
|
|
+ Idx:=0;
|
|
|
+ aPos:=0;
|
|
|
+ SetLength(Result,MaxOffset);
|
|
|
+ Repeat
|
|
|
+ aTSP:=GetNextNewLine(aText,aStartPos+aPos);
|
|
|
+ if aTSP.Offset<>-1 then
|
|
|
+ begin
|
|
|
+ Result[Idx]:=aTSP;
|
|
|
+ aPos:=aTSP.offset+Result[Idx].WhiteSpace;
|
|
|
+ Inc(Idx);
|
|
|
+ end;
|
|
|
+ until (aTSP.Offset=-1);
|
|
|
+ SetLength(Result,Idx);
|
|
|
+end;
|
|
|
+
|
|
|
+{ TTextMeasurer }
|
|
|
+
|
|
|
+constructor TTextMeasurer.Create(aLayouter: TTextLayouter);
|
|
|
+begin
|
|
|
+ FLayouter:=aLayouter;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextMeasurer.SetFont(const aFont: TTextFont);
|
|
|
+begin
|
|
|
+ With aFont do
|
|
|
+ SetFont(Name,Size,Attrs);
|
|
|
+ FWhiteSpaceWidth:=0;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextMeasurer.WhitespaceWidth: TTextUnits;
|
|
|
+begin
|
|
|
+ if FWhiteSpaceWidth=0 then
|
|
|
+ FWhitespaceWidth:=MeasureText(' ').Width;
|
|
|
+ Result:=FWhitespaceWidth;
|
|
|
+end;
|
|
|
+
|
|
|
+{ TFixedSizeTextMeasurer }
|
|
|
+
|
|
|
+constructor TFixedSizeTextMeasurer.Create(aLayouter: TTextLayouter);
|
|
|
+begin
|
|
|
+ Inherited;
|
|
|
+ CharWidth:=8;
|
|
|
+ CharHeight:=12;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TFixedSizeTextMeasurer.SetFont(const aFontName: String; aSize: SmallInt; aAttrs: TFontAttributes);
|
|
|
+begin
|
|
|
+ FSize:=aSize;
|
|
|
+ FFontName:=aFontName;
|
|
|
+ FAttrs:=aAttrs;
|
|
|
+end;
|
|
|
+
|
|
|
+function TFixedSizeTextMeasurer.MeasureText(aText: String): TTextMeasures;
|
|
|
+
|
|
|
+var
|
|
|
+ Scale: TTextUnits;
|
|
|
+begin
|
|
|
+ Scale:=(Size/12);
|
|
|
+ Result.Width:=Length(aText) * CharWidth * Scale;
|
|
|
+ Result.Height:=CharHeight * Scale;
|
|
|
+ Result.Descender:=0;
|
|
|
+end;
|
|
|
+
|
|
|
+{ TTextSplitPoint }
|
|
|
+
|
|
|
+constructor TTextSplitPoint.Create(aOffset, aWhiteSpace: SizeInt);
|
|
|
+begin
|
|
|
+ offSet:=aOffset;
|
|
|
+ whitespace:=aWhiteSpace;
|
|
|
+end;
|
|
|
+
|
|
|
+{ TTextFont }
|
|
|
+
|
|
|
+function TTextFont.GetColor: TColor;
|
|
|
+begin
|
|
|
+ Result:=FPColorToColor(FColor);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextFont.SetAttrs(AValue: TFontAttributes);
|
|
|
+begin
|
|
|
+ if FAttrs=AValue then Exit;
|
|
|
+ FAttrs:=AValue;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextFont.SetColor(AValue: TColor);
|
|
|
+begin
|
|
|
+ FColor:=ColorToFPColor(aValue);
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextFont.SetFPColor(AValue: TFPColor);
|
|
|
+begin
|
|
|
+ if FColor=AValue then Exit;
|
|
|
+ FColor:=AValue;
|
|
|
+ Changed;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextFont.SetName(AValue: string);
|
|
|
+begin
|
|
|
+ if FName=AValue then Exit;
|
|
|
+ FName:=AValue;
|
|
|
+ Changed;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextFont.SetSize(AValue: Smallint);
|
|
|
+begin
|
|
|
+ if FSize=AValue then Exit;
|
|
|
+ FSize:=AValue;
|
|
|
+ Changed;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextFont.Assign(Source: TPersistent);
|
|
|
+var
|
|
|
+ aSource: TTextFont absolute source;
|
|
|
+begin
|
|
|
+ if Source is TTextFont then
|
|
|
+ begin
|
|
|
+ FSize:=aSource.FSize;
|
|
|
+ FOwner:=aSource.FOwner;
|
|
|
+ FName:=aSource.FName;
|
|
|
+ FColor:=aSource.FColor;
|
|
|
+ FAttrs:=aSource.FAttrs;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ inherited Assign(Source);
|
|
|
+end;
|
|
|
+
|
|
|
+constructor TTextFont.Create(aOwner: TPersistent);
|
|
|
+begin
|
|
|
+ FOwner:=aOwner;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextFont.Changed;
|
|
|
+begin
|
|
|
+ if (FOwner is TTextLayouter) then
|
|
|
+ TTextLayouter(FOwner).Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextFont.Clone(aOwner : TPersistent): TTextFont;
|
|
|
+
|
|
|
+begin
|
|
|
+ Result:=TTextFontClass(Self.ClassType).Create(aOwner);
|
|
|
+ Result.Assign(Self);
|
|
|
+end;
|
|
|
+
|
|
|
+{ TTextLayouter }
|
|
|
+
|
|
|
+procedure TTextLayouter.SetCullTreshold(AValue: TCullThreshold);
|
|
|
+begin
|
|
|
+ if FCullTreshold=AValue then Exit;
|
|
|
+ FCullTreshold:=AValue;
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetFont(AValue: TTextFont);
|
|
|
+begin
|
|
|
+ if FFont=AValue then Exit;
|
|
|
+ FFont.Assign(AValue);
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetHorizontalAlign(AValue: TTextAlign);
|
|
|
+begin
|
|
|
+ if FHorizontalAlign=AValue then Exit;
|
|
|
+ FHorizontalAlign:=AValue;
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetHyphenationChar(AValue: String);
|
|
|
+begin
|
|
|
+ if FHyphenationChar=AValue then Exit;
|
|
|
+ FHyphenationChar:=AValue;
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetLineSpacing(AValue: TTextUnits);
|
|
|
+begin
|
|
|
+ if FLineSpacing=AValue then Exit;
|
|
|
+ FLineSpacing:=AValue;
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetBounds(AValue: TTextLayoutBounds);
|
|
|
+begin
|
|
|
+ if FBounds=AValue then Exit;
|
|
|
+ FBounds.Assign(AValue);
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetMaxStretch(AValue: TTextUnits);
|
|
|
+begin
|
|
|
+ if FMaxStretch=AValue then Exit;
|
|
|
+ FMaxStretch:=AValue;
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetRanges(AValue: TTextRangeList);
|
|
|
+begin
|
|
|
+ if FRanges=AValue then Exit;
|
|
|
+ FRanges:=AValue;
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayouter.GetBlock(aIndex : Integer): TTextBlock;
|
|
|
+begin
|
|
|
+ Result:=TTextBlock(FBlocks[aIndex]);
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayouter.GetBlockCount: Integer;
|
|
|
+begin
|
|
|
+ Result:=FBlocks.Count;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayouter.GetColor: TFPColor;
|
|
|
+begin
|
|
|
+ Result:=FFont.FPColor;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetAllowHyphenation(AValue: Boolean);
|
|
|
+begin
|
|
|
+ if FAllowHyphenation=AValue then Exit;
|
|
|
+ FAllowHyphenation:=AValue;
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+procedure TTextLayouter.SetStretchMode(AValue: TStretchMode);
|
|
|
+begin
|
|
|
+ if FStretchMode=AValue then Exit;
|
|
|
+ FStretchMode:=AValue;
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetText(AValue: string);
|
|
|
+begin
|
|
|
+ if FText=AValue then Exit;
|
|
|
+ FText:=AValue;
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetTextRanges(AValue: TTextRangeList);
|
|
|
+begin
|
|
|
+ if FTextRanges=AValue then Exit;
|
|
|
+ FTextRanges.Assign(AValue);
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetVerticalAlign(AValue: TTextAlign);
|
|
|
+begin
|
|
|
+ if FVerticalAlign=AValue then Exit;
|
|
|
+ FVerticalAlign:=AValue;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetWordOverFlow(AValue: TWordOverflow);
|
|
|
+begin
|
|
|
+ if FWordOverFlow=AValue then Exit;
|
|
|
+ FWordOverFlow:=AValue;
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetWordWrap(AValue: Boolean);
|
|
|
+begin
|
|
|
+ if FWordWrap=AValue then Exit;
|
|
|
+ FWordWrap:=AValue;
|
|
|
+ Reset;
|
|
|
+end;
|
|
|
+
|
|
|
+class function TTextLayouter.CreateMeasurer(aLayouter: TTextLayouter): TTextMeasurer;
|
|
|
+
|
|
|
+var
|
|
|
+ aClass : TTextMeasurerClass;
|
|
|
+
|
|
|
+begin
|
|
|
+ aClass:=_TextMeasurerClass;
|
|
|
+ if aClass=Nil then
|
|
|
+ aClass:=TFixedSizeTextMeasurer;
|
|
|
+ Result:=aClass.Create(aLayouter);
|
|
|
+end;
|
|
|
+
|
|
|
+class function TTextLayouter.CreateSplitter(aLayouter: TTextLayouter): TTextSplitter;
|
|
|
+
|
|
|
+var
|
|
|
+ aclass : TTextSplitterClass;
|
|
|
+
|
|
|
+begin
|
|
|
+ aClass:=_TextSplitterClass;
|
|
|
+ if aClass=Nil then
|
|
|
+ aClass:=TTextSplitter;
|
|
|
+ Result:=aClass.Create(aLayouter);
|
|
|
+end;
|
|
|
+
|
|
|
+constructor TTextLayouter.Create(aOwner: TComponent);
|
|
|
+begin
|
|
|
+ Inherited;
|
|
|
+ FBlocks:=TTextBlockList.Create(True);
|
|
|
+ FBounds:=TTextLayoutBounds.Create(Self);
|
|
|
+ FFont:=TTextFont.Create(Self);
|
|
|
+ FRanges:=CreateRanges(Self);
|
|
|
+ FMeasurer:=CreateMeasurer(Self);
|
|
|
+ FSplitter:=CreateSplitter(Self);
|
|
|
+ FLineSpacing:=1.0;
|
|
|
+ HyphenationChar:='-';
|
|
|
+ AllowHyphenation:=False;
|
|
|
+end;
|
|
|
+
|
|
|
+class function TTextLayouter.CreateRanges(aLayouter : TTextLayouter) : TTextRangeList;
|
|
|
+
|
|
|
+var
|
|
|
+ aClass : TTextRangeClass;
|
|
|
+
|
|
|
+begin
|
|
|
+ aClass:=_TextRangeClass;
|
|
|
+ if aClass=Nil then
|
|
|
+ aClass:=TTextRange;
|
|
|
+ Result:=TTextRangeList.Create(aLayouter,aClass);
|
|
|
+end;
|
|
|
+
|
|
|
+class function TTextLayouter.CreateBlock(aLayouter: TTextLayouter; aOffset, aLength: SizeInt): TTextBlock;
|
|
|
+var
|
|
|
+ aClass : TTextBlockClass;
|
|
|
+
|
|
|
+begin
|
|
|
+ aClass:=_TextBlockClass;
|
|
|
+ if aClass=Nil then
|
|
|
+ aClass:=TTextBlock;
|
|
|
+ Result:=aClass.Create(aLayouter,aOffset,aLength);
|
|
|
+end;
|
|
|
+
|
|
|
+destructor TTextLayouter.Destroy;
|
|
|
+begin
|
|
|
+ FreeAndNil(FBounds);
|
|
|
+ FreeAndNil(FFont);
|
|
|
+ FreeAndNil(FBlocks);
|
|
|
+ FreeAndNil(FRanges);
|
|
|
+ FreeAndNil(FMeasurer);
|
|
|
+ FreeAndNil(FSplitter);
|
|
|
+ inherited Destroy;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.Reset;
|
|
|
+begin
|
|
|
+ FBlocks.Clear;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayouter.ToString: RTLString;
|
|
|
+var
|
|
|
+ I : Integer;
|
|
|
+begin
|
|
|
+ Result:='';
|
|
|
+ For I:=0 to TextBlockCount-1 do
|
|
|
+ Result:=Result+TextBlocks[I].ToString+sLineBreak;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayouter.AddBlock(aOffset,aLength : SizeInt; aFont : TTextFont) : TTextBlock;
|
|
|
+
|
|
|
+begin
|
|
|
+ Result:=CreateBlock(Self,aOffset,aLength);
|
|
|
+ Result.Font:=aFont;
|
|
|
+ FBlocks.Add(Result);
|
|
|
+end;
|
|
|
+
|
|
|
+function OffsetRange(Item1, Item2: TCollectionItem): Integer;
|
|
|
+begin
|
|
|
+ Result:=TTextRange(Item1).CharOffset-TTextRange(Item2).CharOffset;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.CheckRanges;
|
|
|
+
|
|
|
+var
|
|
|
+ I,rMax : Integer;
|
|
|
+ R,RN : TTextRange;
|
|
|
+
|
|
|
+begin
|
|
|
+ Ranges.Sort(@OffsetRange);
|
|
|
+ If Ranges.Count=1 then
|
|
|
+ exit;
|
|
|
+ R:=Ranges[0];
|
|
|
+ for I:=1 to Ranges.Count-1 do
|
|
|
+ begin
|
|
|
+ RN:=Ranges[i];
|
|
|
+ rMax:=RN.CharOffset-R.CharOffset;
|
|
|
+ if R.CharLength>rMax then
|
|
|
+ begin
|
|
|
+ if OverlappingRangesAction=oraError then
|
|
|
+ Raise ETextLayout.CreateFmt(SErrOverlappingRanges,[R.ToString,RN.ToString]);
|
|
|
+ R.CharLength:=rMax;
|
|
|
+ end;
|
|
|
+ R:=RN;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+procedure TTextLayouter.HandleRanges;
|
|
|
+
|
|
|
+var
|
|
|
+ I,LastOff,AddLen,MaxLen : Integer;
|
|
|
+ R : TTextRange;
|
|
|
+
|
|
|
+
|
|
|
+begin
|
|
|
+ MaxLen:=Length(Text);
|
|
|
+ if Ranges.Count=0 then
|
|
|
+ AddBlock(0,MaxLen,Font)
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ CheckRanges;
|
|
|
+ LastOff:=0;
|
|
|
+ for I:=0 to Ranges.Count-1 do
|
|
|
+ begin
|
|
|
+ R:=Ranges[i];
|
|
|
+ if R.CharOffset>LastOff then
|
|
|
+ AddBlock(LastOff,R.CharOffset-LastOff,Self.Font);
|
|
|
+ if R.CharOffset<MaxLen then
|
|
|
+ begin
|
|
|
+ AddLen:=R.CharLength;
|
|
|
+ if R.CharOffset+AddLen>=MaxLen then
|
|
|
+ AddLen:=MaxLen-R.CharOffSet;
|
|
|
+ AddBlock(R.CharOffset,AddLen,R.Font);
|
|
|
+ LastOff:=R.CharOffset+AddLen;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ If LastOff<MaxLen then
|
|
|
+ AddBlock(LastOff,MaxLen-LastOff,Self.Font);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.SetColor(AValue: TFPColor);
|
|
|
+begin
|
|
|
+ Font.FPColor:=aValue;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.HandleNewLines;
|
|
|
+
|
|
|
+var
|
|
|
+ i : Integer;
|
|
|
+ aPos : sizeInt;
|
|
|
+ SplitPos : TTextSplitPoint;
|
|
|
+ B,BN : TTextBlock;
|
|
|
+ T : String;
|
|
|
+
|
|
|
+begin
|
|
|
+ I:=0;
|
|
|
+ While (I<FBlocks.Count) do
|
|
|
+ begin
|
|
|
+ B:=FBlocks[i];
|
|
|
+ Repeat
|
|
|
+ T:=B.Text;
|
|
|
+ SplitPos:=Splitter.GetNextNewLine(Text,1+B.TextOffset);
|
|
|
+ if SplitPos.Offset<>-1 then
|
|
|
+ begin
|
|
|
+ aPos:=Splitpos.offset+Splitpos.whitespace;
|
|
|
+ BN:=B.Split(aPos);
|
|
|
+ T:=BN.Text;
|
|
|
+ BN.ForceNewLine:=True;
|
|
|
+ B.TextLen:=B.TextLen-SplitPos.WhiteSpace;
|
|
|
+ B.TrimTrailingWhiteSpace;
|
|
|
+ T:=B.Text;
|
|
|
+ inc(I);
|
|
|
+ FBlocks.Insert(I,BN);
|
|
|
+ B:=BN;
|
|
|
+ end;
|
|
|
+ until SplitPos.Offset=-1;
|
|
|
+ Inc(I);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+// Returns true if the line is full.
|
|
|
+function TTextLayouter.FindWrapPosition(B : TTextBlock; S : String; var aPos : integer; var CurrPos : TTextPoint) : Boolean;
|
|
|
+
|
|
|
+var
|
|
|
+ lSplit : TTextSplitPoint;
|
|
|
+ lSize : TTextMeasures;
|
|
|
+ wSpace : TTextUnits;
|
|
|
+ BlockWidth: TTextUnits;
|
|
|
+ CurrPart : String;
|
|
|
+ maxLen : integer;
|
|
|
+ useHyphen : Boolean;
|
|
|
+
|
|
|
+begin
|
|
|
+ maxLen:=Length(S);
|
|
|
+ BlockWidth:=0;
|
|
|
+ UseHyphen:=False;
|
|
|
+ Repeat
|
|
|
+ lSplit:=Splitter.GetNextSplitPoint(S,aPos,UseHyphen);
|
|
|
+ CurrPart:=Copy(S,aPos,lSplit.offset-aPos+1);
|
|
|
+ if UseHyphen then
|
|
|
+ CurrPart:=CurrPart+HyphenationChar;
|
|
|
+ Writeln('Curr : >',CurrPart,'<');
|
|
|
+ lSize:=Measurer.MeasureText(CurrPart);
|
|
|
+ Result:=CurrPos.X+lSize.Width>=Bounds.Width;
|
|
|
+ if not Result then
|
|
|
+ begin
|
|
|
+ // CurrPart still fits on Line, add it
|
|
|
+ BlockWidth:=BlockWidth+lSize.Width;
|
|
|
+ CurrPos.x:=CurrPos.X+lSize.Width;
|
|
|
+ // Update pos for GetNextSplitPoint.
|
|
|
+ aPos:=lSplit.Offset+lSplit.whitespace+1;
|
|
|
+ // Check if the whitespace would flow over:
|
|
|
+ WSpace:=lSplit.whitespace*Measurer.WhitespaceWidth;
|
|
|
+ Result:=(CurrPos.X+WSpace)>=Bounds.Width;
|
|
|
+ if UseHyphen then
|
|
|
+ B.Suffix:=HyphenationChar;
|
|
|
+ if not Result then
|
|
|
+ CurrPos.X:=CurrPos.X+WSpace;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ // Currpart will no longer fit on the line. Attempt splitting, if we were not yet splitting.
|
|
|
+ if (not UseHyphen) and AllowHyphenation then
|
|
|
+ begin
|
|
|
+ Result:=False;
|
|
|
+ UseHyphen:=True;
|
|
|
+ end
|
|
|
+ else
|
|
|
+ // One word and it does not fit...
|
|
|
+ if aPos=1 then
|
|
|
+ aPos:=MaxLen
|
|
|
+ end;
|
|
|
+ until Result or (aPos>=MaxLen);
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayouter.WrapBlock(B: TTextBlock; S: String; var Idx: integer; var CurrPos: TTextPoint): Boolean;
|
|
|
+
|
|
|
+var
|
|
|
+ aPosOffset,aPos,MaxLen: integer;
|
|
|
+ LineFull : Boolean;
|
|
|
+ NB : TTextBlock;
|
|
|
+ T : String;
|
|
|
+
|
|
|
+begin
|
|
|
+ Result:=False;
|
|
|
+ aPos:=1;
|
|
|
+ aPosOffset:=1;
|
|
|
+ maxLen:=Length(S);
|
|
|
+ // We can have multiple lines
|
|
|
+ Repeat
|
|
|
+ B.LayoutPos:=CurrPos;
|
|
|
+ LineFull:=FindWrapPosition(B,S,aPos,CurrPos);
|
|
|
+ // At this point, aPos is the maximum size that will fit.
|
|
|
+ if aPos>=MaxLen then
|
|
|
+ begin
|
|
|
+ // Correct size.
|
|
|
+ B.Size:=Measurer.MeasureText(B.Text);
|
|
|
+ end
|
|
|
+ else
|
|
|
+ // We're not yet at the end, so we need to split
|
|
|
+ begin
|
|
|
+ Result:=True;
|
|
|
+ NB:=B.Split(aPos-aPosOffset);
|
|
|
+ T:=NB.Text;
|
|
|
+ // Writeln('T new: >>',T,'<<');
|
|
|
+ B.TrimTrailingWhiteSpace;
|
|
|
+ T:=B.Text;
|
|
|
+ // Writeln('T old: >>',T,'<<');
|
|
|
+ B.Size:=Measurer.MeasureText(T);
|
|
|
+ Inc(Idx);
|
|
|
+ FBlocks.Insert(Idx,NB);
|
|
|
+ NB.ForceNewLine:=True;
|
|
|
+ aPosOffset:=NB.TextOffset;
|
|
|
+ if LineFull then
|
|
|
+ begin
|
|
|
+ CurrPos.X:=0;
|
|
|
+ CurrPos.Y:=CurrPos.Y+B.Size.Height+LineSpacing;
|
|
|
+ end;
|
|
|
+ B:=NB;
|
|
|
+ end;
|
|
|
+ until (aPos>=MaxLen);
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayouter.WrapLayout : Boolean;
|
|
|
+
|
|
|
+var
|
|
|
+ CurrPos : TTextPoint; // value in pixels
|
|
|
+ i: integer;
|
|
|
+ lSize : TTextMeasures;
|
|
|
+ B : TTextBlock;
|
|
|
+ lText : TTextString;
|
|
|
+
|
|
|
+begin
|
|
|
+ Result:=False;
|
|
|
+ CurrPos:=Pointf(0,0);
|
|
|
+ I:=0;
|
|
|
+ While I<FBlocks.Count do
|
|
|
+ begin
|
|
|
+ B:=FBlocks[i];
|
|
|
+ if B.ForceNewLine then
|
|
|
+ CurrPos.X:=0;
|
|
|
+ lText:=B.Text;
|
|
|
+ Measurer.SetFont(B.Font);
|
|
|
+ lSize:=Measurer.MeasureText(lText);
|
|
|
+ if CurrPos.X+lSize.Width>Bounds.Width then
|
|
|
+ Result:=WrapBlock(B,lText,I,CurrPos) or Result;
|
|
|
+ inc(I);
|
|
|
+ end;
|
|
|
+ B:=FBlocks[FBlocks.Count-1];
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayouter.NoWrapLayout: Boolean;
|
|
|
+
|
|
|
+var
|
|
|
+ CurrPos : TTextPoint;
|
|
|
+ CurrHeight : TTextUnits;
|
|
|
+ i: integer;
|
|
|
+ B : TTextBlock;
|
|
|
+ lText : TTextString;
|
|
|
+
|
|
|
+begin
|
|
|
+ Result:=False;
|
|
|
+ CurrPos.X:=0;
|
|
|
+ CurrPos.Y:=0;
|
|
|
+ CurrHeight:=0;
|
|
|
+ I:=0;
|
|
|
+ While I<FBlocks.Count do
|
|
|
+ begin
|
|
|
+ B:=FBlocks[i];
|
|
|
+ if B.ForceNewLine then
|
|
|
+ begin
|
|
|
+ CurrPos.X:=0;
|
|
|
+ CurrPos.Y:=CurrPos.Y+CurrHeight+LineSpacing;
|
|
|
+ CurrHeight:=0;
|
|
|
+ Result:=True;
|
|
|
+ end;
|
|
|
+ lText:=B.Text;
|
|
|
+ Measurer.SetFont(B.Font);
|
|
|
+ B.Size:=Measurer.MeasureText(lText);
|
|
|
+ B.LayoutPos:=CurrPos;
|
|
|
+ // Shift pos
|
|
|
+ CurrPos.X:=CurrPos.X+B.Width;
|
|
|
+ if B.Height>CurrHeight then
|
|
|
+ CurrHeight:=B.Height;
|
|
|
+ inc(I);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+function TTextLayouter.Execute: integer;
|
|
|
+
|
|
|
+ function LineCount : integer;
|
|
|
+
|
|
|
+ var
|
|
|
+ I : integer;
|
|
|
+
|
|
|
+ begin
|
|
|
+ Result:=1;
|
|
|
+ For I:=0 to FBlocks.Count-1 do
|
|
|
+ If FBlocks[i].ForceNewLine then
|
|
|
+ Inc(Result);
|
|
|
+ end;
|
|
|
+
|
|
|
+ Function CalcNeededHeight : TTextUnits;
|
|
|
+
|
|
|
+ var
|
|
|
+ I : Integer;
|
|
|
+ NewH : TTextUnits;
|
|
|
+
|
|
|
+ begin
|
|
|
+ Result:=0;
|
|
|
+ For I:=0 to TextBlockCount-1 do
|
|
|
+ begin
|
|
|
+ With TextBlocks[i] do
|
|
|
+ NewH:=LayoutPos.Y+Size.Height;
|
|
|
+ if NewH>Result then
|
|
|
+ Result:=NewH;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+
|
|
|
+begin
|
|
|
+ Reset;
|
|
|
+ HandleRanges;
|
|
|
+ HandleNewLines;
|
|
|
+ if WordWrap then
|
|
|
+ WrapLayout
|
|
|
+ else
|
|
|
+ NoWrapLayout;
|
|
|
+ if StretchMode = TStretchMode.smDontStretch then
|
|
|
+ CullTextOutOfBoundsVertically
|
|
|
+ else
|
|
|
+ ApplyStretchMode(CalcNeededHeight);
|
|
|
+ // We do this after vertical culling, potentially less blocks...
|
|
|
+ CullTextOutOfBoundsHorizontally;
|
|
|
+ ApplyVertTextAlignment;
|
|
|
+ ApplyHorzTextAlignment;
|
|
|
+ Result:=TextBlockCount;
|
|
|
+ Writeln('Result : ',ToString);
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayouter.Execute(const aText: String): Integer;
|
|
|
+
|
|
|
+begin
|
|
|
+ Text:=aText;
|
|
|
+ Result:=Execute;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.ApplyStretchMode(const ADesiredHeight: TTextUnits);
|
|
|
+
|
|
|
+begin
|
|
|
+ Case StretchMode of
|
|
|
+ smDontStretch:
|
|
|
+ ;
|
|
|
+ TStretchMode.smMaxHeight:
|
|
|
+ begin
|
|
|
+ Bounds.Height:=MaxStretch;
|
|
|
+ end;
|
|
|
+ TStretchMode.smActualHeight:
|
|
|
+ begin
|
|
|
+ Bounds.Height := aDesiredHeight;
|
|
|
+ end;
|
|
|
+ TStretchMode.smActualHeightStretchOnly:
|
|
|
+ begin
|
|
|
+ if aDesiredHeight>Bounds.Height then { only grow height if needed. We don't shrink. }
|
|
|
+ Bounds.Height := aDesiredHeight;
|
|
|
+ end;
|
|
|
+ TStretchMode.smActualHeightShrinkOnly:
|
|
|
+ begin
|
|
|
+ if aDesiredHeight<Bounds.Height then { only shrink height if needed. We don't grow. }
|
|
|
+ Bounds.Height := ADesiredHeight;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+procedure TTextLayouter.CullTextOutOfBoundsVertically;
|
|
|
+
|
|
|
+var
|
|
|
+ i: integer;
|
|
|
+ lBlock: TTextBlock;
|
|
|
+ MaxHeight, vPos : TTextUnits;
|
|
|
+ lRemainingHeight: single;
|
|
|
+ d: single;
|
|
|
+ doDelete : Boolean;
|
|
|
+ aSize : TTextMeasures;
|
|
|
+
|
|
|
+begin
|
|
|
+ MaxHeight:=Bounds.Height;
|
|
|
+ for i := FBlocks.Count-1 downto 0 do
|
|
|
+ begin
|
|
|
+ lBlock := FBlocks[i];
|
|
|
+ // completely out of bounds ?
|
|
|
+ vPos := lBlock.LayoutPos.y;
|
|
|
+ doDelete := (vPos >= MaxHeight);
|
|
|
+ aSize:=lBlock.Size;
|
|
|
+ // partially out of bounds ?
|
|
|
+ if not DoDelete and ((vPos + aSize.Height + aSize.Descender) > MaxHeight) then
|
|
|
+ begin
|
|
|
+ lRemainingHeight := (MaxHeight - vPos);
|
|
|
+ { calculate % of text [height] that falls inside the bounderies of the Memo. }
|
|
|
+ d := (lRemainingHeight / (aSize.Height + aSize.Descender)) * 100;
|
|
|
+ {$IFDEF gDEBUG}
|
|
|
+ writeln(Format('Memo Culling: %2.2f%% of line height is visible', [d]));
|
|
|
+ {$ENDIF}
|
|
|
+ DoDelete:=CullThreshold > d;
|
|
|
+ end;
|
|
|
+ if DoDelete then
|
|
|
+ FBlocks.Delete(i);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+function TTextLayouter.FindLastFittingCharPos(B: TTextBlock; const aSuffix: String; out aWidth: TTextUnits): Integer;
|
|
|
+
|
|
|
+var
|
|
|
+ lWidth,SuffWidth,avgWidth,aMaxWidth : TTextUnits;
|
|
|
+ aStart,aEnd,aPivot : Integer;
|
|
|
+
|
|
|
+begin
|
|
|
+ SuffWidth:=Measurer.MeasureText(aSuffix).Width;
|
|
|
+ aMaxWidth:=Bounds.Width-SuffWidth;
|
|
|
+ // Get a starting point
|
|
|
+ AvgWidth:=B.Width/B.TextLen;
|
|
|
+ aStart:=0;
|
|
|
+ aEnd:=B.Textlen;
|
|
|
+ aPivot:=Round(Bounds.Width/AvgWidth);
|
|
|
+ lWidth:=Measurer.MeasureText(Copy(B.Text,1,aPivot)).Width;
|
|
|
+ While (aStart<=aEnd) do
|
|
|
+ begin
|
|
|
+ if lWidth=aMaxWidth then
|
|
|
+ aStart:=aEnd+1
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ if lWidth>aMaxWidth then
|
|
|
+ aEnd:=aPivot-1
|
|
|
+ else
|
|
|
+ aStart:=aPivot+1;
|
|
|
+ aPivot:=(aEnd+aStart) div 2;
|
|
|
+ end;
|
|
|
+ lWidth:=Measurer.MeasureText(Copy(B.Text,1,aPivot)).Width;
|
|
|
+ end;
|
|
|
+ Result:=aPivot;
|
|
|
+ aWidth:=lWidth+SuffWidth;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.CullTextHorizontally(B : TTextBlock);
|
|
|
+
|
|
|
+var
|
|
|
+ P : Integer;
|
|
|
+ Suff : String;
|
|
|
+ Width : TTextUnits;
|
|
|
+
|
|
|
+begin
|
|
|
+ if WordOverflow=TWordOverflow.woOverflow then
|
|
|
+ exit;
|
|
|
+ Suff:='';
|
|
|
+ Case WordOverflow of
|
|
|
+ TWordOverflow.woOverflow: ; // Silence compiler warning
|
|
|
+ TWordOverflow.woTruncate:
|
|
|
+ begin
|
|
|
+ P:=FindLastFittingCharPos(B,'',Width);
|
|
|
+ end;
|
|
|
+ TWordOverflow.woEllipsis:
|
|
|
+ begin
|
|
|
+ {$IF SIZEOF(CHAR)=2}
|
|
|
+ P:=FindLastFittingCharPos(B,cEllipsis);
|
|
|
+ Suff:=cEllipsis;
|
|
|
+ {$ELSE}
|
|
|
+ P:=FindLastFittingCharPos(B,UTF8Encode(cEllipsis),Width);
|
|
|
+ Suff:=UTF8Encode(cEllipsis);
|
|
|
+ {$ENDIF}
|
|
|
+ end;
|
|
|
+ TWordOverflow.woAsterisk:
|
|
|
+ begin
|
|
|
+ P:=FindLastFittingCharPos(B,'*',Width);
|
|
|
+ Suff:='*';
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ B.TextLen:=P;
|
|
|
+ B.Suffix:=Suff;
|
|
|
+ B.Size.Width:=Width;
|
|
|
+end;
|
|
|
+
|
|
|
+procedure TTextLayouter.CullTextOutOfBoundsHorizontally;
|
|
|
+
|
|
|
+var
|
|
|
+ B : TTextBlock;
|
|
|
+ i : Integer;
|
|
|
+
|
|
|
+begin
|
|
|
+ For I:=0 to TextBlockCount-1 do
|
|
|
+ begin
|
|
|
+ B:=TextBlocks[I];
|
|
|
+ if (B.LayoutPos.X+B.Size.Width>Bounds.Width) then
|
|
|
+ CullTextHorizontally(B);
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+{ this affects only X coordinate of text blocks }
|
|
|
+procedure TTextLayouter.ApplyHorzTextAlignment;
|
|
|
+
|
|
|
+var
|
|
|
+ i: integer;
|
|
|
+ tb: TTextBlock;
|
|
|
+ lList: TFPList;
|
|
|
+ lLastYPos: TTextUnits;
|
|
|
+
|
|
|
+ procedure ProcessRightJustified;
|
|
|
+ var
|
|
|
+ idx: integer;
|
|
|
+ b: TTextBlock;
|
|
|
+ lXOffset: TTextUnits;
|
|
|
+ begin
|
|
|
+ lXOffset := Bounds.Width;
|
|
|
+ for idx := lList.Count-1 downto 0 do
|
|
|
+ begin
|
|
|
+ b := TTextBlock(lList[idx]);
|
|
|
+ b.LayoutPos.X := lXOffset - b.Size.Width;
|
|
|
+ lXOffset := b.LayoutPos.X;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+
|
|
|
+ procedure ProcessCentered;
|
|
|
+ var
|
|
|
+ idx: integer;
|
|
|
+ b: TTextBlock;
|
|
|
+ lXOffset: TTextUnits;
|
|
|
+ lTotalWidth: TTextUnits;
|
|
|
+ begin
|
|
|
+ lTotalWidth := 0;
|
|
|
+ for idx := 0 to lList.Count-1 do
|
|
|
+ begin
|
|
|
+ b := TTextBlock(lList[idx]);
|
|
|
+ lTotalWidth := lTotalWidth + b.Width;
|
|
|
+ end;
|
|
|
+ lXOffset := (Bounds.Width - lTotalWidth) / 2;
|
|
|
+ if lXOffset < 0.0 then { it should never be, but lets play it safe }
|
|
|
+ lXOffset := 0.0;
|
|
|
+ for idx := 0 to lList.Count-1 do
|
|
|
+ begin
|
|
|
+ b := TTextBlock(lList[idx]);
|
|
|
+ b.LayoutPos.X := lXOffset;
|
|
|
+ lXOffset := lXOffset + b.Width;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+
|
|
|
+ (* TODO : Justify
|
|
|
+ // This requires splitting the blocks into words
|
|
|
+ procedure ProcessWidth;
|
|
|
+ var
|
|
|
+ idx: integer;
|
|
|
+ b: TTextBlock;
|
|
|
+ lXOffset: TTextUnits;
|
|
|
+ lSpace: TTextUnits;
|
|
|
+ lTotalWidth: TTextUnits;
|
|
|
+ begin
|
|
|
+
|
|
|
+ lTotalWidth := 0;
|
|
|
+ for idx := 0 to lList.Count-1 do
|
|
|
+ begin
|
|
|
+ b := TTextBlock(lList[idx]);
|
|
|
+ lTotalWidth := lTotalWidth + b.Width;
|
|
|
+ end;
|
|
|
+ lSpace := (Bounds.Width - lTotalWidth) / (lList.Count-1);
|
|
|
+ { All the text blocks must move by LeftMargin to the right. }
|
|
|
+ lXOffset := Padding.Right;
|
|
|
+ for idx := 0 to lList.Count-1 do
|
|
|
+ begin
|
|
|
+ b := TTextBlock(lList[idx]);
|
|
|
+ b.Pos.X := lXOffset;
|
|
|
+ lXOffset := lXOffset + b.Width + lSpace;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ *)
|
|
|
+
|
|
|
+begin
|
|
|
+ lList := TFPList.Create;
|
|
|
+ try
|
|
|
+ lLastYPos := 0;
|
|
|
+ i := 0;
|
|
|
+ While I<FBlocks.Count do
|
|
|
+ begin
|
|
|
+ tb := FBlocks[i];
|
|
|
+ if tb.LayoutPos.Y = lLastYPos then // still on the same text line
|
|
|
+ lList.Add(tb)
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ { a new line has started - process what we have collected in lList }
|
|
|
+ case HorizontalAlign of
|
|
|
+ TTextAlign.Leading: ; // Nothing to do
|
|
|
+ TTextAlign.Trailing: ProcessRightJustified;
|
|
|
+ TTextAlign.Center: ProcessCentered;
|
|
|
+ // taWidth: ProcessWidth;
|
|
|
+ end;
|
|
|
+ lList.Clear;
|
|
|
+ lLastYPos := tb.LayoutPos.Y;
|
|
|
+ lList.Add(tb)
|
|
|
+ end; { if..else }
|
|
|
+ inc(I);
|
|
|
+ end; { while i<fblocks.count }
|
|
|
+
|
|
|
+ { process the last text line's items }
|
|
|
+ if lList.Count > 0 then
|
|
|
+ begin
|
|
|
+ case HorizontalAlign of
|
|
|
+ TTextAlign.Leading: ; // Nothing to do
|
|
|
+ TTextAlign.Trailing: ProcessRightJustified;
|
|
|
+ TTextAlign.Center: ProcessCentered;
|
|
|
+ // taWidth: ProcessWidth;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+
|
|
|
+ finally
|
|
|
+ llist.Free;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+{ this affects only Y coordinate of text blocks }
|
|
|
+procedure TTextLayouter.ApplyVertTextAlignment;
|
|
|
+var
|
|
|
+ i: integer;
|
|
|
+ tb: TTextBlock;
|
|
|
+ lList: TFPList;
|
|
|
+ lLastYPos: TTextUnits;
|
|
|
+ lTotalHeight: TTextUnits;
|
|
|
+ lYOffset: TTextUnits;
|
|
|
+
|
|
|
+ procedure ProcessTop;
|
|
|
+ var
|
|
|
+ idx: integer;
|
|
|
+ b: TTextBlock;
|
|
|
+ begin
|
|
|
+ if lList.Count = 0 then
|
|
|
+ Exit;
|
|
|
+ for idx := 0 to lList.Count-1 do
|
|
|
+ begin
|
|
|
+ b := TTextBlock(lList[idx]);
|
|
|
+ b.LayoutPos.Y := lYOffset;
|
|
|
+ end;
|
|
|
+ lYOffset := lYOffset + LineSpacing + b.Height + b.Descender;
|
|
|
+ end;
|
|
|
+
|
|
|
+ procedure ProcessCenter;
|
|
|
+ var
|
|
|
+ idx: integer;
|
|
|
+ b: TTextBlock;
|
|
|
+ begin
|
|
|
+ for idx := 0 to lList.Count-1 do
|
|
|
+ begin
|
|
|
+ b := TTextBlock(lList[idx]);
|
|
|
+ b.LayoutPos.Y := lYOffset;
|
|
|
+ end;
|
|
|
+ lYOffset := lYOffset + LineSpacing + b.Height + b.Descender;
|
|
|
+ end;
|
|
|
+
|
|
|
+ procedure ProcessBottom;
|
|
|
+ var
|
|
|
+ idx: integer;
|
|
|
+ b: TTextBlock;
|
|
|
+ begin
|
|
|
+ for idx := 0 to lList.Count-1 do
|
|
|
+ begin
|
|
|
+ b := TTextBlock(lList[idx]);
|
|
|
+ b.LayoutPos.Y := lYOffset;
|
|
|
+ end;
|
|
|
+ lYOffset := lYOffset - LineSpacing - b.Height - b.Descender;
|
|
|
+ end;
|
|
|
+
|
|
|
+begin
|
|
|
+ if FBlocks.Count = 0 then
|
|
|
+ Exit;
|
|
|
+ lList := TFPList.Create;
|
|
|
+ try
|
|
|
+ lLastYPos := FBlocks[FBlocks.Count-1].LayoutPos.Y; // last textblock's Y coordinate
|
|
|
+ lTotalHeight := 0;
|
|
|
+
|
|
|
+ Case VerticalAlign of
|
|
|
+ TTextAlign.Leading:
|
|
|
+ begin
|
|
|
+ // Nothing to do
|
|
|
+ end;
|
|
|
+
|
|
|
+ TTextAlign.Trailing:
|
|
|
+ begin
|
|
|
+ lYOffset := Bounds.Height;
|
|
|
+ for i := FBlocks.Count-1 downto 0 do
|
|
|
+ begin
|
|
|
+ tb := FBlocks[i];
|
|
|
+ if i = FBlocks.Count-1 then
|
|
|
+ lYOffset := lYOffset - tb.Height - tb.Descender; // only need to do this for one line
|
|
|
+ if tb.LayoutPos.Y = lLastYPos then // still on the same text line
|
|
|
+ lList.Add(tb)
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ { a new line has started - process what we have collected in lList }
|
|
|
+ ProcessBottom;
|
|
|
+
|
|
|
+ lList.Clear;
|
|
|
+ lLastYPos := tb.LayoutPos.Y;
|
|
|
+ lList.Add(tb)
|
|
|
+ end; { if..else }
|
|
|
+ end; { for i }
|
|
|
+ end; // TTextAlign.Trailing:
|
|
|
+
|
|
|
+ TTextAlign.Center:
|
|
|
+ begin
|
|
|
+ { First, collect the total height of all the text lines }
|
|
|
+ lTotalHeight := 0;
|
|
|
+ lLastYPos := 0;
|
|
|
+ for i := 0 to FBlocks.Count-1 do
|
|
|
+ begin
|
|
|
+ tb := FBlocks[i];
|
|
|
+ if i = 0 then // do this only for the first block
|
|
|
+ lTotalHeight := tb.Height + tb.Descender;
|
|
|
+ if tb.LayoutPos.Y = lLastYPos then // still on the same text line
|
|
|
+ Continue
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ { a new line has started - process what we have collected in lList }
|
|
|
+ lTotalHeight := lTotalHeight + LineSpacing + tb.Height + tb.Descender;
|
|
|
+ end; { if..else }
|
|
|
+ lLastYPos := tb.LayoutPos.Y;
|
|
|
+ end; { for i }
|
|
|
+
|
|
|
+ { Now process them line-by-line }
|
|
|
+ lList.Clear;
|
|
|
+ lYOffset := (Bounds.Height - lTotalHeight) / 2;
|
|
|
+ lLastYPos := 0;
|
|
|
+ for i := 0 to FBlocks.Count-1 do
|
|
|
+ begin
|
|
|
+ tb := FBlocks[i];
|
|
|
+ if tb.LayoutPos.Y = lLastYPos then // still on the same text line
|
|
|
+ lList.Add(tb)
|
|
|
+ else
|
|
|
+ begin
|
|
|
+ { a new line has started - process what we have collected in lList }
|
|
|
+ ProcessCenter;
|
|
|
+
|
|
|
+ lList.Clear;
|
|
|
+ lLastYPos := tb.LayoutPos.Y;
|
|
|
+ lList.Add(tb)
|
|
|
+ end; { if..else }
|
|
|
+ end; { for i }
|
|
|
+ end; // TTextAlign.Center
|
|
|
+ end; // Case
|
|
|
+
|
|
|
+ { process the last text line's items }
|
|
|
+ if lList.Count > 0 then
|
|
|
+ begin
|
|
|
+ case VerticalAlign of
|
|
|
+ TTextAlign.Leading: ProcessTop;
|
|
|
+ TTextAlign.Center: ProcessCenter;
|
|
|
+ TTextAlign.Trailing: ProcessBottom;
|
|
|
+ end;
|
|
|
+ end;
|
|
|
+ finally
|
|
|
+ lList.Free;
|
|
|
+ end;
|
|
|
+end;
|
|
|
+
|
|
|
+
|
|
|
+end.
|
|
|
+
|