From efc2605a4fe8f13375b7f3469786ed9ffe02b6ec Mon Sep 17 00:00:00 2001 From: Robert Perce Date: Thu, 26 Feb 2026 21:07:48 -0600 Subject: [PATCH] split webview and png render --- Cargo.lock | 2 ++ Cargo.toml | 2 +- kindle.html | 1 - kindle.png | Bin 221697 -> 0 bytes src/main.rs | 52 +++++++++++++++++++++++++++++++--------------------- 5 files changed, 34 insertions(+), 23 deletions(-) delete mode 100644 kindle.html delete mode 100644 kindle.png diff --git a/Cargo.lock b/Cargo.lock index 1652e11..98e8e38 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1191,6 +1191,8 @@ version = "0.27.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8156733e27020ea5c684db5beac5d1d611e1272ab17901a49466294b84fc217e" dependencies = [ + "axum-core", + "http", "itoa", "maud_macros", ] diff --git a/Cargo.toml b/Cargo.toml index 7bc3b56..bbdbfd0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ axum = { version = "0.8.8", features = ["macros"] } chrono = "0.4.44" headless_chrome = "1.0.21" image = { version = "0.25.9", features = ["png"] } -maud = "0.27.0" +maud = { version = "0.27.0", features = ["axum"] } tempfile = "3.26.0" tokio = { version = "1.49.0", features = ["fs", "rt", "tracing"] } tower-http = { version = "0.6.8", features = ["fs", "trace"] } diff --git a/kindle.html b/kindle.html deleted file mode 100644 index 1bf6a91..0000000 --- a/kindle.html +++ /dev/null @@ -1 +0,0 @@ -
Hello banana
It is 2026-02-26 16:33:19.778042362 UTC
\ No newline at end of file diff --git a/kindle.png b/kindle.png deleted file mode 100644 index 7453f55fe795176df96eaeaf07f59557dff53529..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 221697 zcmeAS@N?(olHy`uVBq!ia0y~yU}0cjU@+rgV_;y2EPSoNz`(##?Bp53!NI{%!;#X# zz`(#+;1OBOz`)%J!i?r8Pp@TQP+;(MaSW+oWZ-D{UeEad|Ns9WV@C042#kinXb6mk zz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQRO*0jA;WwNamqhQMeDjE2By2#kinXb6mk zz-S1JhQMeDjE2By2#kinh!27C+YAR`YXRVaF*+Ck4~kL7Xb6mkz-S1JhQMeDjE2By z2#kinXb6mkz-S1JhQMeD4BrqK9RL`(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7 zfzc2c4S|s!0vjy(8Zp)aFffb`28{HCG3x)(5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C z(GVC7fe{e`qXPgVBAQ1%IT`|^Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiQ?L*Th{ z@d_+!0Y+B?jKH)o>gCZ87!85Z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=Ckr)D_s{ux0 ztdIJ6Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONU^E0qLtyxaz>H<#AF-|lU|?Y2X!u?~ zIwCOq)4-??M?+vV1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONfVdL^LqkIu3=Ivj z2G_-l7a15C85y>2-HKHnn;^(M5C+Lzy?Pa_A4G%r&(F_bVq(IuZrwT*J$LWkWe5riVvv@WX5iuBVK6c>LQwgo)uR;@xYXYby<47Rqm;Oz(?H@dsKGhDiK39cqKHkKhhJ)I#hFOPwXi;IDS zgM-1^+L{5hjRK|!G|(`0>Qn}OeSHRQZfLu>K!EelX0QJ)5Dnww7Vrwrvc~&dv<~|NqCS9b~qTj}L>duP?*= z`SZb@{NUhVhRvHdgZ2FU`4j90&>%olQxjN?q@*N6cz8I&qeqXxYCr^NprWFp0_^rJ zTedKWi;FYF#KbT>fBqaK4erFdxw(M{2te-Z>gr;6{P-~g?5qQ@A_fMA)vH%C96NT5 zVbi8fV1IzjjgF3nNi%>t|2a813?Q>su3X7bQc}XOX3ZLg^73-Hno-7R2#kinXb6mk zz-S1JhQMeDjE2By2#kinXb6mkz-S1N76O|(8rcc01z=!cA=c0f7cPJg^dqEx%9JVK zPXF%RyBP!o1i;EcGvT0Fb3>VSS(E--CbLUP5c6N5KGCe)d0aL3-y z&krnyPW=A;o1w9>ks%`^11uF77|390Y01#i(gGeJc=6%|gO-*SLvwR8gN20!SPf|9 z0BFSmX!U`doE%u}%a<<U1BAT^-D1JGavr~?o33m+dJSPf{# zAGG4(`}gk*oSd9sF%TOxm>?k`0p`Dc{hFb;xR?RtPS7d?(5ycwEV{e9!SW#D=FOW7 zpl}AUNAYL~jE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb4a<1c)90P*6}{=;`Uf zu>1b~`wXR}rEu|s2M>am%K!cQm*MT(w_tHzUS9CB`ZH(Ffcf}{Q>RWbfI9iG&Ob;k zXlXxaTL5UeKS&NFh7E(ZAi%^yw-(7*+V2Kfg>gX{-k(8>hRDuP?LZZRxgyci^fa+Uz-W(8R1A0!Dg z2WA&&l>C>k$E(Q(SgwM~j_T;2ZkU#bJEVJ_FENe$bKtP;VaOC(u#>m^_RI*#~1|qd|U2 zOG^WfQh*LC03A#)IvRj2q(;R?Ltr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1gH@L zR6ig@W;E`(`^)1V~*a1ItmO-&7WBmc&Y8yP--{>%Ux04Xdi z#864doPdA;uzt|Vg74nFV>o*BD1(cO3x+wP0;3@?8UmvsFd71*Aut*OqaiRF0;3@? z8UmvsFd71*AwVz$xON^5C2B1I#3ZT@27uP`gVy1LX3Zg1Fn~|f109QxZG`|#4U7iq z2c577n#G5Soj-pbeEJ|r4km_;Mn2XbGzhVD=~4zOD=V;4(8+zEfrjMdWblkXNbtsu z8z71#3^E7QX?JjNU|?ou2J0o&98lPRPEd@Gk7v-(&;XkOB1km{q-PY5hQMeDjE2By z2#kinXb6mkz-S1JhQMeDjE2By2#kgRF(E+ob^u}w1E~QW767_;57bG{&(8;+76`gB z05k*d=;#Pif)`g-R)SCH^YZdy0O^1K{yjr=bu|NM={{%%9hYX%?0Z2$0eFTTv_v0t zWx&akCmBEo7J&3WE5EN#hffLZFgPZ<`j`}#qr`hm_8 z03C-98XN$1eE03!$AG?fADd@DrxJoX@t~RZgoFg}-GQLf`#>k@fja!y^nk=bR|}TRaG&}m@$I^G#IdM-8u%)asHs|1wf}D9zJ{+ zq>La2U1#9w>B*3ro68Uq62bu5;}1IN4|HE5=>9;^9fkxAVPIeYty}<|2>{Xq+Df6T zs|(&9z{0}9kdl%@j1i-1MnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz3ONfanll z{0~|;Pqgw;g`*)b8UmvsFd71*Aut*OqaiRF0;3@?8UmvsFd71*Aut*OgDM0bT;e%L z(pmr-tOgiV0W#{C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVC7fzc2c4S~@R7!3h%2vB`H z0NB(~Vl)IsLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V#ZpLO}6=(@qlC0x&Qz za5Q|c9~}{(NBoT1F&YA+Aut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiRF0s|ZZqXPf~ z908-&kA}c#2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mk068IGa=l7`^tAvGn??r% zAmK1dj)uT!2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz_1R1(E)&A9lfKT8x4Wc z5Eu=C(GVC7fzc2c4S~@R7!85Z5Eu=C(GVD(A#kZxs*v2Z07PCFFmvWi21Z6khL2IKtO=O$;pY~!i5Vc@*vKXDN`6MEG!s!d3hN$G&C4`dU_cC{P_ctg5%Ab zH-q)_@$oU3n3ynt?1oF?VNlC{Y*v8WK*;|$Zror93JPKn5fNcfR#s-1Jb5xUqp*rS zc<_LsudffQ+?Ow3z~QZ^sK_88A;FNHoz3v|>sPF5(FH-*QI?gJG5q@V3tb8vKkn}C z;5Y@vMR|ES!{5JuG1MUoIVPRngZ*Omgt5>f=Wg!$XY9J~Z81Tg}$Xqu!H*mfI`Fr{Dhu6xLE0Kla?9k9q217$bFdxK*g(EJywY3$j7DRy32q@p$+S)P*3JQYLBq(oy z@(4(N6px0$Xb6mkz-S1JhQMeDjE2By2#kinXb2Dy0{@8sGiJ>A&%nU&|HX?JVAXTx z%=s@MAOL3LB3fEn{`dFyh*|9yOX{;ylN?*Ei2Q~ryHi2S#=xBvg| z-#@VaEnBwyXJlmjpP8BYfA#9s|2;fB{&RD4|9|x85m+8XG&eVc-Ck5w^dF?YqN3tI z3k%Et+S*!>G#Ia4yA~{#mzVc{!-ft2{rvp?b8>S2zjf;tSRNMvGLIVeV>6!^|AX8o zCMNdZ)6?_+nl)?wgZu`HrzK04U^5a!thl)Nzln(nhWL*kKmJ=T~Q{L7awDAHH2Uj5I`&i>!m*Z2RXO`HCM(v^gS#Q)gX zSQK^W98emtv9STG2kC)fkpE$7$%(_7ni{Zs+S}W~X(J;e1MKdtTepIZee&c9hMge0 z)6&xZb8v9{zkByCSRFC$b8&I`&&S92zpts3kAu@JDBXgvtgP&RYiny1vFq2bgVlo4qm`8v zI8SD0XaAo&cP=;_&CSih_943)tYMTG4S~@R7!85Z5Eu=C(GVC7fzc2c4S^950+&4; z@50sskmFqjVl;s|K%mY7s1pHFPt0HdNF62E-QE4av9a<0-@kvujRAF{K<+qp>=;X)EC3}yog`3a|IeR4U=dAC&Ht&X zsbDsUc>n%Axbq7$2PB3KQ_FsAW)tIo&>)D8j?RCOKVfFr+S>jHb(~;)bo%@E@8HfR zr~}T##DsM)VCBk{;DMO?_wU2Cg9cAPgEOB$e};>pGeGfEQd07tpPwJxNkbnD2oDbj zcXnan4$^=0=uvQ|`1bAFATCy{rKR;BG^m4B5-Lbe97;(^{VymefSLfIOifMygF3Ac z9wrHLyOEL6f6xE|OqP)QKpkIDx&;kt!SsLzv_JzfZfR< z8=U|REP@6m&YU@eE(IRA0}Y6Q;srx;RA4j&MnhmU1V%$(Gz3ONU^E0qLtr!nMneE4 z1V}y`pt7=(Atfb+;lqay;HAH?CDG^$4*~-N8IZRFtY5z#yxf|bo0~yKMuwrVun_BV zahN&Ru3cjQEgNTHVSx!TSXx?w*`Oux@87>?*uQ^2187+>Sdf8%fs>OHyo4IIwE4-C zCk&rHePZzO@qsA;(;#!cfB((^T3UVf>{*8E*RM0Arlx}BKm=%O!TIy&!TtpCv0-Z2 zkIih57$N`v{{5Q)wCp-AE{=hXjSZv)j90H-%`jue3@{rT0gAWTvu88hzki=WR8$n3 z5{AW#7c;oHxG*RvD8S`G%l1#5I>o@u%nTO;E#pQG|Ah+|GMqSZfGoE&iZfo>lt zU4ph)+`4rOU22p+8UmvsFd71*Aut*OqaiRF0;3@?3WjkAnAKNCz}5ng=}nG?@AV{) z2vk>BGxYZMGJrPHzj*Ni+_NBL|Cuvqz&#Ef9UX>c%a$=@W@a)>oH!A@Q6HDy0|yQ; zl$MsFNFP0V6wC+plAb+#24-t&YJzDP0pf!;MuRq|OG`^L+_`gy!OqSOCJm-R=72^I zK)tU^mo70dF)=ZKdSjps@E|^Db3CXA3fdeG)_{eemi<_a1`84LKWMb$-Me=TrlzLg z9x7-v_p4X0z`b!@U0tvl*a*;O|HFq5Gk`GY5C?2(Kw$tf7bJJ@-aQ5o=HlXF(AU=o zkGg=wK^U}|9n`A@u|XI#8UZr5uC5Nm#lWE6_QQt{QN-W8dBgDe^Jj+V&!5A^kiUesFw(?15 zAut*OqaiRF0;3@?8UmvsFd71*Api*hk_P}lC#iutVW18dXzCZeQv)#;l{|RxAb2Wy z`t<1x{{H?9pg{ysCkk|`9jZzM57g-aP0hnZK%Knw^mOoPfS?XNsN)5aL!Z(GohAsH zrUfkp0J#m+kq6NrwIB>SRStBD9%zb@gM$Nn>LJLChK2?PkiS5kTo7GURD}2BI%?Su zauYU8$p5g@8bQ;zAU}f!S3vOrn(BqgVKbK)G0;E)sPn0=t`0tR5EO=>)BH}KK8>Os z)Zs5KE=Cc<$BB-PX4tV~2LtHDNl^TNrg5X9qCopnP)>eCHWxJb05TIazyXRQ&~gS) z+H-Jl0C(I$av=Yb6Ni%~O#)BBo12@1Pgn${FVNDEgoFf;u^1S1LLI2130kTGlO)D{ zpmYq1Pne#6|NeoexIw2`!j=|*2BSb2rUta+1T;VcI@J=i1P3Gzk_TZB8-%f`0g24EnGgolSSY~8vQJo^jk1f`{=5i|(Y zF#)X)2F*Hyj#1yRVFP$y0;t0e(hOQ>4WeNfCXT!Y9VGYq^=k&uTKbrn7zPjqnF$gH zb$UUaBhZZE#fulg`w8aFo5uj^C{CC#0i*;ghS@`@{a6i062#?ykh?+3KvyAvt}Fn} zsDlPwK(nW?&Lc>kR1BJV1+9;tHfKr1{z zR}X*&TtEX1ptaqgeF&iA@nPzaX?b~hh7~JTFo669iodY1Fb2?!J*aco+}sRS3JMEy z;t=Ex(5jC4^XD@hIdTNN4jr^N0yN+NHW-Njty%yLB7p|7kc1hCaUZgJP#OcxV1w)c z#U1EM0AzU(8x)_Q6&;{~hAmsRFo0IIfW(n8F=~+2qqB)gx5TJHH<#Lc&@}FLP;>a5}m zcu>a^w3)x7ql2Nbv5^5Z^9|~Vg7ko9zCkpog9@Ty7{mw7{v*$Jf|inlX3IgHJkS6F zsKdtsJ$L}74%G3}*474ZR*#R52jAPaWXTe+v7ja3pw18sgYHVCmi^ew23yL&z<|&H zFn5FQR0D-CXq5(Nl|)TV4Fjmt2nq+7Ii%2_W&fZ92S79TpxJRyM_pA_6@2IbsIv?j zC?Ul>WVN9111)<8&0>SLh=4lnpg|T;2OhMH9a$}m4LVE$G;jo(6^Dtjva*5?f&k5? zgT!EAfiDh0>X0#L**mBczF@%u2GH!kwY4<^=P_%uQoA2bl)=jR9R z+=I*kZB2m5foM=C6tr?cPEHQQg=5eRHOQ?X0nnxIpcC6**v7^NJVQ$j`>~k~vXhYi zLGA{r1I>zqXcz{$8^i{!egM(rV366k%!P#sIp%`)n}cS!K^=S0h4r9LDJZ@`onX-H zH#Rdtoj_2yqsxKP0%!mPBo1;fp*RGoL&l)<06=`unGhfvhD}UNz}pl+?u3bfXwXTB zpg{?cy&yge6XQOR+N!E52GE%apwk9H=RTnO7j%{dXrS!Sp+gKH3>v6F-#SB#8jxLt zFlbc`wtNH92O8V}(Zr|$sYAw~Ef63+Y+wPzM#i8)A5felixFaj2Jt|HQ=q{_Y-&M+ zD&5`P44@S(*yKmWMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1P~!WmjM9K(tpqaX&`Gs zof^+9wagpjlyR z*^kX^kfntD59*+UW=cWf15yvepxJs*2OG4XA0|dB4O*fOT5^x94`eQg4+|3zn^e4L z(IN)WiVM(Qewf}lbLN2e=Q}w$!Gus~(8>+ac@3ZeBbWqe1{ie4fSH*YScsT71S>=$ zKyeA;f)3~d(J&0+gVG6TnLkVn)L8~C;|FbrfQi9qV%!HBcml0xnKo@2%C!_QGe9&b z96)x1(joc^0FXQg6Qc&Cjt~YNeh3P`pFe-Xm4dGQ0G*Kl8vr0i4O}yn0ou|58h`?A zRRHB7s4$oU9gYaPN&;jDSb#_Zl+QqU0km}jG=PY#4b_5M2G9&@ zQ&SUzv$Hb;sIvuHb`Dz74l)xos|s4q4qEOEn#~1?A!E?&E@)K&Xutq8Kmjrbv?>6k z7BtHU>bxMUM`u&Reo)5~-CSb*51L&D-OmVGl>oYv4-_ALeSHj|*-_A8dg$g7%Lkna z0Gh1_t>6G{lK|bZ2(k;bk^;1}9cE5sWF!Nqb5~GM029JXgTlDIy`3R3F%f)F9%!ov z=@=HoeVyLgLXHZj91K*wiTD}kRD=3YF%m!gl2OZ=-kRA{p8583^(Afr{RRo|x z3{cua)(_fx0UBfgsR0e(fWicXk=1|(EJvx4(bHmZwR_K5_I?{C>)6~YgEl>2#kinXb6mkz-S1JhQMeDjE2By2#n|u_)i3w zF=NJm1_p-zFJ8O=tA73Z^?zeyQ|`z=ba6sCRpV!T)6Px#l_{nfPerv-YP08{(t}e9YrlD3`0Uf zP{cr-B} z{qO7R`~T+6n<&}{#o?1DPrz;}DJg;L`}+0k|H{hB|7vP#|GBxj!QnT1_U!-v{{4fi z0Wm;s0fjAyjSUlWUqnO%*ltjGp<_^*1DPoyA%Rm(Mn(om$$w(hfE7XseDQnb%9a1F zuCCzt1?A;s%a%cPAt++hAXFn*$BrHQ@8je1UshK3KNlC*e{F5;|HZ|{|KGlSi%^8d z($UfR4+Nlc;DS^%geqk{p^fEcAl zLtr!nMnhmU1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(IETRK0KjmL-BG`dhQMeDjE2By z2#kinXb6mkz-S1JhQMeDjE2By2n@9ls9o!{lg4WSMz0GPYKdahy`v#88UmvsFd71* zAut*OqaiRF0;3@?8UmvsFd71*AwX6LjIIVCE7(RgjfTKz2#kinXb6mkz-S1JhQMeD zjE2By2#kinXb6mk01ZOm2;-Xr3=9nMo-U3dG%y}!q^TwI=HSbPYe0NYS3j3^P6, +} + #[axum::debug_handler] -async fn gen_png() -> Result { +async fn dashboard() -> Result { let now = chrono::prelude::Utc::now().to_string(); let markup = html! { body { div { "Hello banana" } div { (format!("It is {}", now)) } + style { + "body { + width: 600px; + height: 800px; + border: 1px solid black; + margin: auto; + }" + } } }; + Ok(markup) +} - let mut tempfile = tempfile::Builder::new() - .suffix(".html") - .rand_bytes(5) - .tempfile()?; - - tempfile - .write_all(markup.into_string().as_bytes()) - .expect("unable to write"); - - tempfile.as_file().sync_all()?; - - let launch = headless_chrome::browser::LaunchOptions::default_builder() - .window_size(Some((600, 939))) - .build()?; - let browser = headless_chrome::Browser::new(launch)?; - let tab = browser.new_tab()?; +async fn gen_png(State(state): State) -> Result { + if state.browser.is_none() { + return Err(AppError(anyhow::Error::msg("no headless chrome"))); + } + let tab = state.browser.expect("chrome not available").new_tab()?; let view = tab - .navigate_to(&format!("file://{}", tempfile.path().to_string_lossy()))? + .navigate_to("http://localhost:7777/")? .wait_for_element("body")? .get_box_model()? .margin_viewport(); @@ -80,10 +85,15 @@ async fn gen_png() -> Result { )) } async fn serve(port: &u32) -> Result<(), AppError> { + let launch = headless_chrome::browser::LaunchOptions::default_builder() + .window_size(Some((600, 939))) + .build()?; + let browser = headless_chrome::Browser::new(launch).ok(); + let app = Router::new() + .route("/", get(dashboard)) .route("/kindle.png", get(gen_png)) - //.nest_service("/kindle.png", gen_png()?) - .layer(tower_http::trace::TraceLayer::new_for_http()); + .with_state(AppState{browser}); let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).await?; println!("Starting axum on 0.0.0.0:{}...", port);