// Global defines {$I IPDEFINE.INC} unit ipHtmlBlockLayout; interface uses types, Classes, SysUtils, LCLPRoc, LCLIntf, Graphics, IpUtils, IpHtml, iphtmlprop; 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 := {$ifdef IP_LAZARUS}TFPList{$else}TList{$endif}.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; 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'); {$IFDEF IP_LAZARUS} FCanvas.GetTextMetrics(TextMetrics); aProps.PropA.tmAscent := TextMetrics.Ascender; aProps.PropA.tmDescent := TextMetrics.Descender; aProps.PropA.tmHeight := TextMetrics.Height; {$ELSE} GetTextMetrics(FCanvas.Handle, TextMetrics); aProps.PropA.tmAscent := TextMetrics.tmAscent; aProps.PropA.tmDescent := TextMetrics.tmDescent; aProps.PropA.tmHeight := TextMetrics.tmHeight; {$ENDIF} 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 Pred(FElementQueue.Count) 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; {$IFDEF IP_LAZARUS} var TextMetrics : TLCLTextMetric; begin FCanvas.GetTextMetrics(TextMetrics); FBlockAscent := TextMetrics.Ascender; FBlockDescent := TextMetrics.Descender; FBlockHeight := TextMetrics.Height; end; {$ELSE} var TextMetrics : TTextMetric; begin GetTextMetrics(aCanvas.Handle, TextMetrics); BlockAscent := TextMetrics.tmAscent; BlockDescent := TextMetrics.tmDescent; BlockHeight := TextMetrics.tmHeight; end; {$ENDIF} 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; 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); 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 "

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

and

nodes in if Assigned(aCurElem.Owner) then begin lAlign := FAl; node := aCurElem.Owner.ParentNode; while Assigned(node) do begin if (node is TIpHtmlNodeCore) then lAlign := TIpHtmlNodeCore(node).Align { if (node is TIpHtmlNodeP) then lAlign := TIpHtmlNodeP(node).Align else if (node is TIpHtmlNodeDIV) then lAlign := TIpHtmlNodeDIV(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; 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; {$IFNDEF IP_LAZARUS} NewWordInfo: PWordList; {$ENDIF} begin if (FWordInfo = nil) or (NewLength > FWordInfoSize) then begin NewWordInfoSize := ((NewLength div 256) + 1) * 256; {$IFDEF IP_LAZARUS code below does not check if FWordInfo<>nil} ReallocMem(FWordInfo,NewWordInfoSize * sizeof(TWordInfo)); {$ELSE} NewWordInfo := AllocMem(NewWordInfoSize * sizeof(TWordInfo)); move(WordInfo^, NewWordInfo^, WordInfoSize); Freemem(WordInfo); WordInfo := NewWordInfo; {$ENDIF} 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 : Integer; FirstElem, LastElem : Integer; PendingIndent, PendingOutdent : Integer; Prefor : Boolean; CurElem : 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; while iElem < FElementQueue.Count do begin FCanBreak := False; CurElem := PIpHtmlElement(FElementQueue[iElem]); if CurElem.Props <> nil then ApplyQueueProps(CurElem, Prefor); 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; 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 {$IFDEF IP_LAZARUS} FCanvas.Font.BeginUpdate; // for speedup {$ENDIF} 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; {$IFDEF IP_LAZARUS} FIpHtml.Target.Font.EndUpdate; {$ENDIF} FCurProps := aCurWord.Props; end; procedure TIpNodeBlockLayouter.DoRenderElemWord(aCurWord: PIpHtmlElement; aCurTabFocus: TIpHtmlNode); var P : TPoint; R : TRect; {$IFDEF IP_LAZARUS} OldBrushcolor: TColor; OldFontColor: TColor; OldFontStyle: TFontStyles; OldBrushStyle: TBrushStyle; procedure saveCanvasProperties; begin OldBrushColor := FCanvas.Brush.Color; OldBrushStyle := FCanvas.Brush.Style; OldFontColor := FCanvas.Font.Color; OldFontStyle := FCanvas.Font.Style; end; procedure restoreCanvasProperties; begin FCanvas.Font.Color := OldFontColor; FCanvas.Brush.Color := OldBrushColor; FCanvas.Brush.Style := OldBrushStyle; FCanvas.Font.Style := OldFontStyle; end; {$ENDIF} begin P := FIpHtml.PagePtToScreen(aCurWord.WordRect2.TopLeft); // We dont'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; {$IFDEF IP_LAZARUS} //if (LastOwner <> aCurWord.Owner) then LastPoint := P; saveCanvasProperties; if aCurWord.IsSelected or FIpHtml.AllSelected then begin FCanvas.Font.color := clHighlightText; FCanvas.brush.Style := bsSolid; FCanvas.brush.color := clHighLight; FIpHtml.PageRectToScreen(aCurWord.WordRect2, R); FCanvas.FillRect(R); end else if FCurProps.BgColor > 0 then begin FCanvas.brush.Style := bsSolid; FCanvas.brush.color := FCurProps.BgColor; end else {$ENDIF} FCanvas.Brush.Style := bsClear; //debugln(['TIpHtmlNodeBlock.RenderQueue ',aCurWord.AnsiWord]); FIpHtml.PageRectToScreen(aCurWord.WordRect2, R); {$IFDEF IP_LAZARUS} if aCurWord.Owner.ParentNode = aCurTabFocus then FCanvas.DrawFocusRect(R); if FCanvas.Font.color = -1 then FCanvas.Font.color := clBlack; {$ENDIF} if aCurWord.AnsiWord <> NAnchorChar then FCanvas.TextRect(R, P.x, P.y, NoBreakToSpace(aCurWord.AnsiWord)); {$IFDEF IP_LAZARUS} RestoreCanvasProperties; {$ENDIF} 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; {$IFDEF IP_LAZARUS} // 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; {$ENDIF} 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 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 <> -1 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 Props.Assign(RenderProps); Props.DelayCache:=True; {$IFDEF IP_LAZARUS} FOwner.LoadAndApplyCSSProps; {$ENDIF} //DebugLn('td :', IntToStr(Integer(Props.Alignment))); if FTableElemOwner.BgColor <> -1 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; {$IFDEF IP_LAZARUS_DBG} DebugBox(Owner.Target, PadRect, clYellow, True); {$ENDIF} if FOwner.PageRectToScreen(FTableElemOwner.PadRect, R) then begin if (Props.BgColor <> -1) then begin FIpHtml.Target.Brush.Color := Props.BGColor; FIpHtml.Target.FillRect(R); end else FIpHtml.Target.Brush.Style := bsClear; end; Props.DelayCache:=False; inherited Render(Props); end; initialization BlockLayouterClass := TIpNodeBlockLayouter; TableElemLayouterClass := TIpNodeTableElemLayouter; end.