4
0

generate-font-widths.mjs 2.5 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162
  1. /**
  2. * Regenerates src/features/invoice-designer/Pdf/fontWidths.ts from the TTFs
  3. * the PDFs embed, so text can be wrapped identically on server and client
  4. * without loading font files at runtime. Run: node scripts/generate-font-widths.mjs
  5. */
  6. import { createRequire } from 'node:module'
  7. import { readFileSync, writeFileSync } from 'node:fs'
  8. import path from 'node:path'
  9. const require = createRequire(import.meta.url)
  10. const fontkit = require('fontkit')
  11. const FONTS = [
  12. ['Helvetica', 'Roboto-Regular.ttf', 'Roboto-Bold.ttf'],
  13. ['Times-Roman', 'NotoSerif-Regular.ttf', 'NotoSerif-Bold.ttf'],
  14. ['Courier', 'NotoSansMono-Regular.ttf', 'NotoSansMono-Bold.ttf'],
  15. ['Open Sans', 'OpenSans-Regular.ttf', 'OpenSans-Bold.ttf'],
  16. ['Lato', 'Lato-Regular.ttf', 'Lato-Bold.ttf'],
  17. ['Montserrat', 'Montserrat-Regular.ttf', 'Montserrat-Bold.ttf'],
  18. ['PT Sans', 'PTSans-Regular.ttf', 'PTSans-Bold.ttf'],
  19. ]
  20. // ASCII plus the Latin-1/Latin Extended and Cyrillic ranges the app's locales use.
  21. const CHARS = []
  22. for (let c = 0x20; c <= 0x7e; c++) CHARS.push(c)
  23. for (let c = 0xa0; c <= 0x17f; c++) CHARS.push(c)
  24. for (const c of [0x2013, 0x2014, 0x2018, 0x2019, 0x201c, 0x201d, 0x2022, 0x20ac, 0xb7]) CHARS.push(c)
  25. for (let c = 0x400; c <= 0x45f; c++) CHARS.push(c)
  26. const dir = path.resolve('src/assets/fonts')
  27. const table = {}
  28. for (const [family, regular, bold] of FONTS) {
  29. for (const [weight, file] of [['regular', regular], ['bold', bold]]) {
  30. const font = fontkit.create(readFileSync(path.join(dir, file)))
  31. const upm = font.unitsPerEm
  32. const widths = {}
  33. for (const code of CHARS) {
  34. if (!font.hasGlyphForCodePoint(code)) continue
  35. const glyph = font.glyphForCodePoint(code)
  36. widths[code] = Math.round((glyph.advanceWidth / upm) * 1000)
  37. }
  38. // A representative width for anything outside the table.
  39. const fallback = widths[0x6e] ?? 500 // 'n'
  40. table[`${family}|${weight}`] = { fallback, widths }
  41. }
  42. }
  43. const out = `/**
  44. * Character advance widths, in thousandths of the font size, for the exact
  45. * font files the PDFs embed. Generated by scripts/generate-font-widths.mjs —
  46. * do not edit by hand. This is what lets the server wrap text the same way
  47. * the printed page will, without opening a font file at runtime.
  48. */
  49. export interface WidthTable {
  50. fallback: number
  51. widths: Record<number, number>
  52. }
  53. export const FONT_WIDTHS: Record<string, WidthTable> = ${JSON.stringify(table)}
  54. `
  55. writeFileSync('src/features/invoice-designer/Pdf/fontWidths.ts', out)
  56. console.log('written', Object.keys(table).join(', '))