From 76e81b07de9a70ac42bc40eaae8a42bd60a0cecf Mon Sep 17 00:00:00 2001 From: Lucas Smith Date: Fri, 14 Aug 2026 14:58:34 +1000 Subject: [PATCH] fix: use documenso fork of skia-canvas for rendering Use our fork of `skia-canvas` for rendering which handles encoding characters correctly with the caveat font and other similar fonts that can group glyphs like ligatures. This resolves issues with pdf text extraction where characters were unable to be extracted due to lacking any data within the cmaps. --- .npmrc | 2 +- apps/remix/vite.config.ts | 2 +- package-lock.json | 29 ++++++++++--------- package.json | 1 + packages/lib/package.json | 2 +- packages/lib/server-only/ai/pdf-to-images.ts | 2 +- .../lib/server-only/konva/skia-backend.ts | 5 ++-- packages/lib/server-only/pdf/helpers.ts | 2 +- .../server-only/pdf/insert-field-in-pdf-v2.ts | 2 +- .../lib/server-only/pdf/render-audit-logs.ts | 12 ++++---- .../lib/server-only/pdf/render-certificate.ts | 12 ++++---- .../field-renderer/render-signature-field.ts | 2 +- 12 files changed, 40 insertions(+), 33 deletions(-) diff --git a/.npmrc b/.npmrc index 75baad7f0..cbc6b6537 100644 --- a/.npmrc +++ b/.npmrc @@ -1,3 +1,3 @@ legacy-peer-deps = true prefer-dedupe = true -# min-release-age = 7 +min-release-age = 7 diff --git a/apps/remix/vite.config.ts b/apps/remix/vite.config.ts index 50d21e07d..b6cc0cb1b 100644 --- a/apps/remix/vite.config.ts +++ b/apps/remix/vite.config.ts @@ -121,7 +121,7 @@ export default defineConfig({ 'nodemailer', /playwright/, '@playwright/browser-chromium', - 'skia-canvas', + '@documenso/skia-canvas', ], }, }, diff --git a/package-lock.json b/package-lock.json index a10151db2..4c13b8e33 100644 --- a/package-lock.json +++ b/package-lock.json @@ -35,6 +35,7 @@ "@commitlint/cli": "^20.1.0", "@commitlint/config-conventional": "^20.0.0", "@datadog/pprof": "^5.13.5", + "@documenso/skia-canvas": "^3.0.8-documenso.3", "@lingui/cli": "^5.6.0", "@prisma/client": "^6.19.0", "@trpc/client": "11.8.1", @@ -3515,6 +3516,19 @@ "resolved": "packages/signing", "link": true }, + "node_modules/@documenso/skia-canvas": { + "version": "3.0.8-documenso.3", + "resolved": "https://registry.npmjs.org/@documenso/skia-canvas/-/skia-canvas-3.0.8-documenso.3.tgz", + "integrity": "sha512-IeEr7RxJigCzidsRIQu6tcMX9+bt0tG3Y5kMArCPF4/jEgunPVx3qd7Wyh34mtSNufpzmVwazxR+U2BIz3iYvQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.1.1", + "follow-redirects": "^1.15.11", + "https-proxy-agent": "^7.0.6", + "string-split-by": "^1.0.0" + } + }, "node_modules/@documenso/tailwind-config": { "resolved": "packages/tailwind-config", "link": true @@ -28728,19 +28742,6 @@ "integrity": "sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==", "license": "MIT" }, - "node_modules/skia-canvas": { - "version": "3.0.8", - "resolved": "https://registry.npmjs.org/skia-canvas/-/skia-canvas-3.0.8.tgz", - "integrity": "sha512-FSYKxp8Ng2vOeeOBiyPhnn6ui6FirPJXMyjk4PKl8N/OWzVrkMawUgY9zubIWHMdYtyWFn0gfX3QlRwg6HBmdg==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "detect-libc": "^2.1.1", - "follow-redirects": "^1.15.11", - "https-proxy-agent": "^7.0.6", - "string-split-by": "^1.0.0" - } - }, "node_modules/slice-ansi": { "version": "7.1.2", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", @@ -33231,6 +33232,7 @@ "@documenso/email": "*", "@documenso/prisma": "*", "@documenso/signing": "*", + "@documenso/skia-canvas": "^3.0.8-documenso.3", "@lingui/core": "^5.6.0", "@lingui/macro": "^5.6.0", "@lingui/react": "^5.6.0", @@ -33269,7 +33271,6 @@ "react": "^18", "remeda": "^2.32.0", "sharp": "0.34.5", - "skia-canvas": "^3.0.8", "stripe": "^12.18.0", "ts-pattern": "^5.9.0", "zod": "^3.25.76" diff --git a/package.json b/package.json index 60e97f1c8..d4382924d 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "@commitlint/cli": "^20.1.0", "@commitlint/config-conventional": "^20.0.0", "@datadog/pprof": "^5.13.5", + "@documenso/skia-canvas": "^3.0.8-documenso.3", "@lingui/cli": "^5.6.0", "@prisma/client": "^6.19.0", "@trpc/client": "11.8.1", diff --git a/packages/lib/package.json b/packages/lib/package.json index 41b1f1815..27092f178 100644 --- a/packages/lib/package.json +++ b/packages/lib/package.json @@ -29,6 +29,7 @@ "@documenso/email": "*", "@documenso/prisma": "*", "@documenso/signing": "*", + "@documenso/skia-canvas": "^3.0.8-documenso.3", "@lingui/core": "^5.6.0", "@lingui/macro": "^5.6.0", "@lingui/react": "^5.6.0", @@ -67,7 +68,6 @@ "react": "^18", "remeda": "^2.32.0", "sharp": "0.34.5", - "skia-canvas": "^3.0.8", "stripe": "^12.18.0", "ts-pattern": "^5.9.0", "zod": "^3.25.76" diff --git a/packages/lib/server-only/ai/pdf-to-images.ts b/packages/lib/server-only/ai/pdf-to-images.ts index e14128278..fb2ee13e8 100644 --- a/packages/lib/server-only/ai/pdf-to-images.ts +++ b/packages/lib/server-only/ai/pdf-to-images.ts @@ -1,6 +1,6 @@ +import { Canvas, Image, Path2D } from '@documenso/skia-canvas'; import pMap from 'p-map'; import * as pdfjsLib from 'pdfjs-dist/legacy/build/pdf.mjs'; -import { Canvas, Image, Path2D } from 'skia-canvas'; // @ts-expect-error napi-rs/canvas satisfies the requirements globalThis.Path2D = Path2D; diff --git a/packages/lib/server-only/konva/skia-backend.ts b/packages/lib/server-only/konva/skia-backend.ts index bdcd4ec64..1e41c51ab 100644 --- a/packages/lib/server-only/konva/skia-backend.ts +++ b/packages/lib/server-only/konva/skia-backend.ts @@ -2,8 +2,9 @@ * !: This is a workaround to fix the memory leak in the skia-canvas library. * !: Internals are ported from the original `konva/skia-backend.js` file. */ + +import { Canvas, DOMMatrix, Image, Path2D } from '@documenso/skia-canvas'; import { Konva } from 'konva/lib/_CoreInternals'; -import { Canvas, DOMMatrix, Image, Path2D } from 'skia-canvas'; // @ts-expect-error skia-canvas satisfies the requirements global.DOMMatrix = DOMMatrix; @@ -37,6 +38,6 @@ Konva.Util.createImageElement = () => { return node as unknown as HTMLImageElement; }; -Konva._renderBackend = 'skia-canvas'; +Konva._renderBackend = '@documenso/skia-canvas'; export default Konva; diff --git a/packages/lib/server-only/pdf/helpers.ts b/packages/lib/server-only/pdf/helpers.ts index 2d6a1bc7e..afe53e170 100644 --- a/packages/lib/server-only/pdf/helpers.ts +++ b/packages/lib/server-only/pdf/helpers.ts @@ -1,8 +1,8 @@ import path from 'node:path'; import { AppError, AppErrorCode } from '@documenso/lib/errors/app-error'; +import { FontLibrary } from '@documenso/skia-canvas'; import type { Recipient } from '@prisma/client'; import { FieldType } from '@prisma/client'; -import { FontLibrary } from 'skia-canvas'; import { match } from 'ts-pattern'; /** diff --git a/packages/lib/server-only/pdf/insert-field-in-pdf-v2.ts b/packages/lib/server-only/pdf/insert-field-in-pdf-v2.ts index ad2adff2c..ee337c1e9 100644 --- a/packages/lib/server-only/pdf/insert-field-in-pdf-v2.ts +++ b/packages/lib/server-only/pdf/insert-field-in-pdf-v2.ts @@ -2,8 +2,8 @@ import '../konva/skia-backend'; import type { FieldWithSignature } from '@documenso/prisma/types/field-with-signature'; +import type { Canvas } from '@documenso/skia-canvas'; import Konva from 'konva'; -import type { Canvas } from 'skia-canvas'; import { renderField } from '../../universal/field-renderer/render-field'; import { ensureFontLibrary } from './helpers'; diff --git a/packages/lib/server-only/pdf/render-audit-logs.ts b/packages/lib/server-only/pdf/render-audit-logs.ts index faf982f55..427ed8be1 100644 --- a/packages/lib/server-only/pdf/render-audit-logs.ts +++ b/packages/lib/server-only/pdf/render-audit-logs.ts @@ -1,14 +1,16 @@ +// sort-imports-ignore +import '../konva/skia-backend'; + +import fs from 'node:fs'; +import path from 'node:path'; +import type { Canvas } from '@documenso/skia-canvas'; +import { Image as SkiaImage } from '@documenso/skia-canvas'; import type { I18n } from '@lingui/core'; import { msg } from '@lingui/core/macro'; import type { DocumentMeta, Envelope, RecipientRole } from '@prisma/client'; import Konva from 'konva'; -import 'konva/skia-backend'; -import fs from 'node:fs'; -import path from 'node:path'; import type { DateTimeFormatOptions } from 'luxon'; import { DateTime } from 'luxon'; -import type { Canvas } from 'skia-canvas'; -import { Image as SkiaImage } from 'skia-canvas'; import { match, P } from 'ts-pattern'; import { UAParser } from 'ua-parser-js'; diff --git a/packages/lib/server-only/pdf/render-certificate.ts b/packages/lib/server-only/pdf/render-certificate.ts index a30d265ee..9fa2a73c9 100644 --- a/packages/lib/server-only/pdf/render-certificate.ts +++ b/packages/lib/server-only/pdf/render-certificate.ts @@ -1,14 +1,16 @@ +// sort-imports-ignore +import '../konva/skia-backend'; + +import fs from 'node:fs'; +import path from 'node:path'; +import type { Canvas } from '@documenso/skia-canvas'; +import { Image as SkiaImage } from '@documenso/skia-canvas'; import type { I18n } from '@lingui/core'; import { msg } from '@lingui/core/macro'; import type { Field, RecipientRole, Signature } from '@prisma/client'; import { SigningStatus } from '@prisma/client'; import Konva from 'konva'; -import 'konva/skia-backend'; -import fs from 'node:fs'; -import path from 'node:path'; import { DateTime } from 'luxon'; -import type { Canvas } from 'skia-canvas'; -import { Image as SkiaImage } from 'skia-canvas'; import { UAParser } from 'ua-parser-js'; import { renderSVG } from 'uqr'; diff --git a/packages/lib/universal/field-renderer/render-signature-field.ts b/packages/lib/universal/field-renderer/render-signature-field.ts index a43d46115..eb3d94c57 100644 --- a/packages/lib/universal/field-renderer/render-signature-field.ts +++ b/packages/lib/universal/field-renderer/render-signature-field.ts @@ -14,7 +14,7 @@ let SkiaImage: any; void (async () => { if (typeof window === 'undefined') { - const mod = await import('skia-canvas'); + const mod = await import('@documenso/skia-canvas'); SkiaImage = mod.Image; } })();