From ddbbbe3105994444690089dedd44b01e112aca08 Mon Sep 17 00:00:00 2001 From: wp_xyz Date: Fri, 24 Mar 2023 23:29:55 +0100 Subject: [PATCH] Turbopower_ipro: Improved rendering of line spacings and preformatted html elements by TIpHtmlPanel. Patch by Don Siders. Issue #40179. --- components/turbopower_ipro/iphtml.pas | 6 +- components/turbopower_ipro/iphtmlnodes.pas | 320 +++++++++++++----- .../turbopower_ipro/test_cases/iprotest.lpi | 54 +-- .../turbopower_ipro/test_cases/iprotest.lpr | 2 +- .../test_cases/iprotest_unit.lfm | 6 +- 5 files changed, 263 insertions(+), 125 deletions(-) diff --git a/components/turbopower_ipro/iphtml.pas b/components/turbopower_ipro/iphtml.pas index 43ee6ab23e..14e36bc1ae 100644 --- a/components/turbopower_ipro/iphtml.pas +++ b/components/turbopower_ipro/iphtml.pas @@ -4476,14 +4476,16 @@ var h: Integer; begin hf := Props.FontSize; + // mimic layout/line spacing used in Chrome and Firefox if FChildren.Count > 0 then begin - h := GetMargin(Props.ElemMarginTop, hf div 2); + h := GetMargin(Props.ElemMarginTop, 3 * (Owner.DefaultFontSize div 2)); elem := Owner.BuildLinefeedEntry(etSoftLF, h); EnqueueElement(elem); end; inherited Enqueue; + // mimic layout/line spacing used in Chrome and Firefox if FChildren.Count > 0 then begin - h := GetMargin(Props.ElemMarginBottom, hf div 4); + h := GetMargin(Props.ElemMarginBottom, hf div 2); elem := Owner.BuildLinefeedEntry(etSoftLF, h); EnqueueElement(elem); end; diff --git a/components/turbopower_ipro/iphtmlnodes.pas b/components/turbopower_ipro/iphtmlnodes.pas index 8aa5609a44..4701b847ad 100644 --- a/components/turbopower_ipro/iphtmlnodes.pas +++ b/components/turbopower_ipro/iphtmlnodes.pas @@ -1159,7 +1159,7 @@ uses IpAnImgL, {$ENDIF} {$ENDIF} - LazStringUtils; + StrUtils, LazStringUtils; type @@ -2020,10 +2020,22 @@ end; { TIpHtmlNodeBLOCKQUOTE } procedure TIpHtmlNodeBLOCKQUOTE.Enqueue; +var + hf: Integer; + elem: PIpHtmlElement; begin + // display: block; + hf := Props.FontSize; + elem := TIpHtmlOpener(Owner).BuildLinefeedEntry(etHardLF, hf); + EnqueueElement(elem); + EnqueueElement(TIpHtmlOpener(Owner).FLIndent); inherited; EnqueueElement(TIpHtmlOpener(Owner).FLOutdent); + + // close the block + elem := TIpHtmlOpener(Owner).BuildLinefeedEntry(etHardLF, hf); + EnqueueElement(elem); end; @@ -2097,13 +2109,11 @@ end; procedure TIpHtmlNodeDD.Enqueue; var - h, hf: Integer; elem: PIpHtmlElement; begin - hf := Props.FontSize; + // avoid top and bottom margins... they're always inherited from DL if ChildCount > 0 then begin - h := GetMargin(Props.ElemMarginTop, hf div 2); - elem := TIpHtmlOpener(Owner).BuildLineFeedEntry(etSoftLF, h); + elem := TIpHtmlOpener(Owner).BuildLineFeedEntry(etSoftLF, 0); EnqueueElement(elem); end; @@ -2112,8 +2122,7 @@ begin EnqueueElement(TIpHtmlOpener(Owner).FLOutdent); if ChildCount > 0 then begin - h := GetMargin(Props.ElemMarginTop, hf); - elem := TIpHtmlOpener(Owner).BuildLineFeedEntry(etSoftLF, h); + elem := TIpHtmlOpener(Owner).BuildLineFeedEntry(etSoftLF, 0); EnqueueElement(elem); end; end; @@ -2212,11 +2221,26 @@ begin end; procedure TIpHtmlNodeDL.Enqueue; +var + hf, h: Integer; + elem: PIpHtmlElement; begin - EnqueueElement(TIpHtmlOpener(Owner).HardLF); - EnqueueElement(TIpHtmlOpener(Owner).FLIndent); + // display block + hf := Props.FontSize; + h := GetMargin(Props.ElemMarginTop, hf); + elem := TIpHtmlOpener(Owner).BuildLinefeedEntry(etHardLF, h); + EnqueueElement(elem); + + // indent not needed here + // EnqueueElement(TIpHtmlOpener(Owner).FLIndent); inherited; - EnqueueElement(TIpHtmlOpener(Owner).FLOutdent); + // outdent not needed here + // EnqueueElement(TIpHtmlOpener(Owner).FLOutdent); + + // close the block + h := GetMargin(Props.ElemMarginBottom, hf); + elem := TIpHtmlOpener(Owner).BuildLinefeedEntry(etHardLF, h); + EnqueueElement(elem); end; @@ -2229,9 +2253,32 @@ begin end; procedure TIpHtmlNodeDT.Enqueue; +var + hf, h: integer; + elem: PIPHtmlElement; begin + // display inline block + // avoid top margin... it's always inherited from DL + // use fractional font height between DD and DT + if ChildCount > 0 then + begin + hf := Props.FontSize; + h := 3 * (hf div 8); + elem := TIPHtmlOpener(Owner).BuildLinefeedEntry(etSoftLF, h); + EnqueueElement(elem); + end; + inherited; - EnqueueElement(TIpHtmlOpener(Owner).HardLF); + + // close the inline block + // use fractional font height between DT and DD + if ChildCount > 0 then + begin + hf := Props.FontSize; + h := hf div 8; + elem := TIPHtmlOpener(Owner).BuildLinefeedEntry(etSoftLF, h); + EnqueueElement(elem); + end; end; @@ -2429,27 +2476,70 @@ end; procedure TIpHtmlNodeList.Enqueue; var - i: Integer; + i, hf: Integer; lOwner: TIpHtmlOpener; lParentNode: TIpHtmlNodeOpener; + elem: PIpHtmlElement; begin lOwner := TIpHtmlOpener(Owner); lParentNode := TIpHtmlNodeOpener(FParentNode); - + hf := Props.FontSize; + if ChildCount > 0 then begin - EnqueueElement(lOwner.SoftLF); + // nested list has different first line margin + if (FParentNode is TIpHtmlNodeOL) or + (FParentNode is TIpHtmlNodeList) or + (FParentNode is TIpHtmlNodeLI) then + begin + elem := lOwner.BuildLineFeedEntry(etHardLF, 0); + lParentNode.EnqueueElement(elem); + elem := lOwner.BuildLineFeedEntry(etSoftLF, 3 * (hf div 16)); + lParentNode.EnqueueElement(elem); + lParentNode.EnqueueElement(lOwner.FLIndent); + end + // start block container and inline block for list items + else + begin + elem := lOwner.BuildLineFeedEntry(etHardLF, hf); + EnqueueElement(elem); + elem := lOwner.BuildLineFeedEntry(etSoftLF, 3 * (hf div 16)); + EnqueueElement(elem); + EnqueueElement(lOwner.FLIndent); + end; end; - - {render list} - lParentNode.EnqueueElement(lOwner.FLIndent); + + // render list for i := 0 to Pred(ChildCount) do - if ChildNode[i] is TIpHtmlNodeLI then begin + begin + // handle list items + if (ChildNode[i] is TIpHtmlNodeLI) then + begin TIpHtmlNodeLI(ChildNode[i]).Enqueue; - lParentNode.EnqueueElement(lOwner.SoftLF); - end else + elem := lOwner.BuildLineFeedEntry(etSoftLF, 3 * (hf div 16)); + EnqueueElement(elem); + end + // handle a nested list + else TIpHtmlNodeOpener(ChildNode[i]).Enqueue; - lParentNode.EnqueueElement(lOwner.FLOutdent); - EnqueueElement(lOwner.SoftLF); + end; + + if ChildCount > 0 then begin + // close inline block + lParentNode.EnqueueElement(lOwner.FLOutdent); + elem := lOwner.BuildLineFeedEntry(etSoftLF, 0); + EnqueueElement(elem); + + // nested list has different bottom margin + if (FParentNode is TIpHtmlNodeOL) or + (FParentNode is TIpHtmlNodeList) or + (FParentNode is TIpHtmlNodeLI) then + elem := lOwner.BuildLineFeedEntry(etSoftLF, hf div 8) + // close the block + else + elem := lOwner.BuildLineFeedEntry(etHardLF, 3 * (hf div 8)); + EnqueueElement(elem); + end; + end; procedure TIpHtmlNodeList.LoadAndApplyCSSProps; @@ -2518,27 +2608,26 @@ end; procedure TIpHtmlNodePRE.Enqueue; var - h: Integer; + hf, h: Integer; elem: PIpHtmlElement; begin - //hf := Props.FontSize; - if ChildCount > 0 then begin - h := GetMargin(Props.ElemMarginTop, 0); - elem := TIpHtmlOpener(Owner).BuildLineFeedEntry(etSoftLF, h); - EnqueueElement(elem); - end; - //EnqueueElement(Owner.HardLF); - inherited Enqueue; - if ChildCount > 0 then begin - h := GetMargin(Props.ElemMarginTop, 0); - elem := TIpHtmlOpener(Owner).BuildLineFeedEntry(etSoftLF, h); + hf := Props.FontSize; + + // start block with top margin + if (ChildCount > 0) then begin + h := GetMargin(Props.ElemMarginTop, hf); + elem := TIpHtmlOpener(Owner).BuildLineFeedEntry(etHardLF, h); EnqueueElement(elem); end; - { - if FChildren.Count > 0 then - EnqueueElement(Owner.HardLF); - } + inherited Enqueue; + + // close block with optional bottom margin + if (ChildCount > 0) then begin + h := GetMargin(Props.ElemMarginBottom, 0); + elem := TIpHtmlOpener(Owner).BuildLineFeedEntry(etHardLF, h); + EnqueueElement(elem); + end; end; @@ -3134,7 +3223,7 @@ var begin if FParentNode is TIpHtmlNodeOL then begin S := TIpHtmlNodeOL(FParentNode).GetNumString; - SetRawWordValue(WordEntry, S + '.'); + SetRawWordValue(WordEntry, S); EnqueueElement(WordEntry); end else EnqueueElement(Element); @@ -3191,62 +3280,97 @@ end; procedure TIpHtmlNodeOL.Enqueue; var i: Integer; + iVal: Integer; lParentNode: TIpHtmlNodeOpener; lOwner: TIpHtmlOpener; + elem: PIpHtmlElement; + hf: Integer; begin + // display block lOwner := TIpHtmlOpener(Owner); lParentNode := TIpHtmlNodeOpener(FParentNode); + hf := Props.FontSize; - {render list} if ChildCount > 0 then begin - EnqueueElement(lOwner.SoftLF); + // nested list has different top margin + if (FParentNode is TIpHtmlNodeOL) or + (FParentNode is TIpHtmlNodeList) or + (FParentNode is TIpHtmlNodeLI) then + begin + elem := lOwner.BuildLineFeedEntry(etHardLF, 0); + lParentNode.EnqueueElement(elem); + elem := lOwner.BuildLineFeedEntry(etSoftLF, 3 * (hf div 16)); + lParentNode.EnqueueElement(elem); + lParentNode.EnqueueElement(lOwner.FLIndent); + end + // start block container and inline block for list items + else + begin + elem := lOwner.BuildLineFeedEntry(etHardLF, hf); + EnqueueElement(elem); + elem := lOwner.BuildLineFeedEntry(etSoftLF, 3 * (hf div 16)); + EnqueueElement(elem); + EnqueueElement(lOwner.FLIndent); + end; end; - lParentNode.EnqueueElement(lOwner.FLIndent); + + // render list + iVal := -1; for i := 0 to Pred(ChildCount) do - if ChildNode[i] is TIpHtmlNodeLI then begin - Counter := Start + i; + begin + // handle list items + if (ChildNode[i] is TIpHtmlNodeLI) then + begin + Inc(iVal); + Counter := Start + iVal; TIpHtmlNodeLI(ChildNode[i]).Enqueue; - lParentNode.EnqueueElement(lOwner.SoftLF); - end else + elem := lOwner.BuildLineFeedEntry(etSoftLF, 3 * (hf div 16)); + EnqueueElement(elem); + end + // handle a nested list + else TIpHtmlNodeOpener(ChildNode[i]).Enqueue; - lParentNode.EnqueueElement(lOwner.FLOutdent); - lParentNode.EnqueueElement(lOwner.SoftLF); + end; + + if ChildCount > 0 then begin + // close inline block + lParentNode.EnqueueElement(lOwner.FLOutdent); + elem := lOwner.BuildLineFeedEntry(etSoftLF, 0); + EnqueueElement(elem); + + // nested list has different bottom margin + if (FParentNode is TIpHtmlNodeOL) or + (FParentNode is TIpHtmlNodeList) or + (FParentNode is TIpHtmlNodeLI) then + elem := lOwner.BuildLineFeedEntry(etSoftLF, hf div 8) + // close the block + else + elem := lOwner.BuildLineFeedEntry(etHardLF, 3 * (hf div 8)); + EnqueueElement(elem); + end; end; function TIpHtmlNodeOL.GetNumString: string; - - function IntToRomanStr(i : Integer): string; - const - RC : array[0..6] of AnsiChar = ('M', 'D', 'C', 'L', 'X', 'V', 'I'); - RV : array[0..6] of Integer = (1000, 500, 100, 50, 10, 5, 1); - var - n : Integer; - begin - Result := ''; - n := 0; - repeat - while i >= RV[n] do begin - Result := Result + RC[n]; - Dec(i, RV[n]); - end; - Inc(n); - until i = 0; - end; - begin Result := ''; // stop warning case Style of olArabic : - str(Counter, Result); + Str(Counter, Result); olLowerAlpha : Result := chr(ord('a') + Counter - 1); olUpperAlpha : Result := chr(ord('A') + Counter - 1); olLowerRoman : - Result := LowerCase(IntToRomanStr(Counter)); + // rtl version... its not buggy + Result := Lowercase(StrUtils.IntToRoman(Counter)); olUpperRoman : - Result := IntToRomanStr(Counter); + // rtl version... its not buggy + Result := StrUtils.IntToRoman(Counter); end; + Result := Result + '. '; + // right-align roman counter values + if Style in [olLowerRoman, olUpperRoman] then + Result := PadLeft(Result, 7); end; procedure TIpHtmlNodeOL.LoadAndApplyCSSProps; @@ -3540,34 +3664,46 @@ end; procedure TIpHtmlNodeTABLE.Enqueue; var lOwner: TIpHtmlOpener; + h: Integer; + elem: PIpHtmlElement; begin + // display block lOwner := TIpHtmlOpener(Owner); -//The commented code below prevents a blank line before the table -{ - case Align of - hiaTop, - hiaMiddle, - hiaBottom, - hiaCenter : - EnqueueElement(Owner.SoftLF); - end; -} - EnqueueElement(lOwner.SoftLF); + //The commented code below prevents a blank line before the table + { + case Align of + hiaTop, + hiaMiddle, + hiaBottom, + hiaCenter : + EnqueueElement(Owner.SoftLF); + end; + } + // vertical margin: specified in CSS or none + h := GetMargin(Props.ElemMarginTop, 0); + elem := TIpHtmlOpener(Owner).BuildLinefeedEntry(etSoftLF, h); + EnqueueElement(elem); + + // insert element content EnqueueElement(Element); - EnqueueElement(lOwner.SoftLF); - EnqueueElement(lOwner.HardLF); // LFs needed otherwise next element is too close -{ - case Align of - hiaTop, - hiaMiddle, - hiaBottom, - hiaCenter : - EnqueueElement(Owner.SoftLF); - end; -} + // close block + // vertical margin: specified in CSS or none + h := GetMargin(Props.ElemMarginBottom, 0); + elem := TIpHtmlOpener(Owner).BuildLinefeedEntry(etHardLF, h); + EnqueueElement(elem); + + { + case Align of + hiaTop, + hiaMiddle, + hiaBottom, + hiaCenter : + EnqueueElement(Owner.SoftLF); + end; + } end; procedure TIpHtmlNodeTABLE.SetBorder(const Value: Integer); diff --git a/components/turbopower_ipro/test_cases/iprotest.lpi b/components/turbopower_ipro/test_cases/iprotest.lpi index cf139566d6..b01fd1dbc7 100644 --- a/components/turbopower_ipro/test_cases/iprotest.lpi +++ b/components/turbopower_ipro/test_cases/iprotest.lpi @@ -4,6 +4,9 @@ + + + <Scaled Value="True"/> @@ -12,10 +15,9 @@ <XPManifest> <DpiAware Value="True"/> </XPManifest> - <Icon Value="0"/> </General> - <BuildModes> - <Item Name="Default" Default="True"/> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> </BuildModes> <PublishOptions> <Version Value="2"/> @@ -24,36 +26,36 @@ <RunParams> <FormatVersion Value="2"/> </RunParams> - <RequiredPackages> - <Item> + <RequiredPackages Count="4"> + <Item1> <PackageName Value="TurboPowerIPro"/> - </Item> - <Item> + </Item1> + <Item2> <PackageName Value="SynEditDsgn"/> - </Item> - <Item> + </Item2> + <Item3> <PackageName Value="SynEdit"/> - </Item> - <Item> + </Item3> + <Item4> <PackageName Value="LCL"/> - </Item> + </Item4> </RequiredPackages> - <Units> - <Unit> + <Units Count="3"> + <Unit0> <Filename Value="iprotest.lpr"/> <IsPartOfProject Value="True"/> - </Unit> - <Unit> + </Unit0> + <Unit1> <Filename Value="iprotest_unit.pas"/> <IsPartOfProject Value="True"/> <ComponentName Value="TestForm"/> <HasResources Value="True"/> <ResourceBaseClass Value="Form"/> - </Unit> - <Unit> + </Unit1> + <Unit2> <Filename Value="ipro_tests.pas"/> <IsPartOfProject Value="True"/> - </Unit> + </Unit2> </Units> </ProjectOptions> <CompilerOptions> @@ -78,16 +80,16 @@ </Linking> </CompilerOptions> <Debugging> - <Exceptions> - <Item> + <Exceptions Count="3"> + <Item1> <Name Value="EAbort"/> - </Item> - <Item> + </Item1> + <Item2> <Name Value="ECodetoolError"/> - </Item> - <Item> + </Item2> + <Item3> <Name Value="EFOpenError"/> - </Item> + </Item3> </Exceptions> </Debugging> </CONFIG> diff --git a/components/turbopower_ipro/test_cases/iprotest.lpr b/components/turbopower_ipro/test_cases/iprotest.lpr index e2864b80c3..35a1eea1b7 100644 --- a/components/turbopower_ipro/test_cases/iprotest.lpr +++ b/components/turbopower_ipro/test_cases/iprotest.lpr @@ -16,7 +16,7 @@ uses begin RequireDerivedFormResource:=True; - Application.Scaled:=True; + Application.Scaled := True; Application.Initialize; Application.CreateForm(TTestForm, TestForm); Application.Run; diff --git a/components/turbopower_ipro/test_cases/iprotest_unit.lfm b/components/turbopower_ipro/test_cases/iprotest_unit.lfm index f1defa50b5..e2dcd2fa4d 100644 --- a/components/turbopower_ipro/test_cases/iprotest_unit.lfm +++ b/components/turbopower_ipro/test_cases/iprotest_unit.lfm @@ -8,7 +8,6 @@ object TestForm: TTestForm ClientHeight = 620 ClientWidth = 866 OnCreate = FormCreate - LCLVersion = '2.3.0.0' object Panel1: TPanel Left = 6 Height = 576 @@ -28,7 +27,6 @@ object TestForm: TTestForm Top = 0 Width = 80 Caption = 'html file name:' - Color = clDefault ParentColor = False end object FileNameEdit1: TFileNameEdit @@ -96,8 +94,8 @@ object TestForm: TTestForm Align = alClient BorderSpacing.Top = 3 DataProvider = IpHtmlDataProvider - FixedTypeface = 'Courier New' - DefaultTypeFace = 'default' + FixedTypeface = 'monospace' + DefaultTypeFace = 'sans-serif' DefaultFontSize = 12 FlagErrors = False PrintSettings.MarginLeft = 0.5