reamkit 1.0.2 → 1.2.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.
Files changed (53) hide show
  1. package/README.md +12 -6
  2. package/dist/esm/{layout → core}/arc-to-bezier.d.ts +1 -1
  3. package/dist/esm/{layout → core}/arc-to-bezier.js +2 -2
  4. package/dist/esm/core/bytes.d.ts +1 -0
  5. package/dist/esm/core/bytes.js +6 -1
  6. package/dist/esm/core/converter/facade.d.ts +2 -2
  7. package/dist/esm/core/converter/facade.js +12 -0
  8. package/dist/esm/core/converter/project.js +2 -0
  9. package/dist/esm/core/converter/ream.d.ts +1 -1
  10. package/dist/esm/core/converter/ream.js +10 -0
  11. package/dist/esm/core/document-model/index.d.ts +1 -1
  12. package/dist/esm/core/document-model/types.d.ts +58 -0
  13. package/dist/esm/{layout → core/drawingml}/chart-geometry.d.ts +1 -1
  14. package/dist/esm/{layout → core/drawingml}/preset-geometry.d.ts +2 -2
  15. package/dist/esm/{layout → core/drawingml}/preset-geometry.js +3 -3
  16. package/dist/esm/core/drawingml/shape-render.d.ts +8 -0
  17. package/dist/esm/core/drawingml/shape-render.js +71 -0
  18. package/dist/esm/core/ir/features.d.ts +1 -0
  19. package/dist/esm/core/ir/features.js +1 -0
  20. package/dist/esm/core/ir/flow.d.ts +3 -0
  21. package/dist/esm/core/links.d.ts +6 -0
  22. package/dist/esm/core/links.js +19 -0
  23. package/dist/esm/core/numbering/apply.js +3 -1
  24. package/dist/esm/core/style-cascade/index.d.ts +1 -0
  25. package/dist/esm/core/style-cascade/index.js +1 -0
  26. package/dist/esm/core/style-cascade/table.d.ts +2 -0
  27. package/dist/esm/core/style-cascade/table.js +161 -0
  28. package/dist/esm/core/vector.d.ts +2 -0
  29. package/dist/esm/core/vector.js +19 -1
  30. package/dist/esm/html/html-writer.d.ts +4 -0
  31. package/dist/esm/html/html-writer.js +464 -0
  32. package/dist/esm/index.d.ts +1 -0
  33. package/dist/esm/index.js +2 -1
  34. package/dist/esm/layout/page-doc.d.ts +3 -0
  35. package/dist/esm/layout/styled-layout.d.ts +19 -3
  36. package/dist/esm/layout/styled-layout.js +533 -197
  37. package/dist/esm/pdf/struct-tree.d.ts +7 -1
  38. package/dist/esm/pdf/struct-tree.js +22 -6
  39. package/dist/esm/pdf/styled-page-emitter.d.ts +1 -1
  40. package/dist/esm/pdf/styled-page-emitter.js +161 -29
  41. package/dist/esm/pdf/xmp.d.ts +1 -0
  42. package/dist/esm/pdf/xmp.js +4 -0
  43. package/dist/esm/svg/svg-writer.js +3 -11
  44. package/dist/esm/word/document-parser.d.ts +3 -0
  45. package/dist/esm/word/document-parser.js +210 -23
  46. package/dist/esm/word/docx-reader.js +30 -4
  47. package/dist/esm/word/drawing-parser.d.ts +4 -1
  48. package/dist/esm/word/drawing-parser.js +53 -2
  49. package/dist/esm/word/index.d.ts +2 -2
  50. package/dist/esm/word/styles-parser.js +134 -1
  51. package/dist/esm/word/table-parser.js +35 -0
  52. package/package.json +1 -1
  53. /package/dist/esm/{layout → core/drawingml}/chart-geometry.js +0 -0
package/README.md CHANGED
@@ -33,6 +33,7 @@ const bytes = new Uint8Array(await file.arrayBuffer());
33
33
  const doc = Ream.parse(bytes); // docx or xlsx — sniffed
34
34
  const pdf = await doc.convert('pdf'); // async — fetches a font if needed
35
35
  const svg = await doc.convert('svg'); // same parse, different target
36
+ const html = await doc.convert('html'); // flowed HTML — needs no fonts at all
36
37
 
37
38
  // Hand the bytes to the browser: preview, download, upload, …
38
39
  const url = URL.createObjectURL(new Blob([pdf], { type: 'application/pdf' }));
@@ -84,7 +85,9 @@ are always used first — glyph-exact, no substitution.
84
85
  ### Archival PDF/A + embedded source
85
86
 
86
87
  The whole PDF/A family is supported (1a/1b, 2a/2b/2u, 3a/3b/3u —
87
- veraPDF-validated). PDF/A-3 can carry the source document inside the PDF:
88
+ veraPDF-validated), plus accessible **PDF/UA-1** (`pdfUA: true`, also
89
+ veraPDF-validated and combinable with PDF/A in one file). PDF/A-3 can carry
90
+ the source document inside the PDF:
88
91
 
