Skip to content

@openpresentation/opf-render 0.18.1

Choose a tag to compare

@github-actions github-actions released this 11 Oct 04:49
· 48 commits to main since this release
d425933
  • render#171 (tooling and docs only, no change to the package): npm run bench:soak (scripts/soak.mjs) is a repeatable sustained-load benchmark for long-lived render and export workers. Each worker process holds one fonts handle (loadFonts with the office pack and scripts: 'auto'), blocks the network and repeats ensure, toSvg, toPng and, when @openpresentation/opf-pptx is installed, toPptx over core's example decks and a script-corpora deck, at concurrency 1 and 4 for 500 or more jobs per worker. It samples RSS and retained heap after a forced GC, reports p50 and p95 latency per operation, tests the steady-state window for monotonic memory growth (Mann-Kendall plus Theil-Sen, criterion in the script header), and compares the SVG, PNG and PPTX hashes of every repeat of a deck; the JSON report and Markdown summary go to the git-ignored artifacts/soak/. It is not part of npm test. The README section "Long-lived workers: measured memory and latency" publishes the first measurement with its hardware and Node version.
  • render#123: the vector PDF writer moves to pako 3.0.2 (from 1.0.11), whose exports are named only (import { deflate } from "pako"). Output is byte-identical: flate passes legacyHash: true, so pako 3 keeps writing canonical zlib streams as pako 1 did (its new default, the hash of Chromium zlib, writes about 1% larger streams with the same inflated content and would change every PDF's bytes and /ID). The /export-browser bundle tree-shakes pako's inflate side away: 20 KB smaller minified (6 KB gzipped).
  • render#191: dependency refresh. Upgrade the font packages with the renderer: install @expo-google-fonts/noto-sans-jp, -kr, -sc and -tc 0.4.4, noto-color-emoji 0.4.7 and noto-emoji 0.4.8 in the same step. They are exact-pinned optional peers: npm with an exact older pin refuses the upgrade (ERESOLVE), and pnpm or npm --legacy-peer-deps installs it with a peer warning, after which loadFonts rejects the old files with font-integrity-mismatch. With npm, a caret pin (^0.4.3) moves with the renderer; other package managers can keep the locked older version, so bump the six packages explicitly (-sc, -tc and noto-emoji change only metadata, but their exact peer pins moved too). The script and emoji font pins move to @expo-google-fonts/noto-sans-jp, -kr, -sc and -tc 0.4.4, noto-color-emoji 0.4.7 and noto-emoji 0.4.8 (optional peers; src/font-manifest.js re-pinned, licenses unchanged and verified OFL-1.1). The Noto Sans JP and KR faces are rebuilt files of the same 2.004 release with the same outlines and advances and renumbered glyph ids, so over the core examples and the script corpora SVG with embedded fonts and the raster goldens are unchanged; text-as-paths glyph ids (opf-g-<face hash>-<glyph id>) and the vector PDF's six-letter subset tags change for Japanese and Korean text. Noto Color Emoji moves from 2.051 to 2.057 (nine new emoji, redrawn glyphs), so a slide that embeds or exports it changes bytes. <opf-deck> thumbnails: the slide drawing is no longer inside the thumbnail button but under it, so the button's name ("Go to Slide 2 of 5: Agenda") is not contradicted by the slide's own text (WCAG 2.5.3, axe-core 4.14 label-content-name-mismatch); the strip looks and behaves the same (a host that paints a background on ::part(thumbnail) now paints it over the drawing). harfbuzzjs 1.6.3 (HarfBuzz 14.6.0): shaping is unchanged (text-as-paths byte-identical over the examples and script corpora); hb-subset writes some cmap subtables differently, so 70 of the 807 example SVGs with embedded subsets change by a few bytes (same glyph mapping, same pixels). Development dependencies: axe-core 4.14.0, @napi-rs/canvas 1.0.10, pdfjs-dist 6.4.299.
  • opf#364 (docs): the README now says to pass the same fonts handle (from loadFonts()) to preview, validation, pagination and export so they agree with the drawn faces, and that a call without it uses core's built-in estimate (0.54 em per character, 0.62 em for capitals and digits, 0.32 em for a space, 1 em for CJK, zero for combining marks), which is too narrow for some scripts (opf#566).
  • RR-20 (release workflow only): publishing opf-render to npm now creates its GitHub Release. A release job in npm-publish.yml runs after the npm publish on a tag push, takes the notes from the version's ## X.Y.Z section of CHANGELOG.md (the job fails if the section is missing), and creates the release opf-render-vX.Y.Z titled @openpresentation/opf-render X.Y.Z, marked as the latest release. Only that job holds contents: write; the publish job keeps id-token: write. GitHub Releases start at 0.18; older versions get none.
  • opf-render#125 (output-changing only where earlier measurement had left a stale glyph): a text measurement no longer depends on what the same process measured or drew before. fontkit caches one glyph object per glyph id with the code points of the call that created it, and its layout read a character's code points back from that cache. Bounding the outline of Bengali ra (U+09B0, a composite drawn with the nukta glyph as a component) cached the nukta (U+09BC) with no code points, so every later Bengali run with a nukta was shaped as a broken cluster with a dotted circle: আয় বেড়েছে কিন্তু খরচ অপরিবর্তিত in Noto Sans Bengali at 25 px measured 380.4 or 367.65 px instead of 354.9 (the width HarfBuzz gives, and a fresh process's), and the font-switch matrix drew deck pairwise-50 differently by run order. The registry's faces now give every glyph request exactly the code points it asked for, as the PDF writer's faces already did, so a measurement and the SVG drawn from it are a function of the document and the registry alone. A fresh process measured the right width before and after; only renders that followed an affected outline change.
  • opf-render#188 (output-changing for complex-script and combining-mark PDFs only): the vector PDF is shaped by HarfBuzz, the browser's shaper, as text: "paths" outlines are (RR-64 phase 2), instead of fontkit: with a fonts handle that carries the shaper (the Node loadFonts handle always, a browser handle given shapeWasm; exposed as fonts.outlines.shape) and, in Node without a handle, with the bundled one the conversion loads. Over the 30 script corpora every glyph the PDF shows now has HarfBuzz's id and position, where fontkit drew other glyphs on 10 samples (Bengali conjuncts, Thai and Lao tone marks, Khmer coeng, Myanmar medials, Syriac marks, Japanese combining sound marks) and put Telugu conjuncts 0.8 px off at 25 px. The PDF still draws the SVG's lines and pinned widths; the layout's measurement stays fontkit. Each character stays with the glyph it became (HarfBuzz cluster level 2), so the glyph-to-Unicode map and /ActualText spans read back as before. The writer also draws a glyph's small horizontal GPOS placement (up to 0.025 em, it was dropped: Arabic, Hebrew, Devanagari, Tibetan and combining Latin and Cyrillic marks up to 0.6 px off at 25 px) and continues the line after a glyph it shows alone with that glyph's shaped advance (a zero-width joiner after a mark put the rest of a run its nominal width off). Latin text without combining marks is byte-identical.
  • opf-render#189 (output-changing for PNG and raster PDF of Indic, Thai, Lao, Khmer and Myanmar text only): the raster path pins each complex-script cluster at the fontkit pen of its first character in the run shaped as a whole, not at the advance of the run's prefix shaped alone, which ended without the context of the next character. A Thai leading vowel kerned to its consonant (ไ ด, เ ต) put the consonant with its tone and vowel marks 0.25 px right of the browser's place at 25 px (รายได้เติบโตในทุกภูมิภาค at 60 px: 1,329 channel values off by more than 64 from HarfBuzz's outlines drawn by the same resvg, none now; against Chromium 1,554, now 210, the anti-aliasing difference HarfBuzz's own outlines show too). The script corpora's thai-digits, laoo-words, taml-pulli and telu-zwnj now draw exactly as HarfBuzz's outlines; the approved raster goldens do not change. The raster path's own fontkit faces also pin each glyph's code points (pinGlyphCodePoints, opf-render#125): after an outlined Devanagari letter with a nukta component (ऱ, क़), a later decomposed nukta no longer shapes with a dotted circle.
  • render#199: a Next.js app that imports @openpresentation/opf-render and /fonts-node builds with Turbopack and no next.config. Since RR-64 and RR-65, next build failed because Turbopack compiled harfbuzzjs/dist/harfbuzz.wasm (located with require.resolve) as a WebAssembly module; and in a server bundle, the font packages (require.resolve of a computed name, which a bundler turns into a stub that always throws) and the vendored fonts/ directory (new URL with a computed path) were not found. The Node entries (/fonts-node, /preview-fonts-node) now locate these files at run time through Node's own resolution, in a form bundlers leave alone, so a Next.js server (Turbopack or webpack, route handler or server component) renders the same bytes as plain Node: subset faces, HarfBuzz-shaped outlines and the office pack. Plain Node output is unchanged. loadFonts now reports harfbuzz-unavailable through onDiagnostic when a harfbuzzjs .wasm cannot be loaded (a deployment that did not ship the package) and still loads the fonts, with whole faces and fontkit shaping; a missing file used to reject loadFonts. npm run test:next-bundle builds and serves such an app against the packed package.
  • render#220: a symbol-encoded family (Wingdings, Symbol, Webdings) named as a font-scheme role also maps its codes in list bodies (text: [{ text }] or text: ["..."]), under heading/body and major/minor roles, as #207 made it do for string bodies. Core composes each list paragraph from its block's resolved style, and resolving that open symbol face again dropped the encoding, so toSvg threw missing-glyph. resolveStyle now keeps an encoding whose own preview face was resolved.
  • Tests only: test/script-corpora.mjs (the FF-44 per-face shaping qualification against HarfBuzz) runs in npm test. It was excluded from the runner (test/suites.json) and no CI step ran it, so it went unnoticed that it failed after FF-45 added the emoji and math slots (Zsye, Zmth), which are symbol sets with no language corpus; they are now exempt by name. It stays out of the contract suite.
  • A symbol-encoded family (Wingdings, Symbol, Webdings) named as a font-scheme role (heading, body, code or accent) now keeps its name when a slide is bound, so the script-font planner maps the private-use codes (U+F0FC and the like) through the symbol table and draws the open symbol glyphs, as it does for a symbol run; toSvg with scripts: 'auto' used to throw missing-glyph because the role resolved to the open symbol face and lost the encoding (opf-pptx#169). The emoji fallback now has a second designated face: Zsye names Noto Emoji (monochrome) after Noto Color Emoji, so a host with only @expo-google-fonts/noto-emoji installed previews and exports emoji instead of throwing missing-glyph; the colour face stays first where it is loaded, and designatedFamilies('Zsye') and the glyph fallback chain list both. Output for decks without a symbol-encoded role or an emoji is unchanged.
  • RR-20: CI uses the same pinned npm 11.16.0 as trusted publishing on Node 22, 24 and 26, so strict locked sibling installs complete before coordinated source and font linking. The unpublished 0.18.1 preparation is reset so the release can be prepared again after all engine-range checks pass.
  • RR-59 (opf-render#170): forced colors. A disabled control of <opf-deck>, the slideshow and the speaker view (Previous on the first slide, Next on the last) is drawn in GrayText at full opacity when forced-colors: active, instead of at 45% opacity, which blended its frame and its focus ring into the page; nothing changes outside forced colors. test/forced-colors-browser.mjs verifies the element (the website-starter embed and a bare one), the slideshow and the speaker view with Chromium's forced-colors emulation in light and dark: the slide boundary, buttons, thumbnails and focus rings stay visible in system colors, as computed style and as screenshot pixels. The README states what was verified (emulation only, not the Windows contrast theme) and that the element and player are not tested with assistive technology (screen readers).