From a7e3fb5eaca6b25ec6d630158ad75bf88617c07e Mon Sep 17 00:00:00 2001 From: danielku15 Date: Tue, 6 Oct 2026 17:31:21 +0200 Subject: [PATCH 1/6] fix(rendering): position stems of slashed beats on tab staves Slashed beats on tab staves had no reserved collision slot and answered all note Y queries with 0. With tab rhythm stems enabled this crashed the stem painting and placed stems, beams and dots at the top of the staff. The slash now reserves its area, answers the Y positions like fret numbers do, and attaches the stem at the SMuFL stem anchor like on the score and slash staves. Fixes #2888 --- .../src/rendering/glyphs/TabBeatGlyph.ts | 52 ++++++++++++++++++- .../src/rendering/glyphs/TabNoteChordGlyph.ts | 11 ++-- 2 files changed, 56 insertions(+), 7 deletions(-) diff --git a/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts b/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts index e989f634d6..2fff8e86e2 100644 --- a/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts @@ -46,9 +46,43 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase { } public override getNoteY(note: Note, requestedPosition: NoteYPosition): number { + if (this.slash) { + return this._getSlashY(requestedPosition); + } return this.noteNumbers ? this.noteNumbers.getNoteY(note, requestedPosition) : 0; } + private _getSlashY(requestedPosition: NoteYPosition): number { + const g = this.slash!; + const smufl = this.renderer.smuflMetrics; + let pos = g.y; + switch (requestedPosition) { + case NoteYPosition.Top: + pos = g.getBoundingBoxTop(); + break; + case NoteYPosition.Center: + break; + case NoteYPosition.Bottom: + pos = g.getBoundingBoxBottom(); + break; + case NoteYPosition.StemUp: + pos -= (smufl.stemUp.has(g.symbol) ? smufl.stemUp.get(g.symbol)!.bottomY : 0) * g.glyphScale; + break; + case NoteYPosition.StemDown: + pos -= (smufl.stemDown.has(g.symbol) ? smufl.stemDown.get(g.symbol)!.topY : 0) * g.glyphScale; + break; + case NoteYPosition.TopWithStem: + pos = -this.renderer.settings.notation.rhythmHeight; + pos -= TabNoteChordGlyph.calculateTremoloHeightForStem(this.renderer, this.container.beat); + break; + case NoteYPosition.BottomWithStem: + pos = this.renderer.height + this.renderer.settings.notation.rhythmHeight; + pos += TabNoteChordGlyph.calculateTremoloHeightForStem(this.renderer, this.container.beat); + break; + } + return pos; + } + public override getRestY(requestedPosition: NoteYPosition): number { const g = this.restGlyph; if (g) { @@ -71,10 +105,16 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase { } public override getLowestNoteY(requestedPosition: NoteYPosition): number { + if (this.slash) { + return this._getSlashY(requestedPosition); + } return this.noteNumbers ? this.noteNumbers.getLowestNoteY(requestedPosition) : 0; } public override getHighestNoteY(requestedPosition: NoteYPosition): number { + if (this.slash) { + return this._getSlashY(requestedPosition); + } return this.noteNumbers ? this.noteNumbers.getHighestNoteY(requestedPosition) : 0; } @@ -178,7 +218,17 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase { this.onTimeX = this.slash!.x + this.slash!.width / 2; } this.middleX = this.onTimeX; - this.stemX = this.middleX; + // slashes attach the stem like regular note heads (SMuFL stem anchor) + this.stemX = this.slash ? this.slash.x + this.slash.stemX : this.middleX; + + if (this.slash) { + // reserve the slash area so stems are not painted through it + this.renderer.collisionHelper.reserveBeatSlot( + this.container.beat, + this.slash.getBoundingBoxTop(), + this.slash.getBoundingBoxBottom() + ); + } for (const g of centeredEffectGlyphs) { g.x = this.onTimeX; diff --git a/packages/alphatab/src/rendering/glyphs/TabNoteChordGlyph.ts b/packages/alphatab/src/rendering/glyphs/TabNoteChordGlyph.ts index 43288a8959..8ad028e360 100644 --- a/packages/alphatab/src/rendering/glyphs/TabNoteChordGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/TabNoteChordGlyph.ts @@ -3,7 +3,7 @@ import { Duration } from '@coderline/alphatab/model/Duration'; import type { Note } from '@coderline/alphatab/model/Note'; import { type ICanvas, TextBaseline } from '@coderline/alphatab/platform/ICanvas'; import type { RenderingResources } from '@coderline/alphatab/RenderingResources'; -import { NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase'; +import { type BarRendererBase, NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase'; import { DeadSlappedBeatGlyph } from '@coderline/alphatab/rendering/glyphs/DeadSlappedBeatGlyph'; import { Glyph } from '@coderline/alphatab/rendering/glyphs/Glyph'; import type { NoteNumberGlyph } from '@coderline/alphatab/rendering/glyphs/NoteNumberGlyph'; @@ -87,12 +87,12 @@ export class TabNoteChordGlyph extends Glyph { break; case NoteYPosition.TopWithStem: pos = -this.renderer.settings.notation.rhythmHeight; - pos -= this.calculateTremoloHeightForStem(); + pos -= TabNoteChordGlyph.calculateTremoloHeightForStem(this.renderer, this.beat); break; case NoteYPosition.BottomWithStem: pos = this.renderer.height + this.renderer.settings.notation.rhythmHeight; - pos += this.calculateTremoloHeightForStem(); + pos += TabNoteChordGlyph.calculateTremoloHeightForStem(this.renderer, this.beat); break; } @@ -101,8 +101,7 @@ export class TabNoteChordGlyph extends Glyph { return 0; } - public calculateTremoloHeightForStem() { - const beat = this.beat; + public static calculateTremoloHeightForStem(renderer: BarRendererBase, beat: Beat) { if (!beat.isTremolo) { return 0; } @@ -110,7 +109,7 @@ export class TabNoteChordGlyph extends Glyph { return 0; } const symbol = TremoloPickingGlyph._getSymbol(beat.tremoloPicking!); - const smufl = this.renderer.smuflMetrics; + const smufl = renderer.smuflMetrics; return smufl.glyphHeights.has(symbol) ? smufl.glyphHeights.get(symbol)! : 0; } From deb1d9ed5edac9211d5841c6abbfd33f5c1ddfe1 Mon Sep 17 00:00:00 2001 From: danielku15 Date: Tue, 6 Oct 2026 17:31:21 +0200 Subject: [PATCH 2/6] test(rendering): cover slashed beats with tab rhythm stems --- .../effects-and-annotations/beat-slash.png | Bin 22585 -> 22406 bytes .../guitar-tabs/rhythm-slashed.png | Bin 0 -> 23975 bytes .../guitar-tabs/rhythm-with-beams-slashed.png | Bin 0 -> 25223 bytes .../visualTests/features/GuitarTabs.test.ts | 30 ++++++++++++++++-- 4 files changed, 27 insertions(+), 3 deletions(-) create mode 100644 packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm-slashed.png create mode 100644 packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm-with-beams-slashed.png diff --git a/packages/alphatab/test-data/visual-tests/effects-and-annotations/beat-slash.png b/packages/alphatab/test-data/visual-tests/effects-and-annotations/beat-slash.png index 2e1960543df2cde929ab36ebb99d46b44c48b233..dbdde286546a985d7af06c0fb17bbde0ef228eb4 100644 GIT binary patch delta 19835 zcma&N1yodB{4PAh&>a$zilmgFbQy$nh;)ZYBi#oCMNmMxOOTN6hM|R_k!}zMgrPgX z*9qj3PkEZg&~>T%QBOo0Pd+j^KqU$$D;2dwy_aG$4fjaToh_ZW#=eQ~c;aA2Zvf6Ao2?ticXA zGige8|EELxxKO3E3up^Qs6S6({+8~dK>>jfWZmqbr7UP^1MZa}{$R*@)UNvHJlA6d z8}7yd!>lUiu_PTd6io)J<0oeB%Curlzp8$&UT>V3HNSD7P&h znn|dSF+UI1wD*+do1@aR8O|wfW4ShhmyPlFXpv?djyWjHf~H^zcuwisP&Zm&QFJm) zgq+9beq-wz9o8&nqb2`tTKQE8mJB5N7wjHg+2A#3Py z1rvRovFOG8FQ&@$^`r~`o`xv%{5VxHbN-QD+JkFTxsLpCjhf`6 z+z&@#9${J4rZhIwwAS3d=9Axt4Cv%qy5QvDcB0x*yG^*J1vVl4oQc9y=O>$1J!NEc z7j5TW_{<%C#!8XTMhybA=&BAdMXHNz8@?R_XW93Lb;mBkvJ?3&t~qV}9Awend`Jyc z1!7{G;&%^*@lGSe9dIfk&6OLb3i9*j-Dmapt<^(6{CnZ4b7j`zPMY#hJiUxt#2qu= zeBJL}!Py$OlQ%qhztGLVMo-W@f*G@HV$!Z8n7PZag4!vPmSA=(q1XY|W?KO?<6H!d z70zP*C`)qp^(PP4jSw#yXVz@rGw-gBwbL4th~M{(ZCZFe{tL-2Y(LKp?``VR)gw z=vOf8LCjW}ZD62~G=^DR&NQn)p%vcL#bfIjh0>&{Xb_jE@x$bpE`HSO`+lqB=aoHy zHz0}GHC;AMJ?_&$?$}Hq_He>lWV~i=R8V8z@K|)9`_*PVu&-5ZI5eO{robss)9UI+ zh+or8)$-i&jFjZh75PYWg4D7Z8{tHEP{EbIrccq#fRy+plr|B1FFy(IQRssO%btPV z3AbkZ?31^hsvNzjFLSxhsh>UO9kQ=}cI_Ir4Pu!feB!ieHztwBn;hy=f3Kep2KCHG zk3Zjqqqw{7{WTkT-Z!`8=X{5l169p5zT+n=`Zm{Ge{cSu|3~3c!zAB`V*fjR5|X|5 z1JUu=4`9<-tkJ#Lv>4EO*fam=P3%WdqUbv~3cx!4Crk;)!U?ofOg2|u&3}Fu@)$=N z{2-TOd9Tj~-5f>$G$QK0qA>91j5egblYljJF4{9>JzH_wtZ#8K9B(RG+GiJn=0sab$4R30kG` zC)a#NVqA1(=KusxEp#5c<2d3uCS5({WMu3qBlLiIEMVGAExA2rWF#@yVJL}Z#+`vw z%@JxyNoD7-WwPb}628cw*w!`noNPQnMZRopj?=bl|9p>j1_~Tjv={|nq3A5P7#Y>8 z=->!9r3HdnBA5K^2tATWbE}=__~B@wMR@Or8a2l)Yd(B<-lhIjO?~Vgc^^-hGw|eM z<`x?oJ#@a(GZTHKwX~&Z*@ugSB!5zJOE$|jeACV{qc18X%Vs@CuIRgk<#g5ea%Kzngnzo z)4*~K4ZN=SpS2KW=q0?JyRq6Mxi{RSA1fT0Ns=Bs3DGUlbtk)vAHib3Hk`*Ns*E>i8s3{#eT(ehLnAkSQz9&UDX^B`$_e zVS%Je-`-}kXALZtjan|shmg`cM!$zo216j3sx0b!?)LtC(gADxe zp3r6L=qGNvTwnE23K!H3B?V&MpNNtmdJ!Q2F~1xG1Efhmd}$LKr!CB6@IQrw8_1$VUxADjxFL(4#;dw^9uNj zZZg9j7g3kqHfBG>X(Mufy2Dvp{E+s8nc#g<`f{`+TK_WPU1J?j)P~|M^lMDcisq1s z$@m><_?0~t>lp9?li;o6^zuH7FSE{r3#?V7F4J(emTK06m zt?qF|M|Byv&lK_e(OdX9uJtVuhQ0A$5we3!jWW`K_4q1nHVOoY`wmy*)x5Kgkly1~ zGlix0>(WU81-kd;)w3W|f{kUWTjt!WbZgrc8}TTFssrW9K@!ouzL{57FC>Wnr&CuA0Cba5kf4AMdj`|rOxNrwUkXf1l0hTQiHda}w_OcYGC zi8UGU7`Ouc_)pbWzyPr`v1p?a9G}E1X+CGtaRr#Y$SGs+s+sxu?dxiKR((d$mlzIM zwZ;g#@Zs~Ci>Ylk8UNzfs__g=!kYQe)_v+3hWGdBIk_41OUUHeLO%qvXHw~CU3e4M z_D3q>pTW=7V%Ej*^l?*ObvwP|M<{;0Znrg#+t6`zC7M+0{N-CXr8t3&IM!w~_9J+g z6z&DY*NB}MK7GUdV+VHADe{Z^9a-;UkGz1|)L!WwbDnoPaE5reLBPI*g*SIV`o!bt zTn4Dk3!%()W=X7V^{2dE12!{Wc@-GvKl5~qSduX~9hv$CYUxx7PIE$~mfoWuQgYKH z1HVG4$bwqbukWqwO|9NAFfyin4Kg*ConG*cZXuzE(nrseP~1jfVaX#99nWC*9j;5t zSL`f=SFd*BR34v16OhtSaWW?^=`0e|1U~o2faj7k;kR?pf1u5gjDAeY3@|YA&9v^n znvtb@E39B^NW4u)H3z49=r0#DVNEOFFAxxO>mkIC27TdTsFr7Izw>w{s5iWO^6YBi zjxr*{p2BCYrP0AKvT~SV%@>~sJ;RS@^7cxV64T48Hkk~{5IrDak5^2cs9dDYuVw`d zO~sXRY=oQ)?6vh-7{Qg5Knuq`&V#+V*CRb!_`2#t{`*7;WvA#ubhWq%?e0q1u?Yi@ zkEOjWV+snkvLnq94Sw27ZnTX2nX+fdd%iNHBsinbCS*wD?pZ~vqSqdd900RrS%7zq z{neMNxAbaiYQrQtIyxNbKKnh&ONbnmbP30vC}!55Yiq@R=Nq{VKqu&u+;;|`uYcqX zXFeZYrc3v$|I%}Qw3(Fdx%Of3i}n@q?%2_UQA?3gi=Q1&!&Z43gh>2QHP>}>oP5@O z!MBG>`hnf76N0PAZF_dgW4ur&P0aqjXd-KuGkrXQr=`&lIgmP|k|H$x&|pW=DpZJjVt0}!W;Pq%i-?=|B5R<@q&5d(7NzBt}Mk!f<-(#z+TVFJ;hYcCxwDgYh zRTvfT7sLmWdrouV%(|nwDm%5NouMKsl2frWrZ;D@gSLF47Vb1K=M0smN$K2TR_CD$`S5KJh302Dot zxOCLH?h2zx5}EK`OY{U>nIDtLB~;3@aqB+1IeSkaON_1I0O`@KvK}rml0*xDpUALE zcv#m4yb ze~~^AMpZfkS@W;p{!o0X2aBPqI^x^4D-i8wpR+K~1YhlDs`)7#L~e_DJlGsBx<`zS z?<^YUk+)@@<}u(|I9=l~IF$M9;^m29f+7qPe{nV5NMfsbQy_g%w#I&r!ec|5@HSr% zvqD_07iYBE0NGdNRFU7~Nb{twL@SGgEH5v|TmS&hM7f26vdBiZY?F)ay?m__dKebr zWwPE*mix|prPb*B$9Eh9IiSzmICUSTOZjLixf=Oag~W2i^O}FRx=F6FKu}O{iP&yE z_T3$7Vt~A0?7MkK&_;@0T-bH3;=a{=Mr9c08>)Z{HaNTTEITG~ z)vL)`Xs}(34|m#Kp~ik_5Qg!pfljJX!;McR1u3TaQB0dcWafE(J`3blEuGXSZVW@W zhyF~l2U1g3tAh&fHHWxVzMa8MkLpp6-6@T3j;8mLrg_XYCWgL5bG^Hbv@sVyeR78g4BDhp+b3Ef6CG4@{V7fO^8l$zt<(c)S;1f zXv)i;1sazoOdW`E4h6`$Nb8R_Z5k z>Qsn%&i+0_Vm#m>iZ}A}Y#K-vtw|Sp^To2a!+B7AVhQv_h@^*>%JQ9JUWc!X>(-L) zVG&UOi1wwT2Y5}T;z{nw{FEv4l24HE_39?)(EQ;Ey2}^mrxfJmtH+xYB`e^nUWg2Q z({~w`oRN!i?MoLYd%^^qO9U)pTHygfbDJHwgRv0w6tqJWwPZlKe zrR91A++k#qK?)5)rs)z_tmtR~IA!)VK4(sG+y;QWmDPp`!s~dehaYr~>RB>j^El{e zZ#s|8km+VUQuYm~>T!d57f*I#?4dFVSE|^&PbCV39W%x74~x?XaW9$XoM?Y%U5EZmKwV%9Tw7P92z5E z^{cZ|-Wn8+czJP*x-H`Jx<#WU!slpAYu0+rB`E**XYJ6 z8&13Nt4vWwI6~{RstCBbPVxGc&*O!n1=78@>)1VgQgy3sd*oPC4_|*{m~o8k{eIgr zWv|A(nF!fbXn~0PL@%BM&iUC0+GlFA9ru?!8}m&(99rBL^ZehmO3buN7cD>=ljMI| zNT=lF>|>zp&WVS$;?nOxPzASju`(u)DX+sWJF!a=i&^mWm~I{H$HuxO(PE2jZDU zH^*w5XV}@aOtUiko3sOMuJR|Hq^q|Ud(pRvGRR9O)w;NqhWr|IfX)z72rg-D3l7{L zeLI3$Ebfhhs&Gk&6LO5kMz1$@h0Pa&?<)F%>g&_na%bL&9l6vCFU7xtK8*_K#AEnk zJ?#l6i{)(jLCU@N4MVYvC~&lBr-FG=6A21gFZcS$Odh0y$0L8`uLC!A1uwip1#4}1;{y*iBj-w^Ve)D z(J*HY%8v7TlaJlA#>A%8`l)8eX+35om{3sekGyg-gdU{dYc>|9c)AP$fvWeC8Q16u zwu3ju-F^8Kj_GDh2LP-Ea;+E0D`(17vE}hqU#Nt4GQL$yKfDLQ4V!nI{YaW07rW$a z=S8tgBimL~Z>^_(=Vf0RbgFs+-fVOJd!lioVqSI%HO?u&X1VY6)W%!r;AQp}M|&5& z$_9~#uYAIplid>Q698c{FeLGV8#=Dzj7%N#NF{)i#Bu^8LL9b+)nlrPSVsZ3!?S;N z!W(#P7pOe2*DXc?Ona3;-Q-~R@kF6qXvEY(%S;Js06m7N_V#I_QVGlt*S4rNf$2@u zOSeXK?zR%%mU15Rs3d!|;%X$1Hfke@b96(oo6pOzu{zTkNYSv5k2Ie{*rRWUnKns` zn;U>o%hQc%Eu)vy+Rl-hCI^xg%{|xV{r1Nik@zva7Bg61A_c?;NcD$tulN|`Ynuu5 zH(wJn2XEqunGI?|CbWg!>c0=*#XaUh(_|G^mJ=WN{d|-7g?YA}CupEjI|?i$Wi)bu zwvPc|O%L!X!~_|o^F2{UJNN91F-!d5gzU7Tx$P4EB4EaXDel1?JS9}Hza#Dpi-?7YQuQ823iQn}3 zaTSNWGTZplbks1~meg~@^mM^1u>IVyeiGC1bKe@kDe95;dw)xG`#-|CyJN!Zb(Qh~ zsXR4?A~L#(v@iiGUCyiZoIp+meZ?}U!17fq--~by#@-@ACMN2XlKL%tNEv_-j*WwY)YZNapw=Y8!%+aJSIIvpb9 z#|2y$VlFFED)|Q&h2X-O1%v`#P+kntE*}NhG4Zw`!nJze9Z*+{V0wL1KDo6|m+mS! z+|&Fsyhg%yFtj2^TV>Iz+a;1<;N>W_j?s7b+R09ZID%%k$|F5*7RN&I0JZIf{u>4@b)98r+ee=tPf?o4i|Wc=f<6^vE*Z_c;4t zP_iY>_rpc{j6=)S;(G!bcewpbwOyMZyMBwG?_5tb>!6V1;^Pxs%{mrI$(E_U z#D}>|zxboAqIsX(rMei`a}X!NkE=OcpKCY?3L#_N#*viFH;uM_A_G`e+SjedX^OgU z+ltwYbE-5)XmI@T6Qr+C=pm3h5ILzQ8j5ai#}m0}UcX|#eEA}i?-k}K-zD5~xsTX% z-JY$x>Ifk-^9$p*8UN98GVdL6b3EhV5LS7amlbaP(4;LW??e>1zNDzI9P~53xxPX{ z>RmV85ZQ9k)f2#vI%c~Ey&w22aBR5Fp`iQeHiSKh#MoZ5ms@jJNQ-AuYeSymX?;Ty8^2pA-Ao` zn2(a=y&q^!%0XK>7&dviTCGo0$=TZj+quYe!0))!q+NZx(R1#1ls`A%H~7Db@{w^x zdNqWWd?brt?y~LiGg9_iocPplwJwZCP+e6OK3!!~w~>=Pj~dE^VPIgmY|kcc&ozu@ zJ`1ijD|}f%kvV~gPPxkkZHVr@%fRn@ewbV9K^E|MvE97I?E#hf_2t>tq?!crDL6SQ ze(N77@YeB=2$+*`>Q!$67QiArAex@>&K)B9KG34r=xFV;;XL&D8o&Kc$^@1F-b%M1 zYOG))+K`ck#eGK^tUUgS=h_iH;5ka*x1m_@yFi&BevKC))`zpe;$$~EolzQ?t9R8S z-YnB^^x)-bJ~i#INELNoOK~1p0yor)Yb?T|(Uct-!tHL@RPBkPmCXVJc#6x2Ttv?F z8jp32=4E(0_7pAik z3<*1v*UjiI`bI1NMs<4vv_0qUy(GLJ1bW-fE?acy*BFo7*~dMqAS^H=+0%5-+X*N) z|2_H4f4yPdEXvdmK)$)j89CCGyX- z$07keXve_`Ux>;r&;U{&$TV-woUYo@kl`pTaXT4ObSmQQz(xxDu2cw!rqe#${VwdI|7*8r<)9oy&EqY{RhH0Gg?yA%Te$on!p(}~`xNFmwYS{RS zWijQ#zu`-*qya;BGY@_P>*~y9rht$PC-40BBrtJsESZcyFhOe(5lR|*6hQe+DBR+B z>W=0>(DKXhiF8QHt#-dzFv}^Bd0Hm2N*G}{Bjy>N6%&dhU4wY8JBwhFkY~G}$rfeL zXQdj)ZDNE=k4y8UhPHOt`sh$CH}{IPSGHk!WOO-0W> zo~W{Ofe@iP8d9UI1X3y*PGwjFKuwOdxqb~H!*0528EPUd_?W)UdhSijyiZxtSaQ2P z+@uHwLwOeFTL*N5lGW6q%IyJnV{Kc$ygkdE4sLzTUqv#PQe45q2^NXs9)|Xi;woeX zU4QqNmp@P6Hcm&GG4m5!)(f@C*bv5UFdDy{6j{oW3NNRwGIVMV+x84pXzE@yLCnA` zA$a5Q-O|l;y`ph3nSpdLq-x=xm?xp(p88ov_$4~^@7r56h!YKW~hmMvw-lpt!a|# z?I5b8+!PWD1l5JK7Z@UgUbMbm6gc z-OO=ZkG^YDi_;Rh@P{PIRetaMD3@R-kb=|%qDGXuk0F_%8}N@iN;ug{x~=k-m9DZZ zss3dhe|VDPQcBa_1V+6cS2=yG?dXj4d9c29Prt?wu29!;ozc}_PAr0(P~Reb!F+QX z9y)|nXzr}%(R5rsa*`llRp!sCqUCU;EyL@NTdd9A#cXPBYeK39e#^^<=QMqE~3FWGd z9up&((q>OTqQjaEtMR%9%A*S9v(){qk8o3k(*@iu7j_9A9G*W>vhP(vQ$^2e!mP`k zP}kz}oUE}})TwtP^NCT}_A^E+n~8%VfarEskvE#4h1Lk5_fpaa%!12w55~yP3&)1S zR~?G`0YIp2*lntdEBkZkl=(pStC z-tm3$PW&&Klemt$pc4ucKdF4g0+u48ImyfZU|e{G6*zqZ==iEbzAA2yr+jjq^}_b;j&mjBK|rBC^DY$l3p7QpZI3LR8c3buufs;tLesE8l^ z7y!{lIwM`(>1zAA`t-`z!^@H&9B^$JK`(x+fdl|kFbi^J@cbFc00O@f>TOYAVp75& zV5@EX^LymY$E#$A=6u%~yBeig5OURl^e6ErgSg5`<9wdlJ*|PUJhiOS)|6&TFewvH z;C@!7G*!G>=~Imdw~U6j^nL(`~+6`#N1#7_QyxDfUks0ZU8Sth_w78W5qT z``w`T!nxs7_Cb^4^20aFFUkG1lvqb|=7w!2ic^ILyx~_TD%NW1q!NDFA3O{==?rtl zS|oWqx^CYS+O8!d(xbq~th)k^XU>*C^3<*+*?`}!G}mn9B-@)J;*l^-MIn$SbfbZ^NAd!sz zIEYXeHy!IbIXg$wYjJYduYdV)knZ)1-Foa>Y^BgzUt+rgPt&b8Q=Z7w?g+obd*$UL zH4}-TSw&(R0L2=!Zo+xr69dg6-A-`9RwLOszsn6&%|cFI-UoVdv8x$|No1t9WO8>! zSayx*_9+IrFKFqKrei_PV*RcloQ0$HqqSt73gcG9f}X=%y~809BV&I1&LAEI#acvx zd8}R`PbWJ`+*MngbbZ?b#wAzV<=twfq4V?n=Taa(GfqQMy(Qf(@igM_DlYQn&uSJ8o7f>49#1}*_m z?y=9s0x;B7Lj@rxP#fD)t`eW!1?*WfxrVp6eoTt*pM|TLe=H=bDmvcnEmMqhVRKLX zj-^=18BaK}al@cd0JCYpz6SGk2I!0#Yx%rt!fH`=1F z1EJOBALNtB1UHVvHy@eFwO|YNw*s}YpVc9xkW&!WmuGyTu8oT}i&$_`wPK^!a!w>* zaE`U&zXkPks3O^cwu!Fm+9|~gLLbuc6F*~kAMRMVSrd8YrZN1;AIEd4zufMOmGXqTM%{JcB$6q!&rt|=inp?!vRwS;gL}z}v&Zxd^1ND- zZs(tsrM;uM*dq6VRVNNDt3aC-)SEr9Q&Cq0%~#yh5yUtiJj@J#IPNeQ2v>{!6SMZLi_zC|pJE+Kbj@XhF%5O#%j>l;zt#Z3cj|wingyYG5 zU``y@N&n@0Uwa$&N^9u+IVgoenOi?4x#)Beb1ZbNyrkb%Srl087I=G528`b<>BrR_o->x;0`{(;t{Yh12EMIQlOLIzlzsg-y6rFOP1{ltgy{V9mp!fC04dpP+v9x|Ew3`v*5WyD*QasCRemn-%WuiIVi*QmIFv58m_9I!dt`-F{td zGciDm6koi)V69?f9~P2k++abGhJtl$Ix#jnOGo`2du~b__(y7`=A3Z?2A#x5R-_<1 z2zW_=+#5&NF>2$ z&i~L(zum|u($EDf%t{QO*un}-A-)h^5M%vwW#3}HwS;Xi*;=3MzcQvF zmYD>TMEAoL!`4Us7H<8y)XNlAK8<0mpe2X@sdyN-}_AB=3WGyy`*1c z*USsN4)l&RyLOZHxl@n1+*yRXTpWpO$xkPvf;R(3us2uH!fZS2{xD*o2YUhzGK+ml znqWWLtz9H+2`C9OJitDMITGzM#G4+KlNwu3Hri&uhA`F%X)#iuvv`+XUxlRq9X1;m z{5iS@_IDTyR10!bO6^ql80y$8zODL&!(JjIjG3qb>KpKaGZR#s{kiDB1etm|&-TC< z{9kb8Yh%Yi86XJTGgf=7UnWI`0(rpS9rZ{7H`{Z8{_hVN)uGI{n8_P4Im@2t&AWR%nlSxfDKeu{=Yz4EFC_D`4qXBrv$ zRWTZA{9t`^wr(SlL$h(O?C2{N{MQ(>w}`9taZ$st9RC=W473U0ikw8C{QsULCJ7yT zEMj%-$*t;gzAmi5Pxm=(1+u+Q)-WDzky|_bssbgZY^rgyX zg5FV7GXz1A5fQcw>NNjc^lM&Ehpidsaa6S@Dj!7Egj&ll*8XShQB=jOw}8~~;;VCv?w_PcK4!AN=Ui$we$@@4Y*1gh{sP>Kt+qCb z)c5$e_gl65`f>TaHqBdc~I2Xliwu5!Eu9tVO7s+YHJGt zsOc)KSy09M@5L1TRf8c^N2ir@mkS$n3V5N}^?hU{@Z<5$g3leQ>@l#y%wZY)Yg$Hb zg?#Ez38UkA3NBm*y+)5c3I9K*`elXR73NOqtD*mzE`N!UXQLN!Ss(Yk5a1!i47~7_ zFdB;bFay>nlt&Ut{2c2-7QKCG{M30L+H=#3Uzht`)dOpmX~N47Ys^R-)TVmGM37N9 z8UxZ&gz@YXU7#34fQ$XCerYJv(vKwRWGNW24<3)9A9eE&2m3AxIG~}=+p;yDl9X>x zH+fIIZEMTn0x?wfbfDQRZV-v*wlQ+}{jvXnkHkW6m5su++7x|~fSsS+9pYL6PhN!_ z4)(JoK>FjF^RyKhLPI@1^(wCo3&$&FIB_#N)hgBr_#KV0{LcA5-_5;pn2+Pq)2@8& zclxc_yqks5s<5o9HY1WE4vCkkC0phrTUAoJy#I}|Ng4}kR)1r}9dN)loH9DN#5*WQ+iok1SAfrE0DY-uJnb!*kQ^73-JV8Ylj-6pTibC5L_<&@$Q|$8N4lS4!r8i9l)J|2EQD5v()x3*hhi^ zbc+{r;V=F>CNI8f5Olu7Yne<>)_ZVYbmrvIu4$33PCN!$YqY+PzoZdd! zpZ)**O4?sV;_T<0U6t-aJHx(x#0sb82 z$jT@uoKDA9;~_z(pWh3!!l^~hqg5ybuTNK$L`iGC86Gf&qsqys0{@1K50=>)(a7f_ z?)F~S`>O}>b_>mO@r&k+a6*Oq?``_Q*dkfCo8JTmX5bYq!gtsx`;-RCn`v&`XJ~~a* zjEEa6v$ogw)?aicnpjNR?DJjOluF-=Os`Cm)CKRIu;74*FLW_qRno7?Kt)y1uMSXI zM66k2*uU5s=zx!oCa7s?6m@-mxS{f9j|FTIY~Rf||D`# zWwF|8vEZE!as|-{F;^Ut@f0zV2pIfMw4C@IhB<#8TI8mA4Q&4b9bz5C1aq8I#Qs8p zF}cz2#ThZkmlBR)(M94s08fuJ+y^AQ@Bc4t`SyO}YxLPcUziju7uhc|j(HuKRq?k% z_tmJ51GZ(1(?0I7j-C1I1#sAUq3t3|&3?gh&``FimH{D;HST?5RU=wdGcFn%NWgM= zBbxV!!q82+b$wI-o&M9a8oLSRR6KXE;$fy_D!zJ-z;FpKU~u$KlMsA z`j_<-(QV}PA?G4kMyQx(ncb%0F`RJ;k4B*QqO4B%z?Us*X=`AgE43IBp!n4r9|PRY zCYyP8iH8t5H~6!J=+<|}UEWadCWzlOTupCZy3S$E;$QaVOh6?us_Thu9?`ptEjaLF z=4cm%i38kSvEeBetYZl2=coK@S|%1Obad$3Zi)_NHy;@qs9se5M~$ohHO$`h`++y= zbp|s3?ouwO6CH$=z`W-OJ66h3u;J&)^p-fquiyD45*?Wx2sjoED11Ff8=O2Khx3y} z*;7t=wvv)b!+$Y#>qH|Y*@45X$38BcS--h$k#4!we?fdPIh@Y#ZZprmA-xnB(G;=h zKfSr2@#j6he0Wji;r-T3krh?6kV-Kga-!m2c+~R=0-#qqOu1vvX3z3BLGT()4F9lfL!q$bNiO$srf>&1 z+Nu8@d;)GXOUM!y6c6$_cu4+NUym4SM?nC3Xy`m971w9k?E(2ZjuB%HNi>TqpXltRRg>LoahaQVkQso>T^8Og8y8Jaw!N8MhRs!PF z%d4PpryFHsR{lTS@B;D={#f1*R)|bXBixyjxhgj}0gsKJHqIbdSD_D#;gC2^ou{De zD+jCzhRb07ChhNet$*arzyjc?OjmgY1)XZ!+ukQ}?&89rAfUK*IaC3Z;PEJ} zuIK__C?e3d=;4?N<%BfEHFd;e%1U=RE*Pjf&lAt}C>R}?fM z7Fs|AozbIbJsq8z?Q7oP5b5stuU11r9K6i56Zd$B=O&&Gr0~HUs(L&B&F*yBk18%V zL(y)-I9!X4O_2VKNS^32|22>IucGT>Yiu(bcqT}@nJ4m<97GSnus{22a0eN_!cUn; zQ$34ZpA?NAd8P=|AU+^>EJzvROTy%Sr8E}GG5!AiX?$d^x>xkvDGD*rb?8u@4_Hw5AQUZMZNHs zMb&9xRZp3%-WyrTXRD5H7ycLTUjx)=f|y=Ne&R0vdBf=QfJIOr_p&#Q2U=WxV-b=X zZgJS?VfPFvln)A=ek-w_>#9B76Fq~g_|s|5;9ZPV_xts4Ej#(-Z(%O#dP~emP<8Jw z<$d?d#L%|uDT|{1@X&zPq^Hs+nY+9hGqp`Ngw|qJhfnVx+UK_)D+h*~i8RZ?YFEr< z%Wo7KO6=bVvy_aKeM$qRHzM6V(YO{;ze9x_hdi4f(!<7dpQ;qG=DYd4H)Y+_HsAKq zE{*qfTo}BeCDblOxjbiJl4n024SabZWrJ}L(-@ne+VOVj9otk*&2YV`L+wS7L42XE z@^|=oSQj8vJRGI#1sfE~;lYq_<)!Bt{WJK6TR4sG34cD@zYaCZEk^ZnK=}A1w)E_# zvW0TpgKiCdbCcMZ&QbakKVHK-e!V6t57t)RoM`<;cX+;lxbPMNdt7jEC*WE@ykEpU zz7LGve^|fI+1h1UA9SZQGKfh~MN#OSRmwj=zWUm)@=g%>-4b0yPxK-MTDoy5( zoO0DO;lO?H1*^ZtP>}x|Yhwp1@xkOI(4(+E7DHcOQuoQv)6@p^&VO>IBFAC*^71nv?IiyKW2|y`<6^fI0zrFl#*8VFT3w*bd z+y=i7?R@I6U*K0GWnU$m|oSy-LbN2mS_tKGQN%qb64z;X89K%d)9~hvM6Hw z&5J5ySKJ`%j6YI@X*|T*_F>2FY`Z3;EAlTf1lHxTx*I7ubXxAWl<~N#m+=ZXDt$i# zj`KcXR!0(B{v`r2``m?DLJ9>yjh<=+R=|ZpI6H-R8z%gFxy|MyO4GgbGDQI}j~GGG zGC=6xC<1npghpGYipuu!|46i$ir6!??Srn4X0jNww%U!p-JBbYRzqNOgpBQC=0uU& zo@HC>)GKsY0qHaM=q`raQ~rAc+!R>ZD@z-r>i@_tzG{xHa0ix zSgyTta0eP%EP)+2w``;zk3t-0-988#r+{+WK_ig4gwfYHt@akFi0z@9b%$Ra(z(x; z6*o6uqf7|&Eo}OeW|HdbMXc=XxPCQ9?>D-Ci&!{|yc>@|HEoq6>Tv~b-t=8hC$kA! zgO#%KcTL8fgWkCQS48@LXRAES0#}8LR}lfh0cqpF`1oyJw``50Z>v+a)?-TD?(Qv` z$&CYJ1rAoT6_#4{0dmxCni8ydW)IZ7J$e+J=@y?1Mr}gzfqQpZIL*fyc^Ch0oR$37#Cs}m0$LfA_{O7lgCRRMduHGr3>zjI8i>@6O}Zb zL!V7?BaUsmXtYj2ONpa;YDK#n|Cr;!ePDGm`<`-@7)`FS-KjOpsG};6zhchPxMGe( zyw6w4%hAF>Z!yB(N+tPo#1c7#icvx-u&sJM?XxnW8Plm{?a<)t z0rM|Ru}#rREb`#3Hi}SbQRcW4m_rV&!q;j1N|>aAD6!ngT~7e-l)jW?T}hM*oUq)z z?x5vG>o5FWlq(EhW38|>?z%y4$me=$AcN2ulsvbb#j4sOZ^pf})s_d5Y`>R?1uju{ zY>;!%60%~pwnPM@QU6qu(uCn7@oL16@YLdyicTJ30%S}1u8iEsVj8UY)6b;?cmXWN z2K|^|Hzghb*Qf!#4<>d$T+~NH$=%ZoZO?3q>vH^d=9?gvgzC2EppLP*)N!fJ!2rzI zc!(4-#&Wsh%mNadnwt1gC={#xRQc);5W}?e^749xT4=esWPgz}TLC`3-s<61Og7`Q z<3Gm6G)F^rK{?z;miqRKIb$y**(aJyGX%#1-a!yd^LAK(4)bnKYD( z>mc5uV#=a3E?v}}J3?$98zizUkGBH5lLl9vT-@C&tVUV3n@;9CK{nsWrxk-z6)-ZA zB+b++)aUMQ_Pqe#lfW`CFt7!a)bMaZFl#=z>E+fgHMSB+Hb|1s2+sRzAz@&UYF;rF zUTLQ0`TKcZy-YnhI{Hm)gDT7?pl<-#Fh)?cpwVJs*`IXuS|C|`i}%-e)q#^brmz@m zPXPhJL5qX6Ff0RPitxc}zyWwL2WPT+kf`WuKVA7=JzCAZbJCZRKyAC4?IiJp*t9B% zG~Ix=gl2@WT4OYapR?cm7u}5-^8q-v#dRu>8Dx2eV}(_47<1;!EqCvWZd%xjTmc_Y z8@n!Vk1(gU2irUMXnB#yQ*&pH5@vk5bmz1rQd9nYxS8a>0ovpftDfkq!2eUk)yFfv zwsGuePihpaHKTeo?=Q7;6i!}hjCnSe>YO30vnAwZUXq0U_H!zYnX;6k)=?bFBZLU` zi;z-IDlZ)wLt>4Id1*_}Z%@zX^Zb9`_w~K5>%P9<`~H0H`ywU(H125iN{`i25XSY1 zKyLH48lXU}P&cdRQgZ}SJvw46f1l0JnX18`J)BU}gkf{&^=!byeLEWK@RLvLFelfY z{E;v^w+q^9U<%i^)o;YO*qcnFQVMq*(j5kCez$UWfs$uw6Vsf9K8;7(T^e7w3jmsV z5s#WyLPF?)ll)dHRf$@ z*VL_6TF4e!gW*KKTmiY$@>+-H0q_c;PL2e#43q^_3w7n?JwGjBuZ#q^Jzc>tqaG=_ z4CD5Ct4_yZA%umzxK4oDHLC$6H;bFVZG-x25bD5O)HTSgw2^7th9Bz9yw)Y=&R(SI{%I+QN%w*8p zOwk2beUAlJNnXBwEpVTil1g#G2M#18M@L87RW%SoLfQY9LB+!Z-mnVNoPY_qF7^00 zhv^ia&rWC_?cTHgcHvBdx1l90Btf-PC8Ldvj5|Id6H{;R+fN@FU>(vg>p2)itSSy& zsgBwu^|1Cj|L&c5@aRBP=mIw~;tGvUHy?yG`(QH!c&acO1>6^YF`BW4 z^0dQr$-YHMbFjOq{x3n;=?k7oh-$Ri%mT=)oql11rpxMPcB6f%FGnILMuHxFo3uJC$ z+n1K~uQab}d1rdeQvflq0jzP}ti&-HIQdMK3kKjd!(nm5>&MS!mRpyh`VB+3WW`%E zqaI&_jCs|I2shb8#^fmcHXwxyWGAS+_u6FXD_jEhi5YDVQ!!&Gh^F6Hz)o|ta?MbJ zcRL<7>M$Ez>S%V7{M57@FUdTnY6VFe+V$t1XmGOtkU5xO z9jq=mHrfhA7^x^n@hD3%>9_JX4=*nFkVM(XdX@vug6J#-g_SORK<3o8ErDK$ZUn=! zsDc>HOG|{3SZOBmHgdf055Czlx}H4QctfI(fHf_UeKkc374Jo*+eO1E_7xGSCSXB% zM!$#_(rdbaF+(GFn=t@JN;!?EsQnJ{moRnd=Hm0f-te+MWY?;8PibpZO<*CGcelx> zN)>98BN*p@G6|J!|Ey9DxXxYS6zqIJ3jNBIO3~C1mQ)hfRuCaFd&w&E+|=4pRE! zWc`QKmc&n66E`Guojq4U*0Mo*(9E2 zm;mejKjH|hMlyC=rGac8tF-Jb2YlC#ft$y5y4J33>dE@!ybx9lJ zLexl(sF9S}>$TON1<bgHX4PO&;uHw0f`YB~s;FPW4$RQSfc50O)oy{Lv z;)Of)uM?i9!l?rXH?fc$8#BOk0K2*i$ow^p?y;tmc+LYj0rY3j)@r8ZO-FOZt@Kwg1wS{Id?2CzoeDhqRnGgsxzh z4mQzFIBhp_kE>*TR@hSb)cR8kKc>KY9MG5i$6T zVDir);Wqo(#qoa%Tuzk&^DN0YwCaA!LZ5 zYv^$3A`w(Osn@Cd9+To09#m8+TiP zg6unmAOSy5WV5XR&l|=#VFRl;$5=A5xaWw zY6d^%oN+Ot)x{5G&{>a2i~*XyT;&0?1xDhRMtY zbK+rPzDa}c^^gDOhLmbuT*yG!EPf75sHAYyXwIaVe4KUF5!+rFI=_bS`zU)CVK?Gf zw=^T5pk^=FGGLlh%Q>2Ah`+`7k;o;QQ!owv>%E0WNOf={LeV@;cIHYXEE^Gs;66NU zc1SBjM0k~uz6>r!xF$vO6EHxz@z7uFcHh=vq0_ZgI+-8 zs{^;ZHia0+Apz#1LPX>K6E?1%^(Scl2?KMLPIBFrWOgQuC7((5X@94sV}+S$im=5-bT_l=yqxGA6uu5+XRbKOg0|N$p`RsHG!O4d4Q%=0 zJL^~cXhe?IA#cO)Tw%u#*@A0cDWoJdSo=VMkAt;CE^B>dbKpb-_Wxzb z>9FW4nbn~w$ke^ED(ejX+eh&l-dvzOkw2AE7>oe?2M)GM=yoN1itftCQV`; zigoGx{Ym6mO*Uo9YJp>%VZT;M1XfIqrld`xJs3XXaI>tFT8XzUH{xmO+R zjD3l>Ol>q?z}R5BQX0cr&8Bos|i)-^qobG>}7c++LTf)y7iUl5}PDL!-TNOEMe%x zDp}!aRzcWoFr7mVL_)BOkOb=#Hve$ z_G0v~s%SmTL!9g7?A_9`RcUTYx?fCgY^~Ce%wNXj|K4nTjssmP_DbA8!0DV!ug2tc z0*-9LtOqyEaCi|!7F}yuD_U~m#Wxqg=<}*<)T6|?zrgwPQtPI7dTc*R2v{z6X#JRx z#+78?;1sfP#zg-rP&g=V?iVN9zsKv6GR}bj>ZE0<6`L2@{f+zq@&kcCU1iFzD{G6= z+0t6HA>%XV%eMlipX?AsW$PuNA2(}aq;BuE%s#>Oyq|9k&7$l~I^nOB=&VFOKpTJ{@=HaPfgHD@PJ#&t5QC%D50((C4OEJ_{dz7 zyqkwDzGoep5iF=csm7FP*LSh%=I~EL79gcCSCUD6z+9NB=BxP=<~a)U>$}mTUO?l` z&&viFrny;!JnW=Sh?-iZ1-&W7+@F(&?(ox+pR=nE+5Ia+IHCKwUx^0}q0+fz*uNYxQ5)wp`PToR1byc094ex@CiM++! z43!UQuUpH3X$ZEkQd7tIctYL*_#)F3iX2}~f?p}1@30(9Q9M^vf;Ly68p4l6Wnf3$SReSbL-Dq|N;ZZ`@&e+o8%nJ3RbJ@3V)(QWJc`Pd1Or?ycPv z^njh5#NdS5#5GA*Hj|H-?b$lIbh*Fhe2Upey3A#R+bmf9?BB$-jf=- zQzZoFBU2}whRs6Qf5HRFn3d(tF_16(4^G&->FQn`9)+kL>YF4_AfV1lm1Viv-XHy` z${>@3efCJXK~~e)mpX~edrdlaJcYr9$!~HN}r)~~5 zZRXc;T4WCghJPuJc3j*Qfi<(>tCOXPlSz;s`{SE&Un8rKtM0^cG8sO2D9l36gxrAo zZ=OgZrjwZDklV*Br0KR1^U1+x_~*d@de#wPuiHB3W*b-$@bQCb!j6xfeBc_K=Suc{ zy6tVKSu1HNMF+0GViV+apSo~5xO$BLg-8Cc?1ztu#CAQMRZ<(VvVi6 zrHY>&p2z1;yl$4smQ&Uw9q+GmT%E;_sTXwr6zAB3=#76{I=t-tK@nr}xt1=Ith1ab z;c}abdQS!k{8jqqotnkF^W7&)$tan*`s@77D5=P?}YF;Qw=4WNM7*5h9 zG5M3MmWRIl*`!;wi4mRBw85#g2jex9_F(~<~HfZIM^cE;{mCV>o#GueW` zgYk`!{s*>3garf^apmg_hIgb{yr{On3+~8Q=+PDz6wfMt_BD9k(X=4k-HGO#n=&4+ zr98{qU6;PQO0=xP5@&k_R(ky>kB8hs=_qR~i;BNugs&3`x2(r=^aT4fA*j zTOr*^onhzZl>oXjBK&I~mZXbectV)xyK%4dk7=6`A#`lEso*z^D67(k`u@{`fX=r* zG)}@y&j_zEbgc5|ITJHkYv9-<6Xte@CsYvBrUJ_6eQDDG;h87NnUkS!%4s9QA)uogzq`v{k8y zIAgdf%6tIaarWJA4P8+EJj-|y<@wX(DtAH$JkjA()uP3N@1|F)gXINb&G^fKy@v+) z^5rltO?iA&zRSZQwIGVAywI2X>0@;VS~jHn1JA14)Wt{*=ANWFx=4Ry*wwfm(Me9S zDr77(K%@q}%S8<2opCMhO)Ousvaw}?ms%-L&ica<)GUx~_$>8}nJ{5RC6$;+GeJjM$n4{t0ausF+J=TgC)?8um&++nZ*q@P;UO}@^XJ@-EFyf zcHvA_V67!i+P1AfBVPVwa+L@x_l^!WLuQM1 z^j>)QbNxvUTB3wlTI1=VH;;@af!Rdrq?2Q(`)gKSgaW*uffKqP1_Ai#6vhNU=9{^O zRn={7;j&vrdPjA=hQC)Xo5+{ANK?e64z`}*;@a3F!b8`}>~q3%<7!lR@QwY}PUN@8 zhuT~1$J&N26k=`nSBI3XtgTA}e)VumzZFW83p@?xR7*<#D=n@0bgdw38(`S6-<)Zc zCi?g+M=6X=Bg#AAbS;u=W31dhXeH%~eALT>M+p5fGvK_gEf81Nx>?odhbOZ?_$UKa z`a<7jHt>kklD4D9wNIeFX0p-UX3?^x1C2RIU6-foluLG615H_GOJL-(gT9F?Z=CTa8Sa#;tsfGzOW&amzC1KC1+* zV&iTnY4tlw)*0X9-T6kRZkp1ykpgey%9oEb3bKNv@-ECe-?PX{)jQ9&F?Yu>rwzO6 zEq{sJK0-tsT zAGY)UX~6E>1Fx+~0t5mEZ4MQRMXmIwH7+N;knmWgl9XI7m>bM~M0?}LJ>Oqe0di)w z3&~4&cu!IvN3FO|zQzDbts0jX=WDa=m)Ena{H9#HAr%29zZlcR997YEj`sXB9`|eq zZ-IlYe2a=6Oapsb6Fo9}=~#nW=h@8LkKXN)4 zj4G{u2lRK%5wlZ%tl-B_8fju+FBy$V6lh)zeLCUzJ!-i1VvQ2x#^{a^P26mYz>B&H z+aU`}qIKo+@LY;^c_9OX5AuSL=2N^6G!_>^4pd3ET&zV8fhJ4&f$j>sO&w!x@PvUK zWK?bJXP}w+e8w8u?vW6}I;QYp&5r*rdh6CgUKQMTK?YN4wA4|hWyPMCpNgk7=S3yl zMC&urj~D-Z;F@(oqP);e?FaQTg)7jg(TzIjv~j>WH*8O^t&QUeK2V%r^EBhv&H)Y^ zm4Q9GIO_=lOb1U~gdS3>B-9jTe^5XNPg`II074cI^~-7ooHMh<44J}cn~1WG`Q1TM z3FRKE2v~2-KXpihDoNM(Hb{El+CI?*QuzFSMHvH5Ud+Mo=G~jN4m~jLqDp5K#{<|< z;!hWoEhZAZuMe#MEmpMO@MVztBlH#8WaS|~5ZhG)xq1ei)75C=H1CYja3GyfrVJa8 z8oZqcF6g&IJqxzC0FR7{17gD z`O1>bP3mI;67cPJW4hLf($Wb&D%{jJ2S0IOG~trjAENG@T^)!kyk%T zrMT=Iw{_lk)iK%Ql`_;E`{rYu|+AImzPd2?@ z-I20m^NpCyzElyLr>b2tc0YN*xRlCHM8Px;529xQOO?;OpOc8GTbEU**d?@&m~%LawJRMMmwqtuH5Qq*J#hYel+GZn|8a z!;KOW9Nfk4QmZu7sPPcb@$YyOA$V*5{VyP=pj%6`+?-%73wfni6 zp>T^z-#81nkW5lvszW>3PdB<_lJQ}9%+g*~U6I#kUx-Uf`&+k^!`EkgG0ZGJbFk1& zK5p)?h-=)#d1}d_n?k0o{zo|tKG$Fhb6;%h!7wC0-R#}Y=8+jjcC!#-_z~Ef5$3~17uuz+i9f#PHS>}JPqD})J6$|tl-}6Nr1=Jq$g}FW;vXi-B>oM zhX=EFwY~0OV=PujQ}*C-c?Pfu&X71~Y`IQfl7Qq|E}TW-l=`=;b#w8%x$kE8iB ze*1|k&K#xpY*fTkf7RgSe*21hF{;+&x$&hpv9Hbh{rUu!{szC##56)t@y_BAhJch5 z+uc>UwyaR`rdwJ8XZH>T;i)@+d&vd}ciQLM>+CNELl0TZAVx1{oq-`ar7-3K>~zp0 zyti*p7SAqP%%jt$$!&NhZ?0@R;}6Qi za5;pr)>Aak2kbvh`2q)%Np2VkRaU%5Qxt4Ug<4A57uD(uI`3zTGKULwE?R?tpi1}5 z*(~{q9VDp~>-J+FuwZyA^h|V1W-dZC$;^%<*=H0#e>}Nk4Rtg@60__}T_*|Qfl^Em z56(2zG8$6vCImjijR$eiDp#6(A3~hRtTDoTMX7A6HtVsBKvfS*!~)cbpsZqz1d;Ux z^%`HD;GxD@R_)L?%G-tf6V}~)%bmXOAr15f+ivQnx>o|0=_OkrgLh##XW}0o9H?!) ziw_?I+nuwr)o5zk*VhRGY$G6>N>5v_Y`1g8T33yFG-B6l1u_O)i`3rU`-HqlO}!Vb zuE|3|cv1ew8o7tt<=u~JdIyqMsVJ*z{8V%9J zKM^~ZZnoUC*0Db(UDpimIXt8E`jKu2Ig(VmcPYM0I@c~r59d(2*B7JT`xeU(a!w!6 zwfv!F^WGeJx6L!Q(W-j^Wg1BQpv5N*y84c4S92pJ0kK=?Lxyp04c0!Z;vTm!<#NY0 zuNa*Sv^B@gENes5!_QS4OQyv)dD+;s3)P?U9M%b))_z%Gtr4nK3-&}!`Pzt@+CW_t zIs4463|vi1b`+%@c@Sv+j=j)Nc-M&xEl$XS?>D)h1w)T~M6JwccybNAMpz!6?ReEs zrv(3k0OBAuz2=F~gq8ko`_rmkuL)5iLG!UbQX9UHvCU9%1j5Px#WYn0L zM-04o>t1{bvWgMrpJPy7FKS2!(hXWohokz5VkqNW-SIPIiF1{_VRf3UE(bKvZQ}5) zZKy_U^%eb^hu$lXNXF`6_RRg+OD=)IfX(K~)j0;f?x%DUz8y5HIj~Wc7#;1{>P??K z<4`lp%nCU!U4QO9yNUkr^?gkrVV780vsV%EyBj)O-&GXkV^iGtuRYst@o{7i8F0Ph z(j3i!tuAp)*r+XmjoqM*yAO{bd7L%svT;)kAaJP@bw#vk@#CF|(yL`$B@B421%#BR zsE@s%q27@H)j?n14xzV1%2zjci4HmNo7B_?c^essoMb7kzHrtum{p@g<59#~K*ep;NY9wK}cmB%{m@t1W(6t5UUsYRS$WZ8fV z+-=}ylMSHwM|&K&=`{e;!z zQF$6ad+_We7PDxZ9{?MD`qj1mxSAnc#NSe9|CP(x&%$D9CmAS zN@piUV3u3dNbkG#G1m7un*MI@07hc;Kke06PS~|}* zA2r{ouC*UC1r8@{G^H-jup1M}4VR7QhiFQt@rwB+FoR-N>m1Qsn@{IY4>mTQX3F?} z1JOCqq+8J|dUA5o|7svzlJglj8zMFXGV5FQvqAjyWZLyk)0Dr_{>^SyAfVf}J_}6! zS@ko18xiIeEqTi2rmbA+VB*(2K2fJ&t?$TmGIGG$MXcO4M=ufl{LYhPkmoqy4KG~<5; zcE{g7cUhU)^LZM{H7>>0*%K_L{(~&we+b1L?Kl$+f}{6>OCz~ zJ~|*XWx0t$I*jl@+~QLRRzXCT)3cwSF$3lqtIx?u6SPKHq~8o9`z7*oMi3?E0UDOGqQ%u3l#xX z)w)K;58)iflbp2vN894Jsfg9XYaFJVJSE@}cpzMI?`Dsc`LzQ>9PuF?ryAnkyunA6 zne=IhPwzX0Q84rH@O&it{5jldx@idh*KJ8iF}(LStLT402dj3kyYyBUXD6HE&*Z3% zL3nUe(rsxAL=}gFQh9lK{4eS@0c!ve41u$--MU52@D)tU#8>biO~(ka6h8NA(H2Y} zqYvI&>=xgesK&re*%-MzHXjKEwjzTwWn(Qm z-Xu?xt1IT-1M7&2io1X@XR&%cMa-jEM)ne5gyF-DI@8>4vX8e0v^2}jv%V}$nHAZ- z4%B)#gX9%<%A)+??EskJOO=(o{sSIl>Iq@Tjf+101nchvjwGcHDey1xQ=4h+Ta57F zuyXb@WDD63FKL8N-rwMXxc!^)wwFSs>YyaDR?a$#@0gz#G7xLoPWNsL6AX}3>{2$z zeEoD|S8Lqn)mzb*G@m~qH>a+moSi~$Kd&*lHGn)N>O90Y_WQF+h<^)g^yFW>VJWF; z)%Yc6ObnT^$Pu08bVjfpogOhC2I?xE2wV)grVN*ZjqdGyQ>JXS{TmpZb;+d(bZy&if*Nf z3+cc1S2|fR{J*on{MZL&$H9E{vwMD-=Xj;6GMN2q#SbpuO#hs)z*@H6Fte+@z)Ql@ zM_)Qropr{HOx=n3l7W!a-Q>gDjA4H_1~@8wkkyNl+7Dc87vApPvP>b1xZ`cwQWtj{ zBca=1qO@loHD8E?r6!oVhpP9=Ma#I+$rQH5uxas<1m;$FP&is2zL;|m0KR91;+V0Pg|hBd zmiJGDwzwveBwiNAEbCQQ+;(;bFto~iX#|O#Fe@Ru6zy^4>MTT+5ALu9%x^o_Uc?vyX(FC|qt?`b2h8mZDsR=ZuL^df^4oKNI8Ji3&y^{SH zo4CQ1=&+b)net>WLAu-&eeZy&`S$hRaJfEET-&nJemG=k>0~`#6`-aSntoNis?AJq z@|PZhPA|lTs^Vv5kTigUX(et}UFKUir#GzdOS>I11)X z!(=JdQ@b=-C?+ep*-eZRNAAFa#AN%WypKM$p?KSMGS8_D97rx-z%m58Fr4%MBJ>G| zT5qvNCU_c!V&#A?%A=;KiW!KlU%KE!1wP?$-11{-0^)>wo48$cyg1M6bv+mDDP1B) zQ_IT+y-VMD@dNhDQtfXJboLCm)JlHG)H-#!?w+Kp){0rypdy&$HbrqOqRmx?vfN^o zX?km=;sHxrO*=Ca>qQn`eds9J2JK^w!fVX^MP@|X6_Z?=`jsYk)){Ubd7{+0CsR{> zlhnz91AE`nQ3?3>02D}oYapvHq!~j?MJ9&Cky#FDi1}u0sQ6avkGfxOk)uoGqx?-1 zdjuwTZkYbgBc0{K$&(tH>UCXq6UH|!Ca(5gUVee^?%CR9>9&@{c39|^%+i0b7Kc<^ z_BWD*vzwl8iTE3E=9maoqaf$({J3JzY2ev2eeR(m%Rv4tXiZ&*GFxk=P7z2(M%Mlf zwR$iT6WZC)tJUg?io<$fQYF1QNvoc6uUh%|%qA?$M~V>cW*RxnPjfn*m5QPh z8UW^Czw|Uf!t(UAPq29t==0i{$pFE^hfA;RsgvU#IJa~fSAuci!6?=X*c^A2Lr^O- z4S}3pVOXovcaYrs{~=0Bwv3Pd5GA^onq`JHw$G}6gRxVPxRsK4)lR>2VN~aEaty*8 zT=~@0)YVwCJ88m;+iw;YG=5}A*MD1RKR~?@GH4Gv2Nb(YuzV-k>L3M}Wa7J=NC{Tr zXXT}%c)%I!35l=;_3zH;Fe`nwinJ7X9DIR^Ca z{|lhRKNsj8rI#Dkt``<$`q^PXgc&0QazZaa+^A_(W4o6H^3UhhAUg3c!pZ}cAi!$^ zA<6exRJj=|uZ0T8lZ;U^OWnWw@@x2I*G0eh?9ZmHIyBezc;zC0@T>mRevrHIH+BB` zIgSrwxAZ-3-2CaRh~>BF?fz6zoaq=zdCRZfTz#`Z&#z@B&8Gw*9g|>}%qFrQh1Hig ztryiZirRU6mRjCA7&8;{0oL-81*e)k?WN}?c9+Q+?jMgi)=#5qEW2Y49%8k(4D^&i zK#}=R73dL@BkZ@oIt3217n8JC3EvJ9%qat4PRWY6R$jDQZzq)^wiWZL*=y^EYY-tw zx#r&B@ zjjPUKqH}+3g6}A0 z7T?hHbS9_fEiox?LFZNx$I1J8CC5RW;S<$1i`cx;LKXYV6C}Q=@3LgjL2;`60}#|R zy#pwiRdbXoDR{OW!R9P1ylgu^d;x+kwZ(7mUr#kuqAfV1d!Jgd2JTaX6rI%^0_GGJ z@6scI*)yuLBB7K!HveiJh&x=Kv@hor7Z+c1?OFf{`ACUNaPmLtl^Ur;S%O7G%l@z$ zb#jLHjPKd1vOF6fU!Z?RL|%Hqqeh_5L)vR|ZrXd+dbCK_VWuSo1l)tAFT`hpaE?l` zEE-4KnQcczhk+cZos~TY_O-1T>7WOX+jU2J?90#JC-Nl=TXcHM`n8^{^zj?){yIXv zsNZHh4+(#=z6%caZwPkBaQViDnyYH4b$f+%T-mNS1cJ z@E@v-9b^v#taGzFJI`D}?8l#D)NNVtUiHpvMeBlxi_9USd5IhuSRa$sW2V!ofk!qQ ztlll?j>o)bSdgy}hrEC{3W!03;JX4`K$|#I0=0I zFH72qAyn9;p1%)RfvQO*G^;N`xs2jlbO|=@*FkKDAJhbfhTVVfc1r-XH6c}-JU}U-0+>SxqCn*fbns< z^W^M%F+Y#vTlYZ5HsVlbmkt*_wdi%%bsc}xUA;N+M?+V9WHHRyB(j5VID5+a6V@RZ zUKDV%OUiUtbQe08!80aQ!_f$OBqn=n7$Wi7>nFpH@aCC4O6&6b(t=ZB7{arK9w|$p z4$$8Nq>F4*>pSYOy1U^&d$j8S0sC?*&{@NwQaDP?=PCV{-8fyBR1cL@5>bwU_(h5XC>ZzyR#f$;rwc_XSyYDdja4>9XBr*IDBJP zFCS8Gd|9QLySY)1c`)}`RjHK%k93Q)(-5&CHrN{*{CG4xCuZ~^+1N)09B8gl$+h;n z_Oxh6vbdI0SEo_UWN4q<=lbqLnwKj1o~Q!9ovTT>;EC%sax~z%(kZE0U$;NR5XIES zB+WuENTvwS#+6*;P{67DS>>o|3;r>jlMSaYlH&7;95pP`S>^w!1=^`ZZG5KY9370vA%Mc>U8(>lT@evqT z#m99u3rLn9KUrL`xFa3+o>jih9ITEtUIG27 zx|ZEt0$`jNtpy`J@UvK7=r1@NUM2i(Rh6-Xb4^uM)yWoBV#sLEmMXl!i+OFZJv>>Y zTb$_!rVTGAblukR@bGY*=LYIL_i@yX%bOqzE)OQ3Hloj~(q))wzl~DOGPCvz`S%Q> zex(mW2siLy3S05<@mP=q@CKq8#kUv0FkM8ZrU(|x*OAV;$p1rVW+-^VF`aAju+n=c z=o&r@5o%B`y_}#U0sgQF*iq~oHG2ubxinsaVGl`!53}nUZwvI#^jU}~(rdm&MQp59 zN*ABII^E3L)s+U;xT0RlO;0nV_IH5U%dYh#Srq|rRLGem{`$>?E8KD^LsxPb-5c=r z90gMyo={@S^Tg!jsaoDA{8J6$XaFz#Wl~Hb_f@3SiFK8+whpFnTtN zj-USrtNx>AXUiO_Op`}Ay22k%KYri3^%dS?;Pnlv*~A*I$_?7&% z{-a7l$I75ssV3%sB%+3OK7a6&RjxWq0;e8i&XE$e|HpGltwPU9+de{OOL~0A(1zT_ zipG)#`17R-uPRR`HA2Q*x`O(cd$9J6r#Pg$xX|aJC{Ymc8Lbah!@ySXAFp5T%aly| zV#*%`r;;n=l^uBR@Gi+;07P1hw!{JOC4-R%(2s+5zK2%`C%&xRNPc*60pn24&J_3e`aP&Xo8WbR)9TLH=JOQqQmlN+&{Y9jXUxs&KA-d!S z!=DTApUPI$p9)Z5RAccXIK?Y4c=+tk*vlxAbp2XsfP-%20we88DjujDUed%b%_)kX zUyKYLwf;=#o&-4A*f!Ze*D2=WgBHR&KI8^3L+V;Kyzc(p0|5N3U74WCLm2X47SwsF zK%tz@Mf!$HH~@_bjt$mD*dPS!L%Vg*X0Qqnwi10LD%<`)r9n)rf}SuZW48Nxj4btNMiucwc!#A z3ybP&pC(L69&7GG90d$rM0bgI38-qi`<;k*{tWbWwG=L7hxwrX^Pl@auJg?tkDm@h z%@5Y&WizBiaj5Cl+*Hui4Kq!8vbpA^@aHO;EdF{ZF)zP^*9*g&iy;q`Zrl)KoGN{R zeKS~PUa`3Vfc)90m-xfNci#|}2v1e6KQrU=qW7oP*%`LH0Hj>Jscad^lHO0W%lj0~ zX0=aaEFa&c-~7$5enbnaYHxZ<AMD!ouZ>6hj_LGxfegXT@PeHd3 zdtiJ$1^L+%=PM!nhbIB7uWYJ$sfaI1Dk@xtCTjtE$_D1*uhyi4XeHMhe>y?wE}LyD zO1*Q1hu>imjal@;%ZhdCFy9ReqabJwcJ807&0^j^32+MI+v9crpT4V*ja`d;YCjfq z{>IWR!aXHE;?Fd&ZTGoJ;}r~Kyx126=fcavBI+r)fG*5{s*xQe z$Ml$0)hJEz+fY<3D{xIY$<_b$Y?{d1a#=#e0g$ua9cRGwigm_x42Wj_J+%3=#|w2v zSRd`u5DV^|1n{}L*_99U_Fn!%zy#{A-?%Yg1{MvFddvS<*?X*bN5fv~>D?s_Abs=T zFd32gg9)3g!k+4XCpSF2PrK;dCuDMK{o|<4?i)gYX$$2oda~2V zNF&h=wD%x|J)yvVx+bVv7`gx^f2ZksXY7Lc-G*5VmOvn)A#iKb_t{R4VfC9Bo%|o0 z&vIaq69213$X)uz-i^eJA35%VDGJNRic2mjZjQ!{|7EP9%l2grYTyTe#c$*q85^?L zA1&=oG#|q`)KxiYZ*P==lC!|?+3(yzru{m>NWVG>XfwwQXvC6Tkw9XwrN+&5ORR}o z*{?@d#t89Y_Heptt0vI31D3dQ1sPst=2fTLq%i5yv`e80S-^m}D(J)Mq7?t^B=S~f=oFYuuL zWLjz!KHg+4GCDHCKz60*?^5fZZ|BIJSh+67%FooW!PV|FQmU{m`aK*H$VAyrTg}u)ww{zYxYQ3T6!Zckb)XVG?=JCr)GFKJW%=u z>GqU|fuZ59EAinZJX6||a)2nip*Qe9h#5{LV_-Sgp`2b5h|BiNrp}o4-+3Pfi~Yo$ zkoN};u73pst+`&gY5&IuAx>nVLqgBSEdIjpXj>WdrGS7j%&D7g<`d{N@fhH|_BlN& zoJmlp?EQ4pfd#lAXZ^Ee{aN6BGM*&!sB{_XSSUgPt|EJj*#suUgxRpFhyJ?D+_wr! zkooTEGNuB*tu-on@Nii+f}8Un4hi7pvO+-Uj;(oHpRjLYjP*V!8I4FjWhqYrBWhf( z&ckQ%dBi@wZJz_lj7eyr&7T5j-vwqjQ-8oL;8Z;2=Je*Sre)%tNp5~P4K7rWet|JG zXQLVoUsw6b>_cl{Un{Z#$(W|N^Zspe#&ZF>c~F+weD}pI*|LaNZtnRU0f=S9?*jdH zqdpHFnR5-!=I`Kv?>VTO=2_m91*uL@bM7v-2IJe;>mUNScb|x*p7qSX1U2}MzRsCQ zhGYr7zY|Pk$Aj|NyJlV`dl|wkQefmoba6lJ;%{BPhg{^&IZWP=W{-I8oOnl>rrXd< zyU|!evRrsPwnN`IrjTnCKL#Lhf=U^c=6QMLaE*>uG2X;X?(@%yqe`@1=2+fj6#Baz zeNlh6LsZg<=+^6Uxw}w$s}VZr&bNv{%emVh3p6foHZT+a{LR+ipA+`K@NPzLw={1< zod|zOm3Pswxju!Pl4B0FE;TY;nascnk%;w{(8} z_+?kDNi?sKI7Aqb(@I~)20cukc3|+|N_#4?N`2I3i+M!E5MrI9+mQJL;g6_yB8q>NZru&X z$TI;RI=AGcl7_Ggwd5I#xxeggg7fd1ND~>iRTuUgd61-d_*J z+g40#+jJ=#nBa^?lb2mj>Y;^SBVz24@DK%R;NQ9STz|CZQVWaaH>9qK3+MFu{}jRr z{m}cIsIiYaHn?G?g<6{(Vby*(IjY6aW2`F{EDp{ijjy9shy$?6emxX}WrrE~1$cdl=Z_+1(RMm~_wjT?Y=Ra?p#~=hStC^q|7W3RUJ;9B?;OJz=2)F(po0!~AJD?pEAMdzL$i+sI z>_yTw(~8poOU!Ev({e&Wl)>OFtF3xy7)0&vi-H<&soNunId-ML#HcpX{O%J|Qeo==49eD=QbSHl*Vg7K5|_9ea3W z%u)$)?8-AN#8;dsOLdHldaJDa#+&O~HhLgXLYJ=V`o^hgY5uS8u#SO{stj>QjVp%W zEa~~wj`0qt3G0KjYx7jp{hKQulfs`Op|5$Ji7+oAu_ zdmYrmEkQ@y=B(L;khI&S8UNB^pv0lUll?jL)03v5kNmUVN#}t>7i~@e61zY-#?cGvkliF; z1sFkJcVwuMruD`)dsG#z53S6P%IQ=JKD$XZBizG_$T^%UhN3qse^IaPJ*P$Y&4Xct zZy6-%v7gBw0`Y}rN_6_a0BSjUIHMpzkTo*-x7=9u^=bD#KTt@_c29r)Pb%V7A?ey# zg33d@3><|vmC<6&X%iF6$J(GtwDvHdE>JjTIqbr=*zrom|Hy{%uTR8eCr=qXuCj8v zYkIDTJ zA^0t+R|(QyiE*AFZow6ge_P|QUk8Q2cG}|>!Uc6YUopH#wiR#tf(a}(YO36(dh@&S z0S&kGc)ve^2Fr5yo9p|epTnep&o|*f-rd5eJ)e|+GOAj=`_y1lxys{zd#wTXI^7a5 zBT2i|#QDQw#w?yzv{67%5dE(P)fM&z)uVp|If5Xfbh{Dwm!N3#^B?Cdt2QGeCGOt; z)8#+%n<5HThRJ5Und49q0zCJ2=RALga_8kLXHgUP0qsYEn1-_2JBC)`0bb}m&AbOs z|H&cRnKV|*MrZ_I7DVk!(%V#7O2VdQS~E~UL**Vy+2@bk?N#{P=Yrj|QL+|LbE}D3&s5S%pK}AQG`hcCca(Us68Y_QXEUp?zTddgh9}#=sp0$pox{ey)n}GJJ28b%<{}+VhW$%c@#S(?RgQhMH`A3Mr&y2O zN5vKgb)@APPTf`czI&5N+W2Kb7>0-JQ_;gknbR z9)TrU^k9lr5t6T7`J588sKs%^a`WBUGXxZ&_WR{WHa1bsK%OLV%Wp~yF$wBlm1qMy zGqcij8^9dJ;d_dwtW2M-p?Ier-)H31n}zc1``;W_?n<5sIlTP|2|(T71bS_Lou6Hz zn{l^_dvU>wX66=;6()3Dy1uPmhtRn`tNeOQW63DO>^S)$Ca&Xk23Oar$Ngwt2EI0q z@ox&Y8sQPOFZ0Wz>fn=QTraJ1zP%T+t$sjYMw3|9SG>xmM$j^KF~5WQ(!cE{wCy(T z-l&v*>&mL7W5I+sj6t{bE zuag_rIi!$Q>3-j6X5nEd8g`F)Mcd4+v8nTG#DP1Xgq5Hcp3|M`i_WVXd{C~ZC0S{Gqt}OH9)-k?Jv}_m zV69kzisv#b*6vCnqjO-q5xfaXkTGaK;;#ftI0YbK*eK58ZG0~7-gqjj1X*%B5MOrl ze7nTnO3ZnXT(psGknIn{1q)cCE;kGW26`qrRf;H-zqUAw3{xmFSW!UQu47}{@R>-@ zpu5Y*L@gXDMK29CdAyz*0BQt1$|~_5b7-mvYL!G^isSot89o!t;CmG?Cvd1t${R1= z+y=rgKOOEH++40+q~uhI=TKoCCv16r?^%4VLysS2S1txt_Vv(Z?*80!a|#!dU0-wi zNMXGkS48fI5F4r8d%IgTA@Ljs%|nQ<4YQii7<@quX`bJ=0WCPkFh1CIPTafh*vxVW z2C5G+)8uWl(>k^MRpP^8lBVqtpL-z!$ml)IBvU5{Vm$s5FV&|LsR3R*9A@>f(z!X< z#`2&W3qwM{mq?plhC#gXvoxfsREC3JFzLLF)&xI-=R&_o()MBfWW*(t7vu^>Q+QNT zT&sE%!LJINCLUGFD0gE65zV~a+NF~)y}6zmXdDOmXHP_Ia5p%9@f?84aGP-Uc)f-2 z(MUhysky}@L^lHb+EU`gZtn3Md04!f_UHsj4W4G#f{hSt zL2^SZUoOvU;%Vm6&zjd=JGNWsUMg-q`%pqa0TIii%Hb#krYHkg4WKRf9!FX>JcD>1 z`BYipEpjOf*J&aFa&l!+eghX=#4@UVA(C$$lzSikIQf~j2qG_BG6vL1kA4d*wdXXp zhc#cZ#O7|^-bWQE6pCHH+%)h9_|lXcsHV%#gC|PbcZQOvf$TuT{bd8bDytp=@{A1T z`{Lrsfzv8JpaVsz4a>@H%8Hxu-ywf(Z!g`_+6tVnk~4}efls~X3*ti7;=)2-%eCRW zTSMh9R@t!H*v)upw_ zqoa*6@z3K*rWj+T%P!Iyjn7$hQvbIdoc)^UZnz8$Qs+op^Y_XD_otDr_>wQ)^~Fm3SI|zoCV=1aA^H>e_CWbxf4;drdv)4ShNL4o?}64u1@?5sVw5u%nv1noiPk z#bG@=fPVLxIZXxo%4|Fb`O`7zCDa2CXjbcehewXsLp)P(R~1$rhMAL9h+JGG{yzcN z2PpU?X;3kt_<@J_Qoa$G1FYRo@l=y-(KC_wF?m${m^=ZP4xASWMSyEfC`!_x_IToT zecbP-0Mmf8fzzUFxd}x{88i}@4O|M0kH3NM1a9u-AMcVR9gs{YzCYZyt&j4TV{X5D zf!BZbV_el_6mT(c67b3RQ7K8PPM-x%1Jo|#4M#OBSaAD^X~&@t?AWnm=&)hKX4llz6cT^C z$H!yPTe9SqhR)6onwy)w?o<)4o$SfNci!FIea)y*qw9;M`uyHUMLQk-*knyLLSS9I>C{(*VoLy|J-zI_>RG?{S{f zrp=(W^?`H)lO#!!BuN^ijs~VA7RMX~Tn{t@LsE`Sk|arzBuUZ%=0xDZ=#?4xIM4}v z187J)FiDamNs=T<2d>e;VTqoKlaW?Nf7H8SX$K%ll9XYifHA;Mppar)V}X&ty93mA zEpRBX8_4ga?ogmE{!ZOFK=Gv6o+L? z1lptC8Nd|azkrR2<7=b6UiO&Mx4rIJb^y?_FRBaB@8A zqpSsZCq93GTO+x6TC_6+IHi|kO4~abcs{;!Ns=TA2YN@@YtMBT@u-h!B0a075#qV9db&wKdC^kKkSU@7n~@gA3!#BzS3y=P0>`>QA` z4nckb_*vq7i@gtr#OH7y@a-tOqa>DFWB<4^jw!bHR3uJ+72AJM<~1Tok|arzB>lhQ zyo2+BBcd1H;z-;b8N+P9e-p{LA&JDgmKWzZK87 z#`_kB7$2Fa^CEC>480y33Ayuu_kit@JUph1wtBKU;@y#`J3f+&V}R3RNHf5LJsdw3 z_%8593@ILyIPc!tfBSV0?G?4fFzk1t_h4P@KP(cw&&4?x6RsorXseuKo`_gFBbj+} zAMYngl9XvC6eUTL_U_%p>y0tzUn6jA;?bR`Q=G4`Q1U)+#XNrp$M)DnS#dtUYf9et zUlVoSOuXl|cwSag|LQW@>d6U-y2T`Bcgzv_0Pu;J-|?R#e-T*fJ2400$D^m=#vbaF zwl_LnHy;L*8yLSgy3E5T{*{0i09#vAnqvReI-efGRcIZBuP?b9*TJl z|0sICH3HLnS$w2aTO_ouiJ{9EC(2%ko_ZHVvTb}M@oIZ{ukM(`@$=Dhus#+cnNUVs zy}35#4*X0ke@e0;5}5xOz4|VR9+IVoL{jj*NL>DDNpHj6To!G823TH35|Ts`vNjgR zxulJ6z5R3w_Zvtd>Z0T^lD{2wMVi<-U<25A5Q002ovPDHLkV1h|voJIfu diff --git a/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm-slashed.png b/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm-slashed.png new file mode 100644 index 0000000000000000000000000000000000000000..8b1c848798cd78c34fd45b1c9648377631a3a54e GIT binary patch literal 23975 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

