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.
- package/README.md +12 -6
- package/dist/esm/{layout → core}/arc-to-bezier.d.ts +1 -1
- package/dist/esm/{layout → core}/arc-to-bezier.js +2 -2
- package/dist/esm/core/bytes.d.ts +1 -0
- package/dist/esm/core/bytes.js +6 -1
- package/dist/esm/core/converter/facade.d.ts +2 -2
- package/dist/esm/core/converter/facade.js +12 -0
- package/dist/esm/core/converter/project.js +2 -0
- package/dist/esm/core/converter/ream.d.ts +1 -1
- package/dist/esm/core/converter/ream.js +10 -0
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +58 -0
- package/dist/esm/{layout → core/drawingml}/chart-geometry.d.ts +1 -1
- package/dist/esm/{layout → core/drawingml}/preset-geometry.d.ts +2 -2
- package/dist/esm/{layout → core/drawingml}/preset-geometry.js +3 -3
- package/dist/esm/core/drawingml/shape-render.d.ts +8 -0
- package/dist/esm/core/drawingml/shape-render.js +71 -0
- package/dist/esm/core/ir/features.d.ts +1 -0
- package/dist/esm/core/ir/features.js +1 -0
- package/dist/esm/core/ir/flow.d.ts +3 -0
- package/dist/esm/core/links.d.ts +6 -0
- package/dist/esm/core/links.js +19 -0
- package/dist/esm/core/numbering/apply.js +3 -1
- package/dist/esm/core/style-cascade/index.d.ts +1 -0
- package/dist/esm/core/style-cascade/index.js +1 -0
- package/dist/esm/core/style-cascade/table.d.ts +2 -0
- package/dist/esm/core/style-cascade/table.js +161 -0
- package/dist/esm/core/vector.d.ts +2 -0
- package/dist/esm/core/vector.js +19 -1
- package/dist/esm/html/html-writer.d.ts +4 -0
- package/dist/esm/html/html-writer.js +464 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +2 -1
- package/dist/esm/layout/page-doc.d.ts +3 -0
- package/dist/esm/layout/styled-layout.d.ts +19 -3
- package/dist/esm/layout/styled-layout.js +533 -197
- package/dist/esm/pdf/struct-tree.d.ts +7 -1
- package/dist/esm/pdf/struct-tree.js +22 -6
- package/dist/esm/pdf/styled-page-emitter.d.ts +1 -1
- package/dist/esm/pdf/styled-page-emitter.js +161 -29
- package/dist/esm/pdf/xmp.d.ts +1 -0
- package/dist/esm/pdf/xmp.js +4 -0
- package/dist/esm/svg/svg-writer.js +3 -11
- package/dist/esm/word/document-parser.d.ts +3 -0
- package/dist/esm/word/document-parser.js +210 -23
- package/dist/esm/word/docx-reader.js +30 -4
- package/dist/esm/word/drawing-parser.d.ts +4 -1
- package/dist/esm/word/drawing-parser.js +53 -2
- package/dist/esm/word/index.d.ts +2 -2
- package/dist/esm/word/styles-parser.js +134 -1
- package/dist/esm/word/table-parser.js +35 -0
- package/package.json +1 -1
- /package/dist/esm/{layout → core/drawingml}/chart-geometry.js +0 -0
|
@@ -43,3 +43,5 @@ export declare class PathBuilder {
|
|
|
43
43
|
close(): this;
|
|
44
44
|
build(fillRule?: 'nonzero' | 'evenodd'): VectorPath;
|
|
45
45
|
}
|
|
46
|
+
export declare function flipTransform(m: readonly [number, number, number, number, number, number], pageHeight: number): [number, number, number, number, number, number];
|
|
47
|
+
export declare function svgPathData(segments: ReadonlyArray<PathSegment>, fmt: (n: number) => string): string;
|
package/dist/esm/core/vector.js
CHANGED
|
@@ -44,5 +44,23 @@ var PathBuilder = class {
|
|
|
44
44
|
};
|
|
45
45
|
}
|
|
46
46
|
};
|
|
47
|
+
function flipTransform(m, pageHeight) {
|
|
48
|
+
return [
|
|
49
|
+
m[0],
|
|
50
|
+
-m[1],
|
|
51
|
+
m[2],
|
|
52
|
+
-m[3],
|
|
53
|
+
m[4],
|
|
54
|
+
pageHeight - m[5]
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
function svgPathData(segments, fmt) {
|
|
58
|
+
const parts = [];
|
|
59
|
+
for (const s of segments) if (s.op === "move") parts.push(`M ${fmt(s.x)} ${fmt(s.y)}`);
|
|
60
|
+
else if (s.op === "line") parts.push(`L ${fmt(s.x)} ${fmt(s.y)}`);
|
|
61
|
+
else if (s.op === "cubic") parts.push(`C ${fmt(s.x1)} ${fmt(s.y1)} ${fmt(s.x2)} ${fmt(s.y2)} ${fmt(s.x)} ${fmt(s.y)}`);
|
|
62
|
+
else parts.push("Z");
|
|
63
|
+
return parts.join(" ");
|
|
64
|
+
}
|
|
47
65
|
//#endregion
|
|
48
|
-
export { PathBuilder };
|
|
66
|
+
export { PathBuilder, flipTransform, svgPathData };
|
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
import { FEATURES } from "../core/ir/features.js";
|
|
2
|
+
import { detectImageFormat } from "../core/images.js";
|
|
3
|
+
import { EMPTY_STYLE_SHEET, resolveParagraphProperties, resolveRunProperties } from "../core/style-cascade/resolver.js";
|
|
4
|
+
import "../core/style-cascade/index.js";
|
|
5
|
+
import { PathBuilder, flipTransform, svgPathData } from "../core/vector.js";
|
|
6
|
+
import { arcPoint, arcToBeziers } from "../core/arc-to-bezier.js";
|
|
7
|
+
import { buildChartScene } from "../core/drawingml/chart-geometry.js";
|
|
8
|
+
import { buildShapePaths, buildShapeTransform, buildStroke } from "../core/drawingml/shape-render.js";
|
|
9
|
+
import { sanitizeHref } from "../core/links.js";
|
|
10
|
+
import { toBase64 } from "../core/bytes.js";
|
|
11
|
+
//#region src/html/html-writer.ts
|
|
12
|
+
function writeHtml(flow) {
|
|
13
|
+
const losses = [];
|
|
14
|
+
const out = [];
|
|
15
|
+
const notes = collectNoteNumbers(flow);
|
|
16
|
+
const ctx = {
|
|
17
|
+
resources: flow.resources,
|
|
18
|
+
losses,
|
|
19
|
+
notes,
|
|
20
|
+
referencedAnchors: collectReferencedAnchors(flow.body),
|
|
21
|
+
...flow.charts ? { charts: flow.charts } : {}
|
|
22
|
+
};
|
|
23
|
+
const lang = flow.language ?? "en-US";
|
|
24
|
+
const title = flow.info?.title ?? "Document";
|
|
25
|
+
out.push("<!DOCTYPE html>");
|
|
26
|
+
out.push(`<html lang="${escapeAttr(lang)}">`);
|
|
27
|
+
out.push("<head>");
|
|
28
|
+
out.push("<meta charset=\"utf-8\"/>");
|
|
29
|
+
out.push("<meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>");
|
|
30
|
+
out.push(`<title>${escapeText(title)}</title>`);
|
|
31
|
+
out.push(`<style>${BASE_CSS}</style>`);
|
|
32
|
+
out.push("</head>");
|
|
33
|
+
out.push("<body>");
|
|
34
|
+
out.push(`<article style="max-width: ${fmt(contentWidthPt(flow))}pt">`);
|
|
35
|
+
if (flow.headersFooters && flow.headersFooters.size > 0) losses.push({
|
|
36
|
+
severity: "dropped",
|
|
37
|
+
feature: FEATURES.headersFooters,
|
|
38
|
+
detail: "headers/footers have no fixed pages to band onto in flowed HTML"
|
|
39
|
+
});
|
|
40
|
+
for (const el of flow.body) emitBlock(out, el, ctx);
|
|
41
|
+
emitNotesSection(out, flow.footnotes, ctx.notes.footnotes, "fn", ctx);
|
|
42
|
+
emitNotesSection(out, flow.endnotes, ctx.notes.endnotes, "en", ctx);
|
|
43
|
+
out.push("</article>");
|
|
44
|
+
out.push("</body>");
|
|
45
|
+
out.push("</html>");
|
|
46
|
+
return {
|
|
47
|
+
bytes: new TextEncoder().encode(out.join("\n")),
|
|
48
|
+
losses
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
var htmlWriter = {
|
|
52
|
+
id: "html",
|
|
53
|
+
consumes: "flow",
|
|
54
|
+
supports: new Set([
|
|
55
|
+
FEATURES.text,
|
|
56
|
+
FEATURES.tables,
|
|
57
|
+
FEATURES.tablesNested,
|
|
58
|
+
FEATURES.lists,
|
|
59
|
+
FEATURES.images,
|
|
60
|
+
FEATURES.hyperlinks,
|
|
61
|
+
FEATURES.charts,
|
|
62
|
+
FEATURES.shapes,
|
|
63
|
+
FEATURES.rtl,
|
|
64
|
+
FEATURES.trackedChanges
|
|
65
|
+
]),
|
|
66
|
+
write: (doc) => writeHtml(doc)
|
|
67
|
+
};
|
|
68
|
+
var BASE_CSS = [
|
|
69
|
+
"body{margin:24pt;color:#000;background:#fff}",
|
|
70
|
+
"article{margin:0 auto}",
|
|
71
|
+
"p,h1,h2,h3,h4,h5,h6,figure{margin:0}",
|
|
72
|
+
"table{border-collapse:collapse}",
|
|
73
|
+
"td,th{vertical-align:top}",
|
|
74
|
+
"th{text-align:inherit}",
|
|
75
|
+
".tab{display:inline-block;min-width:18pt}",
|
|
76
|
+
"img{vertical-align:baseline}",
|
|
77
|
+
".notes{margin-top:18pt;font-size:smaller}",
|
|
78
|
+
".notes hr{margin:0 0 6pt;border:none;border-top:0.75pt solid #000;width:144pt;margin-left:0}"
|
|
79
|
+
].join("");
|
|
80
|
+
function collectReferencedAnchors(body) {
|
|
81
|
+
const out = /* @__PURE__ */ new Set();
|
|
82
|
+
const visit = (els) => {
|
|
83
|
+
for (const el of els) if (el.kind === "paragraph") {
|
|
84
|
+
for (const r of el.paragraph.runs) if (r.anchor !== void 0) out.add(r.anchor);
|
|
85
|
+
} else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
|
|
86
|
+
else if (el.kind === "shape" && el.shape.text) visit(el.shape.text.content);
|
|
87
|
+
};
|
|
88
|
+
visit(body);
|
|
89
|
+
return out;
|
|
90
|
+
}
|
|
91
|
+
function collectNoteNumbers(flow) {
|
|
92
|
+
const footnotes = /* @__PURE__ */ new Map();
|
|
93
|
+
const endnotes = /* @__PURE__ */ new Map();
|
|
94
|
+
const visit = (els) => {
|
|
95
|
+
for (const el of els) if (el.kind === "paragraph") for (const r of el.paragraph.runs) {
|
|
96
|
+
if (r.footnoteRef !== void 0 && !footnotes.has(r.footnoteRef)) footnotes.set(r.footnoteRef, footnotes.size + 1);
|
|
97
|
+
if (r.endnoteRef !== void 0 && !endnotes.has(r.endnoteRef)) endnotes.set(r.endnoteRef, endnotes.size + 1);
|
|
98
|
+
}
|
|
99
|
+
else if (el.kind === "table") for (const row of el.table.rows) for (const cell of row.cells) visit(cell.content);
|
|
100
|
+
else if (el.kind === "shape" && el.shape.text) visit(el.shape.text.content);
|
|
101
|
+
};
|
|
102
|
+
visit(flow.body);
|
|
103
|
+
return {
|
|
104
|
+
footnotes,
|
|
105
|
+
endnotes
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
function emitNotesSection(out, content, numbers, prefix, ctx) {
|
|
109
|
+
if (!content || numbers.size === 0) return;
|
|
110
|
+
const ordered = [...numbers.entries()].sort((a, b) => a[1] - b[1]);
|
|
111
|
+
out.push("<section class=\"notes\"><hr/><ol>");
|
|
112
|
+
for (const [id, n] of ordered) {
|
|
113
|
+
const body = content.get(id);
|
|
114
|
+
out.push(`<li id="${prefix}-${n}">`);
|
|
115
|
+
if (body) for (const el of body) emitBlock(out, el, ctx);
|
|
116
|
+
out.push(`<a href="#${prefix}ref-${n}">\u21a9</a></li>`);
|
|
117
|
+
}
|
|
118
|
+
out.push("</ol></section>");
|
|
119
|
+
}
|
|
120
|
+
function emitBlock(out, el, ctx) {
|
|
121
|
+
if (el.kind === "paragraph") emitParagraph(out, el.paragraph, ctx);
|
|
122
|
+
else if (el.kind === "table") emitTable(out, el.table, ctx);
|
|
123
|
+
else if (el.kind === "image") emitImageBlock(out, el.image, ctx);
|
|
124
|
+
else if (el.kind === "chart") emitChartBlock(out, el.chart, ctx);
|
|
125
|
+
else emitShapeBlock(out, el.shape, ctx);
|
|
126
|
+
}
|
|
127
|
+
var approxTextWidth = (text, sizePt) => text.length * sizePt * .55;
|
|
128
|
+
var blockFigureCss = (pp) => {
|
|
129
|
+
const css = [];
|
|
130
|
+
const align = pp.alignment === "center" || pp.alignment === "right" ? pp.alignment : void 0;
|
|
131
|
+
if (align) css.push(`text-align:${align}`);
|
|
132
|
+
if (pp.spacingBefore !== void 0 && pp.spacingBefore > 0) css.push(`margin-top:${fmt(pp.spacingBefore)}pt`);
|
|
133
|
+
if (pp.spacingAfter !== void 0 && pp.spacingAfter > 0) css.push(`margin-bottom:${fmt(pp.spacingAfter)}pt`);
|
|
134
|
+
return css.length > 0 ? ` style="${css.join(";")}"` : "";
|
|
135
|
+
};
|
|
136
|
+
var svgOpen = (w, h, label) => `<svg viewBox="0 0 ${fmt(w)} ${fmt(h)}" width="${fmt(w)}pt" height="${fmt(h)}pt" style="max-width:100%;height:auto" overflow="visible" xmlns="http://www.w3.org/2000/svg" role="img"${label ? ` aria-label="${escapeAttr(label)}"` : ""}>`;
|
|
137
|
+
var strokeAttrs = (stroke) => {
|
|
138
|
+
if (!stroke) return "";
|
|
139
|
+
let out = ` stroke="#${stroke.colorHex}" stroke-width="${fmt(stroke.widthPt)}"`;
|
|
140
|
+
if (stroke.dash && stroke.dash.length > 0) out += ` stroke-dasharray="${stroke.dash.map(fmt).join(" ")}"`;
|
|
141
|
+
if (stroke.cap && stroke.cap !== "butt") out += ` stroke-linecap="${stroke.cap}"`;
|
|
142
|
+
if (stroke.join) out += ` stroke-linejoin="${stroke.join}"`;
|
|
143
|
+
return out;
|
|
144
|
+
};
|
|
145
|
+
function emitChartBlock(out, block, ctx) {
|
|
146
|
+
const chart = ctx.charts?.get(block.chartRelId);
|
|
147
|
+
const label = block.altText ?? chart?.title;
|
|
148
|
+
const figure = `<figure${blockFigureCss(block.paragraphProperties)}>`;
|
|
149
|
+
if (!chart) {
|
|
150
|
+
ctx.losses.push({
|
|
151
|
+
severity: "dropped",
|
|
152
|
+
feature: FEATURES.charts,
|
|
153
|
+
detail: `chart part '${block.chartRelId}' not supplied`
|
|
154
|
+
});
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
const w = block.width;
|
|
158
|
+
const h = block.height;
|
|
159
|
+
const scene = buildChartScene(chart, w, h, approxTextWidth);
|
|
160
|
+
if (!scene) {
|
|
161
|
+
ctx.losses.push({
|
|
162
|
+
severity: "degraded",
|
|
163
|
+
feature: FEATURES.charts,
|
|
164
|
+
detail: `unsupported chart type '${chart.type}' — placeholder box`
|
|
165
|
+
});
|
|
166
|
+
out.push(figure + svgOpen(w, h, label) + `<rect x="0.5" y="0.5" width="${fmt(w - 1)}" height="${fmt(h - 1)}" fill="none" stroke="#D9D9D9"/></svg></figure>`);
|
|
167
|
+
return;
|
|
168
|
+
}
|
|
169
|
+
const svg = [figure, svgOpen(w, h, label)];
|
|
170
|
+
svg.push(`<g transform="matrix(1 0 0 -1 0 ${fmt(h)})">`);
|
|
171
|
+
for (const pg of scene.polygons ?? []) svg.push(polygonSvg(pg));
|
|
172
|
+
for (const r of scene.rects) svg.push(rectSvg(r));
|
|
173
|
+
for (const pl of scene.polylines) svg.push(polylineSvg(pl));
|
|
174
|
+
for (const wd of scene.wedges) svg.push(wedgeSvg(wd));
|
|
175
|
+
svg.push("</g>");
|
|
176
|
+
const anchor = {
|
|
177
|
+
left: "start",
|
|
178
|
+
center: "middle",
|
|
179
|
+
right: "end"
|
|
180
|
+
};
|
|
181
|
+
for (const l of scene.labels) svg.push(labelSvg(l, h, anchor[l.align]));
|
|
182
|
+
svg.push("</svg></figure>");
|
|
183
|
+
out.push(svg.join(""));
|
|
184
|
+
}
|
|
185
|
+
function rectSvg(r) {
|
|
186
|
+
const fill = r.fillHex ? `#${r.fillHex}` : "none";
|
|
187
|
+
const stroke = r.strokeHex ? ` stroke="#${r.strokeHex}" stroke-width="${fmt(r.strokeWidthPt ?? 1)}"` : "";
|
|
188
|
+
return `<rect x="${fmt(r.x)}" y="${fmt(r.y)}" width="${fmt(r.w)}" height="${fmt(r.h)}" fill="${fill}"${stroke}/>`;
|
|
189
|
+
}
|
|
190
|
+
var pointsAttr = (points) => points.map(([x, y]) => `${fmt(x)},${fmt(y)}`).join(" ");
|
|
191
|
+
function polylineSvg(p) {
|
|
192
|
+
return `<polyline points="${pointsAttr(p.points)}" fill="none" stroke="#${p.strokeHex}" stroke-width="${fmt(p.widthPt)}"/>`;
|
|
193
|
+
}
|
|
194
|
+
function polygonSvg(p) {
|
|
195
|
+
const stroke = p.strokeHex ? ` stroke="#${p.strokeHex}" stroke-width="${fmt(p.widthPt ?? 1)}"` : "";
|
|
196
|
+
return `<polygon points="${pointsAttr(p.points)}" fill="#${p.fillHex}"${stroke}/>`;
|
|
197
|
+
}
|
|
198
|
+
function wedgeSvg(w) {
|
|
199
|
+
const start = arcPoint(w.cx, w.cy, w.r, w.r, w.startRad);
|
|
200
|
+
const b = new PathBuilder().moveTo(w.cx, w.cy).lineTo(start[0], start[1]).append(arcToBeziers(w.cx, w.cy, w.r, w.r, w.startRad, w.sweepRad)).close();
|
|
201
|
+
const stroke = w.strokeHex ? ` stroke="#${w.strokeHex}" stroke-width="1"` : "";
|
|
202
|
+
return `<path d="${svgPathData(b.build().segments, fmt)}" fill="#${w.fillHex}"${stroke}/>`;
|
|
203
|
+
}
|
|
204
|
+
function labelSvg(l, sceneH, anchor) {
|
|
205
|
+
return `<text x="${fmt(l.x)}" y="${fmt(sceneH - l.y)}" font-size="${fmt(l.sizePt)}" font-family="sans-serif" fill="#${l.colorHex}" text-anchor="${anchor}">${escapeText(l.text)}</text>`;
|
|
206
|
+
}
|
|
207
|
+
function emitShapeBlock(out, shape, ctx) {
|
|
208
|
+
const w = shape.width;
|
|
209
|
+
const h = shape.height;
|
|
210
|
+
const paths = buildShapePaths(shape.geometry, w, h);
|
|
211
|
+
const fill = shape.fill.kind === "solid" && shape.fill.colorHex ? `#${shape.fill.colorHex}` : "none";
|
|
212
|
+
const stroke = strokeAttrs(buildStroke(shape.line));
|
|
213
|
+
const t = shape.transform;
|
|
214
|
+
const transform = ` transform="matrix(${flipTransform(buildShapeTransform(0, 0, w, h, t?.rotation60k ?? 0, t?.flipH ?? false, t?.flipV ?? false), h).map(fmt).join(" ")})"`;
|
|
215
|
+
const svg = [svgOpen(w, h, shape.altText)];
|
|
216
|
+
for (const path of paths) {
|
|
217
|
+
const rule = path.fillRule === "evenodd" ? " fill-rule=\"evenodd\"" : "";
|
|
218
|
+
svg.push(`<path d="${svgPathData(path.segments, fmt)}" fill="${fill}"${rule}${stroke}${transform}/>`);
|
|
219
|
+
}
|
|
220
|
+
svg.push("</svg>");
|
|
221
|
+
const figureCss = blockFigureCss(shape.paragraphProperties);
|
|
222
|
+
const text = shape.text;
|
|
223
|
+
if (!text || text.content.length === 0) {
|
|
224
|
+
out.push(`<figure${figureCss}>${svg.join("")}</figure>`);
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
const justify = text.anchor === "ctr" ? "center" : text.anchor === "b" ? "flex-end" : "flex-start";
|
|
228
|
+
const il = text.insetLeft ?? 7.2;
|
|
229
|
+
const ir = text.insetRight ?? 7.2;
|
|
230
|
+
const it = text.insetTop ?? 3.6;
|
|
231
|
+
const ib = text.insetBottom ?? 3.6;
|
|
232
|
+
out.push(`<figure${figureCss}><div style="position:relative;width:${fmt(w)}pt;height:${fmt(h)}pt;max-width:100%"><div style="position:absolute;left:0;top:0">${svg.join("")}</div><div style="position:absolute;left:${fmt(il)}pt;right:${fmt(ir)}pt;top:${fmt(it)}pt;bottom:${fmt(ib)}pt;display:flex;flex-direction:column;justify-content:${justify};overflow:hidden">`);
|
|
233
|
+
for (const child of text.content) emitBlock(out, child, ctx);
|
|
234
|
+
out.push("</div></div></figure>");
|
|
235
|
+
}
|
|
236
|
+
function emitParagraph(out, p, ctx) {
|
|
237
|
+
const resolved = resolveParagraphProperties(p.properties, EMPTY_STYLE_SHEET);
|
|
238
|
+
const lvl = resolved.outlineLevel;
|
|
239
|
+
const tag = lvl !== void 0 && lvl >= 0 && lvl <= 8 ? `h${Math.min(lvl, 5) + 1}` : "p";
|
|
240
|
+
const style = paragraphCss(resolved);
|
|
241
|
+
const dir = resolved.bidi ? " dir=\"rtl\"" : "";
|
|
242
|
+
const anchored = (p.bookmarks ?? []).filter((b) => ctx.referencedAnchors.has(b));
|
|
243
|
+
const idAttr = anchored.length > 0 ? ` id="bm-${encodeURIComponent(anchored[0])}"` : "";
|
|
244
|
+
const extraAnchors = anchored.slice(1).map((b) => `<span id="bm-${encodeURIComponent(b)}"></span>`).join("");
|
|
245
|
+
const open = `<${tag}${dir}${idAttr}${style ? ` style="${style}"` : ""}>${extraAnchors}`;
|
|
246
|
+
const parts = [];
|
|
247
|
+
for (const run of p.runs) parts.push(runHtml(run, p, ctx));
|
|
248
|
+
out.push(`${open}${parts.join("") || " "}</${tag}>`);
|
|
249
|
+
}
|
|
250
|
+
function paragraphCss(r) {
|
|
251
|
+
const css = [];
|
|
252
|
+
if (r.alignment === "center" || r.alignment === "right") css.push(`text-align:${r.alignment}`);
|
|
253
|
+
else if (r.alignment === "both" || r.alignment === "distribute") css.push("text-align:justify");
|
|
254
|
+
if (r.spacingBefore > 0) css.push(`margin-top:${fmt(r.spacingBefore)}pt`);
|
|
255
|
+
if (r.spacingAfter > 0) css.push(`margin-bottom:${fmt(r.spacingAfter)}pt`);
|
|
256
|
+
if (r.indentLeft !== 0) css.push(`margin-left:${fmt(r.indentLeft)}pt`);
|
|
257
|
+
if (r.indentRight !== 0) css.push(`margin-right:${fmt(r.indentRight)}pt`);
|
|
258
|
+
if (r.indentFirstLine !== 0) css.push(`text-indent:${fmt(r.indentFirstLine)}pt`);
|
|
259
|
+
if (r.spacingLine > 0) if (r.spacingLineRule === "auto") css.push(`line-height:${fmt(r.spacingLine / 12)}`);
|
|
260
|
+
else css.push(`line-height:${fmt(r.spacingLine)}pt`);
|
|
261
|
+
if (r.pageBreakBefore) css.push("break-before:page");
|
|
262
|
+
return css.join(";");
|
|
263
|
+
}
|
|
264
|
+
function runHtml(run, p, ctx) {
|
|
265
|
+
if (run.footnoteRef !== void 0 || run.endnoteRef !== void 0) {
|
|
266
|
+
const isFoot = run.footnoteRef !== void 0;
|
|
267
|
+
const n = isFoot ? ctx.notes.footnotes.get(run.footnoteRef) : ctx.notes.endnotes.get(run.endnoteRef);
|
|
268
|
+
if (n === void 0) return "";
|
|
269
|
+
const prefix = isFoot ? "fn" : "en";
|
|
270
|
+
return `<sup><a href="#${prefix}-${n}" id="${prefix}ref-${n}">${n}</a></sup>`;
|
|
271
|
+
}
|
|
272
|
+
if (run.noteNumber) return "";
|
|
273
|
+
if (run.math !== void 0) {
|
|
274
|
+
ctx.losses.push({
|
|
275
|
+
severity: "dropped",
|
|
276
|
+
feature: FEATURES.math,
|
|
277
|
+
detail: "inline math not rendered by the HTML writer (v0)"
|
|
278
|
+
});
|
|
279
|
+
return "";
|
|
280
|
+
}
|
|
281
|
+
if (run.inlineImage) {
|
|
282
|
+
const img = run.inlineImage;
|
|
283
|
+
const src = dataUri(img.resource, ctx.resources);
|
|
284
|
+
if (!src) return "";
|
|
285
|
+
return `<img src="${src}" style="width:${fmt(img.width)}pt;height:${fmt(img.height)}pt" alt=""/>`;
|
|
286
|
+
}
|
|
287
|
+
if (run.text.length === 0) return "";
|
|
288
|
+
const resolved = resolveRunProperties(run.properties, p.properties, EMPTY_STYLE_SHEET);
|
|
289
|
+
const style = runCss(resolved);
|
|
290
|
+
let html = `<span${resolved.rtl ? " dir=\"rtl\"" : ""}${style ? ` style="${style}"` : ""}>${textHtml(run.text)}</span>`;
|
|
291
|
+
if (resolved.verticalAlign === "superscript") html = `<sup>${html}</sup>`;
|
|
292
|
+
else if (resolved.verticalAlign === "subscript") html = `<sub>${html}</sub>`;
|
|
293
|
+
if (run.href === void 0 && run.anchor !== void 0) return `<a href="#bm-${encodeURIComponent(run.anchor)}">${html}</a>`;
|
|
294
|
+
if (run.href !== void 0) {
|
|
295
|
+
const safe = sanitizeHref(run.href);
|
|
296
|
+
if (safe !== void 0) html = `<a href="${escapeAttr(safe)}">${html}</a>`;
|
|
297
|
+
else ctx.losses.push({
|
|
298
|
+
severity: "degraded",
|
|
299
|
+
feature: FEATURES.hyperlinks,
|
|
300
|
+
detail: `hyperlink target with a disallowed scheme rendered as plain text`
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return html;
|
|
304
|
+
}
|
|
305
|
+
function runCss(r) {
|
|
306
|
+
const css = [];
|
|
307
|
+
const family = r.fontFamily.ascii;
|
|
308
|
+
if (family) css.push(`font-family:${JSON.stringify(family)}`);
|
|
309
|
+
css.push(`font-size:${fmt(r.fontSizePt)}pt`);
|
|
310
|
+
if (r.bold) css.push("font-weight:700");
|
|
311
|
+
if (r.italic) css.push("font-style:italic");
|
|
312
|
+
const deco = [];
|
|
313
|
+
const underlined = r.underline !== "none";
|
|
314
|
+
if (underlined) deco.push("underline");
|
|
315
|
+
if (r.strike) deco.push("line-through");
|
|
316
|
+
if (deco.length > 0) {
|
|
317
|
+
css.push(`text-decoration-line:${deco.join(" ")}`);
|
|
318
|
+
const style = underlined ? decorationStyle(r.underline) : void 0;
|
|
319
|
+
if (style) css.push(`text-decoration-style:${style}`);
|
|
320
|
+
}
|
|
321
|
+
if (r.colorHex !== "000000") css.push(`color:#${r.colorHex}`);
|
|
322
|
+
return css.join(";");
|
|
323
|
+
}
|
|
324
|
+
function decorationStyle(u) {
|
|
325
|
+
switch (u) {
|
|
326
|
+
case "double": return "double";
|
|
327
|
+
case "dotted":
|
|
328
|
+
case "dottedHeavy": return "dotted";
|
|
329
|
+
case "dash":
|
|
330
|
+
case "dashHeavy": return "dashed";
|
|
331
|
+
case "wave": return "wavy";
|
|
332
|
+
default: return;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
function textHtml(text) {
|
|
336
|
+
return escapeText(text).replaceAll(" ", "<span class=\"tab\"></span>").replaceAll("\n", "<br/>");
|
|
337
|
+
}
|
|
338
|
+
function emitTable(out, table, ctx) {
|
|
339
|
+
const css = [];
|
|
340
|
+
if (table.properties.widthPt !== void 0) css.push(`width:${fmt(table.properties.widthPt)}pt`);
|
|
341
|
+
else if (table.properties.widthFraction !== void 0) css.push(`width:${fmt(table.properties.widthFraction * 100)}%`);
|
|
342
|
+
if (table.properties.alignment === "center") css.push("margin-left:auto;margin-right:auto");
|
|
343
|
+
else if (table.properties.alignment === "right") css.push("margin-left:auto");
|
|
344
|
+
out.push(`<table${css.length > 0 ? ` style="${css.join(";")}"` : ""}>`);
|
|
345
|
+
if (table.grid.length > 0) {
|
|
346
|
+
out.push("<colgroup>");
|
|
347
|
+
for (const w of table.grid) out.push(`<col style="width:${fmt(w)}pt"/>`);
|
|
348
|
+
out.push("</colgroup>");
|
|
349
|
+
}
|
|
350
|
+
const colStarts = table.rows.map((row) => {
|
|
351
|
+
let col = 0;
|
|
352
|
+
return row.cells.map((cell) => {
|
|
353
|
+
const start = col;
|
|
354
|
+
col += cell.properties.colSpan ?? 1;
|
|
355
|
+
return start;
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
for (let ri = 0; ri < table.rows.length; ri++) {
|
|
359
|
+
const row = table.rows[ri];
|
|
360
|
+
out.push("<tr>");
|
|
361
|
+
for (let ci = 0; ci < row.cells.length; ci++) {
|
|
362
|
+
const cell = row.cells[ci];
|
|
363
|
+
const merge = cell.properties.merge;
|
|
364
|
+
if (merge === "middle" || merge === "end") continue;
|
|
365
|
+
const rowSpan = merge === "start" ? mergeRowSpan(table, colStarts, ri, colStarts[ri][ci]) : 1;
|
|
366
|
+
emitCell(out, cell, table, ctx, {
|
|
367
|
+
isHeader: row.properties.isHeader === true,
|
|
368
|
+
firstRow: ri === 0,
|
|
369
|
+
lastRow: ri === table.rows.length - 1,
|
|
370
|
+
firstCol: colStarts[ri][ci] === 0,
|
|
371
|
+
lastCol: colStarts[ri][ci] + (cell.properties.colSpan ?? 1) >= table.grid.length,
|
|
372
|
+
rowSpan
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
out.push("</tr>");
|
|
376
|
+
}
|
|
377
|
+
out.push("</table>");
|
|
378
|
+
}
|
|
379
|
+
function mergeRowSpan(table, colStarts, startRow, colStart) {
|
|
380
|
+
let span = 1;
|
|
381
|
+
for (let r = startRow + 1; r < table.rows.length; r++) {
|
|
382
|
+
const row = table.rows[r];
|
|
383
|
+
const idx = colStarts[r].findIndex((c) => c === colStart);
|
|
384
|
+
const merge = (idx >= 0 ? row.cells[idx] : void 0)?.properties.merge;
|
|
385
|
+
if (merge !== "middle" && merge !== "end") break;
|
|
386
|
+
span++;
|
|
387
|
+
if (merge === "end") break;
|
|
388
|
+
}
|
|
389
|
+
return span;
|
|
390
|
+
}
|
|
391
|
+
function emitCell(out, cell, table, ctx, pos) {
|
|
392
|
+
const tag = pos.isHeader ? "th" : "td";
|
|
393
|
+
const attrs = [];
|
|
394
|
+
const colSpan = cell.properties.colSpan ?? 1;
|
|
395
|
+
if (colSpan > 1) attrs.push(`colspan="${colSpan}"`);
|
|
396
|
+
if (pos.rowSpan > 1) attrs.push(`rowspan="${pos.rowSpan}"`);
|
|
397
|
+
const css = [];
|
|
398
|
+
const t = table.properties.borders;
|
|
399
|
+
const c = cell.properties.borders;
|
|
400
|
+
pushBorder(css, "top", c?.top ?? (pos.firstRow ? t?.top : t?.insideH));
|
|
401
|
+
pushBorder(css, "bottom", c?.bottom ?? (pos.lastRow ? t?.bottom : t?.insideH));
|
|
402
|
+
pushBorder(css, "left", c?.left ?? (pos.firstCol ? t?.left : t?.insideV));
|
|
403
|
+
pushBorder(css, "right", c?.right ?? (pos.lastCol ? t?.right : t?.insideV));
|
|
404
|
+
if (cell.properties.shading) css.push(`background-color:#${cell.properties.shading.colorHex}`);
|
|
405
|
+
const margins = cell.properties.margins ?? table.properties.defaultCellMargins;
|
|
406
|
+
const padTop = margins?.top ?? 0;
|
|
407
|
+
const padRight = margins?.right ?? 5.4;
|
|
408
|
+
const padBottom = margins?.bottom ?? 0;
|
|
409
|
+
const padLeft = margins?.left ?? 5.4;
|
|
410
|
+
css.push(`padding:${fmt(padTop)}pt ${fmt(padRight)}pt ${fmt(padBottom)}pt ${fmt(padLeft)}pt`);
|
|
411
|
+
out.push(`<${tag}${attrs.length > 0 ? ` ${attrs.join(" ")}` : ""} style="${css.join(";")}">`);
|
|
412
|
+
for (const child of cell.content) emitBlock(out, child, ctx);
|
|
413
|
+
out.push(`</${tag}>`);
|
|
414
|
+
}
|
|
415
|
+
function pushBorder(css, side, border) {
|
|
416
|
+
if (!border || border.style === "none") return;
|
|
417
|
+
const width = border.width ?? .5;
|
|
418
|
+
const style = border.style === "double" ? "double" : border.style === "dotted" ? "dotted" : border.style === "dashed" ? "dashed" : "solid";
|
|
419
|
+
css.push(`border-${side}:${fmt(width)}pt ${style} #${border.colorHex ?? "000000"}`);
|
|
420
|
+
}
|
|
421
|
+
function emitImageBlock(out, image, ctx) {
|
|
422
|
+
const src = dataUri(image.resource, ctx.resources);
|
|
423
|
+
if (!src) return;
|
|
424
|
+
const pp = image.paragraphProperties;
|
|
425
|
+
const align = pp.alignment === "center" || pp.alignment === "right" ? pp.alignment : void 0;
|
|
426
|
+
const css = [];
|
|
427
|
+
if (align) css.push(`text-align:${align}`);
|
|
428
|
+
if (pp.spacingBefore !== void 0 && pp.spacingBefore > 0) css.push(`margin-top:${fmt(pp.spacingBefore)}pt`);
|
|
429
|
+
if (pp.spacingAfter !== void 0 && pp.spacingAfter > 0) css.push(`margin-bottom:${fmt(pp.spacingAfter)}pt`);
|
|
430
|
+
const alt = image.altText ?? "";
|
|
431
|
+
out.push(`<figure${css.length > 0 ? ` style="${css.join(";")}"` : ""}><img src="${src}" style="width:${fmt(image.width)}pt;height:${fmt(image.height)}pt" alt="${escapeAttr(alt)}"/></figure>`);
|
|
432
|
+
}
|
|
433
|
+
var MIME_BY_FORMAT = {
|
|
434
|
+
jpeg: "image/jpeg",
|
|
435
|
+
png: "image/png",
|
|
436
|
+
jpeg2000: "image/jp2"
|
|
437
|
+
};
|
|
438
|
+
function dataUri(resource, store) {
|
|
439
|
+
if (resource === void 0) return void 0;
|
|
440
|
+
const bytes = store.get(resource);
|
|
441
|
+
if (!bytes) return void 0;
|
|
442
|
+
const format = detectImageFormat(bytes);
|
|
443
|
+
if (!format) return void 0;
|
|
444
|
+
return `data:${MIME_BY_FORMAT[format]};base64,${toBase64(bytes)}`;
|
|
445
|
+
}
|
|
446
|
+
function contentWidthPt(flow) {
|
|
447
|
+
const props = flow.sections[0]?.properties ?? flow.section;
|
|
448
|
+
const pageWidth = props?.pageSize?.width ?? 595;
|
|
449
|
+
const left = props?.margins?.left ?? 72;
|
|
450
|
+
const right = props?.margins?.right ?? 72;
|
|
451
|
+
return Math.max(72, pageWidth - left - right);
|
|
452
|
+
}
|
|
453
|
+
function escapeText(s) {
|
|
454
|
+
return s.replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">");
|
|
455
|
+
}
|
|
456
|
+
function escapeAttr(s) {
|
|
457
|
+
return escapeText(s).replaceAll("\"", """);
|
|
458
|
+
}
|
|
459
|
+
function fmt(n) {
|
|
460
|
+
const r = Math.round(n * 100) / 100;
|
|
461
|
+
return Object.is(r, -0) ? "0" : String(r);
|
|
462
|
+
}
|
|
463
|
+
//#endregion
|
|
464
|
+
export { htmlWriter, writeHtml };
|
package/dist/esm/index.d.ts
CHANGED
|
@@ -23,5 +23,6 @@ export { chainProviders, callerFontProvider, embeddedDocFontProvider, remoteFont
|
|
|
23
23
|
export type { FontProvider, FontRequest, FontAnswer } from './core/fonts/provider.js';
|
|
24
24
|
export { svgWriter, writeSvg } from './svg/svg-writer.js';
|
|
25
25
|
export type { SvgWriteOptions } from './svg/svg-writer.js';
|
|
26
|
+
export { htmlWriter, writeHtml } from './html/html-writer.js';
|
|
26
27
|
export { layoutStyledDocument } from './layout/styled-layout.js';
|
|
27
28
|
export type { LaidOutDocument } from './layout/page-doc.js';
|
package/dist/esm/index.js
CHANGED
|
@@ -15,7 +15,8 @@ import { createLanguageHyphenator, getHyphenator } from "./core/hyphenation/inde
|
|
|
15
15
|
import { docxReader, readDocx } from "./word/docx-reader.js";
|
|
16
16
|
import { readXlsx, xlsxReader } from "./excel/xlsx-reader.js";
|
|
17
17
|
import { NO_FONT, callerFontProvider, chainProviders, embeddedDocFontProvider, isEmbeddingRestricted, localFontProvider, readOs2FsType, remoteFontProvider } from "./core/fonts/provider.js";
|
|
18
|
+
import { htmlWriter, writeHtml } from "./html/html-writer.js";
|
|
18
19
|
import { svgWriter, writeSvg } from "./svg/svg-writer.js";
|
|
19
20
|
import { createConverter } from "./core/converter/facade.js";
|
|
20
21
|
import { Ream } from "./core/converter/ream.js";
|
|
21
|
-
export { ConversionLossError, FEATURES, FontRegistry, NO_FONT, Ream, ResourceStore, callerFontProvider, chainProviders, createConverter, createHyphenator, createLanguageHyphenator, docxReader, eighthPtToPt, embeddedDocFontProvider, emuToPt, fetchFontSet, formatLoss, getHyphenator, halfPtToPt, inchToPt, isEmbeddingRestricted, layoutStyledDocument, localFontProvider, mmToPt, parseTtf, pt, pxToPt, readDocx, readOs2FsType, readXlsx, remoteFontProvider, renderStyledPdf, resolveFamilyKey, signPdf, splitPatternBundle, subsetTtf, svgWriter, twipsToPt, writeSvg, xlsxReader };
|
|
22
|
+
export { ConversionLossError, FEATURES, FontRegistry, NO_FONT, Ream, ResourceStore, callerFontProvider, chainProviders, createConverter, createHyphenator, createLanguageHyphenator, docxReader, eighthPtToPt, embeddedDocFontProvider, emuToPt, fetchFontSet, formatLoss, getHyphenator, halfPtToPt, htmlWriter, inchToPt, isEmbeddingRestricted, layoutStyledDocument, localFontProvider, mmToPt, parseTtf, pt, pxToPt, readDocx, readOs2FsType, readXlsx, remoteFontProvider, renderStyledPdf, resolveFamilyKey, signPdf, splitPatternBundle, subsetTtf, svgWriter, twipsToPt, writeHtml, writeSvg, xlsxReader };
|
|
@@ -13,6 +13,9 @@ export interface TextToken {
|
|
|
13
13
|
readonly kind: 'text';
|
|
14
14
|
readonly text: string;
|
|
15
15
|
readonly isSpace: boolean;
|
|
16
|
+
readonly href?: string;
|
|
17
|
+
readonly footnoteRef?: string;
|
|
18
|
+
readonly anchor?: string;
|
|
16
19
|
readonly resolvedRun: ResolvedRunProperties;
|
|
17
20
|
readonly font: FontResource;
|
|
18
21
|
readonly fontSizePt: number;
|
|
@@ -23,6 +23,8 @@ export interface StyledRenderOptions {
|
|
|
23
23
|
readonly section?: SectionProperties;
|
|
24
24
|
readonly sections?: ReadonlyArray<Section>;
|
|
25
25
|
readonly headersFooters?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
|
|
26
|
+
readonly footnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
|
|
27
|
+
readonly endnotes?: ReadonlyMap<string, ReadonlyArray<BodyElement>>;
|
|
26
28
|
readonly resources?: ResourceStore;
|
|
27
29
|
readonly charts?: ReadonlyMap<string, Chart>;
|
|
28
30
|
readonly pageWidth?: number;
|
|
@@ -35,6 +37,7 @@ export interface StyledRenderOptions {
|
|
|
35
37
|
readonly info?: DocumentInfo;
|
|
36
38
|
readonly pdfA?: PdfALevel;
|
|
37
39
|
readonly tagged?: boolean;
|
|
40
|
+
readonly pdfUA?: boolean;
|
|
38
41
|
readonly language?: string;
|
|
39
42
|
readonly attachments?: ReadonlyArray<AttachedFile>;
|
|
40
43
|
readonly signaturePlaceholder?: SignaturePlaceholder;
|
|
@@ -42,11 +45,16 @@ export interface StyledRenderOptions {
|
|
|
42
45
|
export type { DocumentInfo } from '../core/document-model/index.js';
|
|
43
46
|
export declare const A4_WIDTH = 595;
|
|
44
47
|
export declare const A4_HEIGHT = 842;
|
|
48
|
+
export interface BookmarkPosition {
|
|
49
|
+
readonly pageIdx: number;
|
|
50
|
+
readonly yTopPt: number;
|
|
51
|
+
}
|
|
45
52
|
export interface PdfLayoutAux {
|
|
46
53
|
readonly structBuilder: StructTreeBuilder | undefined;
|
|
47
54
|
readonly sectionCtxs: ReadonlyArray<SectionRenderCtx>;
|
|
48
55
|
readonly pdfaProfile: PdfAProfile | undefined;
|
|
49
56
|
readonly tagged: boolean;
|
|
57
|
+
readonly bookmarks: ReadonlyMap<string, BookmarkPosition>;
|
|
50
58
|
}
|
|
51
59
|
export interface LaidOutPdfDocument extends LaidOutDocument {
|
|
52
60
|
readonly pdf: PdfLayoutAux;
|
|
@@ -72,13 +80,21 @@ export interface SectionRenderCtx {
|
|
|
72
80
|
readonly marginBottom: number;
|
|
73
81
|
readonly contentWidth: number;
|
|
74
82
|
readonly pageContentHeight: number;
|
|
83
|
+
readonly columns?: ReadonlyArray<{
|
|
84
|
+
readonly xOffsetPt: number;
|
|
85
|
+
readonly widthPt: number;
|
|
86
|
+
}>;
|
|
75
87
|
readonly headerSet: HeaderFooterSet;
|
|
76
88
|
readonly footerSet: HeaderFooterSet;
|
|
77
89
|
readonly titlePg: boolean;
|
|
78
90
|
readonly evenAndOddHeaders: boolean;
|
|
79
91
|
}
|
|
92
|
+
interface HfBandEntry {
|
|
93
|
+
readonly commands: Array<PageItem>;
|
|
94
|
+
readonly renderDynamic?: (pageNumber: number, totalPages: number) => Array<PageItem>;
|
|
95
|
+
}
|
|
80
96
|
interface HeaderFooterSet {
|
|
81
|
-
readonly default:
|
|
82
|
-
readonly first:
|
|
83
|
-
readonly even:
|
|
97
|
+
readonly default: HfBandEntry;
|
|
98
|
+
readonly first: HfBandEntry;
|
|
99
|
+
readonly even: HfBandEntry;
|
|
84
100
|
}
|