@pptx-studio/cli 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +29 -0
- package/LICENSE +202 -0
- package/NOTICE +43 -0
- package/README.md +263 -0
- package/dist/cli.d.ts +1 -0
- package/dist/cli.js +16 -0
- package/dist/cli.js.map +1 -0
- package/dist/index.d.ts +232 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +2 -0
- package/dist/main-DLx2onii.js +1570 -0
- package/dist/main-DLx2onii.js.map +1 -0
- package/package.json +73 -0
- package/scripts/powerpoint-oracle.ps1 +162 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"main-DLx2onii.js","names":[],"sources":["../src/bisect.ts","../src/inspect.ts","../src/render/errors.ts","../src/render/sfnt.ts","../src/render/faces.ts","../src/render/measure.ts","../src/render/render.ts","../src/roundtrip.ts","../src/validate.ts","../src/main.ts"],"sourcesContent":["import { spawnSync } from 'node:child_process';\nimport { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';\nimport { tmpdir } from 'node:os';\nimport { join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { readZip } from '@pptx-studio/opc';\nimport { firstDifference } from '@pptx-studio/xml';\nimport { validatePackage } from '@pptx-studio/validate';\nimport {\n bisectPackages,\n describeChange,\n flattenChanges,\n roundTripPackage,\n summarizeBisect,\n type BisectResult,\n type Change,\n type Oracle,\n type Verdict,\n} from '@pptx-studio/writer';\n\n/**\n * `pptx-studio bisect deck.pptx`\n *\n * Find the smallest change that makes a package fail.\n *\n * ## What it is for\n *\n * It is the debugger for this project, and the plan says so in as many words.\n * `validate` answers \"does this break one of the twenty-nine rules\"; `roundtrip`\n * answers \"did we change anything\"; neither answers the question you actually\n * have at two in the morning, which is *which* of the four hundred things that\n * changed is the one PowerPoint objects to.\n *\n * ## Two ways to call it\n *\n * With one deck, the broken package is **our own export of it**. That is the\n * workflow this exists for: we read a file, wrote it back, and PowerPoint now\n * wants to repair it - so the delta to search is exactly what the writer did.\n *\n * With two, they are the original and the broken package, in that order. Use\n * this when the broken one came from somewhere else: an edit made through the\n * library, a hand-mangled fixture, a file a user sent in.\n *\n * ## The oracles\n *\n * `--oracle validate` (the default) asks the twenty-nine rules. It needs no\n * PowerPoint, runs in milliseconds, and is what the tests use - but it can only\n * find failures we already know how to describe.\n *\n * `--oracle powerpoint` asks the real thing, on Windows, over COM. This is the\n * one that matters, because the interesting failures are the ones no rule\n * covers. It opens each candidate with `OpenAndRepair` switched **off**, which\n * is the only way to see a repair at all: PowerPoint's automation interface\n * repairs silently by default and reports success.\n *\n * `--oracle command --command \"...\"` runs anything else. `{}` in the command is\n * replaced with the path to the candidate package; a non-zero exit means the\n * candidate fails.\n */\n\nexport type OracleName = 'validate' | 'powerpoint' | 'command';\n\nexport interface BisectOptions {\n readonly oracle: OracleName;\n /** The shell command for `--oracle command`. `{}` becomes the candidate path. */\n readonly command: string | null;\n readonly maxRuns: number;\n /** Per-run ceiling in milliseconds, for the oracles that spawn something. */\n readonly timeout: number;\n /** Save the smallest failing package here. */\n readonly write: string | null;\n readonly json: boolean;\n readonly quiet: boolean;\n readonly out: string | null;\n /** Print a line per oracle run. Off by default; a bisection is not quick. */\n readonly progress: boolean;\n}\n\nexport const BISECT_DEFAULTS: BisectOptions = {\n oracle: 'validate',\n command: null,\n maxRuns: 2000,\n timeout: 120_000,\n write: null,\n json: false,\n quiet: false,\n out: null,\n progress: false,\n};\n\n/** Where `powerpoint-oracle.ps1` lives, from `src/` and from `dist/` alike. */\nexport function oracleScriptPath(): string {\n return fileURLToPath(new URL('../scripts/powerpoint-oracle.ps1', import.meta.url));\n}\n\n/**\n * The twenty-nine rules, asked of one candidate.\n *\n * No baseline is supplied and none could be: a candidate is a package that\n * never existed until this run, and it has no history. That makes every fatal\n * finding count as ours, which is the safe direction here for the same reason\n * it is in `validate` - the question is \"is this package broken\", not \"who\n * broke it\".\n */\nexport function validateOracle(): Oracle {\n return (bytes) => {\n try {\n return validatePackage({ bytes }).ok ? 'passes' : 'fails';\n } catch {\n // A candidate that will not open at all is a failure of a different kind,\n // and one this reducer must not chase: it means the splice produced\n // something that is not a package, which is a bug here rather than a\n // finding about the deck.\n return 'unresolved';\n }\n };\n}\n\ninterface SpawnOracleOptions {\n readonly timeout: number;\n readonly directory: string;\n readonly onSpawn?: (verdict: Verdict, detail: string) => void;\n}\n\n/** Write the candidate somewhere, run something over it, read the exit code. */\nfunction spawningOracle(\n run: (path: string) => { status: number | null; signal: string | null; stderr: string },\n options: SpawnOracleOptions,\n): Oracle {\n const path = join(options.directory, 'candidate.pptx');\n return (bytes) => {\n writeFileSync(path, bytes);\n const result = run(path);\n if (result.signal !== null || result.status === null) {\n // Killed, or timed out. Neither is an answer about the package.\n options.onSpawn?.('unresolved', result.signal ?? 'no exit status');\n return 'unresolved';\n }\n const verdict: Verdict =\n result.status === 0 ? 'passes' : result.status === 1 ? 'fails' : 'unresolved';\n options.onSpawn?.(verdict, 'exit ' + String(result.status));\n return verdict;\n };\n}\n\nexport function powerPointOracle(options: SpawnOracleOptions): Oracle {\n const script = oracleScriptPath();\n return spawningOracle((path) => {\n const result = spawnSync(\n 'powershell.exe',\n ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', script, '-File', path],\n { timeout: options.timeout, encoding: 'utf8', windowsHide: true },\n );\n return {\n status: result.status,\n signal: result.signal,\n stderr: result.stderr ?? '',\n };\n }, options);\n}\n\nexport function commandOracle(command: string, options: SpawnOracleOptions): Oracle {\n return spawningOracle((path) => {\n const filled = command.includes('{}') ? command.replaceAll('{}', path) : command + ' ' + path;\n const result = spawnSync(filled, {\n shell: true,\n timeout: options.timeout,\n encoding: 'utf8',\n windowsHide: true,\n });\n // A shell that could not start the command reports 127, and a command\n // that does not exist is not a broken package. Only 0 and 1 are answers.\n return { status: result.status, signal: result.signal, stderr: result.stderr ?? '' };\n }, options);\n}\n\n/** Shut down a PowerPoint this run started, so a bisection leaves nothing behind. */\nexport function quitPowerPoint(timeout: number): void {\n spawnSync(\n 'powershell.exe',\n ['-NoProfile', '-ExecutionPolicy', 'Bypass', '-File', oracleScriptPath(), '-QuitOnly'],\n { timeout, encoding: 'utf8', windowsHide: true },\n );\n}\n\nexport interface BisectStats {\n readonly original: string;\n readonly broken: string;\n readonly bytesOriginal: number;\n readonly bytesBroken: number;\n readonly entriesOriginal: number;\n readonly entriesBroken: number;\n readonly oracle: OracleName;\n}\n\n/**\n * A change's two sides, short enough to read and showing where they part.\n *\n * Truncating both from the front is the obvious thing and it is useless: two\n * versions of a 4 kB element that differ at character 3 000 come out as the\n * same sixty-eight characters twice, which reads as a bug in the bisector. So\n * the window is centred on the first character where they actually differ.\n */\nfunction sides(change: Change, width = 66): string[] {\n const flatten = (text: string): string => text.replaceAll('\\r', '').replaceAll('\\n', '⏎');\n const was = change.was === null ? null : flatten(change.was);\n const text = change.text === null ? null : flatten(change.text);\n\n let from = 0;\n if (was !== null && text !== null && (was.length > width || text.length > width)) {\n const at = firstDifference(was, text);\n if (at > width / 2) from = Math.floor(at - width / 2);\n }\n\n const show = (value: string | null, mark: string): string => {\n if (value === null) return ' ' + mark + ' (absent)';\n if (value === '') return ' ' + mark + ' (nothing)';\n const head = from > 0 ? '…' : '';\n const body = value.slice(from, from + width);\n const tail = from + width < value.length ? '…' : '';\n return ' ' + mark + ' ' + head + body + tail;\n };\n return [show(was, '-'), show(text, '+')];\n}\n\n/**\n * Render a result.\n *\n * Exported for the same reason `formatRoundTrip` is: the branches worth reading\n * are the ones a green corpus cannot produce, and they need a test of their own.\n */\nexport function formatBisect(stats: BisectStats, result: BisectResult, quiet: boolean): string {\n const lines: string[] = [];\n\n if (!quiet) {\n lines.push(\n 'bisect ' + stats.original,\n ' vs. ' + stats.broken,\n '',\n ' original ' +\n String(stats.bytesOriginal) +\n ' bytes, ' +\n String(stats.entriesOriginal) +\n ' entries',\n ' broken ' +\n String(stats.bytesBroken) +\n ' bytes, ' +\n String(stats.entriesBroken) +\n ' entries',\n ' delta ' +\n String(flattenChanges(result.changes).length) +\n ' change(s) in ' +\n String(result.changes.length) +\n ' entry(s)',\n ' oracle ' +\n stats.oracle +\n ', ' +\n String(result.runs) +\n ' run(s)' +\n (result.cached > 0 ? ', ' + String(result.cached) + ' from cache' : '') +\n (result.unresolved > 0 ? ', ' + String(result.unresolved) + ' unresolved' : ''),\n '',\n );\n }\n\n switch (result.outcome) {\n case 'identical':\n lines.push(' the two packages hold the same entries, byte for byte');\n break;\n case 'broken-passes':\n lines.push(\n ' the second package passes the oracle, so there is nothing to look for',\n ' (' +\n String(flattenChanges(result.changes).length) +\n ' change(s) between them, none fatal)',\n );\n break;\n case 'original-fails':\n lines.push(\n ' the *original* fails the oracle, so the cause is not in the difference',\n ' nothing was narrowed down; look at the original package first',\n );\n break;\n case 'localized':\n lines.push(' ' + summarizeBisect(result), '');\n for (const change of result.minimal) {\n lines.push(' ' + describeChange(change), ...sides(change), '');\n }\n break;\n }\n\n if (result.exhausted) {\n lines.push(\n '',\n ' stopped at the ceiling of ' + String(result.runs) + ' oracle run(s).',\n ' the answer above still fails, but it is not minimal - raise --max-runs.',\n );\n }\n if (result.truncated) {\n lines.push(\n '',\n ' the delta was too large to decompose completely, so some changes are',\n ' whole entries rather than elements - raise --max-changes.',\n );\n }\n\n return lines.join('\\n') + '\\n';\n}\n\nexport interface BisectFileResult {\n readonly result: BisectResult;\n readonly output: string;\n readonly exitCode: number;\n}\n\nexport function bisectFiles(\n originalPath: string,\n brokenPath: string | null,\n options: BisectOptions = BISECT_DEFAULTS,\n note: (text: string) => void = () => {},\n): BisectFileResult {\n const original = new Uint8Array(readFileSync(originalPath));\n // One argument means \"bisect what our own writer did to this deck\", which is\n // the case this command was built for.\n const broken =\n brokenPath === null\n ? roundTripPackage(original).exported.bytes\n : new Uint8Array(readFileSync(brokenPath));\n\n const directory = mkdtempSync(join(tmpdir(), 'pptx-bisect-'));\n try {\n const spawnOptions: SpawnOracleOptions = { timeout: options.timeout, directory };\n const oracle: Oracle =\n options.oracle === 'validate'\n ? validateOracle()\n : options.oracle === 'powerpoint'\n ? powerPointOracle(spawnOptions)\n : commandOracle(options.command ?? '', spawnOptions);\n\n const result = bisectPackages(original, broken, {\n oracle,\n maxRuns: options.maxRuns,\n ...(options.progress\n ? {\n onRun: (run) => {\n note(' run ' + String(run.run).padStart(4) + ' ' + run.label + '\\n');\n },\n }\n : {}),\n });\n\n const stats: BisectStats = {\n original: originalPath,\n broken: brokenPath ?? '(our own export of it)',\n bytesOriginal: original.length,\n bytesBroken: broken.length,\n entriesOriginal: readZip(original).entries.length,\n entriesBroken: readZip(broken).entries.length,\n oracle: options.oracle,\n };\n\n const output = options.json\n ? JSON.stringify(\n {\n original: originalPath,\n broken: stats.broken,\n oracle: options.oracle,\n outcome: result.outcome,\n runs: result.runs,\n cached: result.cached,\n unresolved: result.unresolved,\n exhausted: result.exhausted,\n truncated: result.truncated,\n delta: flattenChanges(result.changes).length,\n minimal: result.minimal.map((change) => ({\n entry: change.entry,\n kind: change.kind,\n where: change.where,\n was: change.was,\n text: change.text,\n })),\n },\n null,\n 2,\n ) + '\\n'\n : formatBisect(stats, result, options.quiet);\n\n if (options.write !== null) writeFileSync(options.write, result.bytes);\n\n return {\n result,\n output,\n // Non-zero whenever the deck is not clean, which is the three outcomes\n // that are not \"nothing to report\" - the same shape `validate` and\n // `roundtrip` use, so the three verbs can be chained in a script.\n exitCode: result.outcome === 'localized' || result.outcome === 'original-fails' ? 1 : 0,\n };\n } finally {\n rmSync(directory, { recursive: true, force: true });\n if (options.oracle === 'powerpoint') quitPowerPoint(options.timeout);\n }\n}\n\nexport function runBisect(\n originalPath: string,\n brokenPath: string | null,\n options: BisectOptions,\n write: (text: string) => void,\n): number {\n const result = bisectFiles(originalPath, brokenPath, options, write);\n if (options.out === null) write(result.output);\n else writeFileSync(options.out, result.output);\n return result.exitCode;\n}\n","import { readFileSync, writeFileSync } from 'node:fs';\nimport { censusPackage, formatCensus, type PackageCensus } from '@pptx-studio/census';\n\n/**\n * `pptx-studio inspect deck.pptx`\n *\n * Everything this command knows about `.pptx` lives in `@pptx-studio/census`,\n * which is a browser package with no Node in it. That split is deliberate and\n * it is the reason the census exists as a package at all: the browser explorer\n * in `apps/studio` computes exactly the same object in a Web Worker, so a\n * question answered here and a question answered in a tab cannot drift.\n *\n * What is left for this file is the two things a browser cannot do - read a\n * path off the filesystem, and write to a stream - plus argument handling.\n */\n\nexport interface InspectOptions {\n readonly json: boolean;\n readonly parts: boolean;\n readonly namespaces: boolean;\n readonly top: number;\n /** Write to this path instead of stdout. */\n readonly out: string | null;\n}\n\nexport const INSPECT_DEFAULTS: InspectOptions = {\n json: false,\n parts: false,\n namespaces: false,\n top: 15,\n out: null,\n};\n\nexport interface InspectResult {\n readonly census: PackageCensus;\n readonly output: string;\n /**\n * Process exit code.\n *\n * `1` when the census found something it classes as an error, `0` otherwise -\n * a warning or a note never fails the command. That distinction is what makes\n * `inspect` usable in a script: \"did this deck load, and is anything in it\n * structurally broken\" is a yes/no question, while \"is anything in it\n * unusual\" is not.\n */\n readonly exitCode: number;\n}\n\n/** Run a census over one file and render it. Does no I/O of its own beyond the read. */\nexport function inspectFile(\n path: string,\n options: InspectOptions = INSPECT_DEFAULTS,\n): InspectResult {\n const bytes = new Uint8Array(readFileSync(path));\n const census = censusPackage(bytes);\n\n const output = options.json\n ? JSON.stringify(census, null, 2) + '\\n'\n : formatCensus(census, {\n parts: options.parts,\n namespaces: options.namespaces,\n top: options.top,\n });\n\n const errors = census.problems.filter((problem) => problem.severity === 'error').length;\n return { census, output, exitCode: errors > 0 ? 1 : 0 };\n}\n\n/** Run `inspectFile` and put the result where the options say. */\nexport function runInspect(\n path: string,\n options: InspectOptions,\n write: (text: string) => void,\n): number {\n const result = inspectFile(path, options);\n if (options.out === null) write(result.output);\n else writeFileSync(options.out, result.output);\n return result.exitCode;\n}\n","/**\n * Typed failures for rendering outside a browser.\n *\n * The browser renderer cannot produce any of these: it is handed a measuring\n * canvas and a font stack, and the platform answers. In Node both of those are\n * ours to find, so both are ours to fail at. ADR 0042.\n */\nexport type RenderErrorCode =\n /** Bytes that are not an SFNT font, or a table the reader needs is absent. */\n | 'CLI_FONT_UNREADABLE'\n /** A font directory that was named on the command line and does not exist. */\n | 'CLI_FONT_DIR'\n /** No face on this machine can stand in for a typeface the deck names. */\n | 'CLI_NO_FACE'\n /** A run whose resolved size is not a positive number of hundredths of a point. */\n | 'CLI_FONT_SIZE'\n /** A slide index outside the deck. */\n | 'CLI_NO_SLIDE'\n /** An output path that names no directory, or a directory that is a file. */\n | 'CLI_OUTPUT_PATH';\n\nexport const RENDER_ERROR_CODES: readonly RenderErrorCode[] = [\n 'CLI_FONT_UNREADABLE',\n 'CLI_FONT_DIR',\n 'CLI_NO_FACE',\n 'CLI_FONT_SIZE',\n 'CLI_NO_SLIDE',\n 'CLI_OUTPUT_PATH',\n];\n\nexport class RenderError extends Error {\n override readonly name = 'RenderError';\n readonly code: RenderErrorCode;\n /** The font file, deck path or typeface the failure is about. */\n readonly subject: string;\n\n constructor(code: RenderErrorCode, message: string, subject: string) {\n super(message);\n this.code = code;\n this.subject = subject;\n }\n}\n\nexport function isRenderError(error: unknown): error is RenderError {\n return error instanceof RenderError;\n}\n","/**\n * The four tables a measurement needs, read out of a font file.\n *\n * Not a font library: nothing here decodes an outline, and a CFF font is read\n * as happily as a glyf one because advances live in `hmtx` either way. What it\n * answers is the question the browser answers with `measureText`, which is the\n * one thing `render-svg` cannot get in Node. Every reading below was measured\n * against Chromium in experiment T13 rather than taken from the specification.\n * ADR 0042.\n */\n\nimport { RenderError } from './errors.js';\n\nconst OTTO = 0x4f54544f;\nconst TRUE_TYPE = 0x00010000;\nconst TTCF = 0x74746366;\n\n/** `OS/2.fsSelection` bit 7: the typographic metrics are the ones to use. */\nconst USE_TYPO_METRICS = 0x0080;\n\nexport interface FaceMetrics {\n /** `head.unitsPerEm`, the denominator of every number here. */\n readonly unitsPerEm: number;\n /** Above the baseline, in font units. */\n readonly ascent: number;\n /** Below the baseline, in font units, positive. */\n readonly descent: number;\n /** Which table `ascent` and `descent` came out of, for the diagnostics. */\n readonly source: 'usWin' | 'sTypo';\n}\n\nexport interface Face {\n /** `name` ID 1, which is what PowerPoint matches a typeface on. */\n readonly family: string;\n /** `name` ID 2: `Regular`, `Bold`, `Italic`, `Bold Italic`, or a style name. */\n readonly subfamily: string;\n /** `name` ID 16, present when the family splits into more than four styles. */\n readonly typographicFamily: string | undefined;\n readonly metrics: FaceMetrics;\n readonly bold: boolean;\n readonly italic: boolean;\n /** Code point to advance, in font units. */\n advanceOf(codePoint: number): number | undefined;\n /** The adjustment between two code points, in font units. Usually zero. */\n kernBetween(left: number, right: number): number;\n}\n\ninterface Reader {\n readonly bytes: Uint8Array;\n readonly view: DataView;\n}\n\nfunction readerOf(bytes: Uint8Array): Reader {\n return { bytes, view: new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength) };\n}\n\nfunction u8(r: Reader, at: number): number {\n return r.view.getUint8(at);\n}\nfunction u16(r: Reader, at: number): number {\n return r.view.getUint16(at);\n}\nfunction i16(r: Reader, at: number): number {\n return r.view.getInt16(at);\n}\nfunction u32(r: Reader, at: number): number {\n return r.view.getUint32(at);\n}\n\nfunction unreadable(what: string, subject: string): never {\n throw new RenderError('CLI_FONT_UNREADABLE', what, subject);\n}\n\n/* -------------------------------------------------------------------------- */\n/* the table directory */\n/* -------------------------------------------------------------------------- */\n\ntype Tables = ReadonlyMap<string, Uint8Array>;\n\nfunction directoryAt(bytes: Uint8Array, start: number, subject: string): Tables {\n const r = readerOf(bytes);\n if (start + 12 > bytes.length) unreadable('font directory runs past the file', subject);\n const count = u16(r, start + 4);\n const out = new Map<string, Uint8Array>();\n for (let i = 0; i < count; i++) {\n const at = start + 12 + i * 16;\n if (at + 16 > bytes.length) unreadable('table directory runs past the file', subject);\n const tag = String.fromCharCode(u8(r, at), u8(r, at + 1), u8(r, at + 2), u8(r, at + 3));\n const offset = u32(r, at + 8);\n const length = u32(r, at + 12);\n // A length that runs past the end is a truncated file, not a fatal one:\n // clamping keeps a readable `name` usable when a trailing table is short.\n if (offset < bytes.length)\n out.set(tag, bytes.subarray(offset, Math.min(offset + length, bytes.length)));\n }\n return out;\n}\n\n/** Every font in the file: one for a plain font, several for a collection. */\nexport function fontsIn(bytes: Uint8Array, subject: string): readonly Tables[] {\n if (bytes.length < 12) unreadable('too short to be a font', subject);\n const r = readerOf(bytes);\n const version = u32(r, 0);\n if (version === TTCF) {\n const count = u32(r, 8);\n const out: Tables[] = [];\n for (let i = 0; i < count; i++) out.push(directoryAt(bytes, u32(r, 12 + i * 4), subject));\n return out;\n }\n if (version !== TRUE_TYPE && version !== OTTO) {\n unreadable(`0x${version.toString(16).padStart(8, '0')} is not an SFNT signature`, subject);\n }\n return [directoryAt(bytes, 0, subject)];\n}\n\nfunction required(tables: Tables, tag: string, subject: string): Uint8Array {\n const table = tables.get(tag);\n if (table === undefined) unreadable(`no ${tag} table`, subject);\n return table;\n}\n\n/* -------------------------------------------------------------------------- */\n/* name */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The names a face is known by, decoded from the Windows platform records.\n *\n * Platform 3 encoding 1 is UTF-16BE and is what every font shipped for Windows\n * carries; platform 1 is Mac Roman and is read only where a face has no\n * Windows record at all, which is rare enough to be worth the four lines.\n */\nfunction namesOf(tables: Tables, subject: string): ReadonlyMap<number, string> {\n const table = required(tables, 'name', subject);\n const r = readerOf(table);\n if (table.length < 6) unreadable('name table is truncated', subject);\n const count = u16(r, 2);\n const storage = u16(r, 4);\n const out = new Map<number, string>();\n const seenWindows = new Set<number>();\n for (let i = 0; i < count; i++) {\n const at = 6 + i * 12;\n if (at + 12 > table.length) break;\n const platform = u16(r, at);\n const encoding = u16(r, at + 2);\n const nameId = u16(r, at + 6);\n const length = u16(r, at + 8);\n const offset = storage + u16(r, at + 10);\n if (offset + length > table.length) continue;\n const raw = table.subarray(offset, offset + length);\n if (platform === 3 && (encoding === 1 || encoding === 0)) {\n let text = '';\n for (let j = 0; j + 1 < raw.length; j += 2) {\n text += String.fromCharCode(((raw[j] ?? 0) << 8) | (raw[j + 1] ?? 0));\n }\n out.set(nameId, text);\n seenWindows.add(nameId);\n } else if (platform === 1 && encoding === 0 && !seenWindows.has(nameId)) {\n out.set(nameId, String.fromCharCode(...raw));\n }\n }\n return out;\n}\n\n/* -------------------------------------------------------------------------- */\n/* cmap */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The best Unicode subtable in the font.\n *\n * Preference order is the one every shaper uses: a format 12 full-repertoire\n * table beats a format 4 BMP one, because a face that has both maps astral\n * code points only in the first.\n */\nfunction cmapOf(tables: Tables, subject: string): (codePoint: number) => number {\n const table = required(tables, 'cmap', subject);\n const r = readerOf(table);\n if (table.length < 4) unreadable('cmap table is truncated', subject);\n const count = u16(r, 2);\n\n let best: { offset: number; score: number } | undefined;\n for (let i = 0; i < count; i++) {\n const at = 4 + i * 8;\n if (at + 8 > table.length) break;\n const platform = u16(r, at);\n const encoding = u16(r, at + 2);\n const offset = u32(r, at + 4);\n if (offset + 2 > table.length) continue;\n const unicode =\n (platform === 3 && (encoding === 10 || encoding === 1 || encoding === 0)) || platform === 0;\n if (!unicode) continue;\n const format = u16(readerOf(table.subarray(offset)), 0);\n const score = format === 12 ? 3 : format === 4 ? 2 : format === 6 || format === 0 ? 1 : 0;\n if (score > 0 && (best === undefined || score > best.score)) best = { offset, score };\n }\n if (best === undefined) unreadable('no Unicode cmap subtable', subject);\n\n const sub = table.subarray(best.offset);\n const s = readerOf(sub);\n const format = u16(s, 0);\n\n if (format === 4) {\n const segCount = u16(s, 6) / 2;\n const ends = 14;\n const starts = ends + segCount * 2 + 2;\n const deltas = starts + segCount * 2;\n const ranges = deltas + segCount * 2;\n return (codePoint: number): number => {\n if (codePoint > 0xffff) return 0;\n for (let seg = 0; seg < segCount; seg++) {\n if (u16(s, ends + seg * 2) < codePoint) continue;\n if (u16(s, starts + seg * 2) > codePoint) return 0;\n const rangeOffset = u16(s, ranges + seg * 2);\n if (rangeOffset === 0) return (codePoint + i16(s, deltas + seg * 2)) & 0xffff;\n const at = ranges + seg * 2 + rangeOffset + (codePoint - u16(s, starts + seg * 2)) * 2;\n if (at + 2 > sub.length) return 0;\n const glyph = u16(s, at);\n return glyph === 0 ? 0 : (glyph + i16(s, deltas + seg * 2)) & 0xffff;\n }\n return 0;\n };\n }\n\n if (format === 12) {\n const groups = u32(s, 12);\n return (codePoint: number): number => {\n let low = 0;\n let high = groups - 1;\n while (low <= high) {\n const mid = (low + high) >> 1;\n const at = 16 + mid * 12;\n const start = u32(s, at);\n const end = u32(s, at + 4);\n if (codePoint < start) high = mid - 1;\n else if (codePoint > end) low = mid + 1;\n else return u32(s, at + 8) + (codePoint - start);\n }\n return 0;\n };\n }\n\n if (format === 6) {\n const first = u16(s, 6);\n const entries = u16(s, 8);\n return (codePoint: number): number => {\n const at = codePoint - first;\n return at < 0 || at >= entries ? 0 : u16(s, 10 + at * 2);\n };\n }\n\n // Format 0: a 256-byte byte-to-glyph array.\n return (codePoint: number): number => (codePoint > 0xff ? 0 : u8(s, 6 + codePoint));\n}\n\n/* -------------------------------------------------------------------------- */\n/* kerning */\n/* -------------------------------------------------------------------------- */\n\ntype Kerning = (left: number, right: number) => number;\n\nconst NO_KERNING: Kerning = () => 0;\n\nfunction coverageIndex(r: Reader, at: number, glyph: number): number {\n const format = u16(r, at);\n if (format === 1) {\n const count = u16(r, at + 2);\n for (let i = 0; i < count; i++) if (u16(r, at + 4 + i * 2) === glyph) return i;\n return -1;\n }\n if (format !== 2) return -1;\n const ranges = u16(r, at + 2);\n for (let i = 0; i < ranges; i++) {\n const record = at + 4 + i * 6;\n if (glyph >= u16(r, record) && glyph <= u16(r, record + 2)) {\n return u16(r, record + 4) + (glyph - u16(r, record));\n }\n }\n return -1;\n}\n\nfunction classOf(r: Reader, at: number, glyph: number): number {\n const format = u16(r, at);\n if (format === 1) {\n const start = u16(r, at + 2);\n const count = u16(r, at + 4);\n const index = glyph - start;\n return index < 0 || index >= count ? 0 : u16(r, at + 6 + index * 2);\n }\n if (format !== 2) return 0;\n const ranges = u16(r, at + 2);\n for (let i = 0; i < ranges; i++) {\n const record = at + 4 + i * 6;\n if (glyph >= u16(r, record) && glyph <= u16(r, record + 2)) return u16(r, record + 4);\n }\n return 0;\n}\n\n/** The byte width of a GPOS value record, which is one 16-bit field per set bit. */\nfunction valueSize(format: number): number {\n let bits = 0;\n for (let i = 0; i < 16; i++) if ((format & (1 << i)) !== 0) bits += 1;\n return bits * 2;\n}\n\n/** `XAdvance` is bit 2, and it is the only field a horizontal advance reads. */\nconst X_ADVANCE = 0x0004;\n\nfunction pairPosLookup(r: Reader, at: number): Kerning | undefined {\n const format = u16(r, at);\n const valueFormat1 = u16(r, at + 4);\n const valueFormat2 = u16(r, at + 6);\n if ((valueFormat1 & X_ADVANCE) === 0) return undefined;\n const size1 = valueSize(valueFormat1);\n const size2 = valueSize(valueFormat2);\n const coverage = at + u16(r, at + 2);\n\n if (format === 1) {\n const setCount = u16(r, at + 8);\n return (left: number, right: number): number => {\n const index = coverageIndex(r, coverage, left);\n if (index < 0 || index >= setCount) return 0;\n const set = at + u16(r, at + 10 + index * 2);\n const pairs = u16(r, set);\n for (let i = 0; i < pairs; i++) {\n const record = set + 2 + i * (2 + size1 + size2);\n if (u16(r, record) === right) return i16(r, record + 2);\n }\n return 0;\n };\n }\n\n if (format !== 2) return undefined;\n const classDef1 = at + u16(r, at + 8);\n const classDef2 = at + u16(r, at + 10);\n const class1Count = u16(r, at + 12);\n const class2Count = u16(r, at + 14);\n return (left: number, right: number): number => {\n if (coverageIndex(r, coverage, left) < 0) return 0;\n const c1 = classOf(r, classDef1, left);\n const c2 = classOf(r, classDef2, right);\n if (c1 >= class1Count || c2 >= class2Count) return 0;\n const record = at + 16 + (c1 * class2Count + c2) * (size1 + size2);\n return i16(r, record);\n };\n}\n\n/**\n * The `kern` feature's pair adjustments, if the font has GPOS.\n *\n * Only the `kern` feature: a font's GPOS also carries mark attachment and\n * cursive positioning, and neither changes an advance.\n */\nfunction gposKerning(tables: Tables): Kerning | undefined {\n const table = tables.get('GPOS');\n if (table === undefined || table.length < 10) return undefined;\n const r = readerOf(table);\n const featureList = u16(r, 6);\n const lookupList = u16(r, 8);\n if (featureList >= table.length || lookupList >= table.length) return undefined;\n\n const wanted = new Set<number>();\n const featureCount = u16(r, featureList);\n for (let i = 0; i < featureCount; i++) {\n const record = featureList + 2 + i * 6;\n const tag = String.fromCharCode(\n u8(r, record),\n u8(r, record + 1),\n u8(r, record + 2),\n u8(r, record + 3),\n );\n if (tag !== 'kern') continue;\n const feature = featureList + u16(r, record + 4);\n const lookups = u16(r, feature + 2);\n for (let j = 0; j < lookups; j++) wanted.add(u16(r, feature + 4 + j * 2));\n }\n if (wanted.size === 0) return undefined;\n\n const found: Kerning[] = [];\n const lookupCount = u16(r, lookupList);\n for (const index of wanted) {\n if (index >= lookupCount) continue;\n const lookup = lookupList + u16(r, lookupList + 2 + index * 2);\n if (u16(r, lookup) !== 2) continue; // type 2, pair adjustment\n const subtables = u16(r, lookup + 4);\n for (let i = 0; i < subtables; i++) {\n const pairs = pairPosLookup(r, lookup + u16(r, lookup + 6 + i * 2));\n if (pairs !== undefined) found.push(pairs);\n }\n }\n if (found.length === 0) return undefined;\n return (left, right) => {\n for (const lookup of found) {\n const adjust = lookup(left, right);\n if (adjust !== 0) return adjust;\n }\n return 0;\n };\n}\n\n/** The pre-OpenType `kern` table, format 0, horizontal subtables only. */\nfunction legacyKerning(tables: Tables): Kerning | undefined {\n const table = tables.get('kern');\n if (table === undefined || table.length < 4) return undefined;\n const r = readerOf(table);\n const subtables = u16(r, 2);\n const pairs = new Map<number, number>();\n let at = 4;\n for (let i = 0; i < subtables && at + 14 <= table.length; i++) {\n const length = u16(r, at + 2);\n const coverage = u16(r, at + 4);\n const horizontal = (coverage & 0x0001) !== 0;\n const format = (coverage >> 8) & 0xff;\n if (horizontal && format === 0) {\n const count = u16(r, at + 6);\n for (let j = 0; j < count; j++) {\n const record = at + 14 + j * 6;\n if (record + 6 > table.length) break;\n pairs.set((u16(r, record) << 16) | u16(r, record + 2), i16(r, record + 4));\n }\n }\n at += length === 0 ? 14 : length;\n }\n if (pairs.size === 0) return undefined;\n return (left, right) => pairs.get((left << 16) | right) ?? 0;\n}\n\n/* -------------------------------------------------------------------------- */\n/* the face */\n/* -------------------------------------------------------------------------- */\n\n/**\n * The vertical metrics a browser reports for the face.\n *\n * T13 built one font whose `hhea`, `usWin` and `sTypo` pairs are all different\n * and asked Chromium for `fontBoundingBoxAscent`: it answered `usWin`, and\n * answered `sTypo` from the same font with `fsSelection` bit 7 set. So `hhea`\n * is never the answer, and a reader that ignores bit 7 is wrong on every font\n * that sets it, which is most of the ones shipped since 2015.\n */\nfunction metricsOf(tables: Tables, subject: string): FaceMetrics {\n const head = readerOf(required(tables, 'head', subject));\n const unitsPerEm = u16(head, 18);\n if (unitsPerEm <= 0) unreadable('head.unitsPerEm is zero', subject);\n\n const os2 = tables.get('OS/2');\n if (os2 === undefined || os2.length < 78) {\n // No OS/2 at all is a bare CJK or bitmap face; hhea is then the only source\n // there is, and the browser has nothing else to read either.\n const hhea = readerOf(required(tables, 'hhea', subject));\n return { unitsPerEm, ascent: i16(hhea, 4), descent: -i16(hhea, 6), source: 'usWin' };\n }\n const r = readerOf(os2);\n const useTypo = (u16(r, 62) & USE_TYPO_METRICS) !== 0;\n if (useTypo) {\n return { unitsPerEm, ascent: i16(r, 68), descent: -i16(r, 70), source: 'sTypo' };\n }\n return { unitsPerEm, ascent: u16(r, 74), descent: u16(r, 76), source: 'usWin' };\n}\n\nfunction advancesOf(tables: Tables, subject: string): (glyph: number) => number {\n const hhea = readerOf(required(tables, 'hhea', subject));\n const metrics = u16(hhea, 34);\n const hmtx = required(tables, 'hmtx', subject);\n const r = readerOf(hmtx);\n if (metrics === 0) unreadable('hhea.numberOfHMetrics is zero', subject);\n return (glyph: number): number => {\n const at = Math.min(glyph, metrics - 1) * 4;\n return at + 2 <= hmtx.length ? u16(r, at) : 0;\n };\n}\n\nconst BOLD_STYLE = /bold|black|heavy|semibold|extrabold|demibold/i;\nconst ITALIC_STYLE = /italic|oblique/i;\n\n/** Read one font out of an already-located table directory. */\nexport function faceOf(tables: Tables, subject: string): Face {\n const names = namesOf(tables, subject);\n const family = names.get(1);\n if (family === undefined || family === '') unreadable('no family name', subject);\n const subfamily = names.get(2) ?? 'Regular';\n const cmap = cmapOf(tables, subject);\n const advance = advancesOf(tables, subject);\n\n // GPOS first, and only then the legacy table: T13 built a font whose two\n // disagreed and Chromium took the GPOS value.\n const kerning = gposKerning(tables) ?? legacyKerning(tables) ?? NO_KERNING;\n\n // `head.macStyle` is the fallback for a face whose subfamily is a design name\n // like `Condensed Light` rather than one of the four standard strings.\n const head = readerOf(required(tables, 'head', subject));\n const macStyle = u16(head, 44);\n\n return {\n family,\n subfamily,\n typographicFamily: names.get(16),\n metrics: metricsOf(tables, subject),\n bold: BOLD_STYLE.test(subfamily) || (macStyle & 0x01) !== 0,\n italic: ITALIC_STYLE.test(subfamily) || (macStyle & 0x02) !== 0,\n advanceOf(codePoint: number): number | undefined {\n const glyph = cmap(codePoint);\n return glyph === 0 ? undefined : advance(glyph);\n },\n kernBetween(left: number, right: number): number {\n const a = cmap(left);\n const b = cmap(right);\n return a === 0 || b === 0 ? 0 : kerning(a, b);\n },\n };\n}\n\n/** Every face in a file, which is one unless the file is a collection. */\nexport function facesIn(bytes: Uint8Array, subject: string): readonly Face[] {\n return fontsIn(bytes, subject).map((tables) => faceOf(tables, subject));\n}\n","/**\n * Which file on this machine is the typeface the deck asked for.\n *\n * The browser answers this with a font stack and never tells us what it chose;\n * here the choice is ours, so it is also reportable - `renderDeck` can say\n * \"Aptos was drawn in Carlito\" because this module knows it was. The\n * substitution table itself is `@pptx-studio/text`'s, so the two renderers\n * cannot fall back differently. ADR 0042.\n */\n\nimport { readdirSync, readFileSync, statSync } from 'node:fs';\nimport { homedir } from 'node:os';\nimport { join } from 'node:path';\n\nimport { LAST_RESORT_FAMILIES, substituteFor } from '@pptx-studio/text';\n\nimport { RenderError } from './errors.js';\nimport { facesIn, type Face } from './sfnt.js';\n\nconst FONT_FILE = /\\.(ttf|ttc|otf|otc)$/i;\n\n/** Deep enough for `/usr/share/fonts/truetype/dejavu`, and no deeper. */\nconst MAX_DEPTH = 4;\n\n/** Case and whitespace are not part of a typeface's identity for a lookup. */\nfunction key(family: string): string {\n return family.trim().replace(/\\s+/g, ' ').toLowerCase();\n}\n\n/** Where fonts live on this platform, whether or not the directories exist. */\nexport function systemFontDirectories(platform: string = process.platform): readonly string[] {\n const home = homedir();\n if (platform === 'win32') {\n const root = process.env['SystemRoot'] ?? 'C:\\\\Windows';\n const local = process.env['LOCALAPPDATA'];\n return [\n join(root, 'Fonts'),\n ...(local === undefined ? [] : [join(local, 'Microsoft', 'Windows', 'Fonts')]),\n ];\n }\n if (platform === 'darwin') {\n return ['/System/Library/Fonts', '/Library/Fonts', join(home, 'Library', 'Fonts')];\n }\n return [\n '/usr/share/fonts',\n '/usr/local/share/fonts',\n join(home, '.local', 'share', 'fonts'),\n join(home, '.fonts'),\n ];\n}\n\nfunction filesUnder(directory: string, depth: number, out: string[]): void {\n if (depth > MAX_DEPTH) return;\n let entries;\n try {\n entries = readdirSync(directory, { withFileTypes: true });\n } catch {\n // A directory that is not there, or not readable, contributes no fonts.\n // Only a directory the caller named explicitly is an error, and `indexFonts`\n // checks those before it gets here.\n return;\n }\n for (const entry of entries) {\n const path = join(directory, entry.name);\n if (entry.isDirectory()) filesUnder(path, depth + 1, out);\n else if (FONT_FILE.test(entry.name)) out.push(path);\n }\n}\n\nexport interface IndexedFace {\n readonly face: Face;\n readonly file: string;\n}\n\n/** What the library found for a typeface the deck named. */\nexport interface Resolved {\n readonly face: Face;\n /** The typeface the deck asked for. */\n readonly asked: string;\n /** The family actually drawn in, which differs when a substitute was used. */\n readonly drawn: string;\n readonly file: string;\n readonly substituted: boolean;\n}\n\nexport interface FontLibrary {\n /** Every face found, for the diagnostics and for a test to count. */\n readonly indexed: readonly IndexedFace[];\n /** The directories that were scanned, in order. */\n readonly directories: readonly string[];\n resolve(family: string, bold: boolean, italic: boolean): Resolved | undefined;\n}\n\nexport interface IndexOptions {\n /** Directories to scan before the system ones. Each must exist. */\n readonly extra?: readonly string[];\n /** Whether to scan this platform's own font directories. Default true. */\n readonly system?: boolean;\n readonly platform?: string;\n}\n\n/**\n * The style slot a face fills.\n *\n * Two bits rather than a weight axis, because DrawingML has no weight axis:\n * `a:rPr/@b` is a boolean and \"Roboto Light\" is a typeface name, not a weight.\n */\nfunction slot(bold: boolean, italic: boolean): number {\n return (bold ? 1 : 0) | (italic ? 2 : 0);\n}\n\n/**\n * Read every font file under the given directories, once.\n *\n * A file that will not parse is skipped rather than fatal: a font directory on\n * a real machine holds `.ttf` files that are bitmap-only, damaged, or not fonts\n * at all, and one of them must not stop a deck from rendering.\n */\nexport function indexFonts(options: IndexOptions = {}): FontLibrary {\n for (const directory of options.extra ?? []) {\n try {\n if (!statSync(directory).isDirectory()) {\n throw new RenderError('CLI_FONT_DIR', `${directory} is not a directory`, directory);\n }\n } catch (error) {\n if (error instanceof RenderError) throw error;\n throw new RenderError('CLI_FONT_DIR', `no such directory: ${directory}`, directory);\n }\n }\n\n const directories = [\n ...(options.extra ?? []),\n ...(options.system === false ? [] : systemFontDirectories(options.platform)),\n ];\n\n const files: string[] = [];\n for (const directory of directories) filesUnder(directory, 0, files);\n\n const indexed: IndexedFace[] = [];\n const byFamily = new Map<string, (IndexedFace | undefined)[]>();\n const claim = (family: string, entry: IndexedFace): void => {\n const slots = byFamily.get(key(family)) ?? [undefined, undefined, undefined, undefined];\n const at = slot(entry.face.bold, entry.face.italic);\n // First file wins, so an earlier `--font-dir` beats a system face of the\n // same name and the caller can override without uninstalling anything.\n slots[at] ??= entry;\n byFamily.set(key(family), slots);\n };\n\n for (const file of files) {\n let faces: readonly Face[];\n try {\n faces = facesIn(new Uint8Array(readFileSync(file)), file);\n } catch {\n continue;\n }\n for (const face of faces) {\n const entry: IndexedFace = { face, file };\n indexed.push(entry);\n claim(face.family, entry);\n // Name ID 16 is what a large family is known by once it outgrows the four\n // style slots, and it is the name a deck spells - `Segoe UI` for the face\n // whose ID 1 is `Segoe UI Semibold`.\n if (face.typographicFamily !== undefined) claim(face.typographicFamily, entry);\n }\n }\n\n /** The nearest slot to the one asked for: exact, then drop italic, then bold. */\n const pick = (family: string, bold: boolean, italic: boolean): IndexedFace | undefined => {\n const slots = byFamily.get(key(family));\n if (slots === undefined) return undefined;\n const wanted = [\n slot(bold, italic),\n slot(bold, false),\n slot(false, italic),\n slot(false, false),\n 0,\n 1,\n 2,\n 3,\n ];\n for (const at of wanted) {\n const found = slots[at];\n if (found !== undefined) return found;\n }\n return undefined;\n };\n\n return {\n indexed,\n directories,\n resolve(family: string, bold: boolean, italic: boolean): Resolved | undefined {\n const direct = pick(family, bold, italic);\n if (direct !== undefined) {\n return {\n face: direct.face,\n asked: family,\n drawn: family,\n file: direct.file,\n substituted: false,\n };\n }\n const chain = [substituteFor(family)?.use, ...LAST_RESORT_FAMILIES].filter(\n (name): name is string => name !== undefined,\n );\n for (const name of chain) {\n const found = pick(name, bold, italic);\n if (found !== undefined) {\n return {\n face: found.face,\n asked: family,\n drawn: name,\n file: found.file,\n substituted: true,\n };\n }\n }\n return undefined;\n },\n };\n}\n","/**\n * `TextMeasurer` and `FaceBoxProbe` over font tables instead of a canvas.\n *\n * This is a second measurement engine, and `@pptx-studio/text` says in as many\n * words that nothing inside it may measure by a second route. That rule holds:\n * this is outside it, in the one package where Node exists, and it is here\n * because the browser's engine is not available to a server. What makes it safe\n * is that T13 measured Chromium's arithmetic rather than guessing it, so the two\n * engines agree exactly on a face they both have. ADR 0042.\n */\n\nimport {\n kerningEnabled,\n type FaceBox,\n type FaceBoxProbe,\n type RunFont,\n type TextMeasurer,\n} from '@pptx-studio/text';\n\nimport type { FontLibrary, Resolved } from './faces.js';\nimport { RenderError } from './errors.js';\n\n/**\n * The fixed-point step Chromium reports an advance in.\n *\n * T13 measured 252 widths across seven fonts, six strings and six sizes. Summing\n * exact floats fits 88 of them; quantising each glyph advance toward zero and\n * each kern adjustment to nearest, both at 1/65536 px, fits all 216. The\n * difference is never more than 1.6e-5 px and matters to nothing on a slide -\n * it is here because a rule that reproduces the browser exactly can be tested\n * exactly, and one that is merely close cannot.\n */\nconst FIXED = 65536;\n\n/**\n * Every advance in this font is positive, so truncation and flooring are the\n * same rule; T13 had no probe that could separate them and does not claim one.\n */\nfunction quantiseAdvance(px: number): number {\n return Math.trunc(px * FIXED) / FIXED;\n}\n\nfunction quantiseKern(px: number): number {\n return Math.round(px * FIXED) / FIXED;\n}\n\nexport interface FaceUse {\n readonly asked: string;\n readonly drawn: string;\n readonly file: string;\n readonly substituted: boolean;\n}\n\nexport interface FontMeasurer {\n readonly measurer: TextMeasurer;\n readonly faceBox: FaceBoxProbe;\n /** Every typeface asked for, and what it was drawn in. */\n used(): readonly FaceUse[];\n /** Code points no face in the library could draw. */\n missing(): readonly number[];\n}\n\n/**\n * A measurer bound to one library.\n *\n * Both probes cache by family, because a slide asks for the same handful of\n * typefaces thousands of times and nothing can change between two asks.\n */\nexport function createFontMeasurer(library: FontLibrary): FontMeasurer {\n const resolved = new Map<string, Resolved>();\n const missing = new Set<number>();\n /** Which face draws a code point the asked-for face has no glyph for. */\n const fallbacks = new Map<string, Resolved | null>();\n\n const faceFor = (family: string, bold: boolean, italic: boolean): Resolved => {\n const cacheKey = `${family}\u0000${bold ? 'b' : ''}${italic ? 'i' : ''}`;\n const cached = resolved.get(cacheKey);\n if (cached !== undefined) return cached;\n const found = library.resolve(family, bold, italic);\n if (found === undefined) {\n throw new RenderError(\n 'CLI_NO_FACE',\n `no face on this machine can stand in for ${JSON.stringify(family)}; ` +\n `${String(library.indexed.length)} face(s) were indexed from ` +\n `${library.directories.join(', ')}`,\n family,\n );\n }\n resolved.set(cacheKey, found);\n return found;\n };\n\n /**\n * The advance of one code point, in font units over its own em.\n *\n * A face that lacks the glyph does not draw a blank: the browser would fall\n * back per character, so the whole index is searched once per code point and\n * the answer cached. A code point nothing has is recorded and contributes the\n * face's own `.notdef` width, which is what is drawn.\n */\n const advanceOf = (face: Resolved, codePoint: number): { units: number; em: number } => {\n const own = face.face.advanceOf(codePoint);\n if (own !== undefined) return { units: own, em: face.face.metrics.unitsPerEm };\n\n const cacheKey = String(codePoint);\n let stand = fallbacks.get(cacheKey);\n if (stand === undefined) {\n stand = null;\n for (const entry of library.indexed) {\n if (entry.face.advanceOf(codePoint) !== undefined) {\n stand = { ...face, face: entry.face, drawn: entry.face.family, file: entry.file };\n break;\n }\n }\n fallbacks.set(cacheKey, stand);\n }\n if (stand === null) {\n missing.add(codePoint);\n return { units: 0, em: face.face.metrics.unitsPerEm };\n }\n const units = stand.face.advanceOf(codePoint) ?? 0;\n return { units, em: stand.face.metrics.unitsPerEm };\n };\n\n const measurer: TextMeasurer = {\n measure(text: string, font: RunFont) {\n if (!Number.isFinite(font.sz) || font.sz <= 0) {\n throw new RenderError(\n 'CLI_FONT_SIZE',\n `font size ${String(font.sz)} is not a positive size`,\n font.family,\n );\n }\n const face = faceFor(font.family, font.bold === true, font.italic === true);\n // One CSS pixel is one point here, exactly as in the canvas measurer.\n const px = font.sz / 100;\n const kerns = kerningEnabled(font.kern, font.sz);\n const spacing = (font.spc ?? 0) / 100;\n\n const points = [...text];\n let width = 0;\n for (let i = 0; i < points.length; i++) {\n const codePoint = points[i]?.codePointAt(0) ?? 0;\n const { units, em } = advanceOf(face, codePoint);\n width += quantiseAdvance((units * px) / em);\n // `@spc` is an absolute length applied after every character, the last\n // included - T2 scored that 18/18 - and Chromium's `letterSpacing` does\n // the same, which is why the canvas measurer can pass it straight in.\n width += spacing;\n if (kerns && i + 1 < points.length) {\n const next = points[i + 1]?.codePointAt(0) ?? 0;\n const adjust = face.face.kernBetween(codePoint, next);\n if (adjust !== 0) {\n width += quantiseKern((adjust * px) / face.face.metrics.unitsPerEm);\n }\n }\n }\n return { width };\n },\n };\n\n const boxes = new Map<string, FaceBox>();\n const faceBox: FaceBoxProbe = {\n box(family: string): FaceBox {\n const cached = boxes.get(family);\n if (cached !== undefined) return cached;\n const { metrics } = faceFor(family, false, false).face;\n const box: FaceBox = {\n ascent: metrics.ascent / metrics.unitsPerEm,\n descent: metrics.descent / metrics.unitsPerEm,\n // Chromium reports the face's descent as the ideographic baseline for\n // every face without a `BASE` table `ideo` entry, which is every Latin\n // face; a face that has one is an open question in ADR 0042.\n ideographic: metrics.descent / metrics.unitsPerEm,\n };\n boxes.set(family, box);\n return box;\n },\n };\n\n return {\n measurer,\n faceBox,\n used(): readonly FaceUse[] {\n const seen = new Map<string, FaceUse>();\n for (const face of resolved.values()) {\n seen.set(face.asked, {\n asked: face.asked,\n drawn: face.drawn,\n file: face.file,\n substituted: face.substituted,\n });\n }\n return [...seen.values()].sort((a, b) => (a.asked < b.asked ? -1 : 1));\n },\n missing(): readonly number[] {\n return [...missing].sort((a, b) => a - b);\n },\n };\n}\n","/**\n * `pptx-studio render` - slides to SVG, with no browser anywhere.\n *\n * Every part of the picture except text was already reachable from Node:\n * geometry, fills, strokes, effects and images are pure functions over the\n * model, and an image's size comes out of its own header rather than a decoder.\n * Text was the one thing that needed a canvas, and `measure.ts` is what replaces\n * it. ADR 0042.\n */\n\nimport { mkdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\nimport { PartStore } from '@pptx-studio/opc';\nimport { loadDocument } from '@pptx-studio/model';\nimport { renderSlide, type MediaResolver } from '@pptx-studio/render-svg';\n\nimport { RenderError } from './errors.js';\nimport { indexFonts, type FontLibrary } from './faces.js';\nimport { createFontMeasurer, type FaceUse } from './measure.js';\n\n/** What a slide is drawn at when the caller says nothing. PowerPoint's own. */\nexport const DEFAULT_WIDTH = 1920;\n\nexport interface RenderOptions {\n /** 1-based, or `null` for every slide. */\n readonly slide: number | null;\n readonly width: number;\n /** A directory for many slides, a file for one, or `null` for stdout. */\n readonly out: string | null;\n readonly fontDirs: readonly string[];\n readonly systemFonts: boolean;\n readonly text: boolean;\n readonly json: boolean;\n readonly quiet: boolean;\n}\n\nexport const RENDER_DEFAULTS = {\n width: DEFAULT_WIDTH,\n systemFonts: true,\n text: true,\n} as const;\n\nexport interface RenderedSlide {\n /** 1-based, as the flag and the file name spell it. */\n readonly number: number;\n readonly svg: string;\n}\n\nexport interface RenderResult {\n readonly slides: readonly RenderedSlide[];\n readonly width: number;\n readonly height: number;\n /** Every typeface the deck asked for, and what drew it. Empty with `--no-text`. */\n readonly fonts: readonly FaceUse[];\n /** Code points no indexed face could draw. */\n readonly missing: readonly number[];\n readonly fontDirectories: readonly string[];\n readonly facesIndexed: number;\n}\n\n/** The media resolver: an rId means the rels of the part the fill was written in. */\nfunction mediaFrom(store: PartStore): MediaResolver {\n return (embed: string, part: string) => {\n const target = store.relationships(part).targetOf(embed);\n if (target === undefined) return undefined;\n const contentType = store.contentTypeOf(target);\n if (contentType === undefined) return undefined;\n return { bytes: store.read(target), contentType };\n };\n}\n\n/**\n * Render a deck that is already in memory.\n *\n * Separate from `runRender` so that the whole pipeline is exercised by the test\n * suite without a file system, a process or a captured stdout.\n */\nexport function renderDeck(bytes: Uint8Array, options: RenderOptions): RenderResult {\n const store = PartStore.open(bytes);\n const document = loadDocument(store);\n const size = document.slideSize;\n const height = Math.round((options.width * size.cy) / size.cx);\n\n if (options.slide !== null) {\n const count = document.slides.length;\n if (!Number.isInteger(options.slide) || options.slide < 1 || options.slide > count) {\n throw new RenderError(\n 'CLI_NO_SLIDE',\n `--slide ${String(options.slide)}: the deck has ${String(count)} slide(s)`,\n String(options.slide),\n );\n }\n }\n\n let library: FontLibrary | null = null;\n const fonts = options.text\n ? createFontMeasurer(\n (library = indexFonts({ extra: options.fontDirs, system: options.systemFonts })),\n )\n : null;\n\n const media = mediaFrom(store);\n const wanted =\n options.slide === null\n ? document.slides.map((sheet, at) => ({ sheet, number: at + 1 }))\n : [{ sheet: document.slides[options.slide - 1]!, number: options.slide }];\n\n const slides = wanted.map(({ sheet, number }) => ({\n number,\n svg: renderSlide(sheet, size, {\n width: options.width,\n height,\n idPrefix: `s${String(number)}`,\n media,\n text:\n fonts === null\n ? false\n : {\n defaultTextStyle: document.defaultTextStyle,\n measurer: fonts.measurer,\n faceBox: fonts.faceBox,\n },\n }),\n }));\n\n return {\n slides,\n width: options.width,\n height,\n fonts: fonts?.used() ?? [],\n missing: fonts?.missing() ?? [],\n fontDirectories: library?.directories ?? [],\n facesIndexed: library?.indexed.length ?? 0,\n };\n}\n\n/** `slide-03.svg`, so a directory listing sorts the way the deck reads. */\nfunction fileNameFor(number: number, count: number): string {\n const width = Math.max(2, String(count).length);\n return `slide-${String(number).padStart(width, '0')}.svg`;\n}\n\nfunction isDirectory(path: string): boolean {\n try {\n return statSync(path).isDirectory();\n } catch {\n return false;\n }\n}\n\n/**\n * Where each slide's markup goes.\n *\n * One slide to a path that is not an existing directory writes that file;\n * anything else writes a file per slide into a directory, created if needed.\n * A path ending in `.svg` with more than one slide is a mistake worth naming\n * rather than silently turning into a directory.\n */\nfunction write(result: RenderResult, out: string): readonly string[] {\n const single = result.slides.length === 1 && !isDirectory(out);\n if (single) {\n const first = result.slides[0]!;\n mkdirSync(dirname(out), { recursive: true });\n writeFileSync(out, first.svg, 'utf8');\n return [out];\n }\n if (out.toLowerCase().endsWith('.svg')) {\n throw new RenderError(\n 'CLI_OUTPUT_PATH',\n `--out ${out} names one file but ${String(result.slides.length)} slides were rendered; ` +\n 'name a directory, or pass --slide',\n out,\n );\n }\n mkdirSync(out, { recursive: true });\n return result.slides.map((slide) => {\n const path = join(out, fileNameFor(slide.number, result.slides.length));\n writeFileSync(path, slide.svg, 'utf8');\n return path;\n });\n}\n\nfunction report(result: RenderResult, written: readonly string[], options: RenderOptions): string {\n const lines: string[] = [];\n const slides = `${String(result.slides.length)} slide(s) at ${String(result.width)}x${String(result.height)}`;\n lines.push(\n written.length === 0\n ? slides\n : `${slides} -> ${written.length === 1 ? written[0]! : `${String(written.length)} files`}`,\n );\n\n if (options.text) {\n const substituted = result.fonts.filter((font) => font.substituted);\n lines.push(\n `${String(result.fonts.length)} typeface(s) from ${String(result.facesIndexed)} indexed face(s)` +\n (substituted.length === 0 ? '' : `, ${String(substituted.length)} substituted`),\n );\n for (const font of substituted) lines.push(` ${font.asked} -> ${font.drawn}`);\n if (result.missing.length > 0) {\n const shown = result.missing\n .slice(0, 8)\n .map((code) => `U+${code.toString(16).toUpperCase().padStart(4, '0')}`);\n lines.push(\n ` no face has ${String(result.missing.length)} code point(s): ${shown.join(' ')}` +\n (result.missing.length > shown.length ? ' ...' : ''),\n );\n }\n }\n return `${lines.join('\\n')}\\n`;\n}\n\n/** Read, render, write, and return an exit code. Never calls `process.exit`. */\nexport function runRender(\n file: string,\n options: RenderOptions,\n out: (text: string) => void,\n): number {\n const result = renderDeck(new Uint8Array(readFileSync(file)), options);\n\n if (options.json) {\n const written = options.out === null ? [] : write(result, options.out);\n out(\n `${JSON.stringify(\n {\n slides: result.slides.map((slide) => ({\n number: slide.number,\n bytes: slide.svg.length,\n })),\n width: result.width,\n height: result.height,\n fonts: result.fonts,\n missing: result.missing.map((code) => `U+${code.toString(16).toUpperCase()}`),\n fontDirectories: result.fontDirectories,\n facesIndexed: result.facesIndexed,\n written,\n },\n null,\n 2,\n )}\\n`,\n );\n return 0;\n }\n\n if (options.out === null) {\n for (const slide of result.slides) out(slide.svg);\n return 0;\n }\n\n const written = write(result, options.out);\n if (!options.quiet) out(report(result, written, options));\n return 0;\n}\n","import { readFileSync, writeFileSync } from 'node:fs';\nimport { readZip } from '@pptx-studio/opc';\nimport {\n roundTripPackage,\n summarizeRoundTrip,\n type Difference,\n type RoundTripReport,\n} from '@pptx-studio/writer';\n\n/**\n * `pptx-studio roundtrip deck.pptx`\n *\n * Read a deck, write it back, and say whether it is still the same deck.\n *\n * ## What it is for\n *\n * It is the gate of Phase 1 with a command line on it. Sub-phase 1.6 runs it\n * over the corpus on every pull request, and the number it prints is the one\n * the README's badge carries - so this is a place where the output format is\n * part of the deliverable rather than decoration.\n *\n * It is also the first thing to reach for when a deck misbehaves. `validate`\n * answers \"is this package well formed\"; this answers \"did we change it\", which\n * is a different question and usually the one that matters, because the packages\n * that cause trouble are the ones that are well formed and subtly different.\n *\n * ## What \"the same deck\" means here\n *\n * Not the same bytes. The plan rules that out and `comparePackages` explains\n * why at length: entry order, deflate level, timestamps and attribute order all\n * differ legitimately between two archives holding one document. What is\n * compared is the canonical XML of every XML part, the relationship graph with\n * ids treated as opaque labels, and the SHA-256 of everything else.\n *\n * ## The exit code\n *\n * `0` when there is nothing to report. `1` when the two packages differ, and\n * `1` as well when the export was refused outright - the caller in CI wants one\n * bit, and both of those are \"this deck did not round-trip\". The two are still\n * distinguishable on stderr, because they need completely different work.\n */\n\nexport interface RoundTripOptions {\n /** Print the comparison as JSON instead of as text. */\n readonly json: boolean;\n /** Show only the differences, not the tally. */\n readonly quiet: boolean;\n /** Write the report to this path instead of stdout. */\n readonly out: string | null;\n /**\n * Also write the exported package here.\n *\n * The point of the flag is what happens next: open the file in PowerPoint.\n * Nothing in this repository can assert that a file opens clean, and until\n * 1.5 scripts it there is no substitute for doing it by hand.\n */\n readonly write: string | null;\n}\n\nexport const ROUNDTRIP_DEFAULTS: RoundTripOptions = {\n json: false,\n quiet: false,\n out: null,\n write: null,\n};\n\nexport interface RoundTripFileResult {\n readonly comparison: RoundTripReport;\n readonly output: string;\n readonly bytes: Uint8Array;\n readonly exitCode: number;\n}\n\nfunction describeDifference(difference: Difference): string {\n // Two lines, the second indented, because a `detail` may itself be three\n // lines when it carries an excerpt of the markup either side.\n const body = difference.detail\n .split('\\n')\n .map((line) => ' ' + line.trimEnd())\n .join('\\n');\n return ' ' + difference.kind.padEnd(13) + difference.part + '\\n' + body;\n}\n\n/** What the header line reports, gathered once so the formatter is testable on its own. */\nexport interface RoundTripStats {\n readonly bytesIn: number;\n readonly bytesOut: number;\n readonly entriesIn: number;\n readonly entriesOut: number;\n readonly rewritten: number;\n readonly streamed: number;\n}\n\n/**\n * Render a comparison.\n *\n * Exported so that the branch that lists differences can be tested, which it\n * otherwise could not be: no deck in the corpus makes this writer produce one,\n * which is the point of the whole phase and leaves the most important output in\n * the file as the only output nothing exercises.\n */\nexport function formatRoundTrip(\n path: string,\n stats: RoundTripStats,\n comparison: RoundTripReport,\n quiet: boolean,\n): string {\n const lines: string[] = [];\n if (!quiet) {\n lines.push(\n 'roundtrip ' + path,\n '',\n ' read ' + String(stats.bytesIn) + ' bytes, ' + String(stats.entriesIn) + ' entries',\n ' written ' + String(stats.bytesOut) + ' bytes, ' + String(stats.entriesOut) + ' entries',\n ' export ' +\n String(stats.rewritten) +\n ' part(s) re-serialized, ' +\n String(stats.streamed) +\n ' streamed',\n ' compared ' + summarizeRoundTrip(comparison),\n '',\n );\n }\n\n if (comparison.ok) {\n lines.push(' no differences');\n } else {\n lines.push(' ' + String(comparison.differences.length) + ' difference(s)', '');\n for (const difference of comparison.differences) lines.push(describeDifference(difference), '');\n }\n\n if (!quiet && comparison.relabelled.length > 0) {\n // Never true for a package this writer produced, so when it is true it is\n // worth a line: it means the file being compared came from somewhere else.\n lines.push(\n '',\n ' ' + String(comparison.relabelled.length) + ' relationship id(s) renamed:',\n ...comparison.relabelled\n .slice(0, 10)\n .map((entry) => ' ' + entry.source + ' ' + entry.from + ' -> ' + entry.to),\n );\n }\n\n return lines.join('\\n') + '\\n';\n}\n\nexport function roundTripFile(\n path: string,\n options: RoundTripOptions = ROUNDTRIP_DEFAULTS,\n): RoundTripFileResult {\n const original = new Uint8Array(readFileSync(path));\n const result = roundTripPackage(original);\n const { comparison } = result;\n\n const stats: RoundTripStats = {\n bytesIn: original.length,\n bytesOut: result.exported.bytes.length,\n entriesIn: readZip(original).entries.length,\n entriesOut: readZip(result.exported.bytes).entries.length,\n rewritten: result.exported.rewritten.length,\n streamed: result.exported.streamed,\n };\n\n const output = options.json\n ? JSON.stringify(\n {\n file: path,\n ok: comparison.ok,\n bytesIn: stats.bytesIn,\n bytesOut: stats.bytesOut,\n rewritten: result.exported.rewritten,\n counts: comparison.counts,\n relabelled: comparison.relabelled,\n differences: comparison.differences,\n parts: comparison.parts,\n },\n null,\n 2,\n ) + '\\n'\n : formatRoundTrip(path, stats, comparison, options.quiet);\n\n return {\n comparison,\n output,\n bytes: result.exported.bytes,\n exitCode: comparison.ok ? 0 : 1,\n };\n}\n\n/** Run `roundTripFile` and put the results where the options say. */\nexport function runRoundTrip(\n path: string,\n options: RoundTripOptions,\n write: (text: string) => void,\n): number {\n const result = roundTripFile(path, options);\n if (options.write !== null) writeFileSync(options.write, result.bytes);\n if (options.out === null) write(result.output);\n else writeFileSync(options.out, result.output);\n return result.exitCode;\n}\n","import { readFileSync, writeFileSync } from 'node:fs';\nimport { formatReport, validatePackage, type Report } from '@pptx-studio/validate';\n\n/**\n * `pptx-studio validate deck.pptx`\n *\n * The rules live in `@pptx-studio/validate`, which is a browser package with no\n * Node in it - the same split `inspect` has, for the same reason. What is left\n * here is reading a path, writing a stream, and choosing an exit code.\n *\n * ## What this command can and cannot ask\n *\n * Six of the twenty-nine rules compare a package against the package **as it\n * was opened**, and a file dropped on the command line has no such history.\n * They are skipped, and the report says so by name rather than counting them as\n * passes - `--quiet` still prints the skipped list, because a report that\n * silently means less than it looks like is the failure mode this package\n * exists to avoid.\n *\n * That makes `validate` a diagnostic rather than the export gate. The gate is\n * `assertValid`, called from the writer in sub-phase 1.3 with both packages in\n * hand; this is what you run against a file somebody sent you.\n *\n * ## The exit code\n *\n * `1` when anything fatal was found, `0` otherwise; a warning never fails the\n * command. With no baseline every fatal is treated as ours, which is the safe\n * direction when the question \"did we break this\" cannot be asked at all.\n */\n\nexport interface ValidateOptions {\n /** Print the report as JSON instead of as text. */\n readonly json: boolean;\n /** Append each rule's rationale, once per rule that fired. */\n readonly explain: boolean;\n /** Hide warnings; show only what would refuse an export. */\n readonly quiet: boolean;\n /** Write to this path instead of stdout. */\n readonly out: string | null;\n}\n\nexport const VALIDATE_DEFAULTS: ValidateOptions = {\n json: false,\n explain: false,\n quiet: false,\n out: null,\n};\n\nexport interface ValidateResult {\n readonly report: Report;\n readonly output: string;\n readonly exitCode: number;\n}\n\nexport function validateFile(\n path: string,\n options: ValidateOptions = VALIDATE_DEFAULTS,\n): ValidateResult {\n const bytes = new Uint8Array(readFileSync(path));\n const report = validatePackage({ bytes });\n\n const output = options.json\n ? JSON.stringify(report, null, 2) + '\\n'\n : formatReport(report, { explain: options.explain, warnings: !options.quiet }) + '\\n';\n\n const fatal = report.findings.filter((finding) => finding.severity === 'fatal').length;\n return { report, output, exitCode: fatal > 0 ? 1 : 0 };\n}\n\n/** Run `validateFile` and put the result where the options say. */\nexport function runValidate(\n path: string,\n options: ValidateOptions,\n write: (text: string) => void,\n): number {\n const result = validateFile(path, options);\n if (options.out === null) write(result.output);\n else writeFileSync(options.out, result.output);\n return result.exitCode;\n}\n","import { readFileSync } from 'node:fs';\nimport { parseArgs } from 'node:util';\nimport { isOpcError } from '@pptx-studio/opc';\nimport { isValidateError } from '@pptx-studio/validate';\nimport { BISECT_DEFAULTS, runBisect, type BisectOptions } from './bisect.js';\nimport { INSPECT_DEFAULTS, runInspect, type InspectOptions } from './inspect.js';\nimport { isRenderError } from './render/errors.js';\nimport { RENDER_DEFAULTS, runRender, type RenderOptions } from './render/render.js';\nimport { runRoundTrip, type RoundTripOptions } from './roundtrip.js';\nimport { runValidate, type ValidateOptions } from './validate.js';\n\n/**\n * The command line.\n *\n * `node:util.parseArgs` rather than a dependency. A CLI whose entire job in\n * sub-phase 0.8 is one verb with five flags does not need an argument library,\n * and the ones on offer would each be larger than everything this package\n * contains.\n *\n * The verbs the plan gives this package - `roundtrip`, `render`, `validate`,\n * `fidelity`, `resolve`, `bisect` - are listed in the help text with the phase\n * that brings them, and asking for one says so rather than \"unknown command\".\n * A tool that knows what it will be able to do is more useful than one that\n * pretends the request was nonsense.\n */\n\ninterface PlannedVerb {\n readonly name: string;\n readonly summary: string;\n readonly phase: string;\n}\n\nconst PLANNED: readonly PlannedVerb[] = [\n { name: 'resolve', summary: 'show where a resolved property came from', phase: '7.x' },\n];\n\nfunction version(): string {\n // Resolves the same from `dist/cli.js` and from `src/main.ts`, so a run\n // straight off the source reports the version the package will publish.\n const manifest: unknown = JSON.parse(\n readFileSync(new URL('../package.json', import.meta.url), 'utf8'),\n );\n return typeof manifest === 'object' && manifest !== null && 'version' in manifest\n ? String(manifest.version)\n : '0.0.0';\n}\n\nfunction usage(): string {\n const planned = PLANNED.map(\n (verb) => ' ' + verb.name.padEnd(12) + verb.summary + ' (sub-phase ' + verb.phase + ')',\n ).join('\\n');\n return [\n 'pptx-studio ' + version(),\n '',\n 'Usage: pptx-studio <command> <deck.pptx> [options]',\n '',\n 'Commands:',\n ' inspect what is inside a package: parts, relationships, features',\n ' validate the must-not-break rules, with the part and the XPath',\n ' roundtrip read a deck, write it back, and prove nothing moved',\n ' render draw slides as SVG, with no browser and no LibreOffice',\n ' bisect narrow a broken deck to the change that breaks it',\n '',\n 'render options:',\n ' --slide <n> one slide, 1-based; every slide by default',\n ' --width <px> the SVG width attribute; the height follows the aspect',\n ' --out <path> a directory, or a file when rendering one slide',\n ' --font-dir <d> look for fonts here first; repeatable',\n ' --no-system-fonts do not look in this platform own font directories',\n ' --no-text draw geometry only, and ask no font questions',\n ' --json what was drawn, and which face drew each typeface',\n ' --quiet no summary after writing',\n '',\n ' With no --out the markup goes to stdout, which is one slide worth doing.',\n '',\n 'inspect options:',\n ' --json the census as JSON, for a script or for committing as a fixture',\n ' --parts include the per-part table',\n ' --namespaces include the namespace histogram',\n ' --top <n> rows per histogram before truncating (default 15)',\n ' --out <file> write to a file instead of stdout',\n '',\n 'validate options:',\n ' --json the report as JSON',\n ' --explain append the rationale for every rule that fired',\n ' --quiet fatal findings only',\n ' --out <file> write to a file instead of stdout',\n '',\n 'roundtrip options:',\n ' --json the comparison as JSON, with a digest per part',\n ' --quiet the differences only, without the tally',\n ' --write <file> also save the package that was written, to open in PowerPoint',\n ' --out <file> write the report to a file instead of stdout',\n '',\n 'bisect <deck.pptx> [broken.pptx]',\n ' With one deck the broken package is our own export of it; with two they',\n ' are the original and the broken package, in that order.',\n '',\n ' --oracle <name> validate (default), powerpoint, or command',\n ' --command <cmd> for --oracle command; {} becomes the candidate path',\n ' --max-runs <n> ceiling on oracle runs (default 2000)',\n ' --timeout <ms> per run, for the oracles that spawn something',\n ' --progress a line per oracle run; a bisection is not quick',\n ' --write <file> save the smallest package that still fails',\n ' --json the result as JSON',\n ' --quiet the changes that matter, without the tally',\n ' --out <file> write the report to a file instead of stdout',\n '',\n ' -h, --help this text',\n ' -v, --version print the version',\n '',\n 'Exit status is 1 when inspect finds a structural error, when validate finds',\n 'anything fatal, when roundtrip finds a difference, when render cannot draw,',\n 'or when bisect localizes one. 0 otherwise. Warnings and notes never fail a',\n 'command.',\n '',\n 'Not built yet:',\n planned,\n '',\n ].join('\\n');\n}\n\nexport interface Streams {\n readonly out: (text: string) => void;\n readonly err: (text: string) => void;\n}\n\nconst CONSOLE_STREAMS: Streams = {\n out: (text) => process.stdout.write(text),\n err: (text) => process.stderr.write(text),\n};\n\n/** Parse, dispatch, and return an exit code. Never calls `process.exit`. */\nexport function main(argv: readonly string[], streams: Streams = CONSOLE_STREAMS): number {\n let parsed;\n try {\n parsed = parseArgs({\n args: [...argv],\n allowPositionals: true,\n // `--no-text` and `--no-system-fonts` are the only way to spell turning a\n // defaulted-true flag off, and parseArgs rejects them without this.\n allowNegative: true,\n options: {\n json: { type: 'boolean', default: false },\n explain: { type: 'boolean', default: false },\n quiet: { type: 'boolean', default: false },\n parts: { type: 'boolean', default: false },\n namespaces: { type: 'boolean', default: false },\n top: { type: 'string' },\n out: { type: 'string' },\n write: { type: 'string' },\n oracle: { type: 'string' },\n slide: { type: 'string' },\n width: { type: 'string' },\n 'font-dir': { type: 'string', multiple: true },\n 'system-fonts': { type: 'boolean', default: true },\n text: { type: 'boolean', default: true },\n command: { type: 'string' },\n 'max-runs': { type: 'string' },\n timeout: { type: 'string' },\n progress: { type: 'boolean', default: false },\n help: { type: 'boolean', short: 'h', default: false },\n version: { type: 'boolean', short: 'v', default: false },\n },\n });\n } catch (error) {\n streams.err(describe(error) + '\\n\\n' + usage());\n return 2;\n }\n\n const { values, positionals } = parsed;\n if (values.version === true) {\n streams.out(version() + '\\n');\n return 0;\n }\n const command = positionals[0];\n if (values.help === true || command === undefined) {\n streams.out(usage());\n return command === undefined && values.help !== true ? 2 : 0;\n }\n\n const planned = PLANNED.find((verb) => verb.name === command);\n if (planned !== undefined) {\n streams.err(\n 'pptx-studio ' +\n command +\n ' is not built yet: it arrives with sub-phase ' +\n planned.phase +\n '.\\n' +\n ' ' +\n planned.summary +\n '\\n',\n );\n return 2;\n }\n\n if (\n command !== 'inspect' &&\n command !== 'validate' &&\n command !== 'roundtrip' &&\n command !== 'render' &&\n command !== 'bisect'\n ) {\n streams.err('unknown command: ' + command + '\\n\\n' + usage());\n return 2;\n }\n\n const file = positionals[1];\n if (file === undefined) {\n streams.err(command + ' needs a path to a .pptx\\n\\n' + usage());\n return 2;\n }\n\n if (command === 'bisect') {\n const oracle = values.oracle ?? BISECT_DEFAULTS.oracle;\n if (oracle !== 'validate' && oracle !== 'powerpoint' && oracle !== 'command') {\n streams.err('--oracle wants validate, powerpoint or command, got ' + oracle + '\\n');\n return 2;\n }\n if (oracle === 'command' && values.command === undefined) {\n streams.err('--oracle command needs --command \"<what to run>\"\\n');\n return 2;\n }\n const maxRuns = positiveInteger(values['max-runs'], BISECT_DEFAULTS.maxRuns);\n const timeout = positiveInteger(values.timeout, BISECT_DEFAULTS.timeout);\n if (maxRuns === null || timeout === null) {\n streams.err('--max-runs and --timeout want positive integers\\n');\n return 2;\n }\n\n const options: BisectOptions = {\n oracle,\n command: values.command ?? null,\n maxRuns,\n timeout,\n write: values.write ?? null,\n json: values.json === true,\n quiet: values.quiet === true,\n out: values.out ?? null,\n progress: values.progress === true,\n };\n try {\n return runBisect(file, positionals[2] ?? null, options, streams.out);\n } catch (error) {\n // The same three-way split `roundtrip` makes, plus one of its own: the\n // one-deck form exports the deck first, so the firewall can refuse before\n // there is anything to bisect.\n if (isValidateError(error)) {\n streams.err(\n 'pptx-studio: the export was refused, so there was no broken package to compare.\\n ' +\n error.message +\n '\\n',\n );\n } else if (isOpcError(error)) {\n streams.err('pptx-studio: ' + error.code + ': ' + error.message + '\\n');\n } else {\n streams.err('pptx-studio: ' + describe(error) + '\\n');\n }\n return 1;\n }\n }\n\n if (command === 'render') {\n const slide = values.slide === undefined ? null : Number.parseInt(values.slide, 10);\n if (slide !== null && (!Number.isFinite(slide) || slide < 1)) {\n streams.err('--slide wants a positive integer, got ' + String(values.slide) + '\\n');\n return 2;\n }\n const width = positiveInteger(values.width, RENDER_DEFAULTS.width);\n if (width === null) {\n streams.err('--width wants a positive integer, got ' + String(values.width) + '\\n');\n return 2;\n }\n const options: RenderOptions = {\n slide,\n width,\n out: values.out ?? null,\n fontDirs: values['font-dir'] ?? [],\n systemFonts: values['system-fonts'] !== false,\n text: values.text !== false,\n json: values.json === true,\n quiet: values.quiet === true,\n };\n try {\n return runRender(file, options, streams.out);\n } catch (error) {\n // A render fails for a reason the caller can act on - a font directory\n // that is not there, a typeface nothing can stand in for, a slide the\n // deck does not have - so the code is worth more than the sentence.\n if (isRenderError(error)) {\n streams.err('pptx-studio: ' + error.code + ': ' + error.message + '\\n');\n } else if (isOpcError(error)) {\n streams.err('pptx-studio: ' + error.code + ': ' + error.message + '\\n');\n } else {\n streams.err('pptx-studio: ' + describe(error) + '\\n');\n }\n return 1;\n }\n }\n\n if (command === 'roundtrip') {\n const options: RoundTripOptions = {\n json: values.json === true,\n quiet: values.quiet === true,\n out: values.out ?? null,\n write: values.write ?? null,\n };\n try {\n return runRoundTrip(file, options, streams.out);\n } catch (error) {\n // Three ways to fail and one exit code, so the message has to carry the\n // distinction. A package that will not open, an export the firewall\n // refused, and a comparison that found a difference are three different\n // pieces of work; a script only wants one bit, and a person wants to know\n // which of the three it was.\n if (isValidateError(error)) {\n streams.err(\n 'pptx-studio: the export was refused, so there was nothing to compare.\\n ' +\n error.message +\n '\\n',\n );\n } else if (isOpcError(error)) {\n streams.err('pptx-studio: ' + error.code + ': ' + error.message + '\\n');\n } else {\n streams.err('pptx-studio: ' + describe(error) + '\\n');\n }\n return 1;\n }\n }\n\n if (command === 'validate') {\n const options: ValidateOptions = {\n json: values.json === true,\n explain: values.explain === true,\n quiet: values.quiet === true,\n out: values.out ?? null,\n };\n try {\n return runValidate(file, options, streams.out);\n } catch (error) {\n // The same split `inspect` makes below. A package that will not open at\n // all is a different answer from a package with findings in it, and only\n // the second one is a report.\n if (isOpcError(error)) {\n streams.err('pptx-studio: ' + error.code + ': ' + error.message + '\\n');\n return 1;\n }\n streams.err('pptx-studio: ' + describe(error) + '\\n');\n return 1;\n }\n }\n\n const top = values.top === undefined ? INSPECT_DEFAULTS.top : Number.parseInt(values.top, 10);\n if (!Number.isFinite(top) || top < 1) {\n streams.err('--top wants a positive integer, got ' + String(values.top) + '\\n');\n return 2;\n }\n\n const options: InspectOptions = {\n json: values.json === true,\n parts: values.parts === true,\n namespaces: values.namespaces === true,\n top,\n out: values.out ?? null,\n };\n\n try {\n return runInspect(file, options, streams.out);\n } catch (error) {\n // A census does not refuse a broken deck; it refuses a file that is not a\n // package at all, and `opc` says which rule it broke. Reporting the code\n // matters more than the sentence - it is what a script can branch on.\n if (isOpcError(error)) {\n streams.err('pptx-studio: ' + error.code + ': ' + error.message + '\\n');\n return 1;\n }\n streams.err('pptx-studio: ' + describe(error) + '\\n');\n return 1;\n }\n}\n\nfunction describe(error: unknown): string {\n return error instanceof Error ? error.message : String(error);\n}\n\n/** A numeric flag, or null when what was given is not one. */\nfunction positiveInteger(value: string | undefined, fallback: number): number | null {\n if (value === undefined) return fallback;\n const parsed = Number.parseInt(value, 10);\n return Number.isFinite(parsed) && parsed > 0 ? parsed : null;\n}\n"],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pptx-studio/cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Command line tools for PPTX packages: render slides to SVG without a browser, inspect a deck, validate it, prove a round trip, and bisect the change that breaks it",
|
|
5
|
+
"license": "Apache-2.0",
|
|
6
|
+
"type": "module",
|
|
7
|
+
"sideEffects": false,
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/Dewiride-Open-Source/Dewiride-PPTX-Studio.git",
|
|
11
|
+
"directory": "packages/cli"
|
|
12
|
+
},
|
|
13
|
+
"homepage": "https://github.com/Dewiride-Open-Source/Dewiride-PPTX-Studio/tree/main/packages/cli#readme",
|
|
14
|
+
"bugs": "https://github.com/Dewiride-Open-Source/Dewiride-PPTX-Studio/issues",
|
|
15
|
+
"keywords": [
|
|
16
|
+
"pptx",
|
|
17
|
+
"ooxml",
|
|
18
|
+
"cli",
|
|
19
|
+
"inspect",
|
|
20
|
+
"validate",
|
|
21
|
+
"roundtrip",
|
|
22
|
+
"bisect",
|
|
23
|
+
"delta-debugging",
|
|
24
|
+
"powerpoint",
|
|
25
|
+
"render",
|
|
26
|
+
"svg",
|
|
27
|
+
"thumbnail"
|
|
28
|
+
],
|
|
29
|
+
"engines": {
|
|
30
|
+
"node": ">=24.11.0"
|
|
31
|
+
},
|
|
32
|
+
"publishConfig": {
|
|
33
|
+
"access": "public"
|
|
34
|
+
},
|
|
35
|
+
"files": [
|
|
36
|
+
"dist",
|
|
37
|
+
"scripts",
|
|
38
|
+
"README.md",
|
|
39
|
+
"CHANGELOG.md",
|
|
40
|
+
"LICENSE",
|
|
41
|
+
"NOTICE"
|
|
42
|
+
],
|
|
43
|
+
"bin": {
|
|
44
|
+
"pptx-studio": "./dist/cli.js"
|
|
45
|
+
},
|
|
46
|
+
"exports": {
|
|
47
|
+
".": {
|
|
48
|
+
"types": "./dist/index.d.ts",
|
|
49
|
+
"default": "./dist/index.js"
|
|
50
|
+
},
|
|
51
|
+
"./package.json": "./package.json"
|
|
52
|
+
},
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@pptx-studio/census": "^0.1.0",
|
|
55
|
+
"@pptx-studio/opc": "^0.1.0",
|
|
56
|
+
"@pptx-studio/model": "^0.1.0",
|
|
57
|
+
"@pptx-studio/text": "^0.1.0",
|
|
58
|
+
"@pptx-studio/render-svg": "^0.1.0",
|
|
59
|
+
"@pptx-studio/validate": "^0.1.0",
|
|
60
|
+
"@pptx-studio/writer": "^0.1.0",
|
|
61
|
+
"@pptx-studio/xml": "^0.1.0"
|
|
62
|
+
},
|
|
63
|
+
"devDependencies": {
|
|
64
|
+
"@arethetypeswrong/cli": "0.18.5",
|
|
65
|
+
"publint": "0.3.24",
|
|
66
|
+
"tsdown": "0.22.14",
|
|
67
|
+
"typescript": "6.0.3"
|
|
68
|
+
},
|
|
69
|
+
"scripts": {
|
|
70
|
+
"build": "tsdown",
|
|
71
|
+
"pkg:qa": "publint --strict && attw --pack . --profile esm-only"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# Ask the real PowerPoint whether it will open one file, and say so in an exit code.
|
|
2
|
+
#
|
|
3
|
+
# powershell -File packages/cli/scripts/powerpoint-oracle.ps1 -File <path>
|
|
4
|
+
#
|
|
5
|
+
# exit 0 opened, with no repair
|
|
6
|
+
# exit 1 PowerPoint would not open it as it stands
|
|
7
|
+
# exit 2 the harness itself failed - PowerPoint missing, COM refused, a crash
|
|
8
|
+
#
|
|
9
|
+
# This is the oracle behind `pptx-studio bisect --oracle powerpoint`. The third
|
|
10
|
+
# exit code is not decoration: a bisector that read "the harness is broken" as
|
|
11
|
+
# "the file is broken" would blame whichever change happened to be applied when
|
|
12
|
+
# PowerPoint fell over, and do it with a straight face.
|
|
13
|
+
#
|
|
14
|
+
# ## Why Open2007 and not Open
|
|
15
|
+
#
|
|
16
|
+
# `Presentations.Open` has no repair parameter. `Presentations.Open2007` does -
|
|
17
|
+
# `OpenAndRepair`, documented as **defaulting to msoTrue** - and there is no
|
|
18
|
+
# documented way to make `Open` behave differently, so a file PowerPoint would
|
|
19
|
+
# repair opens through `Open` *successfully, already repaired*, and reports
|
|
20
|
+
# `opened = $true`.
|
|
21
|
+
#
|
|
22
|
+
# That is the whole difficulty of this project stated in one API. The prompt a
|
|
23
|
+
# user sees - "PowerPoint found a problem with content" - is a repair, and under
|
|
24
|
+
# automation the repair is silent: `DisplayAlerts = ppAlertsNone` is documented
|
|
25
|
+
# to choose the message box's default answer and carry on, and the default
|
|
26
|
+
# answer to that box is Repair. So the obvious script reports success on exactly
|
|
27
|
+
# the files this project exists to avoid producing.
|
|
28
|
+
#
|
|
29
|
+
# Passing `OpenAndRepair:=msoFalse` is what turns the silent repair back into a
|
|
30
|
+
# catchable error. `-AllowRepair` flips it, which is how the two are told apart:
|
|
31
|
+
# a file that fails without repair and opens with it is one PowerPoint *repairs*;
|
|
32
|
+
# a file that fails both ways is one it *refuses*.
|
|
33
|
+
#
|
|
34
|
+
# ## What it will not do to your machine
|
|
35
|
+
#
|
|
36
|
+
# - Read-only, no window, and nothing is ever written back.
|
|
37
|
+
# - `AutomationSecurity = msoAutomationSecurityForceDisable`, so a macro in a
|
|
38
|
+
# `.pptm` cannot run. The property defaults to *Low*, which enables all
|
|
39
|
+
# macros, and this script's whole job is opening files that are wrong on
|
|
40
|
+
# purpose.
|
|
41
|
+
# - If PowerPoint is already running it attaches to that instance and never
|
|
42
|
+
# quits it, because it is yours. It only quits one it started itself, and
|
|
43
|
+
# only when asked with -Quit.
|
|
44
|
+
#
|
|
45
|
+
# Reads the one file it is given and nothing else.
|
|
46
|
+
|
|
47
|
+
param(
|
|
48
|
+
[Parameter(Mandatory = $true, ParameterSetName = 'Test')][string]$File,
|
|
49
|
+
[Parameter(ParameterSetName = 'Test')][switch]$AllowRepair,
|
|
50
|
+
[Parameter(ParameterSetName = 'Test')][switch]$Quit,
|
|
51
|
+
[Parameter(Mandatory = $true, ParameterSetName = 'Shutdown')][switch]$QuitOnly
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
$ErrorActionPreference = 'Stop'
|
|
55
|
+
|
|
56
|
+
# msoFalse is 0 and msoTrue is -1. Spelled out because -1 as a literal in an
|
|
57
|
+
# argument list is the kind of thing that gets "tidied" into $true, and $true
|
|
58
|
+
# marshals to the same value only by luck of the VARIANT_BOOL representation.
|
|
59
|
+
$msoTrue = -1
|
|
60
|
+
$msoFalse = 0
|
|
61
|
+
$ppAlertsNone = 1
|
|
62
|
+
$msoAutomationSecurityForceDisable = 3
|
|
63
|
+
|
|
64
|
+
function Get-PowerPoint {
|
|
65
|
+
# Attach to a running instance if there is one. Starting a second PowerPoint
|
|
66
|
+
# per oracle run costs about three seconds each, and a bisection is dozens
|
|
67
|
+
# of runs; more to the point, quitting one we did not start would close
|
|
68
|
+
# whatever the person at this machine had open.
|
|
69
|
+
$created = $false
|
|
70
|
+
$app = $null
|
|
71
|
+
try {
|
|
72
|
+
$app = [Runtime.InteropServices.Marshal]::GetActiveObject('PowerPoint.Application')
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
$app = New-Object -ComObject PowerPoint.Application
|
|
76
|
+
$created = $true
|
|
77
|
+
}
|
|
78
|
+
return @{ app = $app; created = $created }
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if ($PSCmdlet.ParameterSetName -eq 'Shutdown') {
|
|
82
|
+
try {
|
|
83
|
+
$app = [Runtime.InteropServices.Marshal]::GetActiveObject('PowerPoint.Application')
|
|
84
|
+
$app.Quit()
|
|
85
|
+
[System.Runtime.InteropServices.Marshal]::ReleaseComObject($app) | Out-Null
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
# Nothing running is the outcome we wanted anyway.
|
|
89
|
+
}
|
|
90
|
+
exit 0
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (-not (Test-Path -LiteralPath $File -PathType Leaf)) {
|
|
94
|
+
[pscustomobject]@{ ok = $false; harness = $true; error = "no such file: $File" } |
|
|
95
|
+
ConvertTo-Json -Compress
|
|
96
|
+
exit 2
|
|
97
|
+
}
|
|
98
|
+
$path = (Resolve-Path -LiteralPath $File).Path
|
|
99
|
+
|
|
100
|
+
$handle = $null
|
|
101
|
+
try {
|
|
102
|
+
$handle = Get-PowerPoint
|
|
103
|
+
}
|
|
104
|
+
catch {
|
|
105
|
+
[pscustomobject]@{ ok = $false; harness = $true; error = $_.Exception.Message } |
|
|
106
|
+
ConvertTo-Json -Compress
|
|
107
|
+
exit 2
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
$app = $handle.app
|
|
111
|
+
$result = [ordered]@{
|
|
112
|
+
file = $path
|
|
113
|
+
ok = $false
|
|
114
|
+
harness = $false
|
|
115
|
+
repair = [bool]$AllowRepair
|
|
116
|
+
slides = 0
|
|
117
|
+
shapes = @()
|
|
118
|
+
error = $null
|
|
119
|
+
}
|
|
120
|
+
$code = 1
|
|
121
|
+
|
|
122
|
+
try {
|
|
123
|
+
$app.DisplayAlerts = $ppAlertsNone
|
|
124
|
+
$app.AutomationSecurity = $msoAutomationSecurityForceDisable
|
|
125
|
+
|
|
126
|
+
$repair = if ($AllowRepair) { $msoTrue } else { $msoFalse }
|
|
127
|
+
$pres = $null
|
|
128
|
+
try {
|
|
129
|
+
# FileName, ReadOnly, Untitled, WithWindow, OpenAndRepair
|
|
130
|
+
$pres = $app.Presentations.Open2007($path, $msoTrue, $msoFalse, $msoFalse, $repair)
|
|
131
|
+
$result.ok = $true
|
|
132
|
+
$result.slides = $pres.Slides.Count
|
|
133
|
+
# Shape counts per slide, because a repair is not the only way to lose
|
|
134
|
+
# content: PowerPoint will also open a file happily and drop a shape it
|
|
135
|
+
# did not like. Comparing these against what the writer put in is the
|
|
136
|
+
# only way to see that.
|
|
137
|
+
$counts = @()
|
|
138
|
+
foreach ($slide in $pres.Slides) { $counts += $slide.Shapes.Count }
|
|
139
|
+
$result.shapes = $counts
|
|
140
|
+
$code = 0
|
|
141
|
+
}
|
|
142
|
+
catch {
|
|
143
|
+
# The one sentence PowerPoint will ever say about it.
|
|
144
|
+
$result.error = $_.Exception.Message
|
|
145
|
+
$code = 1
|
|
146
|
+
}
|
|
147
|
+
finally {
|
|
148
|
+
if ($null -ne $pres) { try { $pres.Close() } catch {} }
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
$result.harness = $true
|
|
153
|
+
$result.error = $_.Exception.Message
|
|
154
|
+
$code = 2
|
|
155
|
+
}
|
|
156
|
+
finally {
|
|
157
|
+
if ($handle.created -and $Quit) { try { $app.Quit() } catch {} }
|
|
158
|
+
try { [System.Runtime.InteropServices.Marshal]::ReleaseComObject($app) | Out-Null } catch {}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
[pscustomobject]$result | ConvertTo-Json -Depth 4 -Compress
|
|
162
|
+
exit $code
|