Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion docs/guide/12_posters.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
1 change: 1 addition & 0 deletions docs/guide/13_writing_posters.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
29 changes: 24 additions & 5 deletions src/py_doc/poster.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(q<most){most=q;best=w;}}
return best;
}
function fit(){
var lo=MIN,hi=4000;
if(natural(lo)<=lo*R){hi=lo;}
else if(natural(hi)>hi*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();
Expand Down
23 changes: 23 additions & 0 deletions tests/test_poster_browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -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(
'<svg xmlns="http://www.w3.org/2000/svg" width="4000" height="12000"><rect width="4000" height="12000" fill="#ccc"/></svg>',
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"
Loading