mirror of
https://gitlab.com/freepascal.org/lazarus/lazarus.git
synced 2025-04-09 13:29:47 +02:00
1588 lines
45 KiB
ObjectPascal
1588 lines
45 KiB
ObjectPascal
// Global defines
|
|
{$I IPDEFINE.INC}
|
|
|
|
unit ipHtmlBlockLayout;
|
|
|
|
interface
|
|
|
|
uses
|
|
// LCL
|
|
LCLIntf, LazLoggerBase, // Must be before Types
|
|
// RTL, FCL
|
|
Types, Classes, SysUtils,
|
|
// LCL
|
|
Graphics,
|
|
// TurboPower_ipro
|
|
IpUtils, IpHtmlTypes, IpHtmlProp, IpHtmlUtils, IpHtml, IpHtmlNodes;
|
|
|
|
type
|
|
|
|
{ TIpNodeBlockLayouter }
|
|
|
|
TIpNodeBlockLayouter = class(TIpHtmlBaseLayouter)
|
|
private
|
|
FBlockOwner : TIpHtmlNodeBlock;
|
|
FIpHtml : TIpHtml;
|
|
FCanvas : TCanvas;
|
|
FSizeOfSpace : TSize;
|
|
FSizeOfHyphen : TSize;
|
|
FLeftQueue, FRightQueue : TFPList;
|
|
FVRemainL, FVRemainR : Integer;
|
|
FLIdent, FRIdent : Integer;
|
|
FTextWidth, FTotWidth : Integer;
|
|
FFirstWord, FLastWord : Integer;
|
|
FMaxAscent, FMaxDescent, FMaxHeight : Integer;
|
|
FBlockAscent, FBlockDescent, FBlockHeight : Integer;
|
|
FCurAscent, FCurDescent, FCurHeight : Integer;
|
|
iElem, YYY : Integer;
|
|
FBaseOffset : Integer;
|
|
FLineBreak, FExpBreak, FCanBreak : Boolean;
|
|
FIgnoreHardLF : Boolean;
|
|
FTempCenter : Boolean;
|
|
FLTrim : Boolean;
|
|
FLastBreakpoint : Integer;
|
|
FHyphenSpace : Integer;
|
|
FSoftLF, FSoftBreak : Boolean;
|
|
FAl, FSaveAl : TIpHtmlAlign;
|
|
FVAL: TIpHtmlVAlign3;
|
|
FWordInfo : PWordList;
|
|
FWordInfoSize : Integer;
|
|
FClear : (cNone, cLeft, cRight, cBoth);
|
|
FxySize : TSize;
|
|
procedure UpdSpaceHyphenSize(aProps: TIpHtmlProps);
|
|
procedure UpdPropMetrics(aProps: TIpHtmlProps);
|
|
// Used by LayoutQueue :
|
|
procedure QueueInit(const TargetRect: TRect);
|
|
procedure InitMetrics;
|
|
function QueueLeadingObjects: Integer;
|
|
function TrimTrailingBlanks(aFirstElem: Integer = 0): Integer;
|
|
procedure DoQueueAlign(const TargetRect: TRect; aExpLIndent: Integer);
|
|
procedure OutputQueueLine;
|
|
procedure DoQueueClear;
|
|
procedure ApplyQueueProps(aCurElem: PIpHtmlElement; var aPrefor : Boolean);
|
|
procedure DoQueueElemWord(aCurElem: PIpHtmlElement);
|
|
function DoQueueElemObject(var aCurElem: PIpHtmlElement): boolean;
|
|
function DoQueueElemSoftLF(const W: Integer): boolean;
|
|
function DoQueueElemHardLF: boolean;
|
|
function DoQueueElemClear(aCurElem: PIpHtmlElement): boolean;
|
|
procedure DoQueueElemIndentOutdent;
|
|
procedure DoQueueElemSoftHyphen;
|
|
function CalcVRemain(aVRemain: integer; var aIdent: integer): integer;
|
|
procedure SetWordInfoLength(NewLength : Integer);
|
|
function NextElemIsSoftLF: Boolean;
|
|
// RelocateQueue and LayoutQueue
|
|
procedure RelocateQueue(dx, dy: Integer);
|
|
procedure LayoutQueue(TargetRect: TRect);
|
|
{$IFDEF IP_LAZARUS_DBG}
|
|
procedure DumpQueue(bStart: boolean=true);
|
|
{$ENDIF}
|
|
procedure UpdateCurrent(Start: Integer; const CurProps : TIpHtmlProps);
|
|
procedure CalcMinMaxQueueWidth(var aMin, aMax: Integer);
|
|
// Used by RenderQueue :
|
|
procedure DoRenderFont(var aCurWord: PIpHtmlElement);
|
|
procedure DoRenderElemWord(aCurWord: PIpHtmlElement; aCurTabFocus: TIpHtmlNode);
|
|
procedure RenderQueue;
|
|
public
|
|
constructor Create(AOwner: TIpHtmlNodeCore); override;
|
|
destructor Destroy; override;
|
|
// Used by TIpHtmlNodeBlock descendants: Layout, CalcMinMaxPropWidth, Render
|
|
procedure Layout(RenderProps: TIpHtmlProps; TargetRect: TRect); override;
|
|
procedure CalcMinMaxPropWidth(RenderProps: TIpHtmlProps; var aMin, aMax: Integer); override;
|
|
procedure Render(RenderProps: TIpHtmlProps); override;
|
|
end;
|
|
|
|
{ TIpNodeTableElemLayouter }
|
|
|
|
TIpNodeTableElemLayouter = class(TIpNodeBlockLayouter)
|
|
private
|
|
FTableElemOwner : TIpHtmlNodeTableHeaderOrCell;
|
|
public
|
|
constructor Create(AOwner: TIpHtmlNodeCore); override;
|
|
destructor Destroy; override;
|
|
// Methods for TIpHtmlNodeTableHeaderOrCell.
|
|
procedure Layout(RenderProps: TIpHtmlProps; TargetRect: TRect); override;
|
|
procedure CalcMinMaxPropWidth(RenderProps: TIpHtmlProps; var aMin, aMax: Integer); override;
|
|
procedure Render(RenderProps: TIpHtmlProps); override;
|
|
end;
|
|
|
|
|
|
implementation
|
|
|
|
function SameDimensions(const R1, R2 : TRect): Boolean;
|
|
begin
|
|
Result := ( (R1.Bottom - R1.Top = R2.Bottom - R2.Top) or (R1.Top = R2.Top) )
|
|
and ( R1.Right - R1.Left = R2.Right - R2.Left );
|
|
end;
|
|
|
|
function MaxI3(const I1, I2, I3: Integer) : Integer;
|
|
begin
|
|
if I2 > I1 then
|
|
if I3 > I2 then
|
|
Result := I3
|
|
else
|
|
Result := I2
|
|
else
|
|
if I3 > I1 then
|
|
Result := I3
|
|
else
|
|
Result := I1;
|
|
end;
|
|
|
|
{ TIpHtmlLayouter }
|
|
|
|
constructor TIpNodeBlockLayouter.Create(AOwner: TIpHtmlNodeCore);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FIpHtml := FOwner.Owner;
|
|
FBlockOwner := TIpHtmlNodeBlock(FOwner);
|
|
FElementQueue := TFPList.Create;
|
|
end;
|
|
|
|
destructor TIpNodeBlockLayouter.Destroy;
|
|
begin
|
|
ClearWordList;
|
|
FreeAndNil(FElementQueue);
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.UpdSpaceHyphenSize(aProps: TIpHtmlProps);
|
|
begin
|
|
if aProps.PropA.SizeOfSpaceKnown then begin
|
|
FSizeOfSpace := aProps.PropA.KnownSizeOfSpace;
|
|
FSizeOfHyphen := aProps.PropA.KnownSizeOfHyphen;
|
|
end else begin
|
|
Assert(aProps.PropA.tmHeight = 0, 'UpdSpaceHyphenSize: PropA.tmHeight > 0');
|
|
FCanvas.Font.Name := aProps.FontName;
|
|
FCanvas.Font.Size := aProps.FontSize;
|
|
FCanvas.Font.Style := aProps.FontStyle;
|
|
FCanvas.Font.Quality := FOwner.Owner.FontQuality;
|
|
FSizeOfSpace := FCanvas.TextExtent(' ');
|
|
{$IFDEF IP_LAZARUS_DBG}
|
|
if FSizeOfSpace.CX=0 then
|
|
DebugLn('TIpHtmlNodeBlock.UpdSpaceHyphenSize Font not found "',FCanvas.Font.Name,'" Size=',dbgs(FCanvas.Font.Size));
|
|
{$ENDIF}
|
|
FSizeOfHyphen := FCanvas.TextExtent('-');
|
|
aProps.PropA.SetKnownSizeOfSpace(FSizeOfSpace);
|
|
aProps.PropA.KnownSizeOfHyphen := FSizeOfHyphen;
|
|
end;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.UpdPropMetrics(aProps: TIpHtmlProps);
|
|
var
|
|
TextMetrics: TLCLTextMetric; // TTextMetric;
|
|
begin // Debug: remove assertions later
|
|
Assert(aProps.PropA.tmHeight = 0, 'UpdPropMetrics: PropA.tmHeight > 0');
|
|
Assert(FCanvas.Font.Name = aProps.FontName, 'UpdPropMetrics: FCanvas.Font.Name <> aProps.FontName');
|
|
Assert(FCanvas.Font.Size = aProps.FontSize, 'UpdPropMetrics: FCanvas.Font.Size <> aProps.FontSize');
|
|
Assert(FCanvas.Font.Style = aProps.FontStyle, 'UpdPropMetrics: FCanvas.Font.Style <> aProps.FontStyle');
|
|
FCanvas.GetTextMetrics(TextMetrics);
|
|
aProps.PropA.tmAscent := TextMetrics.Ascender;
|
|
aProps.PropA.tmDescent := TextMetrics.Descender;
|
|
aProps.PropA.tmHeight := TextMetrics.Height;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.Layout(RenderProps: TIpHtmlProps; TargetRect: TRect);
|
|
begin
|
|
if EqualRect(TargetRect, FBlockOwner.PageRect) then
|
|
exit;
|
|
|
|
if FBlockOwner is TIpHtmlNodeBody then
|
|
RemoveLeadingLFs;
|
|
ProcessDuplicateLFs;
|
|
|
|
if not RenderProps.IsEqualTo(Props) then
|
|
begin
|
|
Props.Assign(RenderProps);
|
|
FOwner.LoadAndApplyCSSProps;
|
|
FOwner.SetProps(Props);
|
|
end;
|
|
if FElementQueue.Count = 0 then
|
|
FOwner.Enqueue;
|
|
if SameDimensions(TargetRect, FBlockOwner.PageRect) then
|
|
RelocateQueue(TargetRect.Left - FBlockOwner.PageRect.Left,
|
|
TargetRect.Top - FBlockOwner.PageRect.Top)
|
|
else
|
|
LayoutQueue(TargetRect);
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.RelocateQueue(dx, dy: Integer);
|
|
var
|
|
i : Integer;
|
|
CurElem : PIpHtmlElement;
|
|
R : TRect;
|
|
begin
|
|
OffsetRect(FPageRect, dx, dy);
|
|
for i := 0 to FElementQueue.Count-1 do begin
|
|
CurElem := PIpHtmlElement(FElementQueue[i]);
|
|
R := CurElem^.WordRect2;
|
|
if R.Bottom <> 0 then begin
|
|
OffsetRect(R, dx, dy);
|
|
SetWordRect(CurElem, R);
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.QueueInit(const TargetRect: TRect);
|
|
begin
|
|
FWordInfoSize := 0;
|
|
FWordInfo := nil;
|
|
YYY := TargetRect.Top;
|
|
FLeftQueue := TFPList.Create;
|
|
FRightQueue := TFPList.Create;
|
|
//FSizeOfSpace := Owner.Target.TextExtent(' ');
|
|
//FSizeOfHyphen := Owner.Target.TextExtent('-');
|
|
FCurProps := nil;
|
|
FLIdent := 0;
|
|
FRIdent := 0;
|
|
FVRemainL := 0;
|
|
FVRemainR := 0;
|
|
FClear := cNone;
|
|
FExpBreak := True;
|
|
FTempCenter := False;
|
|
FSaveAl := haLeft;
|
|
FIgnoreHardLF := False;
|
|
FLastBreakpoint := 0;
|
|
FPageRect := TargetRect;
|
|
FMaxHeight := 0;
|
|
FMaxAscent := 0;
|
|
FMaxDescent := 0;
|
|
FLineBreak := False;
|
|
FAl := haLeft;
|
|
FVAL := hva3Top;
|
|
FCurAscent := 0;
|
|
FCurDescent := 0;
|
|
FCurHeight := 0;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.InitMetrics;
|
|
var
|
|
TextMetrics : TLCLTextMetric;
|
|
begin
|
|
FCanvas.GetTextMetrics(TextMetrics);
|
|
FBlockAscent := TextMetrics.Ascender;
|
|
FBlockDescent := TextMetrics.Descender;
|
|
FBlockHeight := TextMetrics.Height;
|
|
end;
|
|
|
|
function TIpNodeBlockLayouter.QueueLeadingObjects: Integer;
|
|
// Returns the first element index.
|
|
var
|
|
CurObj : TIpHtmlNodeAlignInline;
|
|
CurElem : PIpHtmlElement;
|
|
begin
|
|
Result := 0;
|
|
while Result <= FElementQueue.Count-1 do begin
|
|
CurElem := PIpHtmlElement(FElementQueue[Result]);
|
|
case CurElem.ElementType of
|
|
etObject :
|
|
begin
|
|
CurObj := TIpHtmlNodeAlignInline(CurElem.Owner);
|
|
case CurObj.Align of
|
|
hiaLeft :
|
|
begin
|
|
FLeftQueue.Add(CurElem);
|
|
Inc(Result);
|
|
end;
|
|
hiaRight :
|
|
begin
|
|
FRightQueue.Add(CurElem);
|
|
Inc(Result);
|
|
end;
|
|
else
|
|
break;
|
|
end;
|
|
end
|
|
else
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
function TIpNodeBlockLayouter.TrimTrailingBlanks(aFirstElem: Integer): Integer;
|
|
// Trim trailing blanks. Returns the last element index.
|
|
var
|
|
CurElem: PIpHtmlElement;
|
|
begin
|
|
Result := FElementQueue.Count - 1;
|
|
repeat
|
|
if (Result < aFirstElem) then Break;
|
|
CurElem := PIpHtmlElement(FElementQueue[Result]);
|
|
if (CurElem.ElementType <> etWord) or (CurElem.IsBlank = 0) then Break;
|
|
Dec(Result)
|
|
until false;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.DoQueueAlign(const TargetRect: TRect; aExpLIndent: Integer);
|
|
|
|
procedure DoQueueAlignSub(aQueue: TFPList; aRight: Boolean);
|
|
var
|
|
CurElem: PIpHtmlElement;
|
|
CurObj: TIpHtmlNodeAlignInline;
|
|
xLeft, xRight, ySize: Integer;
|
|
RectWidth: Integer;
|
|
begin
|
|
if aRight then
|
|
xLeft := FVRemainR
|
|
else
|
|
xLeft := FVRemainL;
|
|
if (aQueue.Count > 0) and (xLeft = 0) then
|
|
while aQueue.Count > 0 do begin
|
|
CurElem := aQueue[0];
|
|
CurObj := TIpHtmlNodeAlignInline(CurElem.Owner);
|
|
RectWidth := TargetRect.Right - TargetRect.Left;
|
|
FxySize := CurObj.GetDim(RectWidth);
|
|
FTotWidth := RectWidth - FLIdent - FRIdent - FxySize.cx - aExpLIndent;
|
|
if FTotWidth < 0 then
|
|
break;
|
|
if aRight then begin
|
|
xRight := TargetRect.Right - FRIdent;
|
|
xLeft := xRight - FxySize.cx;
|
|
Inc(FRIdent, FxySize.cx);
|
|
FVRemainR := MaxI2(FVRemainR, FxySize.cy)
|
|
end
|
|
else begin
|
|
xLeft := TargetRect.Left + FLIdent;
|
|
xRight := xLeft + FxySize.cx;
|
|
Inc(FLIdent, FxySize.cx);
|
|
FVRemainL := MaxI2(FVRemainL, FxySize.cy);
|
|
end;
|
|
ySize := FxySize.cy;
|
|
SetWordRect(CurElem, Rect(xLeft, YYY, xRight, YYY+FxySize.cy));
|
|
Assert(ySize = FxySize.cy, 'TIpNodeBlockLayouter.DoQueueAligned: ySize <> FSize.cy'); // Can be removed later.
|
|
aQueue.Delete(0);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
DoQueueAlignSub(FLeftQueue, False); // Left
|
|
DoQueueAlignSub(FRightQueue, True); // Right
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.OutputQueueLine;
|
|
const
|
|
NullRect : TRect = (Left:0; Top:0; Right:0; Bottom:0);
|
|
var
|
|
WDelta, WMod : Integer;
|
|
|
|
function CalcDelta: Integer; // Returns dx
|
|
begin
|
|
WDelta := 0;
|
|
WMod := 0;
|
|
Result := 0;
|
|
case FAl of
|
|
haUnknown : // by Juha
|
|
Assert(False, 'TIpNodeBlockLayouter.OutputQueueLine: Align = Unknown.');
|
|
haDefault, haLeft :
|
|
;
|
|
haCenter :
|
|
if FTotWidth >= FTextWidth then
|
|
Result := (FTotWidth - FTextWidth) div 2;
|
|
haRight :
|
|
if FTotWidth >= FTextWidth then
|
|
Result := FTotWidth - FTextWidth;
|
|
haChar :
|
|
if FTotWidth >= FTextWidth then
|
|
Result := (FTotWidth - FTextWidth) div 2;
|
|
{
|
|
else
|
|
//haJustify :
|
|
if iElem < FElementQueue.Count then begin
|
|
m := iElem - FFirstWord - 2;
|
|
if m > 0 then begin
|
|
WDelta := (FTotWidth - FTextWidth) div m;
|
|
WMod := (FTotWidth - FTextWidth) mod m;
|
|
end;
|
|
end;
|
|
}
|
|
end;
|
|
end;
|
|
|
|
var
|
|
j, dx, ph : Integer;
|
|
R : TRect;
|
|
CurElem : PIpHtmlElement;
|
|
CurWordInfo : PWordInfo;
|
|
isRTL: Boolean;
|
|
rtlNode: TIpHtmlNodeCORE;
|
|
begin
|
|
dx := CalcDelta;
|
|
ph := FIpHtml.PageHeight;
|
|
if ph <> 0 then begin
|
|
{if we're printing, adjust line's vertical offset to not straddle a page boundary}
|
|
j := YYY mod ph;
|
|
{only do this for 'small' objects, like text lines}
|
|
if (FMaxAscent + FMaxDescent < 200)
|
|
and (j + FMaxAscent + FMaxDescent > ph) then
|
|
Inc(YYY, ((j + FMaxAscent + FMaxDescent) - ph));
|
|
end;
|
|
|
|
for j := FFirstWord to FLastWord do begin
|
|
CurElem := PIpHtmlElement(FElementQueue[j]);
|
|
CurWordInfo := @FWordInfo[j - FFirstWord];
|
|
if CurWordInfo.Sz.cx <> 0 then begin
|
|
R.Left := CurWordInfo.BaseX;
|
|
R.Right := R.Left + CurWordInfo.Sz.cx;
|
|
case CurWordInfo.VA of
|
|
hva3Top :
|
|
begin
|
|
R.Top := YYY;
|
|
R.Bottom := YYY + CurWordInfo.Sz.cy;
|
|
end;
|
|
hva3Middle :
|
|
begin
|
|
R.Top := YYY + (FMaxHeight - CurWordInfo.Sz.cy) div 2;
|
|
R.Bottom := R.Top + CurWordInfo.Sz.cy;
|
|
end;
|
|
hva3Bottom :
|
|
begin
|
|
R.Top := YYY + FMaxHeight - CurWordInfo.Sz.cy;
|
|
R.Bottom := R.Top + CurWordInfo.Sz.cy;
|
|
end;
|
|
hva3Default,
|
|
hva3Baseline :
|
|
begin
|
|
if CurWordInfo.CurAsc >= 0 then
|
|
R.Top := YYY + FMaxAscent - CurWordInfo.CurAsc
|
|
else
|
|
R.Top := YYY;
|
|
R.Bottom := R.Top + CurWordInfo.Sz.cy;
|
|
end;
|
|
end;
|
|
if WMod <> 0 then begin
|
|
OffsetRect(R, dx + WDelta + 1, 0);
|
|
Dec(WMod);
|
|
end else
|
|
OffsetRect(R, dx + WDelta, 0);
|
|
|
|
{ mirroring for RTL reading }
|
|
isRTL := (FOwner.Dir = hdRTL);
|
|
if (CurElem.Owner <> nil) and (CurElem.Owner.ParentNode <> nil) and
|
|
(CurElem.Owner.ParentNode is TIpHTMLNodeCORE) then
|
|
begin
|
|
rtlNode := TIpHtmlNodeCORE(CurElem.Owner.ParentNode);
|
|
if isRTL and (rtlNode.Dir = hdLTR) then
|
|
isRTL := false
|
|
else
|
|
if not isRTL and (rtlNode.Dir = hdRTL) then
|
|
isRTL := true;
|
|
end;
|
|
if isRTL then
|
|
R.SetLocation(FPageRect.Right - R.Right + FPageRect.Left - 1, R.Top);
|
|
|
|
SetWordRect(CurElem, R);
|
|
end else
|
|
SetWordRect(CurElem, NullRect);
|
|
end;
|
|
|
|
if FTempCenter then begin
|
|
FAl := FSaveAl;
|
|
FTempCenter := False;
|
|
end;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.DoQueueClear;
|
|
begin
|
|
case FClear of
|
|
cLeft :
|
|
if FVRemainL > 0 then begin
|
|
Inc(YYY, FVRemainL);
|
|
FVRemainL := 0;
|
|
FLIdent := 0;
|
|
end;
|
|
cRight :
|
|
if FVRemainR > 0 then begin
|
|
Inc(YYY, FVRemainR);
|
|
FVRemainR := 0;
|
|
FRIdent := 0;
|
|
end;
|
|
cBoth :
|
|
begin
|
|
Inc(YYY, MaxI2(FVRemainL, FVRemainR));
|
|
FVRemainL := 0;
|
|
FVRemainR := 0;
|
|
FLIdent := 0;
|
|
FRIdent := 0;
|
|
end;
|
|
end;
|
|
FClear := cNone;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.ApplyQueueProps(aCurElem: PIpHtmlElement; var aPrefor: Boolean);
|
|
begin
|
|
with aCurElem.Props do begin
|
|
if (FCurProps = nil) or not AIsEqualTo(FCurProps) then begin
|
|
UpdSpaceHyphenSize(aCurElem.Props);
|
|
if PropA.tmHeight = 0 then
|
|
UpdPropMetrics(aCurElem.Props);
|
|
FBlockHeight := PropA.tmHeight;
|
|
FBlockAscent := PropA.tmAscent;
|
|
FBlockDescent := PropA.tmDescent;
|
|
end;
|
|
if (FCurProps = nil) or not BIsEqualTo(FCurProps) then begin
|
|
FAl := self.Props.Alignment; // was: FAl := Alignment
|
|
// wp: line was changed to "FAl := self.Props.Alignment" in order
|
|
// to fix horizontal text alignment of table cells (r50145).
|
|
// But with this change, something like "<p align="center"> does not work any more!
|
|
// Alignment within cells still seems to work correctly after user to old code.
|
|
FVAL := VAlignment;
|
|
FBaseOffset := FontBaseline;
|
|
aPrefor := Preformatted;
|
|
end;
|
|
end;
|
|
FCurProps := aCurElem.Props;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.DoQueueElemWord(aCurElem: PIpHtmlElement);
|
|
var
|
|
lAlign: TIpHtmlAlign;
|
|
node: TIpHtmlNode;
|
|
begin
|
|
// wp: added to fix Align of <p> and <div> nodes in <tc>
|
|
if Assigned(aCurElem.Owner) then begin
|
|
lAlign := FAl;
|
|
node := aCurElem.Owner.ParentNode;
|
|
while Assigned(node) do begin
|
|
if (node is TIpHtmlNodeP) or (node is TIpHtmlNodeDIV) or (node is TIpHtmlNodeTableHeaderOrCell) then
|
|
lAlign := TIpHtmlNodeCore(node).Align
|
|
else
|
|
break;
|
|
if lAlign = haDefault then
|
|
node := node.ParentNode
|
|
else begin
|
|
FAl := lAlign;
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
FIgnoreHardLF := False;
|
|
if FLTrim and (aCurElem.IsBlank <> 0) then
|
|
FxySize := SizeRec(0, 0)
|
|
else begin
|
|
if aCurElem.IsBlank <> 0 then begin
|
|
FxySize.cx := FSizeOfSpace.cx * aCurElem.IsBlank;
|
|
FxySize.cy := FSizeOfSpace.cy;
|
|
FCanBreak := True;
|
|
end else begin
|
|
if (aCurElem.SizeProp = FCurProps.PropA) then
|
|
FxySize := aCurElem.Size
|
|
else begin
|
|
FCanvas.Font.Name := FCurProps.FontName;
|
|
FCanvas.Font.Size := FCurProps.FontSize;
|
|
FCanvas.Font.Style := FCurProps.FontStyle;
|
|
FCanvas.Font.Quality := FOwner.Owner.FontQuality;
|
|
aCurElem.Size := FCanvas.TextExtent(NoBreakToSpace(aCurElem.AnsiWord));
|
|
FxySize := aCurElem.Size;
|
|
aCurElem.SizeProp := FCurProps.PropA;
|
|
end;
|
|
end;
|
|
FLTrim := False;
|
|
FLineBreak := False;
|
|
FExpBreak := False;
|
|
end;
|
|
FCurAscent := FBlockAscent;
|
|
FCurDescent := FBlockDescent;
|
|
FCurHeight := FBlockHeight;
|
|
end;
|
|
|
|
function TIpNodeBlockLayouter.DoQueueElemObject(var aCurElem: PIpHtmlElement): boolean;
|
|
|
|
procedure ObjectVertical(Ascent, Descent: Integer);
|
|
begin
|
|
FExpBreak := False;
|
|
FLTrim := False;
|
|
FCurAscent := Ascent;
|
|
FCurDescent := Descent;
|
|
end;
|
|
|
|
function ObjectHorizontal: boolean;
|
|
begin
|
|
aCurElem := nil;
|
|
FCurHeight := 0;
|
|
FxySize.cx := 0;
|
|
Result := FLTrim;
|
|
if Result then
|
|
Inc(iElem);
|
|
end;
|
|
|
|
var
|
|
CurObj : TIpHtmlNodeAlignInline;
|
|
begin
|
|
FIgnoreHardLF := False;
|
|
FCurAscent := 0;
|
|
FCurDescent := 0;
|
|
FCanBreak := True;
|
|
FLineBreak := False;
|
|
CurObj := TIpHtmlNodeAlignInline(aCurElem.Owner);
|
|
FxySize := CurObj.GetDim(FTotWidth);
|
|
FCurHeight := FxySize.cy;
|
|
case Curobj.Align of
|
|
hiaCenter : begin
|
|
ObjectVertical(FMaxAscent, FxySize.cy - FMaxAscent);
|
|
FTempCenter := True;
|
|
FSaveAl := FAl;
|
|
FAl := haCenter;
|
|
end;
|
|
hiaTop :
|
|
ObjectVertical(-1, FxySize.cy);
|
|
hiaMiddle :
|
|
ObjectVertical(FxySize.cy div 2, FxySize.cy div 2);
|
|
hiaBottom :
|
|
ObjectVertical(FxySize.cy, 0);
|
|
hiaLeft : begin
|
|
FLeftQueue.Add(aCurElem);
|
|
if ObjectHorizontal then
|
|
Exit(False);
|
|
end;
|
|
hiaRight : begin
|
|
FRightQueue.Add(aCurElem);
|
|
if ObjectHorizontal then
|
|
Exit(False);
|
|
end;
|
|
end;
|
|
Result := True;
|
|
end;
|
|
|
|
function TIpNodeBlockLayouter.DoQueueElemSoftLF(const W: Integer): boolean;
|
|
// Returns FIgnoreHardLF
|
|
var
|
|
PendingLineBreak : Boolean;
|
|
begin
|
|
if FLineBreak or FExpBreak then begin
|
|
FMaxAscent := 0;
|
|
FMaxDescent := 0;
|
|
PendingLineBreak := False;
|
|
end else begin
|
|
if FMaxAscent = 0 then begin
|
|
FMaxAscent := MaxI2(FMaxAscent, FBlockAscent);
|
|
FMaxDescent := MaxI2(FMaxDescent, FBlockDescent);
|
|
end;
|
|
PendingLineBreak := True;
|
|
end;
|
|
FExpBreak := True;
|
|
if FLineBreak then
|
|
FMaxDescent := 0;
|
|
Inc(iElem);
|
|
FLastWord := iElem - 2;
|
|
if PendingLineBreak then
|
|
FLineBreak := True;
|
|
Result := FIgnoreHardLF;
|
|
if Result then begin
|
|
FxySize.cx := W + 1;
|
|
FSoftLF := True;
|
|
end;
|
|
end;
|
|
|
|
function TIpNodeBlockLayouter.DoQueueElemHardLF: boolean;
|
|
// Returns FIgnoreHardLF
|
|
begin
|
|
FExpBreak := True;
|
|
if FMaxAscent = 0 then begin
|
|
FMaxAscent := MaxI2(FMaxAscent, FBlockAscent);
|
|
FMaxDescent := MaxI2(FMaxDescent, FBlockDescent);
|
|
end;
|
|
if FLineBreak then
|
|
FMaxDescent := 0;
|
|
FLastWord := iElem - 1;
|
|
Result := FIgnoreHardLF;
|
|
if not Result then begin
|
|
if FLineBreak then begin
|
|
FMaxAscent := Round (FMaxAscent * FIpHtml.FactBAParag);
|
|
FMaxDescent := Round (FMaxDescent * FIpHtml.FactBAParag);
|
|
end;
|
|
Inc(iElem);
|
|
end;
|
|
end;
|
|
|
|
function TIpNodeBlockLayouter.DoQueueElemClear(aCurElem: PIpHtmlElement): boolean;
|
|
// Returns FIgnoreHardLF
|
|
begin
|
|
FExpBreak := True;
|
|
case aCurElem.ElementType of
|
|
etClearLeft : FClear := cLeft;
|
|
etClearRight : FClear := cRight;
|
|
etClearBoth : FClear := cBoth;
|
|
end;
|
|
if FLineBreak then
|
|
FMaxDescent := 0;
|
|
Inc(iElem);
|
|
FLastWord := iElem - 2;
|
|
Result := FIgnoreHardLF;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.DoQueueElemIndentOutdent;
|
|
begin
|
|
FCurAscent := 1;
|
|
FCurDescent := 0;
|
|
FCurHeight := 1;
|
|
FxySize := SizeRec(0, 0);
|
|
FCanBreak := True;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.DoQueueElemSoftHyphen;
|
|
begin
|
|
FIgnoreHardLF := False;
|
|
FxySize := FSizeOfHyphen;
|
|
FxySize.cy := FSizeOfSpace.cy;
|
|
FHyphenSpace := FxySize.cx;
|
|
FCanBreak := True;
|
|
FLTrim := False;
|
|
FLineBreak := False;
|
|
FExpBreak := False;
|
|
FCurAscent := FBlockAscent;
|
|
FCurDescent := FBlockDescent;
|
|
FCurHeight := FBlockHeight;
|
|
end;
|
|
|
|
function TIpNodeBlockLayouter.CalcVRemain(aVRemain: integer; var aIdent: integer): integer;
|
|
begin
|
|
if aVRemain > 0 then begin
|
|
if FSoftBreak and (FTextWidth = 0) and (FMaxAscent + FMaxDescent = 0) then begin
|
|
Inc(YYY, aVRemain);
|
|
aVRemain := 0;
|
|
aIdent := 0;
|
|
end else begin
|
|
Dec(aVRemain, FMaxAscent + FMaxDescent);
|
|
if aVRemain <= 0 then begin
|
|
aVRemain := 0;
|
|
aIdent := 0;
|
|
end;
|
|
end;
|
|
end;
|
|
Result := aVRemain;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.SetWordInfoLength(NewLength : Integer);
|
|
var
|
|
NewWordInfoSize: Integer;
|
|
begin
|
|
if (FWordInfo = nil) or (NewLength > FWordInfoSize) then begin
|
|
NewWordInfoSize := ((NewLength div 256) + 1) * 256;
|
|
//code below does not check if FWordInfo<>nil
|
|
ReallocMem(FWordInfo,NewWordInfoSize * sizeof(TWordInfo));
|
|
(*
|
|
NewWordInfo := AllocMem(NewWordInfoSize * sizeof(TWordInfo));
|
|
move(WordInfo^, NewWordInfo^, WordInfoSize);
|
|
Freemem(WordInfo);
|
|
WordInfo := NewWordInfo;
|
|
*)
|
|
FWordInfoSize := NewWordInfoSize;
|
|
end;
|
|
end;
|
|
|
|
function TIpNodeBlockLayouter.NextElemIsSoftLF: Boolean;
|
|
var
|
|
NextElem: PIpHtmlElement;
|
|
begin
|
|
Result := False;
|
|
if iElem < FElementQueue.Count-1 then begin
|
|
NextElem := PIpHtmlElement(FElementQueue[iElem+1]);
|
|
Result := NextElem.ElementType = etSoftLF;
|
|
end;
|
|
end;
|
|
|
|
{$IFDEF IP_LAZARUS_DBG}
|
|
procedure TIpNodeBlockLayouter.DumpQueue(bStart: boolean=true);
|
|
var
|
|
i: Integer;
|
|
CurElem : PIpHtmlElement;
|
|
begin
|
|
if bStart then WriteLn('<<<<<')
|
|
else WriteLn('>>>>>');
|
|
for i := 0 to FElementQueue.Count - 1 do begin
|
|
CurElem := PIpHtmlElement(FElementQueue[i]);
|
|
if CurElem.Owner <> nil then
|
|
write(CurElem.Owner.ClassName,':');
|
|
with CurElem.WordRect2 do
|
|
write(Left,':', Top,':', Right,':', Bottom,':');
|
|
case CurElem.ElementType of
|
|
etWord :
|
|
Write(' wrd:', CurElem.AnsiWord);
|
|
etObject :
|
|
Write(' obj');
|
|
etSoftLF :
|
|
Write(' softlf');
|
|
etHardLF :
|
|
Write(' hardlf');
|
|
etClearLeft :
|
|
Write(' clearleft');
|
|
etClearRight :
|
|
Write(' clearright');
|
|
etClearBoth :
|
|
Write(' clearboth');
|
|
etIndent :
|
|
Write(' indent');
|
|
etOutdent :
|
|
Write(' outdent');
|
|
etSoftHyphen :
|
|
Write(' softhyphen');
|
|
end;
|
|
WriteLn;
|
|
end;
|
|
if bStart then WriteLn('<<<<<')
|
|
else WriteLn('>>>>>');
|
|
end;
|
|
{$ENDIF}
|
|
|
|
procedure TIpNodeBlockLayouter.LayoutQueue(TargetRect: TRect);
|
|
var
|
|
WW, X0, ExpLIndent, RectWidth, i : Integer;
|
|
FirstElem, LastElem : Integer;
|
|
PendingIndent, PendingOutdent : Integer;
|
|
Prefor, NeedProps: Boolean;
|
|
CurElem, PropsElem: PIpHtmlElement;
|
|
wi: PWordInfo;
|
|
lfh: Integer;
|
|
|
|
procedure InitInner;
|
|
begin
|
|
if PendingIndent > PendingOutdent then begin
|
|
if ExpLIndent < RectWidth - FLIdent - FRIdent then
|
|
Inc(ExpLIndent, (PendingIndent - PendingOutdent) * StdIndent);
|
|
end
|
|
else if PendingOutdent > PendingIndent then begin
|
|
Dec(ExpLIndent, (PendingOutdent - PendingIndent) * StdIndent);
|
|
if ExpLIndent < 0 then
|
|
ExpLIndent := 0;
|
|
end;
|
|
PendingIndent := 0;
|
|
PendingOutdent := 0;
|
|
DoQueueAlign(TargetRect, ExpLIndent);
|
|
FTotWidth := RectWidth - FLIdent - FRIdent - ExpLIndent;
|
|
FLTrim := FLineBreak or (FExpBreak and not Prefor) or (ExpLIndent > 0);
|
|
WW := FTotWidth; // total width we have
|
|
X0 := TargetRect.Left + FLIdent + ExpLIndent;
|
|
FTextWidth := 0;
|
|
FFirstWord := iElem;
|
|
FLastWord := iElem-1;
|
|
FBaseOffset := 0;
|
|
FSoftBreak := False;
|
|
FHyphenSpace := 0;
|
|
lfh := 0;
|
|
end;
|
|
|
|
procedure ContinueRow;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if FCanBreak then
|
|
FLastBreakpoint := iElem;
|
|
FMaxAscent := MaxI2(FMaxAscent, FCurAscent);
|
|
FMaxDescent := MaxI2(FMaxDescent, FCurDescent);
|
|
FMaxHeight := MaxI3(FMaxHeight, FCurHeight, FMaxAscent + FMaxDescent);
|
|
// if word fits on line update width and height
|
|
if CurElem.ElementType = etIndent then begin
|
|
i := StdIndent;
|
|
FxySize.cx := MinI2(WW, i - ((X0 - TargetRect.Left) mod i));
|
|
end;
|
|
Dec(WW, FxySize.cx);
|
|
Inc(FTextWidth, FxySize.cx);
|
|
if FHyphenSpace > 0 then
|
|
for i := 0 to iElem - FFirstWord - 1 do begin
|
|
Assert(i < FWordInfoSize);
|
|
wi := @FWordInfo[i];
|
|
if wi^.Hs > 0 then begin
|
|
Inc(WW, wi^.Hs);
|
|
Dec(FTextWidth, wi^.Hs);
|
|
Dec(X0, wi^.Hs);
|
|
wi^.Hs := 0;
|
|
wi^.Sz.cx := 0;
|
|
end;
|
|
end;
|
|
SetWordInfoLength(iElem - FFirstWord + 1);
|
|
wi := @FWordInfo[iElem - FFirstWord];
|
|
wi^.Sz := SizeRec(FxySize.cx, FCurHeight);
|
|
wi^.BaseX := X0;
|
|
wi^.BOff := FBaseOffset;
|
|
wi^.CurAsc := FCurAscent + FBaseOffset;
|
|
wi^.VA := FVAL;
|
|
wi^.Hs := FHyphenSpace;
|
|
FHyphenSpace := 0;
|
|
Inc(X0, FxySize.cx);
|
|
FLastWord := iElem;
|
|
end;
|
|
|
|
procedure EndRow;
|
|
var
|
|
i: Integer;
|
|
begin
|
|
if FHyphenSpace > 0 then
|
|
for i := 0 to iElem - FFirstWord - 2 do begin
|
|
wi := @FWordInfo[i];
|
|
if wi^.Hs > 0 then begin
|
|
Dec(FTextWidth, wi^.Hs);
|
|
wi^.Hs := 0;
|
|
wi^.Sz.cx := 0;
|
|
end;
|
|
end;
|
|
if FCanBreak then
|
|
FLastBreakpoint := iElem - 1;
|
|
if (FLastWord >= 0) and (FLastWord < FElementQueue.Count) then begin
|
|
CurElem := PIpHtmlElement(FElementQueue[FLastWord]);
|
|
if (CurElem.ElementType = etWord)
|
|
and (CurElem.IsBlank <> 0) then begin
|
|
FWordInfo[FLastWord - FFirstWord].Sz.cx := 0;
|
|
FLastWord := iElem - 2;
|
|
end;
|
|
end;
|
|
FLineBreak := True;
|
|
FSoftBreak := not FSoftLF;
|
|
end;
|
|
|
|
begin
|
|
FCanvas := FIpHtml.Target;
|
|
if FElementQueue.Count = 0 then Exit;
|
|
{$IFDEF IP_LAZARUS_DBG}
|
|
DumpQueue; {debug}
|
|
{$endif}
|
|
try
|
|
QueueInit(TargetRect);
|
|
InitMetrics;
|
|
FirstElem := QueueLeadingObjects;
|
|
LastElem := TrimTrailingBlanks(FirstElem);
|
|
DoQueueAlign(TargetRect, 0);
|
|
Prefor := False;
|
|
ExpLIndent := 0;
|
|
PendingIndent := 0;
|
|
PendingOutdent := 0;
|
|
RectWidth := TargetRect.Right - TargetRect.Left;
|
|
iElem := FirstElem;
|
|
while iElem <= LastElem do begin
|
|
InitInner;
|
|
NeedProps := true;
|
|
while iElem < FElementQueue.Count do begin
|
|
FCanBreak := False;
|
|
CurElem := PIpHtmlElement(FElementQueue[iElem]);
|
|
PropsElem := CurElem;
|
|
if (PropsElem.Props = nil) and NeedProps then begin
|
|
// Props exists only for elements with different attributes compared to previous sibling
|
|
// -> search previous element with Props
|
|
i := iElem;
|
|
while (i>=0) and (PIpHtmlElement(FElementQueue[i]).Props=nil) do
|
|
dec(i);
|
|
if i>=0 then
|
|
PropsElem:=PIpHtmlElement(FElementQueue[i]);
|
|
end;
|
|
if PropsElem.Props <> nil then begin
|
|
ApplyQueueProps(PropsElem, Prefor);
|
|
NeedProps:=false;
|
|
end;
|
|
FSoftLF := False;
|
|
case CurElem.ElementType of
|
|
etWord :
|
|
DoQueueElemWord(CurElem);
|
|
etObject :
|
|
if not DoQueueElemObject(CurElem) then
|
|
Break;
|
|
etSoftLF :
|
|
if not DoQueueElemSoftLF(WW) then
|
|
begin
|
|
if CurElem.LFHeight > 0 then
|
|
lfh := CurElem.LFHeight;
|
|
Break;
|
|
end;
|
|
etHardLF :
|
|
if not DoQueueElemHardLF then
|
|
begin
|
|
if CurElem.LFHeight > 0 then
|
|
lfh := CurElem.LFHeight;
|
|
// raise EIpHtmlException.Create('TIpNodeBlockLayouter.LayoutQueue: FIgnoreHardLF is True after all.')
|
|
//else
|
|
Break;
|
|
end;
|
|
etClearLeft, etClearRight, etClearBoth :
|
|
if not DoQueueElemClear(CurElem) then
|
|
Break;
|
|
etIndent : begin
|
|
DoQueueElemIndentOutdent;
|
|
if not NextElemIsSoftLF then
|
|
FIgnoreHardLF := True;
|
|
Inc(PendingIndent);
|
|
FLTrim := True;
|
|
end;
|
|
etOutdent : begin
|
|
DoQueueElemIndentOutdent;
|
|
FIgnoreHardLF := False;
|
|
Inc(PendingOutdent);
|
|
end;
|
|
etSoftHyphen :
|
|
DoQueueElemSoftHyphen;
|
|
end;
|
|
FCanBreak := FCanBreak and Assigned(FCurProps) and not FCurProps.NoBreak;
|
|
if (FxySize.cx <= WW) then begin
|
|
ContinueRow;
|
|
Inc(iElem);
|
|
end
|
|
else begin
|
|
EndRow;
|
|
Break;
|
|
end;
|
|
end;
|
|
|
|
if FSoftBreak and (FLastBreakpoint > 0) then begin
|
|
FLastWord := FLastBreakpoint;
|
|
iElem := FLastBreakpoint + 1;
|
|
end;
|
|
OutputQueueLine;
|
|
if (not FExpBreak) and (FTextWidth=0) and (FVRemainL=0) and (FVRemainR=0) then
|
|
break;
|
|
Inc(YYY, FMaxAscent + FMaxDescent + lfh);
|
|
|
|
// Calculate VRemainL and VRemainR
|
|
FVRemainL := CalcVRemain(FVRemainL, FLIdent);
|
|
FVRemainR := CalcVRemain(FVRemainR, FRIdent);
|
|
FMaxHeight := 0;
|
|
FMaxAscent := 0;
|
|
FMaxDescent := 0;
|
|
// prepare for next line
|
|
DoQueueClear;
|
|
end;
|
|
Inc(YYY, MaxI3(FMaxAscent div 2 + FMaxDescent, FVRemainL, FVRemainR));
|
|
FVRemainL := 0;
|
|
FVRemainR := 0;
|
|
FLIdent := 0;
|
|
FRIdent := 0;
|
|
FMaxDescent := 0;
|
|
|
|
DoQueueAlign(TargetRect, ExpLIndent);
|
|
Inc(YYY, MaxI3(FMaxAscent + FMaxDescent, FVRemainL, FVRemainR));
|
|
FPageRect.Bottom := YYY;
|
|
{clean up}
|
|
finally
|
|
FLeftQueue.Free;
|
|
FRightQueue.Free;
|
|
if FWordInfo <> nil then
|
|
FreeMem(FWordInfo);
|
|
{$IFDEF IP_LAZARUS_DBG}
|
|
DumpQueue(false); {debug}
|
|
{$endif}
|
|
end;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.UpdateCurrent(Start: Integer; const CurProps : TIpHtmlProps);
|
|
{- update other words that use same properties as the one at Start with their lengths.
|
|
Cuts down on the number of time the font properties need to be changed.}
|
|
var
|
|
i : Integer;
|
|
CurElem : PIpHtmlElement;
|
|
begin
|
|
for i := FElementQueue.Count - 1 downto Start + 1 do begin
|
|
CurElem := PIpHtmlElement(FElementQueue[i]);
|
|
if (CurElem.ElementType = etWord) and (CurElem.IsBlank = 0)
|
|
and ( (CurElem.Props = nil) or CurElem.Props.AIsEqualTo(CurProps) )
|
|
and (CurElem.SizeProp <> CurProps.PropA) then begin
|
|
CurElem.Size := FIpHtml.Target.TextExtent(NoBreakToSpace(CurElem.AnsiWord));
|
|
if CurElem.AnsiWord = NAnchorChar then
|
|
CurElem.Size.cx := 1;
|
|
CurElem.SizeProp := CurProps.PropA;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.CalcMinMaxQueueWidth(var aMin, aMax: Integer);
|
|
var
|
|
CurElem : PIpHtmlElement;
|
|
CurProps : TIpHtmlProps;
|
|
CurFontName : string;
|
|
CurFontSize : Integer;
|
|
CurFontStyle : TFontStyles;
|
|
i : Integer;
|
|
MinW, MaxW, IndentW, TextWidth : Integer;
|
|
LIndent, LIndentP : Integer;
|
|
LastW, LastElement : Integer;
|
|
NoBr : Boolean;
|
|
|
|
procedure ApplyMinMaxProps;
|
|
var
|
|
Changed : Boolean;
|
|
begin
|
|
if (CurProps = nil) or not CurElem.Props.AIsEqualTo(CurProps) then begin
|
|
Changed := False;
|
|
if (CurProps = nil) or (CurFontName <> CurElem.Props.FontName)
|
|
or (CurFontName = '') then begin
|
|
CurFontName := CurElem.Props.FontName;
|
|
FCanvas.Font.Name := CurFontName;
|
|
Changed := True;
|
|
end;
|
|
if (CurProps = nil) or (CurFontSize <> CurElem.Props.FontSize)
|
|
or (CurFontSize = 0) then begin
|
|
CurFontSize := CurElem.Props.FontSize;
|
|
FCanvas.Font.Size := CurFontSize;
|
|
Changed := True;
|
|
end;
|
|
if (CurProps = nil) or (CurFontStyle <> CurElem.Props.FontStyle) then begin
|
|
CurFontStyle := CurElem.Props.FontStyle;
|
|
FCanvas.Font.Style := CurFontStyle;
|
|
Changed := True;
|
|
end;
|
|
UpdSpaceHyphenSize(CurElem.Props);
|
|
if Changed and (CurElem.Props.PropA.tmHeight = 0) then
|
|
UpdPropMetrics(CurElem.Props);
|
|
end;
|
|
end;
|
|
|
|
begin
|
|
FCanvas := FIpHtml.Target;
|
|
aMin := 0;
|
|
aMax := 0;
|
|
if FElementQueue.Count = 0 then Exit;
|
|
LIndent := 0;
|
|
LIndentP := 0;
|
|
LastElement := TrimTrailingBlanks; // Trim trailing blanks
|
|
CurProps := nil;
|
|
CurFontName := '';
|
|
CurFontSize := 0;
|
|
CurFontStyle := [];
|
|
FCanvas.Font.Style := CurFontStyle;
|
|
FCanvas.Font.Quality := FOwner.Owner.FontQuality;
|
|
FSizeOfSpace := FCanvas.TextExtent(' ');
|
|
FSizeOfHyphen := FCanvas.TextExtent('-');
|
|
i := 0;
|
|
NoBr := False;
|
|
while i <= LastElement do begin
|
|
TextWidth := 0;
|
|
IndentW := 0;
|
|
LastW := 0;
|
|
while (i <= LastElement) do begin
|
|
MinW := 0;
|
|
CurElem := PIpHtmlElement(FElementQueue[i]);
|
|
if CurElem.Props <> nil then begin
|
|
ApplyMinMaxProps;
|
|
NoBr := CurElem.Props.NoBreak;
|
|
CurProps := CurElem.Props;
|
|
end;
|
|
case CurElem.ElementType of
|
|
etWord :
|
|
begin
|
|
{determine height and width of word}
|
|
if CurElem.IsBlank <> 0 then begin
|
|
MaxW := FSizeOfSpace.cx * CurElem.IsBlank;
|
|
MinW := MaxW;
|
|
if NoBr then
|
|
MinW := MinW + LastW;
|
|
end else begin
|
|
if (CurElem.SizeProp = CurProps.PropA) then
|
|
MaxW := CurElem.Size.cx
|
|
else begin
|
|
CurElem.Size := FCanvas.TextExtent(NoBreakToSpace(CurElem.AnsiWord));
|
|
if CurElem.AnsiWord = NAnchorChar then
|
|
CurElem.Size.cx := 1;
|
|
MaxW := CurElem.Size.cx;
|
|
CurElem.SizeProp := CurProps.PropA;
|
|
UpdateCurrent(i, CurProps);
|
|
end;
|
|
MinW := MaxW + LastW;
|
|
end;
|
|
LastW := MinW;
|
|
end;
|
|
etObject :
|
|
begin
|
|
TIpHtmlNodeAlignInline(CurElem.Owner).CalcMinMaxWidth(MinW, MaxW);
|
|
LastW := 0;
|
|
CurProps := nil;
|
|
end;
|
|
etSoftLF..etClearBoth :
|
|
begin
|
|
if TextWidth + IndentW > aMax then
|
|
aMax := TextWidth + IndentW;
|
|
TextWidth := 0;
|
|
MinW := 0;
|
|
MaxW := 0;
|
|
Inc(i);
|
|
break;
|
|
end;
|
|
etIndent :
|
|
begin
|
|
Inc(LIndent);
|
|
LIndentP := LIndent * StdIndent;
|
|
if LIndentP > IndentW then
|
|
IndentW := LIndentP;
|
|
MinW := 0;
|
|
MaxW := 0;
|
|
end;
|
|
etOutdent :
|
|
begin
|
|
if LIndent > 0 then begin
|
|
Dec(LIndent);
|
|
LIndentP := LIndent * StdIndent;
|
|
end;
|
|
MinW := 0;
|
|
MaxW := 0;
|
|
end;
|
|
etSoftHyphen :
|
|
begin
|
|
MaxW := FSizeOfHyphen.cx;
|
|
MinW := MaxW + LastW;
|
|
end;
|
|
end;
|
|
Inc(MinW, LIndentP);
|
|
if MinW > aMin then
|
|
aMin := MinW;
|
|
Inc(TextWidth, MaxW);
|
|
Inc(i);
|
|
end;
|
|
|
|
aMax := MaxI2(aMax, TextWidth + IndentW);
|
|
end;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.CalcMinMaxPropWidth(RenderProps: TIpHtmlProps;
|
|
var aMin, aMax: Integer);
|
|
begin
|
|
if RenderProps.IsEqualTo(Props) and (FBlockMin <> -1) and (FBlockMax <> -1) then begin
|
|
aMin := FBlockMin;
|
|
aMax := FBlockMax;
|
|
Exit;
|
|
end;
|
|
Props.Assign(RenderProps);
|
|
FOwner.LoadAndApplyCSSProps;
|
|
FOwner.SetProps(Props);
|
|
if FElementQueue.Count = 0 then
|
|
FOwner.Enqueue;
|
|
CalcMinMaxQueueWidth(aMin, aMax);
|
|
FBlockMin := aMin;
|
|
FBlockMax := aMax;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.DoRenderFont(var aCurWord: PIpHtmlElement);
|
|
begin
|
|
FCanvas.Font.BeginUpdate; // for speedup
|
|
if (FCurProps = nil) or not FCurProps.AIsEqualTo(aCurWord.Props) then
|
|
with aCurWord.Props do begin
|
|
FCanvas.Font.Name := FontName;
|
|
if ScaleFonts then
|
|
FCanvas.Font.Size := round(FontSize * Aspect)
|
|
else
|
|
FCanvas.Font.Size := FontSize;
|
|
FCanvas.Font.Style := FontStyle;
|
|
end;
|
|
if ScaleBitmaps and BWPRinter then
|
|
FIpHtml.Target.Font.Color := clBlack
|
|
else
|
|
if (FCurProps = nil) or not FCurProps.BIsEqualTo(aCurWord.Props) then
|
|
FCanvas.Font.Color := aCurWord.Props.FontColor;
|
|
FIpHtml.Target.Font.Quality := FIpHtml.FontQuality;
|
|
FIpHtml.Target.Font.EndUpdate;
|
|
FCurProps := aCurWord.Props;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.DoRenderElemWord(aCurWord: PIpHtmlElement;
|
|
aCurTabFocus: TIpHtmlNode);
|
|
var
|
|
P : TPoint;
|
|
R : TRect;
|
|
TextStyle: TTextStyle;
|
|
isRTL: Boolean;
|
|
rtlNode: TIpHtmlNodeCORE;
|
|
OldBrushcolor: TColor;
|
|
OldFontColor: TColor;
|
|
OldFontStyle: TFontStyles;
|
|
OldBrushStyle: TBrushStyle;
|
|
OldFontQuality: TFontQuality;
|
|
wordIsInTable: Boolean;
|
|
|
|
procedure saveCanvasProperties;
|
|
begin
|
|
OldBrushColor := FCanvas.Brush.Color;
|
|
OldBrushStyle := FCanvas.Brush.Style;
|
|
OldFontColor := FCanvas.Font.Color;
|
|
OldFontStyle := FCanvas.Font.Style;
|
|
OldFontQuality := FCanvas.Font.Quality;
|
|
end;
|
|
|
|
procedure restoreCanvasProperties;
|
|
begin
|
|
FCanvas.Font.Color := OldFontColor;
|
|
FCanvas.Brush.Color := OldBrushColor;
|
|
FCanvas.Brush.Style := OldBrushStyle;
|
|
FCanvas.Font.Style := OldFontStyle;
|
|
FCanvas.Font.Quality := OldFontQuality;
|
|
end;
|
|
|
|
begin
|
|
P := FIpHtml.PagePtToScreen(aCurWord.WordRect2.TopLeft);
|
|
|
|
// We don't want clipped lines at the top of the preview
|
|
if (FIpHtml.RenderDevice = rdPreview) and
|
|
(P.Y < 0) and (FIpHtml.PageViewRect.Top = FIpHtml.PageViewTop)
|
|
then
|
|
exit;
|
|
|
|
//if (LastOwner <> aCurWord.Owner) then LastPoint := P;
|
|
saveCanvasProperties;
|
|
TextStyle := FCanvas.TextStyle;
|
|
TextStyle.Clipping := false;
|
|
wordIsInTable := (aCurWord.Owner <> nil) and (aCurWord.Owner.ParentNode is TIpHtmlNodeTableHeaderOrCell);
|
|
//debugln(['TIpHtmlNodeBlock.RenderQueue ',aCurWord.AnsiWord]);
|
|
FIpHtml.PageRectToScreen(aCurWord.WordRect2, R);
|
|
if aCurWord.IsSelected or FIpHtml.AllSelected then begin
|
|
FCanvas.Font.Color := clHighlightText;
|
|
FCanvas.brush.Style := bsSolid;
|
|
FCanvas.brush.Color := clHighLight;
|
|
FCanvas.FillRect(R);
|
|
end
|
|
else if not wordIsInTable and (FCurProps.BgColor <> clNone) then
|
|
begin
|
|
FCanvas.brush.Style := bsSolid;
|
|
FCanvas.brush.Color := FCurProps.BgColor;
|
|
TextStyle.Opaque := True;
|
|
end
|
|
else
|
|
begin
|
|
TextStyle.Opaque := False;
|
|
FCanvas.Brush.Style := bsClear;
|
|
end;
|
|
|
|
isRTL := (FOwner.Dir = hdRTL);
|
|
if (aCurWord.Owner.ParentNode <> nil) and (aCurWord.Owner.ParentNode is TIpHTMLNodeCORE) then
|
|
begin
|
|
rtlNode := TIpHtmlNodeCORE(aCurWord.Owner.ParentNode);
|
|
if isRTL and (rtlNode.Dir = hdLTR) then
|
|
isRTL := false
|
|
else
|
|
if not isRTL and (rtlNode.Dir = hdRTL) then
|
|
isRTL := true;
|
|
end;
|
|
TextStyle.RightToLeft := isRTL;
|
|
|
|
if aCurWord.Owner.ParentNode = aCurTabFocus then
|
|
FCanvas.DrawFocusRect(R);
|
|
if FCanvas.Font.Color = clNone then
|
|
FCanvas.Font.Color := clBlack;
|
|
FCanvas.Font.Quality := FOwner.Owner.FontQuality;
|
|
if aCurWord.AnsiWord <> NAnchorChar then
|
|
FCanvas.TextRect(R, P.x, P.y, NoBreakToSpace(aCurWord.AnsiWord), TextStyle);
|
|
RestoreCanvasProperties;
|
|
|
|
FIpHtml.AddRect(aCurWord.WordRect2, aCurWord, FBlockOwner);
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.RenderQueue;
|
|
var
|
|
CurWord : PIpHtmlElement;
|
|
CurTabFocus: TIpHtmlNode;
|
|
i : Integer;
|
|
R : TRect;
|
|
P : TPoint;
|
|
L0 : Boolean;
|
|
isVisible: Boolean;
|
|
begin
|
|
L0 := FBlockOwner.Level0;
|
|
FCurProps := nil;
|
|
FCanvas := FIpHtml.Target;
|
|
// to draw focus rect
|
|
i := FIpHtml.TabList.Index;
|
|
if (FIpHtml.TabList.Count > 0) and (i <> -1) then
|
|
CurTabFocus := TIpHtmlNode(FIpHtml.TabList[i])
|
|
else
|
|
CurTabFocus := nil;
|
|
|
|
for i := 0 to pred(FElementQueue.Count) do begin
|
|
CurWord := PIpHtmlElement(FElementQueue[i]);
|
|
if (CurWord.Props <> nil) and (CurWord.Props <> FCurProps) then
|
|
DoRenderFont(CurWord);
|
|
|
|
{$IFDEF IP_LAZARUS_DBG}
|
|
//DumpTIpHtmlProps(FCurProps);
|
|
{$endif}
|
|
//debugln(['TIpHtmlNodeBlock.RenderQueue ',i,' ',IntersectRect(R, CurWord.WordRect2, Owner.PageViewRect),' CurWord.WordRect2=',dbgs(CurWord.WordRect2),' Owner.PageViewRect=',dbgs(Owner.PageViewRect)]);
|
|
|
|
isVisible := (CurWord.WordRect2.Top < FIpHtml.PageViewBottom);
|
|
// Make sure that the printer does not duplicate clipped lines.
|
|
if FIpHtml.RenderDevice = rdPrinter then
|
|
isVisible := isVisible and (CurWord.WordRect2.Top >= FIpHtml.PageViewTop);
|
|
isVisible := (isVisible or (CurWord.ElementType = etObject))
|
|
and IntersectRect(R, CurWord.WordRect2, FIpHtml.PageViewRect);
|
|
|
|
if isVisible then begin
|
|
case CurWord.ElementType of
|
|
etWord :
|
|
DoRenderElemWord(CurWord, CurTabFocus);
|
|
etObject :
|
|
begin
|
|
TIpHtmlNodeAlignInline(CurWord.Owner).Draw(FBlockOwner);
|
|
//Owner.AddRect(CurWord.WordRect2, CurWord, Self);
|
|
FCurProps := nil;
|
|
end;
|
|
etSoftHyphen :
|
|
begin
|
|
P := FIpHtml.PagePtToScreen(CurWord.WordRect2.TopLeft);
|
|
FCanvas.Brush.Style := bsClear;
|
|
FCanvas.TextOut(P.x, P.y, '-');
|
|
FIpHtml.AddRect(CurWord.WordRect2, CurWord, FBlockOwner);
|
|
end;
|
|
end
|
|
end
|
|
else
|
|
case CurWord.ElementType of
|
|
etWord,
|
|
etObject,
|
|
etSoftHyphen :
|
|
if (CurWord.WordRect2.Bottom <> 0) and
|
|
(CurWord.WordRect2.Top > FIpHtml.PageViewRect.Bottom) and L0
|
|
then
|
|
break;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TIpNodeBlockLayouter.Render(RenderProps: TIpHtmlProps);
|
|
begin
|
|
if FOwner.Owner.NeedResize and (not RenderProps.IsEqualTo(Props)) then
|
|
begin
|
|
Props.Assign(RenderProps);
|
|
FOwner.LoadAndApplyCSSProps;
|
|
FOwner.SetProps(Props);
|
|
end;
|
|
if FElementQueue.Count = 0 then
|
|
FOwner.Enqueue;
|
|
RenderQueue;
|
|
end;
|
|
|
|
|
|
{ TIpNodeTableElemLayouter }
|
|
|
|
constructor TIpNodeTableElemLayouter.Create(AOwner: TIpHtmlNodeCore);
|
|
begin
|
|
inherited Create(AOwner);
|
|
FTableElemOwner := TIpHtmlNodeTableHeaderOrCell(FOwner);
|
|
end;
|
|
|
|
destructor TIpNodeTableElemLayouter.Destroy;
|
|
begin
|
|
inherited Destroy;
|
|
end;
|
|
|
|
procedure GetAlignment(ANode: TIpHtmlNode; AProps: TIpHtmlProps; var Done: Boolean);
|
|
begin
|
|
if (ANode is TIpHtmlNodeSpan) then
|
|
begin
|
|
if (ANode as TIpHtmlNodeSpan).Align <> haDefault then
|
|
begin
|
|
AProps.Alignment := (ANode as TIpHtmlNodeSpan).Align;
|
|
Done := true;
|
|
end;
|
|
end else
|
|
if (ANode is TIpHtmlNodeTableHeaderOrCell) then
|
|
begin
|
|
if (ANode as TIpHtmlNodeTableHeaderOrCell).Align <> haDefault then
|
|
begin
|
|
AProps.Alignment := (ANode as TIpHtmlNodeTableHeaderOrCell).Align;
|
|
Done := true;
|
|
end;
|
|
end;
|
|
end;
|
|
|
|
procedure TIpNodeTableElemLayouter.Layout(RenderProps: TIpHtmlProps; TargetRect: TRect);
|
|
begin
|
|
Props.Assign(RenderProps);
|
|
|
|
IterateParents(@GetAlignment);
|
|
if (Props.Alignment = haDefault) then
|
|
begin
|
|
if (FOwner is TIpHtmlNodeTD) then
|
|
Props.Alignment := haLeft
|
|
else
|
|
if (FOwner is TIpHtmlNodeTH) then
|
|
Props.Alignment := haCenter;
|
|
end;
|
|
|
|
{
|
|
if FTableElemOwner.Align <> haDefault then
|
|
Props.Alignment := FTableElemOwner.Align
|
|
else
|
|
if FOwner is TIpHtmlNodeTH then
|
|
Props.Alignment := haCenter;
|
|
}
|
|
|
|
if FOwner is TIpHtmlNodeTH then
|
|
Props.FontStyle := Props.FontStyle + [fsBold];
|
|
|
|
if FTableElemOwner.NoWrap then
|
|
Props.NoBreak := True;
|
|
|
|
case FTableElemOwner.VAlign of
|
|
hva3Default :;
|
|
else
|
|
Props.VAlignment := FTableElemOwner.VAlign;
|
|
end;
|
|
|
|
if FTableElemOwner.BgColor <> clNone then
|
|
Props.BgColor := FTableElemOwner.BgColor;
|
|
|
|
inherited Layout(Props, TargetRect);
|
|
end;
|
|
|
|
procedure TIpNodeTableElemLayouter.CalcMinMaxPropWidth(RenderProps: TIpHtmlProps;
|
|
var aMin, aMax: Integer);
|
|
var
|
|
TmpBGColor, TmpFontColor: TColor;
|
|
begin
|
|
TmpBGColor := Props.BgColor;
|
|
TmpFontColor := Props.FontColor;
|
|
Props.Assign(RenderProps);
|
|
Props.BgColor := TmpBGColor;
|
|
Props.FontColor := TmpFontColor;
|
|
Props.Alignment := FTableElemOwner.Align;
|
|
if FOwner is TIpHtmlNodeTH then
|
|
Props.FontStyle := Props.FontStyle + [fsBold];
|
|
Props.VAlignment := FTableElemOwner.VAlign;
|
|
if FTableElemOwner.NoWrap then
|
|
Props.NoBreak := True;
|
|
inherited CalcMinMaxPropWidth(Props, aMin, aMax);
|
|
if FTableElemOwner.NoWrap then
|
|
aMin := aMax;
|
|
end;
|
|
|
|
procedure TIpNodeTableElemLayouter.Render(RenderProps: TIpHtmlProps);
|
|
var
|
|
R : TRect;
|
|
begin
|
|
if FOwner.Owner.NeedResize then
|
|
begin
|
|
Props.Assign(RenderProps);
|
|
Props.DelayCache:=True;
|
|
FOwner.LoadAndApplyCSSProps;
|
|
//DebugLn('td :', IntToStr(Integer(Props.Alignment)));
|
|
if FTableElemOwner.BgColor <> clNone then
|
|
Props.BgColor := FTableElemOwner.BgColor;
|
|
if FTableElemOwner.Align <> haDefault then
|
|
Props.Alignment := FTableElemOwner.Align
|
|
else if Props.Alignment = haDefault then
|
|
begin
|
|
if FOwner is TIpHtmlNodeTH then
|
|
Props.Alignment := haCenter
|
|
else
|
|
Props.Alignment := haLeft;
|
|
end;
|
|
if FOwner is TIpHtmlNodeTH then
|
|
Props.FontStyle := Props.FontStyle + [fsBold];
|
|
Props.VAlignment := FTableElemOwner.VAlign;
|
|
if FTableElemOwner.NoWrap then
|
|
Props.NoBreak := True;
|
|
end;
|
|
{$IFDEF IP_LAZARUS_DBG}
|
|
DebugBox(Owner.Target, PadRect, clYellow, True);
|
|
{$ENDIF}
|
|
if FOwner.PageRectToScreen(FTableElemOwner.PadRect, R) then
|
|
begin
|
|
if (Props.BgColor <> clNone) then
|
|
begin
|
|
FIpHtml.Target.Brush.Color := Props.BGColor;
|
|
FIpHtml.Target.FillRect(R);
|
|
end else
|
|
FIpHtml.Target.Brush.Style := bsClear;
|
|
end;
|
|
if FOwner.Owner.NeedResize then
|
|
Props.DelayCache:=False;
|
|
inherited Render(Props);
|
|
end;
|
|
|
|
|
|
initialization
|
|
BlockLayouterClass := TIpNodeBlockLayouter;
|
|
TableElemLayouterClass := TIpNodeTableElemLayouter;
|
|
|
|
end.
|
|
|