@@ -476,6 +476,8 @@ function generatePostcardSVG(text, format, options = {}) {
const trennXmm = ( textLowerRightMm + addrLeftMm ) / 2 ; // A6: 84
const paths = [ ] ;
let bodyLineCount = 0 ;
let bodyLineLimit = 1 ;
let overflow = false ;
// Variabler (L-förmiger) Textfluss: oben schmal (bis Frankierzone), unten breit.
@@ -500,37 +502,59 @@ function generatePostcardSVG(text, format, options = {}) {
return out ;
}
// Text links (L-förmig) .
// Absender oben links – ZUERST, damit der Text knapp darunter beginnen kann .
// Größe folgt der „Absender-Größe" aus dem manuellen Druck (options.senderScale).
let senderBottomPx = mmToPx ( 2 ) ;
if ( sender ) {
const senderScale = Math . max ( 0.5 , Math . min ( 1.8 , Number ( options . senderScale ) || 1 ) ) ;
const senderSize = FONT _SIZE _PX * 0.55 * senderScale ;
const senderLH = ( baseLineHeightPx / FONT _SIZE _PX ) * senderSize ;
const senderLines = wrapTextToLinesByWidth ( { text : sender , maxWidthPx : mmToPx ( textUpperRightMm - textLeftMm ) , fontFileName : fontFile , fontSizePx : senderSize } ) . slice ( 0 , 2 ) ;
const senderStartY = mmToPx ( 2 ) + senderSize ;
paths . push ( ... layoutTextToPaths ( {
lines : senderLines , fontFileName : fontFile , fontSizePx : senderSize ,
startX : mmToPx ( textLeftMm ) , startY : senderStartY , lineHeightPx : senderLH , realisticHandwriting : realistic ,
} ) ) ;
senderBottomPx = senderStartY + Math . max ( 0 , senderLines . length - 1 ) * senderLH + senderSize * 0.3 ;
}
// Text links (L-förmig). Beginn nur ETWAS unter dem Absender (≈2 mm); ohne
// Absender wie im Maßblatt bei y≈14 mm. Nur DER Text kann „zu lang" sein.
const baseline0 = ( sender ? senderBottomPx + mmToPx ( 2 ) : mmToPx ( 14 ) ) + fontSizePx ;
bodyLineLimit = Math . max ( 1 , Math . floor ( ( mmToPx ( maxYmm ) - baseline0 ) / lineHeightPx ) + 1 ) ;
if ( body ) {
const baseline0 = mmToPx ( 14 ) + fontSizePx ;
const bodyLines = flowVariabel ( body , fontSizePx , baseline0 , lineHeightPx ) ;
const letzteBaseline = baseline0 + Math . max ( 0 , bodyLines . length - 1 ) * lineHeightPx ;
if ( letzteBaseline > mmToPx ( maxYmm ) ) overflow = true ;
bodyLineCount = bodyLines . length ;
if ( bodyLineCount > bodyLineLimit ) overflow = true ;
paths . push ( ... layoutTextToPaths ( {
lines : bodyLines , fontFileName : fontFile , fontSizePx ,
startX : mmToPx ( textLeftMm ) , startY : baseline0 , lineHeightPx , realisticHandwriting : realistic ,
} ) ) ;
}
// Absender oben links (2 Zeilen), klein genug für die 10-mm-Zone.
if ( sender ) {
const senderSize = FONT _SIZE _PX * 0.55 * Math . max ( 0.7 , Math . min ( 1.2 , Number ( options . senderScale ) || 1 ) ) ;
const senderLH = ( baseLineHeightPx / FONT _SIZE _PX ) * senderSize ;
const senderLines = wrapTextToLinesByWidth ( { text : sender , maxWidthPx : mmToPx ( textUpperRightMm - textLeftMm ) , fontFileName : fontFile , fontSizePx : senderSize } ) . slice ( 0 , 2 ) ;
paths . push ( ... layoutTextToPaths ( {
lines : senderLines , fontFileName : fontFile , fontSizePx : senderSize ,
startX : mmToPx ( textLeftMm ) , startY : mmToPx ( 2 ) + senderSize , lineHeightPx : senderLH , realisticHandwriting : realistic ,
} ) ) ;
}
// Empfänger im sicheren Adressfeld (rechts unten).
// Empfänger im sicheren Adressfeld (rechts unten). Größe folgt der „Schriftgröße
// Kuvert" (options.recipientScale). Passt der Block nicht in die 46-mm-Zone, wird
// er automatisch verkleinert – so bleibt die Codierzone IMMER frei.
if ( recipient ) {
const recLines = wrapTextToLinesByWidth ( { text : recipient , maxWidthPx : mmToPx ( addrRightMm - addrLeftMm ) , fontFileName : fontFile , fontSizePx } ) ;
const recBaseline0 = mmToPx ( addrTopMm ) + fontSizePx ;
if ( recBaseline0 + Math . max ( 0 , recLines . length - 1 ) * lineHeightPx > mmToPx ( maxYmm ) ) overflow = true ;
const feldOben = mmToPx ( addrTopMm ) ;
const feldUnten = mmToPx ( maxYmm ) ;
const feldBreite = mmToPx ( addrRightMm - addrLeftMm ) ;
const recScale = Math . max ( 0.6 , Math . min ( 1.6 , Number ( options . recipientScale ) || 1 ) ) ;
const recBaseSize = FONT _SIZE _PX * recScale ;
const recBaseLH = baseLineHeightPx * recScale ;
let recSize = recBaseSize ;
let recLH = recBaseLH ;
let recLines = [ ] ;
for ( const scale of [ 1 , 0.9 , 0.8 , 0.72 , 0.65 , 0.6 ] ) {
recSize = recBaseSize * scale ;
recLH = recBaseLH * scale ;
recLines = wrapTextToLinesByWidth ( { text : recipient , maxWidthPx : feldBreite , fontFileName : fontFile , fontSizePx : recSize } ) ;
const blockUnten = feldOben + recSize + Math . max ( 0 , recLines . length - 1 ) * recLH + recSize * 0.3 ;
if ( blockUnten <= feldUnten ) break ;
}
paths . push ( ... layoutTextToPaths ( {
lines : recLines , fontFileName : fontFile , fontSizePx ,
startX : mmToPx ( addrLeftMm ) , startY : recBaseline0 , lineHeightPx , realisticHandwriting : realistic ,
lines : recLines , fontFileName : fontFile , fontSizePx : recSize ,
startX : mmToPx ( addrLeftMm ) , startY : feldOben + recSize , lineHeightPx : recLH , realisticHandwriting : realistic ,
} ) ) ;
}
@@ -546,8 +570,9 @@ function generatePostcardSVG(text, format, options = {}) {
/> ` )
. join ( '\n ' ) ;
// Trennstrich (solide Linie, 1,2 mm breit) zwischen Text und Adressfeld.
const trennstrich = ` <path d="M ${ mmToPx ( trennXmm ) . toFixed ( 1 ) } ${ mmToPx ( addrTopMm ) . toFixed ( 1 ) } L ${ mmToPx ( trennXmm ) . toFixed ( 1 ) } ${ mmToPx ( maxYmm ) . toFixed ( 1 ) } " stroke="#000000" stroke-width=" ${ mmToPx ( 1.2 ) . toFixed ( 1 ) } " stroke-linecap="butt" fill="none"/> ` ;
// Trennstrich: einfache Kugelschreiber-Linie (Single-Stroke, keine Füllung) –
// gleiche Strichstärke wie die Handschrift, damit die Ballpoint-Optik greift.
const trennstrich = ` <path d="M ${ mmToPx ( trennXmm ) . toFixed ( 1 ) } ${ mmToPx ( addrTopMm ) . toFixed ( 1 ) } L ${ mmToPx ( trennXmm ) . toFixed ( 1 ) } ${ mmToPx ( maxYmm ) . toFixed ( 1 ) } " stroke="#000000" stroke-width="0.6" fill="none" stroke-linecap="round" vector-effect="non-scaling-stroke"/> ` ;
const svg = ` <svg xmlns="http://www.w3.org/2000/svg"
width=" ${ W } mm"
@@ -558,7 +583,7 @@ function generatePostcardSVG(text, format, options = {}) {
${ pathElements }
</svg> ` ;
return { svg : svg . trim ( ) , overflow , lineCount : 0 , lineLimit : 0 } ;
return { svg : svg . trim ( ) , overflow , lineCount : bodyLineCount , lineLimit : bodyLineLimit } ;
}
/**