From 9f257c6fef3ea445459e15f135f966470a9143a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mart=C3=ADn=20Lucas=20Golini?= Date: Wed, 13 May 2026 00:49:36 -0300 Subject: [PATCH] =?UTF-8?q?CSS:=20Inline-block=20layout,=20line-height=20i?= =?UTF-8?q?nheritance,=20and=20font=20shorthand=20fixes=20Core=20problem?= =?UTF-8?q?=20Three=20interconnected=20bugs=20caused=20incorrect=20renderi?= =?UTF-8?q?ng=20of=20display:=20inline-block=20elements:=201.=20Inline-blo?= =?UTF-8?q?ck=20elements=20with=20explicit=20width/height=20(e.g.=20share?= =?UTF-8?q?=20buttons=20with=20width:=2020px;=20height:=2020px)=20had=20ze?= =?UTF-8?q?ro=20dimensions=20=E2=80=94=20their=20internal=20children=20wer?= =?UTF-8?q?e=20never=20laid=20out.=202.=20Text-based=20inline-blocks=20(e.?= =?UTF-8?q?g.=20Download=20styled=20as=20display:=20?= =?UTF-8?q?inline-block)=20were=20affected=20by=20an=20incorrect=20font=20?= =?UTF-8?q?shorthand=20that=20failed=20to=20reset=20line-height=20to=20nor?= =?UTF-8?q?mal,=20allowing=20an=20inherited=20line-height:=2010=20on=20=20to=20inflate=20element=20heights=20via=20the=20line=20box?= =?UTF-8?q?=20strut.=203.=20Inline-block=20elements=20with=20long=20text?= =?UTF-8?q?=20did=20not=20wrap=20atomically=20to=20the=20next=20line=20whe?= =?UTF-8?q?n=20they=20exceeded=20the=20container=20width.=20Changes=20by?= =?UTF-8?q?=20file=20include/eepp/graphics/richtext.hpp=20/=20src/eepp/gra?= =?UTF-8?q?phics/richtext.cpp=20-=20Added=20lineHeight=20(Float)=20and=20i?= =?UTF-8?q?sAtomic=20(bool)=20fields=20to=20SpanBlock.=20The=20lineHeight?= =?UTF-8?q?=20field=20provides=20per-span=20line-height=20overriding=20the?= =?UTF-8?q?=20global=20mLineHeight=20=E2=80=94=20this=20is=20used=20by=20i?= =?UTF-8?q?nline-block=20spans=20to=20set=20their=20line=20height=20to=20t?= =?UTF-8?q?he=20font's=20em=20height=20rather=20than=20inheriting=20the=20?= =?UTF-8?q?container's=20line-height.=20The=20isAtomic=20flag=20marks=20a?= =?UTF-8?q?=20span=20as=20an=20atomic=20inline-level=20box:=20during=20Ric?= =?UTF-8?q?hText=20Pass=201=20(both=20the=20non-float=20and=20float-aware?= =?UTF-8?q?=20paths),=20atomic=20spans=20check=20whether=20the=20full=20te?= =?UTF-8?q?xt=20width=20plus=20margins=20fits=20on=20the=20current=20line;?= =?UTF-8?q?=20if=20not,=20they=20push=20to=20the=20next=20line=20before=20?= =?UTF-8?q?any=20text=20wrapping=20occurs.=20This=20matches=20the=20CSS=20?= =?UTF-8?q?behavior=20where=20an=20inline-block=20drops=20to=20a=20new=20l?= =?UTF-8?q?ine=20as=20an=20opaque=20unit=20rather=20than=20having=20its=20?= =?UTF-8?q?text=20wrap=20within=20the=20parent=20line.=20-=20Updated=20add?= =?UTF-8?q?Span()=20to=20accept=20lineHeight=20and=20isAtomic=20parameters?= =?UTF-8?q?=20(with=20defaults=20of=200=20and=20false=20to=20preserve=20ex?= =?UTF-8?q?isting=20call=20sites).=20-=20Added=20per-span=20line-height=20?= =?UTF-8?q?priority=20(3-tier=20cascade):=20a=20span's=20own=20lineHeight?= =?UTF-8?q?=20takes=20precedence=20over=20the=20global=20mLineHeight,=20wh?= =?UTF-8?q?ich=20takes=20precedence=20over=20the=20font's=20intrinsic=20li?= =?UTF-8?q?ne=20spacing.=20This=20ensures=20inline-block=20spans=20use=20f?= =?UTF-8?q?ont=20em=20height=20(set=20by=20UIRichText::rebuildRichText),?= =?UTF-8?q?=20pure=20inline=20text=20uses=20the=20container's=20line-heigh?= =?UTF-8?q?t,=20and=20all=20fall=20back=20to=20font=20metrics.=20-=20Added?= =?UTF-8?q?=20line=20box=20strut=20in=20Pass=202=20(both=20paths):=20line.?= =?UTF-8?q?height=20=3D=20std::max(line.height,=20mLineHeight)=20ensures?= =?UTF-8?q?=20every=20line=20box=20respects=20the=20containing=20block's?= =?UTF-8?q?=20line-height,=20per=20CSS=202.1=20=C2=A710.8.1.=20Previously?= =?UTF-8?q?=20a=20line=20box's=20height=20was=20determined=20solely=20by?= =?UTF-8?q?=20the=20tallest=20inline=20element=20on=20that=20line;=20now?= =?UTF-8?q?=20the=20container's=20line-height=20acts=20as=20a=20minimum.?= =?UTF-8?q?=20-=20Propagated=20pText->lineHeight=20and=20pText->isAtomic?= =?UTF-8?q?=20through=20to=20the=20SpanBlock=20construction=20in=20RenderS?= =?UTF-8?q?pan=20creation=20in=20both=20the=20non-float=20and=20float-awar?= =?UTF-8?q?e=20paths,=20so=20the=20per-span=20values=20persist=20from=20ad?= =?UTF-8?q?dSpan()=20through=20to=20layout.=20include/eepp/ui/uitextspan.h?= =?UTF-8?q?pp=20/=20src/eepp/ui/uitextspan.cpp=20-=20isMergeable()=20now?= =?UTF-8?q?=20returns=20true=20for=20both=20CSSDisplay::Inline=20and=20CSS?= =?UTF-8?q?Display::InlineBlock.=20Previously=20only=20Inline=20was=20merg?= =?UTF-8?q?eable.=20This=20allows=20text-based=20inline-block=20content=20?= =?UTF-8?q?to=20participate=20in=20the=20parent=20container's=20RichText?= =?UTF-8?q?=20flow=20(baseline-aligned),=20while=20the=20widget=20still=20?= =?UTF-8?q?draws=20its=20own=20border=20and=20background=20independently.?= =?UTF-8?q?=20-=20Added=20isInlineBlock()=20helper=20method=20that=20retur?= =?UTF-8?q?ns=20mDisplay=20=3D=3D=20CSSDisplay::InlineBlock,=20used=20by?= =?UTF-8?q?=20callers=20throughout=20the=20layouter=20and=20RichText=20cod?= =?UTF-8?q?e=20to=20distinguish=20inline-block=20treatment=20from=20pure?= =?UTF-8?q?=20inline.=20-=20Fixed=20drawBorder():=20inline-block=20widgets?= =?UTF-8?q?=20now=20draw=20their=20border=20at=20the=20widget=20bounds=20(?= =?UTF-8?q?mScreenPos,=20mSize)=20without=20expanding=20by=20padding,=20be?= =?UTF-8?q?cause=20padding=20has=20already=20been=20incorporated=20into=20?= =?UTF-8?q?the=20widget's=20size=20via=20the=20RichText=20bounds=20expansi?= =?UTF-8?q?on.=20Pure=20inline=20spans=20continue=20to=20draw=20the=20bord?= =?UTF-8?q?er=20expanded=20by=20padding=20(the=20old=20behavior).=20src/ee?= =?UTF-8?q?pp/ui/uirichtext.cpp=20-=20In=20rebuildRichText(),=20inline-blo?= =?UTF-8?q?ck=20widgets=20are=20now=20handled=20via=20two=20distinct=20cod?= =?UTF-8?q?e=20paths:=20=20=20-=20Text-based=20inline-block=20(has=20its?= =?UTF-8?q?=20own=20text=20content):=20treated=20as=20mergeable=20?= =?UTF-8?q?=E2=80=94=20text=20flows=20into=20the=20parent=20container's=20?= =?UTF-8?q?RichText=20as=20a=20SpanBlock=20with=20isAtomic=20=3D=20true=20?= =?UTF-8?q?for=20atomic=20line-wrapping,=20and=20the=20span's=20lineHeight?= =?UTF-8?q?=20set=20to=20the=20font's=20em=20height.=20Padding=20and=20mar?= =?UTF-8?q?gins=20are=20propagated=20through=20the=20SpanBlock.=20The=20wi?= =?UTF-8?q?dget's=20size=20and=20hit-boxes=20are=20computed=20from=20the?= =?UTF-8?q?=20parent=20RichText's=20layout=20(via=20BlockLayouter::positio?= =?UTF-8?q?nRichTextChildren=20bounds=20expansion).=20=20=20-=20Inline-blo?= =?UTF-8?q?ck=20without=20text=20(has=20only=20child=20widgets,=20e.g.=20s?= =?UTF-8?q?hare=20buttons=20containing=20nested=20=20elements):=20di?= =?UTF-8?q?verted=20to=20the=20CustomBlock=20path=20=E2=80=94=20the=20widg?= =?UTF-8?q?et=20runs=20its=20own=20BlockLayouter::updateLayout()=20to=20pr?= =?UTF-8?q?ocess=20internal=20children=20and=20determine=20its=20intrinsic?= =?UTF-8?q?=20size,=20and=20is=20added=20to=20the=20parent's=20RichText=20?= =?UTF-8?q?as=20a=20CustomBlock=20(atomic=20inline-level=20box).=20This=20?= =?UTF-8?q?ensures=20inner=20children=20are=20properly=20laid=20out=20rath?= =?UTF-8?q?er=20than=20left=20at=20zero=20dimensions.=20-=20Whitespace=20c?= =?UTF-8?q?ollapse=20logic=20around=20inline-block=20spans=20now=20account?= =?UTF-8?q?s=20for=20whether=20the=20adjacent=20nodes=20are=20inline,=20pr?= =?UTF-8?q?eventing=20incorrect=20stripping=20of=20leading/trailing=20spac?= =?UTF-8?q?es.=20src/eepp/ui/blocklayouter.cpp=20-=20BlockLayouter::update?= =?UTF-8?q?Layout():=20the=20early=20return=20for=20mergeable=20widgets=20?= =?UTF-8?q?now=20makes=20an=20exception=20for=20inline-block=20widgets=20t?= =?UTF-8?q?hat=20have=20no=20text=20of=20their=20own=20=E2=80=94=20these?= =?UTF-8?q?=20still=20need=20their=20internal=20children=20laid=20out,=20s?= =?UTF-8?q?o=20the=20layouter=20proceeds=20rather=20than=20returning=20ear?= =?UTF-8?q?ly.=20-=20BlockLayouter::positionRichTextChildren():=20inline-b?= =?UTF-8?q?lock=20widgets=20are=20processed=20in=20two=20ways=20depending?= =?UTF-8?q?=20on=20whether=20they=20have=20text:=20=20=20-=20Text-based:?= =?UTF-8?q?=20bounds=20computed=20from=20the=20parent=20RichText's=20span?= =?UTF-8?q?=20positions,=20expanded=20by=20padding.=20The=20widget's=20pos?= =?UTF-8?q?ition=20and=20size=20are=20set=20from=20these=20bounds.=20=20?= =?UTF-8?q?=20-=20No-text=20(CustomBlock):=20positioned=20via=20getNextCus?= =?UTF-8?q?tomSpan()=20from=20the=20parent=20RichText's=20CustomBlock=20sp?= =?UTF-8?q?ans,=20with=20padding=20expansion=20for=20bounds.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both paths expand the widget's pixel bounds by padding for inline-block styling. A bool handled flag replaces the previous goto-based control flow. - Added bounds and hit-box padding expansion for inline-block widgets in the CustomBlock positioning path (previously only the mergeable path had this). src/eepp/ui/css/stylesheetspecification.cpp - Fixed font shorthand registration: changed the component list from { font-style, font-size, line-spacing, font-family } to { font-style, font-weight, font-size, line-height, font-family }. The line-spacing property does not participate in the font shorthand per the CSS Fonts specification (§3.1); line-height does. - Fixed font shorthand parser: - font-weight keywords (bold, bolder, lighter) are now emitted as a separate font-weight property instead of being incorrectly merged into the font-style property string. - When the font shorthand does not include an explicit /line-height value, the parser now always emits line-height: normal to reset any previously inherited line-height. Previously it emitted nothing, allowing an inherited line-height (e.g. line-height: 10 on ) to leak into child elements that used the font shorthand. src/tests/unit_tests/uihtml_tests.cpp - Added UIRichText.anchorPadding test: verifies that an inline-block tag with padding and border renders correctly at the expected size (81×28 px for "Download" text in 11px Noto Sans with padding: 6px 14px and border: 1px solid), using both pixel-diff comparison and explicit size assertions. The HTML uses line-height: 1.5 on and font: 11px on the inline-block anchor. - Added UIRichText.anchorPaddingLineHeight test: same layout but with line-height: 10 on , verifying that the per-span line-height prevents the inherited line-height: 10 from inflating the inline-block anchor's dimensions. - Added UIBackground.InlineBlockImageSpans test: verifies that inline-block elements with fixed width/height (20×20 px share buttons) and nested child elements (with display: block and text-indent: -9999px) have non-zero dimensions and render correctly with pixel-diff comparison. This covers the CustomBlock path for inline-blocks without text of their own. --- .../assets/html/anchor_padding.html | 65 +++++++++ .../html/anchor_padding_lineheight.html | 65 +++++++++ .../eepp-ui-anchor-padding-lineheight.webp | Bin 0 -> 1248 bytes .../assets/html/eepp-ui-anchor-padding.webp | Bin 0 -> 1248 bytes .../eepp-ui-inline-block-image-spans.webp | Bin 0 -> 1052 bytes bin/unit_tests/assets/html/inline_block.html | 137 ++++++++++++++++++ include/eepp/graphics/richtext.hpp | 4 +- include/eepp/ui/uitextspan.hpp | 2 + src/eepp/graphics/richtext.cpp | 100 +++++++------ src/eepp/ui/blocklayouter.cpp | 92 ++++++++---- src/eepp/ui/css/stylesheetspecification.cpp | 21 +-- src/eepp/ui/uirichtext.cpp | 134 ++++++++++------- src/eepp/ui/uitextspan.cpp | 23 ++- src/tests/unit_tests/uihtml_tests.cpp | 124 ++++++++++++++++ 14 files changed, 630 insertions(+), 137 deletions(-) create mode 100644 bin/unit_tests/assets/html/anchor_padding.html create mode 100644 bin/unit_tests/assets/html/anchor_padding_lineheight.html create mode 100644 bin/unit_tests/assets/html/eepp-ui-anchor-padding-lineheight.webp create mode 100644 bin/unit_tests/assets/html/eepp-ui-anchor-padding.webp create mode 100644 bin/unit_tests/assets/html/eepp-ui-inline-block-image-spans.webp create mode 100644 bin/unit_tests/assets/html/inline_block.html diff --git a/bin/unit_tests/assets/html/anchor_padding.html b/bin/unit_tests/assets/html/anchor_padding.html new file mode 100644 index 000000000..c9d3646cd --- /dev/null +++ b/bin/unit_tests/assets/html/anchor_padding.html @@ -0,0 +1,65 @@ + + + + + +
+ +
+ + diff --git a/bin/unit_tests/assets/html/anchor_padding_lineheight.html b/bin/unit_tests/assets/html/anchor_padding_lineheight.html new file mode 100644 index 000000000..be0bb34b8 --- /dev/null +++ b/bin/unit_tests/assets/html/anchor_padding_lineheight.html @@ -0,0 +1,65 @@ + + + + + +
+
+
+

