fix(pdf): sanitise non-WinAnsi chars, handle newlines in wrap
This commit is contained in:
@@ -48,6 +48,18 @@ export type IncidentReportData = {
|
|||||||
type EmbeddedFont = Awaited<ReturnType<PDFDocument['embedFont']>>
|
type EmbeddedFont = Awaited<ReturnType<PDFDocument['embedFont']>>
|
||||||
type Page = ReturnType<PDFDocument['addPage']>
|
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 MARGIN = 50
|
||||||
const PAGE_WIDTH = 595
|
const PAGE_WIDTH = 595
|
||||||
const PAGE_HEIGHT = 842
|
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[] {
|
function wrapText(text: string, maxWidth: number, font: EmbeddedFont, size: number): string[] {
|
||||||
const words = text.split(' ')
|
const result: string[] = []
|
||||||
const lines: string[] = []
|
for (const paragraph of text.split('\n')) {
|
||||||
|
const words = paragraph.split(' ')
|
||||||
let current = ''
|
let current = ''
|
||||||
for (const word of words) {
|
for (const word of words) {
|
||||||
const test = current ? `${current} ${word}` : word
|
const test = current ? `${current} ${word}` : word
|
||||||
if (font.widthOfTextAtSize(test, size) > maxWidth && current) {
|
if (font.widthOfTextAtSize(test, size) > maxWidth && current) {
|
||||||
lines.push(current)
|
result.push(current)
|
||||||
current = word
|
current = word
|
||||||
} else {
|
} else {
|
||||||
current = test
|
current = test
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (current) lines.push(current)
|
if (current) result.push(current)
|
||||||
return lines.length ? lines : ['']
|
}
|
||||||
|
return result.length ? result : ['']
|
||||||
}
|
}
|
||||||
|
|
||||||
function drawSection(state: State, title: string): void {
|
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 {
|
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 maxW = PAGE_WIDTH - MARGIN * 2 - 120
|
||||||
const lines = wrapText(v, maxW, state.font, BODY_FONT_SIZE)
|
const lines = wrapText(v, maxW, state.font, BODY_FONT_SIZE)
|
||||||
ensureSpace(state, (lines.length + 1) * LINE_HEIGHT)
|
ensureSpace(state, (lines.length + 1) * LINE_HEIGHT)
|
||||||
|
|||||||
Reference in New Issue
Block a user