ppt-codec 1.5.0 → 1.7.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/README.md +91 -75
- package/dist/base64.cjs +48 -0
- package/dist/base64.d.cts +5 -0
- package/dist/base64.d.ts +5 -0
- package/dist/base64.js +46 -0
- package/dist/diagnostics-CqxAnubw.d.cts +16 -0
- package/dist/diagnostics-CqxAnubw.d.ts +16 -0
- package/dist/diagnostics.cjs +11 -0
- package/dist/diagnostics.d.cts +2 -0
- package/dist/diagnostics.d.ts +2 -0
- package/dist/diagnostics.js +9 -0
- package/dist/document/color-scheme.cjs +4 -0
- package/dist/document/color-scheme.d.cts +2 -1
- package/dist/document/color-scheme.d.ts +2 -1
- package/dist/document/color-scheme.js +4 -1
- package/dist/document/master-write.cjs +21 -16
- package/dist/document/master-write.d.cts +2 -1
- package/dist/document/master-write.d.ts +2 -1
- package/dist/document/master-write.js +21 -16
- package/dist/document/notes-write.cjs +15 -7
- package/dist/document/notes-write.d.cts +2 -1
- package/dist/document/notes-write.d.ts +2 -1
- package/dist/document/notes-write.js +15 -7
- package/dist/document/notes.cjs +4 -3
- package/dist/document/notes.js +4 -3
- package/dist/drawing/blips.cjs +81 -0
- package/dist/drawing/blips.d.cts +16 -0
- package/dist/drawing/blips.d.ts +16 -0
- package/dist/drawing/blips.js +77 -0
- package/dist/drawing/properties.cjs +102 -0
- package/dist/drawing/properties.d.cts +29 -0
- package/dist/drawing/properties.d.ts +29 -0
- package/dist/drawing/properties.js +87 -0
- package/dist/drawing/shapes-write.cjs +234 -17
- package/dist/drawing/shapes-write.d.cts +16 -4
- package/dist/drawing/shapes-write.d.ts +16 -4
- package/dist/drawing/shapes-write.js +237 -19
- package/dist/drawing/shapes.cjs +34 -4
- package/dist/drawing/shapes.d.cts +11 -2
- package/dist/drawing/shapes.d.ts +11 -2
- package/dist/drawing/shapes.js +35 -5
- package/dist/index.cjs +48 -3
- package/dist/index.d.cts +12 -8
- package/dist/index.d.ts +12 -8
- package/dist/index.js +11 -7
- package/dist/ole/embedded-write.cjs +66 -0
- package/dist/ole/embedded-write.d.cts +12 -0
- package/dist/ole/embedded-write.d.ts +12 -0
- package/dist/ole/embedded-write.js +63 -0
- package/dist/ole/embedded.cjs +66 -0
- package/dist/ole/embedded.d.cts +12 -0
- package/dist/ole/embedded.d.ts +12 -0
- package/dist/ole/embedded.js +63 -0
- package/dist/read.cjs +145 -22
- package/dist/read.d.cts +13 -5
- package/dist/read.d.ts +13 -5
- package/dist/read.js +148 -26
- package/dist/record/types.cjs +28 -0
- package/dist/record/types.d.cts +15 -1
- package/dist/record/types.d.ts +15 -1
- package/dist/record/types.js +15 -1
- package/dist/stream/current-user.cjs +1 -0
- package/dist/stream/current-user.d.cts +2 -1
- package/dist/stream/current-user.d.ts +2 -1
- package/dist/stream/current-user.js +1 -1
- package/dist/units.cjs +10 -0
- package/dist/units.d.cts +4 -1
- package/dist/units.d.ts +4 -1
- package/dist/units.js +8 -1
- package/dist/write.cjs +137 -38
- package/dist/write.d.cts +14 -5
- package/dist/write.d.ts +14 -5
- package/dist/write.js +137 -38
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ Created for [documents.js#817](https://github.com/ExaDev/documents.js/issues/817
|
|
|
8
8
|
|
|
9
9
|
## Status
|
|
10
10
|
|
|
11
|
-
**Under active development. The read path for slide text, geometry and speaker notes is built and tested. A narrower write path now exists too: one slide per input slide, with
|
|
11
|
+
**Under active development. The read path for slide text, geometry, pictures, tables, rotation and speaker notes is built and tested. A narrower write path now exists too: one slide per input slide, with text-box, picture, and table shapes (basic character formatting, rotation, no layouts) plus their speaker notes — genuinely conformant [MS-PPT], verified by writing then reading every fixture back through this package's own reader, and, for speaker notes, against LibreOffice in both directions, but not full read/write parity.** What that means concretely is set out in [What it reads](#what-it-reads)/[What it does not read yet](#what-it-does-not-read-yet) and [What it writes](#what-it-writes)/[What it does not write yet](#what-it-does-not-write-yet) below — every one of those four lists is exhaustive rather than illustrative, so a caller can tell from this page alone whether the format's own feature it cares about is covered.
|
|
12
12
|
|
|
13
13
|
## Why the format is shaped the way it is
|
|
14
14
|
|
|
@@ -111,31 +111,41 @@ import { writePpt, writePptContent } from "ppt-codec";
|
|
|
111
111
|
// The tree form: a document-schema.js DocumentTree in, real .ppt bytes out.
|
|
112
112
|
const pptBytes = writePpt(tree);
|
|
113
113
|
|
|
114
|
-
// The flat form: metadata plus ContentSlide[] in -- title/author/dates are
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
114
|
+
// The flat form: metadata plus ContentSlide[] in -- title/author/dates are written to a real "\x05SummaryInformation" stream when metadata carries any of them (see Metadata). An optional sink hears every block this writer had to drop (an unblippable image format, a second table on one shape, or an embeddedObject block with no serialiseEmbeddedObject port to turn its nested document into real bytes) instead of losing that information silently.
|
|
115
|
+
const bytes = writePptContent(
|
|
116
|
+
{ metadata: {}, slides },
|
|
117
|
+
{ sink: (diagnostic) => console.warn(diagnostic.code, diagnostic.message) },
|
|
118
|
+
);
|
|
118
119
|
```
|
|
119
120
|
|
|
120
|
-
`writePptStreams(document)` is the same write one level down, returning the two [MS-PPT] streams without wrapping them in a compound file — the mirror of `readPptStreams`, for a caller assembling its own container. Every function throws `PptUnsupportedContentError` (not `PptFormatError`, which is reserved for malformed bytes on the read side) when asked to write content outside this writer's scope: a document that is not a presentation, or slides that do not all share one size (`[MS-PPT]`'s `DocumentAtom` states exactly one slide size for the whole presentation). A block kind this writer does not represent (an image
|
|
121
|
+
`writePptStreams(document)` is the same write one level down, returning the two [MS-PPT] streams without wrapping them in a compound file — the mirror of `readPptStreams`, for a caller assembling its own container. Every function throws `PptUnsupportedContentError` (not `PptFormatError`, which is reserved for malformed bytes on the read side) when asked to write content outside this writer's scope: a document that is not a presentation, or slides that do not all share one size (`[MS-PPT]`'s `DocumentAtom` states exactly one slide size for the whole presentation). A block kind this writer does not represent (a construct marker, an image in a format with no blip token, or an embeddedObject block whose nested document `serialiseEmbeddedObject` declines or was never supplied for) is, by default, not an error — it is dropped from the written text body with a diagnostic naming it (see [`src/diagnostics.ts`](src/diagnostics.ts)), the same documented-gap convention [What it does not read yet](#what-it-does-not-read-yet) already uses for the reader's own unsupported constructs. `WritePptOptions.onUnwritableBlock: 'throw'` turns exactly that drop into a thrown `PptUnsupportedContentError` instead — see [Silent drop, or a thrown error](#silent-drop-or-a-thrown-error) for why `'drop'` stays the default rather than converging on doc-codec's own throw-always convention.
|
|
122
|
+
|
|
123
|
+
### Silent drop, or a thrown error
|
|
124
|
+
|
|
125
|
+
`doc-codec` throws for a block kind its own writer cannot express; this package's writer, by default, drops the block and names it through the diagnostic sink instead — a real, deliberate divergence between two codecs in the same family, not an oversight ([ExaDev/documents.js#1188](https://github.com/ExaDev/documents.js/issues/1188), split into its own decision as [#1220](https://github.com/ExaDev/documents.js/issues/1220)). The two packages' own upstream differs in kind, not degree: `documents.js`'s PDF-to-ppt and odp-to-ppt reconstruction is this package's primary caller today, and it routinely hands this writer content the binary PPT format simply has no spelling for at all (an unrecognised alignment value, a construct marker, an OLE object with no serialiser port supplied) — not as a rare malformed-input edge case a bug would explain, but as the ordinary shape of reconstructing a narrower target format from a richer source. Converging on `doc-codec`'s own default would turn "this slide's chart degrades to geometry" into "the whole presentation fails to convert" for every one of those callers, a severe regression imposed on working code rather than a bug fixed in it.
|
|
126
|
+
|
|
127
|
+
`WritePptOptions.onUnwritableBlock` is the caller's own choice between the two policies rather than a permanent split: `'drop'` (the default, and every existing caller's own unchanged behaviour) keeps writing the rest of the shape and names the drop through the sink; `'throw'` raises a `PptUnsupportedContentError` naming the identical block and reason the sink would otherwise merely report, for a caller that would rather fail the whole conversion than ship a file quietly missing content it was asked to carry. The option covers a genuine block-level omission only — `PptDiagnosticCodes.TABLE_SPAN_DROPPED` (a merged cell's `colSpan`/`rowSpan` narrowed to one column/row, since this format's own tables carry no merge record at all) is a lossy narrowing of content that is still written, not an omission of it, and always stays sink-only regardless of this option; conflating the two would make `'throw'` fail a conversion over a table that wrote completely, just with a merge visually flattened.
|
|
121
128
|
|
|
122
129
|
## What it reads
|
|
123
130
|
|
|
124
131
|
The whole path from a file's first byte to a slide's text, record by record:
|
|
125
132
|
|
|
126
|
-
| Layer | Records
|
|
127
|
-
| --------------- |
|
|
128
|
-
| Container | The `Current User` and `PowerPoint Document` streams, read through `archive-codec`'s bounded [MS-CFB] reader.
|
|
129
|
-
| Record framing | The generic 8-byte `RecordHeader`, the container/atom distinction, sibling sequences, child walks, and typed-descendant search — shared with [MS-ODRAW]'s records, which carry the identical header.
|
|
130
|
-
| Edit resolution | `CurrentUserAtom` (including its encrypted/plaintext `headerToken`), the `UserEditAtom` chain, `PersistDirectoryAtom`/`PersistDirectoryEntry`'s packed 20-bit/12-bit run form, and the oldest-first directory construction whose later entries supersede earlier ones — [MS-PPT] 2.1.2's own "live record" process, Part 1.
|
|
131
|
-
| Encryption | `DocumentEncryptionAtom` (`UserEditAtom.encryptSessionPersistIdRef` → the persist directory), [MS-OFFCRYPTO] 2.3.5's RC4 CryptoAPI scheme — see [Encryption](#encryption).
|
|
132
|
-
| Document | `DocumentContainer` → `DocumentAtom` (slide size, in master units), `DocumentTextInfoContainer`'s `FontCollectionContainer`/`FontEntityAtom` typeface names and its own document-default `TextMasterStyleAtom`, and `SlideListWithTextContainer` (distinguished from the master and notes lists by `recInstance`, which does not run in the order the names suggest).
|
|
133
|
-
| Masters | `MasterListWithTextContainer` → `MasterPersistAtom` → the persist directory → each `MainMasterContainer`'s own `TextMasterStyleAtom` items and `SlideSchemeColorSchemeAtom` — a real file can carry more than one master, and `SlideAtom.masterIdRef` decides which one a given slide actually follows.
|
|
134
|
-
| Slides | `SlidePersistAtom` → the persist directory → each `SlideContainer`'s own `SlideAtom` (`masterIdRef`, `notesIdRef`) and drawing, and the placeholder texts the slide list carries for it.
|
|
135
|
-
| Speaker notes | `NotesListWithTextContainer` (the third of the three containers sharing `RT_SlideListWithText`) → `NotesPersistAtom` → the persist directory → each `NotesContainer`, and the `NotesAtom.slideIdRef` naming the presentation slide those notes belong to. The text comes from the notes slide's own drawing, since the notes list — unlike the slide list — carries no texts for an `OutlineTextRefAtom` to reach into.
|
|
136
|
-
| Drawing | `DrawingContainer` → `OfficeArtDgContainer` → the `OfficeArtSpgrContainer`/`OfficeArtSpContainer` tree, `OfficeArtFSP`'s group/patriarch/deleted flags, `OfficeArtClientAnchor` in both its 8-byte `SmallRectStruct` and 16-byte `RectStruct` spellings,
|
|
137
|
-
|
|
|
138
|
-
|
|
|
133
|
+
| Layer | Records |
|
|
134
|
+
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
135
|
+
| Container | The `Current User` and `PowerPoint Document` streams, read through `archive-codec`'s bounded [MS-CFB] reader. |
|
|
136
|
+
| Record framing | The generic 8-byte `RecordHeader`, the container/atom distinction, sibling sequences, child walks, and typed-descendant search — shared with [MS-ODRAW]'s records, which carry the identical header. |
|
|
137
|
+
| Edit resolution | `CurrentUserAtom` (including its encrypted/plaintext `headerToken`), the `UserEditAtom` chain, `PersistDirectoryAtom`/`PersistDirectoryEntry`'s packed 20-bit/12-bit run form, and the oldest-first directory construction whose later entries supersede earlier ones — [MS-PPT] 2.1.2's own "live record" process, Part 1. |
|
|
138
|
+
| Encryption | `DocumentEncryptionAtom` (`UserEditAtom.encryptSessionPersistIdRef` → the persist directory), [MS-OFFCRYPTO] 2.3.5's RC4 CryptoAPI scheme — see [Encryption](#encryption). |
|
|
139
|
+
| Document | `DocumentContainer` → `DocumentAtom` (slide size, in master units), `DocumentTextInfoContainer`'s `FontCollectionContainer`/`FontEntityAtom` typeface names and its own document-default `TextMasterStyleAtom`, and `SlideListWithTextContainer` (distinguished from the master and notes lists by `recInstance`, which does not run in the order the names suggest). |
|
|
140
|
+
| Masters | `MasterListWithTextContainer` → `MasterPersistAtom` → the persist directory → each `MainMasterContainer`'s own `TextMasterStyleAtom` items and `SlideSchemeColorSchemeAtom` — a real file can carry more than one master, and `SlideAtom.masterIdRef` decides which one a given slide actually follows. |
|
|
141
|
+
| Slides | `SlidePersistAtom` → the persist directory → each `SlideContainer`'s own `SlideAtom` (`masterIdRef`, `notesIdRef`) and drawing, and the placeholder texts the slide list carries for it. |
|
|
142
|
+
| Speaker notes | `NotesListWithTextContainer` (the third of the three containers sharing `RT_SlideListWithText`) → `NotesPersistAtom` → the persist directory → each `NotesContainer`, and the `NotesAtom.slideIdRef` naming the presentation slide those notes belong to. The text comes from the notes slide's own drawing, since the notes list — unlike the slide list — carries no texts for an `OutlineTextRefAtom` to reach into. |
|
|
143
|
+
| Drawing | `DrawingContainer` → `OfficeArtDgContainer` → the `OfficeArtSpgrContainer`/`OfficeArtSpContainer` tree, `OfficeArtFSP`'s group/patriarch/deleted flags, `OfficeArtClientAnchor` in both its 8-byte `SmallRectStruct` and 16-byte `RectStruct` spellings, `OfficeArtChildAnchor` mapped through nested `OfficeArtFSPGR` group coordinate systems, a shape's own `OfficeArtFOPT` rotation property (`PROPERTY_ROTATION`, an [MS-OSHARED] 2.2.1.6 Fixed Point), and its four text-inset properties (`dxTextLeft`/`dyTextTop`/`dxTextRight`/`dyTextBottom`, each an EMU value read independently — a shape stating only one override still gets PowerPoint's own default on the other three). |
|
|
144
|
+
| Pictures | The document-wide `OfficeArtBStoreContainer` (`OfficeArtFBSE` entries, each an inline or `Pictures`-stream-offset blip), resolved through a picture shape's own `pib` property; only the two `MSOBLIPTYPE` tokens `document-schema.js`'s `ContentImageBlock` can hold losslessly (`0x05` JPEG, `0x06` PNG) decode to an image block, sized to the shape's own frame. An unresolvable pib (past the store's end, an empty slot, or a WMF/EMF/TIFF/DIB blip this package does not decode) keeps the shape with empty content rather than dropping the shape. |
|
|
145
|
+
| Tables | A table group's own grid, recovered from its cells' rectangles rather than from any row/column record — the format states none: row and column boundaries are the cells' own distinct tops and lefts, a cell lands at the intersection of its own top and left, and a real producer's degenerate zero-width/zero-height gridline shapes are excluded from the grid by that same geometry check rather than treated as cells. |
|
|
146
|
+
| OLE embeddings | A shape's `OfficeArtClientData` → `ExObjRefAtom` → the document's `ExObjListContainer` → `ExOleEmbedContainer` (`ExOleObjAtom`'s own `persistIdRef`, and an optional `ProgIDAtom`) → the persist directory → an `ExOleObjStg` persist object, decompressed (zlib/[RFC1950]) when `rh.recInstance` says so — recovering the embedded object's own raw [MS-CFB] compound-file bytes with no further "Package"-stream wrapper. This package cannot decode those bytes into a real nested document itself (see [What it does not read yet](#what-it-does-not-read-yet)), so the recovery stops at the bytes/progId pair unless a caller injects `ReadPptOptions.decodeEmbeddedObject` — `documents.js` wires one from `doc-codec`/`xls-codec`/its own `ppt-codec` adapter. |
|
|
147
|
+
| Text | `OfficeArtClientTextbox`, `TextHeaderAtom`, `TextCharsAtom` (UTF-16) and `TextBytesAtom` (one byte per character), `OutlineTextRefAtom` indirection into the slide list, and the paragraph split on the stored `\r`. |
|
|
148
|
+
| Formatting | `StyleTextPropAtom`: `TextPFRun`/`TextPFException` (indent level, alignment, line spacing, space before/after, left margin, and first-line indent) and `TextCFRun`/`TextCFException` (bold, italic, underline, shadow, emboss, typeface reference, size in points, and a `ColorIndexStruct` colour — literal sRGB or a colour-scheme slot reference), each read in the spec's **declared field order** rather than its mask-bit order — the two differ, and following the mask-bit order desynchronises every field after the first divergence. A field a run states neither directly nor at all resolves against the applicable master's own cascade and colour scheme — see [Master and colour inheritance](#master-and-colour-inheritance). |
|
|
139
149
|
|
|
140
150
|
Geometry is converted from master units (1/576 inch) to points on the way out, so a slide's `size` and every shape's `frame` are in the same unit the shared schema uses everywhere else.
|
|
141
151
|
|
|
@@ -144,8 +154,10 @@ Geometry is converted from master units (1/576 inch) to points on the way out, s
|
|
|
144
154
|
Each of these is a real construct of the format that this package currently ignores or cannot represent — not a claim that it does not exist:
|
|
145
155
|
|
|
146
156
|
- **`DocumentSummaryInformation`'s extended and user-defined properties** (company, manager, custom properties) — a genuinely different stream from the one [Metadata](#metadata) covers, not attempted at all.
|
|
147
|
-
- **
|
|
148
|
-
- **
|
|
157
|
+
- **A linked (as opposed to embedded) OLE object.** `ExOleLinkContainer` names an external file this package has no path to resolve independently of the host document, so a linked object's shape reads with geometry and no blocks — the identical "no recovery path, no entry" degrade an unresolvable persist reference already gets. An embedded OLE object's own linkage and storage recovery is real — see [OLE embeddings](#what-it-reads) in the table above.
|
|
158
|
+
- **Decoding an OLE-embedded object's own nested content, without a caller-supplied port.** This package depends on no sibling format codec (`doc-codec`/`xls-codec`/itself, or `ooxml.js`), so it cannot turn an embedded object's recovered `[MS-CFB]` bytes into a real nested `ContentDocument` on its own — the same architectural boundary `ooxml.js`'s own embedded-object recovery states for a classic-binary `.bin` payload it finds no `Package` stream in. `readPptContent`/`readPpt`'s `ReadPptOptions.decodeEmbeddedObject` is the injected port a caller holding every codec — `documents.js` — supplies; without one, an OLE-embedded shape's blocks stay whatever its own picture/text already give it (the same degrade a decode failure, an unrecognised progId, or a missing persist entry all produce), never a document this package invented or guessed at.
|
|
159
|
+
- **Image formats beyond PNG and JPEG.** A blip in any other `MSOBLIPTYPE` (WMF/EMF metafile, PICT, a raw DIB, TIFF) reads as no image at all, keeping the shape's geometry with empty content — the same convention an unresolvable pib already uses.
|
|
160
|
+
- **Cell merges in a read table.** [MS-PPT]'s own table shapes are a strict grid of one shape per cell with no merge record at all (merged cells arrived only with the 2010 XML format), so a read table never states `colSpan`/`rowSpan`.
|
|
149
161
|
- **Shapes with no anchor.** A shape carrying neither an `OfficeArtClientAnchor` nor an `OfficeArtChildAnchor` is dropped, because `ContentShape` has no way to say "positioned, but unknown where".
|
|
150
162
|
- **Hyperlinks and bullets.** `InteractiveInfo`/`TextInteractiveInfoAtom` and `TextPFException`'s bullet fields are parsed past correctly but not surfaced.
|
|
151
163
|
- **Animations, transitions, comments, headers and footers, and the metacharacter atoms** (slide number, date, header, footer).
|
|
@@ -157,18 +169,21 @@ Each of these is a real construct of the format that this package currently igno
|
|
|
157
169
|
|
|
158
170
|
The whole path from a `ContentSlide[]` to a real `.ppt` file's bytes, mirroring the read-side table above in the opposite direction:
|
|
159
171
|
|
|
160
|
-
| Layer | Records
|
|
161
|
-
| --------------- |
|
|
162
|
-
| Container | The `Current User` and `PowerPoint Document` streams, wrapped in a real [MS-CFB] compound file through `archive-codec`'s conformant writer.
|
|
163
|
-
| Record framing | The generic 8-byte `RecordHeader`, atom and container builders — `record/write.ts`, shared by every writer module below and by this package's own test fixtures.
|
|
164
|
-
| Edit resolution | A single-edit persist layer: one `CurrentUserAtom` pointing at one `UserEditAtom` pointing at one `PersistDirectoryAtom` whose entries name the stream offset of the document container, the master, every slide container and every notes container — never an incremental append, since nothing about this writer's own output needs a second generation of any object.
|
|
165
|
-
| Document | `DocumentContainer` → `DocumentAtom` (one slide size, in master units, taken from the input's own first slide and required to match every other slide — see below), an `Environment`/`FontCollectionContainer` built from every distinct `fontFamily` a run names, a `MasterListWithTextContainer` naming the one master, a `SlideListWithTextContainer` carrying one `SlidePersistAtom` per slide with no placeholder texts, and — only when some slide has notes — a `NotesListWithTextContainer`.
|
|
166
|
-
| Master | One minimal `MainMasterContainer`: its own `SlideAtom`, the title/body/notes `TextMasterStyleAtom` items [MS-PPT] 2.5.3 requires (each stating `cLevels` 0, so every level falls through to the document's own text styles), the five `SL_TitleBody` placeholder shapes a main master must carry, and a default `SlideSchemeColorSchemeAtom`. It exists because speaker notes need it — see below.
|
|
167
|
-
| Slides | One `SlideContainer` per input slide, each opening with a `SlideAtom` that names the master it follows and, when the slide has notes, the notes slide holding them, then its own `DrawingContainer`.
|
|
168
|
-
| Speaker notes | One `NotesContainer` per slide that actually has notes — a `NotesAtom` naming that slide, then a `DrawingContainer` whose single text box carries the notes, one paragraph per line, then the `SlideSchemeColorSchemeAtom` [MS-PPT] 2.5.6 requires of one — the notes slide's own `NotesAtom.slideFlags` leaves `fMasterScheme` clear, so it inherits no scheme and has to state one. A slide with no notes gets no notes slide at all rather than an empty one.
|
|
169
|
-
| Drawing | `OfficeArtDgContainer` → one `OfficeArtSpgrContainer` (the patriarch group every real drawing carries) → one plain `OfficeArtSpContainer` per shape, each anchored in slide coordinates via a 32-bit `OfficeArtClientAnchor` (`RectStruct`, never the 16-bit `SmallRectStruct`) — no grouping,
|
|
170
|
-
|
|
|
171
|
-
|
|
|
172
|
+
| Layer | Records |
|
|
173
|
+
| --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
174
|
+
| Container | The `Current User` and `PowerPoint Document` streams, wrapped in a real [MS-CFB] compound file through `archive-codec`'s conformant writer. |
|
|
175
|
+
| Record framing | The generic 8-byte `RecordHeader`, atom and container builders — `record/write.ts`, shared by every writer module below and by this package's own test fixtures. |
|
|
176
|
+
| Edit resolution | A single-edit persist layer: one `CurrentUserAtom` pointing at one `UserEditAtom` pointing at one `PersistDirectoryAtom` whose entries name the stream offset of the document container, the master, every slide container and every notes container — never an incremental append, since nothing about this writer's own output needs a second generation of any object. |
|
|
177
|
+
| Document | `DocumentContainer` → `DocumentAtom` (one slide size, in master units, taken from the input's own first slide and required to match every other slide — see below), an `Environment`/`FontCollectionContainer` built from every distinct `fontFamily` a run names, a `MasterListWithTextContainer` naming the one master, a `SlideListWithTextContainer` carrying one `SlidePersistAtom` per slide with no placeholder texts, and — only when some slide has notes — a `NotesListWithTextContainer`. |
|
|
178
|
+
| Master | One minimal `MainMasterContainer`: its own `SlideAtom`, the title/body/notes `TextMasterStyleAtom` items [MS-PPT] 2.5.3 requires (each stating `cLevels` 0, so every level falls through to the document's own text styles), the five `SL_TitleBody` placeholder shapes a main master must carry, and a default `SlideSchemeColorSchemeAtom`. It exists because speaker notes need it — see below. |
|
|
179
|
+
| Slides | One `SlideContainer` per input slide, each opening with a `SlideAtom` that names the master it follows and, when the slide has notes, the notes slide holding them, then its own `DrawingContainer`. |
|
|
180
|
+
| Speaker notes | One `NotesContainer` per slide that actually has notes — a `NotesAtom` naming that slide, then a `DrawingContainer` whose single text box carries the notes, one paragraph per line, then the `SlideSchemeColorSchemeAtom` [MS-PPT] 2.5.6 requires of one — the notes slide's own `NotesAtom.slideFlags` leaves `fMasterScheme` clear, so it inherits no scheme and has to state one. A slide with no notes gets no notes slide at all rather than an empty one. |
|
|
181
|
+
| Drawing | `OfficeArtDgContainer` → one `OfficeArtSpgrContainer` (the patriarch group every real drawing carries) → one plain `OfficeArtSpContainer` per shape, each anchored in slide coordinates via a 32-bit `OfficeArtClientAnchor` (`RectStruct`, never the 16-bit `SmallRectStruct`), a shape's own `rotationDeg` as an `OfficeArtFOPT` `PROPERTY_ROTATION` entry, and whichever of its four text insets differ from the default its own picture-ness implies, each as its own `dxTextLeft`/`dyTextTop`/`dxTextRight`/`dyTextBottom` property — no grouping otherwise, beyond the table group below. |
|
|
182
|
+
| Pictures | The first `png`/`jpeg` image block on a shape becomes that shape's one blip-store reference (`blipIndexOf`, feeding the document-wide `OfficeArtBStoreContainer` every picture shares); an image in any other format, or a second image on a shape whose single blip reference an earlier one already claimed, is dropped with a diagnostic (`ppt/image-dropped`) rather than silently discarded. |
|
|
183
|
+
| Tables | A table block turns its whole shape into a table group in the spelling a real PowerPoint-authored file carries (confirmed against Microsoft Office PowerPoint's own output and Apache POI's `table_test.ppt` fixture): an `OfficeArtFSPGR` child coordinate system identical to the shape's own client anchor, `fIsTable`/`tableRowProperties` in the tertiary property table, and one plain text-box shape per cell at its own `OfficeArtChildAnchor` grid position — the same grid `tableBlockFor` reads back. A cell's `colSpan`/`rowSpan` is dropped with a diagnostic (`ppt/table-span-dropped`), since the format has no merge record to state it in. |
|
|
184
|
+
| OLE embeddings | A shape whose blocks carry an `embeddedObject` block gets a real `ExObjRefAtom` in its own `OfficeArtClientData`, naming a fresh entry in the document's own `ExObjListContainer` (`ExOleEmbedContainer`: `ExOleEmbedAtom`, `ExOleObjAtom`, and — for a `wordprocessing`/`spreadsheet` object kind, the two this package can name a real legacy-Office ProgID for — a `ProgIDAtom`) whose `persistIdRef` names a fresh `ExOleObjStgUncompressedAtom` persist object, only when `WritePptOptions.serialiseEmbeddedObject` (this package has no sibling-codec writer of its own to serialise the nested document with — the write-side mirror of the read-side decode port above) actually recovers `[MS-CFB]` bytes for that object's own nested document; a shape whose embed the port declines (no port supplied, or a document kind — `formula`/`drawing` — it cannot serialise) writes with no `clientData` at all, identical to a shape that never carried an embeddedObject block in the first place — the same silent-drop policy every other unwritable block already gets (see [What it does not write yet](#what-it-does-not-write-yet)). |
|
|
185
|
+
| Text | Every shape carries its own text directly on its `OfficeArtClientTextbox` (`TextHeaderAtom` + a UTF-16 `TextCharsAtom`) rather than through the `OutlineTextRefAtom` placeholder indirection into the slide list — a plain text box is all this writer produces, so there is no separate placeholder text to route through the document's own slide list. |
|
|
186
|
+
| Formatting | `StyleTextPropAtom`: one `TextPFRun` per paragraph (indent level, alignment, line spacing, space before/after, left margin, and first-line indent) and one `TextCFRun` per character run (bold, italic, underline, a font-collection reference, size in points, and a literal sRGB `ColorIndexStruct` colour), fields written in the identical spec-declared order `readTextPFException`/`readTextCFException` parse them in. |
|
|
172
187
|
|
|
173
188
|
Geometry is converted from points to master units on the way in, rounding to the nearest whole master unit (1/576 inch) — the format's own smallest unit of length.
|
|
174
189
|
|
|
@@ -194,10 +209,10 @@ The round trip above proves the reader and writer agree with each other. Speaker
|
|
|
194
209
|
|
|
195
210
|
Each of these is either a real construct this writer deliberately does not attempt (a smaller, genuinely correct core rather than a larger, unreliable one — see the two tables above for exactly what it does write), or a `ContentShape`/`ContentParagraph`/`ContentRun` field this writer's own OfficeArt shape tree has nowhere to carry:
|
|
196
211
|
|
|
197
|
-
- **
|
|
212
|
+
- **An OLE embedding's own nested content, without a caller-supplied serialiser.** This package cannot itself turn a nested `ContentDocument` back into `[MS-CFB]` bytes (it depends on no sibling format codec — the write-side mirror of the read-side decode gap above), so `WritePptOptions.serialiseEmbeddedObject` is the injected port a caller holding every codec supplies; without one, a shape's `embeddedObject` block writes with no `clientData` and no `ExOleObjStg` persist object, silently, matching every other block kind this writer cannot express (see [What it writes](#what-it-writes) for what a supplied port produces).
|
|
198
213
|
- **Shapes with no text.** Written with a client anchor and no `OfficeArtClientTextbox` at all, matching how the reader represents one (`blocks: []`); nothing is lost, since there was nothing to write.
|
|
199
|
-
- **Grouped shapes
|
|
200
|
-
- **
|
|
214
|
+
- **Grouped shapes beyond a table, and any coordinate system beyond a plain `OfficeArtClientAnchor` or the table group's own `OfficeArtFSPGR`.** Every non-table shape this writer emits is an ungrouped rectangle in slide coordinates; there is no general `OfficeArtChildAnchor`/`OfficeArtFSPGR` group nesting outside the one a table block itself produces. `ContentShape.rotationDeg` is written (see [Drawing](#what-it-writes) in the table above).
|
|
215
|
+
- **Autofit and paint order.** `ContentShape.fontScale`, `lineSpacingReduction`, and `paintOrder` have no `OfficeArtFOPT` property table entry this writer states (per-shape text insets are written — see [Drawing](#what-it-writes) in the table above).
|
|
201
216
|
- **Master content, layouts, and scheme colours.** A `MainMasterContainer` and its `MasterListWithTextContainer` are written, but only as the minimum [MS-PPT] requires of one (see [Why a writer of plain text-box slides writes a master slide](#why-a-writer-of-plain-text-box-slides-writes-a-master-slide)): its five placeholder shapes carry no text, its `TextMasterStyleAtom` items state no style level of their own, and its `SlideSchemeColorSchemeAtom` is a fixed default rather than anything the input chose. There are still no slide layouts, and every character run's colour must already be a literal, since no scheme is there to resolve one against.
|
|
202
217
|
- **Notes masters, and a notes page geometry of its own.** No `NotesContainer` is written for the notes master, and `DocumentAtom.notesMasterPersistIdRef` stays 0, so each notes slide inherits nothing (its `NotesAtom.slideFlags` is clear) and states the same fixed default `SlideSchemeColorSchemeAtom` the master does rather than a scheme of the input's choosing. The notes page is the same size as the slide, because `ContentSlide` carries no notes-page geometry to state a different one from, and the notes text box is placed in the lower half of it.
|
|
203
218
|
- **Hyperlinks, bullets, and list numbering identity.** `ContentRun.hyperlink`, `ContentParagraph.list.numId`/`checked`/`itemId`, and `pageBreakBefore`/`pageBreakAfter` have no [MS-PPT] field this writer populates; `alignment`, `list.level` (as a `TextPFException` indent level), `spacingBeforePt`/`spacingAfterPt`/`lineSpacing`/`indentLeftPt`/`indentFirstLinePt` round-trip.
|
|
@@ -205,7 +220,7 @@ Each of these is either a real construct this writer deliberately does not attem
|
|
|
205
220
|
- **Construct markers.** A `constructStart`/`constructEnd` pair (or any other non-`paragraph` block kind) is excluded from the written text body exactly like an image or table block, per [Writing a document](#writing-a-document).
|
|
206
221
|
- **Alignment values the shared schema has no name for.** The mirror of the read-side gap: `Tx_ALIGNDistributed`, `Tx_ALIGNThaiDistributed`, and `Tx_ALIGNJustifyLow` are never written, since `Alignment` has no member naming them.
|
|
207
222
|
- **Fractional character sizes.** `ContentRun.sizePt` is rounded to the nearest whole point, since `TextCFException`'s size field is a plain 16-bit integer.
|
|
208
|
-
- **Fonts
|
|
223
|
+
- **Fonts (custom embedding), animations, transitions, comments, and the metacharacter atoms.** Nothing here is written for the same reason none of it is read yet — see the corresponding entries in [What it does not read yet](#what-it-does-not-read-yet).
|
|
209
224
|
|
|
210
225
|
## Metadata
|
|
211
226
|
|
|
@@ -231,43 +246,44 @@ import { readRecordAt } from "ppt-codec/record/tree";
|
|
|
231
246
|
import { readStyleTextPropAtom } from "ppt-codec/text/style";
|
|
232
247
|
```
|
|
233
248
|
|
|
234
|
-
| Module | What it owns
|
|
235
|
-
| ------------------------------ |
|
|
236
|
-
| `record/header` | The generic 8-byte record header and the container/atom distinction.
|
|
237
|
-
| `record/types` | The `RecordType` values this reader dispatches on, plus the [MS-ODRAW] types the drawing walk crosses into.
|
|
238
|
-
| `record/tree` | Offset-addressed records, sibling sequences, child walks, typed-descendant search.
|
|
239
|
-
| `record/write` | Byte primitives and the atom/container builders every writer module below composes records from -- the write-side mirror of `record/header`/`record/tree`, and what this package's own test fixtures build on too.
|
|
240
|
-
| `stream/current-user` | `CurrentUserAtom`: where the live edit is, and whether the file is encrypted.
|
|
241
|
-
| `stream/current-user-write` | Writes a real `CurrentUserAtom` pointing at the single edit this writer always produces.
|
|
242
|
-
| `stream/persist` | `UserEditAtom`, `PersistDirectoryAtom`, and the persist directory the edit chain builds.
|
|
243
|
-
| `stream/persist-write` | Writes a single-edit `UserEditAtom`/`PersistDirectoryAtom` pair covering the document container and every slide container.
|
|
244
|
-
| `encryption` | `readDocumentEncryptionAtom`, `decryptPptDocumentStream` — [MS-OFFCRYPTO] 2.3.5 RC4 CryptoAPI decryption, wired against `archive-codec`'s own key derivation (see [Encryption](#encryption)).
|
|
245
|
-
| `document/document-atom` | `DocumentAtom`: slide and notes sizes, master persist references.
|
|
246
|
-
| `document/document-atom-write` | Writes a `DocumentAtom` for the one slide size every slide must share.
|
|
247
|
-
| `document/fonts` | The font collection, resolved to typeface names a `FontIndexRef` indexes.
|
|
248
|
-
| `document/fonts-write` | Writes an `Environment`/`FontCollectionContainer` from a document's own distinct font families.
|
|
249
|
-
| `document/slide-list` | `SlideListWithTextContainer`: each slide's persist reference and its placeholder texts.
|
|
250
|
-
| `document/slide-list-write` | Writes a `SlideListWithTextContainer` naming each slide's persist reference, with no placeholder texts.
|
|
251
|
-
| `document/notes-list` | `NotesListWithTextContainer`: each notes slide's persist reference and its notes identifier. Its own module rather than a parameter of `slide-list`, because the container holds no texts and `NotesPersistAtom` spends on a reserved field what `SlidePersistAtom` spends on `cTexts`.
|
|
252
|
-
| `document/notes-list-write` | Writes that container from the same `NotesPersist` shape the reader produces.
|
|
253
|
-
| `document/notes` | A `NotesContainer`: its `NotesAtom` (which slide the notes belong to) and the notes text its drawing carries.
|
|
254
|
-
| `document/notes-write` | Writes a `NotesContainer` for one slide's notes, through the same drawing writer a slide's own shapes go through.
|
|
255
|
-
| `document/master` | `readSlideAtom` (`masterIdRef`/`notesIdRef`), `buildMasterStyleTable`, and `resolveCharacterProperties`/`resolveParagraphProperties` — the master text-formatting cascade a run whose own fields are absent resolves against (see [Master and colour inheritance](#master-and-colour-inheritance)).
|
|
256
|
-
| `document/master-write` | The minimal `MainMasterContainer` and `MasterListWithTextContainer`, plus the `SlideAtom` every slide needs in order to name that master and its own notes slide.
|
|
257
|
-
| `document/color-scheme` | `readSlideSchemeColorSchemeAtom`, `resolveSchemeColor` — resolves a `ColorIndexStruct`'s scheme-slot reference against the slide's (or its master's) own colour scheme.
|
|
258
|
-
| `document/color-scheme-write` | The `SlideSchemeColorSchemeAtom` [MS-PPT] 2.9.51 gives four different containers, two of which this writer produces — the main master and every notes slide — so it belongs to neither of them.
|
|
259
|
-
| `drawing/shapes` | The OfficeArt shape tree, flattened, with every anchor resolved into slide coordinates through its enclosing groups.
|
|
260
|
-
| `drawing/shapes-write` | Writes the patriarch group and one plain, anchored `OfficeArtSpContainer` per shape.
|
|
261
|
-
| `text/atoms` | The two text-body spellings, the text-type enumeration, and the paragraph split.
|
|
262
|
-
| `text/style` | `StyleTextPropAtom`'s two run arrays and their mask-driven exception structures.
|
|
263
|
-
| `text/style-write` | Writes a `StyleTextPropAtom` from the same `StyleRun`/`ParagraphProperties`/`CharacterProperties` shapes `text/style` reads into.
|
|
264
|
-
| `content` | The mapping of PowerPoint's character-counted runs onto the schema's paragraph-owned runs.
|
|
265
|
-
| `content-write` | The inverse: a shape's `ContentBlock[]` to the flat character-counted text body and `StyleTextProps` `text/style-write` needs.
|
|
266
|
-
| `metadata` | Wraps `archive-codec`'s own `SummaryInformationProperties` <-> `LayoutMetadata` mapping with this package's `createdIso`/`modifiedIso` date validation, throwing `PptUnsupportedContentError` for a malformed one rather than letting an opaque `RangeError` escape the FILETIME conversion (see [Metadata](#metadata)).
|
|
267
|
-
| `read` | The whole read pipeline, and the `readPpt`/`readPptContent`/`readPptStreams` surface.
|
|
268
|
-
| `write` | The whole write pipeline, and the `writePpt`/`writePptContent`/`writePptStreams` surface.
|
|
269
|
-
| `units` | Master units to points, and points to master units.
|
|
270
|
-
| `errors` | `PptFormatError` for malformed input, `PptEncryptedError` for encrypted input given no password, an incorrect one, or an encryption scheme this package does not implement (anything other than RC4 CryptoAPI), `PptUnsupportedContentError` for well-formed content this package's writer cannot express.
|
|
249
|
+
| Module | What it owns |
|
|
250
|
+
| ------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
251
|
+
| `record/header` | The generic 8-byte record header and the container/atom distinction. |
|
|
252
|
+
| `record/types` | The `RecordType` values this reader dispatches on, plus the [MS-ODRAW] types the drawing walk crosses into. |
|
|
253
|
+
| `record/tree` | Offset-addressed records, sibling sequences, child walks, typed-descendant search. |
|
|
254
|
+
| `record/write` | Byte primitives and the atom/container builders every writer module below composes records from -- the write-side mirror of `record/header`/`record/tree`, and what this package's own test fixtures build on too. |
|
|
255
|
+
| `stream/current-user` | `CurrentUserAtom`: where the live edit is, and whether the file is encrypted. |
|
|
256
|
+
| `stream/current-user-write` | Writes a real `CurrentUserAtom` pointing at the single edit this writer always produces. |
|
|
257
|
+
| `stream/persist` | `UserEditAtom`, `PersistDirectoryAtom`, and the persist directory the edit chain builds. |
|
|
258
|
+
| `stream/persist-write` | Writes a single-edit `UserEditAtom`/`PersistDirectoryAtom` pair covering the document container and every slide container. |
|
|
259
|
+
| `encryption` | `readDocumentEncryptionAtom`, `decryptPptDocumentStream` — [MS-OFFCRYPTO] 2.3.5 RC4 CryptoAPI decryption, wired against `archive-codec`'s own key derivation (see [Encryption](#encryption)). |
|
|
260
|
+
| `document/document-atom` | `DocumentAtom`: slide and notes sizes, master persist references. |
|
|
261
|
+
| `document/document-atom-write` | Writes a `DocumentAtom` for the one slide size every slide must share. |
|
|
262
|
+
| `document/fonts` | The font collection, resolved to typeface names a `FontIndexRef` indexes. |
|
|
263
|
+
| `document/fonts-write` | Writes an `Environment`/`FontCollectionContainer` from a document's own distinct font families. |
|
|
264
|
+
| `document/slide-list` | `SlideListWithTextContainer`: each slide's persist reference and its placeholder texts. |
|
|
265
|
+
| `document/slide-list-write` | Writes a `SlideListWithTextContainer` naming each slide's persist reference, with no placeholder texts. |
|
|
266
|
+
| `document/notes-list` | `NotesListWithTextContainer`: each notes slide's persist reference and its notes identifier. Its own module rather than a parameter of `slide-list`, because the container holds no texts and `NotesPersistAtom` spends on a reserved field what `SlidePersistAtom` spends on `cTexts`. |
|
|
267
|
+
| `document/notes-list-write` | Writes that container from the same `NotesPersist` shape the reader produces. |
|
|
268
|
+
| `document/notes` | A `NotesContainer`: its `NotesAtom` (which slide the notes belong to) and the notes text its drawing carries. |
|
|
269
|
+
| `document/notes-write` | Writes a `NotesContainer` for one slide's notes, through the same drawing writer a slide's own shapes go through. |
|
|
270
|
+
| `document/master` | `readSlideAtom` (`masterIdRef`/`notesIdRef`), `buildMasterStyleTable`, and `resolveCharacterProperties`/`resolveParagraphProperties` — the master text-formatting cascade a run whose own fields are absent resolves against (see [Master and colour inheritance](#master-and-colour-inheritance)). |
|
|
271
|
+
| `document/master-write` | The minimal `MainMasterContainer` and `MasterListWithTextContainer`, plus the `SlideAtom` every slide needs in order to name that master and its own notes slide. |
|
|
272
|
+
| `document/color-scheme` | `readSlideSchemeColorSchemeAtom`, `resolveSchemeColor` — resolves a `ColorIndexStruct`'s scheme-slot reference against the slide's (or its master's) own colour scheme. |
|
|
273
|
+
| `document/color-scheme-write` | The `SlideSchemeColorSchemeAtom` [MS-PPT] 2.9.51 gives four different containers, two of which this writer produces — the main master and every notes slide — so it belongs to neither of them. |
|
|
274
|
+
| `drawing/shapes` | The OfficeArt shape tree, flattened, with every anchor resolved into slide coordinates through its enclosing groups. |
|
|
275
|
+
| `drawing/shapes-write` | Writes the patriarch group and one plain, anchored `OfficeArtSpContainer` per shape. |
|
|
276
|
+
| `text/atoms` | The two text-body spellings, the text-type enumeration, and the paragraph split. |
|
|
277
|
+
| `text/style` | `StyleTextPropAtom`'s two run arrays and their mask-driven exception structures. |
|
|
278
|
+
| `text/style-write` | Writes a `StyleTextPropAtom` from the same `StyleRun`/`ParagraphProperties`/`CharacterProperties` shapes `text/style` reads into. |
|
|
279
|
+
| `content` | The mapping of PowerPoint's character-counted runs onto the schema's paragraph-owned runs. |
|
|
280
|
+
| `content-write` | The inverse: a shape's `ContentBlock[]` to the flat character-counted text body and `StyleTextProps` `text/style-write` needs. |
|
|
281
|
+
| `metadata` | Wraps `archive-codec`'s own `SummaryInformationProperties` <-> `LayoutMetadata` mapping with this package's `createdIso`/`modifiedIso` date validation, throwing `PptUnsupportedContentError` for a malformed one rather than letting an opaque `RangeError` escape the FILETIME conversion (see [Metadata](#metadata)). |
|
|
282
|
+
| `read` | The whole read pipeline, and the `readPpt`/`readPptContent`/`readPptStreams` surface. |
|
|
283
|
+
| `write` | The whole write pipeline, and the `writePpt`/`writePptContent`/`writePptStreams` surface. |
|
|
284
|
+
| `units` | Master units to points, and points to master units. |
|
|
285
|
+
| `errors` | `PptFormatError` for malformed input, `PptEncryptedError` for encrypted input given no password, an incorrect one, or an encryption scheme this package does not implement (anything other than RC4 CryptoAPI), `PptUnsupportedContentError` for well-formed content this package's writer cannot express. |
|
|
286
|
+
| `diagnostics` | `PptDiagnostic`/`PptDiagnosticSink`/`NOOP_PPT_DIAGNOSTIC_SINK` and the stable `PptDiagnosticCodes` a caller branches on — the write-side channel a dropped image, block, or table span fires through, matching the shape markdown-codec's, pdf-codec's, epub-codec's, and rtf-codec's own diagnostic channels already use. |
|
|
271
287
|
|
|
272
288
|
### Every fixture is built from the specification, not captured
|
|
273
289
|
|
|
@@ -281,7 +297,7 @@ The [MS-CFB] container beneath the format is the one piece not hand-written agai
|
|
|
281
297
|
|
|
282
298
|
- Worker-isomorphic (see the [family-wide convention](../../README.md#conventions)): runtime `src/` must not import `node:*`, a bare Node builtin, or use the `Buffer` global — enforced by a `no-restricted-imports`/`no-restricted-globals` ESLint rule and exercised in CI by running a suite inside an actual `workerd` isolate (`pnpm test:workers`). Test files under `src/**/*.test.ts` and `src/test-support/` are exempt and may use Node APIs for fixtures.
|
|
283
299
|
- Only `src/index.ts` may be named `index.*` — a custom ESLint rule (`local/no-non-barrel-index`) rejects any other module using an `index` basename, since that would be a hidden entry point the `exports` map in `package.json` doesn't advertise.
|
|
284
|
-
- Every structural failure throws `PptFormatError` rather than degrading: a malformed file fails whole, never returning a partial slide list that looks complete. On the write side, content this writer
|
|
300
|
+
- Every structural failure throws `PptFormatError` rather than degrading: a malformed file fails whole, never returning a partial slide list that looks complete. On the write side, content genuinely outside this writer's own scope (a document that is not a presentation, slides that disagree on size) throws `PptUnsupportedContentError`. A block kind this writer cannot represent at all (an image, a table, a construct marker) is different: by default it is excluded from the written text body with a diagnostic naming it, never approximated, and `WritePptOptions.onUnwritableBlock: 'throw'` lets a caller opt into raising `PptUnsupportedContentError` for that case too — see [Silent drop, or a thrown error](#silent-drop-or-a-thrown-error) for why the default stays a diagnosed drop rather than a throw.
|
|
285
301
|
|
|
286
302
|
## Specification references
|
|
287
303
|
|
package/dist/base64.cjs
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/base64.ts
|
|
3
|
+
const TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
4
|
+
const DECODE = (() => {
|
|
5
|
+
const map = (/* @__PURE__ */ new Uint8Array(256)).fill(255);
|
|
6
|
+
for (let index = 0; index < 64; index += 1) map[TABLE.charCodeAt(index)] = index;
|
|
7
|
+
return map;
|
|
8
|
+
})();
|
|
9
|
+
function bytesToBase64(bytes) {
|
|
10
|
+
let out = "";
|
|
11
|
+
const len = bytes.length;
|
|
12
|
+
for (let index = 0; index < len; index += 3) {
|
|
13
|
+
const b0 = bytes[index] ?? 0;
|
|
14
|
+
const b1 = index + 1 < len ? bytes[index + 1] ?? 0 : 0;
|
|
15
|
+
const b2 = index + 2 < len ? bytes[index + 2] ?? 0 : 0;
|
|
16
|
+
out += TABLE.charAt(b0 >> 2);
|
|
17
|
+
out += TABLE.charAt((b0 & 3) << 4 | b1 >> 4);
|
|
18
|
+
out += index + 1 < len ? TABLE.charAt((b1 & 15) << 2 | b2 >> 6) : "=";
|
|
19
|
+
out += index + 2 < len ? TABLE.charAt(b2 & 63) : "=";
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
23
|
+
function base64ToBytes(base64) {
|
|
24
|
+
const clean = base64.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
25
|
+
const len = clean.length;
|
|
26
|
+
const out = new Uint8Array(len * 3 / 4 | 0);
|
|
27
|
+
let position = 0;
|
|
28
|
+
for (let index = 0; index < len; index += 4) {
|
|
29
|
+
const c0 = DECODE[clean.charCodeAt(index)] ?? 255;
|
|
30
|
+
const c1 = DECODE[clean.charCodeAt(index + 1)] ?? 255;
|
|
31
|
+
const c2 = clean.charCodeAt(index + 2);
|
|
32
|
+
const c3 = clean.charCodeAt(index + 3);
|
|
33
|
+
if (c0 === 255 || c1 === 255) throw new Error("invalid base64 input");
|
|
34
|
+
out[position++] = c0 << 2 | c1 >> 4;
|
|
35
|
+
if (c2 !== 61) {
|
|
36
|
+
const d2 = DECODE[c2] ?? 255;
|
|
37
|
+
out[position++] = (c1 & 15) << 4 | d2 >> 2;
|
|
38
|
+
if (c3 !== 61) {
|
|
39
|
+
const d3 = DECODE[c3] ?? 255;
|
|
40
|
+
out[position++] = (d2 & 3) << 6 | d3;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return out.subarray(0, position);
|
|
45
|
+
}
|
|
46
|
+
//#endregion
|
|
47
|
+
exports.base64ToBytes = base64ToBytes;
|
|
48
|
+
exports.bytesToBase64 = bytesToBase64;
|
package/dist/base64.d.ts
ADDED
package/dist/base64.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
//#region src/base64.ts
|
|
2
|
+
const TABLE = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
|
|
3
|
+
const DECODE = (() => {
|
|
4
|
+
const map = (/* @__PURE__ */ new Uint8Array(256)).fill(255);
|
|
5
|
+
for (let index = 0; index < 64; index += 1) map[TABLE.charCodeAt(index)] = index;
|
|
6
|
+
return map;
|
|
7
|
+
})();
|
|
8
|
+
function bytesToBase64(bytes) {
|
|
9
|
+
let out = "";
|
|
10
|
+
const len = bytes.length;
|
|
11
|
+
for (let index = 0; index < len; index += 3) {
|
|
12
|
+
const b0 = bytes[index] ?? 0;
|
|
13
|
+
const b1 = index + 1 < len ? bytes[index + 1] ?? 0 : 0;
|
|
14
|
+
const b2 = index + 2 < len ? bytes[index + 2] ?? 0 : 0;
|
|
15
|
+
out += TABLE.charAt(b0 >> 2);
|
|
16
|
+
out += TABLE.charAt((b0 & 3) << 4 | b1 >> 4);
|
|
17
|
+
out += index + 1 < len ? TABLE.charAt((b1 & 15) << 2 | b2 >> 6) : "=";
|
|
18
|
+
out += index + 2 < len ? TABLE.charAt(b2 & 63) : "=";
|
|
19
|
+
}
|
|
20
|
+
return out;
|
|
21
|
+
}
|
|
22
|
+
function base64ToBytes(base64) {
|
|
23
|
+
const clean = base64.replace(/[^A-Za-z0-9+/=]/g, "");
|
|
24
|
+
const len = clean.length;
|
|
25
|
+
const out = new Uint8Array(len * 3 / 4 | 0);
|
|
26
|
+
let position = 0;
|
|
27
|
+
for (let index = 0; index < len; index += 4) {
|
|
28
|
+
const c0 = DECODE[clean.charCodeAt(index)] ?? 255;
|
|
29
|
+
const c1 = DECODE[clean.charCodeAt(index + 1)] ?? 255;
|
|
30
|
+
const c2 = clean.charCodeAt(index + 2);
|
|
31
|
+
const c3 = clean.charCodeAt(index + 3);
|
|
32
|
+
if (c0 === 255 || c1 === 255) throw new Error("invalid base64 input");
|
|
33
|
+
out[position++] = c0 << 2 | c1 >> 4;
|
|
34
|
+
if (c2 !== 61) {
|
|
35
|
+
const d2 = DECODE[c2] ?? 255;
|
|
36
|
+
out[position++] = (c1 & 15) << 4 | d2 >> 2;
|
|
37
|
+
if (c3 !== 61) {
|
|
38
|
+
const d3 = DECODE[c3] ?? 255;
|
|
39
|
+
out[position++] = (d2 & 3) << 6 | d3;
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
return out.subarray(0, position);
|
|
44
|
+
}
|
|
45
|
+
//#endregion
|
|
46
|
+
export { base64ToBytes, bytesToBase64 };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/diagnostics.d.ts
|
|
2
|
+
type PptDiagnosticSeverity = "info" | "warning";
|
|
3
|
+
interface PptDiagnostic {
|
|
4
|
+
readonly code: string;
|
|
5
|
+
readonly severity: PptDiagnosticSeverity;
|
|
6
|
+
readonly message: string;
|
|
7
|
+
}
|
|
8
|
+
type PptDiagnosticSink = (diagnostic: PptDiagnostic) => void;
|
|
9
|
+
declare const NOOP_PPT_DIAGNOSTIC_SINK: PptDiagnosticSink;
|
|
10
|
+
declare const PptDiagnosticCodes: {
|
|
11
|
+
readonly IMAGE_DROPPED: "ppt/image-dropped";
|
|
12
|
+
readonly BLOCK_DROPPED: "ppt/block-dropped";
|
|
13
|
+
readonly TABLE_SPAN_DROPPED: "ppt/table-span-dropped";
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { PptDiagnosticSink as a, PptDiagnosticSeverity as i, PptDiagnostic as n, PptDiagnosticCodes as r, NOOP_PPT_DIAGNOSTIC_SINK as t };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
//#region src/diagnostics.d.ts
|
|
2
|
+
type PptDiagnosticSeverity = "info" | "warning";
|
|
3
|
+
interface PptDiagnostic {
|
|
4
|
+
readonly code: string;
|
|
5
|
+
readonly severity: PptDiagnosticSeverity;
|
|
6
|
+
readonly message: string;
|
|
7
|
+
}
|
|
8
|
+
type PptDiagnosticSink = (diagnostic: PptDiagnostic) => void;
|
|
9
|
+
declare const NOOP_PPT_DIAGNOSTIC_SINK: PptDiagnosticSink;
|
|
10
|
+
declare const PptDiagnosticCodes: {
|
|
11
|
+
readonly IMAGE_DROPPED: "ppt/image-dropped";
|
|
12
|
+
readonly BLOCK_DROPPED: "ppt/block-dropped";
|
|
13
|
+
readonly TABLE_SPAN_DROPPED: "ppt/table-span-dropped";
|
|
14
|
+
};
|
|
15
|
+
//#endregion
|
|
16
|
+
export { PptDiagnosticSink as a, PptDiagnosticSeverity as i, PptDiagnostic as n, PptDiagnosticCodes as r, NOOP_PPT_DIAGNOSTIC_SINK as t };
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
2
|
+
//#region src/diagnostics.ts
|
|
3
|
+
const NOOP_PPT_DIAGNOSTIC_SINK = () => {};
|
|
4
|
+
const PptDiagnosticCodes = {
|
|
5
|
+
IMAGE_DROPPED: "ppt/image-dropped",
|
|
6
|
+
BLOCK_DROPPED: "ppt/block-dropped",
|
|
7
|
+
TABLE_SPAN_DROPPED: "ppt/table-span-dropped"
|
|
8
|
+
};
|
|
9
|
+
//#endregion
|
|
10
|
+
exports.NOOP_PPT_DIAGNOSTIC_SINK = NOOP_PPT_DIAGNOSTIC_SINK;
|
|
11
|
+
exports.PptDiagnosticCodes = PptDiagnosticCodes;
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as PptDiagnosticSink, i as PptDiagnosticSeverity, n as PptDiagnostic, r as PptDiagnosticCodes, t as NOOP_PPT_DIAGNOSTIC_SINK } from "./diagnostics-CqxAnubw.cjs";
|
|
2
|
+
export { NOOP_PPT_DIAGNOSTIC_SINK, PptDiagnostic, PptDiagnosticCodes, PptDiagnosticSeverity, PptDiagnosticSink };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as PptDiagnosticSink, i as PptDiagnosticSeverity, n as PptDiagnostic, r as PptDiagnosticCodes, t as NOOP_PPT_DIAGNOSTIC_SINK } from "./diagnostics-CqxAnubw.js";
|
|
2
|
+
export { NOOP_PPT_DIAGNOSTIC_SINK, PptDiagnostic, PptDiagnosticCodes, PptDiagnosticSeverity, PptDiagnosticSink };
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
//#region src/diagnostics.ts
|
|
2
|
+
const NOOP_PPT_DIAGNOSTIC_SINK = () => {};
|
|
3
|
+
const PptDiagnosticCodes = {
|
|
4
|
+
IMAGE_DROPPED: "ppt/image-dropped",
|
|
5
|
+
BLOCK_DROPPED: "ppt/block-dropped",
|
|
6
|
+
TABLE_SPAN_DROPPED: "ppt/table-span-dropped"
|
|
7
|
+
};
|
|
8
|
+
//#endregion
|
|
9
|
+
export { NOOP_PPT_DIAGNOSTIC_SINK, PptDiagnosticCodes };
|
|
@@ -32,6 +32,10 @@ function resolveSchemeColor(schemeIndex, colorScheme) {
|
|
|
32
32
|
if (color === void 0) throw new require_errors.PptFormatError(`colour scheme slot ${schemeIndex} has no entry in a ${colorScheme.length}-entry colour scheme`);
|
|
33
33
|
return color;
|
|
34
34
|
}
|
|
35
|
+
function findSlideSchemeColorSchemeAtom(records) {
|
|
36
|
+
return records.find((record) => record.header.recType === 2032 && record.header.recInstance === SLIDE_SCHEME_REC_INSTANCE);
|
|
37
|
+
}
|
|
35
38
|
//#endregion
|
|
39
|
+
exports.findSlideSchemeColorSchemeAtom = findSlideSchemeColorSchemeAtom;
|
|
36
40
|
exports.readSlideSchemeColorSchemeAtom = readSlideSchemeColorSchemeAtom;
|
|
37
41
|
exports.resolveSchemeColor = resolveSchemeColor;
|
|
@@ -4,5 +4,6 @@ import { RgbColor } from "../text/style.cjs";
|
|
|
4
4
|
declare function readSlideSchemeColorSchemeAtom(record: PptRecord): readonly RgbColor[];
|
|
5
5
|
/** Resolves a scheme-slot index (0-7, see text/style.ts's own ColorIndexStruct table) against a resolved 8-entry colour scheme. Throws rather than returning undefined for an out-of-range index: readColorIndexStruct already rejects any index outside 0x00-0x07/0xFE/0xFF at parse time, so a RunColor of kind "scheme" reaching this function always carries a genuinely valid slot. */
|
|
6
6
|
declare function resolveSchemeColor(schemeIndex: number, colorScheme: readonly RgbColor[]): RgbColor;
|
|
7
|
+
declare function findSlideSchemeColorSchemeAtom(records: readonly PptRecord[]): PptRecord | undefined;
|
|
7
8
|
//#endregion
|
|
8
|
-
export { readSlideSchemeColorSchemeAtom, resolveSchemeColor };
|
|
9
|
+
export { findSlideSchemeColorSchemeAtom, readSlideSchemeColorSchemeAtom, resolveSchemeColor };
|