fix print
This commit is contained in:
@ -3,7 +3,7 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||||
<title>4 cm Sticker Sheet – Hex vs Grid</title>
|
<title>4 cm Sticker Sheet – Hex vs Grid (Print-Fixed)</title>
|
||||||
<style>
|
<style>
|
||||||
:root {
|
:root {
|
||||||
--sticker-diameter-cm: 4; /* Content circle diameter */
|
--sticker-diameter-cm: 4; /* Content circle diameter */
|
||||||
@ -16,6 +16,8 @@
|
|||||||
--page-height-cm: 29.7;
|
--page-height-cm: 29.7;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Base @page; the exact size is injected dynamically so the printer uses
|
||||||
|
the selected paper & orientation without scaling. */
|
||||||
@page { margin: 0; }
|
@page { margin: 0; }
|
||||||
|
|
||||||
* { box-sizing: border-box; }
|
* { box-sizing: border-box; }
|
||||||
@ -85,6 +87,8 @@
|
|||||||
gap: 12px;
|
gap: 12px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* On-screen preview uses px so it fits the viewport nicely.
|
||||||
|
For print we override width/height to exact cm below. */
|
||||||
.page {
|
.page {
|
||||||
background: white;
|
background: white;
|
||||||
margin: 0 auto;
|
margin: 0 auto;
|
||||||
@ -123,13 +127,14 @@
|
|||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Print: force exact physical size (cm) so PDF printers don't scale or clip. */
|
||||||
@media print {
|
@media print {
|
||||||
body { background: white; }
|
body { background: white; }
|
||||||
.controls { display: none !important; }
|
.controls { display: none !important; }
|
||||||
.pages { padding: 0; }
|
.pages { padding: 0; }
|
||||||
.page {
|
.page {
|
||||||
width: auto !important;
|
width: calc(var(--page-width-cm) * 1cm) !important;
|
||||||
height: auto !important;
|
height: calc(var(--page-height-cm) * 1cm) !important;
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
outline: none !important;
|
outline: none !important;
|
||||||
page-break-after: always;
|
page-break-after: always;
|
||||||
@ -141,6 +146,8 @@
|
|||||||
.page { width: 100%; height: auto; aspect-ratio: var(--page-width-cm) / var(--page-height-cm); }
|
.page { width: 100%; height: auto; aspect-ratio: var(--page-width-cm) / var(--page-height-cm); }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
<!-- This style tag will be populated dynamically to set @page size -->
|
||||||
|
<style id="page-size-style"></style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
@ -206,7 +213,7 @@
|
|||||||
<span class="stats" id="stats">–</span>
|
<span class="stats" id="stats">–</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<span class="hint">Print at 100% scale with “Print backgrounds/graphics” enabled. If edges clip, increase Printer margin slightly (e.g., 0.3–0.5 cm).</span>
|
<span class="hint">Print to PDF at 100% scale (or “Actual size”) with backgrounds enabled. If edges clip, increase Printer margin slightly (0.3–0.5 cm).</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@ -232,7 +239,8 @@
|
|||||||
pages: document.getElementById('pages'),
|
pages: document.getElementById('pages'),
|
||||||
generate: document.getElementById('generate'),
|
generate: document.getElementById('generate'),
|
||||||
print: document.getElementById('print'),
|
print: document.getElementById('print'),
|
||||||
stats: document.getElementById('stats')
|
stats: document.getElementById('stats'),
|
||||||
|
pageSizeStyle: document.getElementById('page-size-style')
|
||||||
};
|
};
|
||||||
|
|
||||||
let imageDataURL = null;
|
let imageDataURL = null;
|
||||||
@ -273,7 +281,7 @@
|
|||||||
const isLandscape = el.orientation.value === 'landscape';
|
const isLandscape = el.orientation.value === 'landscape';
|
||||||
if (isLandscape) [w, h] = [h, w];
|
if (isLandscape) [w, h] = [h, w];
|
||||||
|
|
||||||
// Push CSS variables for preview
|
// Push CSS variables
|
||||||
document.documentElement.style.setProperty('--sticker-diameter-cm', d);
|
document.documentElement.style.setProperty('--sticker-diameter-cm', d);
|
||||||
document.documentElement.style.setProperty('--gutter-cm', g);
|
document.documentElement.style.setProperty('--gutter-cm', g);
|
||||||
document.documentElement.style.setProperty('--margin-cm', m);
|
document.documentElement.style.setProperty('--margin-cm', m);
|
||||||
@ -282,7 +290,10 @@
|
|||||||
document.documentElement.style.setProperty('--outline-on', outlineOn);
|
document.documentElement.style.setProperty('--outline-on', outlineOn);
|
||||||
document.documentElement.style.setProperty('--fit-mode', fit);
|
document.documentElement.style.setProperty('--fit-mode', fit);
|
||||||
|
|
||||||
// Build one page (duplicate in print dialog for more)
|
// Force the printer page to use exactly this size (prevents the "quarter page" issue).
|
||||||
|
el.pageSizeStyle.textContent = `@page { size: ${w}cm ${h}cm; margin: 0; }`;
|
||||||
|
|
||||||
|
// Build page
|
||||||
el.pages.innerHTML = '';
|
el.pages.innerHTML = '';
|
||||||
const page = document.createElement('div');
|
const page = document.createElement('div');
|
||||||
page.className = 'page';
|
page.className = 'page';
|
||||||
@ -291,7 +302,7 @@
|
|||||||
page.appendChild(canvas);
|
page.appendChild(canvas);
|
||||||
el.pages.appendChild(page);
|
el.pages.appendChild(page);
|
||||||
|
|
||||||
// Dimensions inside margins
|
// Dimensions inside margins (cm)
|
||||||
const W = w - 2*m;
|
const W = w - 2*m;
|
||||||
const H = h - 2*m;
|
const H = h - 2*m;
|
||||||
|
|
||||||
@ -299,30 +310,30 @@
|
|||||||
const outlineWidthCm = outlineOn ? (getCssNumber('--outline-mm') / 10) : 0; // mm -> cm
|
const outlineWidthCm = outlineOn ? (getCssNumber('--outline-mm') / 10) : 0; // mm -> cm
|
||||||
const outerD = d + 2*outlineWidthCm;
|
const outerD = d + 2*outlineWidthCm;
|
||||||
|
|
||||||
// Generate placements for selected layout
|
// Generate placements
|
||||||
const placements = layout === 'grid'
|
const placements = (layout === 'grid')
|
||||||
? packGrid(W, H, outerD, g)
|
? packGrid(W, H, outerD, g)
|
||||||
: packHex(W, H, outerD, g);
|
: packHex(W, H, outerD, g);
|
||||||
|
|
||||||
// Render stickers
|
// Render stickers
|
||||||
placements.forEach(p => {
|
placements.forEach(p => {
|
||||||
const s = createSticker(imageDataURL);
|
const s = createSticker(imageDataURL);
|
||||||
// Offset by outline so the content circle sits at p.x..p.x+outerD
|
// Align content circle; outline is border around it
|
||||||
s.style.left = cm(p.x + outlineWidthCm);
|
s.style.left = cm(p.x + outlineWidthCm);
|
||||||
s.style.top = cm(p.y + outlineWidthCm);
|
s.style.top = cm(p.y + outlineWidthCm);
|
||||||
canvas.appendChild(s);
|
canvas.appendChild(s);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Compute comparison stats for both layouts
|
// Compare both layouts
|
||||||
const hexCount = packHex(W, H, outerD, g).length;
|
const hexCount = packHex(W, H, outerD, g).length;
|
||||||
const gridCount = packGrid(W, H, outerD, g).length;
|
const gridCount = packGrid(W, H, outerD, g).length;
|
||||||
const used = placements.length;
|
const used = placements.length;
|
||||||
const areaCanvas = W * H;
|
const areaCanvas = W * H;
|
||||||
const areaCircle = Math.PI * Math.pow(outerD/2, 2); // using outer diameter for sheet usage
|
const areaCircle = Math.PI * Math.pow(outerD/2, 2);
|
||||||
const coverage = areaCanvas > 0 ? (used * areaCircle / areaCanvas) : 0;
|
const coverage = areaCanvas > 0 ? (used * areaCircle / areaCanvas) : 0;
|
||||||
|
|
||||||
const gain = hexCount - gridCount;
|
const gain = hexCount - gridCount;
|
||||||
const gainText = gain === 0 ? 'same' : (gain > 0 ? `+${gain} with Hex` : `+${-gain} with Grid`);
|
const gainText = gain === 0 ? 'same'
|
||||||
|
: (gain > 0 ? `+${gain} with Hex` : `+${-gain} with Grid`);
|
||||||
|
|
||||||
el.stats.innerHTML =
|
el.stats.innerHTML =
|
||||||
`<strong>${layout.toUpperCase()}</strong> layout: ${used} stickers • ` +
|
`<strong>${layout.toUpperCase()}</strong> layout: ${used} stickers • ` +
|
||||||
@ -331,7 +342,6 @@
|
|||||||
` • Coverage ${(coverage*100).toFixed(1)}%` +
|
` • Coverage ${(coverage*100).toFixed(1)}%` +
|
||||||
` — Compare ⇒ Hex: ${hexCount}, Grid: ${gridCount} (${gainText})`;
|
` — Compare ⇒ Hex: ${hexCount}, Grid: ${gridCount} (${gainText})`;
|
||||||
|
|
||||||
// If none fit, show a helpful message
|
|
||||||
if (placements.length === 0) {
|
if (placements.length === 0) {
|
||||||
const msg = document.createElement('div');
|
const msg = document.createElement('div');
|
||||||
msg.style.position = 'absolute';
|
msg.style.position = 'absolute';
|
||||||
@ -345,18 +355,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hexagonal packing (odd rows offset by half pitch)
|
// Hexagonal packing
|
||||||
function packHex(W, H, outerD, gutter) {
|
function packHex(W, H, outerD, gutter) {
|
||||||
const res = [];
|
const res = [];
|
||||||
const pitchX = outerD + gutter;
|
const pitchX = outerD + gutter;
|
||||||
const pitchY = (Math.sqrt(3) / 2) * outerD + gutter;
|
const pitchY = (Math.sqrt(3) / 2) * outerD + gutter;
|
||||||
const halfStep = 0.5 * (outerD + gutter);
|
const halfStep = 0.5 * (outerD + gutter);
|
||||||
|
|
||||||
let y = 0;
|
let y = 0, row = 0;
|
||||||
let row = 0;
|
|
||||||
while (y + outerD <= H + 1e-6) {
|
while (y + outerD <= H + 1e-6) {
|
||||||
const startX = (row % 2 === 1) ? halfStep : 0;
|
let x = (row % 2 === 1) ? halfStep : 0;
|
||||||
let x = startX;
|
|
||||||
while (x + outerD <= W + 1e-6) {
|
while (x + outerD <= W + 1e-6) {
|
||||||
res.push({ x, y });
|
res.push({ x, y });
|
||||||
x += pitchX;
|
x += pitchX;
|
||||||
@ -366,10 +374,9 @@
|
|||||||
if (row > 5000) break;
|
if (row > 5000) break;
|
||||||
}
|
}
|
||||||
return res;
|
return res;
|
||||||
// Note: This is edge-to-edge packing using the outer diameter (includes outline if enabled).
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Simple rows & columns grid
|
// Rows & columns grid
|
||||||
function packGrid(W, H, outerD, gutter) {
|
function packGrid(W, H, outerD, gutter) {
|
||||||
const res = [];
|
const res = [];
|
||||||
const pitchX = outerD + gutter;
|
const pitchX = outerD + gutter;
|
||||||
|
Reference in New Issue
Block a user