stash
This commit is contained in:
parent
1356aa7749
commit
491ee05f15
13 changed files with 328 additions and 402 deletions
|
|
@ -1,5 +1,10 @@
|
|||
import {loadAnyDCode} from "../vendor/anyd-qr.js";
|
||||
import {encodeHandleForUrl} from "@/router"
|
||||
import {drawPixelText, preloadPixelFontRenderer} from "@/pixel-font.js";
|
||||
|
||||
// Re-exported so every caller can preload both the QR encoder and the bitmap-font rasterizer
|
||||
// (see fontTierFor's `pixel` tiers below) the same way, alongside this file's own preloadQrEncoder.
|
||||
export {preloadPixelFontRenderer};
|
||||
|
||||
// Mirrors loadAnyDCode()'s memoized wasm instance for synchronous use in buildRenderTree. See docs/implementation.md#qr-encoder-loading.
|
||||
let anyd = null;
|
||||
|
|
@ -79,26 +84,64 @@ export function tapeFromStatus(status) {
|
|||
// See docs/implementation.md#layout-tree-structure.
|
||||
const TEXT_REFERENCE_PX = 100; /* font size text leaves measure their natural aspect ratio at */
|
||||
|
||||
// Below 10px, a general-purpose sans-serif gets illegible, so drawTextLeaf switches to a bitmap
|
||||
// font (Tom Thumb/Silkscreen) instead; see the empirical rationale (font choice, `scale`, and why
|
||||
// sizes aren't dpi-adjusted) at docs/implementation.md#pixel-font-selection. The 10px+ tier names
|
||||
// Below 33px, a general-purpose sans-serif gets illegible at real label sizes, so drawTextLeaf
|
||||
// switches to one of these bitmap-style fonts instead, rendered through pixel-font.js's
|
||||
// drawPixelText (libfreetype-wasm's own monochrome rasterizer) rather than canvas fillText - see
|
||||
// that file's own comment for why (fillText is always anti-aliased, however carefully its
|
||||
// size/position are snapped, and canvasToBitmap's hard threshold turns that softness into
|
||||
// stray/missing pixels). See the empirical rationale for the font/size choices themselves (and why
|
||||
// sizes aren't dpi-adjusted) at docs/implementation.md#pixel-font-selection. The top branch names
|
||||
// Inter explicitly (see ../scss/_label-fonts.scss) rather than falling back to the CSS generic
|
||||
// "sans-serif" keyword, which resolves to a different real font per browser/OS and would make the
|
||||
// same label print differently depending on where it was rendered from.
|
||||
const FONT_TIERS = [
|
||||
{belowPx: 8, family: "Tom Thumb", scale: 3.2, pixel: true},
|
||||
{belowPx: 10, family: "Silkscreen", pixel: true},
|
||||
{belowPx: Infinity, family: "Inter"},
|
||||
];
|
||||
|
||||
function fontFamilyFor(fontPx) {
|
||||
return FONT_TIERS.find(t => fontPx < t.belowPx);
|
||||
// same label print differently depending on where it was rendered from - Inter is the one tier
|
||||
// still drawn with fillText, since anti-aliasing is expected/fine at this size (see drawTextLeaf).
|
||||
// Checked top-down (first branch whose threshold `fontPx` clears wins) - retune by eye in
|
||||
// prototypes/freetype-wasm/ladder.html before editing this.
|
||||
//
|
||||
// `size` is the actual px handed to the rasterizer - a fixed/snapped value for the bitmap fonts
|
||||
// (they read best at specific pixel sizes, not any arbitrary one - see each branch's comment) or
|
||||
// `fontPx` itself for Inter, which scales cleanly. `fontPx` itself (the layout-derived logical
|
||||
// size) still drives box centering/stacking regardless of what `size` resolves to - same idea as
|
||||
// the old Tom Thumb `scale` correction this replaces, generalized to every tier. `tracking`
|
||||
// (optional, px) retunes glyph spacing (drawPixelText adds it to every glyph's advance) without
|
||||
// touching the rendered glyph size. `pixel: true` routes a tier through drawPixelText instead of
|
||||
// fillText. ladder.html's own copy of this table additionally tunes a `lineHeight` per branch -
|
||||
// meaningful there since it sizes that page's own canvas outright, but deliberately not ported
|
||||
// here: inside drawTextLeaf the box is already sized correctly by the layout engine, so a tier's
|
||||
// tighter lineHeight would only crop real glyph ink (descenders, accented capitals) for no benefit.
|
||||
function fontTierFor(fontPx) {
|
||||
if (fontPx >= 34)
|
||||
return {family: "Inter", size: fontPx};
|
||||
// Terminus reads best at its own hinted sizes rather than whatever fontPx asks for.
|
||||
if (fontPx >= 32)
|
||||
return {family: "Terminus", size: 32, pixel: true};
|
||||
if (fontPx >= 28)
|
||||
return {family: "Terminus", size: 28, pixel: true};
|
||||
if (fontPx >= 24)
|
||||
return {family: "Terminus", size: 24, pixel: true};
|
||||
if (fontPx >= 12)
|
||||
return {family: "Terminus", size: fontPx - (fontPx % 2), pixel: true};
|
||||
if (fontPx >= 10)
|
||||
return {family: "Pixelon", size: 10, tracking: -1, pixel: true};
|
||||
// Uppercase-only - see ../assets/fonts/pixel/LICENSE.md - kept anyway; this tier's narrow.
|
||||
if (fontPx >= 8)
|
||||
return {family: "Chava", size: 8, pixel: true};
|
||||
// TODO find or build better fonts for 6px, 7px and 9px - the criteria would be to make better
|
||||
// use of the height available to be more readable.
|
||||
// Effective ink comes out to ~5px tall at size 8 - see ../assets/fonts/pixel/LICENSE.md.
|
||||
if (fontPx >= 6)
|
||||
return {family: "Silkscreen", size: 8, tracking: -1, pixel: true};
|
||||
// checkTextSizes (see MIN_TEXT_PX) already rejects anything below this floor before
|
||||
// drawTextLeaf ever calls this, so this is a same-file consistency bug, not reachable from
|
||||
// user input.
|
||||
throw new Error(`No font tier covers ${fontPx}px - is MIN_TEXT_PX out of sync?`);
|
||||
}
|
||||
|
||||
// Below this font size (px) or physical height (mm) - Tom Thumb's real-Chromium-tested legibility
|
||||
// floor - text is a hard failure, the same two-metric shape as MIN_RECOMMENDED_QR_PX_PER_MODULE/
|
||||
// MIN_RECOMMENDED_QR_MODULE_MM below (see checkTextSizes). No longer just silently blanked.
|
||||
const MIN_TEXT_PX = 5;
|
||||
// Below this font size (px) or physical height (mm) - fontTierFor's own floor (Silkscreen's
|
||||
// real-Chromium-tested legibility limit; see its `fontPx >= 6` branch above) - text is a hard
|
||||
// failure, the same two-metric shape as MIN_RECOMMENDED_QR_PX_PER_MODULE/MIN_RECOMMENDED_QR_MODULE_MM
|
||||
// below (see checkTextSizes). No longer just silently blanked.
|
||||
const MIN_TEXT_PX = 6;
|
||||
const MIN_TEXT_MM = 0.5;
|
||||
// Below this, still legible but a soft warning - flagged rather than blocking, same idea as
|
||||
// MIN_RECOMMENDED_QR_PX_PER_MODULE/MIN_RECOMMENDED_QR_MODULE_MM.
|
||||
|
|
@ -295,7 +338,7 @@ function checkTextSizes(node, referencePx, pxPerMm, warnings, textInfo) {
|
|||
}
|
||||
}
|
||||
|
||||
// Always measures in Inter at the reference size, since the eventual font (see FONT_TIERS) isn't
|
||||
// Always measures in Inter at the reference size, since the eventual font (see fontTierFor) isn't
|
||||
// known until layoutTree sizes the box this aspect ratio feeds into; the resulting mismatch (when
|
||||
// a pixel font tier ends up chosen instead) is invisible in practice, and checkTextSizes still
|
||||
// catches real failures.
|
||||
|
|
@ -347,35 +390,28 @@ function drawQrLeaf(ctx, node) {
|
|||
// there's no "too small to draw" case left to special-case here.
|
||||
function drawTextLeaf(ctx, node, referencePx) {
|
||||
const fontPx = effectiveFontPx(node, referencePx);
|
||||
const {family, scale = 1, pixel: isPixelFont = false} = fontFamilyFor(fontPx);
|
||||
// Position (not size - fontPx is already a whole pixel) still needs snapping for pixel fonts
|
||||
// to stay grid-aligned, since node.box.x/y are ordinary (fractional) layout math; Inter is
|
||||
// left exact since anti-aliasing handles fractional positions fine.
|
||||
const snap = isPixelFont ? Math.round : (v) => v;
|
||||
// `scale` (Tom Thumb only, see FONT_TIERS above) corrects the size handed to ctx.font for its
|
||||
// real ink; fontPx itself stays the logical size used for centering/stacking math.
|
||||
ctx.font = `${fontPx * scale}px "${family}"`;
|
||||
const tier = fontTierFor(fontPx);
|
||||
if (tier.pixel) {
|
||||
// Own rasterizer, own centering/pixel-snapping, own multi-line stacking - see
|
||||
// pixel-font.js's drawPixelText for why (and its own doc comment for what it does with
|
||||
// node.box/node.lines, the same inputs drawTextLeaf itself would otherwise use below).
|
||||
drawPixelText(ctx, tier, node.lines, node.box);
|
||||
return fontPx;
|
||||
}
|
||||
// Only Inter reaches here (fontTierFor's non-pixel tier) - canvas fillText's anti-aliasing is
|
||||
// expected/fine at this size, unlike the bitmap tiers above (see pixel-font.js/fontTierFor).
|
||||
ctx.font = `${tier.size}px "${tier.family}"`;
|
||||
// Canvas text silently falls back if drawn before a not-yet-loaded font resolves, unlike DOM
|
||||
// text. See docs/implementation.md#canvas-font-loading. Every tier now names a real,
|
||||
// self-hosted webfont (see FONT_TIERS above), so this always applies, not just to pixel fonts.
|
||||
// text. See docs/implementation.md#canvas-font-loading.
|
||||
document.fonts.load(ctx.font);
|
||||
ctx.textAlign = "center";
|
||||
const centerX = snap(node.box.x + node.box.width / 2);
|
||||
ctx.textBaseline = "middle";
|
||||
const centerX = node.box.x + node.box.width / 2;
|
||||
const lineHeight = node.box.height / node.lines.length;
|
||||
// Lines stack as a block, each centered under the last, so a multi-line field reads as one unit.
|
||||
let sliceTop = node.box.y;
|
||||
for (const line of node.lines) {
|
||||
if (isPixelFont) {
|
||||
// textBaseline:"middle" centers on declared ascent/descent, which is backwards for
|
||||
// Tom Thumb; centering on actualBoundingBox{Ascent,Descent} instead measures this
|
||||
// string's real rendered ink and stays correct regardless.
|
||||
ctx.textBaseline = "alphabetic";
|
||||
const {actualBoundingBoxAscent: up, actualBoundingBoxDescent: down} = ctx.measureText(line);
|
||||
ctx.fillText(line, centerX, snap(sliceTop + (lineHeight + up - down) / 2));
|
||||
} else {
|
||||
ctx.textBaseline = "middle";
|
||||
ctx.fillText(line, centerX, sliceTop + lineHeight / 2);
|
||||
}
|
||||
ctx.fillText(line, centerX, sliceTop + lineHeight / 2);
|
||||
sliceTop += lineHeight;
|
||||
}
|
||||
return fontPx;
|
||||
|
|
@ -468,6 +504,10 @@ export function drawLabel(canvas, tape, content, orientation = "along") {
|
|||
canvas.height = tape.printAreaPx;
|
||||
|
||||
const ctx = canvas.getContext("2d", {willReadFrequently: true});
|
||||
// Defensive, not load-bearing: drawPixelText's own blits already land on integer pixels (see
|
||||
// its own comment), but a 1:1 drawImage getting resampled by an engine quirk would reintroduce
|
||||
// exactly the anti-aliasing canvasToBitmap's hard threshold turns into stray pixels.
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.fillRect(0, 0, printedLength, canvas.height);
|
||||
ctx.fillStyle = "#000";
|
||||
|
|
@ -507,6 +547,7 @@ export function drawFallbackLabel(canvas, content, orientation = "along") {
|
|||
canvas.height = FALLBACK_LABEL_HEIGHT_PX;
|
||||
|
||||
const ctx = canvas.getContext("2d", {willReadFrequently: true});
|
||||
ctx.imageSmoothingEnabled = false; /* see drawLabel's own comment */
|
||||
ctx.fillStyle = "#fff";
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
ctx.fillStyle = "#000";
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue