Skip to content

Releases: textcortex/SlideWise

@textcortex/slidewise@1.17.0

03 Jun 16:41
30e4a06

Choose a tag to compare

Minor Changes

  • 019e000: feat(pptx): support PowerPoint templates (.potx)

    .potx and .pptx share an identical OOXML package; only the main part's
    content type in [Content_Types].xml differs. This adds first-class template
    support across import and export:

    • parsePptx already parsed .potx transparently (it reads parts by path, not
      by content type) — now the rest of the pipeline preserves template-ness.
    • New exported isPptxTemplate(blob) detects a template by inspecting the
      package content type rather than trusting a filename extension (a mis-named
      .pptx that is really a template is detected correctly).
    • serializeDeck gains an asTemplate?: boolean option. When omitted,
      template-ness is inherited from the source archive, so a parsed .potx
      round-trips back to a .potx; pass true/false to force the output kind.
      Templates are emitted with the …presentationml.template.main+xml main-part
      content type and the .potx MIME type.

@textcortex/slidewise@1.16.4

03 Jun 12:35
a3bc116

Choose a tag to compare

Patch Changes

  • a085c8d: fix(pptx): import-fidelity fixes for think-cell / brand-template decks

    • Skip shapes flagged hidden="1" (e.g. think-cell "do not delete" data objects)
    • Render run-level text highlight (<a:rPr><a:highlight>) end to end
    • Apply cap="all"/"small" (including when inherited from a placeholder list style) as a render-time letter-case transform
    • Derive font weight from weight-named families ("Gilroy ExtraBold" → 800, "… Medium" → 500, …) so substitute fonts render at the right heaviness
    • Tables: per-cell fills, text colours, per-side borders, proportional column widths / row heights, cell spans (gridSpan/hMerge/rowSpan/vMerge), per-cell vertical anchor, and rich per-cell runs (highlight / bold / ✓ glyphs / bullet line breaks). Unfilled cells stay transparent instead of inheriting a sibling fill
    • Map Wingdings bullet glyphs to Unicode (ü→✓, q→☐, §→▪, …)
    • Bullets: repeat a character bullet across in-paragraph line breaks, suppress the glyph on empty paragraphs, and trim trailing empty paragraphs
    • Synthesise block-arrow paths (down/up/left/rightArrow) and resolve outline colour from <p:style><a:lnRef> so dashed/outlined shapes draw
    • Keep a text-bearing preset or custom-geometry shape's fill, border, and corner radius behind its text (roundRect callouts, outlined chevrons)
    • Honour <a:bodyPr><a:spAutoFit> no-wrap for short single-line labels; skip the arrow-tip text inset on no-fill label shapes
    • Render per-paragraph hanging-indent bullets as one block per line so multi-line items align correctly

@textcortex/slidewise@1.16.3

02 Jun 14:35
b8bc627

Choose a tag to compare

Patch Changes

  • 80e1b4e: Fix several PPTX import-rendering fidelity gaps surfaced by real-world decks:

    • Picture/SVG fills on shapes — shapes whose fill is an <a:blipFill> (the modern Office "icon" pattern, including dual PNG+SVG blips) now render their artwork. Previously these custGeom icons (globes, stars, grid textures, brand marks) imported with no fill and showed blank. The image is painted clipped to the shape's silhouette, or as a box-filling background for rect/rounded/circle shapes.
    • Empty picture placeholders — empty picture placeholders inherited from the slide layout no longer leak onto the slide as grey "Insert Picture" prompt boxes. Picture placeholders the slide actually hosts now inherit their rounded geometry and fill from the layout/master so they render as the template intends.
    • Embedded fonts (EOT / MicroType-Express) — embedded .fntdata fonts now decode to browser-valid TTFs. Two bugs were fixed: composite glyphs that carried WE_HAVE_INSTRUCTIONS on a non-first component produced a malformed glyf table, and format-12 cmap subtables shipped a non-zero language field — both caused the browser's font sanitizer (OTS) to reject the whole font and fall back to a system typeface.
    • Weight-named font families — weight-named embedded families (e.g. "Montserrat Bold", "Montserrat Semi-Bold") are now also aliased to their base family at the matching numeric weight, so bold/semi-bold text bound to the base family renders with the real embedded face instead of a synthetic bold.

