feat: add a personal-letter template
Same window-envelope geometry as the Form B letter (45mm address field, fold marks), but no info block: a three-line sender block on top and a right-aligned place/date line instead. The gallery thumb swaps the info column for a date line on this card. Positions verified headlessly: return address 44.8mm, recipient 49.8mm, date flush with the right margin.
This commit is contained in:
1 parent
70888409da
commit
46c59ffb11
5 files changed
+85
-3
No files matched your search
@@ -42,10 +42,14 @@
|
||||
<div class="grid">
|
||||
{#each TEMPLATES as entry (entry.id)}
|
||||
<button class="tpl" onclick={() => pick(entry)}>
|
||||
<!-- A stylized letter page: address block, info column, body lines. -->
|
||||
<!-- A stylized letter page: address block, info column or date line, body lines. -->
|
||||
<span class="thumb" aria-hidden="true">
|
||||
<span class="addr"></span>
|
||||
<span class="info"></span>
|
||||
{#if entry.id === 'din5008b'}
|
||||
<span class="info"></span>
|
||||
{:else}
|
||||
<span class="date"></span>
|
||||
{/if}
|
||||
<span class="line l1"></span>
|
||||
<span class="line l2"></span>
|
||||
<span class="line l3"></span>
|
||||
@@ -155,6 +159,7 @@
|
||||
}
|
||||
.addr { top: 26px; left: 12px; width: 28px; height: 16px; }
|
||||
.info { top: 26px; left: 52px; width: 22px; height: 16px; }
|
||||
.date { top: 48px; right: 12px; width: 20px; height: 3px; }
|
||||
.line { left: 12px; height: 3px; }
|
||||
.l1 { top: 56px; width: 40px; background: #8a94a0; }
|
||||
.l2 { top: 66px; width: 60px; }
|
||||
|
||||
@@ -680,6 +680,10 @@ const de: Messages = {
|
||||
name: 'Brief nach DIN 5008',
|
||||
description: 'Geschäftsbrief (Form B): Anschriftfeld fürs Fensterkuvert, Infoblock, Falzmarken.',
|
||||
},
|
||||
privateLetter: {
|
||||
name: 'Privatbrief',
|
||||
description: 'Privater Brief auf derselben Geometrie: Anschriftfeld fürs Fensterkuvert, Ort/Datum-Zeile, Falzmarken.',
|
||||
},
|
||||
letter: {
|
||||
companyName: 'Firmenname',
|
||||
returnAddress: 'Absender · Straße · PLZ Ort',
|
||||
@@ -697,6 +701,7 @@ const de: Messages = {
|
||||
date: 'Datum',
|
||||
phoneNumber: 'Nummer',
|
||||
emailAddress: 'Adresse',
|
||||
place: 'Ort',
|
||||
subject: 'Betreff',
|
||||
salutation: 'Anrede',
|
||||
bodyText: 'Brieftext',
|
||||
|
||||
@@ -686,6 +686,10 @@ const en = {
|
||||
name: 'DIN 5008 letter',
|
||||
description: 'Business letter (Form B): address field for a window envelope, info block, fold marks.',
|
||||
},
|
||||
privateLetter: {
|
||||
name: 'Personal letter',
|
||||
description: 'Private letter on the same geometry: window envelope address field, place and date line, fold marks.',
|
||||
},
|
||||
letter: {
|
||||
companyName: 'Company name',
|
||||
returnAddress: 'Sender · Street · Postal code City',
|
||||
@@ -703,6 +707,7 @@ const en = {
|
||||
date: 'Date',
|
||||
phoneNumber: 'Number',
|
||||
emailAddress: 'Address',
|
||||
place: 'Place',
|
||||
subject: 'Subject',
|
||||
salutation: 'Salutation',
|
||||
bodyText: 'Letter text',
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
import { t } from '../i18n/i18n.svelte';
|
||||
import { toDateValue, DEFAULT_DATE_FORMAT } from '../utils/dateTime';
|
||||
import type { TemplateData, TemplateEntry } from './types';
|
||||
|
||||
// Personal letter on the DIN 5008 window-envelope geometry: same 45mm address field
|
||||
// and fold marks as Form B, but no info block — just a right-aligned place/date line.
|
||||
|
||||
// Sender block height in pt; the space that lands the return-address line at 45mm.
|
||||
const LINE_PT = 12 * 1.15;
|
||||
const MM_TO_PT = 72 / 25.4;
|
||||
// 45mm target − 20mm margin − the three sender-address lines.
|
||||
const RETURN_SPACE_BEFORE_PT = Math.round((45 - 20) * MM_TO_PT - 3 * LINE_PT);
|
||||
// The return-address zone is 5mm tall; its 8pt line fills ~3.25mm of it.
|
||||
const RETURN_SPACE_AFTER_PT = Math.round((5 * MM_TO_PT - 8 * 1.15) * 10) / 10;
|
||||
|
||||
type N = Record<string, unknown>;
|
||||
const T = (text: string, ...marks: N[]): N => ({ type: 'text', text, ...(marks.length ? { marks } : {}) });
|
||||
const PLH = (text: string, ...marks: N[]): N => ({ type: 'placeholderField', attrs: { text }, ...(marks.length ? { marks } : {}) });
|
||||
const P = (attrs: N | null, ...content: N[]): N => ({ type: 'paragraph', ...(attrs ? { attrs } : {}), ...(content.length ? { content } : {}) });
|
||||
const SMALL = { type: 'textStyle', attrs: { fontSize: '8pt' } };
|
||||
const BOLD = { type: 'bold' };
|
||||
|
||||
function buildLetter(): TemplateData {
|
||||
const L = t().templates.letter;
|
||||
const date: N = { type: 'dateTimeField', attrs: { kind: 'date', format: DEFAULT_DATE_FORMAT, fixed: true, value: toDateValue(new Date()) } };
|
||||
return {
|
||||
margins: { top: 2, bottom: 2, left: 2.5, right: 2 },
|
||||
foldMarks: true,
|
||||
content: {
|
||||
type: 'doc',
|
||||
content: [
|
||||
P(null, PLH(L.recipientName)),
|
||||
P(null, PLH(L.recipientStreet)),
|
||||
P(null, PLH(L.recipientCity)),
|
||||
// The one-line return address opens the address field at 45mm from the top.
|
||||
// fontSize also shrinks the paragraph mark, so the line box is 8pt tall.
|
||||
P({ spaceBefore: RETURN_SPACE_BEFORE_PT, spaceAfter: RETURN_SPACE_AFTER_PT, fontSize: '8pt' }, PLH(L.returnAddress, SMALL)),
|
||||
P(null, PLH(L.recipientName)),
|
||||
P(null, PLH(L.recipientStreet)),
|
||||
P(null, PLH(L.recipientCity)),
|
||||
P(null),
|
||||
P({ textAlign: 'right' }, PLH(L.place), T(', '), date),
|
||||
P(null),
|
||||
P(null),
|
||||
P(null, PLH(L.subject, BOLD)),
|
||||
P(null),
|
||||
P(null, PLH(L.salutation)),
|
||||
P(null),
|
||||
P(null, PLH(L.bodyText)),
|
||||
P(null),
|
||||
P(null, T(L.closing)),
|
||||
P(null),
|
||||
P(null),
|
||||
P(null),
|
||||
P(null, PLH(L.signature)),
|
||||
],
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const privateLetter: TemplateEntry = {
|
||||
id: 'privateLetter',
|
||||
name: () => t().templates.privateLetter.name,
|
||||
description: () => t().templates.privateLetter.description,
|
||||
build: buildLetter,
|
||||
};
|
||||
@@ -1,4 +1,5 @@
|
||||
import { din5008b } from './din5008b';
|
||||
import { privateLetter } from './privateLetter';
|
||||
import type { TemplateEntry } from './types';
|
||||
|
||||
export const TEMPLATES: TemplateEntry[] = [din5008b];
|
||||
export const TEMPLATES: TemplateEntry[] = [din5008b, privateLetter];
|
||||
Reference in new issue
Block a user