ppt-codec 1.3.0 → 1.4.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 CHANGED
@@ -82,6 +82,27 @@ Pictures in a separate `Pictures` stream are also RC4 CryptoAPI-encrypted per [M
82
82
 
83
83
  `writePptContent` never encrypts.
84
84
 
85
+ ## Master and colour inheritance
86
+
87
+ A real `.ppt` deck overwhelmingly relies on master-inherited formatting rather than direct, per-run formatting: a title placeholder typically states its own text and nothing else, leaving size, typeface, weight, and colour entirely to its slide's own master. `readPptStreams`/`readPptContent`/`readPpt` resolve this cascade for every run, not just report the run's own direct formatting as before.
88
+
89
+ ```ts
90
+ import { readPptContent } from "ppt-codec";
91
+
92
+ const { slides } = readPptContent(pptBytes);
93
+ // A run whose own TextCFException states neither bold nor colour still
94
+ // reports both here, resolved against its master and the slide's own
95
+ // colour scheme.
96
+ slides[0]?.shapes[0]?.blocks[0]?.runs[0]?.bold;
97
+ slides[0]?.shapes[0]?.blocks[0]?.runs[0]?.color;
98
+ ```
99
+
100
+ **Text-formatting cascade** (`document/master.ts`): a run's own `TextPFException`/`TextCFException` field wins outright when it states one. For everything it leaves unstated, resolution walks the applicable `TextMasterStyleAtom` from the run's own (clamped) outline level down to level 0, then -- if the run's own `TextTypeEnum` is a variant with no cascade of its own (`CENTER_BODY`/`HALF_BODY`/`QUARTER_BODY` retry as plain `BODY`; `CENTER_TITLE` retries as plain `TITLE`) -- repeats the same walk against the fallback type. `NOTES` and `OTHER` have no further fallback. A `TextTypeEnum` a slide's own master carries no atom for falls back to the single document-wide default `TextMasterStyleAtom` [MS-PPT] 2.9.35 states lives inside `DocumentTextInfoContainer` (a child of `Environment`), the fallback of last resort for every type.
101
+
102
+ **Multi-master resolution**: unlike this package's own writer, which only ever produces one `MainMasterContainer`, a real file can carry several (different design templates within one deck). Every slide's own `SlideAtom.masterIdRef` is read and resolved against the master list, not assumed to be the deck's only master.
103
+
104
+ **Colour-scheme resolution** (`document/color-scheme.ts`) is a separate, simpler step: a `ColorIndexStruct` naming a colour-scheme slot (`0x00`-`0x07` -- background, text, shadow, title text, fill, Accent 1, Accent 2, Accent 3) resolves against the slide's own `SlideSchemeColorSchemeAtom` when it carries one, or its master's otherwise. Unlike text formatting, this never walks a master-level cascade: [MS-PPT] mandates every slide-shaped container carry its own complete colour scheme (a slide that visually "follows the master's scheme" does so by a real producer duplicating the master's own values into it), so a slide's own scheme is the only place this reader looks first.
105
+
85
106
  ## Writing a document
86
107
 
87
108
  ```ts
@@ -102,18 +123,19 @@ const bytes = writePptContent({ metadata: {}, slides });
102
123
 
103
124
  The whole path from a file's first byte to a slide's text, record by record:
104
125
 
105
- | Layer | Records |
106
- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
107
- | Container | The `Current User` and `PowerPoint Document` streams, read through `archive-codec`'s bounded [MS-CFB] reader. |
108
- | 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. |
109
- | 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. |
110
- | Encryption | `DocumentEncryptionAtom` (`UserEditAtom.encryptSessionPersistIdRef` → the persist directory), [MS-OFFCRYPTO] 2.3.5's RC4 CryptoAPI scheme — see [Encryption](#encryption). |
111
- | Document | `DocumentContainer` → `DocumentAtom` (slide size, in master units), `DocumentTextInfoContainer`'s `FontCollectionContainer`/`FontEntityAtom` typeface names, and `SlideListWithTextContainer` (distinguished from the master and notes lists by `recInstance`, which does not run in the order the names suggest). |
112
- | Slides | `SlidePersistAtom` → the persist directory → each `SlideContainer`, and the placeholder texts the slide list carries for it. |
113
- | 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. |
114
- | 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, and `OfficeArtChildAnchor` mapped through nested `OfficeArtFSPGR` group coordinate systems. |
115
- | 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`. |
116
- | 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 when it is a literal sRGB value), 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. |
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, and `OfficeArtChildAnchor` mapped through nested `OfficeArtFSPGR` group coordinate systems. |
137
+ | 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`. |
138
+ | 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). |
117
139
 
118
140
  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.
119
141
 
@@ -122,8 +144,6 @@ Geometry is converted from master units (1/576 inch) to points on the way out, s
122
144
  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:
123
145
 
124
146
  - **`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.
125
- - **Master and layout inheritance.** A run that states no size, typeface, or weight inherits it from the master's `TextMasterStyleAtom`; this reader reports such a property as absent rather than resolving the cascade, so a run's formatting is what the slide itself states and no more.
126
- - **Scheme colours.** A `ColorIndexStruct` naming a colour-scheme slot (rather than a literal sRGB value) yields no colour, because resolving it needs the slide's `SlideSchemeColorSchemeAtom`.
127
147
  - **Per-shape text insets.** Every shape reports PowerPoint's own defaults (0.1 inch left and right, 0.05 inch top and bottom); a per-shape override lives in the shape's `OfficeArtFOPT` property table, which is not read.
128
148
  - **Images, tables, and OLE embeddings.** A picture shape, a table object, and an embedded or linked OLE object all read as a shape with geometry and no blocks. `ExObjListContainer` and the `ExOleObjStg` persist objects are not walked.
129
149
  - **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".
@@ -232,7 +252,9 @@ import { readStyleTextPropAtom } from "ppt-codec/text/style";
232
252
  | `document/notes-list-write` | Writes that container from the same `NotesPersist` shape the reader produces. |
233
253
  | `document/notes` | A `NotesContainer`: its `NotesAtom` (which slide the notes belong to) and the notes text its drawing carries. |
234
254
  | `document/notes-write` | Writes a `NotesContainer` for one slide's notes, through the same drawing writer a slide's own shapes go through. |
235
- | `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. Write-only: nothing reads a `SlideAtom` yet. |
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. |
236
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. |
237
259
  | `drawing/shapes` | The OfficeArt shape tree, flattened, with every anchor resolved into slide coordinates through its enclosing groups. |
238
260
  | `drawing/shapes-write` | Writes the patriarch group and one plain, anchored `OfficeArtSpContainer` per shape. |
@@ -24,9 +24,12 @@ function pointsToParaSpacing(pt) {
24
24
  function mapColorToPpt(color) {
25
25
  if (color === void 0) return;
26
26
  return {
27
- red: Math.round(color.r * BYTE_MAX),
28
- green: Math.round(color.g * BYTE_MAX),
29
- blue: Math.round(color.b * BYTE_MAX)
27
+ kind: "rgb",
28
+ rgb: {
29
+ red: Math.round(color.r * BYTE_MAX),
30
+ green: Math.round(color.g * BYTE_MAX),
31
+ blue: Math.round(color.b * BYTE_MAX)
32
+ }
30
33
  };
31
34
  }
32
35
  function storedRunText(text) {
@@ -23,9 +23,12 @@ function pointsToParaSpacing(pt) {
23
23
  function mapColorToPpt(color) {
24
24
  if (color === void 0) return;
25
25
  return {
26
- red: Math.round(color.r * BYTE_MAX),
27
- green: Math.round(color.g * BYTE_MAX),
28
- blue: Math.round(color.b * BYTE_MAX)
26
+ kind: "rgb",
27
+ rgb: {
28
+ red: Math.round(color.r * BYTE_MAX),
29
+ green: Math.round(color.g * BYTE_MAX),
30
+ blue: Math.round(color.b * BYTE_MAX)
31
+ }
29
32
  };
30
33
  }
31
34
  function storedRunText(text) {
package/dist/content.cjs CHANGED
@@ -2,6 +2,8 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
2
  require("./text/style.cjs");
3
3
  const require_text_atoms = require("./text/atoms.cjs");
4
4
  const require_units = require("./units.cjs");
5
+ const require_document_color_scheme = require("./document/color-scheme.cjs");
6
+ const require_document_master = require("./document/master.cjs");
5
7
  //#region src/content.ts
6
8
  const BYTE_MAX = 255;
7
9
  function mapAlignment(alignment) {
@@ -35,6 +37,10 @@ function mapColor(color) {
35
37
  b: color.blue / BYTE_MAX
36
38
  };
37
39
  }
40
+ function resolveRunColor(color, colorScheme) {
41
+ if (color === void 0) return;
42
+ return color.kind === "rgb" ? color.rgb : require_document_color_scheme.resolveSchemeColor(color.schemeIndex, colorScheme);
43
+ }
38
44
  function toExtents(runs) {
39
45
  const extents = [];
40
46
  let at = 0;
@@ -48,7 +54,7 @@ function toExtents(runs) {
48
54
  }
49
55
  return extents;
50
56
  }
51
- function runFrom(text, properties, fontNames) {
57
+ function runFrom(text, properties, fontNames, colorScheme) {
52
58
  return {
53
59
  text,
54
60
  bold: properties.bold,
@@ -56,35 +62,39 @@ function runFrom(text, properties, fontNames) {
56
62
  underline: properties.underline,
57
63
  fontFamily: properties.fontRef === void 0 ? void 0 : fontNames[properties.fontRef],
58
64
  sizePt: properties.sizePt,
59
- color: mapColor(properties.color)
65
+ color: mapColor(resolveRunColor(properties.color, colorScheme))
60
66
  };
61
67
  }
62
- function buildParagraphs(text, style, fontNames) {
68
+ function buildParagraphs(text, style, fontNames, masterStyles, textType, colorScheme) {
63
69
  const paragraphExtents = toExtents(style.paragraphRuns);
64
70
  const characterExtents = toExtents(style.characterRuns);
65
71
  return require_text_atoms.splitParagraphs(text).map((paragraph) => {
66
72
  const end = paragraph.start + paragraph.text.length;
67
- const paragraphProperties = paragraphExtents.find((extent) => paragraph.start >= extent.start && paragraph.start < extent.end)?.properties;
73
+ const rawParagraphProperties = paragraphExtents.find((extent) => paragraph.start >= extent.start && paragraph.start < extent.end)?.properties;
74
+ const indentLevel = rawParagraphProperties?.indentLevel ?? 0;
75
+ const paragraphProperties = require_document_master.resolveParagraphProperties(rawParagraphProperties, masterStyles, textType, indentLevel);
68
76
  const runs = [];
69
77
  for (const extent of characterExtents) {
70
78
  const from = Math.max(extent.start, paragraph.start);
71
79
  const to = Math.min(extent.end, end);
72
80
  if (from >= to) continue;
73
- runs.push(runFrom(paragraph.text.slice(from - paragraph.start, to - paragraph.start), extent.properties, fontNames));
81
+ const resolvedCharacterProperties = require_document_master.resolveCharacterProperties(extent.properties, masterStyles, textType, indentLevel);
82
+ runs.push(runFrom(paragraph.text.slice(from - paragraph.start, to - paragraph.start), resolvedCharacterProperties, fontNames, colorScheme));
83
+ }
84
+ if (runs.length === 0 && paragraph.text.length > 0) {
85
+ const resolvedCharacterProperties = require_document_master.resolveCharacterProperties(void 0, masterStyles, textType, indentLevel);
86
+ runs.push(runFrom(paragraph.text, resolvedCharacterProperties, fontNames, colorScheme));
74
87
  }
75
- if (runs.length === 0 && paragraph.text.length > 0) runs.push({ text: paragraph.text });
76
- const alignment = mapAlignment(paragraphProperties?.alignment);
77
- const indentLevel = paragraphProperties?.indentLevel ?? 0;
78
88
  return {
79
89
  kind: "paragraph",
80
90
  runs,
81
- alignment,
91
+ alignment: mapAlignment(paragraphProperties.alignment),
82
92
  list: indentLevel > 0 ? { level: indentLevel } : void 0,
83
- spacingBeforePt: paraSpacingToPoints(paragraphProperties?.spaceBefore),
84
- spacingAfterPt: paraSpacingToPoints(paragraphProperties?.spaceAfter),
85
- lineSpacing: paraSpacingToLineSpacing(paragraphProperties?.lineSpacing),
86
- indentLeftPt: marginOrIndentToPoints(paragraphProperties?.leftMargin),
87
- indentFirstLinePt: marginOrIndentToPoints(paragraphProperties?.indent)
93
+ spacingBeforePt: paraSpacingToPoints(paragraphProperties.spaceBefore),
94
+ spacingAfterPt: paraSpacingToPoints(paragraphProperties.spaceAfter),
95
+ lineSpacing: paraSpacingToLineSpacing(paragraphProperties.lineSpacing),
96
+ indentLeftPt: marginOrIndentToPoints(paragraphProperties.leftMargin),
97
+ indentFirstLinePt: marginOrIndentToPoints(paragraphProperties.indent)
88
98
  };
89
99
  });
90
100
  }
@@ -1,6 +1,7 @@
1
- import { StyleTextProps } from "./text/style.cjs";
1
+ import { RgbColor, StyleTextProps } from "./text/style.cjs";
2
+ import { n as MasterStyleTable } from "./master-zlMRXyPU.cjs";
2
3
  import { ContentParagraph } from "document-schema.js";
3
4
  //#region src/content.d.ts
4
- declare function buildParagraphs(text: string, style: StyleTextProps, fontNames: readonly string[]): ContentParagraph[];
5
+ declare function buildParagraphs(text: string, style: StyleTextProps, fontNames: readonly string[], masterStyles: MasterStyleTable, textType: number, colorScheme: readonly RgbColor[]): ContentParagraph[];
5
6
  //#endregion
6
7
  export { buildParagraphs };
package/dist/content.d.ts CHANGED
@@ -1,6 +1,7 @@
1
- import { StyleTextProps } from "./text/style.js";
1
+ import { RgbColor, StyleTextProps } from "./text/style.js";
2
+ import { n as MasterStyleTable } from "./master-Cs_sDM1-.js";
2
3
  import { ContentParagraph } from "document-schema.js";
3
4
  //#region src/content.d.ts
4
- declare function buildParagraphs(text: string, style: StyleTextProps, fontNames: readonly string[]): ContentParagraph[];
5
+ declare function buildParagraphs(text: string, style: StyleTextProps, fontNames: readonly string[], masterStyles: MasterStyleTable, textType: number, colorScheme: readonly RgbColor[]): ContentParagraph[];
5
6
  //#endregion
6
7
  export { buildParagraphs };
package/dist/content.js CHANGED
@@ -1,6 +1,8 @@
1
1
  import "./text/style.js";
2
2
  import { splitParagraphs } from "./text/atoms.js";
3
3
  import { masterUnitsToPoints } from "./units.js";
4
+ import { resolveSchemeColor } from "./document/color-scheme.js";
5
+ import { resolveCharacterProperties, resolveParagraphProperties } from "./document/master.js";
4
6
  //#region src/content.ts
5
7
  const BYTE_MAX = 255;
6
8
  function mapAlignment(alignment) {
@@ -34,6 +36,10 @@ function mapColor(color) {
34
36
  b: color.blue / BYTE_MAX
35
37
  };
36
38
  }
39
+ function resolveRunColor(color, colorScheme) {
40
+ if (color === void 0) return;
41
+ return color.kind === "rgb" ? color.rgb : resolveSchemeColor(color.schemeIndex, colorScheme);
42
+ }
37
43
  function toExtents(runs) {
38
44
  const extents = [];
39
45
  let at = 0;
@@ -47,7 +53,7 @@ function toExtents(runs) {
47
53
  }
48
54
  return extents;
49
55
  }
50
- function runFrom(text, properties, fontNames) {
56
+ function runFrom(text, properties, fontNames, colorScheme) {
51
57
  return {
52
58
  text,
53
59
  bold: properties.bold,
@@ -55,35 +61,39 @@ function runFrom(text, properties, fontNames) {
55
61
  underline: properties.underline,
56
62
  fontFamily: properties.fontRef === void 0 ? void 0 : fontNames[properties.fontRef],
57
63
  sizePt: properties.sizePt,
58
- color: mapColor(properties.color)
64
+ color: mapColor(resolveRunColor(properties.color, colorScheme))
59
65
  };
60
66
  }
61
- function buildParagraphs(text, style, fontNames) {
67
+ function buildParagraphs(text, style, fontNames, masterStyles, textType, colorScheme) {
62
68
  const paragraphExtents = toExtents(style.paragraphRuns);
63
69
  const characterExtents = toExtents(style.characterRuns);
64
70
  return splitParagraphs(text).map((paragraph) => {
65
71
  const end = paragraph.start + paragraph.text.length;
66
- const paragraphProperties = paragraphExtents.find((extent) => paragraph.start >= extent.start && paragraph.start < extent.end)?.properties;
72
+ const rawParagraphProperties = paragraphExtents.find((extent) => paragraph.start >= extent.start && paragraph.start < extent.end)?.properties;
73
+ const indentLevel = rawParagraphProperties?.indentLevel ?? 0;
74
+ const paragraphProperties = resolveParagraphProperties(rawParagraphProperties, masterStyles, textType, indentLevel);
67
75
  const runs = [];
68
76
  for (const extent of characterExtents) {
69
77
  const from = Math.max(extent.start, paragraph.start);
70
78
  const to = Math.min(extent.end, end);
71
79
  if (from >= to) continue;
72
- runs.push(runFrom(paragraph.text.slice(from - paragraph.start, to - paragraph.start), extent.properties, fontNames));
80
+ const resolvedCharacterProperties = resolveCharacterProperties(extent.properties, masterStyles, textType, indentLevel);
81
+ runs.push(runFrom(paragraph.text.slice(from - paragraph.start, to - paragraph.start), resolvedCharacterProperties, fontNames, colorScheme));
82
+ }
83
+ if (runs.length === 0 && paragraph.text.length > 0) {
84
+ const resolvedCharacterProperties = resolveCharacterProperties(void 0, masterStyles, textType, indentLevel);
85
+ runs.push(runFrom(paragraph.text, resolvedCharacterProperties, fontNames, colorScheme));
73
86
  }
74
- if (runs.length === 0 && paragraph.text.length > 0) runs.push({ text: paragraph.text });
75
- const alignment = mapAlignment(paragraphProperties?.alignment);
76
- const indentLevel = paragraphProperties?.indentLevel ?? 0;
77
87
  return {
78
88
  kind: "paragraph",
79
89
  runs,
80
- alignment,
90
+ alignment: mapAlignment(paragraphProperties.alignment),
81
91
  list: indentLevel > 0 ? { level: indentLevel } : void 0,
82
- spacingBeforePt: paraSpacingToPoints(paragraphProperties?.spaceBefore),
83
- spacingAfterPt: paraSpacingToPoints(paragraphProperties?.spaceAfter),
84
- lineSpacing: paraSpacingToLineSpacing(paragraphProperties?.lineSpacing),
85
- indentLeftPt: marginOrIndentToPoints(paragraphProperties?.leftMargin),
86
- indentFirstLinePt: marginOrIndentToPoints(paragraphProperties?.indent)
92
+ spacingBeforePt: paraSpacingToPoints(paragraphProperties.spaceBefore),
93
+ spacingAfterPt: paraSpacingToPoints(paragraphProperties.spaceAfter),
94
+ lineSpacing: paraSpacingToLineSpacing(paragraphProperties.lineSpacing),
95
+ indentLeftPt: marginOrIndentToPoints(paragraphProperties.leftMargin),
96
+ indentFirstLinePt: marginOrIndentToPoints(paragraphProperties.indent)
87
97
  };
88
98
  });
89
99
  }
@@ -0,0 +1,37 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("../errors.cjs");
3
+ const require_record_types = require("../record/types.cjs");
4
+ require("../text/style.cjs");
5
+ //#region src/document/color-scheme.ts
6
+ const SLIDE_SCHEME_REC_INSTANCE = 1;
7
+ const COLOR_STRUCT_SIZE = 4;
8
+ function readSlideSchemeColorSchemeAtom(record) {
9
+ if (record.header.recType !== 2032) throw new require_errors.PptFormatError(`expected RT_ColorSchemeAtom (0x${require_record_types.RT_ColorSchemeAtom.toString(16)}) at offset ${record.offset}, found record type 0x${record.header.recType.toString(16)}`);
10
+ if (record.header.recInstance !== SLIDE_SCHEME_REC_INSTANCE) throw new require_errors.PptFormatError(`ColorSchemeAtom at offset ${record.offset} declares recInstance 0x${record.header.recInstance.toString(16)}, not the SlideSchemeColorSchemeAtom's own 0x${SLIDE_SCHEME_REC_INSTANCE.toString(16)}`);
11
+ const expectedLength = 32;
12
+ if (record.data.length !== expectedLength) throw new require_errors.PptFormatError(`SlideSchemeColorSchemeAtom at offset ${record.offset} carries ${record.data.length} bytes, not the mandated ${expectedLength} (8 four-byte scheme slots)`);
13
+ const { data } = record;
14
+ const colors = [];
15
+ for (let slot = 0; slot < 8; slot += 1) {
16
+ const at = slot * COLOR_STRUCT_SIZE;
17
+ const red = data[at];
18
+ const green = data[at + 1];
19
+ const blue = data[at + 2];
20
+ if (red === void 0 || green === void 0 || blue === void 0) throw new require_errors.PptFormatError(`SlideSchemeColorSchemeAtom slot ${slot} at offset ${record.offset + at} is missing bytes`);
21
+ colors.push({
22
+ red,
23
+ green,
24
+ blue
25
+ });
26
+ }
27
+ return colors;
28
+ }
29
+ /** 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. */
30
+ function resolveSchemeColor(schemeIndex, colorScheme) {
31
+ const color = colorScheme[schemeIndex];
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
+ return color;
34
+ }
35
+ //#endregion
36
+ exports.readSlideSchemeColorSchemeAtom = readSlideSchemeColorSchemeAtom;
37
+ exports.resolveSchemeColor = resolveSchemeColor;
@@ -0,0 +1,8 @@
1
+ import { t as PptRecord } from "../tree-Du_LXAF0.cjs";
2
+ import { RgbColor } from "../text/style.cjs";
3
+ //#region src/document/color-scheme.d.ts
4
+ declare function readSlideSchemeColorSchemeAtom(record: PptRecord): readonly RgbColor[];
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
+ declare function resolveSchemeColor(schemeIndex: number, colorScheme: readonly RgbColor[]): RgbColor;
7
+ //#endregion
8
+ export { readSlideSchemeColorSchemeAtom, resolveSchemeColor };
@@ -0,0 +1,8 @@
1
+ import { t as PptRecord } from "../tree-PMcPNgd-.js";
2
+ import { RgbColor } from "../text/style.js";
3
+ //#region src/document/color-scheme.d.ts
4
+ declare function readSlideSchemeColorSchemeAtom(record: PptRecord): readonly RgbColor[];
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
+ declare function resolveSchemeColor(schemeIndex: number, colorScheme: readonly RgbColor[]): RgbColor;
7
+ //#endregion
8
+ export { readSlideSchemeColorSchemeAtom, resolveSchemeColor };
@@ -0,0 +1,35 @@
1
+ import { PptFormatError } from "../errors.js";
2
+ import { RT_ColorSchemeAtom } from "../record/types.js";
3
+ import "../text/style.js";
4
+ //#region src/document/color-scheme.ts
5
+ const SLIDE_SCHEME_REC_INSTANCE = 1;
6
+ const COLOR_STRUCT_SIZE = 4;
7
+ function readSlideSchemeColorSchemeAtom(record) {
8
+ if (record.header.recType !== 2032) throw new PptFormatError(`expected RT_ColorSchemeAtom (0x${RT_ColorSchemeAtom.toString(16)}) at offset ${record.offset}, found record type 0x${record.header.recType.toString(16)}`);
9
+ if (record.header.recInstance !== SLIDE_SCHEME_REC_INSTANCE) throw new PptFormatError(`ColorSchemeAtom at offset ${record.offset} declares recInstance 0x${record.header.recInstance.toString(16)}, not the SlideSchemeColorSchemeAtom's own 0x${SLIDE_SCHEME_REC_INSTANCE.toString(16)}`);
10
+ const expectedLength = 32;
11
+ if (record.data.length !== expectedLength) throw new PptFormatError(`SlideSchemeColorSchemeAtom at offset ${record.offset} carries ${record.data.length} bytes, not the mandated ${expectedLength} (8 four-byte scheme slots)`);
12
+ const { data } = record;
13
+ const colors = [];
14
+ for (let slot = 0; slot < 8; slot += 1) {
15
+ const at = slot * COLOR_STRUCT_SIZE;
16
+ const red = data[at];
17
+ const green = data[at + 1];
18
+ const blue = data[at + 2];
19
+ if (red === void 0 || green === void 0 || blue === void 0) throw new PptFormatError(`SlideSchemeColorSchemeAtom slot ${slot} at offset ${record.offset + at} is missing bytes`);
20
+ colors.push({
21
+ red,
22
+ green,
23
+ blue
24
+ });
25
+ }
26
+ return colors;
27
+ }
28
+ /** 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. */
29
+ function resolveSchemeColor(schemeIndex, colorScheme) {
30
+ const color = colorScheme[schemeIndex];
31
+ if (color === void 0) throw new PptFormatError(`colour scheme slot ${schemeIndex} has no entry in a ${colorScheme.length}-entry colour scheme`);
32
+ return color;
33
+ }
34
+ //#endregion
35
+ export { readSlideSchemeColorSchemeAtom, resolveSchemeColor };
@@ -0,0 +1,82 @@
1
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
2
+ const require_errors = require("../errors.cjs");
3
+ const require_record_types = require("../record/types.cjs");
4
+ require("../text/atoms.cjs");
5
+ //#region src/document/master.ts
6
+ /** Builds one MainMasterContainer's own MasterStyleTable from its direct-child TextMasterStyleAtoms, falling back to the document-wide default (the single OTHER-typed TextMasterStyleAtom [MS-PPT] states lives in the DocumentTextInfoContainer inside Environment) for any TextTypeEnum member the master itself carries no atom for. A real master this package's own writer produces always states TITLE/BODY/NOTES explicitly (master-write.ts's own comment), so the document default is realistically only ever consulted for OTHER-typed runs or a third-party master that omits one of the three -- but [MS-PPT] 2.9.35 makes it the genuine fallback of last resort for every type, not a special case for OTHER alone. */
7
+ function buildMasterStyleTable(masterAtoms, documentDefault) {
8
+ const byType = /* @__PURE__ */ new Map();
9
+ if (documentDefault !== void 0) byType.set(documentDefault.textType, documentDefault.levels);
10
+ for (const atom of masterAtoms) byType.set(atom.textType, atom.levels);
11
+ return { byType };
12
+ }
13
+ function typeFamilyFallback(textType) {
14
+ switch (textType) {
15
+ case 5:
16
+ case 7:
17
+ case 8: return 1;
18
+ case 6: return 0;
19
+ default: return;
20
+ }
21
+ }
22
+ function orderedMasterLevels(table, textType, indentLevel) {
23
+ const levelsFor = (type) => {
24
+ const levels = table.byType.get(type);
25
+ if (levels === void 0 || levels.length === 0) return [];
26
+ const clampedLevel = Math.min(indentLevel, levels.length - 1);
27
+ return levels.slice(0, clampedLevel + 1).reverse();
28
+ };
29
+ const fallbackType = typeFamilyFallback(textType);
30
+ return fallbackType === void 0 ? levelsFor(textType) : [...levelsFor(textType), ...levelsFor(fallbackType)];
31
+ }
32
+ function firstDefined(candidates, get) {
33
+ for (const candidate of candidates) {
34
+ if (candidate === void 0) continue;
35
+ const value = get(candidate);
36
+ if (value !== void 0) return value;
37
+ }
38
+ }
39
+ /** Resolves a run's own ParagraphProperties (possibly stating nothing at all, when the run's paragraph itself carries no TextPFException of its own) against the master cascade -- every field the run itself states wins outright; every field it doesn't falls through to the first master level that does. indentLevel is never itself resolved from the cascade: it is the run's own stated (or default-zero) outline depth, and is what selects which master levels apply in the first place. */
40
+ function resolveParagraphProperties(run, table, textType, indentLevel) {
41
+ const candidates = [run, ...orderedMasterLevels(table, textType, indentLevel).map((level) => level.paragraph)];
42
+ return {
43
+ indentLevel,
44
+ alignment: firstDefined(candidates, (c) => c.alignment),
45
+ lineSpacing: firstDefined(candidates, (c) => c.lineSpacing),
46
+ spaceBefore: firstDefined(candidates, (c) => c.spaceBefore),
47
+ spaceAfter: firstDefined(candidates, (c) => c.spaceAfter),
48
+ leftMargin: firstDefined(candidates, (c) => c.leftMargin),
49
+ indent: firstDefined(candidates, (c) => c.indent)
50
+ };
51
+ }
52
+ /** The character-property counterpart of resolveParagraphProperties -- see that function's own comment for the cascade this implements. `color` resolves to whichever RunColor (literal or still-unresolved scheme reference) the cascade finds first; converting a scheme reference to an actual RgbColor is document/color-scheme.ts's own concern, deliberately kept separate since it needs the slide's colour scheme rather than anything the text-formatting cascade itself touches. */
53
+ function resolveCharacterProperties(run, table, textType, indentLevel) {
54
+ const candidates = [run, ...orderedMasterLevels(table, textType, indentLevel).map((level) => level.character)];
55
+ return {
56
+ bold: firstDefined(candidates, (c) => c.bold),
57
+ italic: firstDefined(candidates, (c) => c.italic),
58
+ underline: firstDefined(candidates, (c) => c.underline),
59
+ shadow: firstDefined(candidates, (c) => c.shadow),
60
+ emboss: firstDefined(candidates, (c) => c.emboss),
61
+ fontRef: firstDefined(candidates, (c) => c.fontRef),
62
+ sizePt: firstDefined(candidates, (c) => c.sizePt),
63
+ color: firstDefined(candidates, (c) => c.color)
64
+ };
65
+ }
66
+ function readSlideAtom(record) {
67
+ if (record.header.recType !== 1007) throw new require_errors.PptFormatError(`expected RT_SlideAtom (0x${require_record_types.RT_SlideAtom.toString(16)}) at offset ${record.offset}, found record type 0x${record.header.recType.toString(16)}`);
68
+ const MASTER_ID_REF_OFFSET = 12;
69
+ const NOTES_ID_REF_OFFSET = 16;
70
+ if (record.data.length < 20) throw new require_errors.PptFormatError(`SlideAtom at offset ${record.offset} carries ${record.data.length} bytes, too few for its masterIdRef/notesIdRef fields`);
71
+ const { data } = record;
72
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
73
+ return {
74
+ masterIdRef: view.getUint32(MASTER_ID_REF_OFFSET, true),
75
+ notesIdRef: view.getUint32(NOTES_ID_REF_OFFSET, true)
76
+ };
77
+ }
78
+ //#endregion
79
+ exports.buildMasterStyleTable = buildMasterStyleTable;
80
+ exports.readSlideAtom = readSlideAtom;
81
+ exports.resolveCharacterProperties = resolveCharacterProperties;
82
+ exports.resolveParagraphProperties = resolveParagraphProperties;
@@ -0,0 +1,2 @@
1
+ import { a as readSlideAtom, i as buildMasterStyleTable, n as MasterStyleTable, o as resolveCharacterProperties, r as SlideAtomInfo, s as resolveParagraphProperties, t as MasterInfo } from "../master-zlMRXyPU.cjs";
2
+ export { MasterInfo, MasterStyleTable, SlideAtomInfo, buildMasterStyleTable, readSlideAtom, resolveCharacterProperties, resolveParagraphProperties };
@@ -0,0 +1,2 @@
1
+ import { a as readSlideAtom, i as buildMasterStyleTable, n as MasterStyleTable, o as resolveCharacterProperties, r as SlideAtomInfo, s as resolveParagraphProperties, t as MasterInfo } from "../master-Cs_sDM1-.js";
2
+ export { MasterInfo, MasterStyleTable, SlideAtomInfo, buildMasterStyleTable, readSlideAtom, resolveCharacterProperties, resolveParagraphProperties };
@@ -0,0 +1,78 @@
1
+ import { PptFormatError } from "../errors.js";
2
+ import { RT_SlideAtom } from "../record/types.js";
3
+ import "../text/atoms.js";
4
+ //#region src/document/master.ts
5
+ /** Builds one MainMasterContainer's own MasterStyleTable from its direct-child TextMasterStyleAtoms, falling back to the document-wide default (the single OTHER-typed TextMasterStyleAtom [MS-PPT] states lives in the DocumentTextInfoContainer inside Environment) for any TextTypeEnum member the master itself carries no atom for. A real master this package's own writer produces always states TITLE/BODY/NOTES explicitly (master-write.ts's own comment), so the document default is realistically only ever consulted for OTHER-typed runs or a third-party master that omits one of the three -- but [MS-PPT] 2.9.35 makes it the genuine fallback of last resort for every type, not a special case for OTHER alone. */
6
+ function buildMasterStyleTable(masterAtoms, documentDefault) {
7
+ const byType = /* @__PURE__ */ new Map();
8
+ if (documentDefault !== void 0) byType.set(documentDefault.textType, documentDefault.levels);
9
+ for (const atom of masterAtoms) byType.set(atom.textType, atom.levels);
10
+ return { byType };
11
+ }
12
+ function typeFamilyFallback(textType) {
13
+ switch (textType) {
14
+ case 5:
15
+ case 7:
16
+ case 8: return 1;
17
+ case 6: return 0;
18
+ default: return;
19
+ }
20
+ }
21
+ function orderedMasterLevels(table, textType, indentLevel) {
22
+ const levelsFor = (type) => {
23
+ const levels = table.byType.get(type);
24
+ if (levels === void 0 || levels.length === 0) return [];
25
+ const clampedLevel = Math.min(indentLevel, levels.length - 1);
26
+ return levels.slice(0, clampedLevel + 1).reverse();
27
+ };
28
+ const fallbackType = typeFamilyFallback(textType);
29
+ return fallbackType === void 0 ? levelsFor(textType) : [...levelsFor(textType), ...levelsFor(fallbackType)];
30
+ }
31
+ function firstDefined(candidates, get) {
32
+ for (const candidate of candidates) {
33
+ if (candidate === void 0) continue;
34
+ const value = get(candidate);
35
+ if (value !== void 0) return value;
36
+ }
37
+ }
38
+ /** Resolves a run's own ParagraphProperties (possibly stating nothing at all, when the run's paragraph itself carries no TextPFException of its own) against the master cascade -- every field the run itself states wins outright; every field it doesn't falls through to the first master level that does. indentLevel is never itself resolved from the cascade: it is the run's own stated (or default-zero) outline depth, and is what selects which master levels apply in the first place. */
39
+ function resolveParagraphProperties(run, table, textType, indentLevel) {
40
+ const candidates = [run, ...orderedMasterLevels(table, textType, indentLevel).map((level) => level.paragraph)];
41
+ return {
42
+ indentLevel,
43
+ alignment: firstDefined(candidates, (c) => c.alignment),
44
+ lineSpacing: firstDefined(candidates, (c) => c.lineSpacing),
45
+ spaceBefore: firstDefined(candidates, (c) => c.spaceBefore),
46
+ spaceAfter: firstDefined(candidates, (c) => c.spaceAfter),
47
+ leftMargin: firstDefined(candidates, (c) => c.leftMargin),
48
+ indent: firstDefined(candidates, (c) => c.indent)
49
+ };
50
+ }
51
+ /** The character-property counterpart of resolveParagraphProperties -- see that function's own comment for the cascade this implements. `color` resolves to whichever RunColor (literal or still-unresolved scheme reference) the cascade finds first; converting a scheme reference to an actual RgbColor is document/color-scheme.ts's own concern, deliberately kept separate since it needs the slide's colour scheme rather than anything the text-formatting cascade itself touches. */
52
+ function resolveCharacterProperties(run, table, textType, indentLevel) {
53
+ const candidates = [run, ...orderedMasterLevels(table, textType, indentLevel).map((level) => level.character)];
54
+ return {
55
+ bold: firstDefined(candidates, (c) => c.bold),
56
+ italic: firstDefined(candidates, (c) => c.italic),
57
+ underline: firstDefined(candidates, (c) => c.underline),
58
+ shadow: firstDefined(candidates, (c) => c.shadow),
59
+ emboss: firstDefined(candidates, (c) => c.emboss),
60
+ fontRef: firstDefined(candidates, (c) => c.fontRef),
61
+ sizePt: firstDefined(candidates, (c) => c.sizePt),
62
+ color: firstDefined(candidates, (c) => c.color)
63
+ };
64
+ }
65
+ function readSlideAtom(record) {
66
+ if (record.header.recType !== 1007) throw new PptFormatError(`expected RT_SlideAtom (0x${RT_SlideAtom.toString(16)}) at offset ${record.offset}, found record type 0x${record.header.recType.toString(16)}`);
67
+ const MASTER_ID_REF_OFFSET = 12;
68
+ const NOTES_ID_REF_OFFSET = 16;
69
+ if (record.data.length < 20) throw new PptFormatError(`SlideAtom at offset ${record.offset} carries ${record.data.length} bytes, too few for its masterIdRef/notesIdRef fields`);
70
+ const { data } = record;
71
+ const view = new DataView(data.buffer, data.byteOffset, data.byteLength);
72
+ return {
73
+ masterIdRef: view.getUint32(MASTER_ID_REF_OFFSET, true),
74
+ notesIdRef: view.getUint32(NOTES_ID_REF_OFFSET, true)
75
+ };
76
+ }
77
+ //#endregion
78
+ export { buildMasterStyleTable, readSlideAtom, resolveCharacterProperties, resolveParagraphProperties };
package/dist/index.cjs CHANGED
@@ -54,6 +54,8 @@ exports.CF_SYMBOL_TYPEFACE = require_text_style.CF_SYMBOL_TYPEFACE;
54
54
  exports.CF_TYPEFACE = require_text_style.CF_TYPEFACE;
