stash
This commit is contained in:
parent
fb3c1e78e8
commit
368268d288
1 changed files with 108 additions and 76 deletions
|
|
@ -50,34 +50,39 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!--div class="card mb-3 border-warning">
|
<div class="card mb-3 border-warning">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="card-title mb-0">Experimental: new font candidates</h5>
|
<h5 class="card-title mb-0">Experimental: optimal font by size</h5>
|
||||||
<small class="text-muted">for judging legibility - not part of the real label pipeline</small>
|
<small class="text-muted">for judging legibility - not part of the real label pipeline</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p class="text-muted small mb-3">
|
<p class="text-muted small mb-3">
|
||||||
Ten pixel/bitmap-style fonts found in frontend/public, rendered live from the "Text"
|
For every nominal size from 5px to 30px, the font and effective size
|
||||||
field above at a range of sizes so they can be judged the same way Tom Thumb/Silkscreen
|
<code>optimal_fonts</code> actually picks for it - rendered live from the "Text"
|
||||||
were. <strong>CodersCrux</strong> and <strong>712Serif</strong> fail to load as web
|
field above through libfreetype-wasm's own monochrome rasterizer
|
||||||
fonts at all - Chromium's OTS sanitizer rejects their cmap table - so their cells below
|
(FT_LOAD_TARGET_MONO), a true 1-bit render, not canvas fillText's grayscale AA
|
||||||
are actually just the browser's fallback serif, not a rendering bug in this page.
|
thresholded after the fact. The line height under each canvas is
|
||||||
<strong>Chava</strong> is uppercase-only, same dealbreaker as PICO-8.
|
<code>ft_get_line_height</code> at that cell's actually-drawn size, the same
|
||||||
<strong>6pxExpert</strong> only has pixel glyphs for digits; its letters are the same
|
metric prototypes/freetype-wasm's own demo shows.
|
||||||
fallback serif.
|
|
||||||
</p>
|
</p>
|
||||||
<div v-for="cfg in candidateFonts" :key="cfg.family" class="mb-3">
|
<div v-if="freetypeError" class="alert alert-warning small py-2">
|
||||||
<div class="fw-bold small mb-1">{{ cfg.family }}</div>
|
FreeType failed to load: {{ freetypeError }} - canvases will stay blank.
|
||||||
|
</div>
|
||||||
<div class="d-flex flex-wrap gap-3">
|
<div class="d-flex flex-wrap gap-3">
|
||||||
<div v-for="size in candidateSizes" :key="cfg.family + '-' + size" class="text-center">
|
<div v-for="size in previewSizes" :key="size" class="text-center">
|
||||||
<div class="small text-muted mb-1">{{ size }}px</div>
|
<div class="small text-muted mb-1">{{ size }}px</div>
|
||||||
<canvas :ref="el => setCandidateCanvasRef(cfg.family + '-' + size, el)"
|
<canvas :ref="el => setPreviewCanvasRef(size, el)"
|
||||||
class="pixel-test-canvas"></canvas>
|
class="pixel-test-canvas"></canvas>
|
||||||
|
<div class="small text-muted mt-1" v-if="optimalFontFor(size)">
|
||||||
|
{{ optimalFontFor(size).family }} @{{ optimalFontFor(size).size }}px
|
||||||
|
</div>
|
||||||
|
<div class="small text-muted" v-if="freetypeLineHeights[size] !== undefined">
|
||||||
|
line height: {{ freetypeLineHeights[size] }}px
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div-->
|
|
||||||
|
|
||||||
<div v-if="!usbSupported" class="row">
|
<div v-if="!usbSupported" class="row">
|
||||||
<div class="col-lg-7">
|
<div class="col-lg-7">
|
||||||
|
|
@ -137,7 +142,9 @@
|
||||||
<div class="card">
|
<div class="card">
|
||||||
<div class="card-header d-flex justify-content-between align-items-center">
|
<div class="card-header d-flex justify-content-between align-items-center">
|
||||||
<h5 class="card-title mb-0">Label</h5>
|
<h5 class="card-title mb-0">Label</h5>
|
||||||
<small v-if="tape" class="text-muted">{{ tape.mediaWidthMm }} mm tape{{ textSizesSummary }}</small>
|
<small v-if="tape" class="text-muted">{{ tape.mediaWidthMm }} mm tape{{
|
||||||
|
textSizesSummary
|
||||||
|
}}</small>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<p v-if="!tape" class="text-muted">
|
<p v-if="!tape" class="text-muted">
|
||||||
|
|
@ -267,9 +274,28 @@ import LabelLayoutPreview from "@/components/LabelLayoutPreview.vue";
|
||||||
import printerManager from "@/printerManager.js";
|
import printerManager from "@/printerManager.js";
|
||||||
|
|
||||||
import {MultiPrinterBlob, canvasToBitmap, bitmapToCanvas} from "../../vendor/weblabel.js";
|
import {MultiPrinterBlob, canvasToBitmap, bitmapToCanvas} from "../../vendor/weblabel.js";
|
||||||
import {tapeFromStatus, drawLabel, drawFallbackLabel, buildLabelContent, buildLabelFields, preloadQrEncoder} from "@/label.js";
|
import {
|
||||||
|
tapeFromStatus,
|
||||||
|
drawLabel,
|
||||||
|
drawFallbackLabel,
|
||||||
|
buildLabelContent,
|
||||||
|
buildLabelFields,
|
||||||
|
preloadQrEncoder
|
||||||
|
} from "@/label.js";
|
||||||
import {LABEL_TEMPLATES, BASE_VARS, DERIVED_VARS, withDerivedVars, templateContent} from "@/label-layouts.js";
|
import {LABEL_TEMPLATES, BASE_VARS, DERIVED_VARS, withDerivedVars, templateContent} from "@/label-layouts.js";
|
||||||
import {shortenedRoute} from "@/router";
|
import {shortenedRoute} from "@/router";
|
||||||
|
import {loadFreeType, loadFontFace, drawMonoText} from "@/freetype-preview.js";
|
||||||
|
|
||||||
|
// URLs (not raw bytes - fetched lazily, see drawOptimalFontPreviews) for each font optimal_fonts
|
||||||
|
// can pick, fed to freetype-preview.js's loadFontFace. Silkscreen and Inter each need their own TTF
|
||||||
|
// copy (see pixel-candidates/LICENSE.md) since this build of FreeType can't parse WOFF2 (no brotli
|
||||||
|
// support - confirmed: FT error 2 on both production pixel/Silkscreen-Regular.woff2 and
|
||||||
|
// label/Inter-Regular.woff2).
|
||||||
|
import pixelonFontUrl from "@/assets/fonts/pixel-candidates/Pixelon.ttf?url";
|
||||||
|
import chavaFontUrl from "@/assets/fonts/pixel-candidates/Chava.ttf?url";
|
||||||
|
import terminusFontUrl from "@/assets/fonts/pixel-candidates/Terminus.ttf?url";
|
||||||
|
import silkscreenCandidateFontUrl from "@/assets/fonts/pixel-candidates/Silkscreen.ttf?url";
|
||||||
|
import interCandidateFontUrl from "@/assets/fonts/pixel-candidates/Inter.ttf?url";
|
||||||
|
|
||||||
// Print.vue-local calculated fields on top of label-layouts.js's DERIVED_VARS. See
|
// Print.vue-local calculated fields on top of label-layouts.js's DERIVED_VARS. See
|
||||||
// docs/implementation.md#calculated-short-link-fields.
|
// docs/implementation.md#calculated-short-link-fields.
|
||||||
|
|
@ -297,10 +323,14 @@ const RULER_TIERS = [
|
||||||
//after manually comparing all fonts at all sizes, it was decides that in some cases it's more readable to round down the font size to a more readable
|
//after manually comparing all fonts at all sizes, it was decides that in some cases it's more readable to round down the font size to a more readable
|
||||||
// this is a lookup table for these optimal settings
|
// this is a lookup table for these optimal settings
|
||||||
const optimal_fonts = (size) => {
|
const optimal_fonts = (size) => {
|
||||||
if (size > 20)
|
if (size > 32)
|
||||||
return {family: "Inter", url: interCandidateFontUrl, size: size};
|
return {family: "Inter", url: interCandidateFontUrl, size: size};
|
||||||
|
if (size >= 28)
|
||||||
|
return {family: "Terminus", url: terminusFontUrl, size: 28}; // would even support bold mode >=14px
|
||||||
|
if (size >= 24)
|
||||||
|
return {family: "Terminus", url: terminusFontUrl, size: 24}; // would even support bold mode >=14px
|
||||||
if (size >= 12)
|
if (size >= 12)
|
||||||
return {family: "Terminus", url: terminusFontUrl, size: (size - (size % 2))};
|
return {family: "Terminus", url: terminusFontUrl, size: (size - (size % 2))}; // would even support bold mode >=14px
|
||||||
if (size >= 10)
|
if (size >= 10)
|
||||||
return {family: "Pixelon", url: pixelonFontUrl, size: 10};
|
return {family: "Pixelon", url: pixelonFontUrl, size: 10};
|
||||||
if (size >= 8)
|
if (size >= 8)
|
||||||
|
|
@ -372,21 +402,19 @@ export default {
|
||||||
brotherCliExample: "brother_ql --model QL-000 --printer usb://0000:0000 print --label 00 label.png",
|
brotherCliExample: "brother_ql --model QL-000 --printer usb://0000:0000 print --label 00 label.png",
|
||||||
niimbotCliExample: "niimprint --model b00 --conn usb print --density 3 --image label.png",
|
niimbotCliExample: "niimprint --model b00 --conn usb print --density 3 --image label.png",
|
||||||
|
|
||||||
// Ten pixel/bitmap font candidates for legibility testing only (see the disabled card above); not used by label.js's real FONT_TIERS.
|
// Nominal sizes previewed by the "optimal font by size" card - 5px to 30px inclusive.
|
||||||
candidateFonts: [
|
previewSizes: Array.from({length: 36}, (_, i) => i + 5),
|
||||||
{family: "Pixelon"},
|
previewZoom: 4,
|
||||||
{family: "Pixelbasel"},
|
// Keyed by family, populated lazily the first time drawOptimalFontPreviews loads a font -
|
||||||
{family: "Chava"},
|
// avoids refetching the same bytes on every varValues-triggered redraw.
|
||||||
{family: "CodersCrux"},
|
freetypeFontBytes: {},
|
||||||
{family: "712Serif"},
|
// Set if FreeType itself (not a specific font) fails to load - shown once above the grid
|
||||||
{family: "6pxNormal"},
|
// instead of failing every cell silently.
|
||||||
{family: "6pxExpert"},
|
freetypeError: null,
|
||||||
{family: "Jersey10"},
|
// Keyed by nominal size - each cell's effective line height in px (ft_get_line_height at
|
||||||
{family: "Jersey15"},
|
// the actually-drawn size), same metric prototypes/freetype-wasm's own index.html demo
|
||||||
{family: "Terminus"},
|
// shows below its canvas.
|
||||||
],
|
freetypeLineHeights: {},
|
||||||
candidateSizes: [5, 6, 7, 8, 10, 12, 16, 20],
|
|
||||||
candidateZoom: 4,
|
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
|
|
@ -482,7 +510,7 @@ export default {
|
||||||
} else {
|
} else {
|
||||||
this.redrawFallback();
|
this.redrawFallback();
|
||||||
}
|
}
|
||||||
this.drawCandidateFontTests();
|
this.drawOptimalFontPreviews();
|
||||||
},
|
},
|
||||||
deep: true,
|
deep: true,
|
||||||
},
|
},
|
||||||
|
|
@ -750,48 +778,52 @@ export default {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Works around Vue's refInFor array-collecting behavior for :ref in v-for, same as LabelLayoutPreview.vue's setTemplateCanvasRef.
|
|
||||||
setCandidateCanvasRef(key, el) {
|
// Looks up the (family, url, effective size) optimal_fonts picks for a nominal size -
|
||||||
|
// exposed as a method (not a computed) since the template calls it per-size inside a v-for.
|
||||||
|
optimalFontFor(size) {
|
||||||
|
return optimal_fonts(size);
|
||||||
|
},
|
||||||
|
|
||||||
|
setPreviewCanvasRef(size, el) {
|
||||||
if (el) {
|
if (el) {
|
||||||
this.candidateCanvases[key] = el;
|
this.freetypeCanvases[size] = el;
|
||||||
} else {
|
} else {
|
||||||
delete this.candidateCanvases[key];
|
delete this.freetypeCanvases[size];
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
// Renders the "Text" field at exact size/family with no layout snapping, so the font itself is judged; ink-centered vertically (like label.js's drawTextLeaf) so unreliable metrics don't clip. Thresholded through canvasToBitmap/bitmapToCanvas afterwards, same as redraw()'s real label preview, so a candidate is judged on the same 1-bit dots the printer would actually fire, not the anti-aliased canvas render.
|
// Draws every previewSizes cell - see the card in the template. For each nominal size, looks
|
||||||
drawCandidateCell(canvas, family, fontPx) {
|
// up the font optimal_fonts actually picks and draws through libfreetype-wasm's own
|
||||||
if (!canvas) {
|
// FT_LOAD_TARGET_MONO rasterizer - a true 1-bit render, not canvas fillText's grayscale AA
|
||||||
|
// thresholded after the fact. Font bytes are cached per family (see freetypeFontBytes) since
|
||||||
|
// several sizes can share the same font.
|
||||||
|
async drawOptimalFontPreviews() {
|
||||||
|
let ft;
|
||||||
|
try {
|
||||||
|
ft = await loadFreeType();
|
||||||
|
} catch (e) {
|
||||||
|
this.freetypeError = e.message;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const text = this.varValues.text;
|
this.freetypeError = null;
|
||||||
const ctx = canvas.getContext("2d");
|
for (const size of this.previewSizes) {
|
||||||
ctx.font = `${fontPx}px "${family}"`;
|
const cfg = optimal_fonts(size);
|
||||||
const {width, actualBoundingBoxAscent: up, actualBoundingBoxDescent: down} = ctx.measureText(text);
|
const canvas = this.freetypeCanvases[size];
|
||||||
canvas.width = Math.ceil(width) + 4;
|
if (!cfg || !canvas) {
|
||||||
canvas.height = Math.ceil(up + down) + 8;
|
continue;
|
||||||
// Re-set font/alignment - changing canvas.width/height above resets the 2D context.
|
|
||||||
ctx.font = `${fontPx}px "${family}"`;
|
|
||||||
ctx.textAlign = "left";
|
|
||||||
ctx.textBaseline = "alphabetic";
|
|
||||||
ctx.fillStyle = "#fff";
|
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
|
||||||
ctx.fillStyle = "#000";
|
|
||||||
ctx.fillText(text, 2, (canvas.height + up - down) / 2);
|
|
||||||
bitmapToCanvas(canvas, canvasToBitmap(canvas));
|
|
||||||
canvas.style.width = `${canvas.width * this.candidateZoom}px`;
|
|
||||||
canvas.style.height = `${canvas.height * this.candidateZoom}px`;
|
|
||||||
},
|
|
||||||
|
|
||||||
// Draws every (candidateFonts x candidateSizes) cell - see the card in the template.
|
|
||||||
async drawCandidateFontTests() {
|
|
||||||
await Promise.all(this.candidateFonts
|
|
||||||
.map(({family}) => document.fonts.load(`20px "${family}"`).catch(() => {})));
|
|
||||||
for (const {family} of this.candidateFonts) {
|
|
||||||
for (const size of this.candidateSizes) {
|
|
||||||
const canvas = this.candidateCanvases[`${family}-${size}`];
|
|
||||||
this.drawCandidateCell(canvas, family, size);
|
|
||||||
}
|
}
|
||||||
|
let bytes = this.freetypeFontBytes[cfg.family];
|
||||||
|
if (!bytes) {
|
||||||
|
const res = await fetch(cfg.url);
|
||||||
|
bytes = new Uint8Array(await res.arrayBuffer());
|
||||||
|
this.freetypeFontBytes[cfg.family] = bytes;
|
||||||
|
}
|
||||||
|
if (loadFontFace(ft, bytes) !== 0) {
|
||||||
|
continue; // fails to parse for FreeType too - leave that cell blank.
|
||||||
|
}
|
||||||
|
this.freetypeLineHeights[size] =
|
||||||
|
drawMonoText(ft, canvas, cfg.size, this.varValues.text, this.previewZoom);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
@ -799,11 +831,11 @@ export default {
|
||||||
this.blob = null;
|
this.blob = null;
|
||||||
this.resizeObserver = null;
|
this.resizeObserver = null;
|
||||||
this.resizeTimer = null;
|
this.resizeTimer = null;
|
||||||
this.candidateCanvases = {};
|
this.freetypeCanvases = {};
|
||||||
this.recentTemplateIds = this.loadRecentTemplateIds();
|
this.recentTemplateIds = this.loadRecentTemplateIds();
|
||||||
},
|
},
|
||||||
async mounted() {
|
async mounted() {
|
||||||
this.drawCandidateFontTests();
|
this.drawOptimalFontPreviews();
|
||||||
// Not awaited: resolving late just means shortUrl briefly shows "—" instead of blocking the unrelated printer/wasm setup below.
|
// Not awaited: resolving late just means shortUrl briefly shows "—" instead of blocking the unrelated printer/wasm setup below.
|
||||||
this.fetchIdMap().catch(e => {
|
this.fetchIdMap().catch(e => {
|
||||||
this.error = e.message;
|
this.error = e.message;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue