Vorschau-Kuli: Optik komplett ins SVG gebacken (blaue Tinte, mitskalierende Striche, dezentes Papier) -> Anzeige == gespeichertes Bild
This commit is contained in:
@@ -324,19 +324,25 @@ const NeuAuftrag = defineComponent({
|
||||
const sel = (val, opts, on, extra) => h('select', { style: WSELECT + 'width:100%;' + (extra || ''), value: val, onChange: on },
|
||||
opts.map((o) => h('option', { value: o.v, selected: String(val) === String(o.v) }, o.t)));
|
||||
const svgUri = (svg) => { try { return 'data:image/svg+xml;base64,' + btoa(unescape(encodeURIComponent(svg))); } catch { return null; } };
|
||||
// Vorschau-Optik „Kugelschreiber": NUR für die Anzeige – Striche in Kuli-Blau,
|
||||
// etwas dicker, und der weiße Untergrund wird transparent, damit das dezente
|
||||
// Papier (CSS) durchscheint. Die generierten/gecachten SVGs bleiben schwarz –
|
||||
// die Produktion (die die gezeigten Dokumente 1:1 übernimmt) ist unberührt.
|
||||
// Vorschau-Optik „Kugelschreiber" – wird komplett INS SVG gebacken, damit
|
||||
// Anzeige UND gespeichertes Bild identisch sind:
|
||||
// • blaue Tinte (leichte Deckkraft für den Ink-Look),
|
||||
// • mitskalierende Strichstärke (kein non-scaling → im großen PNG nicht dünn),
|
||||
// • dezentes Papier (warmer Ton + feine Struktur) statt reinweiß.
|
||||
// Nur Anzeige/Download; die generierten/gecachten SVGs (Produktion) bleiben schwarz.
|
||||
const BALLPOINT = '#1a2a6c';
|
||||
const inkify = (svg) => (typeof svg === 'string'
|
||||
? svg
|
||||
.replace(/stroke="#0{3,6}"/gi, `stroke="${BALLPOINT}"`)
|
||||
.replace(/stroke-width="0\.6"/gi, 'stroke-width="0.9"')
|
||||
.replace(/fill="#FFFFFF"/gi, 'fill="transparent"')
|
||||
: svg);
|
||||
// Dezentes, leicht strukturiertes Papier hinter der (nun transparenten) Vorschau.
|
||||
const PAPIER = "background-color:#fbfaf6;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='p'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='2' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23p)' opacity='0.05'/%3E%3C/svg%3E\");";
|
||||
const inkify = (svg) => {
|
||||
if (typeof svg !== 'string') return svg;
|
||||
return svg
|
||||
// Papier-Struktur-Filter einmalig einfügen.
|
||||
.replace(/(<svg[^>]*>)/i, '$1<defs><filter id="skpaper" x="0" y="0" width="100%" height="100%"><feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="2" stitchTiles="stitch"/><feColorMatrix type="saturate" values="0"/></filter></defs>')
|
||||
// Weißer Hintergrund → Papierton + dezente Struktur darüber.
|
||||
.replace(/<rect([^>]*?)fill="#FFFFFF"([^>]*?)\/>/i, (m, a, b) => `<rect${a}fill="#f7f4ec"${b}/><rect${a}fill="#000"${b} filter="url(#skpaper)" opacity="0.06"/>`)
|
||||
// Tinte: blau + leichte Deckkraft, mitskalierend, etwas dicker.
|
||||
.replace(/stroke="#0{3,6}"/gi, `stroke="${BALLPOINT}" stroke-opacity="0.9"`)
|
||||
.replace(/\s*vector-effect="non-scaling-stroke"/gi, '')
|
||||
.replace(/stroke-width="0\.6"/gi, 'stroke-width="1.7"');
|
||||
};
|
||||
// Vorschau als PNG speichern (wie in der App); Fallback: SVG.
|
||||
function bildSpeichern(d) {
|
||||
const svg = d && typeof d.svg === 'string' ? inkify(d.svg) : null;
|
||||
@@ -349,7 +355,7 @@ const NeuAuftrag = defineComponent({
|
||||
const cv = document.createElement('canvas');
|
||||
cv.width = (img.naturalWidth || 800) * scale; cv.height = (img.naturalHeight || 1130) * scale;
|
||||
const ctx = cv.getContext('2d');
|
||||
ctx.fillStyle = '#fbfaf6'; ctx.fillRect(0, 0, cv.width, cv.height);
|
||||
ctx.fillStyle = '#f7f4ec'; ctx.fillRect(0, 0, cv.width, cv.height);
|
||||
ctx.drawImage(img, 0, 0, cv.width, cv.height);
|
||||
cv.toBlob((blob) => {
|
||||
if (!blob) return;
|
||||
@@ -371,7 +377,7 @@ const NeuAuftrag = defineComponent({
|
||||
const seite = (d?.page && d?.pageCount) ? ` · Seite ${d.page}/${d.pageCount}` : '';
|
||||
const txt = (d?.art === 'umschlag' ? '✉ Kuvert' : '📄 Schriftstück') + seite;
|
||||
return h('div', { style: 'display:flex;flex-direction:column;align-items:center;gap:4px;' }, [
|
||||
src ? h('img', { src, onClick: () => { neu.lb = src; neu.lbDoc = d; }, style: 'width:100%;max-height:260px;object-fit:contain;border-radius:6px;' + PAPIER + 'cursor:zoom-in;box-shadow:0 1px 3px rgba(0,0,0,.12);' })
|
||||
src ? h('img', { src, onClick: () => { neu.lb = src; neu.lbDoc = d; }, style: 'width:100%;max-height:260px;object-fit:contain;border-radius:6px;background:#f7f4ec;cursor:zoom-in;box-shadow:0 1px 3px rgba(0,0,0,.12);' })
|
||||
: h('div', { style: 'width:100%;height:200px;border-radius:6px;display:flex;align-items:center;justify-content:center;background:#fff;color:var(--foreground-subdued);' }, 'Dok.'),
|
||||
h('span', { style: 'font-size:11px;color:var(--foreground-subdued);' }, txt),
|
||||
]);
|
||||
@@ -712,7 +718,7 @@ const NeuAuftrag = defineComponent({
|
||||
style: 'position:fixed;inset:0;background:rgba(0,0,0,.8);z-index:1100;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:4vh 4vw;cursor:zoom-out;',
|
||||
}, [
|
||||
h('img', { src: neu.lb, onClick: (e) => e.stopPropagation(),
|
||||
style: 'max-width:100%;max-height:82vh;object-fit:contain;border-radius:8px;cursor:default;' + PAPIER }),
|
||||
style: 'max-width:100%;max-height:82vh;object-fit:contain;border-radius:8px;cursor:default;background:#f7f4ec;' }),
|
||||
// Aktion wie in der App: „Bild speichern" unter der großen Vorschau.
|
||||
neu.lbDoc ? h('button', { type: 'button', onClick: (e) => { e.stopPropagation(); bildSpeichern(neu.lbDoc); },
|
||||
style: 'cursor:pointer;border:none;background:#fff;color:#1a1a1a;border-radius:8px;padding:10px 20px;font:inherit;font-size:14px;font-weight:600;box-shadow:0 2px 8px rgba(0,0,0,.3);' }, '💾 Bild speichern') : null,
|
||||
|
||||
Reference in New Issue
Block a user