All files / src CanvasSizing.ts

100% Statements 58/58
100% Branches 8/8
100% Functions 2/2
100% Lines 58/58

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 591x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 48x 48x 48x 48x 48x 48x 42x 42x 42x 42x 3x 3x 3x 3x 42x 48x 48x 1x 1x 40x 40x 40x  
// Sizing the canvas BITMAP for HiDPI displays (#71, #173).
//
// The bitmap (canvas.width/height) is set to the CSS display size × dpr so
// the figure renders on native device pixels. Coordinates stay in CSS px;
// Slate.drawElements scales the context by dpr.
//
// #173 — that is only correct when the CSS box is pinned INDEPENDENTLY of
// the bitmap. A canvas with no CSS width/height takes its layout size from
// its width/height ATTRIBUTES, which are the bitmap. So assigning the
// bitmap resizes the very box it was computed from: at dpr 1.5 a 340-wide
// canvas becomes 510 wide, and now bitmap == client rather than
// client × dpr. The figure paints 1.5× too large and every pick lands
// 1.5× off — silently, since it is just arithmetic. Each window resize
// compounds it (510 → 765 → 1147 …) until the container's max-width caps
// the box, which is why "resize the window" or "maximize" appeared to fix
// it. Invisible at dpr 1, where bitmap == client × 1 is already true.
//
// The site emits every figure attribute-sized, so this hit every HiDPI
// desktop visitor.
 
export interface IBitmapSync {
    displayWidth: number;      // CSS px the bitmap was sized from
    displayHeight: number;
    pinned: boolean;           // true when the CSS box had followed the
                               // attribute and had to be pinned
}
 
/**
 * Set the bitmap to displaySize × dpr without letting that assignment
 * move the display size.
 *
 * Detect-and-correct rather than always pinning: a canvas that CSS sizes
 * responsively keeps responding, because its box does not move when the
 * attribute changes and so is left alone. Only a box that DID follow the
 * attribute gets pinned, to exactly the size just measured.
 */
export function syncBitmapToDisplaySize(canvas: HTMLCanvasElement, dpr: number) : IBitmapSync {
    const cw = canvas.clientWidth, ch = canvas.clientHeight;
    const width = Math.round(cw * dpr), height = Math.round(ch * dpr);
    let pinned = false;
 
    if (canvas.width !== width || canvas.height !== height) {
        canvas.width = width;
        canvas.height = height;
        // Reading clientWidth forces layout, so this sees the new box.
        if (canvas.clientWidth !== cw || canvas.clientHeight !== ch) {
            canvas.style.width = cw + "px";
            canvas.style.height = ch + "px";
            pinned = true;
        }
    }
    return { displayWidth: cw, displayHeight: ch, pinned };
}
 
/** window.devicePixelRatio, or 1 headless. */
export function currentDpr() : number {
    return (typeof window !== "undefined" && window.devicePixelRatio) || 1;
}