@textcortex/slidewise@1.16.2

31 May 09:45
ff3c7ac

Choose a tag to compare

Patch Changes

  • 71f96cb: fix(pptx): resolve theme colours when persisting verbatim custGeom, so brand-coloured vectors qualify for cross-process replay

    The cross-process verbatim-replay fix (1.16.1) only stamped a custGeom shape's source <p:sp> into the deck JSON when the XML was fully self-contained — and it excluded anything referencing a theme colour (<a:schemeClr>). Brand marks are almost always filled with a theme accent (e.g. E.ON red is schemeClr val="accent2"), so the very shapes this was meant to fix (the bicycle) were skipped and fell back to the lossy synth path — still blank.

    The importer now resolves <a:schemeClr> references to literal <a:srgbClr> against the slide's theme before persisting, instead of bailing. Both elements accept the same child transforms (lumMod, alpha, …) so the swap is lossless — only the colour source changes from a theme reference to a baked hex, making the fragment valid without the source theme. Shapes that still reference media (r:embed/r:id/r:link) or carry a colour token absent from the theme remain on the synth path.

@textcortex/slidewise@1.16.1

31 May 08:48
cf2ff19

Choose a tag to compare

Patch Changes

  • 65eeac2: fix(pptx): carry verbatim custGeom OOXML in the deck JSON so vector shapes survive cross-process serialize

    The high-fidelity replay of imported elements relies on two module-global registries (sourceBufferCache, elementSourceRegistry) populated only by parsePptx and never written to the deck JSON. In a pipeline that parses in one process and serializes in another (parse client-side → store deck JSON → serialize server-side), those registries are empty, so every element is re-synthesised from its deck fields. Synthesis can't represent OOXML even-odd / winding, so complex custGeom vectors (e.g. a bicycle diagram) render blank even though simpler ones (the brand logo) happen to survive.

    The importer now stamps the verbatim <p:sp> of self-contained custGeom shapes (no r:embed / r:id / a:schemeClr references) onto the element as pristineOoxml = { xml, snapshot }, which rides along in the deck JSON. On serialize, an unedited such shape (snapshot still matches) is replayed verbatim — exact source geometry and winding — instead of being re-synthesised; its cNvPr/@id is rewritten to avoid spTree collisions. Edited shapes fall back to synthesis. This is the same persist-in-JSON pattern already used for embedded fonts (deck.fonts), scoped to vector shapes to keep JSON bloat negligible (~a few KB per deck).

@textcortex/slidewise@1.16.0

30 May 17:37
c2614f7

Choose a tag to compare

