From 0c1d2fa8718852d4ae5e0ce5774b462bc72c2d74 Mon Sep 17 00:00:00 2001 From: Chuck <33324927+ChuckBuilds@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:32:05 -0400 Subject: [PATCH] fix(countdown): make every offered font work, and document every setting Only two of the five font families in the picker actually rendered. Hashing a render per family showed three producing byte-identical output -- the default face -- from two separate causes: 9bd0dbce77: ['press_start'] b59efdfd01: ['four_by_six'] d6e4d86478: ['tom_thumb', 'tiny', 'picopixel'] tiny and picopixel have no font file in the core or the plugin, so they could only ever fall back; they are removed. The rest is the .bdf pixel-size problem: a bitmap face exists at exactly one size and FreeType rejects any other, so asking for font_size (8) failed for every bitmap family. Bitmap faces are now loaded at the size the file's own PIXEL_SIZE header declares. That alone was not enough for five_by_seven. The plugin only reached its own loader when the core FontManager's catalog *missed* a family, and the catalog has five_by_seven -- so resolve_font returned a silently degraded default and the plugin trusted it. Bitmap families now go through the plugin's loader regardless of what the catalog claims. tom_thumb is also wired up: tom-thumb.bdf was always shipped, just absent from the family map. All four families in the new enum now render distinctly, verified the same way: 9bd0dbce77 press_start | b59efdfd01 four_by_six eb2d9294c8 five_by_seven | 0ed03a8f38 tom_thumb The README is rewritten around real screenshots and documents all 40 settings, including the per-entry layout and style override objects. It also corrects the countdown-value ladder, which the old version got wrong: there is no "1 Day" and no "TODAY!" anywhere in the code. Between one and two days out the plugin writes "Tomorrow" with no number, and a passed countdown reads "Nd ago" and is hidden entirely unless show_expired is on. Every rung is now shown as a real render. The stale Pillow >= 9.0.0 requirement is corrected to >= 12.2.0. Audits before opening: no config token dropped, all 40 schema leaves documented, no broken TOC anchors, and the dropped-section check surfaced the dependency and technical notes, which are restored. Harness 8/8 with goldens unchanged -- the harness pins press_start, a .ttf, so the font work does not move them. Co-Authored-By: Claude Opus 5 --- README.md | 2 +- docs/assets/countdown/count-formats.png | Bin 0 -> 20552 bytes docs/assets/countdown/fonts.png | Bin 0 -> 15988 bytes docs/assets/countdown/hero.png | Bin 0 -> 2628 bytes docs/assets/countdown/layout-presets.png | Bin 0 -> 15431 bytes docs/assets/countdown/modes.png | Bin 0 -> 7252 bytes docs/assets/countdown/panel-sizes.png | Bin 0 -> 26040 bytes docs/assets/countdown/sample/gift.png | Bin 0 -> 361 bytes docs/assets/countdown/shots.json | 561 +++++++++++++++++++++++ docs/assets/countdown/text-align.png | Bin 0 -> 10202 bytes plugins.json | 4 +- plugins/countdown/README.md | 524 +++++++++++++-------- plugins/countdown/config_schema.json | 14 +- plugins/countdown/manager.py | 48 +- plugins/countdown/manifest.json | 10 +- 15 files changed, 954 insertions(+), 209 deletions(-) create mode 100644 docs/assets/countdown/count-formats.png create mode 100644 docs/assets/countdown/fonts.png create mode 100644 docs/assets/countdown/hero.png create mode 100644 docs/assets/countdown/layout-presets.png create mode 100644 docs/assets/countdown/modes.png create mode 100644 docs/assets/countdown/panel-sizes.png create mode 100644 docs/assets/countdown/sample/gift.png create mode 100644 docs/assets/countdown/shots.json create mode 100644 docs/assets/countdown/text-align.png diff --git a/README.md b/README.md index 13432c47..75604af4 100644 --- a/README.md +++ b/README.md @@ -150,7 +150,7 @@ curl -X POST http://your-pi-ip:5000/api/v3/plugins/install \ | Plugin | Description | Preview | |--------|-------------|---------| | [Flight Tracker](./plugins/ledmatrix-flights/) | Real-time ADS-B aircraft tracking with map display | | -| [Countdown Display](./plugins/countdown/) | Customizable countdowns for birthdays, events, holidays | | +| [Countdown Display](./plugins/countdown/) | Customizable countdowns for birthdays, events, holidays | countdown on an LED panel | | [Election Results](./plugins/ledmatrix-elections/) | Live election results ticker with full-screen race interrupts | | ### Holiday (1) diff --git a/docs/assets/countdown/count-formats.png b/docs/assets/countdown/count-formats.png new file mode 100644 index 0000000000000000000000000000000000000000..e3557725ddcefdd8aa8d2b72b80895189658d2ab GIT binary patch literal 20552 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 Lb)n*C_Xqz4xD;zN literal 0 HcmV?d00001 diff --git a/docs/assets/countdown/layout-presets.png b/docs/assets/countdown/layout-presets.png new file mode 100644 index 0000000000000000000000000000000000000000..d20f459b02c966919269bbda739d5698e91879f2 GIT binary patch literal 15431 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 QwgfsF2I}bFet-D?0K>zI!TVUHA37Yht9k>t~Ul zArQzey+5?gAdv0a5XhEacl-dZP&J5C5XeDMJ?*m=AtQ_9;nxzUs+B*eJ>32J;OV2k zK2-SmPHg6-#;6Yr6In}(>_fO9Pqp+%ewWO3VJ$B^BNWIsMXC>qan$Z{JK4k_VL$U< zu~OQvL;l!uJ#+hxJ7=PP-~Pi-WT;na`nf&lcBnj@XUY^^(V&!P(Deq0a=s)rEWAr| z3(x~1dsft7b3L=8d~^BjmhI+p>%`8@<=oS4n~OxprpjNpb~hZ`ytd=A(B|^bt*G5@ zo5H@z_`3CtjBmw%mGO=EuQL8hd`0w$`FjeTt;Ee=-Bgl2dUQ-Y?DMm!_GI2h3(SZe zurk?HU0rPq<5b9QgH&XA&OMCX0r~A>O8>#Yg(1J5Y&`;(S}J9f_B7;oB_*mFnPC*e zFLuN@<0=voHK3>SBQU%$R7A!9Q&%K+?WIf05h*Dt@!=|8a=RSiqMaSH$3V^&3duy6 z32uQH9va@$*TtceBY7OUJs;$Z&J{68l>1q0H%pR?w2aJrPO_t2wg1#ubwHQ;S_g{5 z)S&j6wFJDM zQmD`4Z4kvLLZ*f~jor_*pvd+2%H)WEskX$mnJ&UK+Ka#RjwcqoCt3i?{ro(>o;qN! zqM}k-N3(N&h|c{Hvb%-SRiOj=;|jJkI_X9-^XFmBNtw_>?xfD zPzm|;NVf#Ps&D@z#QjGi9u3|-sL-XJkd&0P0Vkk1z!uF&F1u#XV&A@fdjT*M;6_tO z(XjQ;C$F?VWUS2$D&}~PRu1onoH>{m?v@_0`cA>EqqVcssLux2WmE2EXk|43oSC+9 zW2YSc#G#&^9u%z{bNKM#eZT&CAw!+PUh9fns-8JC*`d&dpNJOSykY%0R7d;l**DpG z3C;0Pf^$>s23_2d^s6obK}q=fbwDo;&K$=X*IWoKuT^sKbg z{M(L}yAj*k+U#lqJ+j;xnIh#=}*e^XOaye9KAm&4ji z9d3OjIa=il72GtY1~DJBFtpOR*Vsa%E#LI{9?QN8k6wV+%8->wO(avh@u7qkjY8(# zgt9LIHCpz#6$7}u&(ZgaSf%OL+AaqxKo_hA-5B_ z^XTD5CXz)SqF#z&X0yk_tzBZD_jg5^x z?XBs1si+#j9+CoJnvm@GQv6a;7Z|Z;}dlAo%Sr<-pI~6W95~0S9tT>4LFw zaUWAz-b$@cAW_#`tt4$;IyaH(!X+mwM}0&U-A(N5M(!O@7^(CY74*fgG=um^OG_Ve z9;xui%LtpRs;3rX*q@{HrjQ05!@Uo3_Sw&5g!`nP^1dnp$;2vJDj_)3L1T<2a+8&2B5w2&b_pg$b_!-+`C0 z1_@l;>Pn75awi5x$jX{bw030Bst6kH-I>~t0$Xpdj8=MwgKYC5aAc_jn2eRb{fp|pbM4Gj$~;^9{Zifq$W5n-$Eq=1`m^vfRxtiO6f=8$8^#BG4; z3&7T)7ncEAfD_QLNG=6MuWj@d+yo@Zeo#r&LG2k2CZ_{V{umE)q|So(41=67R37gm zMDohY$_!Ia9rhu=05t;`n}Haxm8)Af!?hsUD=iB0@@3`a0^;f$c`y$4)vFMo4k9f; zPwySj6;4F=0||}3ptz&;6D-UNt%{i&ALIFc+5a#t4IjETeS4>{CTG1@S8WZ1sU$P= z5_5f4EBcqCa&kOCRb;@?c7V%GinB8`STWvXCnW_Ple{OMfFM>8 z%-e;tb90SSRJ~~$ygXw}{e6{}&B2X1?&t1b%i0peHuB=Tm*c!otqJnO~4I%)lK?OkOaWMSW9F ztULycL7g8suYLafbZ{wQrLC}DBXH<|{m-X_g@vK=f8-gZmtrE;EUldaMm+K;qgBc( zDz1woO8uHEwS(l4&ei}H6?(#leM_0gXdagAWGqpY9V6bka+m@`53785T#lGp5I%f* zA((_J_YZS*Wd&U&qb}24O;HHSj9hzZIBOQ&58IfCw<@u5G&MCv$cw24;+bf#(i-N- z&Sp2th~+CzVQEjVLP?!ZPolMxLf8eX^XHp|F_Ea{OFD*pXk-_1v?>zu(Xi^m(~uQC zsq_j7Uyg)N{+xPZ#VmIqS(n(~kecS9RgpZqkSJkW0WujC|C z=MZzzyF&T+7fP9pS_NeQE7lej@VVluMY&NYlNZ$^4Bf_s@TJi@ufhAu#zgU8Q`7eu z>-X+a##J@K$8-c=dbn^U+WkXrcc0GDacIks6rX&3ok!d_-XU(CEO==0#>-*cudzz$ zPgMC48 zn0jhyxIBDheN_2sE317hnFd<)CQIv5n|k%hOH(^QovuRi_~r+TIbgFARZY$MNtgmU zJrG^67X$O77K{Zgjubl)mSB;*tJ-NunhNh?*#^cJuX%OoKuWqQk~SRu> ztY;^~N*$^1KEI!VUvWcm8F1Xee23aTLI={1IA@vcRi@(n-}x|#IEMZF_`Cpa+j6_F4t)-sh-v8ktZ-aT1C#o zl$0=aWg0bsq?(IM=#(X}gd zzLe9MEf8Rp1lmCRrwE(dCdm!6Lk4k@NgB1mlmgCf%Y>rF!jkqKbz#(!sSAgjv9#cyhYLj||;?P6YmN+;`3;Nhs!t1~vcOy!<(bP79n= z&T%aP9$g{~{LDoC41nFCKlDk}eM$eK9d4MXokTF0J$~WX8m#@D%EJ z3|EKC6P*mz(enbSYy_w|_sJm~muZcnFmj{4G;ke>9VaTaZ!ZraJGo2F+;4Y|K7Br85de7FmN0g)2U%W$(f`M!yGi&}m$wG4}1;FYx3mj@(b4Ptkm@@0eG|luCOU& zl_1a-KQLCu8Iui;*G&?f(s})r-bfZzRCWE{oqx#47#JDp85tQ}98b^4Kv|S?YO=Ef zl8^JOM1&S+$8ro@sRP)6L^$Vl7&%2c zlpzexHolp7+oi7_^5OM$kP2xSrAmj|py!=p=_$jr`EIEg3^~^@^{2gZv*3HP%dxIY z)vK}jzNn;gLu1@%6{Wj6V8-Bh_0_4^DTIoNET2Zm@&}M*{Jufvy=HxiF?U++2|j(H z&p{jYt02a^Rc*dVP%U`j^rut1bki(lKPoqDGL@Can4s?VD52ublxfDhH#B*8c!2(` ze!Zt1#$3KxL&#E162cW?&$*}dWYNlI?!=raUMi{ERo1e>3qPrJKyJHP6+4`%>j-@bkO z=Js9n3#&4q=jF}jd8s1i(o$J|+7YAmYqRbBqqf$ zwH_3Y820&-a=zm#l3jJ{W$mEvTYo17T^zA|T7_B!{gXr3@II}WT~0V@dR-3X2#RRK zx-M;fsW1Zpo-0L6ZkNPff47 zCe(+2Y&D&kE_R}K9rit_9^&q8Vw)&t7Sd-{W5FqV@6ri$Q(m!-fybYQe{5zqq^rsB zmHu~d65?T?2FO?+larH+msa*EZQrqV`!9IT18Bd0Zh^+p{(92e7*+No&$vajRX_bG zglK5im4URIXcs@}d?oCBGobmGTOknsldKF@MhH8(i9AOBS!&ytqA_H)K0E5$Pw-Di zfoWFMwXGY|6atr479arr?&fyv1`8zXfMW_`A=Z=4+J-Yac!I{0PfKCJnM~)uF+TOq5HDpQ?fRJ{*S;DPaWn6RpGTi(tMSY@o z6LZU>HAO&3cb9w_kbe8h{}m5qt#grY&#PEzVcXN@{ny{NVB48}zE|u9xGE>7%?aaP zu&p7=>Lev&m-@K${r%?xQZLVAbHV8He@tNibVPc~C%Qr#G5bTWiG#XGIoZ&KcBR5z zBv06JjSSm`YY_>6v5dByULmEY@@kC@F1}Qw z51{Fqc5i)DP!8efAYMh|cww-_8H2o_27bPV#J`;x*Bm%kvCNGQD|~N8>spx~&G!P% zM!x3pe{0~Mj+jB-Xcx}L4w<9}&h^lx~Nv=IKMxB<)IFV=D5P z<_Nxtw!fWEkN+ZJXTg^cEmB=gwZW1j0;R7O^q)RSGoDWohzeF+Gv(MpSqHwF_1&}T zze3A@Y@Gftj432v{mI>rUtB$;u~biA-?PG{-?+E)#j^(_cJR603^9nE7Os1YR4O{h zF~w!tL3UXG?S%fb+YY}5YNkg=r*2Gq#D*l|K=rc>OM;)>q4}+ub|!U-7St>|6IsZ2 zVkV0#-%_e&q6H-8A2`)_8e#i(VC>P%MzL}4Qh)P;FM{nhR6%Z!@|@V=Bz*NBk z>1q4P+lbysku|AQ&ff1zt2;tNC$)KT?uqH~BA$b|#(ImEQl~vzSkZ<^T0{!36mNCAZHh#oE1Gc5Xi34RO;(gM{%Oi zm2urGta^xh2gbqB-sSV-T;L8xp646x^{;gF%~{L0=OlmxZlP`vbAi8!)yLO7@*hYP z=at*L1m#~Pa-u}m)O3Q4G7nqrywBK6+2G(cOe1Rwge-Qxcdo^bzzlJk1;p>8JSh|F zM|+>=-5qz1e*anQ#@t}s-b^I|U5sEoF;nb$7I!J8s*~fQd;1$&`O^&QChm^v?%D=1 zk-fzG9{&7t67~0|NPim5ZyfhkhP&9~_!Wk*A-?U<50K3QE~2~HC=_j;bU`M%e%UJ}(=pO6Id|#Ce*x-j+6n*w literal 0 HcmV?d00001 diff --git a/docs/assets/countdown/panel-sizes.png b/docs/assets/countdown/panel-sizes.png new file mode 100644 index 0000000000000000000000000000000000000000..4cb30d7f40437d8f2be80164fa851dc061257a57 GIT binary patch literal 26040 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 literal 0 HcmV?d00001 diff --git a/docs/assets/countdown/sample/gift.png b/docs/assets/countdown/sample/gift.png new file mode 100644 index 0000000000000000000000000000000000000000..a4b653a97352ca043c66ca1341eb4a3abe7ad1fd GIT binary patch literal 361 zcmeAS@N?(olHy`uVBq!ia0vp^4j|0I1|(Ny7T#lEU^MV_aSW-L^Y)r{(P0CT)`#)w zXSlq&cC~+K(pl8~(rDKVJtc+L8XR1P26^f_k3as<)I2xmx8u)Wzr3z-J8>wsJkXl@ z&!ODz-`Cr5c7B@Sf5H|mSa|M9&i$_pCBm-X=j->LSDor{OaEIP7r$+^)PEE2gcT1~ z^6B*6*Lvz#@&9;HURm+ycX@1Y<{GG<;VeEix8Mzjbk=>L|02c+gpbW$(Wiw)cD8FVCy#4!bwqi2Auor_n6<;!>;n%K;Jle^`1iPH7i^&lvmA zcSYOA$<8x`pS4&fu3NcR`TbiFfoHQ;Hr&s?r~Nk3&F5WypCCxi52A!HWxK0Ez=^}= aoV~AK)%1_0Nix77WbkzLb6Mw<&;$VXqoKI~ literal 0 HcmV?d00001 diff --git a/docs/assets/countdown/shots.json b/docs/assets/countdown/shots.json new file mode 100644 index 00000000..9bef9713 --- /dev/null +++ b/docs/assets/countdown/shots.json @@ -0,0 +1,561 @@ +{ + "plugin": "countdown", + "defaults": { + "width": 128, + "height": 32, + "scale": 6, + "freeze_time": "2026-09-02T15:00:00+00:00", + "config": { + "enabled": true, + "show_expired": false + } + }, + "shots": [ + { + "name": "hero", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "image_path": "docs/assets/countdown/sample/gift.png", + "layout_preset": "image-left" + } + ] + } + }, + { + "name": "layout-image-left", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "image_path": "docs/assets/countdown/sample/gift.png", + "layout_preset": "image-left" + } + ] + }, + "standalone": false + }, + { + "name": "layout-image-right", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "image_path": "docs/assets/countdown/sample/gift.png", + "layout_preset": "image-right" + } + ] + }, + "standalone": false + }, + { + "name": "layout-text-only", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "image_path": "docs/assets/countdown/sample/gift.png", + "layout_preset": "text-only" + } + ] + }, + "standalone": false + }, + { + "name": "layout-image-only", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "image_path": "docs/assets/countdown/sample/gift.png", + "layout_preset": "image-only" + } + ] + }, + "standalone": false + }, + { + "name": "mode-until", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "New Year", + "target_date": "2027-01-01", + "mode": "until", + "layout_preset": "text-only" + } + ] + }, + "standalone": false + }, + { + "name": "mode-since", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Launched", + "target_date": "2026-04-07", + "mode": "since", + "layout_preset": "text-only" + } + ] + }, + "standalone": false + }, + { + "name": "align-left", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "layout_preset": "text-only", + "text_align": "left" + } + ] + }, + "standalone": false + }, + { + "name": "align-center", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "layout_preset": "text-only", + "text_align": "center" + } + ] + }, + "standalone": false + }, + { + "name": "align-right", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "layout_preset": "text-only", + "text_align": "right" + } + ] + }, + "standalone": false + }, + { + "name": "size-64x32", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "image_path": "docs/assets/countdown/sample/gift.png" + } + ] + }, + "width": 64, + "height": 32, + "scale": 8, + "standalone": false + }, + { + "name": "size-128x32", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "image_path": "docs/assets/countdown/sample/gift.png" + } + ] + }, + "width": 128, + "height": 32, + "scale": 8, + "standalone": false + }, + { + "name": "size-128x64", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "image_path": "docs/assets/countdown/sample/gift.png" + } + ] + }, + "width": 128, + "height": 64, + "scale": 8, + "standalone": false + }, + { + "name": "size-256x32", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "image_path": "docs/assets/countdown/sample/gift.png" + } + ] + }, + "width": 256, + "height": 32, + "scale": 4, + "standalone": false + }, + { + "name": "fmt-days", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "layout_preset": "text-only", + "name": "Christmas", + "target_date": "2026-12-25" + } + ] + }, + "standalone": false + }, + { + "name": "fmt-tomorrow", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "layout_preset": "text-only", + "name": "Results Day", + "target_date": "2026-09-04" + } + ] + }, + "standalone": false + }, + { + "name": "fmt-hours", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "layout_preset": "text-only", + "name": "Doors Open", + "target_date": "2026-09-02", + "target_time": "21:30" + } + ] + }, + "standalone": false + }, + { + "name": "fmt-minutes", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "layout_preset": "text-only", + "name": "Standup", + "target_date": "2026-09-02", + "target_time": "11:30" + } + ] + }, + "standalone": false + }, + { + "name": "fmt-now", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "layout_preset": "text-only", + "name": "Liftoff", + "target_date": "2026-09-02", + "target_time": "11:00" + } + ] + }, + "standalone": false + }, + { + "name": "fmt-expired", + "config": { + "countdowns": [ + { + "id": "a", + "enabled": true, + "layout_preset": "text-only", + "name": "Deadline", + "target_date": "2026-08-30" + } + ], + "show_expired": true + }, + "standalone": false + }, + { + "name": "font-press_start", + "standalone": false, + "config": { + "font_family": "press_start", + "name_font_family": "press_start", + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "layout_preset": "text-only" + } + ] + } + }, + { + "name": "font-four_by_six", + "standalone": false, + "config": { + "font_family": "four_by_six", + "name_font_family": "four_by_six", + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "layout_preset": "text-only" + } + ] + } + }, + { + "name": "font-five_by_seven", + "standalone": false, + "config": { + "font_family": "five_by_seven", + "name_font_family": "five_by_seven", + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "layout_preset": "text-only" + } + ] + } + }, + { + "name": "font-tom_thumb", + "standalone": false, + "config": { + "font_family": "tom_thumb", + "name_font_family": "tom_thumb", + "countdowns": [ + { + "id": "a", + "enabled": true, + "name": "Christmas", + "target_date": "2026-12-25", + "layout_preset": "text-only" + } + ] + } + } + ], + "composites": [ + { + "name": "layout-presets", + "columns": 2, + "cells": [ + { + "shot": "layout-image-left", + "label": "image-left", + "sublabel": "the default: image on the left third" + }, + { + "shot": "layout-image-right", + "label": "image-right", + "sublabel": "mirrored" + }, + { + "shot": "layout-text-only", + "label": "text-only", + "sublabel": "any image is ignored" + }, + { + "shot": "layout-image-only", + "label": "image-only", + "sublabel": "no text at all" + } + ] + }, + { + "name": "count-formats", + "columns": 2, + "cells": [ + { + "shot": "fmt-days", + "label": "\"113 Days\"", + "sublabel": "more than 2 days away" + }, + { + "shot": "fmt-tomorrow", + "label": "\"Tomorrow\"", + "sublabel": "1 to 2 days away -- no number is shown" + }, + { + "shot": "fmt-hours", + "label": "\"10h 30m\"", + "sublabel": "1 to 24 hours away" + }, + { + "shot": "fmt-minutes", + "label": "\"30m\"", + "sublabel": "1 to 60 minutes away" + }, + { + "shot": "fmt-now", + "label": "\"NOW!\"", + "sublabel": "less than a minute away" + }, + { + "shot": "fmt-expired", + "label": "\"3d ago\"", + "sublabel": "past, and only visible with show_expired on" + } + ] + }, + { + "name": "modes", + "columns": 2, + "cells": [ + { + "shot": "mode-until", + "label": "mode: until", + "sublabel": "the default; counts down to the date" + }, + { + "shot": "mode-since", + "label": "mode: since", + "sublabel": "counts up from a date that has passed" + } + ] + }, + { + "name": "text-align", + "columns": 1, + "cells": [ + { + "shot": "align-left", + "label": "text_align: left", + "sublabel": "" + }, + { + "shot": "align-center", + "label": "text_align: center", + "sublabel": "the default" + }, + { + "shot": "align-right", + "label": "text_align: right", + "sublabel": "" + } + ] + }, + { + "name": "fonts", + "columns": 2, + "cells": [ + { + "shot": "font-press_start", + "label": "press_start", + "sublabel": "the default; chunky 8-bit, scalable" + }, + { + "shot": "font-four_by_six", + "label": "four_by_six", + "sublabel": "scalable; fits more per line" + }, + { + "shot": "font-five_by_seven", + "label": "five_by_seven", + "sublabel": "bitmap, drawn at its native 7px" + }, + { + "shot": "font-tom_thumb", + "label": "tom_thumb", + "sublabel": "bitmap, native 6px -- the smallest" + } + ] + }, + { + "name": "panel-sizes", + "columns": 1, + "cells": [ + { + "shot": "size-64x32", + "label": "64 x 32", + "sublabel": "image and text share very little width" + }, + { + "shot": "size-128x32", + "label": "128 x 32", + "sublabel": "the common two-panel chain" + }, + { + "shot": "size-128x64", + "label": "128 x 64", + "sublabel": "the image gets real room" + }, + { + "shot": "size-256x32", + "label": "256 x 32", + "sublabel": "a long chain" + } + ] + } + ] +} diff --git a/docs/assets/countdown/text-align.png b/docs/assets/countdown/text-align.png new file mode 100644 index 0000000000000000000000000000000000000000..71f2925da1b8b1e9d6bbec0701eafb529e49308f GIT binary patch literal 10202 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 Q&K{znqK!`d+qvKV1LqxK!vFvP literal 0 HcmV?d00001 diff --git a/plugins.json b/plugins.json index 610c1736..a087fd6e 100644 --- a/plugins.json +++ b/plugins.json @@ -187,10 +187,10 @@ "plugin_path": "plugins/countdown", "stars": 0, "downloads": 0, - "last_updated": "2026-08-13", + "last_updated": "2026-09-02", "verified": true, "screenshot": "", - "latest_version": "3.2.1", + "latest_version": "3.3.0", "icon": "fa-clock" }, { diff --git a/plugins/countdown/README.md b/plugins/countdown/README.md index 391e8859..5f23fc1e 100644 --- a/plugins/countdown/README.md +++ b/plugins/countdown/README.md @@ -1,253 +1,393 @@ -# Countdown Plugin for LEDMatrix +# Countdown -Display customizable countdowns with images on your LED matrix. Perfect for birthdays, holidays, vacations, events, and any special occasion you want to count down to! +Count down to the things you care about — birthdays, holidays, a holiday, a +launch — or count up from a date that has passed. Each countdown gets a name, a +target, an optional image, and its own styling, and the plugin rotates through +however many you configure. -## Features +![A countdown to Christmas on a 128x32 panel: a gift image on the left, the +name and "113 Days" on the right](../../docs/assets/countdown/hero.png) -- **Multiple Countdowns**: Create and manage multiple countdown entries -- **Custom Images**: Use a unique local image path for each countdown -- **Individual Control**: Enable/disable each countdown independently -- **Customizable Display**: Configure fonts, colors, and sizes -- **Smart Layout**: Image on left 1/3rd, countdown text on right 2/3rds -- **Auto Rotation**: Automatically cycles through enabled countdowns -- **Flexible Dates**: Supports future dates with automatic "TODAY!" display -- **Web UI Management**: Full configuration via LEDMatrix web interface +*Every image in this README is real plugin output, rendered at the true panel +size against a frozen clock so the figures are reproducible. The gift icon is a +plain sample standing in for your own image.* -## Installation - -### From Plugin Store (Recommended) - -1. Open the LEDMatrix web interface (`http://your-pi-ip:5000`) -2. Open the **Plugin Manager** tab -3. Find **Countdown Display** in the **Plugin Store** section and click - **Install** - -### Manual Installation - -1. Copy the plugin from the monorepo: - ```bash - cp -r ledmatrix-plugins/plugins/countdown /path/to/LEDMatrix/plugin-repos/ - ``` +--- -2. Restart LEDMatrix or reload plugins via the web UI +## Table of Contents -## Configuration +1. [What's On Screen](#whats-on-screen) +2. [Installation](#installation) +3. [Quick Start](#quick-start) +4. [How the Countdown Value Is Written](#how-the-countdown-value-is-written) +5. [Per-Countdown Settings](#per-countdown-settings) + - [Layout presets](#layout-presets) + - [Text alignment](#text-alignment) + - [until and since](#until-and-since) + - [Per-countdown overrides](#per-countdown-overrides) +6. [Global Settings](#global-settings) + - [Fonts and colours](#fonts-and-colours) + - [Images](#images) +7. [Panel Sizes](#panel-sizes) +8. [Troubleshooting](#troubleshooting) +9. [Development](#development) +10. [Support](#support) -### Adding a Countdown +--- -1. Open the LEDMatrix web UI -2. Open the **Countdown Display** tab (second nav row, added once the - plugin is installed) -3. Click "Add Countdown" -4. Fill in the details: - - **Name**: Display name (e.g., "Birthday", "Vacation") - - **Target Date**: The date you're counting down to - - **Image Path**: Enter a local path (e.g., `assets/plugins/countdown/uploads/birthday.png`) - - **Enabled**: Toggle to show/hide this countdown -5. Click Save +## What's On Screen -### Configuration Options +One countdown at a time, drawn as an optional image beside two lines of text: -The web UI form is generated from `config_schema.json` (the source of truth). - -#### Per-Countdown Settings - -Each entry in the **Countdowns** table supports: - -| Field | Default | Notes | -|-------|---------|-------| -| `name` (required) | — | Display name, max 30 chars | -| `target_date` (required) | — | `YYYY-MM-DD` — the date to count to/from | -| `target_time` | `00:00` | Optional `HH:MM` (24h) for sub-day precision; switches to hours/minutes as the event nears | -| `mode` | `until` | `until` counts down to the date (Days Until); `since` counts up from it (Days Since) | -| `layout_preset` | `image-left` | `image-left`, `image-right`, `text-only`, or `image-only` | -| `text_align` | `center` | `left`, `center`, or `right` for the name/value text | -| `image_path` | — | Optional image shown alongside the countdown | -| `enabled` | `true` | Show/hide this countdown | -| `display_order` | `0` | Rotation order (lower first) | -| `layout.*` | auto | Optional pixel position/size overrides for the image and text | -| `style.*` | inherit | Optional per-countdown font/size/color overrides (`null` inherits the global settings below) | +```text +┌──────────────────────────────┐ +│ │ Countdown name │ +│ IMAGE │ │ +│ (1/3) │ 113 Days │ +└──────────────────────────────┘ +``` -#### Global Settings (defaults for all countdowns) +With several countdowns configured the plugin rotates through them in +`display_order`, holding each for `display_duration` seconds. -| Key | Default | Notes | -|-----|---------|-------| -| `display_duration` | `15` | Seconds to show each countdown before rotating (5–300) | -| `font_family` | `press_start` | One of `press_start`, `four_by_six`, `tom_thumb`, `tiny`, `picopixel` | -| `font_size` | `8` | Countdown value font size (4–16px) | -| `font_color` | `[255, 255, 255]` | RGB color for the countdown value | -| `name_font_size` | `8` | Countdown name font size (4–16px) | -| `name_font_color` | `[200, 200, 200]` | RGB color for the countdown name | -| `background_color` | `[0, 0, 0]` | RGB background color | -| `show_expired` | `false` | Show `until` countdowns that have passed (as "Nd ago") | -| `fit_to_display` | `true` | Auto-scale images to their allocated area | -| `preserve_aspect_ratio` | `true` | Keep image proportions when scaling | +--- -## Display Layout +## Installation -The plugin uses a split layout for optimal readability: +**From the Plugin Store (recommended).** Open the LEDMatrix web interface at +`http://:5000`, go to **Plugin Manager**, find **Countdown Display** +in the **Plugin Store** section, and click **Install**. Countdowns are managed +from the plugin's own tab. -``` -┌──────────────────────────────┐ -│ │ │ -│ IMAGE │ Countdown │ -│ (1/3) │ Name │ -│ │ │ -│ │ 15 Days │ -│ │ │ -└──────────────────────────────┘ -``` +**Manually.** Copy this directory into your LEDMatrix `plugin-repos/` and +restart the display service. -### Display Formats +--- -- **Multiple Days**: "15 Days", "100 Days" -- **One Day**: "1 Day" -- **Event Day**: "TODAY!" (shown in bright yellow) +## Quick Start -## Examples +A countdown needs only a `name` and a `target_date` — those two are the schema's +only required fields: -### Birthday Countdown ```json { - "name": "Mom's Birthday", - "target_date": "2026-06-15", - "enabled": true, - "image_path": "assets/plugins/countdown/uploads/birthday-cake.png" + "countdown": { + "enabled": true, + "countdowns": [ + { "name": "Christmas", "target_date": "2026-12-25" } + ] + } } ``` -### Vacation Countdown +Several countdowns, with images and ordering: + ```json { - "name": "Hawaii Trip", - "target_date": "2026-07-20", - "enabled": true, - "image_path": "assets/plugins/countdown/uploads/beach.jpg" + "countdown": { + "enabled": true, + "display_duration": 15, + "countdowns": [ + { + "name": "Christmas", + "target_date": "2026-12-25", + "image_path": "assets/countdown/tree.png", + "layout_preset": "image-left", + "display_order": 1 + }, + { + "name": "Doors Open", + "target_date": "2026-09-02", + "target_time": "21:30", + "layout_preset": "text-only", + "display_order": 2 + }, + { + "name": "Launched", + "target_date": "2026-04-07", + "mode": "since", + "display_order": 3 + } + ] + } } ``` -### Holiday Countdown +--- + +## How the Countdown Value Is Written + +The second line adapts to how close the event is. It is **not** always a day +count, and there is no separate "today" state: + +| Time to target | Renders as | Example | +|----------------|------------|---------| +| More than 2 days | `N Days` | `113 Days` | +| 1 to 2 days | `Tomorrow` | *(no number at all)* | +| 1 to 24 hours | `Nh Nm` | `10h 30m` | +| 1 to 60 minutes | `Nm` | `30m` | +| Under a minute | `NOW!` | | +| Already passed | `Nd ago` | `3d ago` | + +![Six panels showing each rung of that ladder: 113 Days, Tomorrow, 10h 30m, +30m, NOW! and 3d ago](../../docs/assets/countdown/count-formats.png) + +Two consequences worth knowing: + +- **The hours-and-minutes rungs only appear if you set `target_time`.** Without + it the target is midnight, so an event "today" is already in the past by the + time anyone is looking at the panel. Set `target_time` for anything where the + hour matters. +- **A passed countdown is hidden by default.** `Nd ago` is only ever visible + with [`show_expired`](#global-settings) turned on. + +In `since` mode the same granularity applies with elapsed wording: `Just now`, +`Nm ago`, `Nh Nm ago`, `N Days ago`. + +--- + +## Per-Countdown Settings + +Each object in the `countdowns` array takes these: + +| Key | Type | Default | What it does | +|-----|------|---------|--------------| +| `name` | string | *required* | The label on the top line | +| `target_date` | string | *required* | `YYYY-MM-DD`. The date to count to, or from in `since` mode | +| `target_time` | string | `"00:00"` | `HH:MM`, 24-hour. Gives sub-day precision — see the table above | +| `enabled` | boolean | `true` | Include this entry in the rotation | +| `mode` | string | `until` | `until` counts down; `since` counts up | +| `layout_preset` | string | `image-left` | `image-left`, `image-right`, `text-only`, `image-only` | +| `text_align` | string | `center` | `left`, `center`, `right` — within the text area | +| `image_path` | string / null | `null` | Image to show beside the text | +| `display_order` | integer | `0` | Rotation order; lower goes first | +| `id` | string | *auto* | Unique identifier, generated for you | +| `layout` | object / null | `null` | Pixel position and size overrides | +| `style` | object / null | `null` | Per-entry font and colour overrides | + +### Layout presets + +![Four panels showing image-left, image-right, text-only and +image-only](../../docs/assets/countdown/layout-presets.png) + +`text-only` ignores any `image_path` you have set rather than erroring, so you +can switch a countdown to text without deleting its image. `image-only` draws +no name and no value — useful as a static picture in the rotation. + +### Text alignment + +`text_align` positions the name and value inside the text area, which is the +whole panel under `text-only` and the remaining two thirds otherwise. + +![Three panels showing text_align set to left, center and +right](../../docs/assets/countdown/text-align.png) + +### `until` and `since` + +`until` (the default) counts down to `target_date`. `since` counts up from it, +which is what you want for "days since we shipped" or an anniversary. + +![Two panels: until mode counting down to New Year, since mode counting up from +a launch date](../../docs/assets/countdown/modes.png) + +### Per-countdown overrides + +`style` overrides the global fonts and colours for one entry. Every key is +`null` by default, meaning "inherit the global setting": + ```json { - "name": "Christmas", - "target_date": "2026-12-25", - "enabled": true, - "image_path": "assets/plugins/countdown/uploads/christmas-tree.png" + "name": "Birthday", + "target_date": "2026-11-14", + "style": { "font_color": [255, 105, 180], "name_font_color": [255, 182, 193] } } ``` -## Image Guidelines +`style` accepts `font_family`, `font_size`, `font_color`, `name_font_family`, +`name_font_size`, `name_font_color` and `background_color`. -- **Supported Formats**: PNG, JPEG, BMP, GIF -- **Recommended Size**: Images will be scaled to fit left 1/3 of display -- **Transparency**: PNG transparency is supported -- **Max File Size**: 5MB per image -- **Best Practice**: Use square or portrait-oriented images for best fit +`layout` overrides the automatic positioning, in pixels. Leave it out unless +something sits wrong — the defaults place the image on the left third and the +text on the right two thirds. -## Troubleshooting +| Key | Default | Meaning | +|-----|---------|---------| +| `image_x` / `image_y` | `0` | Image top-left corner | +| `image_width` | `0` | `0` means auto — a third of the panel width | +| `image_height` | `0` | `0` means auto — the full panel height | +| `name_x` / `name_y` | `null` | `null` means auto: centred in the text area, upper third | +| `value_x` / `value_y` | `null` | `null` means auto: centred, lower two thirds | + +Both `layout` and `style` also accept `null` or an empty string, which the web +UI may write when you clear a field. + +--- + +## Global Settings + +These apply to every countdown that does not override them. + +| Option | Type | Default | What it does | +|--------|------|---------|--------------| +| `enabled` | boolean | `true` | Whether the plugin runs at all | +| `display_duration` | number | `15` | Seconds each countdown holds the panel | +| `show_expired` | boolean | `false` | Keep showing a countdown after its date has passed | +| `fit_to_display` | boolean | `true` | Scale images to fit the space they are given | +| `preserve_aspect_ratio` | boolean | `true` | Keep image proportions when scaling | +| `background_color` | array | `[0, 0, 0]` | Panel background, `[R, G, B]` | +| `font_family` | string | `press_start` | Face for the countdown value | +| `font_size` | integer | `8` | Size for the countdown value | +| `font_color` | array | `[255, 255, 255]` | Colour for the countdown value | +| `name_font_family` | string / null | `null` | Face for the name; `null` inherits `font_family` | +| `name_font_size` | integer | `8` | Size for the name | +| `name_font_color` | array | `[200, 200, 200]` | Colour for the name | + +`show_expired` is the one to reach for if a countdown vanishes the moment it +lands. With it off — the default — a passed entry is dropped from the rotation +entirely, and if it was the only one the panel shows "No Active Countdowns". + +### Fonts and colours + +Four faces are available, and all four render distinctly: + +![Four panels showing press_start, four_by_six, five_by_seven and tom_thumb +rendering the same countdown](../../docs/assets/countdown/fonts.png) + +| Family | Kind | Notes | +|--------|------|-------| +| `press_start` | Scalable | The default; chunky 8-bit, very legible across a room | +| `four_by_six` | Scalable | Fits noticeably more text per line | +| `five_by_seven` | Bitmap | Crisp; always drawn at its native 7px | +| `tom_thumb` | Bitmap | The smallest option; native 6px | + +**`font_size` only affects the scalable faces.** A bitmap font exists at exactly +one pixel size, so `five_by_seven` and `tom_thumb` are drawn at the size their +file declares and ignore `font_size`. That is why they look sharp while a +scalable face at an awkward size looks soft. -### Countdown Not Showing -- Verify the countdown is **enabled** in the configuration -- Check that the target date is in the correct format (YYYY-MM-DD) -- Ensure at least one countdown is enabled +Name and value are styled separately, so a dimmer name over a bright value +(the default) reads well at a glance. Setting `name_font_family` to `null` +makes the name inherit `font_family`. -### Image Not Displaying -- Verify the configured image path exists on disk -- Check image format is supported (PNG, JPG, BMP, GIF) -- Confirm the path is readable by the LEDMatrix process -- Check LEDMatrix logs for image loading errors +### Images -### Wrong Date Calculation -- Verify date format is YYYY-MM-DD -- Check your system date/time is correct -- Dates are calculated based on midnight local time +`image_path` may be absolute, relative to the working directory, or relative to +the plugins repository root — the plugin tries each in that order. -### Font Too Large/Small -- Adjust `font_size` and `name_font_size` in configuration -- Range is 6-16 pixels -- Smaller fonts work better on smaller displays +- Square images work best; the image area is a third of the panel width by the + full height. +- PNG with transparency is fine. Transparent pixels show `background_color`. +- `fit_to_display` scales the image into its area; `preserve_aspect_ratio` + keeps it from stretching. Both default to on, which is what you want unless + you are deliberately filling the area. +- An image that cannot be found logs a warning and the countdown draws as + text — it does not fail the render. -## Technical Details +--- -### API Version -- LEDMatrix API: 1.0.0 -- Compatible with LEDMatrix >=2.0.0 +## Panel Sizes -### Dependencies -- Pillow >= 9.0.0 -- python-dateutil >= 2.8.0 +![The same countdown on 64x32, 128x32, 128x64 and 256x32 +panels](../../docs/assets/countdown/panel-sizes.png) -### Plugin Architecture -- Inherits from `BasePlugin` -- Uses font manager for text rendering -- Implements dynamic duration for smooth rotation -- Caches loaded images for performance +- **64×32** leaves the image about 21 pixels of width. Consider `text-only` at + this size, or a very simple image. +- **128×32** is the size the defaults are tuned for. +- **128×64** gives the image real room and separates the two text lines. +- **256×32** keeps the same proportions across a longer panel. -### File Locations -- **Plugin Directory**: `plugin-repos/countdown/` -- **Suggested Image Directory**: `assets/plugins/countdown/uploads/` (example location only; set each countdown's `image_path` explicitly in plugin configuration) -- **Configuration**: Stored in LEDMatrix `config.json` +--- + +## Troubleshooting + +**Nothing shows, or "No Active Countdowns".** +Every countdown has either been disabled or has passed its date. Check +`enabled` on each entry, and turn on `show_expired` if you want passed ones to +stay. + +**A countdown disappeared the day it arrived.** +That is `show_expired` (off by default). With no `target_time`, the target is +midnight, so the entry expires at the start of the day rather than the end. + +**It says "Tomorrow" and I want a number.** +That is the format ladder, not a fault — between 1 and 2 days out the plugin +writes `Tomorrow` rather than `1 Day`. See +[the table above](#how-the-countdown-value-is-written). + +**The image is not showing.** +Check the log for `Image not found` — the path is tried absolute, then relative +to the working directory, then relative to the repository root. Also check +`layout_preset` is not `text-only`, which ignores images by design. + +**I picked a font and nothing changed.** +On a current version all four families work. Older versions offered `tiny` and +`picopixel`, which had no font file at all, and silently fell back to the +default for every bitmap family. If you had one of those selected, pick again. + +**The date arithmetic looks off by a day.** +Countdowns are computed in the host's local time against `target_date` at +`target_time` (midnight if unset). A target early in the morning can therefore +tick over a day sooner than you expect. + +--- ## Development -### Project Structure -``` +### Project structure + +```text countdown/ -├── manifest.json # Plugin metadata -├── config_schema.json # Configuration schema -├── manager.py # Plugin implementation -├── requirements.txt # Python dependencies -└── README.md # This file +├── manifest.json # Plugin metadata and version history +├── manager.py # CountdownPlugin +├── config_schema.json # Settings schema; source of truth for defaults +├── requirements.txt +├── test/ # Harness config and golden images +└── README.md ``` -### Key Methods -- `update()`: Recalculates all countdown values -- `display()`: Renders current countdown with image and text -- `_calculate_time_remaining()`: Computes days/hours/minutes to target -- `_load_and_scale_image()`: Loads and scales images to fit layout +### Requirements and compatibility + +| | | +|---|---| +| LEDMatrix core | `>= 2.0.0` | +| Pillow | `>= 12.2.0` | +| python-dateutil | `>= 2.8.0` | + +The plugin subclasses `BasePlugin`, renders text through the core font manager, +supports dynamic duration so the rotation stays smooth, and **caches loaded +images** so a countdown that comes round repeatedly does not re-decode its +picture each time. -## Contributing +### Where to keep images -Feel free to submit issues, feature requests, or pull requests! +There is no enforced location — `image_path` is whatever you set. A convention +that keeps uploads out of the plugin directory (and so survives a plugin +update) is: -## License +```text +assets/plugins/countdown/uploads/ +``` + +### Regenerating the images in this README -Released under the GNU General Public License v3.0 — see the LICENSE file for details. +Every screenshot comes from a declarative shot list against a frozen clock: -## Credits +```bash +python scripts/render_docs_assets.py --plugin countdown +``` -Created for the LEDMatrix project by Charles +`--check` verifies the committed images still match what the plugin renders. +The sample gift icon lives in `docs/assets/countdown/sample/`. -Inspired by the static-image plugin for image handling patterns. +--- ## Support -For issues or questions: -1. Check the LEDMatrix documentation -2. Review the troubleshooting section above -3. Check LEDMatrix logs for error messages -4. Open an issue on the repository - -## Changelog - -### Version 2.0.0 -- Breaking: countdown image configuration is now path-based only (no per-row upload widget in web UI) -- Updated documentation to use `image_path` text input workflow - -### Version 1.0.2 -- Removed redundant legacy image fallback in `display()` and rely on normalized `image_path` -- Improved cache invalidation to refresh images when countdown metadata changes (not only count changes) -- Added strict date-schema note and manifest version history metadata - -### Version 1.0.1 -- Fixed web UI schema for countdown table editing -- Improved config normalization (auto-generate IDs and migrate legacy image format) - -### Version 1.0.0 -- Initial release -- Multiple countdown support -- Custom image uploads per countdown -- Configurable fonts and colors -- Split layout (image left 1/3, text right 2/3) -- Auto-rotation through enabled countdowns -- "TODAY!" special display for event day +- YouTube: +- Instagram: +- Discord: +- Sponsor: [GitHub Sponsors](https://github.com/sponsors/ChuckBuilds) · + [Buy Me a Coffee](https://buymeacoffee.com/chuckbuilds) · + [Ko-fi](https://ko-fi.com/chuckbuilds/) + +Released under the GNU General Public License v3.0 — see [LICENSE](LICENSE). diff --git a/plugins/countdown/config_schema.json b/plugins/countdown/config_schema.json index d7eda80b..991eb7d0 100644 --- a/plugins/countdown/config_schema.json +++ b/plugins/countdown/config_schema.json @@ -240,9 +240,8 @@ "enum": [ "press_start", "four_by_six", + "five_by_seven", "tom_thumb", - "tiny", - "picopixel", null ], "default": null, @@ -288,9 +287,8 @@ "enum": [ "press_start", "four_by_six", + "five_by_seven", "tom_thumb", - "tiny", - "picopixel", null ], "default": null, @@ -359,9 +357,8 @@ "enum": [ "press_start", "four_by_six", - "tom_thumb", - "tiny", - "picopixel" + "five_by_seven", + "tom_thumb" ], "default": "press_start", "description": "Global font family for countdown text (can be overridden per countdown)" @@ -404,9 +401,8 @@ "enum": [ "press_start", "four_by_six", + "five_by_seven", "tom_thumb", - "tiny", - "picopixel", null ], "default": null, diff --git a/plugins/countdown/manager.py b/plugins/countdown/manager.py index 2fb1adca..315b49c8 100644 --- a/plugins/countdown/manager.py +++ b/plugins/countdown/manager.py @@ -293,8 +293,40 @@ def _register_fonts(self): "press_start": "assets/fonts/PressStart2P-Regular.ttf", "four_by_six": "assets/fonts/4x6-font.ttf", "five_by_seven": "assets/fonts/5x7.bdf", + "tom_thumb": "assets/fonts/tom-thumb.bdf", } + @staticmethod + def _bdf_pixel_size(path) -> Optional[int]: + """The pixel size a .bdf font declares, or None if it does not.""" + try: + with open(path, "r", encoding="latin-1") as handle: + for line in handle: + if line.startswith("PIXEL_SIZE"): + return int(line.split()[1]) + if line.startswith("CHARS"): + break # past the header + except (OSError, ValueError, IndexError): + return None + return None + + @classmethod + def _truetype_or_native(cls, path, size_px: int): + """Load a font, falling back to a bitmap face's own pixel size. + + A .bdf exists at exactly one size, and FreeType rejects any other with + "invalid pixel size". Asking for the configured font_size therefore + failed for every bitmap family, and the text silently fell back to the + default face -- so picking one of them appeared to do nothing. + """ + try: + return ImageFont.truetype(str(path), size_px) + except OSError: + native = cls._bdf_pixel_size(path) + if native is None or native == size_px: + raise + return ImageFont.truetype(str(path), native) + def _load_family_font_direct(self, family: str, size_px: int): """Load a family's font file without relying on the process cwd. @@ -325,8 +357,9 @@ def _load_family_font_direct(self, family: str, size_px: int): for candidate in candidates: try: if candidate.exists(): - font = ImageFont.truetype(str(candidate), int(size_px)) - break + font = self._truetype_or_native(candidate, int(size_px)) + if font is not None: + break except Exception as exc: self.logger.debug("Font candidate %s failed to load: %s", candidate, exc) continue @@ -388,7 +421,16 @@ def _resolve_font(self, countdown_id: str, role: str, family: str, size_px: int, # signal. Detect the miss via the catalog and prefer loading # the real file cwd-independently so rendering doesn't depend # on the working directory. - if font is not None and self._is_family_missing(fm, family): + # Bitmap families go through our own loader even when the + # catalog has them: resolve_font asks for size_px, FreeType + # rejects any .bdf at a size other than the one it was drawn + # at, and the degraded default comes back looking like a + # successful load. _load_family_font_direct retries at the + # face's own PIXEL_SIZE. + if font is not None and ( + self._is_family_missing(fm, family) + or str(self._FAMILY_FILES.get(family, "")).endswith(".bdf") + ): direct = self._load_family_font_direct(family, size_px) if direct is not None: return direct diff --git a/plugins/countdown/manifest.json b/plugins/countdown/manifest.json index 7f91125e..b298d471 100644 --- a/plugins/countdown/manifest.json +++ b/plugins/countdown/manifest.json @@ -1,7 +1,7 @@ { "id": "countdown", "name": "Countdown Display", - "version": "3.2.1", + "version": "3.3.0", "author": "ChuckBuilds", "description": "Create and manage customizable countdowns with images. Perfect for birthdays, holidays, events, and special occasions.", "entry_point": "manager.py", @@ -21,6 +21,12 @@ ">=2.0.0" ], "versions": [ + { + "version": "3.3.0", + "released": "2026-09-02", + "ledmatrix_min_version": "2.0.0", + "notes": "Make every font in the picker actually work. Only two of the five offered families rendered: tiny and picopixel have no font file anywhere, and every bitmap family fell back silently because a .bdf exists at exactly one pixel size and the loader asked for font_size. Bitmap faces are now loaded at the size the file's PIXEL_SIZE header declares, and bitmap families use the plugin's own loader even when the core's catalog claims them -- the core resolver degrades to the default face without signalling it, which is why five_by_seven silently did nothing. tom_thumb is wired up (the file was always shipped, just unmapped), and the enum now lists the four families that verifiably render distinctly: press_start, four_by_six, five_by_seven, tom_thumb. Also rewrites the README around real rendered screenshots and documents all 40 settings, including the countdown-value ladder, which the old README got wrong: there is no '1 Day' and no 'TODAY!' string -- between one and two days out the plugin writes 'Tomorrow', and a passed countdown reads 'Nd ago' and is hidden unless show_expired is on." + }, { "version": "3.2.1", "released": "2026-08-13", @@ -93,5 +99,5 @@ "license": "GPL-3.0", "homepage": "https://github.com/ChuckBuilds/ledmatrix-plugins/tree/main/plugins/countdown", "config_schema": "config_schema.json", - "last_updated": "2026-08-13" + "last_updated": "2026-09-02" }