lazarus-ccr/components/fpspreadsheet/source/visual/fpsvisualutils.pas

1006 lines
33 KiB
ObjectPascal

unit fpsvisualutils;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, Graphics,
fpstypes, fpspreadsheet;
procedure Convert_sFont_to_Font(sFont: TsFont; AFont: TFont); overload;
procedure Convert_sFont_to_Font(AWorkbook: TsWorkbook; sFont: TsFont; AFont: TFont); overload; deprecated;
procedure Convert_Font_to_sFont(AFont: TFont; sFont: TsFont); overload;
procedure Convert_Font_to_sFont(AWorkbook: TsWorkbook; AFont: TFont; sFont: TsFont); overload; deprecated;
function WrapText(ACanvas: TCanvas; const AText: string; AMaxWidth: integer): string;
procedure DrawRichText(ACanvas: TCanvas; AWorkbook: TsWorkbook; const ARect: TRect;
const AText: String; ARichTextParams: TsRichTextParams; AFontIndex: Integer;
AWordwrap: Boolean; AHorAlignment: TsHorAlignment; AVertAlignment: TsVertAlignment;
ARotation: TsTextRotation; AOverrideTextColor: TColor; ARightToLeft: Boolean;
AZoomFactor: Double);
function RichTextWidth(ACanvas: TCanvas; AWorkbook: TsWorkbook; ARect: TRect;
const AText: String; ARichTextParams: TsRichTextParams; AFontIndex: Integer;
ATextRotation: TsTextRotation; AWordWrap, ARightToLeft: Boolean;
AZoomFactor: Double): Integer;
function RichTextHeight(ACanvas: TCanvas; AWorkbook: TsWorkbook; ARect: TRect;
const AText: String; ARichTextParams: TsRichTextParams; AFontIndex: Integer;
ATextRotation: TsTextRotation; AWordWrap, ARightToLeft: Boolean;
AZoomFactor: Double): Integer;
type
TsLineInfo = class
pStart: PChar;
WordList: TStringList;
NumSpaces: Integer;
BeginsWithFontOfRtpIndex: Integer;
Width: Integer;
Height: Integer;
constructor Create;
destructor Destroy; override;
end;
{ TsTextPainter }
TsTextPainter = class
private
FCanvas: TCanvas;
FWorkbook: TsWorkbook;
FRect: TRect;
FFontIndex: Integer;
FTextRotation: TsTextRotation;
FHorAlignment: TsHorAlignment;
FVertAlignment: TsVertAlignment;
FWordWrap: Boolean;
FRightToLeft: Boolean;
FText: String;
FRtParams: TsRichTextParams;
FMaxLineLen: Integer;
FTotalHeight: Integer;
FLines: TFPList;
FPtr: PChar;
FRtpIndex: Integer;
FCharIndex: integer;
FCharIndexOfNextFont: Integer;
FFontHeight: Integer;
FFontPos: TsFontPosition;
FZoomFactor: Double;
private
function GetHeight: Integer;
function GetWidth: Integer;
protected
procedure DrawHor(AOverrideTextColor: TColor);
procedure DrawLine(pEnd: PChar; x, y, ALineHeight: Integer; AOverrideTextColor: TColor);
procedure DrawStacked(AOverrideTextColor: TColor);
procedure DrawText(var x, y: Integer; s: String; ALineHeight: Integer);
procedure DrawVert(AOverrideTextColor: TColor; AClockwise: Boolean);
function GetTextPt(x,y,ALineHeight: Integer): TPoint;
procedure InitFont(out ACurrRtpIndex, ACharIndexOfNextFont, ACurrFontHeight: Integer;
out ACurrFontPos: TsFontPosition);
procedure NextChar(ANumBytes: Integer);
procedure Prepare;
procedure ScanLine(var ANumSpaces, ALineWidth, ALineHeight: Integer;
AWordList: TStringList);
procedure UpdateFont(ACharIndex: Integer; var ACurrRtpIndex,
ACharIndexOfNextFont, ACurrFontHeight: Integer; var ACurrFontPos: TsFontPosition);
public
constructor Create(ACanvas: TCanvas; AWorkbook: TsWorkbook; ARect: TRect;
AText: String; ARichTextParams: TsRichTextParams; AFontIndex: Integer;
ATextRotation: TsTextRotation; AHorAlignment: TsHorAlignment;
AVertAlignment: TsVertAlignment; AWordWrap, ARightToLeft: Boolean;
AZoomFactor: Double);
destructor Destroy; override;
procedure Draw(AOverrideTextColor: TColor);
property Height: Integer read GetHeight;
property Width: Integer read GetWidth;
end;
implementation
uses
Types, Math, LCLType, LCLIntf, LazUTF8, fpsUtils;
const
{@@ Font size factor for sub-/superscript characters }
SUBSCRIPT_SUPERSCRIPT_FACTOR = 0.66;
{@@ ----------------------------------------------------------------------------
Converts a spreadsheet font to a font used for painting (TCanvas.Font).
@param sFont Font as used by fpspreadsheet (input)
@param AFont Font as used by TCanvas for painting (output)
-------------------------------------------------------------------------------}
procedure Convert_sFont_to_Font(sFont: TsFont; AFont: TFont);
begin
if Assigned(AFont) and Assigned(sFont) then begin
AFont.Name := sFont.FontName;
AFont.Size := round(sFont.Size);
AFont.Style := [];
if fssBold in sFont.Style then AFont.Style := AFont.Style + [fsBold];
if fssItalic in sFont.Style then AFont.Style := AFont.Style + [fsItalic];
if fssUnderline in sFont.Style then AFont.Style := AFont.Style + [fsUnderline];
if fssStrikeout in sFont.Style then AFont.Style := AFont.Style + [fsStrikeout];
AFont.Color := TColor(sFont.Color and $00FFFFFF);
end;
end;
procedure Convert_sFont_to_Font(AWorkbook: TsWorkbook; sFont: TsFont; AFont: TFont);
begin
Unused(AWorkbook);
Convert_sFont_to_Font(sFont, AFont);
end;
{@@ ----------------------------------------------------------------------------
Converts a font used for painting (TCanvas.Font) to a spreadsheet font.
@param AFont Font as used by TCanvas for painting (input)
@param sFont Font as used by fpspreadsheet (output)
-------------------------------------------------------------------------------}
procedure Convert_Font_to_sFont(AFont: TFont; sFont: TsFont);
begin
if Assigned(AFont) and Assigned(sFont) then begin
sFont.FontName := AFont.Name;
sFont.Size := AFont.Size;
sFont.Style := [];
if fsBold in AFont.Style then Include(sFont.Style, fssBold);
if fsItalic in AFont.Style then Include(sFont.Style, fssItalic);
if fsUnderline in AFont.Style then Include(sFont.Style, fssUnderline);
if fsStrikeout in AFont.Style then Include(sFont.Style, fssStrikeout);
sFont.Color := ColorToRGB(AFont.Color);
end;
end;
procedure Convert_Font_to_sFont(AWorkbook: TsWorkbook; AFont: TFont; sFont: TsFont);
begin
Unused(AWorkbook);
Convert_Font_to_sFont(AFont, sFont);
end;
{@@ ----------------------------------------------------------------------------
Wraps text by inserting line ending characters so that the lines are not
longer than AMaxWidth.
@param ACanvas Canvas on which the text will be drawn
@param AText Text to be drawn
@param AMaxWidth Maximimum line width (in pixels)
@return Text with inserted line endings such that the lines are shorter than
AMaxWidth.
@note Based on ocde posted by user "taazz" in the Lazarus forum
http://forum.lazarus.freepascal.org/index.php/topic,21305.msg124743.html#msg124743
-------------------------------------------------------------------------------}
function WrapText(ACanvas: TCanvas; const AText: string; AMaxWidth: integer): string;
var
DC: HDC;
textExtent: TSize = (cx:0; cy:0);
S, P, E: PChar;
line: string;
isFirstLine: boolean;
begin
Result := '';
DC := ACanvas.Handle;
isFirstLine := True;
P := PChar(AText);
while P^ = ' ' do
Inc(P);
while P^ <> #0 do begin
S := P;
E := nil;
while (P^ <> #0) and (P^ <> #13) and (P^ <> #10) do begin
LCLIntf.GetTextExtentPoint(DC, S, P - S + 1, textExtent);
if (textExtent.CX > AMaxWidth) and (E <> nil) then begin
if (P^ <> ' ') and (P^ <> ^I) then begin
while (E >= S) do
case E^ of
'.', ',', ';', '?', '!', '-', ':',
')', ']', '}', '>', '/', '\', ' ':
break;
else
Dec(E);
end;
if E < S then
E := P - 1;
end;
Break;
end;
E := P;
Inc(P);
end;
if E <> nil then begin
while (E >= S) and (E^ = ' ') do
Dec(E);
end;
if E <> nil then
SetString(Line, S, E - S + 1)
else
SetLength(Line, 0);
if (P^ = #13) or (P^ = #10) then begin
Inc(P);
if (P^ <> (P - 1)^) and ((P^ = #13) or (P^ = #10)) then
Inc(P);
if P^ = #0 then
line := line + LineEnding;
end
else if P^ <> ' ' then
P := E + 1;
while P^ = ' ' do
Inc(P);
if isFirstLine then begin
Result := Line;
isFirstLine := False;
end else
Result := Result + LineEnding + line;
end;
end;
{------------------------------------------------------------------------------}
{ Public rich-text functios }
{------------------------------------------------------------------------------}
procedure DrawRichText(ACanvas: TCanvas; AWorkbook: TsWorkbook; const ARect: TRect;
const AText: String; ARichTextParams: TsRichTextParams; AFontIndex: Integer;
AWordwrap: Boolean; AHorAlignment: TsHorAlignment; AVertAlignment: TsVertAlignment;
ARotation: TsTextRotation; AOverrideTextColor: TColor; ARightToLeft: Boolean;
AZoomFactor: Double);
var
painter: TsTextPainter;
begin
if (ARect.Left = ARect.Right) or (ARect.Top = ARect.Bottom) then
exit;
painter := TsTextPainter.Create(ACanvas, AWorkbook, ARect, AText, ARichTextParams,
AFontIndex, ARotation, AHorAlignment, AVertAlignment, AWordWrap, ARightToLeft,
AZoomFactor);
try
painter.Draw(AOverrideTextColor);
finally
painter.Free;
end;
end;
function RichTextWidth(ACanvas: TCanvas; AWorkbook: TsWorkbook; ARect: TRect;
const AText: String; ARichTextParams: TsRichTextParams; AFontIndex: Integer;
ATextRotation: TsTextRotation; AWordWrap, ARightToLeft: Boolean;
AZoomFactor: Double): Integer;
var
painter: TsTextPainter;
begin
if (ARect.Left = ARect.Right) or (ARect.Top = ARect.Bottom) then
exit(0);
painter := TsTextPainter.Create(ACanvas, AWorkbook, ARect, AText, ARichTextParams,
AFontIndex, ATextRotation, haLeft, vaTop, AWordWrap, ARightToLeft, AZoomFactor);
try
Result := painter.Width;
finally
painter.Free;
end;
end;
function RichTextHeight(ACanvas: TCanvas; AWorkbook: TsWorkbook; ARect: TRect;
const AText: String; ARichTextParams: TsRichTextParams; AFontIndex: Integer;
ATextRotation: TsTextRotation; AWordWrap, ARightToLeft: Boolean;
AZoomFactor: Double): Integer;
var
painter: TsTextPainter;
begin
if (ARect.Left = ARect.Right) or (ARect.Top = ARect.Bottom) then
exit(0);
painter := TsTextPainter.Create(ACanvas, AWorkbook, ARect, AText, ARichTextParams,
AFontIndex, ATextRotation, haLeft, vaTop, AWordWrap, ARightToLeft, AZoomFactor);
try
Result := painter.Height;
finally
painter.Free;
end;
end;
{------------------------------------------------------------------------------}
{ Painting engine for rich-text }
{------------------------------------------------------------------------------}
constructor TsLineInfo.Create;
begin
inherited;
WordList := TStringList.Create;
end;
destructor TsLineInfo.Destroy;
begin
WordList.Free;
inherited;
end;
{ TsTextPainter }
{ ARect ........ Defines the rectangle in which the text is to be drawn,
AFontIndex ... Base font of the text, to be used if not rich-text is defined.
ATextRoation . Text is rotated this way
AWordwrap .... Wrap text at word boundaries if text is wider than the MaxRect
(or higher, in case of vertical text).
ARightToLeft . if true, paint text from left to right }
constructor TsTextPainter.Create(ACanvas: TCanvas; AWorkbook: TsWorkbook;
ARect: TRect; AText: String; ARichTextParams: TsRichTextParams;
AFontIndex: Integer; ATextRotation: TsTextRotation; AHorAlignment: TsHorAlignment;
AVertAlignment: TsVertAlignment; AWordWrap, ARightToLeft: Boolean;
AZoomFactor: Double);
begin
FLines := TFPList.Create;
FCanvas := ACanvas;
FWorkbook := AWorkbook;
FRect := ARect;
FText := AText;
FRtParams := ARichTextParams;
FFontIndex := AFontIndex;
FTextRotation := ATextRotation;
FHorAlignment := AHorAlignment;
FVertAlignment := AVertAlignment;
FWordwrap := AWordwrap;
FRightToLeft := ARightToLeft;
FZoomfactor := AZoomFactor;
Prepare;
end;
destructor TsTextPainter.Destroy;
var
j: Integer;
begin
for j := FLines.Count-1 downto 0 do TObject(FLines[j]).Free;
FLines.Free;
inherited Destroy;
end;
{ Draw the lines }
procedure TsTextPainter.Draw(AOverrideTextColor: TColor);
begin
case FTextRotation of
trHorizontal : DrawHor(AOverrideTextColor);
rt90DegreeClockwiseRotation : DrawVert(AOverrideTextColor, true);
rt90DegreeCounterClockwiseRotation : DrawVert(AOverrideTextColor, false);
rtStacked : DrawStacked(AOverrideTextColor);
end;
end;
{ Draw lines in horizontal orienation }
procedure TsTextPainter.DrawHor(AOverrideTextColor: TColor);
var
xpos, ypos, j: Integer;
lineinfo: TsLineInfo;
pEnd: PChar;
begin
// (1) Get starting point of line
case FVertAlignment of
vaTop : ypos := FRect.Top;
vaCenter : ypos := (FRect.Top + FRect.Bottom - FTotalHeight) div 2;
vaBottom : ypos := FRect.Bottom - FTotalHeight;
end;
// (2) Draw text line-by-line
FPtr := PChar(FText);
FCharIndex := 1;
InitFont(FRtpIndex, FCharIndexOfNextFont, FFontHeight, FFontPos);
for j := 0 to FLines.Count-1 do
begin
if j < FLines.Count-1 then
pEnd := TsLineInfo(FLines[j+1]).pStart else
pEnd := PChar(FText) + Length(FText);
lineinfo := TsLineInfo(FLines[j]);
// xpos is x coordinate of left edge of first character
if FRightToLeft then
case FHorAlignment of
haLeft : xpos := FRect.Left + lineinfo.Width;
haCenter : xpos := (FRect.Left + FRect.Right + lineinfo.Width) div 2;
haRight : xpos := FRect.Right;
end
else
case FHorAlignment of
haLeft : xpos := FRect.Left;
haCenter : xpos := (FRect.Left + FRect.Right - lineinfo.Width) div 2;
haRight : xpos := FRect.Right - lineinfo.Width;
end;
DrawLine(pEnd, xpos, ypos, lineinfo.Height, AOverrideTextColor);
inc(ypos, lineinfo.Height);
end;
end;
{ Draw a single line. The font can change within the line. }
procedure TsTextPainter.DrawLine(pEnd: PChar; x, y, ALineHeight: Integer;
AOverrideTextColor: TColor);
var
charLen: Integer;
s: String;
begin
s := '';
while (FPtr^ <> #0) and (FPtr < pEnd) do begin
if FCharIndex = FCharIndexOfNextFont then begin
DrawText(x, y, s, ALineHeight);
s := '';
end;
UpdateFont(FCharIndex, FRtpIndex, FCharIndexOfNextFont, FFontHeight, FFontPos);
if AOverrideTextColor <> clNone then
FCanvas.Font.Color := AOverrideTextColor;
case FPtr^ of
#10: begin
DrawText(x, y, s, ALineHeight);
s := '';
NextChar(1);
break;
end;
#13: begin
DrawText(x, y, s, ALineHeight);
s := '';
NextChar(1);
if FPtr^ = #10 then
NextChar(1);
break;
end;
else
s := s + UnicodeToUTF8(UTF8CharacterToUnicode(FPtr, charLen));
if FCharIndex = FCharIndexOfNextFont then begin
DrawText(x, y, s, ALineHeight);
s := '';
end;
NextChar(charLen);
end;
end;
if s <> '' then
DrawText(x, y, s, ALineHeight);
end;
// Draws text in vertical columns using upright characters
procedure TsTextPainter.DrawStacked(AOverrideTextColor: TColor);
const
IGNORE = 0;
var
xpos, ypos, dx: Integer;
j: Integer;
lineinfo: TsLineInfo;
pEnd: PChar;
begin
// (1) Get starting point of line
lineinfo := TsLineInfo(FLines[0]);
dx := lineInfo.Height;
if FRightToLeft then
case FHorAlignment of
haLeft : xpos := FRect.Left + FTotalHeight + dx;
haCenter : xpos := (FRect.Left + FRect.Right + FTotalHeight) div 2 - dx;
haRight : xpos := FRect.Right - dx;
end
else
case FHorAlignment of
haLeft : xpos := FRect.Left + dx;
haCenter : xpos := (FRect.Left + FRect.Right - FTotalHeight) div 2;
haRight : xpos := FRect.Right - FTotalHeight + dx;
end;
// (2) Draw text line-by-line
FPtr := PChar(FText);
FCharIndex := 1;
InitFont(FRtpIndex, FCharIndexOfNextFont, FFontHeight, FFontPos);
for j := 0 to FLines.Count-1 do
begin
if j < FLines.Count-1 then
pEnd := TsLineInfo(FLines[j+1]).pStart
else
pEnd := PChar(FText) + Length(FText);
lineinfo := TsLineInfo(FLines[j]);
case FVertAlignment of
vaTop : ypos := FRect.Top;
vaCenter : ypos := (FRect.Top + FRect.Bottom - lineinfo.Width) div 2;
vaBottom : ypos := FRect.Bottom - lineinfo.Width;
end;
DrawLine(pEnd, xpos, ypos, IGNORE, AOverrideTextColor);
if FRightToLeft then
dec(xpos, 2*lineinfo.Height) else // "height" is horizontal here!
inc(xpos, 2*lineinfo.Height);
end;
end;
{ Draw a text chunk. Font does not change here }
procedure TsTextPainter.DrawText(var x, y: Integer; s: String;
ALineHeight: Integer);
const
MULTIPLIER: Array[TsTextRotation, boolean] of Integer = (
(+1, -1), // horiz ^
(+1, -1), // 90° CW FRightToLeft
(-1, +1), // 90° CCW
(+1, -1) // stacked
);
TEXT_ANGLE: array[TsTextRotation] of Integer = ( 0, -900, 900, 0);
var
w, wlead, wtrail: Integer;
Pt: TPoint;
i, nlead, ntrail, nchar: Integer;
p: PChar;
charLen: Integer;
ch: String;
begin
wlead := 0;
wtrail := 0;
if FRightToLeft then
begin
{ Right-to-left character handling of RTL strings containing spaces is very
confusing -- probably this is not correct... }
// Count leading spaces
nlead := 0;
i := 1;
while (i <= Length(s)) and (s[i] = ' ') do begin
inc(i);
inc(nlead);
end;
wlead := nlead * FCanvas.TextWidth(' ');
// count trailing spaces
ntrail := 0;
i := Length(s);
while (i >= 1) and (s[i] = ' ') do begin
dec(i);
inc(ntrail);
end;
wtrail := ntrail * FCanvas.TextWidth(' ');
// Remove leading and trailing spaces from string; their size will be
// compensated by coordinate offset wlead/wtrail.
s := trim(s);
end;
w := FCanvas.TextWidth(s);
Pt := GetTextPt(x, y, ALineHeight);
FCanvas.Font.Orientation := TEXT_ANGLE[FTextRotation];
case FTextRotation of
trHorizontal:
begin
if FRightToLeft
then FCanvas.TextOut(Pt.x-w-wlead, Pt.y, s)
else FCanvas.TextOut(Pt.x, Pt.y, s);
inc(x, (wlead+w+wtrail)*MULTIPLIER[FTextRotation, FRightToLeft]);
end;
rt90DegreeClockwiseRotation:
begin
if FRightToLeft
then FCanvas.TextOut(Pt.x, Pt.y-w-wlead, s)
else FCanvas.TextOut(Pt.x, Pt.y, s);
inc(y, (wlead+w+wtrail)*MULTIPLIER[FTextRotation, FRightToLeft]);
end;
rt90DegreeCounterClockwiseRotation:
begin
if FRightToLeft
then FCanvas.TextOut(Pt.x, Pt.y+w+wlead, s)
else FCanvas.TextOut(Pt.x, Pt.y, s);
inc(y, (wlead+w+wtrail)*MULTIPLIER[FTextRotation, FRightToLeft]);
end;
rtStacked:
begin
nChar := 0;
P := PChar(s);
while (P^ <> #0) do
begin
ch := UnicodeToUTF8(UTF8CharacterToUnicode(P, charLen));
ALineHeight := FCanvas.TextHeight(ch);
Pt := GetTextPt(x, y, ALineHeight);
w := FCanvas.TextWidth(ch);
// x is at the center of the character here
case FHorAlignment of
haLeft : FCanvas.TextOut(Pt.x - w div 2, Pt.y, ch);
haCenter : FCanvas.TextOut(Pt.x - w div 2, Pt.y, ch);
haRight : FCanvas.TextOut(Pt.x - w div 2, Pt.y, ch);
end;
inc(y, ALineHeight);
inc(nChar);
inc(P, charLen);
end;
end;
end;
end;
// Draw text in 90° clockwise or counter-clockwise rotation
procedure TsTextPainter.DrawVert(AOverrideTextColor: TColor; AClockwise: Boolean);
const // CCW CW
SGN: array[boolean] of Integer = (-1, +1);
var
j, xpos, ypos: Integer;
lineinfo: TsLineInfo;
pEnd: PChar;
begin
// (1) Get starting point
case FHorAlignment of
haLeft : xpos := IfThen(AClockwise, FRect.Left + FTotalHeight, FRect.Left);
haCenter : xpos := (FRect.Left + FRect.Right + FTotalHeight*SGN[AClockwise]) div 2;
haRight : xpos := IfThen(AClockwise, FRect.Right, FRect.Right - FTotalHeight);
end;
// (2) Draw text line by line and respect text rotation
FPtr := PChar(FText);
FCharIndex := 1; // Counter for utf8 character position
InitFont(FRtpIndex, FCharIndexOfNextFont, FFontHeight, FFontPos);
for j := 0 to FLines.Count-1 do
begin
if j < FLines.Count-1 then
pEnd := TsLineInfo(FLines[j+1]).pStart else
pEnd := PChar(FText) + Length(FText);
lineinfo := TsLineInfo(FLines[j]);
if FRightToLeft then
case FVertAlignment of
vaTop : ypos := IfThen(AClockwise, FRect.Top + lineinfo.Width, FRect.Top);
vaCenter : ypos := (FRect.Top + FRect.Bottom + lineinfo.Width*SGN[AClockwise]) div 2;
vaBottom : ypos := IfThen(AClockwise, FRect.Bottom, FRect.Bottom - lineinfo.Width);
end
else
case FVertAlignment of
vaTop : ypos := IfThen(AClockwise, FRect.Top, FRect.Top + lineinfo.Width);
vaCenter : ypos := (FRect.Top + FRect.Bottom - lineinfo.Width*SGN[AClockwise]) div 2;
vaBottom : ypos := IfThen(AClockwise, FRect.Bottom - lineinfo.Width, FRect.Bottom);
end;
DrawLine(pEnd, xpos, ypos, lineinfo.Height, AOverrideTextColor);
inc(xpos, -lineinfo.Height*SGN[AClockwise]);
end;
end;
function TsTextPainter.GetHeight: Integer;
begin
if FTextRotation = trHorizontal then
Result := FTotalHeight
else
Result := FMaxLineLen;
end;
function TsTextPainter.GetTextPt(x,y,ALineHeight: Integer): TPoint;
begin
case FTextRotation of
trHorizontal, rtStacked:
case FFontPos of
fpNormal : Result := Point(x, y);
fpSubscript : Result := Point(x, y + ALineHeight div 2);
fpSuperscript : Result := Point(x, y - ALineHeight div 6);
end;
rt90DegreeClockwiseRotation:
case FFontPos of
fpNormal : Result := Point(x, y);
fpSubscript : Result := Point(x - ALineHeight div 2, y);
fpSuperscript : Result := Point(x + ALineHeight div 6, y);
end;
rt90DegreeCounterClockWiseRotation:
case FFontPos of
fpNormal : Result := Point(x, y);
fpSubscript : Result := Point(x + ALineHeight div 2, y);
fpSuperscript : Result := Point(x - ALineHeight div 6, y);
end;
end;
end;
function TsTextPainter.GetWidth: Integer;
begin
if FTextRotation = trHorizontal then
Result := FMaxLineLen else
Result := FTotalHeight;
end;
{ Called before analyzing and rendering of the text.
ACurrRtpIndex ......... Index of CURRENT rich-text parameter
ACharIndexOfNextFont .. Character index when NEXT font change will occur
ACurrFontHeight ....... CURRENT font height
ACurrFontPos .......... CURRENT font position (normal/sub/superscript) }
procedure TsTextPainter.InitFont(out ACurrRtpIndex, ACharIndexOfNextFont,
ACurrFontHeight: Integer; out ACurrFontPos: TsFontPosition);
var
fnt: TsFont;
begin
FCharIndex := 1;
if (Length(FRtParams) = 0) then
begin
FRtpIndex := -1;
fnt := FWorkbook.GetFont(FFontIndex);
ACharIndexOfNextFont := MaxInt;
end
else if (FRtParams[0].FirstIndex = 1) then
begin
ACurrRtpIndex := 0;
fnt := FWorkbook.GetFont(FRtParams[0].FontIndex);
if Length(FRtParams) > 1 then
ACharIndexOfNextFont := FRtParams[1].FirstIndex
else
ACharIndexOfNextFont := MaxInt;
end else
begin
fnt := FWorkbook.GetFont(FFontIndex);
ACurrRtpIndex := -1;
ACharIndexOfNextFont := FRtParams[0].FirstIndex;
end;
Convert_sFont_to_Font(fnt, FCanvas.Font);
FCanvas.Font.Height := round(FZoomFactor * FCanvas.Font.Height);
ACurrFontHeight := FCanvas.TextHeight('Tg');
if (fnt <> nil) and (fnt.Position <> fpNormal) then
FCanvas.Font.Size := round(fnt.Size * SUBSCRIPT_SUPERSCRIPT_FACTOR * FZoomFactor);
ACurrFontPos := fnt.Position;
end;
procedure TsTextPainter.NextChar(ANumBytes: Integer);
begin
inc(FPtr, ANumBytes);
inc(FCharIndex);
end;
{ Get layout of lines
"lineinfos" collect data for where lines start and end, their width and
height, the rich-text parameter index range, and the number of spaces and
a word list (for text justification). }
procedure TsTextPainter.Prepare;
var
lineInfo: TsLineInfo;
ts: TTextStyle;
oldPtr: PChar;
begin
FTotalHeight := 0;
FMaxLinelen := 0;
if FText = '' then
exit;
ts := FCanvas.TextStyle;
ts.RightToLeft := FRightToLeft;
FCanvas.TextStyle := ts;
InitFont(FRtpIndex, FCharIndexOfNextFont, FFontHeight, FFontPos);
FPtr := PChar(FText);
FCharIndex := 1;
while (FPtr^ <> #0) do begin
lineInfo := TsLineInfo.Create;
lineInfo.pStart := FPtr;
lineInfo.BeginsWithFontOfRtpIndex := FRtpIndex;
oldPtr := FPtr;
ScanLine(lineInfo.NumSpaces, lineInfo.Width, lineInfo.Height, lineInfo.WordList);
if oldPtr = FPtr then // Detect scan is stuck
break;
FLines.Add(lineinfo);
FTotalHeight := FTotalHeight + IfThen(FTextRotation = rtStacked, 2, 1)*lineInfo.Height;
FMaxLineLen := Max(FMaxLineLen, lineInfo.Width);
end;
end;
{ Scans the line for a possible line break and a font change.
The scan starts at the current position of FPtr.
ANumSpaces is how many spaces were found between the start and end value
of FPtr.
ALineWidth the pixel width of the line seen along drawing direction, i.e.
in case of stacked text it is the sum of the character heights!
ALineHeight The height of the line as seen vertically to the drawing
direction. Normally this is the height of the largest font
found in the line; in case of stacked text it is the
width of character 'M'. }
procedure TsTextPainter.ScanLine(var ANumSpaces, ALineWidth, ALineHeight: Integer;
AWordList: TStringList);
var
tmpWidth: Integer;
savedWidth: Integer;
savedHeight: Integer;
savedSpaces: Integer;
savedCharIndex: Integer;
savedCurrRtpIndex: Integer;
savedCharIndexOfNextFont: Integer;
maxWidth: Integer;
s: String;
charLen: Integer;
ch: String;
EOL: Boolean;
pWordStart: PChar;
part, savedPart: String;
begin
ANumSpaces := 0;
ALineHeight := FFontHeight;
ALineWidth := 0;
savedWidth := 0;
savedHeight := 0;
savedSpaces := 0;
s := ''; // current word
part := ''; // current part of the string where all characters have the same font
savedpart := '';
tmpWidth := 0;
maxWidth := MaxInt;
if FWordWrap then
begin
if FTextRotation = trHorizontal then
maxWidth := FRect.Right - FRect.Left
else
maxWidth := FRect.Bottom - FRect.Top;
end;
while (FPtr^ <> #0) do
begin
case FPtr^ of
#13: begin
NextChar(1);
if FPtr^ = #10 then
NextChar(1);
break;
end;
#10: begin
NextChar(1);
break;
end;
' ': begin
ALineWidth := ALineWidth + tmpWidth;
part := '';
tmpWidth := 0; // width of the spaces, growing during scan
// Save data for the case that max width is exceeded here
savedWidth := ALineWidth;
savedHeight := ALineHeight;
savedSpaces := ANumSpaces;
savedPart := part;
// Find next word
while FPtr^ = ' ' do
begin
// We reached a character at which the font changes
// --> update current line width
// This has to be done before "UpdateFont" because the collected
// part string uses the old font.
if (FCharIndex = FCharIndexOfNextFont) then
begin
if (FTextRotation <> rtStacked) then
tmpwidth := tmpwidth + FCanvas.TextWidth(part);
part := '';
savedPart := '';
tmpwidth := 0;
end;
// Update font if required
UpdateFont(FCharIndex, FRtpIndex, FCharIndexOfNextFont, FFontHeight, FFontPos);
part := part + ' ';
if FTextRotation = rtStacked then
begin
tmpwidth := tmpwidth + FFontHeight;
ALineHeight := Max(ALineHeight, FCanvas.TextWidth('M'));
end else begin
tmpwidth := tmpwidth + FCanvas.TextWidth(' ');
ALineHeight := Max(ALineHeight, FFontHeight);
end;
inc(ANumSpaces);
NextChar(1);
end;
if ALineWidth + tmpWidth <= maxWidth then
begin
if FTextRotation = rtStacked then
ALineWidth := ALineWidth + tmpWidth;
end else
begin
// max width has been exceeded while scanning spaces
// --> restore values stored at the end of previous word
ALineWidth := savedWidth;
ALineHeight := savedHeight;
ANumSpaces := savedSpaces;
part := savedPart;
while (part <> '') and (part[Length(part)] = ' ') do
begin
Delete(part, Length(part), 1);
if FTextRotation = rtStacked then dec(ALineWidth, FFontHeight);
end;
break;
end;
end;
else
// Here, a new word begins. Find the end of this word and check if
// it fits into the line.
// Store the data valid for the word start. They are needed if the
// scan would go beyond the max line width in this word.
s := '';
pWordStart := FPtr;
savedCharIndex := FCharIndex;
savedCurrRtpIndex := FRtpIndex;
savedCharIndexOfNextFont := FCharIndexOfNextFont;
savedpart := part;
savedHeight := ALineHeight;
tmpWidth := 0; // width of the current word, growing during the scan
EOL := false;
while (FPtr^ <> #0) and (FPtr^ <> #13) and (FPtr^ <> #10) and (FPtr^ <> ' ') do
begin
if FCharIndex = FCharIndexOfNextFont then
begin
if (FTextRotation <> rtStacked) then
ALineWidth := ALineWidth + FCanvas.TextWidth(part);
part := '';
tmpWidth := 0;
end;
UpdateFont(FCharIndex, FRtpIndex, FCharIndexOfNextFont, FFontHeight, FFontPos);
ch := UnicodeToUTF8(UTF8CharacterToUnicode(FPtr, charLen));
part := part + ch;
if FTextRotation = rtStacked then
begin
tmpWidth := tmpWidth + FFontHeight;
ALineHeight := Max(ALineHeight, FCanvas.TextWidth('M'));
end else
begin
tmpWidth := FCanvas.TextWidth(part);
ALineHeight := Max(FFontHeight, ALineHeight);
end;
if ALineWidth + tmpWidth <= maxWidth then
s := s + ch
else
begin
// The line exeeds the max line width.
// There are two cases:
if ANumSpaces > 0 then
begin
// (a) This is not the only word: Go back to where this
// word began. We already had stored everything needed!
FPtr := pWordStart;
FCharIndex := savedCharIndex;
FCharIndexOfNextFont := savedCharIndexOfNextFont;
FRtpIndex := savedCurrRtpIndex;
ALineHeight := savedHeight;
part := savedPart;
while (part <> '') and (part[Length(part)] = ' ') do
begin
Delete(part, Length(part), 1);
if FTextRotation = rtStacked then dec(ALineWidth, FFontHeight);
end;
end else
begin
// (b) This is the only word in the line --> we break at the
// current cursor position.
if Length(part) = 1 then
NextChar(1)
else
UTF8Delete(part, UTF8Length(part), 1);
end;
EOL := true;
break;
end;
NextChar(charLen);
end;
if EOL then break;
end;
end;
if s <> '' then
AWordList.Add(s);
if (part <> '') then
begin
if (FTextRotation <> rtStacked) then
ALineWidth := ALineWidth + FCanvas.TextWidth(part)
else
ALineWidth := ALineWidth + tmpWidth;
end;
end;
{ The scanner has reached the text character at the specified position.
Determines the
- index of the NEXT rich-text parameter (ANextRtParamIndex)
- character index where NEXT font change will occur (ACharIndexOfNextFont)
- CURRENT font height (ACurrFontHeight)
- CURRENT font position (normal/sub/super) (ACurrFontPos) }
procedure TsTextPainter.UpdateFont(ACharIndex: Integer;
var ACurrRtpIndex, ACharIndexOfNextFont, ACurrFontHeight: Integer;
var ACurrFontPos: TsFontPosition);
var
fnt: TsFont;
begin
if (ACurrRtpIndex < High(FRtParams)) and (ACharIndex = ACharIndexOfNextFont) then
begin
inc(ACurrRtpIndex);
if ACurrRtpIndex < High(FRtParams) then
ACharIndexOfNextFont := FRtParams[ACurrRtpIndex+1].FirstIndex else
ACharIndexOfNextFont := MaxInt;
fnt := FWorkbook.GetFont(FRtParams[ACurrRtpIndex].FontIndex);
Convert_sFont_to_Font(fnt, FCanvas.Font);
FCanvas.Font.Height := round(FZoomFactor * FCanvas.Font.Height);
ACurrFontHeight := FCanvas.TextHeight('Tg');
if fnt.Position <> fpNormal then
FCanvas.Font.Size := round(fnt.Size * SUBSCRIPT_SUPERSCRIPT_FACTOR);
ACurrFontPos := fnt.Position;
end;
end;
end.