diff --git a/bun.lock b/bun.lock
index ab0a685139..7227a29c2c 100644
--- a/bun.lock
+++ b/bun.lock
@@ -184,7 +184,7 @@
"mitt": "^3.0.1",
"motion": "^12.34.3",
"nanoid": "^5.1.6",
- "pdfkit": "^0.19.1",
+ "pdfkit": "^0.20.2",
"tailwind-merge": "^3.5.0",
"three-bvh-csg": "^0.0.18",
"three-mesh-bvh": "~0.9.8",
@@ -1170,8 +1170,6 @@
"brotli": ["brotli@1.3.3", "", { "dependencies": { "base64-js": "^1.1.2" } }, "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg=="],
- "browserify-zlib": ["browserify-zlib@0.2.0", "", { "dependencies": { "pako": "~1.0.5" } }, "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA=="],
-
"browserslist": ["browserslist@4.28.2", "", { "dependencies": { "baseline-browser-mapping": "^2.10.12", "caniuse-lite": "^1.0.30001782", "electron-to-chromium": "^1.5.328", "node-releases": "^2.0.36", "update-browserslist-db": "^1.2.3" }, "bin": { "browserslist": "cli.js" } }, "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg=="],
"buffer": ["buffer@6.0.3", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.2.1" } }, "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA=="],
@@ -1600,8 +1598,6 @@
"jose": ["jose@6.2.3", "", {}, "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw=="],
- "js-md5": ["js-md5@0.8.3", "", {}, "sha512-qR0HB5uP6wCuRMrWPTrkMaev7MJZwJuuw4fnwAzRgP4J4/F8RwtodOKpGp4XpqsLBFzzgqIO42efFAyz2Et6KQ=="],
-
"js-tokens": ["js-tokens@4.0.0", "", {}, "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="],
"js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="],
@@ -1810,7 +1806,7 @@
"pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="],
- "pdfkit": ["pdfkit@0.19.1", "", { "dependencies": { "@noble/ciphers": "^1.0.0", "@noble/hashes": "^1.6.0", "fontkit": "^2.0.4", "js-md5": "^0.8.3", "linebreak": "^1.1.0", "png-js": "^1.1.0" } }, "sha512-6Gzk+wDwTs4VSxsR5rCMTnIl5nlmkye1oWB0l2hDB1EX6ZNSIBroKQEv+2+fPPn+stVjyqzmsqRJVDfB9fo5DA=="],
+ "pdfkit": ["pdfkit@0.20.2", "", { "dependencies": { "@noble/ciphers": "^1.3.0", "@noble/hashes": "^1.8.0", "fflate": "^0.8.3", "fontkit": "^2.0.4", "linebreak": "^1.1.0", "png-js": "^2.0.0" } }, "sha512-Q/w03ICAQyXfHNfTsg1udp0ADerdBN0s7a6XSPL7J7Ro6ABnafoBOCDBstIO9U02mvzHEV8HrvFIw5iV5ejIRA=="],
"picocolors": ["picocolors@1.1.1", "", {}, "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA=="],
@@ -1818,7 +1814,7 @@
"pkce-challenge": ["pkce-challenge@5.0.1", "", {}, "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ=="],
- "png-js": ["png-js@1.1.0", "", { "dependencies": { "browserify-zlib": "^0.2.0" } }, "sha512-PM/uYGzGdNSzqeOgly68+6wKQDL1SY0a/N+OEa/+br6LnHWOAJB0Npiamnodfq3jd2LS/i2fMeOKSAILjA+m5Q=="],
+ "png-js": ["png-js@2.0.0", "", { "dependencies": { "fflate": "^0.8.2" } }, "sha512-GdzJuUMc6ZSpxFJWVxtOH1bzYHym+TOnveqUjb+VJIbZWbZzyiRGFiKhbiielfpYbgMlhHVhsJ0FTazfuRFkMA=="],
"polygon-clipping": ["polygon-clipping@0.15.7", "", { "dependencies": { "robust-predicates": "^3.0.2", "splaytree": "^3.1.0" } }, "sha512-nhfdr83ECBg6xtqOAJab1tbksbBAOMUltN60bU+llHVOL0e5Onm1WpAXXWXVB39L8AJFssoIhEVuy/S90MmotA=="],
@@ -2204,8 +2200,6 @@
"agent-install/commander": ["commander@14.0.3", "", {}, "sha512-H+y0Jo/T1RZ9qPP4Eh1pkcQcLRglraJaSLoyOtHxu6AapkjWVCy2Sit1QQ4x3Dng8qDlSsZEet7g5Pq06MvTgw=="],
- "browserify-zlib/pako": ["pako@1.0.11", "", {}, "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw=="],
-
"conf/semver": ["semver@7.8.3", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-wnilbGyMxzbY7dNOl7jpKbLSjcfeweJWU5j4+u5qW+6/wuGD9KzIGOyZnQVSBM9E7DtWaaH3CyHkppYrKYoxwg=="],
"deslop-js/fast-glob": ["fast-glob@3.3.3", "", { "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", "glob-parent": "^5.1.2", "merge2": "^1.3.0", "micromatch": "^4.0.8" } }, "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg=="],
diff --git a/packages/editor/package.json b/packages/editor/package.json
index 1cd4fa24f1..b13cdbb386 100644
--- a/packages/editor/package.json
+++ b/packages/editor/package.json
@@ -57,7 +57,7 @@
"mitt": "^3.0.1",
"motion": "^12.34.3",
"nanoid": "^5.1.6",
- "pdfkit": "^0.19.1",
+ "pdfkit": "^0.20.2",
"tailwind-merge": "^3.5.0",
"three-bvh-csg": "^0.0.18",
"three-mesh-bvh": "~0.9.8",
diff --git a/packages/editor/src/lib/floorplan/floorplan-pdfkit-document.ts b/packages/editor/src/lib/floorplan/floorplan-pdfkit-document.ts
index 6788bc63eb..f1653b0d29 100644
--- a/packages/editor/src/lib/floorplan/floorplan-pdfkit-document.ts
+++ b/packages/editor/src/lib/floorplan/floorplan-pdfkit-document.ts
@@ -1,7 +1,11 @@
+///
import type PdfKitDocument from 'pdfkit'
type PdfKitDocumentInstance = InstanceType
+/** pdfkit 0.20's browser-build export, which @types/pdfkit does not describe. */
+type PdfKitStandardFontRegistry = { registerStdFonts?: (...fonts: unknown[]) => void }
+
type PdfTextOptions = {
align?: 'left' | 'center' | 'right'
maxWidth?: number
@@ -461,16 +465,29 @@ export async function createFloorplanPdfDocument(
defaultPageSize: readonly [number, number],
options: { title?: string } = {},
) {
- const [{ default: PDFDocument }, { default: blobStream }, fonts] = await Promise.all([
- import('pdfkit/js/pdfkit.standalone'),
- import('blob-stream'),
- // an export in the standard fonts beats no export when the font files are unreachable
- loadFloorplanPdfFonts().catch((error: unknown) => {
- console.warn('[floorplan-export] Falling back to the standard PDF fonts', error)
- return null
- }),
- ])
- const raw = new PDFDocument({
+ const [pdfkit, helvetica, helveticaBold, courier, courierBold, { default: blobStream }, fonts] =
+ await Promise.all([
+ import('pdfkit'),
+ import('pdfkit/standard-fonts/Helvetica'),
+ import('pdfkit/standard-fonts/HelveticaBold'),
+ import('pdfkit/standard-fonts/Courier'),
+ import('pdfkit/standard-fonts/CourierBold'),
+ import('blob-stream'),
+ // an export in the standard fonts beats no export when the font files are unreachable
+ loadFloorplanPdfFonts().catch((error: unknown) => {
+ console.warn('[floorplan-export] Falling back to the standard PDF fonts', error)
+ return null
+ }),
+ ])
+ // The browser build has no standard-font metrics until they are registered, and
+ // the constructor already sets Helvetica; the Node build loads its own and has no registry.
+ ;(pdfkit as PdfKitStandardFontRegistry).registerStdFonts?.(
+ helvetica.default,
+ helveticaBold.default,
+ courier.default,
+ courierBold.default,
+ )
+ const raw = new pdfkit.default({
autoFirstPage: false,
compress: true,
margin: 0,
diff --git a/packages/editor/src/lib/floorplan/pdfkit-standard-fonts.d.ts b/packages/editor/src/lib/floorplan/pdfkit-standard-fonts.d.ts
new file mode 100644
index 0000000000..0b8adff53b
--- /dev/null
+++ b/packages/editor/src/lib/floorplan/pdfkit-standard-fonts.d.ts
@@ -0,0 +1,5 @@
+// pdfkit 0.20 ships these entry points without types, and @types/pdfkit predates them.
+declare module 'pdfkit/standard-fonts/*' {
+ const font: unknown
+ export default font
+}