(function () {
'use strict';
/* =========================================================
GROM – Live-Gravurvorschau v4.1
(v4.1: Vorschau verschwindet, wenn Gravurtext/Schriftart ausgeblendet werden)
Gravurtext: Merkmal 109
Schriftart: Merkmal 110
Anmerkungen: Merkmal 120 (optional)
Änderungen gegenüber v3:
- Schutz gegen doppelte Einbindung
- Keine veraltete Vorschau mehr bei schnellem Tippen (Render-Token)
- Kurzes Debounce bei Eingabe
- Canvas-Höhe passt exakt in die Box, keine Mindestbreite mehr (Mobil)
- Lange Texte werden bei Web-Fonts nicht mehr abgeschnitten
- Keine Browser-Ersatzschrift, falls ein Font nicht lädt
- Platzhalter-Kästchen in den Sprites werden ausgefiltert
- Vorschau funktioniert auch ohne "Anmerkungen"-Feld
- Ungenutzter Code entfernt
========================================================= */
if (window.__gromGravurLiveInit) return;
window.__gromGravurLiveInit = true;
const PREVIEW_H = 52; /* Innenhöhe der Box: 62px - 2 * 5px Padding */
const BASE_SOURCE_PX = 180;
const MAX_TEMP_W = 6000;
const WEB_FONTS = {
"AZURE": {
family: "GromAzure",
url: "https://cdn02.plentyone.com/f2d5nd8wf1gh/frontend/gravur-fonts/AZURE.otf",
format: "opentype"
},
"Baby Script 42": {
family: "GromBabyScript42",
url: "https://cdn02.plentyone.com/f2d5nd8wf1gh/frontend/gravur-fonts/Baby_Script_42.otf",
format: "opentype"
},
"Optima 3L": {
family: "GromOptima3L",
url: "https://cdn02.plentyone.com/f2d5nd8wf1gh/frontend/gravur-fonts/Optima_3L.TTF",
format: "truetype"
},
"Script 4L": {
family: "GromScript4L",
url: "https://cdn02.plentyone.com/f2d5nd8wf1gh/frontend/gravur-fonts/Script_4L.otf",
format: "opentype"
}
};
const CFG = {
"Script Rund 1L": {
sprite: "https://cdn02.plentyone.com/f2d5nd8wf1gh/frontend/gravur-fonts/script-rund-1l-sprite-v3.png",
/* Diese Slots enthalten nur Platzhalter-Kästchen (identische bbox) */
blocked: [91, 93, 94, 95, 96, 123, 124, 125],
metrics: {"cellW":192,"cellH":160,"cols":16,"firstCodepoint":32,"metrics":{"32":{"advance":52,"bbox":null},"33":{"advance":40,"bbox":[82,27,114,114]},"34":{"advance":44,"bbox":[79,26,115,41]},"35":{"advance":61,"bbox":[71,27,124,71]},"36":{"advance":79,"bbox":[61,21,132,121]},"37":{"advance":74,"bbox":[65,27,131,114]},"38":{"advance":76,"bbox":[62,35,130,115]},"39":{"advance":28,"bbox":[90,27,104,42]},"40":{"advance":41,"bbox":[80,27,113,114]},"41":{"advance":41,"bbox":[81,28,114,114]},"42":{"advance":46,"bbox":[78,27,116,63]},"43":{"advance":60,"bbox":[71,46,123,95]},"44":{"advance":28,"bbox":[90,106,104,120]},"45":{"advance":50,"bbox":[76,89,118,92]},"46":{"advance":28,"bbox":[95,107,99,114]},"47":{"advance":66,"bbox":[71,27,129,114]},"48":{"advance":77,"bbox":[62,26,131,115]},"49":{"advance":40,"bbox":[82,27,114,114]},"50":{"advance":79,"bbox":[62,26,133,114]},"51":{"advance":78,"bbox":[63,27,133,115]},"52":{"advance":68,"bbox":[67,28,127,114]},"53":{"advance":78,"bbox":[63,27,133,115]},"54":{"advance":77,"bbox":[62,27,131,115]},"55":{"advance":73,"bbox":[65,26,130,114]},"56":{"advance":79,"bbox":[61,26,132,114]},"57":{"advance":77,"bbox":[63,26,132,115]},"58":{"advance":28,"bbox":[88,69,106,114]},"59":{"advance":36,"bbox":[83,69,111,120]},"60":{"advance":80,"bbox":[61,44,133,114]},"61":{"advance":60,"bbox":[70,58,122,82]},"62":{"advance":80,"bbox":[61,44,133,114]},"63":{"advance":54,"bbox":[74,26,120,114]},"64":{"advance":83,"bbox":[59,35,134,106]},"65":{"advance":94,"bbox":[57,26,143,115]},"66":{"advance":96,"bbox":[53,27,141,115]},"67":{"advance":81,"bbox":[61,27,134,114]},"68":{"advance":92,"bbox":[55,28,139,115]},"69":{"advance":76,"bbox":[64,27,132,115]},"70":{"advance":93,"bbox":[54,27,139,115]},"71":{"advance":93,"bbox":[54,27,139,117]},"72":{"advance":117,"bbox":[42,27,151,116]},"73":{"advance":83,"bbox":[60,26,135,115]},"74":{"advance":99,"bbox":[51,27,142,139]},"75":{"advance":120,"bbox":[41,27,153,115]},"76":{"advance":94,"bbox":[54,27,140,116]},"77":{"advance":140,"bbox":[30,27,162,115]},"78":{"advance":122,"bbox":[40,27,154,115]},"79":{"advance":84,"bbox":[58,27,134,116]},"80":{"advance":97,"bbox":[53,27,142,115]},"81":{"advance":87,"bbox":[58,27,137,116]},"82":{"advance":96,"bbox":[53,27,141,115]},"83":{"advance":78,"bbox":[65,26,135,116]},"84":{"advance":95,"bbox":[53,27,140,116]},"85":{"advance":107,"bbox":[48,27,147,116]},"86":{"advance":76,"bbox":[63,25,131,113]},"87":{"advance":98,"bbox":[53,25,143,113]},"88":{"advance":118,"bbox":[42,25,152,113]},"89":{"advance":113,"bbox":[45,26,150,142]},"90":{"advance":117,"bbox":[42,25,151,114]},"91":{"advance":81,"bbox":[61,43,134,113]},"92":{"advance":32,"bbox":[84,26,108,113]},"93":{"advance":80,"bbox":[61,43,133,113]},"94":{"advance":80,"bbox":[61,43,133,113]},"95":{"advance":80,"bbox":[61,43,133,113]},"96":{"advance":80,"bbox":[61,43,133,113]},"97":{"advance":70,"bbox":[65,54,127,114]},"98":{"advance":72,"bbox":[65,26,129,114]},"99":{"advance":62,"bbox":[70,54,124,114]},"100":{"advance":84,"bbox":[59,27,135,114]},"101":{"advance":62,"bbox":[73,53,127,113]},"102":{"advance":56,"bbox":[72,25,120,113]},"103":{"advance":81,"bbox":[61,54,134,142]},"104":{"advance":70,"bbox":[66,27,128,114]},"105":{"advance":41,"bbox":[81,26,114,114]},"106":{"advance":59,"bbox":[72,26,123,142]},"107":{"advance":70,"bbox":[67,27,129,114]},"108":{"advance":43,"bbox":[78,26,113,114]},"109":{"advance":89,"bbox":[57,53,138,114]},"110":{"advance":69,"bbox":[66,54,127,114]},"111":{"advance":62,"bbox":[70,54,124,115]},"112":{"advance":81,"bbox":[61,54,134,141]},"113":{"advance":66,"bbox":[68,55,126,141]},"114":{"advance":51,"bbox":[75,54,118,114]},"115":{"advance":63,"bbox":[69,53,124,114]},"116":{"advance":39,"bbox":[81,34,112,114]},"117":{"advance":66,"bbox":[68,54,126,114]},"118":{"advance":69,"bbox":[66,54,127,114]},"119":{"advance":89,"bbox":[59,54,140,115]},"120":{"advance":81,"bbox":[60,55,133,114]},"121":{"advance":76,"bbox":[63,55,131,143]},"122":{"advance":74,"bbox":[64,55,130,114]},"123":{"advance":80,"bbox":[62,43,134,114]},"124":{"advance":80,"bbox":[61,43,133,114]},"125":{"advance":81,"bbox":[61,43,134,114]},"126":{"advance":45,"bbox":[78,65,115,75]}}}
},
"US Block 1L": {
sprite: "https://cdn02.plentyone.com/f2d5nd8wf1gh/frontend/gravur-fonts/us-block-1l-sprite-v3.png",
/* Diese Slots enthalten nur Platzhalter-Kästchen (identische bbox) */
blocked: [94, 96, 124],
metrics: {"cellW":192,"cellH":160,"cols":16,"firstCodepoint":32,"metrics":{"32":{"advance":52,"bbox":null},"33":{"advance":28,"bbox":[93,27,103,114]},"34":{"advance":37,"bbox":[84,26,110,43]},"35":{"advance":72,"bbox":[67,27,128,86]},"36":{"advance":59,"bbox":[73,27,121,114]},"37":{"advance":87,"bbox":[60,26,136,115]},"38":{"advance":83,"bbox":[60,26,132,116]},"39":{"advance":28,"bbox":[92,24,102,42]},"40":{"advance":28,"bbox":[92,28,102,114]},"41":{"advance":28,"bbox":[92,27,102,114]},"42":{"advance":31,"bbox":[87,26,107,50]},"43":{"advance":78,"bbox":[63,37,130,103]},"44":{"advance":28,"bbox":[90,105,104,121]},"45":{"advance":67,"bbox":[69,81,125,84]},"46":{"advance":28,"bbox":[92,105,102,114]},"47":{"advance":63,"bbox":[74,26,126,114]},"48":{"advance":80,"bbox":[62,26,131,115]},"49":{"advance":31,"bbox":[88,26,108,114]},"50":{"advance":71,"bbox":[67,26,127,114]},"51":{"advance":74,"bbox":[67,26,130,115]},"52":{"advance":80,"bbox":[62,26,131,114]},"53":{"advance":73,"bbox":[67,26,129,115]},"54":{"advance":75,"bbox":[65,26,129,115]},"55":{"advance":72,"bbox":[67,26,128,114]},"56":{"advance":74,"bbox":[65,26,128,116]},"57":{"advance":75,"bbox":[65,26,129,115]},"58":{"advance":28,"bbox":[92,62,102,114]},"59":{"advance":28,"bbox":[91,62,104,119]},"60":{"advance":45,"bbox":[80,38,114,103]},"61":{"advance":52,"bbox":[76,60,117,80]},"62":{"advance":45,"bbox":[80,38,114,103]},"63":{"advance":62,"bbox":[72,25,123,114]},"64":{"advance":83,"bbox":[61,33,133,108]},"65":{"advance":79,"bbox":[66,26,134,114]},"66":{"advance":71,"bbox":[67,26,127,114]},"67":{"advance":72,"bbox":[67,25,128,115]},"68":{"advance":71,"bbox":[67,27,127,115]},"69":{"advance":70,"bbox":[68,27,127,115]},"70":{"advance":68,"bbox":[68,27,125,114]},"71":{"advance":72,"bbox":[67,26,128,116]},"72":{"advance":74,"bbox":[65,27,128,114]},"73":{"advance":28,"bbox":[96,27,99,114]},"74":{"advance":55,"bbox":[74,27,118,115]},"75":{"advance":72,"bbox":[67,27,128,114]},"76":{"advance":73,"bbox":[66,28,128,115]},"77":{"advance":87,"bbox":[59,27,135,114]},"78":{"advance":73,"bbox":[66,26,128,115]},"79":{"advance":78,"bbox":[63,26,130,116]},"80":{"advance":70,"bbox":[68,27,127,114]},"81":{"advance":84,"bbox":[60,26,133,121]},"82":{"advance":73,"bbox":[66,26,128,114]},"83":{"advance":75,"bbox":[68,26,132,116]},"84":{"advance":78,"bbox":[63,27,130,114]},"85":{"advance":72,"bbox":[67,27,128,116]},"86":{"advance":77,"bbox":[64,25,130,113]},"87":{"advance":109,"bbox":[49,25,147,113]},"88":{"advance":75,"bbox":[65,25,129,113]},"89":{"advance":76,"bbox":[65,25,130,113]},"90":{"advance":72,"bbox":[66,25,127,113]},"91":{"advance":28,"bbox":[90,25,105,113]},"92":{"advance":62,"bbox":[71,25,122,113]},"93":{"advance":28,"bbox":[90,25,105,113]},"94":{"advance":83,"bbox":[61,43,133,113]},"95":{"advance":57,"bbox":[74,123,120,126]},"96":{"advance":83,"bbox":[61,43,133,113]},"97":{"advance":68,"bbox":[68,48,125,115]},"98":{"advance":65,"bbox":[70,26,124,115]},"99":{"advance":63,"bbox":[71,48,123,115]},"100":{"advance":65,"bbox":[70,27,124,115]},"101":{"advance":68,"bbox":[72,48,129,115]},"102":{"advance":28,"bbox":[88,25,105,113]},"103":{"advance":67,"bbox":[70,48,126,138]},"104":{"advance":58,"bbox":[74,28,121,113]},"105":{"advance":28,"bbox":[97,27,100,113]},"106":{"advance":28,"bbox":[92,28,102,131]},"107":{"advance":61,"bbox":[73,27,123,113]},"108":{"advance":28,"bbox":[95,26,98,113]},"109":{"advance":98,"bbox":[54,48,141,113]},"110":{"advance":58,"bbox":[73,48,120,113]},"111":{"advance":70,"bbox":[68,48,127,115]},"112":{"advance":67,"bbox":[69,49,125,132]},"113":{"advance":67,"bbox":[69,49,125,133]},"114":{"advance":36,"bbox":[84,50,109,113]},"115":{"advance":58,"bbox":[73,47,120,115]},"116":{"advance":28,"bbox":[89,36,106,114]},"117":{"advance":57,"bbox":[74,51,120,116]},"118":{"advance":65,"bbox":[70,50,124,113]},"119":{"advance":93,"bbox":[59,50,141,113]},"120":{"advance":63,"bbox":[71,50,123,113]},"121":{"advance":56,"bbox":[75,51,120,135]},"122":{"advance":58,"bbox":[74,50,121,114]},"123":{"advance":41,"bbox":[83,26,113,114]},"124":{"advance":83,"bbox":[61,43,133,114]},"125":{"advance":40,"bbox":[83,26,112,114]},"126":{"advance":48,"bbox":[78,65,115,75]}}}
}
};
Object.keys(CFG).forEach(function (name) {
CFG[name].blockedSet = new Set(CFG[name].blocked || []);
});
/* Alle sechs Vorschauen werden auf dieselbe sichtbare Höhe normalisiert.
Dünne Schriften werden nur leicht angeglichen – die echte Charakteristik bleibt erhalten. */
const VISUAL = {
"AZURE": { height: 34, sourceStroke: 0.8 },
"Baby Script 42": { height: 34, sourceStroke: 1.6 },
"Optima 3L": { height: 34, sourceStroke: 0.0 },
"Script 4L": { height: 34, sourceStroke: 1.7 },
"Script Rund 1L": { height: 34, thicken: 0.22 },
"US Block 1L": { height: 34, thicken: 0.28 }
};
/* Hinweis: Die Werte 128–159 stammen aus der Font-Kodierung (Windows-1252-Positionen)
und lassen sich über die Tastatur praktisch nicht eingeben. Ein eingetipptes "€"
(Unicode 8364) wird daher bei diesen Schriften weiterhin ausgelassen. */
const WEB_SUPPORTED = {"AZURE":[32,38,45,48,49,50,51,52,53,54,55,56,57,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90],"Baby Script 42":[32,34,35,38,39,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,61,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,128,133,134,138,154,159,161,167,176,196,214,220,223,228,246,252],"Optima 3L":[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,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,338,339,352,353,376,402],"Script 4L":[32,33,34,35,37,39,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,61,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,128,133,134,138,154,159,161,176,196,214,220,228,246,252]};
const WEB_SUPPORTED_SETS = {};
Object.keys(WEB_SUPPORTED).forEach(function (name) {
WEB_SUPPORTED_SETS[name] = new Set(WEB_SUPPORTED[name]);
});
const imageCache = {};
const measureCtx = document.createElement('canvas').getContext('2d');
let renderToken = 0;
let debounceTimer = null;
/* ---------- Fonts ---------- */
function installWebFonts() {
if (document.getElementById('grom-gravur-live-fonts')) return;
const style = document.createElement('style');
style.id = 'grom-gravur-live-fonts';
let css = '';
Object.keys(WEB_FONTS).forEach(function (name) {
const f = WEB_FONTS[name];
css += '@font-face{' +
'font-family:"' + f.family + '";' +
'src:url("' + f.url + '") format("' + f.format + '");' +
'font-style:normal;' +
'font-weight:400;' +
'font-display:swap;' +
'}';
});
style.textContent = css;
document.head.appendChild(style);
}
/* true = Font ist geladen; false = Laden fehlgeschlagen
(dann wird NICHT in einer Ersatzschrift gezeichnet). */
async function ensureFontLoaded(cfg, sample) {
if (!document.fonts || !document.fonts.load) return true;
try {
const faces = await document.fonts.load('400 40px "' + cfg.family + '"', sample);
if (!faces || !faces.length) return false;
return faces.every(function (f) { return f.status === 'loaded'; });
} catch (e) {
return false;
}
}
/* ---------- DOM ---------- */
function getTextInput() {
return document.querySelector(
'#order-property-input-container_109 input, ' +
'#order-property-input-container_109 textarea'
);
}
function getFontSelect() {
return document.querySelector('#order-property-input-select_110');
}
function getSelectedFontName() {
const select = getFontSelect();
if (!select || select.selectedIndex < 0) return '';
const opt = select.options[select.selectedIndex];
return opt ? opt.text.trim() : '';
}
function findNotesContainer() {
/* Im echten Plenty-DOM ist Anmerkungen Merkmal 120. */
const direct = document.getElementById('order-property-input-container_120');
if (direct) return direct;
/* Fallback, falls Plenty die ID irgendwann ändert. */
const containers = document.querySelectorAll('[id^="order-property-input-container_"]');
for (const container of containers) {
const labels = container.querySelectorAll('label');
for (const label of labels) {
if ((label.textContent || '').trim() === 'Anmerkungen') return container;
}
}
return null;
}
function getNotesLabel(notesContainer) {
if (!notesContainer) return null;
const labels = notesContainer.querySelectorAll('label');
for (const label of labels) {
if ((label.textContent || '').trim() === 'Anmerkungen') return label;
}
return null;
}
function setPreviewState(previewVisible) {
const notesContainer = findNotesContainer();
const notesLabel = getNotesLabel(notesContainer);
const customNotesLabel = document.getElementById('grom-live-notes-label');
/* Eigenes Label nur zeigen, wenn wir das Plenty-Label tatsächlich ersetzen. */
if (customNotesLabel) {
customNotesLabel.style.display = (previewVisible && notesLabel) ? 'block' : 'none';
}
if (!notesContainer) return;
if (previewVisible) {
/* Das absolut positionierte Plenty-Label würde die Vorschau überlagern. */
notesContainer.style.setProperty('padding-top', '0px', 'important');
if (notesLabel) notesLabel.style.setProperty('display', 'none', 'important');
} else {
/* Geschlossener Zustand: native Plenty-Beschriftung + 9 px Abstand. */
notesContainer.style.setProperty('padding-top', '9px', 'important');
notesContainer.style.setProperty('box-sizing', 'border-box', 'important');
if (notesLabel) notesLabel.style.removeProperty('display');
}
}
function ensurePreview() {
const input = getTextInput();
const textContainer = document.getElementById('order-property-input-container_109');
if (!input || !textContainer) return null;
let wrap = document.getElementById('grom-live-engraving-preview');
if (!wrap) {
wrap = document.createElement('div');
wrap.id = 'grom-live-engraving-preview';
wrap.style.cssText =
'display:none;' +
'width:100%;' +
'margin:9px 0 0 0;' +
'padding:0;' +
'box-sizing:border-box;' +
'clear:both;';
const label = document.createElement('div');
label.textContent = 'Gravurvorschau';
label.style.cssText =
'font-size:11px;' +
'font-family:inherit;' +
'font-weight:400;' +
'line-height:1.15;' +
'color:#333;' +
'margin:0 0 4px 0;' +
'padding:0;' +
'text-align:left;';
const box = document.createElement('div');
box.id = 'grom-live-engraving-box';
box.style.cssText =
'width:100%;' +
'height:62px;' +
'padding:5px 8px;' +
'border:0;' +
'background:#f7f7f7;' +
'box-sizing:border-box;' +
'display:flex;' +
'align-items:center;' +
'justify-content:center;' +
'overflow:hidden;';
const canvas = document.createElement('canvas');
canvas.id = 'grom-live-engraving-canvas';
canvas.style.cssText =
'display:block;' +
'width:100%;' +
'height:' + PREVIEW_H + 'px;' +
'margin:0;' +
'padding:0;';
const customNotesLabel = document.createElement('div');
customNotesLabel.id = 'grom-live-notes-label';
customNotesLabel.textContent = 'Anmerkungen';
customNotesLabel.style.cssText =
'display:none;' +
'font-size:11px;' +
'font-family:inherit;' +
'font-weight:400;' +
'line-height:1.15;' +
'color:#333;' +
'margin:10px 0 4px 0;' +
'padding:0;' +
'text-align:left;';
box.appendChild(canvas);
wrap.appendChild(label);
wrap.appendChild(box);
wrap.appendChild(customNotesLabel);
textContainer.insertAdjacentElement('afterend', wrap);
} else if (wrap.previousElementSibling !== textContainer) {
textContainer.insertAdjacentElement('afterend', wrap);
}
return wrap;
}
function showPreview(wrap) {
wrap.style.display = 'block';
setPreviewState(true);
}
function hidePreview(wrap) {
if (wrap) wrap.style.display = 'none';
setPreviewState(false);
}
/* ---------- Canvas ---------- */
function loadImage(url) {
if (imageCache[url]) return imageCache[url];
imageCache[url] = new Promise(function (resolve, reject) {
const im = new Image();
im.onload = function () { resolve(im); };
im.onerror = function (e) {
delete imageCache[url]; /* späterer neuer Versuch möglich */
reject(e);
};
im.src = url;
});
return imageCache[url];
}
function setupCanvas(canvas) {
const box = document.getElementById('grom-live-engraving-box');
if (!box) return null;
const dpr = Math.min(window.devicePixelRatio || 1, 2);
const cssW = Math.max(1, box.clientWidth - 16); /* 2 * 8px Padding */
const cssH = PREVIEW_H;
canvas.width = Math.round(cssW * dpr);
canvas.height = Math.round(cssH * dpr);
canvas.style.width = cssW + 'px';
canvas.style.height = cssH + 'px';
const ctx = canvas.getContext('2d');
ctx.setTransform(1, 0, 0, 1, 0, 0);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.imageSmoothingEnabled = true;
ctx.imageSmoothingQuality = 'high';
return { ctx: ctx, dpr: dpr, width: canvas.width, height: canvas.height };
}
function findInkBounds(ctx, w, h) {
const a = ctx.getImageData(0, 0, w, h).data;
let minX = w, minY = h, maxX = -1, maxY = -1;
for (let y = 0; y < h; y++) {
const row = y * w * 4;
for (let x = 0; x < w; x++) {
if (a[row + x * 4 + 3] > 8) {
if (x < minX) minX = x;
if (x > maxX) maxX = x;
if (y < minY) minY = y;
if (y > maxY) maxY = y;
}
}
}
if (maxX < 0) return null;
return { x: minX, y: minY, w: maxX - minX + 1, h: maxY - minY + 1 };
}
/* ---------- Web-Fonts ---------- */
function sanitizeWebText(text, fontName) {
let value = text;
/* AZURE: nur Großbuchstaben. Umlaute/ß auf vorhandene Zeichen abbilden. */
if (fontName === 'AZURE') {
value = value
.toUpperCase()
.replace(/Ä/g, 'A')
.replace(/Ö/g, 'O')
.replace(/Ü/g, 'U')
.replace(/ẞ/g, 'SS')
.replace(/ß/g, 'SS');
}
/* Script 4L hat echte ÄÖÜ/äöü, aber kein ß. */
if (fontName === 'Script 4L') {
value = value.replace(/ẞ/g, 'SS').replace(/ß/g, 'ss');
}
/* Bei den anderen Fonts ẞ auf SS abbilden, da nur ß vorhanden ist. */
if (fontName === 'Baby Script 42' || fontName === 'Optima 3L') {
value = value.replace(/ẞ/g, 'SS');
}
const set = WEB_SUPPORTED_SETS[fontName] || new Set();
/* Nicht vorhandene Zeichen werden ausgelassen – keine Browser-Ersatzschrift. */
return Array.from(value).filter(function (ch) {
return set.has(ch.codePointAt(0));
}).join('');
}
/* Rückgabe: false = Font konnte nicht geladen werden, sonst true. */
async function renderWebFont(text, fontName, token) {
const canvas = document.getElementById('grom-live-engraving-canvas');
const cfg = WEB_FONTS[fontName];
const visual = VISUAL[fontName] || { height: 34, sourceStroke: 0 };
if (!canvas || !cfg) return true;
const previewText = sanitizeWebText(text, fontName);
if (!previewText) {
setupCanvas(canvas); /* leeren */
return true;
}
const loaded = await ensureFontLoaded(cfg, previewText);
if (token !== renderToken) return true; /* inzwischen neuere Eingabe */
if (!loaded) return false;
/* Ab hier kein await mehr – alles läuft synchron mit dem aktuellen Text. */
let sourcePx = BASE_SOURCE_PX;
let pad = Math.round(sourcePx * 0.28);
measureCtx.font = '400 ' + sourcePx + 'px "' + cfg.family + '"';
let measured = Math.max(1, Math.ceil(measureCtx.measureText(previewText).width));
/* Lange Texte: Quellgröße verkleinern statt abschneiden. */
if (measured + pad * 2 > MAX_TEMP_W) {
sourcePx = Math.max(24, Math.floor(sourcePx * (MAX_TEMP_W - pad * 2) / measured));
pad = Math.round(sourcePx * 0.28);
measureCtx.font = '400 ' + sourcePx + 'px "' + cfg.family + '"';
measured = Math.max(1, Math.ceil(measureCtx.measureText(previewText).width));
}
const temp = document.createElement('canvas');
temp.width = Math.min(MAX_TEMP_W, measured + pad * 2);
temp.height = Math.ceil(sourcePx * 1.78);
const t = temp.getContext('2d', { willReadFrequently: true });
t.font = '400 ' + sourcePx + 'px "' + cfg.family + '"';
t.textAlign = 'left';
t.textBaseline = 'alphabetic';
t.lineJoin = 'round';
t.lineCap = 'round';
t.fillStyle = '#111';
t.strokeStyle = '#111';
const baseline = Math.round(sourcePx * 1.25);
if (visual.sourceStroke > 0) {
t.lineWidth = visual.sourceStroke * (sourcePx / BASE_SOURCE_PX);
t.strokeText(previewText, pad, baseline);
}
t.fillText(previewText, pad, baseline);
/* Sichtbare Pixel suchen – so bekommen alle Schriften dieselbe sichtbare Höhe. */
const b = findInkBounds(t, temp.width, temp.height);
const state = setupCanvas(canvas);
if (!state || !b) return true;
const out = state.ctx;
const W = state.width;
const H = state.height;
const targetH = visual.height * state.dpr;
const availableW = W - (20 * state.dpr);
let scale = targetH / b.h;
if ((b.w * scale) > availableW) scale = availableW / b.w;
const dw = b.w * scale;
const dh = b.h * scale;
const dx = (W - dw) / 2;
const dy = (H - dh) / 2;
out.drawImage(temp, b.x, b.y, b.w, b.h, dx, dy, dw, dh);
return true;
}
/* ---------- Sprite-Fonts ---------- */
function getSpriteGlyph(ch, cfg) {
const M = cfg.metrics;
const cp = ch.codePointAt(0);
if (cfg.blockedSet.has(cp)) return null;
if (cp >= 32 && cp <= 126 && M.metrics[String(cp)]) {
const mm = M.metrics[String(cp)];
return {
index: cp - M.firstCodepoint,
advance: mm.advance,
bbox: mm.bbox,
diaeresis: false
};
}
return null;
}
function expandSpriteText(text, cfg) {
const out = [];
const umlautMap = { 'ä': 'a', 'ö': 'o', 'ü': 'u', 'Ä': 'A', 'Ö': 'O', 'Ü': 'U' };
Array.from(text).forEach(function (ch) {
/* ß ist in den Sprites nicht als eigener Slot vorhanden → ss / SS. */
if (ch === 'ß') {
out.push({ ch: 's', diaeresis: false });
out.push({ ch: 's', diaeresis: false });
return;
}
if (ch === 'ẞ') {
out.push({ ch: 'S', diaeresis: false });
out.push({ ch: 'S', diaeresis: false });
return;
}
if (umlautMap[ch]) {
out.push({ ch: umlautMap[ch], diaeresis: true });
return;
}
if (getSpriteGlyph(ch, cfg)) {
out.push({ ch: ch, diaeresis: false });
}
/* Nicht vorhandene Zeichen werden bewusst ausgelassen. */
});
return out;
}
/* Rückgabe: false = Sprite konnte nicht geladen werden, sonst true. */
async function renderSprite(text, fontName, token) {
const canvas = document.getElementById('grom-live-engraving-canvas');
const cfg = CFG[fontName];
const visual = VISUAL[fontName] || { height: 34, thicken: 0 };
if (!canvas || !cfg) return true;
let im;
try {
im = await loadImage(cfg.sprite);
} catch (e) {
if (token !== renderToken) return true;
return false;
}
if (token !== renderToken) return true; /* inzwischen neuere Eingabe */
/* Ab hier kein await mehr. */
const state = setupCanvas(canvas);
if (!state) return true;
const ctx = state.ctx;
const dpr = state.dpr;
const W = state.width;
const H = state.height;
const M = cfg.metrics;
const glyphs = [];
expandSpriteText(text, cfg).forEach(function (item) {
const g = getSpriteGlyph(item.ch, cfg);
if (g) {
g.diaeresis = item.diaeresis;
glyphs.push(g);
}
});
if (!glyphs.length) return true;
let minTop = 999;
let maxBottom = 0;
glyphs.forEach(function (g) {
if (g.bbox) {
let top = g.bbox[1];
if (g.diaeresis) top -= 14;
minTop = Math.min(minTop, top);
maxBottom = Math.max(maxBottom, g.bbox[3]);
}
});
if (minTop === 999) {
minTop = 25;
maxBottom = 116;
}
const sourceVisualH = Math.max(1, maxBottom - minTop);
let sc = (visual.height * dpr) / sourceVisualH;
let totalW = 0;
glyphs.forEach(function (g) { totalW += g.advance * sc; });
const availableW = W - (20 * dpr);
if (totalW > availableW && totalW > 0) {
sc *= availableW / totalW;
totalW = availableW;
}
const sourceBaseline = 116;
const centerOffset = (((minTop + maxBottom) / 2) - sourceBaseline) * sc;
const baseline = (H / 2) - centerOffset;
let x = (W - totalW) / 2;
const th = (visual.thicken || 0) * dpr;
function drawGlyph(g, dx, dy) {
const idx = g.index;
const sx = (idx % M.cols) * M.cellW;
const sy = Math.floor(idx / M.cols) * M.cellH;
const dw = M.cellW * sc;
const dh = M.cellH * sc;
const adv = g.advance * sc;
const drawX = x - (dw / 2) + (adv / 2) + dx;
const drawY = baseline - (sourceBaseline * sc) + dy;
ctx.drawImage(im, sx, sy, M.cellW, M.cellH, drawX, drawY, dw, dh);
}
glyphs.forEach(function (g) {
if (th > 0) {
drawGlyph(g, -th, 0);
drawGlyph(g, th, 0);
drawGlyph(g, 0, -th);
drawGlyph(g, 0, th);
}
drawGlyph(g, 0, 0);
if (g.diaeresis) {
const adv = g.advance * sc;
const cx = x + (adv / 2);
const topY = baseline - (sourceBaseline * sc) + ((g.bbox ? g.bbox[1] : 50) * sc);
const dotY = topY - (7 * sc);
const dotOffset = Math.max(3.0 * dpr, 7.5 * sc);
const r = Math.max(1.15 * dpr, 2.7 * sc);
ctx.fillStyle = '#111';
ctx.beginPath();
ctx.arc(cx - dotOffset, dotY, r, 0, Math.PI * 2);
ctx.fill();
ctx.beginPath();
ctx.arc(cx + dotOffset, dotY, r, 0, Math.PI * 2);
ctx.fill();
}
x += g.advance * sc;
});
/* Sprite-Dateien sind blau; alle Vorschauen sollen gleich schwarz aussehen. */
ctx.globalCompositeOperation = 'source-in';
ctx.fillStyle = '#111';
ctx.fillRect(0, 0, W, H);
ctx.globalCompositeOperation = 'source-over';
return true;
}
/* ---------- Steuerung ---------- */
/* true, wenn das Element im DOM hängt und tatsächlich angezeigt wird. */
function isVisible(el) {
if (!el || !el.isConnected) return false;
if (!el.getClientRects().length) return false;
return window.getComputedStyle(el).visibility !== 'hidden';
}
async function renderPreview() {
const token = ++renderToken;
installWebFonts();
const input = getTextInput();
const select = getFontSelect();
const textContainer = document.getElementById('order-property-input-container_109');
/* BUGFIX v4.1: Blendet Plenty Gravurtext oder Schriftart aus
(z. B. bei "Lasergravur: Bild/Logo/Wappen"), muss auch die
Vorschau verschwinden – sonst bleibt der alte Text stehen. */
if (!input || !select || !isVisible(textContainer) || !isVisible(select)) {
hidePreview(document.getElementById('grom-live-engraving-preview'));
return;
}
const wrap = ensurePreview();
if (!wrap) return;
const text = input.value || '';
const fontName = getSelectedFontName();
const isWeb = !!WEB_FONTS[fontName];
const isSprite = !!CFG[fontName];
if (!text.trim() || (!isWeb && !isSprite)) {
hidePreview(wrap);
return;
}
/* Erst sichtbar machen, damit die Box eine Breite hat. */
showPreview(wrap);
let ok = true;
try {
ok = isWeb
? await renderWebFont(text, fontName, token)
: await renderSprite(text, fontName, token);
} catch (e) {
ok = false;
}
if (token !== renderToken) return; /* neuere Eingabe übernimmt */
if (!ok) hidePreview(wrap); /* Font/Sprite nicht ladbar → lieber keine Vorschau */
}
function scheduleRender(delay) {
clearTimeout(debounceTimer);
debounceTimer = setTimeout(renderPreview, delay || 0);
}
document.addEventListener('input', function (e) {
if (e.target && e.target.closest && e.target.closest('#order-property-input-container_109')) {
scheduleRender(60);
}
});
document.addEventListener('change', function (e) {
if (e.target && e.target.id === 'order-property-input-select_110') {
scheduleRender(0);
return;
}
/* BUGFIX v4.1: Jede andere Merkmal-Auswahl (z. B. Gravurart) kann Felder
ein- oder ausblenden. Plenty baut das DOM danach neu auf → mehrfach prüfen. */
if (e.target && e.target.closest && e.target.closest('[id^="order-property-input-container_"]')) {
scheduleRender(0);
setTimeout(renderPreview, 250);
setTimeout(renderPreview, 700);
}
});
document.addEventListener('click', function (e) {
if (e.target && e.target.closest && e.target.closest('.properties-toggle')) {
setTimeout(renderPreview, 150);
setTimeout(renderPreview, 400);
}
});
document.addEventListener('onVariationChanged', function () {
setTimeout(renderPreview, 250);
setTimeout(renderPreview, 600);
});
window.addEventListener('resize', function () {
scheduleRender(120);
});
installWebFonts();
setPreviewState(false);
setTimeout(renderPreview, 300);
setTimeout(renderPreview, 900);
})();