wwnBakL6g7f$ia<%Mj;g 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 QC$B@0dPWyYbnPSm2^)>V%K!iX literal 0 HcmV?d00001 diff --git a/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm-with-beams-slashed.png b/packages/alphatab/test-data/visual-tests/guitar-tabs/rhythm-with-beams-slashed.png new file mode 100644 index 0000000000000000000000000000000000000000..9a4b23cfcc31176d6a8cec154006d0b75a405100 GIT binary patch literal 25223 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 literal 0 HcmV?d00001 diff --git a/packages/alphatab/test/visualTests/features/GuitarTabs.test.ts b/packages/alphatab/test/visualTests/features/GuitarTabs.test.ts index 1617b2904b..1498d0b583 100644 --- a/packages/alphatab/test/visualTests/features/GuitarTabs.test.ts +++ b/packages/alphatab/test/visualTests/features/GuitarTabs.test.ts @@ -1,8 +1,8 @@ -import { describe, it } from 'vitest'; -import { StaveProfile } from '@coderline/alphatab/StaveProfile'; import { TabRhythmMode } from '@coderline/alphatab/NotationSettings'; import { Settings } from '@coderline/alphatab/Settings'; -import { VisualTestHelper } from 'test/visualTests/VisualTestHelper'; +import { StaveProfile } from '@coderline/alphatab/StaveProfile'; +import { VisualTestHelper, VisualTestOptions, VisualTestRun } from 'test/visualTests/VisualTestHelper'; +import { describe, it } from 'vitest'; describe('GuitarTabsTests', () => { it('rhythm', async () => { @@ -19,6 +19,30 @@ describe('GuitarTabsTests', () => { await VisualTestHelper.runVisualTest('guitar-tabs/rhythm-with-beams.gp', settings); }); + it('rhythm-slashed', async () => { + const settings: Settings = new Settings(); + settings.notation.rhythmMode = TabRhythmMode.ShowWithBars; + await VisualTestHelper.runVisualTestFull( + await VisualTestOptions.file( + 'effects-and-annotations/beat-slash.gp', + [new VisualTestRun(-1, 'test-data/visual-tests/guitar-tabs/rhythm-slashed.png')], + settings + ) + ); + }); + + it('rhythm-with-beams-slashed', async () => { + const settings: Settings = new Settings(); + settings.notation.rhythmMode = TabRhythmMode.ShowWithBeams; + await VisualTestHelper.runVisualTestFull( + await VisualTestOptions.file( + 'effects-and-annotations/beat-slash.gp', + [new VisualTestRun(-1, 'test-data/visual-tests/guitar-tabs/rhythm-with-beams-slashed.png')], + settings + ) + ); + }); + it('string-variations', async () => { const settings: Settings = new Settings(); settings.display.staveProfile = StaveProfile.Tab; From f62f34e743fc2082934ee20a06cf2ff4fb3d0ea2 Mon Sep 17 00:00:00 2001 From: danielku15 Date: Tue, 6 Oct 2026 17:38:55 +0200 Subject: [PATCH 3/6] fix(rendering): render slashed tie destinations as slash on tab staves Slashed beats containing tied notes fell back to fret numbers on tab staves while standard notation shows a slash. The hidden tied note then pulled the rhythm stem up to its string. --- packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts b/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts index 2fff8e86e2..bd99228c97 100644 --- a/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts @@ -136,7 +136,7 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase { let beatEffects: Map; - if (this.container.beat.slashed && !this.container.beat.notes.some(x => x.isTieDestination as boolean)) { + if (this.container.beat.slashed) { const line = Math.floor((this.renderer.bar.staff.tuning.length - 1) / 2); const slashY = tabRenderer.getLineY(line); const slashNoteHead = new SlashNoteHeadGlyph(0, slashY, this.container.beat); From 3818e7fa2a41887ef3cedf3cb972163410576628 Mon Sep 17 00:00:00 2001 From: danielku15 Date: Tue, 6 Oct 2026 17:38:55 +0200 Subject: [PATCH 4/6] fix(rendering): align tremolo of slashed tab beats with the stem Like Guitar Pro, the tremolo sits on the (potentially hidden) stem of the slash, beats without stems keep it centered. --- packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts b/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts index bd99228c97..66266adb26 100644 --- a/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts @@ -13,6 +13,7 @@ import { TabNoteChordGlyph } from '@coderline/alphatab/rendering/glyphs/TabNoteC import { TabRestGlyph } from '@coderline/alphatab/rendering/glyphs/TabRestGlyph'; import { TremoloPickingGlyph } from '@coderline/alphatab/rendering/glyphs/TremoloPickingGlyph'; import type { TabBarRenderer } from '@coderline/alphatab/rendering/TabBarRenderer'; +import { BeamingHelper } from '@coderline/alphatab/rendering/utils/BeamingHelper'; import type { BeatBounds } from '@coderline/alphatab/rendering/utils/BeatBounds'; /** @@ -230,8 +231,10 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase { ); } + // effects like tremolos sit on the (potentially hidden) stem + const effectX = BeamingHelper.beatHasStem(this.container.beat) ? this.stemX : this.onTimeX; for (const g of centeredEffectGlyphs) { - g.x = this.onTimeX; + g.x = effectX; } } From 4e960fdd5621992c9bd0d94b4b636735f6cbe4b2 Mon Sep 17 00:00:00 2001 From: danielku15 Date: Tue, 6 Oct 2026 17:57:02 +0200 Subject: [PATCH 5/6] refactor(rendering): share note head stem anchor positions Note heads resolve their own positions and stem attachment points from the SMuFL anchors. Standard notation chords, slash beats and slashes on tab staves use them instead of individual copies of the calculation. The tab beat glyph resolves the stem ends in the rhythm area once for fret numbers and slashes. --- .../src/rendering/glyphs/NoteHeadGlyph.ts | 54 ++++++++++- .../rendering/glyphs/ScoreNoteChordGlyph.ts | 53 +++-------- .../glyphs/ScoreNoteChordGlyphBase.ts | 39 ++------ .../src/rendering/glyphs/SlashBeatGlyph.ts | 90 ++++++------------- .../rendering/glyphs/SlashNoteHeadGlyph.ts | 11 +-- .../src/rendering/glyphs/TabBeatGlyph.ts | 72 ++++++++------- .../src/rendering/glyphs/TabNoteChordGlyph.ts | 25 +----- 7 files changed, 139 insertions(+), 205 deletions(-) diff --git a/packages/alphatab/src/rendering/glyphs/NoteHeadGlyph.ts b/packages/alphatab/src/rendering/glyphs/NoteHeadGlyph.ts index 53735156c5..18defc648c 100644 --- a/packages/alphatab/src/rendering/glyphs/NoteHeadGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/NoteHeadGlyph.ts @@ -1,8 +1,10 @@ +import { EngravingSettings } from '@coderline/alphatab/EngravingSettings'; import { Duration } from '@coderline/alphatab/model/Duration'; -import { MusicFontGlyph } from '@coderline/alphatab/rendering/glyphs/MusicFontGlyph'; import { MusicFontSymbol } from '@coderline/alphatab/model/MusicFontSymbol'; import type { ICanvas } from '@coderline/alphatab/platform/ICanvas'; -import { EngravingSettings } from '@coderline/alphatab/EngravingSettings'; +import { NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase'; +import { MusicFontGlyph } from '@coderline/alphatab/rendering/glyphs/MusicFontGlyph'; +import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection'; /** * @internal @@ -19,6 +21,54 @@ export class NoteHeadGlyphBase extends MusicFontGlyph { } super.paint(cx, cy, canvas); } + + /** + * Gets the x-offset from the left of the note head at which a stem in the given direction attaches. + * @remarks The metrics are passed explicitly as positions are also requested before the note head is laid out. + */ + public getStemX(smufl: EngravingSettings, direction: BeamDirection): number { + if (direction === BeamDirection.Up) { + // stem-up attaches on the right side + return ( + (smufl.stemUp.has(this.symbol) + ? smufl.stemUp.get(this.symbol)!.x + : smufl.glyphWidths.get(this.symbol)!) * this.glyphScale + ); + } + // stem-down attaches on the left side + return (smufl.stemDown.has(this.symbol) ? smufl.stemDown.get(this.symbol)!.x : 0) * this.glyphScale; + } + + /** + * Gets the y-position of the note head at the given position (in the same coordinate space as {@link y}). + * Positions depending on the stem length cannot be resolved by the note head and + * are treated like {@link NoteYPosition.Top} and {@link NoteYPosition.Bottom}. + * @remarks The metrics are passed explicitly as positions are also requested before the note head is laid out. + */ + public getNoteHeadY(smufl: EngravingSettings, requestedPosition: NoteYPosition): number { + switch (requestedPosition) { + case NoteYPosition.TopWithStem: + case NoteYPosition.Top: + return this.y - this.height / 2; + case NoteYPosition.BottomWithStem: + case NoteYPosition.Bottom: + return this.y + this.height / 2; + case NoteYPosition.StemUp: + return ( + this.y - + (smufl.stemUp.has(this.symbol) ? smufl.stemUp.get(this.symbol)!.bottomY : 0) * this.glyphScale + ); + case NoteYPosition.StemDown: + return ( + this.y - + (smufl.stemDown.has(this.symbol) + ? smufl.stemDown.get(this.symbol)!.topY + : -smufl.glyphHeights.get(this.symbol)! / 2) * + this.glyphScale + ); + } + return this.y; + } } /** diff --git a/packages/alphatab/src/rendering/glyphs/ScoreNoteChordGlyph.ts b/packages/alphatab/src/rendering/glyphs/ScoreNoteChordGlyph.ts index 05c336ab09..ea472d74a3 100644 --- a/packages/alphatab/src/rendering/glyphs/ScoreNoteChordGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/ScoreNoteChordGlyph.ts @@ -7,7 +7,6 @@ import type { ICanvas } from '@coderline/alphatab/platform/ICanvas'; import { NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase'; import { DeadSlappedBeatGlyph } from '@coderline/alphatab/rendering/glyphs/DeadSlappedBeatGlyph'; import type { EffectGlyph } from '@coderline/alphatab/rendering/glyphs/EffectGlyph'; -import type { MusicFontGlyph } from '@coderline/alphatab/rendering/glyphs/MusicFontGlyph'; import type { NoteHeadGlyphBase } from '@coderline/alphatab/rendering/glyphs/NoteHeadGlyph'; import { ScoreChordNoteHeadInfo, @@ -25,7 +24,7 @@ import { NoteBounds } from '@coderline/alphatab/rendering/utils/NoteBounds'; * @internal */ export class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase { - private _noteGlyphLookup: Map = new Map(); + private _noteGlyphLookup: Map = new Map(); private _notes: Note[] = []; private _deadSlapped: DeadSlappedBeatGlyph | null = null; private _tremoloPicking: TremoloPickingGlyph | null = null; @@ -103,18 +102,13 @@ export class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase { return this.minStepsNote ? this._internalGetNoteY(this.minStepsNote.glyph, requestedPosition) : 0; } - private _internalGetNoteY(n: MusicFontGlyph, requestedPosition: NoteYPosition): number { - let pos = this.y + n.y; - + private _internalGetNoteY(n: NoteHeadGlyphBase, requestedPosition: NoteYPosition): number { const sr = this.renderer as ScoreBarRenderer; - const scale = this.beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1; + const scale = this.scale; switch (requestedPosition) { - case NoteYPosition.TopWithStem: - // stem start - pos -= - (sr.smuflMetrics.stemUp.has(n.symbol) ? sr.smuflMetrics.stemUp.get(n.symbol)!.bottomY : 0) * scale; - - // stem size according to duration + case NoteYPosition.TopWithStem: { + // stem start and size according to duration + let pos = this.y + n.getNoteHeadY(sr.smuflMetrics, NoteYPosition.StemUp); pos -= sr.smuflMetrics.getStemLength(this.beat.duration, sr.hasFlag(this.beat)) * scale; pos -= this._stemLengthExtension; @@ -122,22 +116,10 @@ export class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase { topCenterY -= this._stemLengthExtension; return Math.min(topCenterY, pos); - - case NoteYPosition.Top: - pos -= n.height / 2; - break; - case NoteYPosition.Center: - break; - case NoteYPosition.Bottom: - pos += n.height / 2; - break; - case NoteYPosition.BottomWithStem: - pos -= - (this.renderer.smuflMetrics.stemDown.has(n.symbol) - ? this.renderer.smuflMetrics.stemDown.get(n.symbol)!.topY - : -this.renderer.smuflMetrics.glyphHeights.get(n.symbol)! / 2) * scale; - - // stem size according to duration + } + case NoteYPosition.BottomWithStem: { + // stem start and size according to duration + let pos = this.y + n.getNoteHeadY(sr.smuflMetrics, NoteYPosition.StemDown); pos += sr.smuflMetrics.getStemLength(this.beat.duration, sr.hasFlag(this.beat)) * scale; pos += this._stemLengthExtension; @@ -145,20 +127,9 @@ export class ScoreNoteChordGlyph extends ScoreNoteChordGlyphBase { bottomCenterY += this._stemLengthExtension; return Math.max(bottomCenterY, pos); - - case NoteYPosition.StemUp: - pos -= - (sr.smuflMetrics.stemUp.has(n.symbol) ? sr.smuflMetrics.stemUp.get(n.symbol)!.bottomY : 0) * scale; - break; - case NoteYPosition.StemDown: - pos -= - (sr.smuflMetrics.stemDown.has(n.symbol) - ? sr.smuflMetrics.stemDown.get(n.symbol)!.topY - : -sr.smuflMetrics.glyphHeights.get(n.symbol)! / 2) * scale; - break; + } } - - return pos; + return this.y + n.getNoteHeadY(sr.smuflMetrics, requestedPosition); } public addMainNoteGlyph(noteGlyph: NoteHeadGlyphBase, note: Note, noteLine: number): void { diff --git a/packages/alphatab/src/rendering/glyphs/ScoreNoteChordGlyphBase.ts b/packages/alphatab/src/rendering/glyphs/ScoreNoteChordGlyphBase.ts index 5ae9a37177..2f01179d5a 100644 --- a/packages/alphatab/src/rendering/glyphs/ScoreNoteChordGlyphBase.ts +++ b/packages/alphatab/src/rendering/glyphs/ScoreNoteChordGlyphBase.ts @@ -484,9 +484,6 @@ export abstract class ScoreNoteChordGlyphBase extends Glyph { side: ScoreChordNoteHeadGroupSide, leftOfStem: boolean ) { - const scale = this.scale; - const smufl = this.renderer.smuflMetrics; - for (const stepInfos of side.notes.values()) { // NOTE: for now we do not displace "third" voices even further but they overlap for (const info of stepInfos) { @@ -499,17 +496,9 @@ export abstract class ScoreNoteChordGlyphBase extends Glyph { } // shift left/right according to stem position or glyph size else if (leftOfStem) { - // stem-up is the offset on the right side of the notehead - if (smufl.stemUp.has(info.glyph.symbol)) { - info.glyph.x -= smufl.stemUp.get(info.glyph.symbol)!.x * scale; - } else { - info.glyph.x -= smufl.glyphWidths.get(info.glyph.symbol)! * scale; - } + info.glyph.x -= info.glyph.getStemX(this.renderer.smuflMetrics, BeamDirection.Up); } else { - // stem-down is the offset on the left side of the notehead - if (smufl.stemDown.has(info.glyph.symbol)) { - info.glyph.x += smufl.stemDown.get(info.glyph.symbol)!.x * scale; - } + info.glyph.x += info.glyph.getStemX(this.renderer.smuflMetrics, BeamDirection.Down); } // update side @@ -573,26 +562,10 @@ export abstract class ScoreNoteChordGlyphBase extends Glyph { } private _updateGroupStemXPosition(info: ScoreNoteGlyphInfo, noteGroup: ScoreChordNoteHeadGroup) { - const smufl = this.renderer.smuflMetrics; - const scale = this.scale; - let stemX: number; - - if (noteGroup.direction === BeamDirection.Up || noteGroup.displacedNotes) { - if (smufl.stemUp.has(info.glyph.symbol)) { - const stemInfo = smufl.stemUp.get(info.glyph.symbol)!; - stemX = stemInfo.x * scale; - } else { - stemX = smufl.glyphWidths.get(info.glyph.symbol)! * scale; - } - } else { - if (smufl.stemDown.has(info.glyph.symbol)) { - const stemInfo = smufl.stemDown.get(info.glyph.symbol)!; - stemX = stemInfo.x * scale; - } else { - stemX = 0; - } - } - + let stemX = info.glyph.getStemX( + this.renderer.smuflMetrics, + noteGroup.direction === BeamDirection.Up || noteGroup.displacedNotes ? BeamDirection.Up : BeamDirection.Down + ); stemX += this.noteStartX; if (stemX > noteGroup.stemX) { diff --git a/packages/alphatab/src/rendering/glyphs/SlashBeatGlyph.ts b/packages/alphatab/src/rendering/glyphs/SlashBeatGlyph.ts index 60175b1241..4b6e80a854 100644 --- a/packages/alphatab/src/rendering/glyphs/SlashBeatGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/SlashBeatGlyph.ts @@ -1,8 +1,5 @@ -import { EngravingSettings } from '@coderline/alphatab/EngravingSettings'; import { BeatSubElement } from '@coderline/alphatab/model/Beat'; import { Duration } from '@coderline/alphatab/model/Duration'; -import { GraceType } from '@coderline/alphatab/model/GraceType'; -import { MusicFontSymbol } from '@coderline/alphatab/model/MusicFontSymbol'; import type { Note } from '@coderline/alphatab/model/Note'; import type { ICanvas } from '@coderline/alphatab/platform/ICanvas'; import { NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase'; @@ -107,73 +104,42 @@ export class SlashBeatGlyph extends BeatOnNoteGlyphBase { } public _internalGetNoteY(requestedPosition: NoteYPosition): number { - let g: Glyph | null = null; - let symbol: MusicFontSymbol = MusicFontSymbol.None; - let hasStem = false; - if (this.noteHeads) { - g = this.noteHeads; - symbol = SlashNoteHeadGlyph.getSymbol(this.container.beat.duration); - hasStem = true; - } else if (this.deadSlapped) { - g = this.deadSlapped; - } - - if (g) { - let pos = this.y + g.y; + const noteHeads = this.noteHeads; + if (noteHeads) { const sr = this.renderer as SlashBarRenderer; const beat = this.container.beat; - const scale = beat.graceType !== GraceType.None ? EngravingSettings.GraceScale : 1; - switch (requestedPosition) { case NoteYPosition.TopWithStem: - if (hasStem) { - // stem start - pos -= - (sr.smuflMetrics.stemUp.has(symbol) ? sr.smuflMetrics.stemUp.get(symbol)!.bottomY : 0) * - scale; + // stem start and size according to duration + return ( + this.y + + noteHeads.getNoteHeadY(sr.smuflMetrics, NoteYPosition.StemUp) - + sr.smuflMetrics.getStemLength(beat.duration, sr.hasFlag(beat)) * noteHeads.glyphScale - + this._stemLengthExtension + ); + case NoteYPosition.BottomWithStem: + return ( + this.y + + noteHeads.getNoteHeadY(sr.smuflMetrics, NoteYPosition.StemDown) + + sr.smuflMetrics.getStemLength(beat.duration, sr.hasFlag(beat)) * noteHeads.glyphScale + + this._stemLengthExtension + ); + } + return this.y + noteHeads.getNoteHeadY(sr.smuflMetrics, requestedPosition); + } - // stem size according to duration - pos -= sr.smuflMetrics.getStemLength(beat.duration, sr.hasFlag(beat)) * scale; - pos -= this._stemLengthExtension; - } else { - pos -= g.height / 2; - } - return pos; + const g = this.deadSlapped; + if (g) { + // no stems + const pos = this.y + g.y; + switch (requestedPosition) { + case NoteYPosition.TopWithStem: case NoteYPosition.Top: - pos -= g.height / 2; - break; - case NoteYPosition.Center: - break; - case NoteYPosition.Bottom: - pos += g.height / 2; - break; + return pos - g.height / 2; case NoteYPosition.BottomWithStem: - if (hasStem) { - pos -= - (sr.smuflMetrics.stemDown.has(symbol) - ? sr.smuflMetrics.stemDown.get(symbol)!.topY - : -sr.smuflMetrics.glyphHeights.get(symbol)! / 2) * scale; - - // stem size according to duration - pos += sr.smuflMetrics.getStemLength(beat.duration, sr.hasFlag(beat)) * scale; - pos += this._stemLengthExtension; - } else { - pos += g.height / 2; - } - return pos; - - case NoteYPosition.StemUp: - pos -= this.renderer.smuflMetrics.stemUp.has(symbol) - ? this.renderer.smuflMetrics.stemUp.get(symbol)!.bottomY - : 0; - break; - case NoteYPosition.StemDown: - pos -= this.renderer.smuflMetrics.stemDown.has(symbol) - ? this.renderer.smuflMetrics.stemDown.get(symbol)!.topY - : 0; - break; + case NoteYPosition.Bottom: + return pos + g.height / 2; } - return pos; } return 0; diff --git a/packages/alphatab/src/rendering/glyphs/SlashNoteHeadGlyph.ts b/packages/alphatab/src/rendering/glyphs/SlashNoteHeadGlyph.ts index e2eb54c0ff..cd35d7b94f 100644 --- a/packages/alphatab/src/rendering/glyphs/SlashNoteHeadGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/SlashNoteHeadGlyph.ts @@ -7,7 +7,6 @@ import type { ICanvas } from '@coderline/alphatab/platform/ICanvas'; import type { Glyph } from '@coderline/alphatab/rendering/glyphs/Glyph'; import { NoteHeadGlyphBase } from '@coderline/alphatab/rendering/glyphs/NoteHeadGlyph'; import type { LineBarRenderer } from '@coderline/alphatab/rendering/LineBarRenderer'; -import { BeamDirection } from '@coderline/alphatab/rendering/utils/BeamDirection'; import { ElementStyleHelper } from '@coderline/alphatab/rendering/utils/ElementStyleHelper'; /** @@ -69,15 +68,7 @@ export class SlashNoteHeadGlyph extends NoteHeadGlyphBase { lr.registerBeatEffectOverflowsForBeat(this.beat!, minEffectY, maxEffectY); } - const direction = lr.getBeatDirection(this.beat!); - const symbol = this.symbol; - if (direction === BeamDirection.Up) { - const stemInfoUp = lr.smuflMetrics.stemUp.has(symbol) ? lr.smuflMetrics.stemUp.get(symbol)!.x : 0; - this.stemX = stemInfoUp; - } else { - const stemInfoDown = lr.smuflMetrics.stemDown.has(symbol) ? lr.smuflMetrics.stemDown.get(symbol)!.x : 0; - this.stemX = stemInfoDown; - } + this.stemX = this.getStemX(lr.smuflMetrics, lr.getBeatDirection(this.beat!)); } public static getSymbol(duration: Duration): MusicFontSymbol { diff --git a/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts b/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts index 66266adb26..5275ae88c8 100644 --- a/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts @@ -47,41 +47,41 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase { } public override getNoteY(note: Note, requestedPosition: NoteYPosition): number { + if (TabBeatGlyph._isStemEnd(requestedPosition)) { + return this._getStemEndY(requestedPosition); + } if (this.slash) { - return this._getSlashY(requestedPosition); + return this.slash.getNoteHeadY(this.renderer.smuflMetrics, requestedPosition); } return this.noteNumbers ? this.noteNumbers.getNoteY(note, requestedPosition) : 0; } - private _getSlashY(requestedPosition: NoteYPosition): number { - const g = this.slash!; - const smufl = this.renderer.smuflMetrics; - let pos = g.y; - switch (requestedPosition) { - case NoteYPosition.Top: - pos = g.getBoundingBoxTop(); - break; - case NoteYPosition.Center: - break; - case NoteYPosition.Bottom: - pos = g.getBoundingBoxBottom(); - break; - case NoteYPosition.StemUp: - pos -= (smufl.stemUp.has(g.symbol) ? smufl.stemUp.get(g.symbol)!.bottomY : 0) * g.glyphScale; - break; - case NoteYPosition.StemDown: - pos -= (smufl.stemDown.has(g.symbol) ? smufl.stemDown.get(g.symbol)!.topY : 0) * g.glyphScale; - break; - case NoteYPosition.TopWithStem: - pos = -this.renderer.settings.notation.rhythmHeight; - pos -= TabNoteChordGlyph.calculateTremoloHeightForStem(this.renderer, this.container.beat); - break; - case NoteYPosition.BottomWithStem: - pos = this.renderer.height + this.renderer.settings.notation.rhythmHeight; - pos += TabNoteChordGlyph.calculateTremoloHeightForStem(this.renderer, this.container.beat); - break; + private static _isStemEnd(requestedPosition: NoteYPosition): boolean { + return requestedPosition === NoteYPosition.TopWithStem || requestedPosition === NoteYPosition.BottomWithStem; + } + + /** + * Stems end in the rhythm area above or below the staff, independent of the notes. + */ + private _getStemEndY(requestedPosition: NoteYPosition): number { + const rhythmHeight = this.renderer.settings.notation.rhythmHeight; + const tremoloHeight = this._calculateTremoloHeightForStem(); + return requestedPosition === NoteYPosition.TopWithStem + ? -rhythmHeight - tremoloHeight + : this.renderer.height + rhythmHeight + tremoloHeight; + } + + private _calculateTremoloHeightForStem(): number { + const beat = this.container.beat; + if (!beat.isTremolo) { + return 0; + } + if (beat.duration <= Duration.Quarter) { + return 0; } - return pos; + const symbol = TremoloPickingGlyph._getSymbol(beat.tremoloPicking!); + const smufl = this.renderer.smuflMetrics; + return smufl.glyphHeights.has(symbol) ? smufl.glyphHeights.get(symbol)! : 0; } public override getRestY(requestedPosition: NoteYPosition): number { @@ -106,15 +106,21 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase { } public override getLowestNoteY(requestedPosition: NoteYPosition): number { + if (TabBeatGlyph._isStemEnd(requestedPosition)) { + return this._getStemEndY(requestedPosition); + } if (this.slash) { - return this._getSlashY(requestedPosition); + return this.slash.getNoteHeadY(this.renderer.smuflMetrics, requestedPosition); } return this.noteNumbers ? this.noteNumbers.getLowestNoteY(requestedPosition) : 0; } public override getHighestNoteY(requestedPosition: NoteYPosition): number { + if (TabBeatGlyph._isStemEnd(requestedPosition)) { + return this._getStemEndY(requestedPosition); + } if (this.slash) { - return this._getSlashY(requestedPosition); + return this.slash.getNoteHeadY(this.renderer.smuflMetrics, requestedPosition); } return this.noteNumbers ? this.noteNumbers.getHighestNoteY(requestedPosition) : 0; } @@ -226,8 +232,8 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase { // reserve the slash area so stems are not painted through it this.renderer.collisionHelper.reserveBeatSlot( this.container.beat, - this.slash.getBoundingBoxTop(), - this.slash.getBoundingBoxBottom() + this.slash.getNoteHeadY(this.renderer.smuflMetrics, NoteYPosition.Top), + this.slash.getNoteHeadY(this.renderer.smuflMetrics, NoteYPosition.Bottom) ); } diff --git a/packages/alphatab/src/rendering/glyphs/TabNoteChordGlyph.ts b/packages/alphatab/src/rendering/glyphs/TabNoteChordGlyph.ts index 8ad028e360..6211e6472e 100644 --- a/packages/alphatab/src/rendering/glyphs/TabNoteChordGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/TabNoteChordGlyph.ts @@ -1,13 +1,11 @@ import { type Beat, BeatSubElement } from '@coderline/alphatab/model/Beat'; -import { Duration } from '@coderline/alphatab/model/Duration'; import type { Note } from '@coderline/alphatab/model/Note'; import { type ICanvas, TextBaseline } from '@coderline/alphatab/platform/ICanvas'; import type { RenderingResources } from '@coderline/alphatab/RenderingResources'; -import { type BarRendererBase, NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase'; +import { NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase'; import { DeadSlappedBeatGlyph } from '@coderline/alphatab/rendering/glyphs/DeadSlappedBeatGlyph'; import { Glyph } from '@coderline/alphatab/rendering/glyphs/Glyph'; import type { NoteNumberGlyph } from '@coderline/alphatab/rendering/glyphs/NoteNumberGlyph'; -import { TremoloPickingGlyph } from '@coderline/alphatab/rendering/glyphs/TremoloPickingGlyph'; import type { BeatBounds } from '@coderline/alphatab/rendering/utils/BeatBounds'; import { ElementStyleHelper } from '@coderline/alphatab/rendering/utils/ElementStyleHelper'; @@ -85,15 +83,6 @@ export class TabNoteChordGlyph extends Glyph { case NoteYPosition.StemDown: pos = this.y + n.getBoundingBoxBottom(); break; - case NoteYPosition.TopWithStem: - pos = -this.renderer.settings.notation.rhythmHeight; - pos -= TabNoteChordGlyph.calculateTremoloHeightForStem(this.renderer, this.beat); - break; - - case NoteYPosition.BottomWithStem: - pos = this.renderer.height + this.renderer.settings.notation.rhythmHeight; - pos += TabNoteChordGlyph.calculateTremoloHeightForStem(this.renderer, this.beat); - break; } return pos; @@ -101,18 +90,6 @@ export class TabNoteChordGlyph extends Glyph { return 0; } - public static calculateTremoloHeightForStem(renderer: BarRendererBase, beat: Beat) { - if (!beat.isTremolo) { - return 0; - } - if (beat.duration <= Duration.Quarter) { - return 0; - } - const symbol = TremoloPickingGlyph._getSymbol(beat.tremoloPicking!); - const smufl = renderer.smuflMetrics; - return smufl.glyphHeights.has(symbol) ? smufl.glyphHeights.get(symbol)! : 0; - } - public override doLayout(): void { let w: number = 0; From ac1d5bc623a9128a079e672ad4dde933b925d2cf Mon Sep 17 00:00:00 2001 From: danielku15 Date: Tue, 6 Oct 2026 18:15:41 +0200 Subject: [PATCH 6/6] refactor(rendering): share rest positions of beat glyphs Standard notation, slash and tab beats resolve rest positions through a single helper. This also fixes the bottom position of tab rests which resolved to the top of the rest. --- .../rendering/glyphs/BeatOnNoteGlyphBase.ts | 26 ++++++++++++++++++- .../src/rendering/glyphs/ScoreBeatGlyph.ts | 19 +------------- .../src/rendering/glyphs/SlashBeatGlyph.ts | 19 +------------- .../src/rendering/glyphs/TabBeatGlyph.ts | 19 +------------- 4 files changed, 28 insertions(+), 55 deletions(-) diff --git a/packages/alphatab/src/rendering/glyphs/BeatOnNoteGlyphBase.ts b/packages/alphatab/src/rendering/glyphs/BeatOnNoteGlyphBase.ts index 382dfc8d82..7e450f081c 100644 --- a/packages/alphatab/src/rendering/glyphs/BeatOnNoteGlyphBase.ts +++ b/packages/alphatab/src/rendering/glyphs/BeatOnNoteGlyphBase.ts @@ -1,6 +1,8 @@ +import { Duration } from '@coderline/alphatab/model/Duration'; import type { Note } from '@coderline/alphatab/model/Note'; -import type { NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase'; +import { type NoteXPosition, NoteYPosition } from '@coderline/alphatab/rendering/BarRendererBase'; import { BeatGlyphBase } from '@coderline/alphatab/rendering/glyphs/BeatGlyphBase'; +import type { Glyph } from '@coderline/alphatab/rendering/glyphs/Glyph'; import type { BeatBounds } from '@coderline/alphatab/rendering/utils/BeatBounds'; /** @@ -17,4 +19,26 @@ export abstract class BeatOnNoteGlyphBase extends BeatGlyphBase { public abstract getRestY(requestedPosition: NoteYPosition): number; public abstract getHighestNoteY(requestedPosition: NoteYPosition): number; public abstract getLowestNoteY(requestedPosition: NoteYPosition): number; + + /** + * Resolves the requested position on the given rest glyph. + * Rests have no stems, positions with stems reserve the space of a quarter note stem (e.g. for beams passing by). + */ + protected getRestGlyphY(g: Glyph | null, requestedPosition: NoteYPosition): number { + if (!g) { + return 0; + } + switch (requestedPosition) { + case NoteYPosition.TopWithStem: + return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true); + case NoteYPosition.Top: + return g.getBoundingBoxTop(); + case NoteYPosition.Bottom: + return g.getBoundingBoxBottom(); + case NoteYPosition.BottomWithStem: + return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true); + } + // center and stem positions + return g.getBoundingBoxTop() + g.height / 2; + } } diff --git a/packages/alphatab/src/rendering/glyphs/ScoreBeatGlyph.ts b/packages/alphatab/src/rendering/glyphs/ScoreBeatGlyph.ts index e74cba30d5..dd39c0f5c0 100644 --- a/packages/alphatab/src/rendering/glyphs/ScoreBeatGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/ScoreBeatGlyph.ts @@ -114,24 +114,7 @@ export class ScoreBeatGlyph extends BeatOnNoteGlyphBase { } public override getRestY(requestedPosition: NoteYPosition): number { - const g = this.restGlyph; - if (g) { - switch (requestedPosition) { - case NoteYPosition.TopWithStem: - return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true); - case NoteYPosition.Top: - return g.getBoundingBoxTop(); - case NoteYPosition.Center: - case NoteYPosition.StemUp: - case NoteYPosition.StemDown: - return g.getBoundingBoxTop() + g.height / 2; - case NoteYPosition.Bottom: - return g.getBoundingBoxBottom(); - case NoteYPosition.BottomWithStem: - return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true); - } - } - return 0; + return this.getRestGlyphY(this.restGlyph, requestedPosition); } public applyRestCollisionOffset() { diff --git a/packages/alphatab/src/rendering/glyphs/SlashBeatGlyph.ts b/packages/alphatab/src/rendering/glyphs/SlashBeatGlyph.ts index 4b6e80a854..3fe65e3771 100644 --- a/packages/alphatab/src/rendering/glyphs/SlashBeatGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/SlashBeatGlyph.ts @@ -79,24 +79,7 @@ export class SlashBeatGlyph extends BeatOnNoteGlyphBase { } public override getRestY(requestedPosition: NoteYPosition): number { - const g = this.restGlyph; - if (g) { - switch (requestedPosition) { - case NoteYPosition.TopWithStem: - return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true); - case NoteYPosition.Top: - return g.getBoundingBoxTop(); - case NoteYPosition.Center: - case NoteYPosition.StemUp: - case NoteYPosition.StemDown: - return g.getBoundingBoxTop() + g.height / 2; - case NoteYPosition.Bottom: - return g.getBoundingBoxBottom(); - case NoteYPosition.BottomWithStem: - return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true); - } - } - return 0; + return this.getRestGlyphY(this.restGlyph, requestedPosition); } public override getNoteY(_note: Note, requestedPosition: NoteYPosition): number { diff --git a/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts b/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts index 5275ae88c8..1684c84dd0 100644 --- a/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts +++ b/packages/alphatab/src/rendering/glyphs/TabBeatGlyph.ts @@ -85,24 +85,7 @@ export class TabBeatGlyph extends BeatOnNoteGlyphBase { } public override getRestY(requestedPosition: NoteYPosition): number { - const g = this.restGlyph; - if (g) { - switch (requestedPosition) { - case NoteYPosition.TopWithStem: - return g.getBoundingBoxTop() - this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true); - case NoteYPosition.Top: - return g.getBoundingBoxTop(); - case NoteYPosition.Center: - case NoteYPosition.StemUp: - case NoteYPosition.StemDown: - return g.getBoundingBoxTop() + g.height / 2; - case NoteYPosition.Bottom: - return g.getBoundingBoxTop(); - case NoteYPosition.BottomWithStem: - return g.getBoundingBoxBottom() + this.renderer.smuflMetrics.getStemLength(Duration.Quarter, true); - } - } - return 0; + return this.getRestGlyphY(this.restGlyph, requestedPosition); } public override getLowestNoteY(requestedPosition: NoteYPosition): number {