fix(pdf): sanitise non-WinAnsi chars, handle newlines in wrap
This commit is contained in:
+27
-13
@@ -48,6 +48,18 @@ export type IncidentReportData = {
|
||||
type EmbeddedFont = Awaited<ReturnType<PDFDocument['embedFont']>>
|
||||
type Page = ReturnType<PDFDocument['addPage']>
|
||||
|
||||
// WinAnsi (Windows-1252) cannot encode chars outside its range.
|
||||
// Replace common typographic Unicode with ASCII equivalents, strip the rest.
|
||||
function sanitize(str: string): string {
|
||||
return str
|
||||
.replace(/[‘’]/g, "'")
|
||||
.replace(/[“”]/g, '"')
|
||||
.replace(/–|—/g, '-')
|
||||
.replace(/…/g, '...')
|
||||
.replace(/ /g, ' ')
|
||||
.replace(/[^\x20-\xFF]/g, '?')
|
||||
}
|
||||
|
||||
const MARGIN = 50
|
||||
const PAGE_WIDTH = 595
|
||||
const PAGE_HEIGHT = 842
|
||||
@@ -81,20 +93,22 @@ function ensureSpace(state: State, needed: number): void {
|
||||
}
|
||||
|
||||
function wrapText(text: string, maxWidth: number, font: EmbeddedFont, size: number): string[] {
|
||||
const words = text.split(' ')
|
||||
const lines: string[] = []
|
||||
let current = ''
|
||||
for (const word of words) {
|
||||
const test = current ? `${current} ${word}` : word
|
||||
if (font.widthOfTextAtSize(test, size) > maxWidth && current) {
|
||||
lines.push(current)
|
||||
current = word
|
||||
} else {
|
||||
current = test
|
||||
const result: string[] = []
|
||||
for (const paragraph of text.split('\n')) {
|
||||
const words = paragraph.split(' ')
|
||||
let current = ''
|
||||
for (const word of words) {
|
||||
const test = current ? `${current} ${word}` : word
|
||||
if (font.widthOfTextAtSize(test, size) > maxWidth && current) {
|
||||
result.push(current)
|
||||
current = word
|
||||
} else {
|
||||
current = test
|
||||
}
|
||||
}
|
||||
if (current) result.push(current)
|
||||
}
|
||||
if (current) lines.push(current)
|
||||
return lines.length ? lines : ['']
|
||||
return result.length ? result : ['']
|
||||
}
|
||||
|
||||
function drawSection(state: State, title: string): void {
|
||||
@@ -117,7 +131,7 @@ function drawSection(state: State, title: string): void {
|
||||
}
|
||||
|
||||
function drawField(state: State, label: string, value: string | null): void {
|
||||
const v = value ?? '—'
|
||||
const v = sanitize(value ?? '—')
|
||||
const maxW = PAGE_WIDTH - MARGIN * 2 - 120
|
||||
const lines = wrapText(v, maxW, state.font, BODY_FONT_SIZE)
|
||||
ensureSpace(state, (lines.length + 1) * LINE_HEIGHT)
|
||||
|
||||
Reference in New Issue
Block a user