From 8f0ce19765daa3dfd8aaae4f118c9fa8d1da5735 Mon Sep 17 00:00:00 2001 From: LoukasPap Date: Thu, 18 Jun 2026 15:16:17 +0300 Subject: [PATCH] Fix floating toolbar positioning to ensure it fits within the viewport --- src/display/editor/toolbar.js | 43 ++++++- test/integration/floating_toolbar_spec.mjs | 137 +++++++++++++++++++++ test/pdfs/.gitignore | 1 + test/pdfs/floating-toolbar-positioning.pdf | Bin 0 -> 16350 bytes test/test_manifest.json | 7 ++ 5 files changed, 186 insertions(+), 2 deletions(-) create mode 100644 test/integration/floating_toolbar_spec.mjs create mode 100644 test/pdfs/floating-toolbar-positioning.pdf diff --git a/src/display/editor/toolbar.js b/src/display/editor/toolbar.js index 563ad965e01e2..36e286210e70b 100644 --- a/src/display/editor/toolbar.js +++ b/src/display/editor/toolbar.js @@ -341,8 +341,47 @@ class FloatingToolbar { const [x, y] = this.#getLastPoint(boxes, isLTR); const { style } = (this.#toolbar ||= this.#render()); parent.append(this.#toolbar); - style.insetInlineEnd = `${100 * x}%`; - style.top = `calc(${100 * y}% + var(--editor-toolbar-vert-offset))`; + + const parentRect = parent.getBoundingClientRect(); + const parentHeight = parentRect.height; + const parentWidth = parentRect.width; + + const toolbarRect = this.#toolbar.getBoundingClientRect(); + const toolbarHeight = toolbarRect.height; + const toolbarWidth = toolbarRect.width; + const offset = parseFloat( + getComputedStyle(parent).getPropertyValue("--editor-toolbar-vert-offset") + ); + + // Y-axis positioning: check if toolbar fits below the text + const textBottomY = parentHeight * y; + const toolbarBottomY = textBottomY + offset + toolbarHeight; + + if (toolbarBottomY <= parentHeight) { + // Toolbar fits below: position toolbar below the text + style.top = `calc(${100 * y}% + var(--editor-toolbar-vert-offset))`; + } else { + // Toolbar doesn't fit below: position toolbar above the text. + // The first box will always have the lowest Y because reading + // is from top to bottom and boxes[0] is the topmost text. + const lowestY = boxes[0].y * 100; + style.top = `max(0px, calc(${lowestY}% - ${toolbarHeight}px - var(--editor-toolbar-vert-offset)))`; + } + + // X-axis positioning: check if toolbar fits on the right side + const textRightX = parentWidth * (1 - x); + const toolbarLeftX = textRightX - toolbarWidth; + + if (toolbarLeftX >= 0) { + // Toolbar fits on the right: align right with the text + style.insetInlineEnd = `${100 * x}%`; + style.insetInlineStart = ""; + } else { + // Toolbar doesn't fit on the right: align left with the start of the text + const minX = boxes[0].x * 100; + style.insetInlineStart = `${minX}%`; + style.insetInlineEnd = ""; + } } hide() { diff --git a/test/integration/floating_toolbar_spec.mjs b/test/integration/floating_toolbar_spec.mjs new file mode 100644 index 0000000000000..c20822febc0b5 --- /dev/null +++ b/test/integration/floating_toolbar_spec.mjs @@ -0,0 +1,137 @@ +/* Copyright 2026 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +import { + closePages, + getRect, + getSpanRectFromText, + loadAndWait, +} from "./test_utils.mjs"; + +async function selectTextAndShowToolbar(page, pageNumber, text) { + const rect = await getSpanRectFromText(page, pageNumber, text); + const x = rect.x + rect.width * 0.5; + const y = rect.y + rect.height * 0.5; + await page.mouse.click(x, y, { count: 2, delay: 400 }); + await page.waitForSelector(".editToolbar", { timeout: 2000 }); + return rect; +} + +function expectToolbarWithinPage(browserName, toolbarBounds, pageBounds) { + expect(toolbarBounds.x) + .withContext( + `In ${browserName}: Toolbar should not be cut off at the left of the page` + ) + .toBeGreaterThanOrEqual(pageBounds.x); + + expect(toolbarBounds.x + toolbarBounds.width) + .withContext( + `In ${browserName}: Toolbar should not be cut off at the right of the page` + ) + .toBeLessThanOrEqual(pageBounds.x + pageBounds.width); + + expect(toolbarBounds.y) + .withContext( + `In ${browserName}: Toolbar should not be cut off at the top of the page` + ) + .toBeGreaterThanOrEqual(pageBounds.y); + + expect(toolbarBounds.y + toolbarBounds.height) + .withContext( + `In ${browserName}: Toolbar should not be cut off at the bottom of the page` + ) + .toBeLessThanOrEqual(pageBounds.y + pageBounds.height); +} + +function expectToolbarBelowText(browserName, toolbarBounds, textRect) { + expect(toolbarBounds.y) + .withContext(`In ${browserName}: Toolbar should be shown below the text`) + .toBeGreaterThanOrEqual(textRect.y + textRect.height); +} + +function expectToolbarAboveText(browserName, toolbarBounds, textRect) { + expect(toolbarBounds.y + toolbarBounds.height) + .withContext(`In ${browserName}: Toolbar should be shown above the text`) + .toBeLessThanOrEqual(textRect.y); +} + +describe("Floating Toolbar Positioning", () => { + describe("Toolbar positioning with text selection", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait( + "floating-toolbar-positioning.pdf", + ".textLayer" + ); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("must show the toolbar below the text and inside the page when selecting BR", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + const textRect = await selectTextAndShowToolbar(page, 1, "BR"); + const toolbarBounds = await getRect(page, ".editToolbar"); + const pageBounds = await getRect(page, '.page[data-page-number="1"]'); + + expectToolbarWithinPage(browserName, toolbarBounds, pageBounds); + expectToolbarBelowText(browserName, toolbarBounds, textRect); + }) + ); + }); + + it("must show the toolbar below the text and inside the page when selecting BL", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + const textRect = await selectTextAndShowToolbar(page, 1, "BL"); + const toolbarBounds = await getRect(page, ".editToolbar"); + const pageBounds = await getRect(page, '.page[data-page-number="1"]'); + + expectToolbarWithinPage(browserName, toolbarBounds, pageBounds); + expectToolbarBelowText(browserName, toolbarBounds, textRect); + }) + ); + }); + + it("must show the toolbar above the text and inside the page when selecting TR", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + const textRect = await selectTextAndShowToolbar(page, 1, "TR"); + const toolbarBounds = await getRect(page, ".editToolbar"); + const pageBounds = await getRect(page, '.page[data-page-number="1"]'); + + expectToolbarWithinPage(browserName, toolbarBounds, pageBounds); + expectToolbarAboveText(browserName, toolbarBounds, textRect); + }) + ); + }); + + it("must show the toolbar above the text and inside the page when selecting TL", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + const textRect = await selectTextAndShowToolbar(page, 1, "TL"); + const toolbarBounds = await getRect(page, ".editToolbar"); + const pageBounds = await getRect(page, '.page[data-page-number="1"]'); + + expectToolbarWithinPage(browserName, toolbarBounds, pageBounds); + expectToolbarAboveText(browserName, toolbarBounds, textRect); + }) + ); + }); + }); +}); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index b617ab60badf8..196c3b1b711ff 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -547,6 +547,7 @@ !issue11549_reduced.pdf !issue8097_reduced.pdf !issue15262.pdf +!floating-toolbar-positioning.pdf !issue17904.pdf !bug1743245.pdf !quadpoints.pdf diff --git a/test/pdfs/floating-toolbar-positioning.pdf b/test/pdfs/floating-toolbar-positioning.pdf new file mode 100644 index 0000000000000000000000000000000000000000..d7ae60da7fa9dabb746dafcdc8de92ec6cd350d5 GIT binary patch literal 16350 zcmeHu2UL??vo02-NEeVQp^6}+5{iJ*J19ju5+Fe6hTcUG1nD5XN)eSJMG!$yIwFGf zA|2^cqzVf64Ss%~g>%pO&${>Ab=UesLiRiF%--|N-g{;znc=)DEz1Yx7bGR-d3}$lk~ocINg#AGjvk3;wk8%G^{9fNj7W3OV- z=4dS1&J-8`c{qZ&I4LpO&g@VT9N`y@4mANw**n_-1A>(>W{&zGIIcTG(9d!>(jRgF z3}7Iqzi_Cd9qpa5z_5U#vi5dPxHl;1I|f-82>O>UU>P?jISnV26B=NU(||%iz!RrF z(1wD5C(s7KUB%j)YM`C;!GKM{nrJsCu!1dMG|BI8Qs3Vca7qGpFvFlE?cMYNQ3zlR z7)VeUW(Wd6**O8~If4-1`^VZlI{;%IPJ|rp0;~bn#G>pR9dIyA-N8~CU}>}q#uTkC zCkgbci865nSiaB7cc_ja$j?^6b^OCBvKSjDG!`st10a(|o7$V9!OCbm3nxnuQb-Ud zDtGh4iOW9mRk zTLCj96zC+%>wm{A*qeNDzCAQ8*}i6EdBg--tM}6I(q&)YsNF5|blpLb3su4Hlb-k!fdygh;)c3l^oTL}IXUsLZ9D>!gLzHh(CFEX- zA{k)d*o7REU!4IF^zw$cu@u~>=AUwnY#yP%yv%rw``omSicZ4uwYv^wI?2yYPKq%V zQhrKwycAT~LD*N)J)yEIDd~|7Dx0yR1@#&adrDU~cTgo(gWL(#$Yz3F(;d-4A zJWcrE#ijR2!4$FiD7HT3$E_uS@+DliiKIDWY~FIj5gKvEn6zhypU#gaN<-+2%W+c6 z^KkXiGAd+H)6iJQFb`*BczARUp`~QyugNqHnM++XO@ESU@~PAhU*{o>brxw$q_bXe zxgI}ZUU$f|er)RX+u_6xagZ4e~)P#vTZ%v`RziV`90wa7Gxzz{n!j9k(MDFXa;rxWL+wA@kOzJ!OL=a z=1aFjLcX;5U!5gmrdfYSyEvYiWwQK?UYb)+ywxhbwLeX~v5+5OwBhdcP-_78iH#e3 z%Ywm4pyPQ`vwnL_!A$1$b=R_BZbItr7TueYCh(udMoOnK}q-7Av1dtkgU zF^l3VU%M9IrXxi5I0N+}=GKkz=!tU>Q4LYhdgwzzSx--o!Y$^T`97&e0U;8D-~$ z;|JW<;eOS$*RsRl_7O-x5J&iVoJ$4?9cqe`!ePK-0Ek$S@F7tf zByiaHS#2Eq57pGLW@s#62<{(-00MwI+5!WZ+MQbh@YZNtL~wR+utD46oa`@-2iNnD zj{dg)KPnvhUsaSX8vKjx0pDcd2!g}F3P2RXm`d1L*q}iWumsNfP9R~p@Lx0G1mc4U z!GK`pAdkjaSbo1H;bNhUF$3J001^rWBDe1sP$a;J!>|3F69Je==PxOk00eNJXxr<6 z$RUs9p_AqlfItoh0Gz!L3^y#`t8rZb9uy4&2^`w=XR95?hhNbC9YVm8DqvN6tS!n0 zY>EOwf06nl_#P!CCGnfo9|7`kN&Hwbnn2_|wD+NLWnsT8a@ju@xi-d5!p;%%_oDx8 zr9clOEm#(Vb##)lL}77rqm25eeQ0A~KO6?gU%>qlkALm?mj&>TUjM%?fS=>6gtL>S zJ(k-b3=^s)&JrVWy*Q71SmrUS6F z+1Ju$r?EVAiTE`5>WVyk>O4HBkKN{Z%ciV#En4U8xz}XHWgI%BC%AOT;^o_d`S8?1 z*IRgrilVexuS<%v({f}|#69L9PiqD#PB5X}SFxg3NzQUN`fIyg$p9C7$wmRM`vwvL zb)3z6RK<16<5QWJF2UEi-^{(f`1XUSPYppS-R?K+^U;8UO3w#NiEq7R_-9hm)6>5C z2Ru|-atco7kD8~tY#6N;PuuZ8#PnI43naBFe6`9O62Z{~ZZXO`{!!zMk_hDvC#;`WFfPSGs;K=-_)iAup^SHoXLcq znLo`g^z)IuN#=8BM0fBU3!(Qm@k=E8QcW^HwnAvz|4b5Ir~?^skj|Xeb3rI;r_FE$ z&w@z)-rSeA9X)2r#L9udsOu9;0n*^NceBl*LOo!pkVMx0eDBNxfjf;j`s{2%`#|w@ z@Z;IABxSXT#K$U^n2{n}A_l67ig8sR7IJ*%FK(l!C%r$gkMV;eW&2O9%sTgRYJ1ck zQ}bXw+KN9PNB(0g3POb;e|?mJf&gC#NB-(IK8X@JF*ZByljy>5*XDOot zlfTL)qsM`!XOnr8%^wnUa_;GKyttFn26Yl4<>)r78LzxI!pD}X;*qIfhr$yfqoH87 z$^z5#<$FV#3Pb&fuo#3wazA3_nO!mhW2Gd0@Rt8dWLzqus4zLMYM>VGyZ_a5e@T+U z5APM#Nh-H(ash$ugXZ&Z@usOw4x> z4?!DP`zm+VIGM1p64gb=dNuP006)K*n9@FahB?oUa z0*_GX)*wG2`GL6mZs3$s`L~lie6PaoeMR?9RUOzquiWIRbjcC5)(td-6K1do6)n|R zr^}@pXt#Fw1WZ8=BKK3?C%k#(?_+qJ?OmC~8IniAsI*u!S{`a;CK z>jZHq(O82gr~L6t9K>}eg!pOQR+t}98wEVJ)^=vT*cL!bJbe?%*EKiWAbA=Mf05doIN#4k1w7(O*YRjv}UyU)a~n6 zDK~~Rw<71A1E;%0bRI>{I-;}@jizgVy@6Qv8Zr1RqTdQHeCB7VQybsk)(Zk|@1$R9 zaUDC6xil=Z#Ww~E2pHqMyIw^KrONl`A2{s~Az_t&y;86AMfB;)uFI>m3gf}|#1)cc z`)QifuAo}yD7XSsc(zSfjcH(#%(yoy)-jpfT~kk7i`UdPLon$qpG%Gu>^*Ae&T zjYp4KZMafJ5|tZF-v^(GTHQ%tAU+lQeyW1ylE?JZG1s~Ameo!_zi)e6%Y!3huET|+ zq-6Rs?k5r-<mIv1eUl0Ay8@O`>3xe$Mhd=X zSI$;d@X;;vlC)|1`x)>fPMC;IzTBd(W6WJ9-+50}n~(pYL}Tlb{3X+4PaNIK81?%k z?{ylsL>ZMx;1!LtexZWu1$i?tGp~;Y_JDeG;onk{D z({&?EYuc#&-xVLPhjA=k?eiNs$-bU%Jh8FWW^@bgr8!ABO8$U`$>PE|;Yl!&$h+Lr z*qahYZPVwjjA!T_!_E(cnz)3$aP)XnEFELZoRDkcedFpf zvkVn|JgLtv1&ye@+_P7CjLAKH$%|;+Oo){Zl>P1|T>A0(VbrDFcAH)vvu8?AVyi;f zE9we%?tG5b|w`DRMQV+m7# zOrZ#Uqk^$@jkr+YIMtn3c}$&@HqQJFVPNS{R;>%F%NCP6sjHraH-td&b=2i!GL7G6&&DD7mICBL-OikVuknlPz|H_Vz7h)Cm2nl?$Cb+kIa zq}Q{;AI&|Kh}V8C`2#Eb)%g{Z{yg)#ro@c8keI}e=h${BCA^QlMVk#=O5!zRQjIZ? zQPBG!7Qq>(2@8F2$HP3_#r#(I?))6L$<0lnPnz(F)bI-zD|H5$J3rCC8j29)6bx|m z;{#P)54`j$;lUf#{5}T#vKQNl;Z#-xQsSgbl_@FRgpYN`?lrWG<|G<=F9oJ&>tEL- zVT+NYx!>_g#<|j!PS7)AX@foZYKM1lc)b;+F({$lrYC&D3f|j4ZRJ=d7NoUEM{CeZ zBPtX!eEivaqc>6o#$5388^viX^z#89H$y3A*CUkfRa%7K@9jYCoS*0m=1bwVLe6!c zBIZDU^$&}&Q^^|+@qg1Xl|-n$K~hXgGbWU^J=(+)!N^s(mLq0`5@AF>9< z8&*6UE7*ORPO(`8SseWB8-h?RO#Y?b!aLuVBgNZnG#x@{ zXn0*$@qCAbZa4tTX4?{$Ojz;h*vOM_9?r(P3LfaWMkW};)8mLpdv+2$ ze!eeAl~;DfoR@j=id*yt3k*g@*dt;2TlsW@kMnir9ACeWthtsIk9y+G=!t->Ux{BI zP%Y^~EWrmkFRRhVBR?Wbbb?}*CwHjzW6=0&dILIzLY9+Dxf>rG=pDLjE0^Z|IGQI+%gXTTCh3-ka@#EV8yRz(toVCOy7qxOYxFuL z$yXoT8|pM<{*cbENwZfOPOm-#BUBE(lDE?Gk>~Vg`u&5p9X#T%6I5R-?+|`MG@URt zbpA%!CTT+>$9S!fXOzB6hm2-K4=w2FgYwi6R>C81+TP%mZJ&rZ!x=uoT~4i3%$vB< zM$E}N!T*+T$2Jsh_)!4>e2~6|Dnl$8ACf#nlhp* zm_zCHZh>p3><%M3z*Z~vn!Z7G%g0r}^AEVI$=3Q4?(bX_Xh{oSBfV07t%S6E9xPR| zmgeVqOLJw|+F)I_Ef7M88A0x;`n{M@zmMOUv2ko?+0$DTlLyRU|y- zuZ0a#M3}ord8S>+GtUe#>NZ~Y#P@9Dtv2=^_@-JP+uqER!yKA z(q`veWw4$mo-7-8&MCggF1KM&Pp<8oOKN6VGYt!v37;XG+RN0?Z#&88VO-%6mhN~( zMf@$|nEn1vZuQhwyL)qSZtRq0cUCyNihDCkZFFMW;N}j)=WwYo_vY74yy29tqtNQ& zF=!cdg|7O-ijYG$e)B8Yg@M}4=3@JL{N_a4MkE*k|bt{=+xLrnX3 ztFtP^SLPnojt^XzWgc1l9L_&W(@bVDI^k^O@IkRisJy#+igq{8l+hOJG#ZI@`Vi?_ zcIk6?K{z}-xj&c5)_Og>@0m7}exJe3vi#OdN&N;6HlwjvHwL?#2xK;spl$WYwTP0w zqVggUo}zV;zThX?%+7^1jS>~8`kVF7rvk%xwV4d{<6ZN0qg)5{wVj!C2g@H!wOFm- zS@#!jeb!3ltqY1T?+@=O8A@h*)^oMbms@Fp9-E&nV+0!Sh2M9iRNU;dE zgt-(?@kcW3xU=)V?rBubYxm_lII+f(c?xvkF_Gkb@jxwCWNVnIFQ{tV=v!@b=0^VTT>n~Meo`a^n2 z+81w_BZaL~8hTwE_4OBN^3qsuer zU3vFZ-`(z5>o7j~Cgb?h=wsqm6Xb~no}1`v&0D!~3_UOvy}K7h8go?NOZsdb|I$C( zVX${^@3P$f#$Ilrpb@CMl$;Pbaqbm!vLnLx{gTPpXUftInzQ29Zcy_Ou|6cb?Rk2S z_Nr9Uh1eG-ultc$zwC|ceWHyzgFj62x?LH|7h^EhQ*h&ThHjxNa+vEsHnXaB-9 zE3t>*VNa?Y!!@ZeZ?(Hg2^qU=if2jZm2H&594oizS*;g5x)1a-x|7nz^)6K-i>npO z^xBP^bmJbpd-h4i*}!6h7V{`7tZIw+iz>^PCuc3nJ4Qu?t1wlM78l4HwCt-v-U|!K z59ZrZv_vy85uk0FD~WP%78l~c1R)>d4f@$Xa0(JSE1}=Bx4cVAdZ3TM^6h=;sjn3C zW9Ida-{PleyA~#*r-4^Us&kdf}8FQP+G~C~Q zQq|Hk-f+6ZYYV^UE0N|@^$vq(J=PvwY00ld=}62o5G~az%H_=c3@sc2jqep*ySCm$ zZXVye*+}qc=i|ezQ16D`5r;526M0MOQ3Po{gF_~tmeVJ_hv;B*&U1!*rOZbkKhC_^ z0JT>$RHU+H_6*;6ApfHtI8+RxAe=_nMoCB4{fY9Y8o#tSfbF z5QXVCZ7VC=Pz;fM`ta%;TRm0K&t`Yvf7d>8#EmrpPo=DqL!k{*3}ZUn3IZt^%% z{|@z|JaqXph*+S3B=0UZ@hVrGX0&zr3yTG-r1GszTifAh_q-5N3oK~f&G&=-U8Vaq zedVL0<$dDjYu`*X)J%I=*5d(GZd)mu=2zz z(OltVmF%y0%oPR)v@TKhcXmq@yiibeWxn8t=9R;pNBnujHXq8@`xzWuM zw7xpvyY}^+r_a9GGRNWpW5-&B@XFqO(Hr}h-S$_t*Qt}vb~HM!8HkSMz*h^?Sad|k zV%FCV2G67u7*4l+-DX{S>A`#JJ(qFaIjE0W%$r=eKC@w{;|{AyIEE$G1|cwJvKzg>9aZt)dDnxPchD zKA@>(QTC)^U8^e8MS!WyyU}u3ai{<7d|F}HL3 zBJG1Ed@Z8)rd9D;vx7)!wF}Im|nxIXY ztr>RHJt}^dY@O5^UGxIIWeU~*JR(bzs=H6 zRFs*d(CV6(hCBdWW>7p?3KdA3u*)@#$_qilSn?TR=J~@dcLFpy-=wrO5lcA{r$)Az zccSK%d8o`p86vsq+5;2F?o3#6&_X#Jwij+n=MEzDUTxT96iai%nwEJ4c@s&c+2)MyBeSyyF8A`J4qG8BPc^|EgB`E;!K)w z5aTEz5ZHyn*CNjgtlgm2Qp{I?&d-xHiQO)2&Uw|ySJqX^o~V$WxKG@k9UIA@p?(Jc zL1iN%P0=hy)Sjw1n@a9jnuN0`qn0|sdp_j?^N!s1)IA_(&T{3HwwfVsClzl_XYNbK zHN|3RpGBhs3yE?y?=%va-Vf#JelZpEh%o#?IxN8`0V_+V(}fa;e+t!6H(-X^6}_kH zUZ3Pca%co!MJBw?8{ciNB(+hMz!S0OVIm4VOMUCM<7}B)Lsqwwlo`90=`jIK$#AAo z1%1_b-7h$XyVoCRPxrQpJ!QSE4zmi?D%L!i6G)}8W+_S3zMb}K&sNX1QzLdG;W z`}f{M6L$slT4HN}qw8czf2-~lo$ws033<*;&|7gGrVq*+E3m(!TfqT=+o@# zvfItc9$iF{%Sa}!?sJ%P%?w?gCVK|XGWHtZz9k8_sk1&^z9o6`cI54o*=r2`B$F?S z@89MKA~sd1n|Mf|hVAFeSFOMIa zekGfL2qa<$H=#q7P@f-0hM*+nn}uX=aXn@2@YG!++&-5Uq4DhX1l%i^Wj(b0#h|39qNp^|$x&FDSd5goZYsS_bs$>O7|6 z7CuEcl_)cRZaU@`oe7N;0S!Zxg+x#;el??#o8ZmMoD@h_f;+>FmdVEC8>j#eV-^zD zjd4ptBsSgE@v;iL7ORLywxHDB@UCAw<9LdxC>e%*sOPR?Ld2s$aI?E?LnS|}<@hzz zEXPq316`XY%Klg1lEZJUYM87l6} zre<{aOhSsD-#$lr>LKX!+t0o>UEiKMxKQsw!+D>f_&t^SvkEbBMXiBUz9lXs!^&@T zii_f;d0W?!Z(cIbBv9Bd>JdEW7eT*}r=PCjYw4PL-(XGfQRk(}oQO1uZ%5;%M~yXi z@Dn?wl7!4ed|iaa3ibB&C1z9gJcO=kZ;tTCloX1f(DjtC^O0;V9~Th#%lSq!Ul>w( zWcga0iEoOr2l_B`_C{*eo-D#RUb|peaI*nTk{-945qaR~!)^Fz-6H1;anH+hko>f^ zqAs4t)hagXGQ-`XW0NUWX`8!+5Q~wHdpQJ)s?nbOh8wkY^SYd<-3FnL4f;Zc_7)7Y z-!5WPo(Nu?Q7Fu@^9x#$oBGOfhkn*yww$`UHE{DxklG+Fmuz9lsA+MJrO4wVv z4~C;BJ`;C@T&Q`d%6Q)?L_ckO@oOqpEiqn;Cqk+27X1`a0K+9z*8usZMZIKu_m_Y# zb}?SfOugwQQ?h0=&(B_$&wN+%m1tcIY_c=B9%mu;ZJzC$Qtt`x^Ty>&lFN6P$0^Ds z4W4|fwt^LUj4;=2G0P7&^On^3WbC-ce6DTZC+l%0H~BC)mO*tQ%Cf~WNb>!T(CFn* zZEHRI(%PP$H%2CbXM(wFPT8;7V76@*vImQ$*L8CJ%iWNr%Wh60^r|btCq*oi2|836 zluC841rAOFY@f{R`M(Nyiq_RAC4@db+eJ3SKw>vaZ9}?z4Yqb_NGvr3*=cw|E7dY1 zCQ6o>k=Q}>U#xdUbSz?^<=?ZPft0HzUYJSjHgn{f1!Kz4|uOAER zSHYh6NLZ|tJZCXmJ{Ke9i&mci0(pu|MOF6dplOPIhA)UZi+&igMylgcU>;pe5QZHc=4M6(J51c2RC7$+@#+>`XMB za+2~weh{t(3a&dGbr&W`>h|vJQHpbTQB24MM6i0P?@-rS1keYhQ;PeqI@Zlnh=Vf> z>t^qqrL5rr8rpY?Siinl?I*l(#z<<1Z$h8&Kz4_$ZiUD)Yt?&#g>t`i6-?;Mk&#oU zcIOG9#T{J`TT;kVW$E;Ly(R*Z$(B+&ynSp2tc6#es~ZHbo?1Hs}!Wzp^)CVB{R=Bu%El3Vsmh1mmr`hSy?n* zW}jKWnCw`g$5usO+I^!aqx<`G>q-so9Vg^{xxFcsyW%6|Xxb#^$+P1R&YZe;@$J#S zH~yWq1Y~OJgAjsHLl6jt)Cclg5I{>9st*EEgMbzkE`TELnO>o7OwFgfV2n}*t7*yASw=0XiJQ|2Ntd2p{i->VQngG#&%g;)J??A*1;A? z%mumG+SoaYxQVf$aJgI}KpRH}vw;p-oUFyz;QUZNK?syhoLCfVW-g*GE&G=ca3#iO z>Ez@f0tUOfy7Ifi`R%b5V5qRLFc<;@!(e;>2cM(6ofFE9&(4wkM-M*|z<|^h5Uxj* ziM_Lv7#kbtuYMhV>etSZ|4=c0Q+r!5a03kGhl6n)0d0{Vc`M>77*njhqrJIPT_H%@ z9%}|N2lCtgCW7Ed0a2XrKLVm)+`vC6@)x+DC`YPhX8Jc`2WPC!Azm|6F#3D)nVOjkoX`-J{VjR1{HxqMGo_jL?94R@Nb-l zl0aq>#@zkCBKZ^Ne|PEt1yof2_hzxR{b3mu6%lEBQ)gU8qk^=!vopp_Lb8S>Rt{{`czox4Hf$3;avW|6X1Hnz_D*ec+zpim80xAwjG@34sEu*d}Zy?#wWT3=KWCMhW_C<8$VKn38^2pQZ# z>F0Bj_Qk4aG6nizd%P41%G{9>h45l@m8|4kO~N5S-vxwwMp|?kL4OtE|w}QOV=1oWa`*Ay7MfbQ&sdiDeA!}RByP}imy*O z{8kxF3{R*(&b9i)AgN|`wN}?yij2vDmHgZe#T7z)Zpo9-*bEXP%?x6JJSk#QOUcti zoqqfq8RRGE>Ij?6=Pe)l(vHRU;)Oe0>jOuTXNSz>mOzI06Ua2pqur`$HGR5jYS>;6NOK191cn#1S|UN8msl zfkWU390Et+5I6#dz!5lr{qYB0fg^AT9DxJyBEQMP5r6JWxFD`H@RvOeg+hQi`ke-p z8~sTGu>Hn|fCGE$4;m6E(!@P*e$5Gs0*dRfhlS|hBM##KdYJhhNKja(?~WMwCJzpH yVopvOHCdnt`Y-1U`G2DuP+pF9!F{<1M}To*7$}?qu7Izzepea^6@>n!^#1}0Iw^+$ literal 0 HcmV?d00001 diff --git a/test/test_manifest.json b/test/test_manifest.json index 08b6dfdbf7ec8..417c656a05ee0 100644 --- a/test/test_manifest.json +++ b/test/test_manifest.json @@ -13501,6 +13501,13 @@ "type": "eq", "useWasm": false }, + { + "id": "floating-toolbar-positioning", + "file": "pdfs/floating-toolbar-positioning.pdf", + "md5": "bef4ff1ef3b51b1dffb3e84e60d1c900", + "rounds": 1, + "type": "eq" + }, { "id": "jbig2_bitmap_initially_unknown_size", "file": "pdfs/bitmap-initially-unknown-size.pdf",