89
92
  ```ts
90
93
  const { bytes: pdfa, losses } = await doc.convertWithReport('pdf', {
@@ -147,7 +150,7 @@ automatically from the document's `docProps/core.xml`), `attachments`
147
150
 
148
151
  ### Lower-level APIs
149
152
 
150
- - `docxReader` / `xlsxReader`, `svgWriter` — the `@experimental` reader/writer
153
+ - `docxReader` / `xlsxReader`, `svgWriter`, `htmlWriter` — the `@experimental` reader/writer
151
154
  interfaces of the interlayer, for building custom pipelines (and keeping
152
155
  unused formats out of your bundle); `layoutStyledDocument` produces the
153
156
  frozen page model (`PageItem` pages in a top-left `Pt` frame) they consume.
@@ -156,14 +159,17 @@ automatically from the document's `docProps/core.xml`), `attachments`
156
159
 
157
160
  ## Scope
158
161
 
159
- Implemented: WordprocessingML text/styles/tables/lists/multi-section
160
- layout/headers-footers/images/tracked changes, SpreadsheetML grids,
162
+ Implemented: WordprocessingML text/styles/tables (incl. table styles)/lists/
163
+ multi-section and multi-column layout/headers-footers (incl. PAGE/NUMPAGES
164
+ fields)/footnotes and endnotes/hyperlinks and bookmarks/floating drawings/
165
+ images/tracked changes, SpreadsheetML grids,
161
166
  number formats and the print model (gridlines, print area, fit-to-page,
162
167
  repeated titles, page breaks), DrawingML shapes and charts, OMML math,
163
168
  Type0+CIDFontType2 embedding with subsetting, Knuth-Plass line breaking,
164
169
  Liang hyphenation, OpenType ligatures/kerning + Arabic cursive joining,
165
- BiDi (UAX #9), tagged PDF, PDF/A-1/2/3 (a/b/u), digital signatures
166
- (PKCS#7/ECDSA/PAdES/RFC 3161), SVG page preview.
170
+ BiDi (UAX #9), hyperlinks (PDF link annotations + HTML anchors,
171
+ scheme-allowlisted), tagged PDF, PDF/A-1/2/3 (a/b/u), digital signatures
172
+ (PKCS#7/ECDSA/PAdES/RFC 3161), SVG page preview, flowed HTML export.
167
173
 
168
174
  See `handoff.md` for the full feature matrix and known limitations.
169
175
 
@@ -1,4 +1,4 @@
1
- import { PathSegment } from '../core/vector.js';
1
+ import { PathSegment } from './vector.js';
2
2
  export declare function arcPoint(cx: number, cy: number, rx: number, ry: number, angleRad: number): readonly [number, number];
3
3
  export declare function arcToBeziers(cx: number, cy: number, rx: number, ry: number, startAngleRad: number, sweepAngleRad: number): Array<PathSegment>;
4
4
  export declare function ellipseSegments(w: number, h: number): ReadonlyArray<PathSegment>;
@@ -1,5 +1,5 @@
1
- import { PathBuilder } from "../core/vector.js";
2
- //#region src/layout/arc-to-bezier.ts
1
+ import { PathBuilder } from "./vector.js";
2
+ //#region src/core/arc-to-bezier.ts
3
3
  function arcPoint(cx, cy, rx, ry, angleRad) {
4
4
  return [cx + rx * Math.cos(angleRad), cy + ry * Math.sin(angleRad)];
5
5
  }
@@ -1 +1,2 @@
1
+ export declare function toBase64(bytes: Uint8Array): string;
1
2
  export declare function bytesInclude(haystack: Uint8Array, needle: string): boolean;
@@ -1,4 +1,9 @@
1
1
  //#region src/core/bytes.ts
2
+ function toBase64(bytes) {
3
+ let bin = "";
4
+ for (const b of bytes) bin += String.fromCharCode(b);
5
+ return btoa(bin);
6
+ }
2
7
  function bytesInclude(haystack, needle) {
3
8
  const n = new TextEncoder().encode(needle);
4
9
  outer: for (let i = 0; i + n.length <= haystack.length; i++) {
@@ -8,4 +13,4 @@ function bytesInclude(haystack, needle) {
8
13
  return false;
9
14
  }
10
15
  //#endregion
11
- export { bytesInclude };
16
+ export { bytesInclude, toBase64 };
@@ -5,8 +5,8 @@ import { FontProvider } from '../fonts/provider.js';
5
5
  import { Loss, LossReport } from '../ir/index.js';
6
6
  import { FontBytesByVariant } from '../font/index.js';
7
7
  export interface ConvertOptions extends ConvertDocxOptions {
8
- /** Target format: 'pdf' (default) or 'svg' (page-stack preview). */
9
- readonly to?: 'pdf' | 'svg';
8
+ /** Target: 'pdf' (default), 'svg' (page-stack preview) or 'html' (flowed). */
9
+ readonly to?: 'pdf' | 'svg' | 'html';
10
10
  /**
11
11
  * Strict mode (handoff v1 §5): throw ConversionLossError on the first
12
12
  * recorded loss instead of returning it in the report.
@@ -6,6 +6,7 @@ import { docxReader } from "../../word/docx-reader.js";
6
6
  import { xlsxReader } from "../../excel/xlsx-reader.js";
7
7
  import { chainProviders } from "../fonts/provider.js";
8
8
  import { flowRenderOptions } from "./project.js";
9
+ import { writeHtml } from "../../html/html-writer.js";
9
10
  import { writeSvg } from "../../svg/svg-writer.js";
10
11
  import { convertDocxToPdf } from "../../word/docx-to-pdf.js";
11
12
  import { convertXlsxToPdf } from "../../excel/xlsx-to-pdf.js";
@@ -19,6 +20,17 @@ function createConverter(opts = {}) {
19
20
  const reader = detect(bytes);
20
21
  if (!reader) throw new Error("Unrecognized input format (no registered reader sniffs these bytes)");
21
22
  const losses = [];
23
+ if (to === "html") {
24
+ const { doc: flow, losses: readLosses } = reader.read(bytes);
25
+ losses.push(...readLosses);
26
+ const html = writeHtml(flow);
27
+ losses.push(...html.losses);
28
+ if (strict && losses.length > 0) throw new ConversionLossError(losses[0]);
29
+ return {
30
+ bytes: html.bytes,
31
+ losses
32
+ };
33
+ }
22
34
  let conv = rest;
23
35
  if (fontProviders && fontProviders.length > 0 && !rest.fonts && !rest.fontBytes) {
24
36
  const { fonts, loss } = await resolveFontsViaChain(fontProviders);
@@ -7,6 +7,8 @@ function flowRenderOptions(flow) {
7
7
  ...flow.sections.length > 0 ? { sections: flow.sections } : {},
8
8
  ...flow.section ? { section: flow.section } : {},
9
9
  ...flow.headersFooters ? { headersFooters: flow.headersFooters } : {},
10
+ ...flow.footnotes ? { footnotes: flow.footnotes } : {},
11
+ ...flow.endnotes ? { endnotes: flow.endnotes } : {},
10
12
  resources: flow.resources,
11
13
  ...flow.charts ? { charts: flow.charts } : {},
12
14
  ...flow.embeddedFonts ? { embeddedFonts: flow.embeddedFonts } : {},
@@ -6,7 +6,7 @@ import { Loss } from '../ir/index.js';
6
6
  import { DocumentReader } from '../ir/adapters.js';
7
7
  import { FlowDoc } from '../ir/flow.js';
8
8
  import { SignatureOptions, StyledRenderOptions } from '../../pdf/index.js';
9
- export type ReamTarget = 'pdf' | 'svg';
9
+ export type ReamTarget = 'pdf' | 'svg' | 'html';
10
10
  export interface ReamParseOptions {
11
11
  readonly readers?: ReadonlyArray<DocumentReader>;
12
12
  }
@@ -6,6 +6,7 @@ import { signPdf } from "../../pdf/signature.js";
6
6
  import { renderStyledPdf } from "../../pdf/styled-page-renderer.js";
7
7
  import "../../pdf/index.js";
8
8
  import { flowRenderOptions } from "./project.js";
9
+ import { writeHtml } from "../../html/html-writer.js";
9
10
  import { writeSvg } from "../../svg/svg-writer.js";
10
11
  import { resolveDocxAutoFonts } from "../../word/docx-to-pdf.js";
11
12
  import { DEFAULT_READERS, resolveFontsViaChain } from "./facade.js";
@@ -36,6 +37,15 @@ var Ream = class Ream {
36
37
  }
37
38
  async convertWithReport(to, options = {}) {
38
39
  const losses = [...this.losses];
40
+ if (to === "html") {
41
+ const html = writeHtml(this.flow);
42
+ losses.push(...html.losses);
43
+ this.enforceStrict(options, losses);
44
+ return {
45
+ bytes: html.bytes,
46
+ losses
47
+ };
48
+ }
39
49
  const { fonts, registriesByFamily } = await this.resolveFonts(options, losses);
40
50
  const registry = FontRegistry.fromBytes(fonts);
41
51
  if (to === "svg") {
@@ -1 +1 @@
1
- export type { AbstractNumbering, Alignment, Border, BorderStyle, BodyElement, CellBorders, CellMerge, DocumentInfo, CellMargins, CellProperties, CellShading, Chart, ChartBlock, ChartDataPoint, ChartSeries, ChartType, CustomGeometry, CustomPathCmd, DocumentModel, FontFamilyMap, HeaderFooterReference, HeaderFooterType, ImageBlock, InlineImage, MathAccent, MathBar, MathDelimiter, MathEqArray, MathFraction, MathFunc, MathGroupChr, MathLimit, MathMatrix, MathNary, MathNode, MathRadical, MathRow, MathRun, MathScript, Numbering, NumberingFormat, NumberingInstance, NumberingLevel, NumberingReference, PageMargins, PageSize, Paragraph, ParagraphProperties, Run, RunProperties, RowProperties, Section, SectionProperties, ShapeBlock, ShapeDash, ShapeFill, ShapeFillKind, ShapeGeometry, ShapeLine, ShapeTextBody, ShapeTransform, Style, StyleSheet, StyleType, Table, TableCell, TableProperties, TableRow, UnderlineStyle, VerticalAlign, } from './types.js';
1
+ export type { AbstractNumbering, Alignment, Border, BorderStyle, BodyElement, CellBorders, CellMerge, DocumentInfo, CellMargins, CellProperties, CellShading, Chart, ChartBlock, ChartDataPoint, ChartSeries, ChartType, CustomGeometry, CustomPathCmd, DocumentModel, FontFamilyMap, HeaderFooterReference, HeaderFooterType, ImageBlock, InlineImage, MathAccent, MathBar, MathDelimiter, MathEqArray, MathFraction, MathFunc, MathGroupChr, MathLimit, MathMatrix, MathNary, MathNode, MathRadical, MathRow, MathRun, MathScript, Numbering, NumberingFormat, NumberingInstance, NumberingLevel, NumberingReference, PageMargins, PageSize, Paragraph, ParagraphProperties, Run, RunProperties, RowProperties, Section, FloatAnchor, SectionColumns, SectionProperties, ShapeBlock, ShapeDash, ShapeFill, ShapeFillKind, ShapeGeometry, ShapeLine, ShapeTextBody, ShapeTransform, Style, StyleSheet, StyleType, Table, TableCell, TableLook, TableProperties, TableStyleCondition, TableStyleConditionType, TableStyleLayer, TableRow, UnderlineStyle, VerticalAlign, } from './types.js';
@@ -126,6 +126,12 @@ export interface Run {
126
126
  readonly native?: NativeBag;
127
127
  readonly text: string;
128
128
  readonly properties: RunProperties;
129
+ readonly href?: string;
130
+ readonly field?: 'PAGE' | 'NUMPAGES';
131
+ readonly anchor?: string;
132
+ readonly footnoteRef?: string;
133
+ readonly endnoteRef?: string;
134
+ readonly noteNumber?: true;
129
135
  readonly inlineImage?: InlineImage;
130
136
  readonly math?: MathNode;
131
137
  readonly pageBreak?: boolean;
@@ -134,6 +140,7 @@ export interface Paragraph {
134
140
  readonly native?: NativeBag;
135
141
  readonly properties: ParagraphProperties;
136
142
  readonly runs: ReadonlyArray<Run>;
143
+ readonly bookmarks?: ReadonlyArray<string>;
137
144
  }
138
145
  export type NumberingFormat = 'decimal' | 'lowerLetter' | 'upperLetter' | 'lowerRoman' | 'upperRoman' | 'bullet' | 'none';
139
146
  export interface NumberingReference {
@@ -161,6 +168,18 @@ export interface Numbering {
161
168
  readonly numInstances: ReadonlyMap<string, NumberingInstance>;
162
169
  }
163
170
  export type StyleType = 'paragraph' | 'character' | 'table' | 'numbering';
171
+ export interface TableStyleLayer {
172
+ readonly borders?: CellBorders;
173
+ readonly cellMargins?: CellMargins;
174
+ readonly shading?: CellShading;
175
+ readonly runProperties?: RunProperties;
176
+ readonly paragraphProperties?: ParagraphProperties;
177
+ }
178
+ export type TableStyleConditionType = 'wholeTable' | 'band1Vert' | 'band2Vert' | 'band1Horz' | 'band2Horz' | 'firstCol' | 'lastCol' | 'firstRow' | 'lastRow' | 'nwCell' | 'neCell' | 'swCell' | 'seCell';
179
+ export interface TableStyleCondition {
180
+ readonly type: TableStyleConditionType;
181
+ readonly layer: TableStyleLayer;
182
+ }
164
183
  export interface Style {
165
184
  readonly id: string;
166
185
  readonly type: StyleType;
@@ -168,6 +187,10 @@ export interface Style {
168
187
  readonly isDefault: boolean;
169
188
  readonly runProperties: RunProperties;
170
189
  readonly paragraphProperties: ParagraphProperties;
190
+ readonly tableLayer?: TableStyleLayer;
191
+ readonly tableConditions?: ReadonlyArray<TableStyleCondition>;
192
+ readonly rowBandSize?: number;
193
+ readonly colBandSize?: number;
171
194
  }
172
195
  export interface StyleSheet {
173
196
  readonly defaultRunProperties: RunProperties;
@@ -213,7 +236,17 @@ export interface RowProperties {
213
236
  readonly isHeader?: boolean;
214
237
  readonly pageBreakBefore?: boolean;
215
238
  }
239
+ export interface TableLook {
240
+ readonly firstRow?: boolean;
241
+ readonly lastRow?: boolean;
242
+ readonly firstColumn?: boolean;
243
+ readonly lastColumn?: boolean;
244
+ readonly noHBand?: boolean;
245
+ readonly noVBand?: boolean;
246
+ }
216
247
  export interface TableProperties {
248
+ readonly styleId?: string;
249
+ readonly look?: TableLook;
217
250
  readonly widthPt?: Pt;
218
251
  readonly widthFraction?: number;
219
252
  readonly widthType?: 'auto' | 'dxa' | 'pct' | 'nil';
@@ -237,6 +270,7 @@ export interface Table {
237
270
  readonly rows: ReadonlyArray<TableRow>;
238
271
  }
239
272
  export interface ImageBlock {
273
+ readonly float?: FloatAnchor;
240
274
  readonly resource?: ResourceId;
241
275
  readonly width: Pt;
242
276
  readonly height: Pt;
@@ -312,6 +346,7 @@ export interface ShapeTextBody {
312
346
  readonly anchor?: 't' | 'ctr' | 'b';
313
347
  }
314
348
  export interface ShapeBlock {
349
+ readonly float?: FloatAnchor;
315
350
  readonly width: Pt;
316
351
  readonly height: Pt;
317
352
  readonly geometry: ShapeGeometry;
@@ -349,6 +384,7 @@ export interface Chart {
349
384
  readonly valAxisTitle?: string;
350
385
  }
351
386
  export interface ChartBlock {
387
+ readonly float?: FloatAnchor;
352
388
  readonly chartRelId: string;
353
389
  readonly width: Pt;
354
390
  readonly height: Pt;
@@ -396,6 +432,28 @@ export interface SectionProperties {
396
432
  readonly footers: ReadonlyArray<HeaderFooterReference>;
397
433
  readonly titlePg?: boolean;
398
434
  readonly evenAndOddHeaders?: boolean;
435
+ readonly columns?: SectionColumns;
436
+ }
437
+ export interface FloatAnchor {
438
+ readonly wrap: 'none' | 'square' | 'tight' | 'through' | 'topAndBottom';
439
+ readonly behind?: boolean;
440
+ readonly posH?: {
441
+ readonly relativeFrom: 'margin' | 'page' | 'column';
442
+ readonly offsetPt?: Pt;
443
+ readonly align?: 'left' | 'center' | 'right';
444
+ };
445
+ readonly posV?: {
446
+ readonly relativeFrom: 'margin' | 'page' | 'paragraph' | 'line';
447
+ readonly offsetPt?: Pt;
448
+ };
449
+ }
450
+ export interface SectionColumns {
451
+ readonly count: number;
452
+ readonly spacePt: number;
453
+ readonly explicit?: ReadonlyArray<{
454
+ readonly widthPt: number;
455
+ readonly spacePt: number;
456
+ }>;
399
457
  }
400
458
  export interface Section {
401
459
  readonly properties: SectionProperties;
@@ -1,4 +1,4 @@
1
- import { Chart, ChartSeries } from '../core/document-model/index.js';
1
+ import { Chart, ChartSeries } from '../document-model/index.js';
2
2
  export interface ChartRect {
3
3
  readonly x: number;
4
4
  readonly y: number;
@@ -1,5 +1,5 @@
1
- import { CustomGeometry } from '../core/document-model/index.js';
2
- import { PathSegment, VectorPath } from '../core/vector.js';
1
+ import { CustomGeometry } from '../document-model/index.js';
2
+ import { PathSegment, VectorPath } from '../vector.js';
3
3
  export declare function rectSegments(w: number, h: number): ReadonlyArray<PathSegment>;
4
4
  export declare function rectPath(w: number, h: number): VectorPath;
5
5
  export declare function presetPaths(preset: string, w: number, h: number, adjust: ReadonlyMap<string, number>): Array<VectorPath> | null;
@@ -1,6 +1,6 @@
1
- import { PathBuilder } from "../core/vector.js";
2
- import { arcToBeziers, ellipseSegments, roundRectSegments } from "./arc-to-bezier.js";
3
- //#region src/layout/preset-geometry.ts
1
+ import { PathBuilder } from "../vector.js";
2
+ import { arcToBeziers, ellipseSegments, roundRectSegments } from "../arc-to-bezier.js";
3
+ //#region src/core/drawingml/preset-geometry.ts
4
4
  function rectSegments(w, h) {
5
5
  return new PathBuilder().moveTo(0, 0).lineTo(w, 0).lineTo(w, h).lineTo(0, h).close().build().segments;
6
6
  }
@@ -0,0 +1,8 @@
1
+ import { ShapeGeometry, ShapeLine } from '../document-model/index.js';
2
+ import { StrokeStyle, VectorPath } from '../vector.js';
3
+ export declare const EMU_PER_PT = 12700;
4
+ export declare const DEFAULT_INSET_LR_PT: number;
5
+ export declare const DEFAULT_INSET_TB_PT: number;
6
+ export declare function buildShapePaths(geometry: ShapeGeometry, widthPt: number, heightPt: number): Array<VectorPath>;
7
+ export declare function buildStroke(line: ShapeLine | undefined): StrokeStyle | undefined;
8
+ export declare function buildShapeTransform(pageX: number, pageY: number, widthPt: number, heightPt: number, rotation60k: number, flipH: boolean, flipV: boolean): [number, number, number, number, number, number];
@@ -0,0 +1,71 @@
1
+ import { customPaths, presetPaths, rectPath } from "./preset-geometry.js";
2
+ //#region src/core/drawingml/shape-render.ts
3
+ var EMU_PER_PT = 12700;
4
+ var DEFAULT_LINE_WIDTH_EMU = 9525;
5
+ var DEFAULT_INSET_LR_PT = 91440 / EMU_PER_PT;
6
+ var DEFAULT_INSET_TB_PT = 45720 / EMU_PER_PT;
7
+ function buildShapePaths(geometry, widthPt, heightPt) {
8
+ if (geometry.kind === "preset") return presetPaths(geometry.preset ?? "rect", widthPt, heightPt, geometry.adjust ?? /* @__PURE__ */ new Map()) ?? [rectPath(widthPt, heightPt)];
9
+ if (geometry.custom) return customPaths(geometry.custom, widthPt, heightPt);
10
+ return [rectPath(widthPt, heightPt)];
11
+ }
12
+ function buildStroke(line) {
13
+ if (!line || line.fill === "none") return void 0;
14
+ const widthPt = line.width ?? DEFAULT_LINE_WIDTH_EMU / 12700;
15
+ const dash = line.dash && line.dash !== "solid" ? dashPattern(line.dash, widthPt) : void 0;
16
+ const cap = line.cap === "flat" ? "butt" : line.cap;
17
+ return {
18
+ colorHex: line.colorHex ?? "000000",
19
+ widthPt,
20
+ ...dash ? { dash } : {},
21
+ ...cap ? { cap } : {}
22
+ };
23
+ }
24
+ function dashPattern(dash, w) {
25
+ const u = Math.max(w, .1);
26
+ switch (dash) {
27
+ case "solid": return;
28
+ case "dot": return [u, 2 * u];
29
+ case "dash": return [4 * u, 3 * u];
30
+ case "dashDot": return [
31
+ 4 * u,
32
+ 3 * u,
33
+ u,
34
+ 3 * u
35
+ ];
36
+ case "lgDash": return [8 * u, 3 * u];
37
+ case "lgDashDot": return [
38
+ 8 * u,
39
+ 3 * u,
40
+ u,
41
+ 3 * u
42
+ ];
43
+ case "sysDash": return [3 * u, u];
44
+ case "sysDot": return [u, u];
45
+ }
46
+ }
47
+ function buildShapeTransform(pageX, pageY, widthPt, heightPt, rotation60k, flipH, flipV) {
48
+ const theta = -rotation60k / 6e4 * (Math.PI / 180);
49
+ const sx = flipH ? -1 : 1;
50
+ const sy = flipV ? -1 : 1;
51
+ const cos = Math.cos(theta);
52
+ const sin = Math.sin(theta);
53
+ const a = sx * cos;
54
+ const b = sx * sin;
55
+ const c = -sy * sin;
56
+ const d = sy * cos;
57
+ const cxL = widthPt / 2;
58
+ const cyL = heightPt / 2;
59
+ const centerX = pageX + cxL;
60
+ const centerY = pageY + cyL;
61
+ return [
62
+ a,
63
+ b,
64
+ c,
65
+ d,
66
+ centerX - (a * cxL + c * cyL),
67
+ centerY - (b * cxL + d * cyL)
68
+ ];
69
+ }
70
+ //#endregion
71
+ export { DEFAULT_INSET_LR_PT, DEFAULT_INSET_TB_PT, buildShapePaths, buildShapeTransform, buildStroke };
@@ -8,6 +8,7 @@ export declare const FEATURES: {
8
8
  readonly headersFooters: "headersFooters";
9
9
  readonly images: "images";
10
10
  readonly imagesJp2: "images.jp2";
11
+ readonly hyperlinks: "hyperlinks";
11
12
  readonly shapes: "shapes";
12
13
  readonly charts: "charts";
13
14
  readonly math: "math";
@@ -8,6 +8,7 @@ var FEATURES = {
8
8
  headersFooters: "headersFooters",
9
9
  images: "images",
10
10
  imagesJp2: "images.jp2",
11
+ hyperlinks: "hyperlinks",
11
12
  shapes: "shapes",
12
13
  charts: "charts",
13
14
  math: "math",
@@ -11,6 +11,9 @@ export interface FlowDoc {
11
11
  readonly styles: StyleSheet;
12
12
  readonly numbering?: Numbering;
13
13
  readonly headersFooters?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
14
+ /** §17.11 footnotes/endnotes content by id (separator stubs excluded). */
15
+ readonly footnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
16
+ readonly endnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
14
17
  /** Parsed charts keyed by relationship id (ChartBlock.chartRelId). */
15
18
  readonly charts?: ReadonlyMap<string, Chart>;
16
19
  /** Content-addressed binary resources (images). */
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Returns the trimmed URL when its scheme is on the allowlist, undefined
3
+ * otherwise (including scheme-less/relative targets — those are not external
4
+ * links for a converted document).
5
+ */
6
+ export declare function sanitizeHref(href: string): string | undefined;
@@ -0,0 +1,19 @@
1
+ //#region src/core/links.ts
2
+ var ALLOWED_SCHEMES = new Set([
3
+ "http",
4
+ "https",
5
+ "mailto"
6
+ ]);
7
+ /**
8
+ * Returns the trimmed URL when its scheme is on the allowlist, undefined
9
+ * otherwise (including scheme-less/relative targets — those are not external
10
+ * links for a converted document).
11
+ */
12
+ function sanitizeHref(href) {
13
+ const url = href.trim();
14
+ const m = /^([a-zA-Z][a-zA-Z0-9+.-]*):/.exec(url);
15
+ if (!m) return void 0;
16
+ return ALLOWED_SCHEMES.has(m[1].toLowerCase()) ? url : void 0;
17
+ }
18
+ //#endregion
19
+ export { sanitizeHref };
@@ -14,8 +14,10 @@ function applyNumbering(body, numbering) {
14
14
  text: `${marker}\t`,
15
15
  properties: level?.runProperties ?? {}
16
16
  };
17
+ const newProps = mergeIndentFromLevel(p.properties, level?.paragraphProperties);
17
18
  return {
18
- properties: mergeIndentFromLevel(p.properties, level?.paragraphProperties),
19
+ ...p,
20
+ properties: newProps,
19
21
  runs: [markerRun, ...p.runs]
20
22
  };
21
23
  };
@@ -1,3 +1,4 @@
1
1
  export { EMPTY_STYLE_SHEET, resolveBodyStyles, resolveHeadersFootersStyles, resolveParagraphProperties, resolveRunProperties, } from './resolver.js';
2
2
  export type { ResolvedRunProperties, ResolvedParagraphProperties, } from './types.js';
3
3
  export { DEFAULT_RESOLVED_RUN, DEFAULT_RESOLVED_PARAGRAPH } from './types.js';
4
+ export { resolveTableStyles } from './table.js';
@@ -1,2 +1,3 @@
1
1
  import "./types.js";
2
2
  import "./resolver.js";
3
+ import "./table.js";
@@ -0,0 +1,2 @@
1
+ import { BodyElement, StyleSheet } from '../document-model/index.js';
2
+ export declare function resolveTableStyles(body: ReadonlyArray<BodyElement>, sheet: StyleSheet): ReadonlyArray<BodyElement>;
@@ -0,0 +1,161 @@
1
+ //#region src/core/style-cascade/table.ts
2
+ function resolveTableStyles(body, sheet) {
3
+ const visit = (el) => {
4
+ if (el.kind === "table") {
5
+ applyTableStyle(el.table, sheet);
6
+ for (const row of el.table.rows) for (const cell of row.cells) for (const child of cell.content) visit(child);
7
+ } else if (el.kind === "shape" && el.shape.text) for (const child of el.shape.text.content) visit(child);
8
+ };
9
+ for (const el of body) visit(el);
10
+ return body;
11
+ }
12
+ function applyTableStyle(table, sheet) {
13
+ const styleId = table.properties.styleId;
14
+ if (!styleId) return;
15
+ const folded = foldTableStyleChain(styleId, sheet);
16
+ if (!folded) return;
17
+ const tp = table.properties;
18
+ if (!tp.borders && folded.base.borders) tp.borders = folded.base.borders;
19
+ if (!tp.defaultCellMargins && folded.base.cellMargins) tp.defaultCellMargins = folded.base.cellMargins;
20
+ const look = table.properties.look ?? {};
21
+ const rows = table.rows;
22
+ const colCount = Math.max(table.grid.length, ...rows.map((r) => r.cells.reduce((s, c) => s + (c.properties.colSpan ?? 1), 0)));
23
+ for (let r = 0; r < rows.length; r++) {
24
+ const row = rows[r];
25
+ let colStart = 0;
26
+ for (const cell of row.cells) {
27
+ const span = cell.properties.colSpan ?? 1;
28
+ const layer = cellLayer(folded, look, {
29
+ firstRow: r === 0,
30
+ lastRow: r === rows.length - 1,
31
+ firstCol: colStart === 0,
32
+ lastCol: colStart + span >= colCount,
33
+ rowBand: bandIndex(r, look.firstRow === true, folded.rowBandSize),
34
+ colBand: bandIndex(colStart, look.firstColumn === true, folded.colBandSize),
35
+ noHBand: look.noHBand === true,
36
+ noVBand: look.noVBand === true
37
+ });
38
+ colStart += span;
39
+ if (!layer) continue;
40
+ applyLayerToCell(cell, layer);
41
+ }
42
+ }
43
+ }
44
+ function applyLayerToCell(tableCell, layer) {
45
+ const cell = tableCell;
46
+ if (layer.shading && !cell.properties.shading) cell.properties.shading = layer.shading;
47
+ if (layer.cellMargins && !cell.properties.margins) cell.properties.margins = layer.cellMargins;
48
+ if (layer.borders) {
49
+ const own = cell.properties.borders ?? {};
50
+ const merged = {
51
+ ...layer.borders,
52
+ ...definedOnly(own)
53
+ };
54
+ cell.properties.borders = merged;
55
+ }
56
+ if (layer.runProperties || layer.paragraphProperties) for (const el of cell.content) {
57
+ if (el.kind !== "paragraph") continue;
58
+ const p = el.paragraph;
59
+ if (layer.paragraphProperties) p.properties = {
60
+ ...layer.paragraphProperties,
61
+ ...definedOnly(p.properties)
62
+ };
63
+ if (layer.runProperties) for (const run of p.runs) run.properties = {
64
+ ...layer.runProperties,
65
+ ...definedOnly(run.properties)
66
+ };
67
+ }
68
+ }
69
+ function bandIndex(index, skipFirst, bandSize) {
70
+ const adjusted = index - (skipFirst ? 1 : 0);
71
+ if (adjusted < 0) return -1;
72
+ return Math.floor(adjusted / Math.max(1, bandSize));
73
+ }
74
+ function cellLayer(folded, look, pos) {
75
+ const layers = [folded.base];
76
+ const cond = (t) => folded.conditions.get(t);
77
+ if (!pos.noVBand && pos.colBand >= 0) layers.push(pos.colBand % 2 === 0 ? cond("band1Vert") : cond("band2Vert"));
78
+ if (!pos.noHBand && pos.rowBand >= 0) layers.push(pos.rowBand % 2 === 0 ? cond("band1Horz") : cond("band2Horz"));
79
+ if (look.firstColumn && pos.firstCol) layers.push(cond("firstCol"));
80
+ if (look.lastColumn && pos.lastCol) layers.push(cond("lastCol"));
81
+ if (look.firstRow && pos.firstRow) layers.push(cond("firstRow"));
82
+ if (look.lastRow && pos.lastRow) layers.push(cond("lastRow"));
83
+ if (look.firstRow && look.firstColumn && pos.firstRow && pos.firstCol) layers.push(cond("nwCell"));
84
+ if (look.firstRow && look.lastColumn && pos.firstRow && pos.lastCol) layers.push(cond("neCell"));
85
+ if (look.lastRow && look.firstColumn && pos.lastRow && pos.firstCol) layers.push(cond("swCell"));
86
+ if (look.lastRow && look.lastColumn && pos.lastRow && pos.lastCol) layers.push(cond("seCell"));
87
+ let out;
88
+ for (const layer of layers) {
89
+ if (!layer) continue;
90
+ out = out ? mergeLayer(out, layer) : layer;
91
+ }
92
+ return out;
93
+ }
94
+ function foldTableStyleChain(styleId, sheet) {
95
+ const chain = [];
96
+ const visited = /* @__PURE__ */ new Set();
97
+ let cursor = styleId;
98
+ while (cursor && !visited.has(cursor)) {
99
+ visited.add(cursor);
100
+ const style = sheet.styles.get(cursor);
101
+ if (!style || style.type !== "table") break;
102
+ chain.unshift(style);
103
+ cursor = style.basedOn;
104
+ }
105
+ if (chain.length === 0) return void 0;
106
+ let base = {};
107
+ const conditions = /* @__PURE__ */ new Map();
108
+ let rowBandSize = 1;
109
+ let colBandSize = 1;
110
+ for (const style of chain) {
111
+ const own = {
112
+ ...style.tableLayer ?? {},
113
+ ...Object.keys(style.runProperties).length > 0 && !style.tableLayer?.runProperties ? { runProperties: style.runProperties } : {},
114
+ ...Object.keys(style.paragraphProperties).length > 0 && !style.tableLayer?.paragraphProperties ? { paragraphProperties: style.paragraphProperties } : {}
115
+ };
116
+ base = mergeLayer(base, own);
117
+ for (const c of style.tableConditions ?? []) {
118
+ const prev = conditions.get(c.type);
119
+ conditions.set(c.type, prev ? mergeLayer(prev, c.layer) : c.layer);
120
+ }
121
+ if (style.rowBandSize !== void 0) rowBandSize = style.rowBandSize;
122
+ if (style.colBandSize !== void 0) colBandSize = style.colBandSize;
123
+ }
124
+ return {
125
+ base,
126
+ conditions,
127
+ rowBandSize,
128
+ colBandSize
129
+ };
130
+ }
131
+ function mergeLayer(base, override) {
132
+ return {
133
+ ...override.borders ?? base.borders ? { borders: {
134
+ ...base.borders,
135
+ ...definedOnly(override.borders ?? {})
136
+ } } : {},
137
+ ...override.cellMargins ?? base.cellMargins ? { cellMargins: {
138
+ ...base.cellMargins,
139
+ ...definedOnly(override.cellMargins ?? {})
140
+ } } : {},
141
+ ...override.shading ?? base.shading ? { shading: override.shading ?? base.shading } : {},
142
+ ...override.runProperties ?? base.runProperties ? { runProperties: {
143
+ ...base.runProperties,
144
+ ...definedOnly(override.runProperties ?? {})
145
+ } } : {},
146
+ ...override.paragraphProperties ?? base.paragraphProperties ? { paragraphProperties: {
147
+ ...base.paragraphProperties,
148
+ ...definedOnly(override.paragraphProperties ?? {})
149
+ } } : {}
150
+ };
151
+ }
152
+ function definedOnly(obj) {
153
+ const out = {};
154
+ for (const key of Object.keys(obj)) {
155
+ const v = obj[key];
156
+ if (v !== void 0) out[key] = v;
157
+ }
158
+ return out;
159
+ }
160
+ //#endregion
161
+ export { resolveTableStyles };