@@ -13,6 +13,23 @@ import { useEffect, useMemo, useState } from 'react';
export type PreviewDoc = { art : 'brief' | 'umschlag' ; svg : string } ;
export type PreviewDoc = { art : 'brief' | 'umschlag' ; svg : string } ;
// Vorschau-Optik „Kugelschreiber" – IDENTISCH zu Directus (neuauftrag): blaue
// Tinte, etwas dicker (1.4) + dezenter Wobble (feTurbulence/Displacement) für die
// typische Kuli-Linie. non-scaling-stroke bleibt (Glyphen skalieren per transform),
// weißer Grund. Nur Anzeige/Download – die an die Produktion gehenden SVGs bleiben
// schwarz. Inline-SVGs brauchen eine EINDEUTIGE Filter-ID pro Dokument.
let inkSeq = 0 ;
const inkify = ( svg : string ) : string = > {
if ( typeof svg !== 'string' ) return svg ;
const id = ` skink ${ inkSeq ++ } ` ;
return svg
. replace ( /stroke="#0{3,6}"/gi , 'stroke="#1a2a6c" stroke-opacity="0.95"' )
. replace ( /stroke-width="0\.6"/gi , 'stroke-width="1.4"' )
. replace ( /(<svg[^>]*>)/i , ` $ 1<defs><filter id=" ${ id } " x="-4%" y="-4%" width="108%" height="108%" color-interpolation-filters="sRGB"><feTurbulence type="fractalNoise" baseFrequency="0.05" numOctaves="1" seed="4" result="n"/><feDisplacementMap in="SourceGraphic" in2="n" scale="1.3" xChannelSelector="R" yChannelSelector="G"/></filter></defs> ` )
. replace ( /(<rect[^>]*fill="#FFFFFF"[^>]*\/>)/i , ` $ 1<g filter="url(# ${ id } )"> ` )
. replace ( /<\/svg>\s*$/i , '</g></svg>' ) ;
} ;
/** Ein Empfänger-Dokument: Schriftstück + optional zugehöriger Umschlag. */
/** Ein Empfänger-Dokument: Schriftstück + optional zugehöriger Umschlag. */
type Gruppe = { brief? : PreviewDoc ; umschlag? : PreviewDoc } ;
type Gruppe = { brief? : PreviewDoc ; umschlag? : PreviewDoc } ;
@@ -81,10 +98,12 @@ export default function PreviewPanel({
const m = zoomSvg . match ( /viewBox="0 0 ([\d.]+) ([\d.]+)"/ ) ;
const m = zoomSvg . match ( /viewBox="0 0 ([\d.]+) ([\d.]+)"/ ) ;
const w = Math . round ( m ? Number ( m [ 1 ] ) : 794 ) ;
const w = Math . round ( m ? Number ( m [ 1 ] ) : 794 ) ;
const h = Math . round ( m ? Number ( m [ 2 ] ) : 1123 ) ;
const h = Math . round ( m ? Number ( m [ 2 ] ) : 1123 ) ;
const scale = 2 ; // für ein scharfes Musterbild
// Natürliche Größe (nicht 2× ): non-scaling-stroke wirkt konstant in
// Gespeicherte Vorschau in Kugelschreiber-Blau (wie die Anzeige); die
// Gerätepixeln – bei 2× würde die Linie relativ dünn. So sieht das PNG aus
// an die P roduktion gehenden SVGs bleiben davon unberührt (schwarz).
// wie die G roßansicht. Gespeicherte Vorschau in Kuli-Optik (wie Anzeige);
const inkSvg = zoomSvg . replace ( /stroke="#0{3,6}"/gi , 'stroke="#1a2a6c"' ) ;
// die an die Produktion gehenden SVGs bleiben schwarz.
const scale = 1 ;
const inkSvg = inkify ( zoomSvg ) ;
const url = URL . createObjectURL ( new Blob ( [ inkSvg ] , { type : 'image/svg+xml;charset=utf-8' } ) ) ;
const url = URL . createObjectURL ( new Blob ( [ inkSvg ] , { type : 'image/svg+xml;charset=utf-8' } ) ) ;
const img = new Image ( ) ;
const img = new Image ( ) ;
img . onload = ( ) = > {
img . onload = ( ) = > {
@@ -142,7 +161,7 @@ export default function PreviewPanel({
>
>
< figcaption className = "sk-preview-doc-label" > { label } < / figcaption >
< figcaption className = "sk-preview-doc-label" > { label } < / figcaption >
{ /* Das SVG stammt aus unserer eigenen Generierung, nicht aus Nutzereingaben. */ }
{ /* Das SVG stammt aus unserer eigenen Generierung, nicht aus Nutzereingaben. */ }
< div className = "sk-preview-doc-sheet" dangerouslySetInnerHTML = { { __html : svg } } / >
< div className = "sk-preview-doc-sheet" dangerouslySetInnerHTML = { { __html : inkify ( svg ) } } / >
< / figure >
< / figure >
) ;
) ;
@@ -199,7 +218,7 @@ export default function PreviewPanel({
aria-label = "Vergrößerte Vorschau" >
aria-label = "Vergrößerte Vorschau" >
< div className = "sk-lightbox-sheet" style = { { aspectRatio : seitenverhaeltnis ( zoomSvg ) } }
< div className = "sk-lightbox-sheet" style = { { aspectRatio : seitenverhaeltnis ( zoomSvg ) } }
onClick = { ( e ) = > e . stopPropagation ( ) }
onClick = { ( e ) = > e . stopPropagation ( ) }
dangerouslySetInnerHTML = { { __html : zoomSvg } } / >
dangerouslySetInnerHTML = { { __html : inkify ( zoomSvg) } } / >
< div className = "sk-lightbox-actions" >
< div className = "sk-lightbox-actions" >
< button className = "sk-lightbox-close" onClick = { ( e ) = > { e . stopPropagation ( ) ; bildSpeichern ( ) ; } } >
< button className = "sk-lightbox-close" onClick = { ( e ) = > { e . stopPropagation ( ) ; bildSpeichern ( ) ; } } >
Bild speichern
Bild speichern