Windows

+ Download +
+
+
+ + diff --git a/bin/unit_tests/assets/html/eepp-ui-anchor-padding-lineheight.webp b/bin/unit_tests/assets/html/eepp-ui-anchor-padding-lineheight.webp new file mode 100644 index 0000000000000000000000000000000000000000..5cdf1db375c93dbcfce35681d65f8a8d6d7dca4f GIT binary patch literal 1248 zcmWIYbaT7G!oU#j>J$(bVBvEb$kvxXJe47yU2~F(dyw0=EV0x7cRxr8pQG?lW0H#F z%3DX7FJ0);i#zhx&ofWuE5H0)-~97mUAVS_no*T7t1j#W`+Y!4hgc?5_dK>S-kaLyUuszF2*AbD`s~D32->9NxkZ;>2mb# z*R-{FH~Ia(dC>RzIwpqFJIkAAAGUM2x~jD`LzIzGurXB2&Dn)vkpdG_fO$idA(KF9 zaMsS3t2Q}a;X1&;pu~}#`=^HKY=+ySB`V#X^Pj6tloT>PJSAm9Vv$PGmUP3V1~b#1 z@JwvcY*oH^#$e{NzRZY*edRaPXHH3(t+S{!>+B zKf_1JQ(&@=kJ6-*hn!TrCYtb0Ry6i$Hu#q>ta4{tZjQHg?Tz_=Yj1ALy$waBn{xm1 z?tXVW!~IJ=lV7#nrNe>$CjbA}{^IY6^RLp*-QRuviQ;SDyzQw^mc5TlnPhuszwfrG$#FOM_;yJ;MVU`Bt3K2F-TI_)ZGO*-%#{>cCN!^WWxcc6 zR;EgKPPBDg?Y;xO)@hsn&O5hjv&MtG`nof0{u}+Cs7TwL>Y0J94eB$@Z=SIKSF{aYo!W$F9^^_Kkg6a3$^v)h<_`nh<)0{8VNYPXeqy}k1A zw_8P@?#?ZJSn}l9y@*ux>2cA?*7ggHZ=OAII`+gmx45@XWjnptY@}@J{x#8de+tk7+6a^UwuX4H@i^C znm|Q1PVKOjvxPlk7DqLf@x|@grZH7+rk7H=oQURRv9HP(?U;p3|2nMvaw0^i@X^C0 z;{`csOS0cT__@U?TmQ!vw@XFSUoF)wS*Lfw&hElL=CyktT$b|e3SWCgF5@w;zPP?? z>Xr|!`P@hHCT-hLemLi6_r^u|SD~iMvt$HdcD@`ow zcv-4wg)gbbl_Ei&G1oUJDtI312pe@|{bB#a-ut}w`TY3t$6>LPN&pb(=Ij^X=YSvh z)JOwC2@ndmpd;MUA=Ux_OblUG%1W;HT-~$)6{X(q6@gA?^iBQoFkaTL4(ncZ z_1>J<3U8@@Fz)-SE2CdTjd@$R_N?zl*UdEg96(*(6ZJ44X_}qQ5TMXw&vSAJUn(eQ zWL^6P>*5Mj8p2?H_SB&BF!iJ>vAmV%jhIoDP5>}gjw2qX!^8w^Wi{dV z6h{_?kr1S&nDaH(WzkYq+C7ULli@+@vZcaX*QxC(P)u2IB;CX2$7*X1O3uJVrO6X8 zwl+1^khQlY219A=6dqFCVH`SB^CuCwHG z#l5n_v~;IZye3Zl@wa$mx~SV3;Yqu2>ame6KHK^@;S6FLB*uF=W=yh zUHi4y2@6viM=YxC?~itVDV9q+O4T#Y?Yhl~_wUMcFU4mF^+D$J8-eU*ez`QZO{1#@ z%nRkR4_YAb_L|p}sH?@q*taSs)YY42!5F$P*KsB;892(BZ@J)9}(1@g?2MvJziCEl#ZZ2bVZD05KMYYX%FK+9s829 zXCzvwtK$xI{g^@iQ8!IS2@e)#Ufi%`ez=n{f;VxAzMSrGxmbF#O`{(LK9E*8PNc1o zq{pxNDe@m2WU{28O>T)iJahP2kXQ%mz~nWIbTunl1q?3DQ>Avam;U1XlYXWrY?`+) z&-4iN4w6960C$}e-J|4jl@rSkLDQd(eGaB|5C zK`;5=KXdxYQ^mzb!wfilh;1iH?P8yzmvn?U-I$l{Qq{Jy)C_JdoM2t_2&xQPR6_?R zr1DZNYAb4x>A}Xye#&`t)zFT}kmSwEShma73t^C^c>wA= z&#QPDxrb{Uhihu3#_Lm6w2LIX+JIWm&*nmSI*V0V%(^#4T>i##xn*Eg`UR5xb7q58 sQMJz)%v-OGRdL8pJxVXpQ2+n{ literal 0 HcmV?d00001 diff --git a/bin/unit_tests/assets/html/eepp-ui-inline-block-image-spans.webp b/bin/unit_tests/assets/html/eepp-ui-inline-block-image-spans.webp new file mode 100644 index 0000000000000000000000000000000000000000..c44d05b20eabbcbca343428589b6ffc6772f4281 GIT binary patch literal 1052 zcmWIYbaNA7VPFV%bqWXzu<+pkvi1KnFJ>_9o6zv!e^F>x$C6+2TXSz;+jcho-NpRm z4q3L(D(ekTXwEpb#%R_?AI9T}ncXv|II7%k&X>AV?($##fX{8SKO47y-{a=;@6-PO z|G(S+*Ju2he~+d0oy1dQRG+>2zJ`?ezcZCM&BPD6qFd;hM<-?N>{u+x#qhTd+Yg%4_yAo8a_)v!iVf{o{{cf8$Hi zL66@auVo)z=6LDDcj8GX^Lh@BMGU-69JWW(_4$SN1^2UO%gxW~3wpTa-O_#A#KNBM zn>O35n=8`wO1PV zW!u}%=-Zxm?vA;)rS5F0uFsn@dB6VbO#JeB--JM+)oat5V@oxrN4v6jy()WoXJ&0s zX=>E><@(#!ow;M^u2~Ybdh6G|RbR}n{<`v~Iv z$so1ZJaGnds{%%aR3rP{K~`ULvJ_KaK9Bqj z`_Immd-61()yT81YF^VcY2b^L&AUyHKMF~uF9pFC`{NZfcgYW0kr_vTMzU{O^NnX>8d zhv&?nHY5pby6HJVp>YD^>gYYyx*zvN8J70i9og_e;;Y*q@h!DKf1I!X_j&&RKhN#| K{~R{VbOr#7QAG&= literal 0 HcmV?d00001 diff --git a/bin/unit_tests/assets/html/inline_block.html b/bin/unit_tests/assets/html/inline_block.html new file mode 100644 index 000000000..b0b5edf86 --- /dev/null +++ b/bin/unit_tests/assets/html/inline_block.html @@ -0,0 +1,137 @@ + + + + + + + + diff --git a/include/eepp/graphics/richtext.hpp b/include/eepp/graphics/richtext.hpp index 84a08c182..6e340e504 100644 --- a/include/eepp/graphics/richtext.hpp +++ b/include/eepp/graphics/richtext.hpp @@ -35,7 +35,7 @@ class EE_API RichText : public Drawable { void addSpan( const String& text, const FontStyleConfig& style ); void addSpan( const String& text, const FontStyleConfig& style, const Rectf& margin, - const Rectf& padding ); + const Rectf& padding, Float lineHeight = 0, bool isAtomic = false ); /** * @brief Adds a text span with individual style parameters. @@ -92,6 +92,8 @@ class EE_API RichText : public Drawable { std::shared_ptr text; Rectf margin; Rectf padding; + Float lineHeight{ 0 }; + bool isAtomic{ false }; }; using Block = std::variant, CustomBlock>; diff --git a/include/eepp/ui/uitextspan.hpp b/include/eepp/ui/uitextspan.hpp index f89acabee..9fea7da7d 100644 --- a/include/eepp/ui/uitextspan.hpp +++ b/include/eepp/ui/uitextspan.hpp @@ -43,6 +43,8 @@ class EE_API UITextSpan : public UIRichText { virtual bool isMergeable() const; + virtual bool isInlineBlock() const; + virtual void draw(); virtual bool applyProperty( const StyleSheetProperty& attribute ); diff --git a/src/eepp/graphics/richtext.cpp b/src/eepp/graphics/richtext.cpp index 00a2873a3..f9698d303 100644 --- a/src/eepp/graphics/richtext.cpp +++ b/src/eepp/graphics/richtext.cpp @@ -302,14 +302,14 @@ Sizef RichText::getPixelsSize() { } void RichText::addSpan( const String& text, const FontStyleConfig& style, const Rectf& margin, - const Rectf& padding ) { - if ( text.empty() && margin == Rectf::Zero && padding == Rectf::Zero ) + const Rectf& padding, Float lineHeight, bool isAtomic ) { + if ( text.empty() && margin == Rectf::Zero && padding == Rectf::Zero && lineHeight == 0 ) return; auto span = std::make_shared(); span->setString( text ); span->setStyleConfig( style ); - mBlocks.push_back( SpanBlock{ span, margin, padding } ); + mBlocks.push_back( SpanBlock{ span, margin, padding, lineHeight, isAtomic } ); invalidateLayout(); } @@ -512,6 +512,18 @@ void RichText::updateLayout() { if ( !mLines.empty() ) mLines.back().width += extraLeft; + if ( pText->isAtomic && curX > extraLeft && mMaxWidth > 0 ) { + Float extraRight = pText->margin.Right + pText->padding.Right; + Float fullTextWidth = span->getTextWidth(); + if ( curX + fullTextWidth + extraRight > mMaxWidth ) { + maxWidth = std::max( maxWidth, curX - extraLeft ); + mLines.push_back( RenderParagraph() ); + curX = extraLeft; + if ( !mLines.empty() ) + mLines.back().width += extraLeft; + } + } + Uint32 textHints = span->getTextHints(); // Compute where lines break within this text span. @@ -538,18 +550,20 @@ void RichText::updateLayout() { renderSpanText->setStyleConfig( fontStyle ); Float ascent = fontStyle.Font->getAscent( fontStyle.CharacterSize ); - Float height = mLineHeight > 0 - ? mLineHeight - : fontStyle.Font->getLineSpacing( fontStyle.CharacterSize ); + Float height = + pText->lineHeight > 0 ? pText->lineHeight + : mLineHeight > 0 + ? mLineHeight + : fontStyle.Font->getLineSpacing( fontStyle.CharacterSize ); Float spanWidth = renderSpanText->getTextWidth(); - RenderSpan renderSpan; - renderSpan.block = - SpanBlock{ renderSpanText, pText->margin, pText->padding }; - renderSpan.position = { curX, 0 }; - renderSpan.size = Sizef( spanWidth, height ); - renderSpan.startCharIndex = curCharIdx; - renderSpan.endCharIndex = curCharIdx + ( endIdx - startIdx ); + RenderSpan renderSpan{ + SpanBlock{ renderSpanText, pText->margin, pText->padding, + pText->lineHeight, pText->isAtomic }, + { curX, 0 }, + Sizef( spanWidth, height ), + curCharIdx, + static_cast( curCharIdx + ( endIdx - startIdx ) ) }; curCharIdx = renderSpan.endCharIndex; RenderParagraph& currentLine = mLines.back(); @@ -619,12 +633,7 @@ void RichText::updateLayout() { curX = 0; } - RenderSpan renderSpan; - renderSpan.block = block; - renderSpan.position = { curX, 0 }; - renderSpan.size = blockSize; - renderSpan.startCharIndex = curCharIdx; - renderSpan.endCharIndex = curCharIdx + 1; + RenderSpan renderSpan{ block, { curX, 0 }, blockSize, curCharIdx, curCharIdx + 1 }; curCharIdx = renderSpan.endCharIndex; RenderParagraph& currentLine = mLines.back(); @@ -688,6 +697,8 @@ void RichText::updateLayout() { } line.height = std::max( line.height, maxLineHeight ); + if ( mLineHeight > 0 ) + line.height = std::max( line.height, mLineHeight ); curY += line.height; } @@ -788,6 +799,18 @@ void RichText::updateLayout() { if ( !mLines.empty() ) mLines.back().width += extraLeft; + if ( pText->isAtomic && curX > extraLeft && mMaxWidth > 0 ) { + Float extraRight = pText->margin.Right + pText->padding.Right; + Float fullTextWidth = span->getTextWidth(); + if ( curX + fullTextWidth + extraRight > mMaxWidth ) { + maxWidth = std::max( maxWidth, curX - extraLeft ); + mLines.push_back( RenderParagraph() ); + curX = extraLeft; + if ( !mLines.empty() ) + mLines.back().width += extraLeft; + } + } + // Shift curX inside to the left edge — text starts // to the right of any left floats. Float le = floatLeftEdge( curY ); @@ -821,17 +844,20 @@ void RichText::updateLayout() { renderSpanText->setStyleConfig( fontStyle ); Float ascent = fontStyle.Font->getAscent( fontStyle.CharacterSize ); - Float height = mLineHeight > 0 + Float height = pText->lineHeight > 0 ? pText->lineHeight + : mLineHeight > 0 ? mLineHeight : fontStyle.Font->getLineSpacing( fontStyle.CharacterSize ); Float spanWidth = renderSpanText->getTextWidth(); - RenderSpan renderSpan; - renderSpan.block = SpanBlock{ renderSpanText, pText->margin, pText->padding }; - renderSpan.position = { curX, 0 }; - renderSpan.size = Sizef( spanWidth, height ); - renderSpan.startCharIndex = curCharIdx; - renderSpan.endCharIndex = curCharIdx + ( endIdx - startIdx ); + RenderSpan renderSpan{ + SpanBlock{ renderSpanText, pText->margin, pText->padding, pText->lineHeight, + pText->isAtomic }, + { curX, 0 }, + Sizef( spanWidth, height ), + curCharIdx, + static_cast( curCharIdx + ( endIdx - startIdx ) ) }; + curCharIdx = renderSpan.endCharIndex; RenderParagraph& currentLine = mLines.back(); @@ -844,7 +870,8 @@ void RichText::updateLayout() { currentLine.width += spanWidth; } - // Trailing margin may force a wrap. + // After the last segment, add trailing margin and check if the + // margin itself forces a wrap. if ( i == wrapInfo.wraps.size() - 2 && !isNewline ) { Float extraRight = pText->margin.Right + pText->padding.Right; curX += extraRight; @@ -916,20 +943,14 @@ void RichText::updateLayout() { posX = le; } - RenderSpan renderSpan; - renderSpan.block = block; - renderSpan.position = { posX, 0 }; - renderSpan.size = blockSize; - renderSpan.startCharIndex = curCharIdx; - renderSpan.endCharIndex = curCharIdx + 1; + RenderSpan renderSpan{ block, { posX, 0 }, blockSize, curCharIdx, curCharIdx + 1 }; curCharIdx = renderSpan.endCharIndex; mLines.back().spans.push_back( renderSpan ); // Record the float's bounding box so subsequent // content can wrap around it. - Rectf fr( posX, curY, posX + blockSize.getWidth(), - curY + blockSize.getHeight() ); + Rectf fr( posX, curY, posX + blockSize.getWidth(), curY + blockSize.getHeight() ); if ( floatType == UI::CSSFloat::Left ) leftFloats.push_back( fr ); else @@ -956,12 +977,7 @@ void RichText::updateLayout() { curX = 0; } - RenderSpan renderSpan; - renderSpan.block = block; - renderSpan.position = { curX, 0 }; - renderSpan.size = blockSize; - renderSpan.startCharIndex = curCharIdx; - renderSpan.endCharIndex = curCharIdx + 1; + RenderSpan renderSpan{ block, { curX, 0 }, blockSize, curCharIdx, curCharIdx + 1 }; curCharIdx = renderSpan.endCharIndex; RenderParagraph& currentLine = mLines.back(); @@ -1032,6 +1048,8 @@ void RichText::updateLayout() { } line.height = std::max( line.height, maxLineHeight ); + if ( mLineHeight > 0 ) + line.height = std::max( line.height, mLineHeight ); accumY += line.height; } diff --git a/src/eepp/ui/blocklayouter.cpp b/src/eepp/ui/blocklayouter.cpp index 258f13e07..584e22e82 100644 --- a/src/eepp/ui/blocklayouter.cpp +++ b/src/eepp/ui/blocklayouter.cpp @@ -54,6 +54,10 @@ void BlockLayouter::updateLayout() { auto* rt = widget->getRichTextPtr(); if ( rt == nullptr || mPacking ) return; + + if ( widget->isMergeable() ) + return; + mResizedCount = 0; mPacking = true; @@ -235,11 +239,14 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) { p = p->getParent(); } + bool handled = false; + if ( widget->isType( UI_TYPE_HTML_WIDGET ) && widget->asType()->isMergeable() ) { UITextSpan* textSpan = widget->asType(); Int64 startChar = curCharIdx; Int64 endChar = curCharIdx; + if ( !textSpan->getText().empty() ) { endChar += textSpan->getText().length(); curCharIdx = endChar; @@ -280,6 +287,20 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) { spanChild = spanChild->getNextNode(); } + if ( textSpan->isInlineBlock() ) { + Rectf pad = textSpan->getPixelsPadding(); + bounds.Left -= pad.Left; + bounds.Top -= pad.Top; + bounds.Right += pad.Right; + bounds.Bottom += pad.Bottom; + for ( auto& hb : hitBoxes ) { + hb.Left -= pad.Left; + hb.Top -= pad.Top; + hb.Right += pad.Right; + hb.Bottom += pad.Bottom; + } + } + if ( bounds.Left <= bounds.Right && bounds.Top <= bounds.Bottom ) { Vector2f boundsPos = bounds.getPosition(); @@ -296,34 +317,55 @@ void BlockLayouter::positionRichTextChildren( Graphics::RichText* rt ) { hitBoxes.clear(); } - } else if ( widget->isType( UI_TYPE_BR ) ) { - curCharIdx += 1; - Vector2f pos; - if ( widget->getPrevNode() && widget->getPrevNode()->isWidget() ) { - pos = widget->getPrevNode()->asType()->getPixelsPosition(); - pos.y += widget->getPrevNode()->getPixelsSize().getHeight(); - } - widget->setPixelsPosition( pos ); - widget->setPixelsSize( { eemax( 0.f, mContainer->getPixelsSize().getWidth() - - mContainer->getPixelsContentOffset().Left - - mContainer->getPixelsContentOffset().Right ), - 0 } ); - } else { - curCharIdx += 1; - const auto* span = getNextCustomSpan(); - if ( span ) { - size_t lineIdx = currentSpan > 0 ? currentLine : currentLine - 1; - Float lineY = lines[lineIdx].y; - Rectf margin = widget->getLayoutPixelsMargin(); + handled = true; + } - Vector2f targetPos( mContainer->getPixelsContentOffset().Left + span->position.x + - margin.Left, - mContainer->getPixelsContentOffset().Top + lineY + - span->position.y + margin.Top ); + if ( !handled ) { + if ( widget->isType( UI_TYPE_BR ) ) { + curCharIdx += 1; + Vector2f pos; + if ( widget->getPrevNode() && widget->getPrevNode()->isWidget() ) { + pos = widget->getPrevNode()->asType()->getPixelsPosition(); + pos.y += widget->getPrevNode()->getPixelsSize().getHeight(); + } + widget->setPixelsPosition( pos ); + widget->setPixelsSize( + { eemax( 0.f, mContainer->getPixelsSize().getWidth() - + mContainer->getPixelsContentOffset().Left - + mContainer->getPixelsContentOffset().Right ), + 0 } ); + } else { + curCharIdx += 1; + const auto* span = getNextCustomSpan(); + if ( span ) { + size_t lineIdx = currentSpan > 0 ? currentLine : currentLine - 1; + Float lineY = lines[lineIdx].y; + Rectf margin = widget->getLayoutPixelsMargin(); - widget->setPixelsPosition( targetPos - offset ); + Vector2f targetPos( mContainer->getPixelsContentOffset().Left + + span->position.x + margin.Left, + mContainer->getPixelsContentOffset().Top + lineY + + span->position.y + margin.Top ); - bounds = Rectf( targetPos, span->size ); + widget->setPixelsPosition( targetPos - offset ); + + bounds = Rectf( targetPos, span->size ); + + if ( widget->isType( UI_TYPE_TEXTSPAN ) && + widget->asType()->isInlineBlock() ) { + Rectf pad = widget->getPixelsPadding(); + bounds.Left -= pad.Left; + bounds.Top -= pad.Top; + bounds.Right += pad.Right; + bounds.Bottom += pad.Bottom; + Vector2f boundsPos = bounds.getPosition(); + widget->setPixelsPosition( boundsPos - offset ); + if ( bounds.getSize() != widget->getPixelsSize() ) { + widget->setPixelsSize( bounds.getSize() ); + mResizedCount++; + } + } + } } } return bounds; diff --git a/src/eepp/ui/css/stylesheetspecification.cpp b/src/eepp/ui/css/stylesheetspecification.cpp index 1a5bf0d01..8074f5b1d 100644 --- a/src/eepp/ui/css/stylesheetspecification.cpp +++ b/src/eepp/ui/css/stylesheetspecification.cpp @@ -563,7 +563,8 @@ void StyleSheetSpecification::registerDefaultProperties() { registerShorthand( "list-style", { "list-style-type", "list-style-position", "list-style-image" }, "list-style" ); - registerShorthand( "font", { "font-style", "font-size", "line-spacing", "font-family" }, + registerShorthand( "font", + { "font-style", "font-weight", "font-size", "line-height", "font-family" }, "font" ); } @@ -1356,8 +1357,9 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { int stylePos = getIndexEndingWith( propNames, "-style" ); int sizePos = getIndexEndingWith( propNames, "-size" ); - int linePos = getIndexEndingWith( propNames, "-spacing" ); + int linePos = getIndexEndingWith( propNames, "-height" ); int familyPos = getIndexEndingWith( propNames, "-family" ); + int weightPos = getIndexEndingWith( propNames, "-weight" ); static const std::string sizeKeywords[] = { "xx-small", "x-small", "small", "medium", "large", "x-large", "xx-large", "xxx-large" }; @@ -1394,6 +1396,7 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { std::string sizeStr; std::string lineStr; std::string familyStr; + std::string weightStr; bool inLineHeight = false; for ( size_t i = 0; i < tokens.size(); i++ ) { @@ -1446,11 +1449,8 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { if ( isWeightWord( tok ) ) { std::string lt = String::toLower( tok ); - if ( lt != "normal" ) { - if ( !styleStr.empty() ) - styleStr += "|"; - styleStr += "bold"; - } + if ( lt != "normal" ) + weightStr = "bold"; continue; } @@ -1465,10 +1465,13 @@ void StyleSheetSpecification::registerDefaultShorthandParsers() { if ( !sizeStr.empty() ) { if ( stylePos != -1 && !styleStr.empty() ) properties.emplace_back( StyleSheetProperty( propNames[stylePos], styleStr ) ); + if ( weightPos != -1 && !weightStr.empty() ) + properties.emplace_back( StyleSheetProperty( propNames[weightPos], weightStr ) ); if ( sizePos != -1 ) properties.emplace_back( StyleSheetProperty( propNames[sizePos], sizeStr ) ); - if ( linePos != -1 && !lineStr.empty() ) - properties.emplace_back( StyleSheetProperty( propNames[linePos], lineStr ) ); + if ( linePos != -1 ) + properties.emplace_back( StyleSheetProperty( + propNames[linePos], lineStr.empty() ? "normal" : lineStr ) ); if ( familyPos != -1 && !familyStr.empty() ) { String::trimInPlace( familyStr ); if ( familyStr.size() >= 2 && diff --git a/src/eepp/ui/uirichtext.cpp b/src/eepp/ui/uirichtext.cpp index 0b4245677..91f8dfa21 100644 --- a/src/eepp/ui/uirichtext.cpp +++ b/src/eepp/ui/uirichtext.cpp @@ -941,6 +941,8 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri UIWidget* widget = node->asType(); + bool handled = false; + if ( widget->isType( UI_TYPE_HTML_WIDGET ) && widget->asType()->isMergeable() ) { UITextSpan* span = widget->asType(); @@ -948,6 +950,14 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri Rectf padding = span->getPixelsPadding(); bool hasOwnText = !span->getText().empty() && NULL != span->getFontStyleConfig().Font; + Float spanLineHeight = 0; + if ( span->isInlineBlock() ) { + auto& fontStyle = span->getFontStyleConfig(); + if ( fontStyle.Font ) + spanLineHeight = + (Float)fontStyle.Font->getFontHeight( fontStyle.CharacterSize ); + } + if ( hasOwnText ) { String::View spanText = span->getText().view(); @@ -969,7 +979,8 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri spanText = spanText.substr( 1 ); if ( !spanText.empty() ) { - richText.addSpan( spanText, span->getFontStyleConfig(), margin, padding ); + richText.addSpan( spanText, span->getFontStyleConfig(), margin, padding, + spanLineHeight, span->isInlineBlock() ); if ( shouldCollapse ) lastSpanEndsWithSpace = spanText.back() == ' '; } @@ -994,68 +1005,80 @@ void UIRichText::rebuildRichText( UILayout* container, RichText& richText, Intri Rectf padRightOnly( 0, 0, padding.Right, padding.Bottom ); richText.addSpan( "", span->getFontStyleConfig(), rightOnly, padRightOnly ); } - } else if ( widget->isType( UI_TYPE_BR ) ) { - richText.addSpan( "\n", - widget->asType()->getRichText().getFontStyleConfig() ); - lastSpanEndsWithSpace = false; - } else { - Rectf margin = widget->getLayoutPixelsMargin(); - bool isBlock = widget->getLayoutWidthPolicy() == SizePolicy::MatchParent; - if ( widget->isType( UI_TYPE_HTML_WIDGET ) ) { - CSSDisplay display = widget->asType()->getDisplay(); - if ( display == CSSDisplay::Inline || display == CSSDisplay::InlineBlock ) - isBlock = false; - else if ( display == CSSDisplay::ListItem ) - isBlock = true; - } - if ( mode == IntrinsicMode::None ) { - if ( isBlock ) { - if ( container->getPixelsSize().getWidth() != 0 ) { - Float maxSize = eemax( 0.f, container->getPixelsSize().getWidth() - - container->getPixelsContentOffset().Left - - container->getPixelsContentOffset().Right - - margin.Left - margin.Right ); - widget->setPixelsSize( eemax( 0.f, maxSize ), - widget->getPixelsSize().getHeight() ); - } else { + handled = true; + } + + if ( !handled ) { + if ( widget->isType( UI_TYPE_BR ) ) { + richText.addSpan( + "\n", widget->asType()->getRichText().getFontStyleConfig() ); + lastSpanEndsWithSpace = false; + } else { + Rectf margin = widget->getLayoutPixelsMargin(); + bool isBlock = widget->getLayoutWidthPolicy() == SizePolicy::MatchParent; + if ( widget->isType( UI_TYPE_HTML_WIDGET ) ) { + CSSDisplay display = widget->asType()->getDisplay(); + if ( display == CSSDisplay::Inline || display == CSSDisplay::InlineBlock ) + isBlock = false; + else if ( display == CSSDisplay::ListItem ) + isBlock = true; + } + + if ( mode == IntrinsicMode::None ) { + if ( isBlock ) { + if ( container->getPixelsSize().getWidth() != 0 ) { + Float maxSize = + eemax( 0.f, container->getPixelsSize().getWidth() - + container->getPixelsContentOffset().Left - + container->getPixelsContentOffset().Right - + margin.Left - margin.Right ); + widget->setPixelsSize( eemax( 0.f, maxSize ), + widget->getPixelsSize().getHeight() ); + } else { + container->onAutoSizeChild( widget ); + } + } else if ( widget->getLayoutWidthPolicy() == SizePolicy::WrapContent || + widget->getLayoutHeightPolicy() == SizePolicy::WrapContent ) { container->onAutoSizeChild( widget ); } - } else if ( widget->getLayoutWidthPolicy() == SizePolicy::WrapContent || - widget->getLayoutHeightPolicy() == SizePolicy::WrapContent ) { - container->onAutoSizeChild( widget ); + + if ( widget->isType( UI_TYPE_TEXTSPAN ) && + widget->asType()->isInlineBlock() && + widget->getPixelsSize().getWidth() == 0 ) + widget->asType()->updateLayout(); } - } - Sizef size; - if ( mode == IntrinsicMode::Min ) { - size = Sizef( widget->getMinIntrinsicWidth(), 0 ); - } else if ( mode == IntrinsicMode::Max ) { - size = Sizef( widget->getMaxIntrinsicWidth(), 0 ); - } else { - size = widget->getPixelsSize(); - } + Sizef size; + if ( mode == IntrinsicMode::Min ) { + size = Sizef( widget->getMinIntrinsicWidth(), 0 ); + } else if ( mode == IntrinsicMode::Max ) { + size = Sizef( widget->getMaxIntrinsicWidth(), 0 ); + } else { + size = widget->getPixelsSize(); + } - Float w = size.getWidth(); - if ( isBlock && mode == IntrinsicMode::None && - container->getPixelsSize().getWidth() != 0 ) { - w = eemax( 0.f, container->getPixelsSize().getWidth() - - container->getPixelsContentOffset().Left - - container->getPixelsContentOffset().Right - margin.Left - - margin.Right ); - } + Float w = size.getWidth(); + if ( isBlock && mode == IntrinsicMode::None && + container->getPixelsSize().getWidth() != 0 ) { + w = eemax( 0.f, container->getPixelsSize().getWidth() - + container->getPixelsContentOffset().Left - + container->getPixelsContentOffset().Right - margin.Left - + margin.Right ); + } - CSSFloat floatType = CSSFloat::None; - CSSClear clearType = CSSClear::None; - if ( widget->isType( UI_TYPE_HTML_WIDGET ) ) { - floatType = widget->asType()->getCSSFloat(); - clearType = widget->asType()->getCSSClear(); - } + CSSFloat floatType = CSSFloat::None; + CSSClear clearType = CSSClear::None; + if ( widget->isType( UI_TYPE_HTML_WIDGET ) ) { + floatType = widget->asType()->getCSSFloat(); + clearType = widget->asType()->getCSSClear(); + } - richText.addCustomSize( Sizef( w + margin.Left + margin.Right, - size.getHeight() + margin.Top + margin.Bottom ), - isBlock, floatType, clearType ); - lastSpanEndsWithSpace = false; + richText.addCustomSize( Sizef( w + margin.Left + margin.Right, + size.getHeight() + margin.Top + margin.Bottom ), + isBlock, floatType, clearType ); + lastSpanEndsWithSpace = false; + } } }; @@ -1289,5 +1312,4 @@ void UIRichText::selCurEnd( const Int64& end ) { invalidateDraw(); } } - }} // namespace EE::UI diff --git a/src/eepp/ui/uitextspan.cpp b/src/eepp/ui/uitextspan.cpp index f3ea7e5f9..a23890d06 100644 --- a/src/eepp/ui/uitextspan.cpp +++ b/src/eepp/ui/uitextspan.cpp @@ -62,16 +62,29 @@ bool UITextSpan::isType( const Uint32& type ) const { } bool UITextSpan::isMergeable() const { - return mDisplay == CSSDisplay::Inline; + if ( mDisplay == CSSDisplay::Inline ) + return true; + if ( mDisplay == CSSDisplay::InlineBlock ) + return !getText().empty() && NULL != getFontStyleConfig().Font; + return false; +} + +bool UITextSpan::isInlineBlock() const { + return mDisplay == CSSDisplay::InlineBlock; } void UITextSpan::drawBorder() { if ( ( mFlags & UI_BORDER ) && NULL != mBorder ) { mBorder->setAlpha( mAlpha ); - mBorder->draw( { std::trunc( mScreenPos.x - mPaddingPx.Left ), - std::trunc( mScreenPos.y - mPaddingPx.Top ) }, - { std::floor( mSize.x + mPaddingPx.Left + mPaddingPx.Right ), - std::floor( mSize.y + mPaddingPx.Top + mPaddingPx.Bottom ) } ); + if ( isInlineBlock() ) { + mBorder->draw( Vector2f( std::trunc( mScreenPos.x ), std::trunc( mScreenPos.y ) ), + Sizef( std::floor( mSize.x ), std::floor( mSize.y ) ) ); + } else { + mBorder->draw( { std::trunc( mScreenPos.x - mPaddingPx.Left ), + std::trunc( mScreenPos.y - mPaddingPx.Top ) }, + { std::floor( mSize.x + mPaddingPx.Left + mPaddingPx.Right ), + std::floor( mSize.y + mPaddingPx.Top + mPaddingPx.Bottom ) } ); + } } } diff --git a/src/tests/unit_tests/uihtml_tests.cpp b/src/tests/unit_tests/uihtml_tests.cpp index 45ee02fd3..cfae2bdf3 100644 --- a/src/tests/unit_tests/uihtml_tests.cpp +++ b/src/tests/unit_tests/uihtml_tests.cpp @@ -209,6 +209,86 @@ UTEST( UIRichText, spanPadding ) { Engine::destroySingleton(); } +UTEST( UIRichText, anchorPadding ) { + auto win = Engine::instance()->createWindow( + WindowSettings( 800, 600, "Anchor Span Padding Test", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + FontTrueType* font = FontTrueType::New( "NotoSans-Regular" ); + font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" ); + ASSERT_TRUE( font != nullptr && font->loaded() ); + FontFamily::loadFromRegular( font ); + + UI::UISceneNode* sceneNode = UI::UISceneNode::New(); + SceneManager::instance()->add( sceneNode ); + UI::UIThemeManager* themeManager = sceneNode->getUIThemeManager(); + themeManager->setDefaultFont( font ); + sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" ); + std::string html; + FileSystem::fileGet( "assets/html/anchor_padding.html", html ); + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + win->setClearColor( Color::White ); + + win->getInput()->update(); + SceneManager::instance()->update(); + + win->clear(); + SceneManager::instance()->draw(); + win->display(); + + compareImages( utest_state, utest_result, win, "eepp-ui-anchor-padding", "html" ); + + auto anchors = sceneNode->getRoot()->findAllByTag( "a" ); + ASSERT_GE( anchors.size(), (size_t)1 ); + auto downloadLink = anchors[0]->asType(); + EXPECT_NEAR( downloadLink->getPixelsSize().getWidth(), 81.f, 3.f ); + EXPECT_NEAR( downloadLink->getPixelsSize().getHeight(), 28.f, 3.f ); + + Engine::destroySingleton(); +} + +UTEST( UIRichText, anchorPaddingLineHeight ) { + auto win = Engine::instance()->createWindow( + WindowSettings( 800, 600, "Anchor Padding LineHeight Test", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + FontTrueType* font = FontTrueType::New( "NotoSans-Regular" ); + font->loadFromFile( "../assets/fonts/NotoSans-Regular.ttf" ); + ASSERT_TRUE( font != nullptr && font->loaded() ); + FontFamily::loadFromRegular( font ); + + UI::UISceneNode* sceneNode = UI::UISceneNode::New(); + SceneManager::instance()->add( sceneNode ); + UI::UIThemeManager* themeManager = sceneNode->getUIThemeManager(); + themeManager->setDefaultFont( font ); + sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" ); + std::string html; + FileSystem::fileGet( "assets/html/anchor_padding_lineheight.html", html ); + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + win->setClearColor( Color::White ); + + win->getInput()->update(); + SceneManager::instance()->update(); + + win->clear(); + SceneManager::instance()->draw(); + win->display(); + + compareImages( utest_state, utest_result, win, "eepp-ui-anchor-padding-lineheight", "html" ); + + auto anchors = sceneNode->getRoot()->findAllByTag( "a" ); + ASSERT_GE( anchors.size(), (size_t)1 ); + auto downloadLink = anchors[0]->asType(); + EXPECT_NEAR( downloadLink->getPixelsSize().getWidth(), 81.f, 3.f ); + EXPECT_NEAR( downloadLink->getPixelsSize().getHeight(), 28.f, 3.f ); + + Engine::destroySingleton(); +} + UTEST( UIHTMLTable, complexLayout3 ) { auto win = Engine::instance()->createWindow( WindowSettings( 1024, 650, "HTML Tables Test 3", WindowStyle::Default, @@ -1391,3 +1471,47 @@ UTEST( UIBackground, imageAtlasPositioningPixelDensity2 ) { Engine::destroySingleton(); EE::Graphics::PixelDensity::setPixelDensity( 1.0f ); } + +UTEST( UIBackground, InlineBlockImageSpans ) { + auto win = Engine::instance()->createWindow( + WindowSettings( 1024, 653, "inline-block image spans", WindowStyle::Default, + WindowBackend::Default, 32, {}, 1, false, true ), + ContextSettings( false, 0, 0, GLv_default, true, false ) ); + FileSystem::changeWorkingDirectory( Sys::getProcessPath() ); + + UI::UISceneNode* sceneNode = init_test_inline_block(); + + sceneNode->setURI( "file://" + Sys::getProcessPath() + "assets/html/" ); + + std::string html; + FileSystem::fileGet( "assets/html/inline_block.html", html ); + sceneNode->loadLayoutFromString( HTMLFormatter::HTMLtoXML( html ) ); + win->setClearColor( Color::White ); + + win->getInput()->update(); + SceneManager::instance()->update(); + + win->clear(); + SceneManager::instance()->draw(); + win->display(); + + auto anchors = sceneNode->getRoot()->findAllByTag( "a" ); + auto spans = sceneNode->getRoot()->querySelectorAll( "a > span" ); + + EXPECT_GT( anchors.size(), (size_t)0 ); + EXPECT_GT( spans.size(), (size_t)0 ); + + for ( auto anchor : anchors ) { + EXPECT_GT( anchor->getPixelsSize().getWidth(), 0 ); + EXPECT_GT( anchor->getPixelsSize().getHeight(), 0 ); + } + + for ( auto span : spans ) { + EXPECT_GT( span->getPixelsSize().getWidth(), 0 ); + EXPECT_GT( span->getPixelsSize().getHeight(), 0 ); + } + + compareImages( utest_state, utest_result, win, "eepp-ui-inline-block-image-spans", "html", 4 ); + + Engine::destroySingleton(); +}