Minor Changes

  • ea3007a: Begin the MTX → TTF decoder for PPTX-embedded fonts.

    PPTX stores embedded fonts as MTX-compressed EOT inside ppt/fonts/*.fntdata. PowerPoint decodes them natively; browsers can't, which is why editor previews fall back to system fonts even when parsePptx extracted the bytes into Deck.fonts. This change lays the groundwork:

    New packages/slidewise/src/lib/fonts/eot.ts

    • Full EOT wrapper parser — header, flags, variable-length name fields, version 1.0 / 2.0 / 2.1 / 2.2 tail variants
    • Uncompressed-EOT extraction → ready-to-register TTF/OTF bytes
    • MTX detection via the TTEMBED_TTCOMPRESSED flag
    • EotDecodeError with discriminated kind so callers can distinguish "truncated", "magic-mismatch", "mtx-not-implemented", "mtx-failed"

    New packages/slidewise/src/lib/fonts/mtx.ts

    • MTX outer container parser scaffolding
    • Recognises but does not yet decompress the PowerPoint MTX variant (Office-embedded fonts use a different major version than the W3C MTX submission spec; the post-2010 Office variant isn't publicly documented).
    • Throws EotDecodeError("mtx-not-implemented") for unsupported sub-methods so the fallback chain (Deck.webFonts → fontRegistry → system fonts) runs cleanly. No noisy console errors — diagnostic only when window.__slidewiseFontDebug = true.

    Auto-wiring through resolveWebFonts()

    The font loader now decodes Deck.fonts on the fly. When a font is uncompressed EOT (~30% of real-world embedded fonts), we synthesise a data:font/ttf;base64,… URL and register it via @font-face — no fontRegistry needed, no platform involvement. Brand-embedded fonts that use MTX glyph compression (the EON case, most enterprise decks) still need fontRegistry for editor preview, but the export path still embeds the original MTX bytes verbatim.

    What still needs to happen for full coverage

    A real MTX decompressor for the Office variant. Either:

    • Reverse-engineering the format against a test corpus, or
    • A WebAssembly port of FontForge's GPL'd parsettf.c MTX path

    Both are multi-week projects. Tracked as a follow-up.

    Tests

    3 new tests in src/lib/fonts/__tests__/eot.test.ts against the real eon-deck.pptx fixture:

    • EOT header parser succeeds on every embedded font (5 entries)
    • isMtxCompressed() correctly reports the EON fonts as MTX
    • decodeEot() returns EotDecodeError.kind === "mtx-not-implemented" for MTX-flagged fonts (so the caller's fallback fires)

    No public API changes. FontAsset, WebFontAsset, and the rest of the font surface are untouched. Additive.

  • ea3007a: Complete the in-browser MTX decoder: TrueType-glyf font reconstruction.

    Milestone 2 decoded CFF/OTTO embedded fonts. This adds TrueType-outline fonts: MTX stores glyf in Compact Table Format (the WOFF2 triplet point encoding) and strips loca. ctf-glyf.ts reconstructs a standard glyf + loca and reassembles the sfnt with recomputed table checksums + head.checkSumAdjustment (so strict browser sanitizers accept it). TrueType hinting instructions are dropped (browsers ignore them; unhinted outlines render identically on screen). Simple and composite glyphs are handled.

    Verified against eon-deck.pptx: all 5 embedded EON fonts now decode in-browser — the 4 CFF EON Brix Sans weights (OTTO) and the TrueType EON Office Head (FontForge confirms the font name and correct glyph outlines). No CDN, no fontRegistry, no network: embedded PPTX fonts render exactly and automatically on import.

  • ea3007a: Decode CFF embedded PowerPoint fonts in-browser via a clean-room MTX (MicroType Express) decompressor.

    PPTX embeds fonts as MTX-compressed EOT in ppt/fonts/*.fntdata. Browsers can't decode MTX, so editor previews fell back to system fonts even though the importer extracts the bytes into Deck.fonts. This ports the W3C MTX submission (Appendix C: BITIO / AHUFF / LZCOMP) to TypeScript so the editor renders the real embedded typeface — no CDN, no fontRegistry, no network.

    • lib/fonts/lzcomp.ts — full LZCOMP decompressor: MSB-first bit reader, adaptive Huffman (complete-tree init + priming + sibling-rule update/swap), 7168-byte preload dictionary, copy-model loop.
    • lib/fonts/mtx.ts — MTX v3 container parse + decompressMtx: for CFF/OTTO fonts, block 1 decompresses to the complete font and is returned directly.
    • lib/fonts/eot.ts — locates FontData as the trailing fontDataSize bytes (spec-correct); routes compressed payloads through the MTX decoder.
    • resolveWebFonts / fontAssetToWebFont wrap the decoded bytes as a data:font/otf URL, so embedded CFF fonts render automatically on import.

    Verified against eon-deck.pptx: the 4 CFF EON Brix Sans weights decode to valid OTTO fonts (FontForge confirms "EON Brix Sans Regular"). TrueType-glyf fonts (EON Office Head) fall back with mtx-not-implemented — CTF glyf reconstruction is the remaining milestone. Export is unchanged (original .fntdata bytes still round-trip to PPTX).

@textcortex/slidewise@1.15.4

29 May 18:38
33c8e0c

Choose a tag to compare

Patch Changes

  • 03b71b7: fix(pptx): custGeom export — map path coords to the shape's EMU extent and drop the bogus fill="darken"

    Two correctness issues in svgPathToOoxml (custGeom emission):

    • Wrong fill="darken" on even-odd paths. OOXML's <a:path fill="…"> is a shading hint (none / norm / lighten / darken), not a winding rule — custGeom has no even-odd flag at all. Emitting fill="darken" for fillRule: "evenodd" silently darkened the shape and tripped some renderers (LibreOffice) without ever producing the hole. We now leave the default norm shading; holes are carried by the subpath directions already encoded in d.

    • Path coordinate space didn't match the shape box. <a:path w/h> was emitted at the source viewBox dimensions while the points stayed in that space. PowerPoint itself emits custGeom with w/h equal to the shape's EMU extent, and LibreOffice only maps the path onto the shape correctly when the two line up. svgPathToOoxml now takes the target EMU extent and rescales the points so <a:path w/h> matches the shape — improving cross-renderer fidelity for vectors whose viewBox differs from their box.

@textcortex/slidewise@1.15.3

29 May 18:01
3237b7c

Choose a tag to compare

Patch Changes

  • 0343bca: fix(pptx): vector shapes no longer render blank — gradient paint servers, full-circle arcs, and embedded brand fonts

    Three independent fidelity bugs that made imported brand decks render blank or fall back to system fonts:

    • Gradient fills on vector shapes rendered blank. SVG <path> / <polygon> fill= cannot take a CSS linear-gradient(...) / radial-gradient(...) string, so any custGeom silhouette or triangle/diamond/star carrying a gradient fill painted nothing. The renderer now builds an SVG paint server (<linearGradient> / <radialGradient>, including #RRGGBBAA alpha → stop-opacity) and references it via fill="url(#…)". Solid / transparent / url() fills are unchanged. Applies to shape paths, the polygon presets, and text backing paths.

    • Full-circle custGeom arcs (e.g. bicycle wheels) imported blank. A 360° <a:arcTo> was converted to a single SVG elliptical-arc whose end point equals its start — which the SVG spec renders as nothing, so wheels/rings vanished. The importer now splits any arc sweep into ≤120° segments, so full circles render.

    • custGeom arcs downgraded to a rect on export. svgPathToOoxml bailed on SVG A commands, collapsing the whole shape to a prstGeom rect (invisible on line-art). Arcs are now approximated as cubic Béziers (≤90° segments, sub-pixel error) and emitted as <a:cubicBezTo>, so arc-bearing vectors survive export.

    • Embedded brand fonts fell back to Calibri in the editor. The importer populated Deck.fonts (the PPTX-embedded payload) but never Deck.webFonts, so the canvas had nothing browser-renderable to paint with. It now sniffs each embedded font's signature and, when it's a browser-loadable SFNT/WOFF (which is what PowerPoint embeds), surfaces a WebFontAsset so the real typeface renders. Non-renderable payloads are skipped (no regression).

@textcortex/slidewise@1.15.2

29 May 17:03
9da2d07

Choose a tag to compare

Patch Changes

  • b3f083a: fix(pptx): preserve groups (and the custGeom / radial-gradient children inside them) on import instead of flattening

    The PPTX importer flattened <p:grpSp> on the way in — it parsed the group's children and spliced them in as loose top-level elements, so no GroupElement ever reached the editor. The group structure was lost, and because the flattened children were registered with their own child-coordinate-space <p:sp> XML, they re-injected at the slide's top level with the wrong coordinates on round-trip. custGeom logos and radial-gradient panels — which in real decks almost always live inside groups — went down with the group. The schema, renderer, and export writer already supported all three; only the importer dropped them.

    The importer now builds a real GroupElement (children keep slide-absolute coordinates, z re-stamped in document order, bounding box from the group's <a:xfrm> with a child-union fallback) and registers the whole <p:grpSp> for verbatim replay, so an unedited group round-trips byte-for-byte. snapshotElement now recurses into group children, so editing any descendant flips the group off verbatim-replay onto the synth path (which re-emits custGeom, gradients, and nested groups) rather than re-emitting stale source XML.

    Deferred (unchanged from before): text/image children inside an edited group still round-trip lossy through the synth path, and group-level in-canvas selection/drag remains a follow-up.

@textcortex/slidewise@1.15.1

29 May 15:18
eaf5448

Choose a tag to compare

Patch Changes

  • d2529c3: fix(pptx): preserve per-stop and solid-fill alpha from 8-digit hex colors

    parseFill truncated #RRGGBBAA / #RGBA colors to 6 digits via hexBare,
    dropping the alpha channel before it could reach <a:alpha>. Translucent
    gradient stops (and flat translucent fills) were therefore serialized opaque.
    parseFill now extracts alpha from 4- and 8-digit hex, so gradient stops carry
    their <a:alpha> and solid shape fills map alpha to pptxgenjs transparency.