55
55
  exports.CF_UNDERLINE = require_text_style.CF_UNDERLINE;
56
56
  exports.COLOR_INDEX_SRGB = require_text_style.COLOR_INDEX_SRGB;
57
+ exports.COLOR_INDEX_UNDEFINED = require_text_style.COLOR_INDEX_UNDEFINED;
58
+ exports.COLOR_SCHEME_SLOT_COUNT = require_text_style.COLOR_SCHEME_SLOT_COUNT;
57
59
  exports.CONTAINER_REC_VER = require_record_header.CONTAINER_REC_VER;
58
60
  exports.CURRENT_USER_DOC_FILE_VERSION = require_stream_current_user.CURRENT_USER_DOC_FILE_VERSION;
59
61
  exports.CURRENT_USER_FIXED_SIZE = require_stream_current_user.CURRENT_USER_FIXED_SIZE;
@@ -190,6 +192,7 @@ exports.readSlideListWithText = require_document_slide_list.readSlideListWithTex
190
192
  exports.readStyleTextPropAtom = require_text_style.readStyleTextPropAtom;
191
193
  exports.readTextBody = require_text_atoms.readTextBody;
192
194
  exports.readTextHeaderAtom = require_text_atoms.readTextHeaderAtom;
195
+ exports.readTextMasterStyleAtom = require_text_style.readTextMasterStyleAtom;
193
196
  exports.readUserEditAtom = require_stream_persist.readUserEditAtom;
194
197
  exports.resolvePersistObject = require_stream_persist.resolvePersistObject;
195
198
  exports.splitParagraphs = require_text_atoms.splitParagraphs;