diff --git a/docs/guide/12_posters.md b/docs/guide/12_posters.md index 0a29076..29e9613 100644 --- a/docs/guide/12_posters.md +++ b/docs/guide/12_posters.md @@ -319,7 +319,7 @@ SN-100-0042 ## Proportions and printing -A portrait poster has the proportions of its paper, such as 594 by 841 mm for A1. A landscape poster has those of the paper turned, so it prints on exactly one page. A script in the page finds the narrowest layout, no narrower than `width`, at which all the content fits, then scales the poster to the browser window. Adding or removing content needs no change of size. When a poster has little content there is space above the footer: lower `width` to make everything larger. +A portrait poster has the proportions of its paper, such as 594 by 841 mm for A1. A landscape poster has those of the paper turned, so it prints on exactly one page. A script in the page finds the narrowest layout, no narrower than `width`, at which all the content fits, then scales the poster to the browser window. Adding or removing content needs no change of size. Pictures as wide as their column grow with the page, so a poster with several large ones may fit at no width. It is then taller than its paper, and prints smaller and centred, with margins at the sides. The page says so in the browser's console. When a poster has little content there is space above the footer: lower `width` to make everything larger. To print from a browser, use the print dialogue of Chrome with no margins and background graphics on. The page size and its orientation come from `paper` and `orientation`, and the poster fills exactly one page. Text, charts, QR codes, shapes and diagrams stay vector, apart from PNG diagrams and pictures. diff --git a/docs/guide/13_writing_posters.md b/docs/guide/13_writing_posters.md index b3dd810..503510b 100644 --- a/docs/guide/13_writing_posters.md +++ b/docs/guide/13_writing_posters.md @@ -370,6 +370,7 @@ The page always has the shape of its paper. A script in it finds the narrowest l | One column much longer than the others | One section is taller than the rest. Mark it `{.flow}`, make it `{.wide}`, split it in two, or choose the columns with `split:` | | A diagram with tiny text | Simplify it, or put it in a `{.wide}` section | | A wide table squeezed into a column | Make its section `{.wide}`, or cut columns | +| Margins at the sides of the printed page | The content is taller than the paper at every width, usually because of large pictures. Add `columns:`, use fewer or smaller pictures, or cut words | `orientation: landscape` turns the page and uses three columns. `paper:` sets the paper size for printing, from `a0` to `a4`, and does not change the layout. diff --git a/src/py_doc/poster.py b/src/py_doc/poster.py index 3b65882..293a10c 100644 --- a/src/py_doc/poster.py +++ b/src/py_doc/poster.py @@ -272,17 +272,36 @@ } function natural(w){p.style.zoom='1';p.style.width=w+'px';p.style.height='auto';p.style.minHeight='0';balance();return p.scrollHeight;} function scale(){p.style.zoom=document.documentElement.clientWidth/(+p.dataset.w);} +// When no width fits, as when pictures grow with their columns, the width whose content is least tall for it, +// so the poster prints as large as it can. +function leastTall(lo,hi){ + var best=hi,most=Infinity; + for(var i=0;i<=8;i++){var w=Math.round(lo+(hi-lo)*i/8),q=natural(w)/w;if(qhi*R){hi=leastTall(lo,hi);} else{while(hi-lo>6){var m=(lo+hi)>>1;if(natural(m)<=m*R){hi=m;}else{lo=m;}}} - natural(hi); // balanced at the width it keeps, not the last one it tried - p.style.width=hi+'px';p.style.height=(hi*R)+'px';p.style.minHeight=''; - p.dataset.w=hi;scale(); + var h=Math.max(natural(hi),hi*R); // balanced at the width it keeps, not the last one it tried + p.style.width=hi+'px';p.style.height=h+'px';p.style.minHeight=''; + p.dataset.w=hi;p.dataset.h=h; + if(h>hi*R+1){ + p.dataset.squeezed=''; + console.warn('py-doc poster: the content is taller than the paper at any width, so it prints smaller, '+ + 'with margins at the sides. Use more columns, smaller pictures or fewer words.'); + }else{delete p.dataset.squeezed;} + scale(); +} +// Printed, the whole poster fits on the page: a poster taller than its paper is made smaller and centred. +function toPaper(){ + var w=+p.dataset.w,z=Math.min(PAPER/w,PAPER*R/(+p.dataset.h)); + p.style.zoom=z;p.style.marginLeft=(PAPER-w*z)/2/z+'px'; } window.addEventListener('resize',scale); -window.addEventListener('beforeprint',function(){p.style.zoom=PAPER/(+p.dataset.w);}); -window.addEventListener('afterprint',scale); +window.addEventListener('beforeprint',toPaper); +window.addEventListener('afterprint',function(){p.style.marginLeft='';scale();}); window.addEventListener('load',fit); if(document.fonts&&document.fonts.ready){document.fonts.ready.then(fit);} fit(); diff --git a/tests/test_poster_browser.py b/tests/test_poster_browser.py index 74b236f..e7c81a1 100644 --- a/tests/test_poster_browser.py +++ b/tests/test_poster_browser.py @@ -130,3 +130,26 @@ def test_the_poster_fits_its_paper_and_scales_to_the_window(browser, tmp_path): assert page.evaluate("() => +getComputedStyle(document.querySelector('.poster')).zoom") == pytest.approx( 1400 / width, rel=0.01 ) + + +def test_a_poster_taller_than_its_paper_at_any_width_prints_whole_on_one_page(browser, tmp_path): + # A picture as wide as its column grows with the page, so no width makes it fit the paper. + (tmp_path / "tall.svg").write_text( + '', + encoding="utf-8", + ) + page = open_poster(browser, tmp_path, "## A\n\n![Tall](tall.svg)\n\n" + section("B", 1)) + width, height, overflow, squeezed = page.evaluate( + "() => { const p = document.querySelector('.poster'); " + "return [+p.dataset.w, +p.style.height.slice(0, -2), p.scrollHeight - p.clientHeight, 'squeezed' in p.dataset]; }" + ) + assert squeezed and height > width * 1.4141, "taller than the paper" + assert overflow <= 1, "the poster holds all of its content" + page.emulate_media(media="print") + page.evaluate("() => window.dispatchEvent(new Event('beforeprint'))") + zoom, margin = page.evaluate( + "() => { const p = document.querySelector('.poster'); return [+getComputedStyle(p).zoom, parseFloat(p.style.marginLeft)]; }" + ) + paper = 297 / 25.4 * 96 # A3 portrait, in CSS pixels + assert height * zoom <= paper * 1.4141 + 1, "it prints on one page" + assert margin * zoom == pytest.approx((paper - width * zoom) / 2, abs=1), "centred on the page"