From 977588f51b949a653f3b31ff67bfb05854dffa37 Mon Sep 17 00:00:00 2001 From: Moritz Utcke Date: Fri, 23 Feb 2024 13:25:57 +0700 Subject: [PATCH] PDF Designer Variablen Plugin --- package.json | 2 + .../Dashboard/DashboardSidebar.svelte | 7 +- src/lib/pdf/plugins/variables/constants.ts | 26 ++ src/lib/pdf/plugins/variables/index.ts | 311 ++++++++++++++++++ src/lib/pdf/plugins/variables/types.ts | 29 ++ .../DashboardPDFDesignerModule.svelte | 12 +- .../Dashboard/DashboardPDFViewerModule.svelte | 34 +- 7 files changed, 403 insertions(+), 18 deletions(-) create mode 100644 src/lib/pdf/plugins/variables/constants.ts create mode 100644 src/lib/pdf/plugins/variables/index.ts create mode 100644 src/lib/pdf/plugins/variables/types.ts diff --git a/package.json b/package.json index 9b373fcf..3ecc2f07 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "esbuild": "^0.18.17", "express": "^4.18.2", "flag-icons": "^6.9.2", + "fontkit": "^2.0.2", "i18next": "^23.4.1", "i18next-fs-backend": "^2.1.5", "i18next-http-backend": "^2.2.1", @@ -68,6 +69,7 @@ "@faker-js/faker": "^8.3.1", "@tailwindcss/typography": "^0.5.10", "@types/body-scroll-lock": "^3.1.2", + "@types/fontkit": "^2.0.6", "@types/js-cookie": "^3.0.6", "@types/uuid": "^9.0.1", "@typescript-eslint/eslint-plugin": "^5.59.6", diff --git a/src/components/Dashboard/DashboardSidebar.svelte b/src/components/Dashboard/DashboardSidebar.svelte index ca86aba0..c17b096f 100644 --- a/src/components/Dashboard/DashboardSidebar.svelte +++ b/src/components/Dashboard/DashboardSidebar.svelte @@ -78,7 +78,12 @@
  • - PDF Erstellen + PDF Designer + +
  • +
  • + + PDF Viewer
  • diff --git a/src/lib/pdf/plugins/variables/constants.ts b/src/lib/pdf/plugins/variables/constants.ts new file mode 100644 index 00000000..0278f15a --- /dev/null +++ b/src/lib/pdf/plugins/variables/constants.ts @@ -0,0 +1,26 @@ +import { ALIGNMENT, VERTICAL_ALIGNMENT, DYNAMIC_FONT_SIZE_FIT } from './types'; + +export const DEFAULT_FONT_SIZE = 13; + +export const ALIGN_LEFT = 'left' as ALIGNMENT; +export const ALIGN_CENTER = 'center' as ALIGNMENT; +export const ALIGN_RIGHT = 'right' as ALIGNMENT; +export const DEFAULT_ALIGNMENT = ALIGN_LEFT; +export const VERTICAL_ALIGN_TOP = 'top' as VERTICAL_ALIGNMENT; +export const VERTICAL_ALIGN_MIDDLE = 'middle' as VERTICAL_ALIGNMENT; +export const VERTICAL_ALIGN_BOTTOM = 'bottom' as VERTICAL_ALIGNMENT; +export const DEFAULT_VERTICAL_ALIGNMENT = VERTICAL_ALIGN_TOP; +export const DEFAULT_LINE_HEIGHT = 1; +export const DEFAULT_CHARACTER_SPACING = 0; +export const DEFAULT_FONT_COLOR = '#000000'; +export const PLACEHOLDER_FONT_COLOR = '#A0A0A0'; +export const DYNAMIC_FIT_VERTICAL = 'vertical' as DYNAMIC_FONT_SIZE_FIT; +export const DYNAMIC_FIT_HORIZONTAL = 'horizontal' as DYNAMIC_FONT_SIZE_FIT; +export const DEFAULT_DYNAMIC_FIT = DYNAMIC_FIT_VERTICAL; +export const DEFAULT_DYNAMIC_MIN_FONT_SIZE = 4; + +export const DEFAULT_DYNAMIC_MAX_FONT_SIZE = 72; +export const FONT_SIZE_ADJUSTMENT = 0.25; + +export const DEFAULT_OPACITY = 1; +export const HEX_COLOR_PATTERN = '^#(?:[A-Fa-f0-9]{3,4}|[A-Fa-f0-9]{6}|[A-Fa-f0-9]{8})$'; \ No newline at end of file diff --git a/src/lib/pdf/plugins/variables/index.ts b/src/lib/pdf/plugins/variables/index.ts new file mode 100644 index 00000000..bb34286c --- /dev/null +++ b/src/lib/pdf/plugins/variables/index.ts @@ -0,0 +1,311 @@ +import { + ZOOM, + Plugin, + Schema, + PropPanel, + DEFAULT_FONT_NAME, + getFallbackFontName, + PropPanelSchema, + PropPanelWidgetProps, +} from "@pdfme/common"; +import { image, text } from "@pdfme/schemas"; +import type { TextSchema } from "@pdfme/schemas/dist/types/src/text/types"; + +import { + DEFAULT_FONT_SIZE, + DEFAULT_ALIGNMENT, + DEFAULT_VERTICAL_ALIGNMENT, + DEFAULT_CHARACTER_SPACING, + DEFAULT_LINE_HEIGHT, + VERTICAL_ALIGN_TOP, + VERTICAL_ALIGN_MIDDLE, + VERTICAL_ALIGN_BOTTOM, + DEFAULT_FONT_COLOR, + DYNAMIC_FIT_VERTICAL, + DYNAMIC_FIT_HORIZONTAL, + DEFAULT_DYNAMIC_FIT, + DEFAULT_DYNAMIC_MIN_FONT_SIZE, + DEFAULT_DYNAMIC_MAX_FONT_SIZE, + ALIGN_RIGHT, + ALIGN_CENTER, + DEFAULT_OPACITY, + HEX_COLOR_PATTERN, +} from "./constants"; +import { + GebaeudeStammdaten, + Rechnungen, + VerbrauchsausweisWohnen, +} from "@ibcornelsen/database/client"; + +const UseDynamicFontSize = (props: PropPanelWidgetProps) => { + const { rootElement, changeSchemas, activeSchema, i18n } = props; + + const checkbox = document.createElement("input"); + checkbox.type = "checkbox"; + checkbox.checked = Boolean((activeSchema as any)?.dynamicFontSize); + checkbox.onchange = (e: any) => { + const val = e.target.checked + ? { + min: DEFAULT_DYNAMIC_MIN_FONT_SIZE, + max: DEFAULT_DYNAMIC_MAX_FONT_SIZE, + fit: DEFAULT_DYNAMIC_FIT, + } + : undefined; + changeSchemas([ + { key: "dynamicFontSize", value: val, schemaId: activeSchema.id }, + ]); + }; + const label = document.createElement("label"); + label.innerText = i18n("schemas.text.dynamicFontSize") || ""; + label.style.cssText = "display: flex; width: 100%;"; + label.appendChild(checkbox); + rootElement.appendChild(label); +}; + +type AusweisIndex = keyof VerbrauchsausweisWohnen +| `gebaeude_stammdaten.${keyof GebaeudeStammdaten}` +| `rechnung.${keyof Rechnungen}`; + +const sampleData: Partial> = { + "gebaeude_stammdaten.adresse": "Musterstraße 123", + "gebaeude_stammdaten.plz": "12345", + "gebaeude_stammdaten.ort": "Musterstadt", + "gebaeude_stammdaten.baujahr_gebaeude": "1990", + "gebaeude_stammdaten.baujahr_heizung": "2000", +} + +const variableOptions: { + label: string; + value: AusweisIndex; +}[] = [ + { + label: "Gebäude -> Adresse", + value: "gebaeude_stammdaten.adresse", + }, + { + label: "Gebäude -> PLZ", + value: "gebaeude_stammdaten.plz", + }, + { + label: "Gebaeude -> Ort", + value: "gebaeude_stammdaten.ort", + }, + { + label: "Gebäude -> Baujahr Gebäude", + value: "gebaeude_stammdaten.baujahr_gebaeude", + }, + { + label: "Gebäude -> Baujahr Heizung", + value: "gebaeude_stammdaten.baujahr_heizung", + }, + { + label: "Gebäude -> Fläche", + value: "gebaeude_stammdaten.flaeche" + }, + { + label: "Gebäude -> Einheiten", + value: "gebaeude_stammdaten.einheiten" + } +]; + +interface VariableSchema extends TextSchema { + variable: AusweisIndex | undefined +} + +const propPanel: PropPanel = { + schema: ({ options, activeSchema, i18n }) => { + const font = options.font || { + [DEFAULT_FONT_NAME]: { data: "", fallback: true }, + }; + const fontNames = Object.keys(font); + const fallbackFontName = getFallbackFontName(font); + + const enableDynamicFont = Boolean( + (activeSchema as any)?.dynamicFontSize + ); + + const textSchema: Record = { + variable: { + title: "Variable", + type: "string", + widget: "select", + props: { options: variableOptions }, + span: 24, + }, + fontName: { + title: i18n("schemas.text.fontName"), + type: "string", + widget: "select", + default: fallbackFontName, + props: { + options: fontNames.map((name) => ({ + label: name, + value: name, + })), + }, + span: 12, + }, + fontSize: { + title: i18n("schemas.text.size"), + type: "number", + widget: "inputNumber", + span: 6, + disabled: enableDynamicFont, + }, + characterSpacing: { + title: i18n("schemas.text.spacing"), + type: "number", + widget: "inputNumber", + span: 6, + }, + alignment: { + title: i18n("schemas.text.textAlign"), + type: "string", + widget: "select", + props: { + options: [ + { + label: i18n("schemas.left"), + value: DEFAULT_ALIGNMENT, + }, + { label: i18n("schemas.center"), value: ALIGN_CENTER }, + { label: i18n("schemas.right"), value: ALIGN_RIGHT }, + ], + }, + span: 8, + }, + verticalAlignment: { + title: i18n("schemas.text.verticalAlign"), + type: "string", + widget: "select", + props: { + options: [ + { + label: i18n("schemas.top"), + value: VERTICAL_ALIGN_TOP, + }, + { + label: i18n("schemas.middle"), + value: VERTICAL_ALIGN_MIDDLE, + }, + { + label: i18n("schemas.bottom"), + value: VERTICAL_ALIGN_BOTTOM, + }, + ], + }, + span: 8, + }, + lineHeight: { + title: i18n("schemas.text.lineHeight"), + type: "number", + widget: "inputNumber", + props: { + step: 0.1, + }, + span: 8, + }, + useDynamicFontSize: { + type: "boolean", + widget: "UseDynamicFontSize", + bind: false, + span: 16, + }, + dynamicFontSize: { + type: "object", + widget: "card", + column: 3, + properties: { + min: { + title: i18n("schemas.text.min"), + type: "number", + widget: "inputNumber", + hidden: !enableDynamicFont, + }, + max: { + title: i18n("schemas.text.max"), + type: "number", + widget: "inputNumber", + hidden: !enableDynamicFont, + }, + fit: { + title: i18n("schemas.text.fit"), + type: "string", + widget: "select", + hidden: !enableDynamicFont, + props: { + options: [ + { + label: i18n("schemas.horizontal"), + value: DYNAMIC_FIT_HORIZONTAL, + }, + { + label: i18n("schemas.vertical"), + value: DYNAMIC_FIT_VERTICAL, + }, + ], + }, + }, + }, + }, + fontColor: { + title: i18n("schemas.textColor"), + type: "string", + widget: "color", + rules: [ + { + pattern: HEX_COLOR_PATTERN, + message: i18n("hexColorPrompt"), + }, + ], + }, + backgroundColor: { + title: i18n("schemas.bgColor"), + type: "string", + widget: "color", + rules: [ + { + pattern: HEX_COLOR_PATTERN, + message: i18n("hexColorPrompt"), + }, + ], + }, + }; + + return textSchema; + }, + widgets: { UseDynamicFontSize }, + defaultValue: "Type Something...", + defaultSchema: { + type: "variable", + position: { x: 0, y: 0 }, + width: 45, + height: 10, + rotate: 0, + alignment: DEFAULT_ALIGNMENT, + verticalAlignment: DEFAULT_VERTICAL_ALIGNMENT, + fontSize: DEFAULT_FONT_SIZE, + lineHeight: DEFAULT_LINE_HEIGHT, + characterSpacing: DEFAULT_CHARACTER_SPACING, + dynamicFontSize: undefined, + fontColor: DEFAULT_FONT_COLOR, + fontName: undefined, + backgroundColor: "", + opacity: DEFAULT_OPACITY, + variable: undefined + }, +}; + +export const variable: Plugin = { + ui: (props) => { + if (props.schema.variable) { + props.value = sampleData[props.schema.variable] as string; + } + return text.ui(props); + }, + pdf: (props) => { + props.value = props.schema.variable as string; + text.pdf(props); + }, + propPanel, +}; diff --git a/src/lib/pdf/plugins/variables/types.ts b/src/lib/pdf/plugins/variables/types.ts new file mode 100644 index 00000000..eefc9e5c --- /dev/null +++ b/src/lib/pdf/plugins/variables/types.ts @@ -0,0 +1,29 @@ +import type { Schema } from '@pdfme/common'; +import type { Font as FontKitFont } from 'fontkit'; + +export type ALIGNMENT = 'left' | 'center' | 'right'; +export type VERTICAL_ALIGNMENT = 'top' | 'middle' | 'bottom'; +export type DYNAMIC_FONT_SIZE_FIT = 'horizontal' | 'vertical'; + +export type FontWidthCalcValues = { + font: FontKitFont; + fontSize: number; + characterSpacing: number; + boxWidthInPt: number; +}; +export interface TextSchema extends Schema { + fontName?: string; + alignment: ALIGNMENT; + verticalAlignment: VERTICAL_ALIGNMENT; + fontSize: number; + lineHeight: number; + characterSpacing: number; + dynamicFontSize?: { + min: number; + max: number; + fit: DYNAMIC_FONT_SIZE_FIT; + }; + + fontColor: string; + backgroundColor: string; +} \ No newline at end of file diff --git a/src/modules/Dashboard/DashboardPDFDesignerModule.svelte b/src/modules/Dashboard/DashboardPDFDesignerModule.svelte index 51a72cf3..10314ba9 100644 --- a/src/modules/Dashboard/DashboardPDFDesignerModule.svelte +++ b/src/modules/Dashboard/DashboardPDFDesignerModule.svelte @@ -1,5 +1,7 @@ -
    +
    diff --git a/src/modules/Dashboard/DashboardPDFViewerModule.svelte b/src/modules/Dashboard/DashboardPDFViewerModule.svelte index ecc1af2e..942bcc39 100644 --- a/src/modules/Dashboard/DashboardPDFViewerModule.svelte +++ b/src/modules/Dashboard/DashboardPDFViewerModule.svelte @@ -1,7 +1,10 @@