reamkit 1.33.2 → 1.34.1
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 +13 -10
- package/dist/esm/core/converter/facade.js +1 -0
- package/dist/esm/core/converter/ream.d.ts +6 -6
- package/dist/esm/core/converter/ream.js +11 -5
- package/dist/esm/core/document-model/index.d.ts +1 -1
- package/dist/esm/core/document-model/types.d.ts +15 -0
- package/dist/esm/core/drawingml/text-warp.d.ts +34 -0
- package/dist/esm/core/drawingml/text-warp.js +49 -1
- package/dist/esm/core/font/index.d.ts +1 -1
- package/dist/esm/core/font/index.js +8 -0
- package/dist/esm/core/font/indic-shaper.d.ts +47 -0
- package/dist/esm/core/font/indic-shaper.js +1146 -0
- package/dist/esm/core/font/indic-table.d.ts +12 -0
- package/dist/esm/core/font/indic-table.js +463 -0
- package/dist/esm/core/font/measure.d.ts +45 -4
- package/dist/esm/core/font/measure.js +253 -14
- package/dist/esm/core/font/opentype-lookups.d.ts +214 -0
- package/dist/esm/core/font/opentype-lookups.js +1112 -0
- package/dist/esm/core/fonts/families.d.ts +4 -0
- package/dist/esm/core/fonts/families.js +43 -12
- package/dist/esm/core/fonts/provider.js +1 -0
- package/dist/esm/core/fonts/remote-fonts.d.ts +4 -2
- package/dist/esm/core/fonts/remote-fonts.js +36 -0
- package/dist/esm/core/fonts/scripts.d.ts +19 -0
- package/dist/esm/core/fonts/scripts.js +96 -1
- package/dist/esm/core/numbering/apply.js +12 -8
- package/dist/esm/core/style-cascade/index.d.ts +1 -1
- package/dist/esm/core/style-cascade/index.js +2 -2
- package/dist/esm/core/style-cascade/resolver.d.ts +9 -0
- package/dist/esm/core/style-cascade/resolver.js +31 -3
- package/dist/esm/core/style-cascade/table.js +3 -8
- package/dist/esm/excel/xlsx-to-pdf.js +1 -0
- package/dist/esm/index.js +1 -0
- package/dist/esm/layout/line-paint.d.ts +5 -4
- package/dist/esm/layout/line-paint.js +6 -5
- package/dist/esm/layout/page-doc.d.ts +26 -0
- package/dist/esm/layout/styled-layout.d.ts +8 -1
- package/dist/esm/layout/styled-layout.js +459 -112
- package/dist/esm/layout/undrawn.d.ts +18 -0
- package/dist/esm/layout/undrawn.js +59 -0
- package/dist/esm/pdf/cid-font.d.ts +5 -0
- package/dist/esm/pdf/cid-font.js +8 -2
- package/dist/esm/pdf/styled-page-emitter.js +117 -28
- package/dist/esm/pdf/text-page-renderer.js +1 -0
- package/dist/esm/pdf-reader/content.d.ts +1 -1
- package/dist/esm/pdf-reader/content.js +48 -13
- package/dist/esm/pdf-reader/embedded-fonts.js +1 -0
- package/dist/esm/pdf-reader/encodings.d.ts +9 -0
- package/dist/esm/pdf-reader/encodings.js +66 -1
- package/dist/esm/pdf-reader/face-outlines.js +1 -0
- package/dist/esm/pdf-reader/text.d.ts +10 -0
- package/dist/esm/pdf-reader/text.js +25 -3
- package/dist/esm/pdf-reader/vector.js +2 -2
- package/dist/esm/pptx/embedded-fonts.js +1 -0
- package/dist/esm/pptx/sp-helpers.js +4 -1
- package/dist/esm/svg/svg-text.js +30 -7
- package/dist/esm/word/doc/doc-reader.js +148 -57
- package/dist/esm/word/doc/doc-text.d.ts +80 -11
- package/dist/esm/word/doc/doc-text.js +438 -75
- package/dist/esm/word/document-parser.js +48 -28
- package/dist/esm/word/docx-to-pdf.d.ts +5 -5
- package/dist/esm/word/docx-to-pdf.js +9 -7
- package/dist/esm/word/docx-writer.js +3 -5
- package/dist/esm/word/font-embed.js +1 -0
- package/dist/esm/word/font-table.js +1 -0
- package/dist/esm/word/numbering-parser.js +3 -0
- package/dist/esm/word/paragraph-properties.js +2 -2
- package/dist/esm/word/run-properties.js +2 -2
- package/dist/esm/word/xml-helpers.d.ts +18 -0
- package/dist/esm/word/xml-helpers.js +48 -1
- package/package.json +7 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { PDF_NULL, PdfName, PdfStream } from "../pdf/objects.js";
|
|
1
|
+
import { PDF_NULL, PdfHexString, PdfName, PdfStream } from "../pdf/objects.js";
|
|
2
|
+
import { pdfTextString } from "./encodings.js";
|
|
2
3
|
import { buildColorSpaceMap, buildShadingMap } from "./shading.js";
|
|
3
4
|
import { IDENTITY, interpretContent, multiply } from "./content.js";
|
|
4
5
|
import { textMarkupOf } from "./annot-draw.js";
|
|
@@ -275,8 +276,29 @@ function spacesOf(file, resources) {
|
|
|
275
276
|
spaceCache.set(resources, made);
|
|
276
277
|
return made;
|
|
277
278
|
}
|
|
279
|
+
/**
|
|
280
|
+
* §14.9.4 — the replacement text of each property list the page's
|
|
281
|
+
* `/Properties` names: the content says `/Span /P1 BDC` and the `/ActualText`
|
|
282
|
+
* is in the resources, not in the stream.
|
|
283
|
+
*
|
|
284
|
+
* @param file The owning file.
|
|
285
|
+
* @param resources The resource dictionary in force.
|
|
286
|
+
* @returns Name → the text, for the lists that hold one.
|
|
287
|
+
*/
|
|
288
|
+
function actualTextsOf(file, resources) {
|
|
289
|
+
const out = /* @__PURE__ */ new Map();
|
|
290
|
+
const props = resources ? file.get(resources, "Properties") : void 0;
|
|
291
|
+
if (!(props instanceof Map)) return out;
|
|
292
|
+
for (const [name, value] of props) {
|
|
293
|
+
const list = file.resolve(value);
|
|
294
|
+
const text = list instanceof Map ? file.get(list, "ActualText") : void 0;
|
|
295
|
+
if (text instanceof PdfHexString) out.set(name, pdfTextString(text.bytes));
|
|
296
|
+
else if (typeof text === "string") out.set(name, pdfTextString(Uint8Array.from(text, (c) => c.charCodeAt(0) & 255)));
|
|
297
|
+
}
|
|
298
|
+
return out;
|
|
299
|
+
}
|
|
278
300
|
function collectRuns(file, resources, content, baseCtm, depth, visiting, out, shown) {
|
|
279
|
-
const result = interpretContent(content, buildFonts(file, resources), baseCtm, shadingsOf(file, resources), void 0, spacesOf(file, resources), hiddenProperties(file, resources));
|
|
301
|
+
const result = interpretContent(content, buildFonts(file, resources), baseCtm, shadingsOf(file, resources), void 0, spacesOf(file, resources), hiddenProperties(file, resources), actualTextsOf(file, resources));
|
|
280
302
|
out.push(...result.texts.map((r) => withPatternColour(file, resources, r, visiting)));
|
|
281
303
|
if (shown) addShown(shown, result.shown);
|
|
282
304
|
if (depth >= MAX_FORM_DEPTH) return;
|
|
@@ -411,4 +433,4 @@ function inRect(x, y, r) {
|
|
|
411
433
|
return x >= r[0] - 1 && x <= r[2] + 1 && y >= r[1] - 2 && y <= r[3] + 2;
|
|
412
434
|
}
|
|
413
435
|
//#endregion
|
|
414
|
-
export { buildFonts, extractPageText };
|
|
436
|
+
export { actualTextsOf, buildFonts, extractPageText };
|
|
@@ -4,7 +4,7 @@ import { buildAlphaMap, buildColorSpaceMap, buildShadingMap, gradientShading, sh
|
|
|
4
4
|
import { IDENTITY, interpretContent, multiply } from "./content.js";
|
|
5
5
|
import { appearanceContent, collectPageAppearances } from "./annots.js";
|
|
6
6
|
import { hiddenProperties, hiddenXObject } from "./optional-content.js";
|
|
7
|
-
import { buildFonts } from "./text.js";
|
|
7
|
+
import { actualTextsOf, buildFonts } from "./text.js";
|
|
8
8
|
import { patternTint, tintedHex } from "./pattern-tint.js";
|
|
9
9
|
//#region src/pdf-reader/vector.ts
|
|
10
10
|
function paintedVectors(file, page) {
|
|
@@ -53,7 +53,7 @@ function paintedVectors(file, page) {
|
|
|
53
53
|
const xobjects = resources ? file.get(resources, "XObject") : PDF_NULL;
|
|
54
54
|
const xobjDict = xobjects instanceof Map ? xobjects : void 0;
|
|
55
55
|
const maps = mapsOf(resources);
|
|
56
|
-
const result = interpretContent(content, buildFonts(file, resources), baseCtm, maps.shadings, maps.alphas, maps.spaces, hiddenProperties(file, resources));
|
|
56
|
+
const result = interpretContent(content, buildFonts(file, resources), baseCtm, maps.shadings, maps.alphas, maps.spaces, hiddenProperties(file, resources), actualTextsOf(file, resources));
|
|
57
57
|
for (const paint of result.shadings) {
|
|
58
58
|
if (out.length >= MAX_VECTORS) break;
|
|
59
59
|
const dict = resources ? file.get(resources, "Shading") : PDF_NULL;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { FontRegistry } from "../core/font/font-registry.js";
|
|
2
|
+
import "../core/font/index.js";
|
|
2
3
|
import { poAttr, poChildren, poIs } from "../core/po-helpers.js";
|
|
3
4
|
//#region src/pptx/embedded-fonts.ts
|
|
4
5
|
var REL_FONT = "http://schemas.openxmlformats.org/officeDocument/2006/relationships/font";
|
|
@@ -85,7 +85,10 @@ function rPrToRunProps(rPr, colors, themeFonts) {
|
|
|
85
85
|
...u !== void 0 ? { underline: u === "none" ? "none" : "single" } : {},
|
|
86
86
|
...sz !== void 0 ? { fontSizePt: pt(sz / 100) } : {},
|
|
87
87
|
...colorHex ? { colorHex } : {},
|
|
88
|
-
...typeface ? { fontFamily: {
|
|
88
|
+
...typeface ? { fontFamily: {
|
|
89
|
+
ascii: typeface,
|
|
90
|
+
hAnsi: typeface
|
|
91
|
+
} } : {},
|
|
89
92
|
...capsOf(poAttr(rPr, "cap"))
|
|
90
93
|
};
|
|
91
94
|
}
|
package/dist/esm/svg/svg-text.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { svgPathData } from "../core/vector.js";
|
|
2
2
|
import { reverseByCodePoint } from "../core/bidi/index.js";
|
|
3
3
|
import { FAUX_ITALIC_SHEAR, computeJustifyExtra, lineVisualOrder } from "../layout/line-paint.js";
|
|
4
|
-
import { warpGlyphMatrix } from "../core/drawingml/text-warp.js";
|
|
4
|
+
import { warpGlyphMatrix, warpGlyphRun } from "../core/drawingml/text-warp.js";
|
|
5
5
|
import { sanitizeHref } from "../core/links.js";
|
|
6
6
|
import { cffOutlineSource, openTypeCff } from "../pdf-reader/cff-outline.js";
|
|
7
7
|
import { outlineSource } from "../pdf-reader/glyf-outline.js";
|
|
@@ -128,10 +128,16 @@ function emitTextToken(out, tok, x, baseline, ctx) {
|
|
|
128
128
|
const scale = (tok.synthetic?.widthScale ?? 1) * (tok.resolvedRun.widthScale ?? 1);
|
|
129
129
|
const slant = tok.synthetic?.italic === true ? FAUX_ITALIC_SHEAR : 0;
|
|
130
130
|
const rise = tok.risePt ?? 0;
|
|
131
|
-
const
|
|
131
|
+
const reversed = tok.bidiLevel % 2 === 1;
|
|
132
|
+
const text = reversed ? reverseByCodePoint(tok.text) : tok.text;
|
|
132
133
|
const run = tok.font.measure.glyphRun(text);
|
|
133
134
|
const upem = tok.font.parsed.unitsPerEm;
|
|
134
135
|
const spacingEm = (tok.resolvedRun.letterSpacingPt ?? 0) / (size * scale);
|
|
136
|
+
const outline = tok.resolvedRun.textOutline;
|
|
137
|
+
const strokeWidth = outline !== void 0 ? outline.widthPt : tok.synthetic?.bold === true ? size * .03 : 0;
|
|
138
|
+
const colours = reversed ? tok.charColorsHex?.slice().reverse() : tok.charColorsHex;
|
|
139
|
+
const sources = colours ? tok.font.measure.glyphSources(text) : void 0;
|
|
140
|
+
const paint = (hex) => hex === void 0 || hex === tok.resolvedRun.colorHex ? "" : ` fill="#${hex}"${strokeWidth > 0 && outline === void 0 ? ` stroke="#${hex}"` : ""}`;
|
|
135
141
|
const uses = [];
|
|
136
142
|
let pen = 0;
|
|
137
143
|
for (let g = 0; g < run.gids.length; g++) {
|
|
@@ -140,12 +146,16 @@ function emitTextToken(out, tok, x, baseline, ctx) {
|
|
|
140
146
|
emitAsText(out, tok, x, baseline);
|
|
141
147
|
return;
|
|
142
148
|
}
|
|
143
|
-
|
|
144
|
-
|
|
149
|
+
const gx = pen + (run.xOffsets?.[g] ?? 0) / upem;
|
|
150
|
+
const gy = (run.yOffsets?.[g] ?? 0) / upem;
|
|
151
|
+
if (id !== null) {
|
|
152
|
+
const fill = paint(sources ? colours?.[sources[g]] : void 0);
|
|
153
|
+
uses.push(`<use href="#${id}"${gx !== 0 ? ` x="${fmtEm(gx)}"` : ""}${gy !== 0 ? ` y="${fmtEm(gy)}"` : ""}${fill}/>`);
|
|
154
|
+
}
|
|
155
|
+
const spaced = run.clusterEnds ? run.clusterEnds[g] === true : true;
|
|
156
|
+
pen += (run.advances[g] ?? 0) / upem + (spaced ? spacingEm : 0);
|
|
145
157
|
}
|
|
146
158
|
if (uses.length === 0) return;
|
|
147
|
-
const outline = tok.resolvedRun.textOutline;
|
|
148
|
-
const strokeWidth = outline !== void 0 ? outline.widthPt : tok.synthetic?.bold === true ? size * .03 : 0;
|
|
149
159
|
const stroke = strokeWidth > 0 ? ` stroke="#${outline?.colorHex ?? tok.resolvedRun.colorHex}" stroke-width="${fmtEm(strokeWidth / size)}"` : "";
|
|
150
160
|
const group = `<g transform="matrix(${fmt(size * scale)} 0 ${fmt(slant * size)} ${fmt(-size)} ${fmt(x)} ${fmt(baseline - rise)})" fill="#${tok.resolvedRun.colorHex}"${stroke}>${uses.join("")}</g>`;
|
|
151
161
|
const href = tok.href !== void 0 ? sanitizeHref(tok.href) : void 0;
|
|
@@ -282,6 +292,19 @@ function emitWarped(out, item, ctx) {
|
|
|
282
292
|
continue;
|
|
283
293
|
}
|
|
284
294
|
const size = tok.fontSizePt;
|
|
295
|
+
const group = (m, uses, hex = tok.resolvedRun.colorHex) => `<g transform="matrix(${fmt(m[0] * size)} ${fmt(m[1] * size)} ${fmt(m[2] * size)} ${fmt(m[3] * size)} ${fmt(m[4])} ${fmt(m[5])})" fill="#${hex}">${uses}</g>`;
|
|
296
|
+
if (tok.font.measure.clustered(tok.text)) {
|
|
297
|
+
const glyphs = warpGlyphRun(warp, tok.font.measure.glyphRun(tok.text), cursor, tok.widthPt, size / tok.font.parsed.unitsPerEm, item.baselineY);
|
|
298
|
+
const colours = tok.charColorsHex;
|
|
299
|
+
const sources = colours ? tok.font.measure.glyphSources(tok.text) : void 0;
|
|
300
|
+
for (const g of glyphs) {
|
|
301
|
+
const id = ctx.glyphs.use(tok.font, g.gid);
|
|
302
|
+
const hex = sources ? colours?.[sources[g.index]] : void 0;
|
|
303
|
+
if (id) out.push(group(g.matrix, `<use href="#${id}"/>`, hex));
|
|
304
|
+
}
|
|
305
|
+
cursor += tok.widthPt;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
285
308
|
const chars = [...tok.text];
|
|
286
309
|
const raw = chars.map((c) => tok.font.measure.textWidthPt(c, size));
|
|
287
310
|
const total = raw.reduce((a, b) => a + b, 0);
|
|
@@ -301,7 +324,7 @@ function emitWarped(out, item, ctx) {
|
|
|
301
324
|
pen += (run.advances[g] ?? 0) / upem;
|
|
302
325
|
}
|
|
303
326
|
if (uses.length === 0) continue;
|
|
304
|
-
out.push(
|
|
327
|
+
out.push(group(m, uses.join("")));
|
|
305
328
|
}
|
|
306
329
|
}
|
|
307
330
|
}
|
|
@@ -27,6 +27,11 @@ var TABLE_BORDERS = {
|
|
|
27
27
|
insideH: TABLE_BORDER,
|
|
28
28
|
insideV: TABLE_BORDER
|
|
29
29
|
};
|
|
30
|
+
var LVL_FOLLOW = [
|
|
31
|
+
" ",
|
|
32
|
+
" ",
|
|
33
|
+
""
|
|
34
|
+
];
|
|
30
35
|
function createListMarkers(tables) {
|
|
31
36
|
const counters = /* @__PURE__ */ new Map();
|
|
32
37
|
const resolve = (ilfo, ilvl) => {
|
|
@@ -43,7 +48,8 @@ function createListMarkers(tables) {
|
|
|
43
48
|
const r = resolve(ilfo, ilvl);
|
|
44
49
|
if (!r) return void 0;
|
|
45
50
|
const { lsid, lvl } = r;
|
|
46
|
-
|
|
51
|
+
const follow = LVL_FOLLOW[lvl.follow] ?? " ";
|
|
52
|
+
if (lvl.nfc === 23 || lvl.nfc === 255 || !lvl.xst.some((cu) => cu <= 8)) return lvl.xst.map((cu) => String.fromCharCode(cu)).join("") + follow;
|
|
47
53
|
const col = counters.get(lsid) ?? [];
|
|
48
54
|
col[ilvl] = (col[ilvl] ?? lvl.iStartAt - 1) + 1;
|
|
49
55
|
for (let l = ilvl + 1; l < col.length; l++) col[l] = void 0;
|
|
@@ -53,7 +59,7 @@ function createListMarkers(tables) {
|
|
|
53
59
|
const other = resolve(ilfo, cu);
|
|
54
60
|
out += formatListNumber(other?.lvl.nfc ?? 0, col[cu] ?? other?.lvl.iStartAt ?? 1);
|
|
55
61
|
} else out += String.fromCharCode(cu);
|
|
56
|
-
return out;
|
|
62
|
+
return out + follow;
|
|
57
63
|
} };
|
|
58
64
|
}
|
|
59
65
|
function formatListNumber(nfc, n) {
|
|
@@ -128,54 +134,79 @@ var DOC_ENCRYPTED_LOSS = {
|
|
|
128
134
|
function readDoc(bytes) {
|
|
129
135
|
const content = extractDocContent(bytes);
|
|
130
136
|
const resources = new ResourceStore();
|
|
131
|
-
const body = buildBody(content, resources);
|
|
137
|
+
const { body, sectionEnds } = buildBody(content, resources);
|
|
132
138
|
const headersFooters = /* @__PURE__ */ new Map();
|
|
133
|
-
const
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
const
|
|
138
|
-
const
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
139
|
+
const bandIds = /* @__PURE__ */ new Map();
|
|
140
|
+
const bands = (index) => {
|
|
141
|
+
const headers = [];
|
|
142
|
+
const footers = [];
|
|
143
|
+
const hf = content.headerFooters?.[index];
|
|
144
|
+
const add = (story, kind, type) => {
|
|
145
|
+
if (!story) return;
|
|
146
|
+
let id = bandIds.get(story);
|
|
147
|
+
if (id === void 0) {
|
|
148
|
+
id = `${kind}-${type}-${index}`;
|
|
149
|
+
bandIds.set(story, id);
|
|
150
|
+
headersFooters.set(id, resolveBodyStyles(story.flatMap((p) => mapParagraph(p, resources)), EMPTY_STYLE_SHEET));
|
|
151
|
+
}
|
|
152
|
+
(kind === "header" ? headers : footers).push({
|
|
153
|
+
type,
|
|
154
|
+
relationshipId: id
|
|
155
|
+
});
|
|
156
|
+
};
|
|
157
|
+
add(hf?.defaultHeader, "header", "default");
|
|
158
|
+
add(hf?.firstHeader, "header", "first");
|
|
159
|
+
add(hf?.evenHeader, "header", "even");
|
|
160
|
+
add(hf?.defaultFooter, "footer", "default");
|
|
161
|
+
add(hf?.firstFooter, "footer", "first");
|
|
162
|
+
add(hf?.evenFooter, "footer", "even");
|
|
163
|
+
return {
|
|
164
|
+
headers,
|
|
165
|
+
footers
|
|
166
|
+
};
|
|
144
167
|
};
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
168
|
+
const sectionProperties = (sec, index) => {
|
|
169
|
+
const ps = sec?.pageSize;
|
|
170
|
+
const sectionStart = sec?.bkc !== void 0 ? sectionStartOf(sec.bkc) : void 0;
|
|
171
|
+
const pageNumberFormat = sec?.pageNumberNfc !== void 0 ? pageNumberFormatOf(sec.pageNumberNfc) : void 0;
|
|
172
|
+
return {
|
|
173
|
+
pageSize: ps ? {
|
|
174
|
+
width: pt(ps.widthPt),
|
|
175
|
+
height: pt(ps.heightPt),
|
|
176
|
+
orientation: ps.widthPt > ps.heightPt ? "landscape" : "portrait"
|
|
177
|
+
} : {
|
|
178
|
+
width: pt(612),
|
|
179
|
+
height: pt(792)
|
|
180
|
+
},
|
|
181
|
+
margins: toMargins(sec?.margins),
|
|
182
|
+
...bands(index),
|
|
183
|
+
...sec?.titlePage ? { titlePg: true } : {},
|
|
184
|
+
...content.facingPages ? { evenAndOddHeaders: true } : {},
|
|
185
|
+
...sectionStart ? { sectionStart } : {},
|
|
186
|
+
...sec?.pageNumberStart !== void 0 ? { pageNumberStart: sec.pageNumberStart } : {},
|
|
187
|
+
...pageNumberFormat ? { pageNumberFormat } : {}
|
|
188
|
+
};
|
|
162
189
|
};
|
|
190
|
+
const docSections = content.sections ?? [];
|
|
191
|
+
const sections = [];
|
|
192
|
+
for (const end of sectionEnds) {
|
|
193
|
+
const last = sections[sections.length - 1]?.endIndex ?? 0;
|
|
194
|
+
if (end.endIndex <= last || end.endIndex >= body.length) continue;
|
|
195
|
+
sections.push({
|
|
196
|
+
properties: sectionProperties(docSections[end.section], end.section),
|
|
197
|
+
endIndex: end.endIndex
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
if (sections.length > 0) sections.push({
|
|
201
|
+
properties: sectionProperties(docSections[docSections.length - 1], docSections.length - 1),
|
|
202
|
+
endIndex: body.length
|
|
203
|
+
});
|
|
163
204
|
return {
|
|
164
205
|
doc: {
|
|
165
206
|
kind: "flow",
|
|
166
207
|
body: resolveBodyStyles(body, EMPTY_STYLE_SHEET),
|
|
167
|
-
sections
|
|
168
|
-
section:
|
|
169
|
-
pageSize,
|
|
170
|
-
margins: {
|
|
171
|
-
top: pt(72),
|
|
172
|
-
right: pt(72),
|
|
173
|
-
bottom: pt(72),
|
|
174
|
-
left: pt(72)
|
|
175
|
-
},
|
|
176
|
-
headers,
|
|
177
|
-
footers
|
|
178
|
-
},
|
|
208
|
+
sections,
|
|
209
|
+
section: sectionProperties(docSections[0], 0),
|
|
179
210
|
styles: EMPTY_STYLE_SHEET,
|
|
180
211
|
resources,
|
|
181
212
|
...headersFooters.size > 0 ? { headersFooters } : {},
|
|
@@ -186,12 +217,16 @@ function readDoc(bytes) {
|
|
|
186
217
|
}
|
|
187
218
|
function buildBody(content, resources) {
|
|
188
219
|
const markers = createListMarkers(content.listTables);
|
|
189
|
-
const
|
|
220
|
+
const sectionEnds = [];
|
|
221
|
+
const body = groupTables(content.paragraphs, resources, markers, sectionEnds);
|
|
190
222
|
while (body.length > 1 && isEmptyParagraph(body[body.length - 1])) body.pop();
|
|
191
223
|
if (body.length === 0) body.push(emptyParagraph());
|
|
192
|
-
return
|
|
224
|
+
return {
|
|
225
|
+
body,
|
|
226
|
+
sectionEnds
|
|
227
|
+
};
|
|
193
228
|
}
|
|
194
|
-
function groupTables(paras, resources, markers) {
|
|
229
|
+
function groupTables(paras, resources, markers, sectionEnds) {
|
|
195
230
|
const out = [];
|
|
196
231
|
let i = 0;
|
|
197
232
|
while (i < paras.length) if (paras[i].props.inTable) {
|
|
@@ -201,7 +236,14 @@ function groupTables(paras, resources, markers) {
|
|
|
201
236
|
kind: "table",
|
|
202
237
|
table: buildTable(group, resources, markers)
|
|
203
238
|
});
|
|
204
|
-
} else
|
|
239
|
+
} else {
|
|
240
|
+
const p = paras[i++];
|
|
241
|
+
out.push(...mapParagraph(p, resources, markers));
|
|
242
|
+
if (p.sectionEnd !== void 0) sectionEnds.push({
|
|
243
|
+
section: p.sectionEnd,
|
|
244
|
+
endIndex: out.length
|
|
245
|
+
});
|
|
246
|
+
}
|
|
205
247
|
return out;
|
|
206
248
|
}
|
|
207
249
|
function buildTable(group, resources, markers) {
|
|
@@ -331,16 +373,24 @@ function mapParagraph(p, resources, markers) {
|
|
|
331
373
|
const textRuns = p.runs.filter((r) => r.picture === void 0);
|
|
332
374
|
const ilvl = p.props.listIlvl ?? 0;
|
|
333
375
|
const isList = (p.props.listIlfo ?? 0) > 0;
|
|
334
|
-
if (textRuns.some((r) => r.text.length > 0) || isList) {
|
|
376
|
+
if (textRuns.some((r) => r.text.length > 0 || r.pageBreak || r.field) || isList) {
|
|
335
377
|
const runs = textRuns.map((r) => ({
|
|
336
378
|
text: r.text,
|
|
337
|
-
properties: toRunProperties(r.props)
|
|
379
|
+
properties: toRunProperties(r.props),
|
|
380
|
+
...r.pageBreak ? { pageBreak: true } : {},
|
|
381
|
+
...r.columnBreak ? { columnBreak: true } : {},
|
|
382
|
+
...r.field ? { field: r.field } : {}
|
|
338
383
|
}));
|
|
384
|
+
const last = textRuns[textRuns.length - 1];
|
|
385
|
+
if (last?.pageBreak || last?.columnBreak) runs.push({
|
|
386
|
+
text: ZERO_WIDTH_SPACE,
|
|
387
|
+
properties: p.mark ? toRunProperties(p.mark) : {}
|
|
388
|
+
});
|
|
339
389
|
let properties = toParaProperties(p.props);
|
|
340
390
|
if (isList) {
|
|
341
|
-
const marker = markers?.marker(p.props.listIlfo, ilvl) ?? LIST_BULLETS[ilvl % LIST_BULLETS.length]
|
|
391
|
+
const marker = markers?.marker(p.props.listIlfo, ilvl) ?? `${LIST_BULLETS[ilvl % LIST_BULLETS.length]} `;
|
|
342
392
|
runs.unshift({
|
|
343
|
-
text:
|
|
393
|
+
text: marker,
|
|
344
394
|
properties: {}
|
|
345
395
|
});
|
|
346
396
|
properties = {
|
|
@@ -360,7 +410,7 @@ function mapParagraph(p, resources, markers) {
|
|
|
360
410
|
});
|
|
361
411
|
}
|
|
362
412
|
for (const r of p.runs) if (r.picture) out.push(imageBlock(r.picture, resources, toParaProperties(p.props)));
|
|
363
|
-
if (out.length === 0) out.push(emptyParagraph());
|
|
413
|
+
if (out.length === 0) out.push(emptyParagraph(toParaProperties(p.props), p.mark));
|
|
364
414
|
return out;
|
|
365
415
|
}
|
|
366
416
|
function imageBlock(pic, resources, paragraphProperties) {
|
|
@@ -374,14 +424,14 @@ function imageBlock(pic, resources, paragraphProperties) {
|
|
|
374
424
|
}
|
|
375
425
|
};
|
|
376
426
|
}
|
|
377
|
-
function emptyParagraph() {
|
|
427
|
+
function emptyParagraph(properties = {}, mark = void 0) {
|
|
378
428
|
return {
|
|
379
429
|
kind: "paragraph",
|
|
380
430
|
paragraph: {
|
|
381
|
-
properties
|
|
431
|
+
properties,
|
|
382
432
|
runs: [{
|
|
383
433
|
text: ZERO_WIDTH_SPACE,
|
|
384
|
-
properties: {}
|
|
434
|
+
properties: mark ? toRunProperties(mark) : {}
|
|
385
435
|
}]
|
|
386
436
|
}
|
|
387
437
|
};
|
|
@@ -392,13 +442,24 @@ function isEmptyParagraph(el) {
|
|
|
392
442
|
function toRunProperties(p) {
|
|
393
443
|
const underline = p.underlineKul ? underlineStyle(p.underlineKul) : void 0;
|
|
394
444
|
const size = p.sizeHalfPts && p.sizeHalfPts > 0 ? pt(p.sizeHalfPts / 2) : void 0;
|
|
445
|
+
const fontFamily = toFontFamily(p);
|
|
395
446
|
return {
|
|
396
447
|
...p.bold ? { bold: true } : {},
|
|
397
448
|
...p.italic ? { italic: true } : {},
|
|
398
449
|
...underline ? { underline } : {},
|
|
399
|
-
...size ? { fontSizePt: size } : {}
|
|
450
|
+
...size ? { fontSizePt: size } : {},
|
|
451
|
+
...fontFamily ? { fontFamily } : {}
|
|
400
452
|
};
|
|
401
453
|
}
|
|
454
|
+
function toFontFamily(p) {
|
|
455
|
+
const fonts = {
|
|
456
|
+
...p.fontAscii ? { ascii: p.fontAscii } : {},
|
|
457
|
+
...p.fontOther ? { hAnsi: p.fontOther } : {},
|
|
458
|
+
...p.fontEastAsia ? { eastAsia: p.fontEastAsia } : {},
|
|
459
|
+
...p.fontBi ? { cs: p.fontBi } : {}
|
|
460
|
+
};
|
|
461
|
+
return Object.keys(fonts).length > 0 ? fonts : void 0;
|
|
462
|
+
}
|
|
402
463
|
function toParaProperties(p) {
|
|
403
464
|
const alignment = p.jc !== void 0 ? alignmentFromJc(p.jc) : void 0;
|
|
404
465
|
return {
|
|
@@ -410,7 +471,8 @@ function toParaProperties(p) {
|
|
|
410
471
|
...p.spaceAfterTwips !== void 0 ? { spacingAfter: pt(p.spaceAfterTwips / 20) } : {},
|
|
411
472
|
...p.widowControl !== void 0 ? { widowControl: p.widowControl } : {},
|
|
412
473
|
...p.keepNext !== void 0 ? { keepNext: p.keepNext } : {},
|
|
413
|
-
...p.keepLines !== void 0 ? { keepLines: p.keepLines } : {}
|
|
474
|
+
...p.keepLines !== void 0 ? { keepLines: p.keepLines } : {},
|
|
475
|
+
...p.pageBreakBefore !== void 0 ? { pageBreakBefore: p.pageBreakBefore } : {}
|
|
414
476
|
};
|
|
415
477
|
}
|
|
416
478
|
function underlineStyle(kul) {
|
|
@@ -422,6 +484,35 @@ function underlineStyle(kul) {
|
|
|
422
484
|
default: return "single";
|
|
423
485
|
}
|
|
424
486
|
}
|
|
487
|
+
function toMargins(m) {
|
|
488
|
+
const side = (twips) => twipsToPt(twips ?? 1440);
|
|
489
|
+
return {
|
|
490
|
+
top: side(m?.topTwips),
|
|
491
|
+
right: side(m?.rightTwips),
|
|
492
|
+
bottom: side(m?.bottomTwips),
|
|
493
|
+
left: side(m?.leftTwips),
|
|
494
|
+
...m?.headerTwips !== void 0 ? { header: twipsToPt(m.headerTwips) } : {},
|
|
495
|
+
...m?.footerTwips !== void 0 ? { footer: twipsToPt(m.footerTwips) } : {},
|
|
496
|
+
...m?.gutterTwips ? { gutter: twipsToPt(m.gutterTwips) } : {}
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
function sectionStartOf(bkc) {
|
|
500
|
+
switch (bkc) {
|
|
501
|
+
case 0: return "continuous";
|
|
502
|
+
case 3: return "evenPage";
|
|
503
|
+
case 4: return "oddPage";
|
|
504
|
+
default: return "nextPage";
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
function pageNumberFormatOf(nfc) {
|
|
508
|
+
switch (nfc) {
|
|
509
|
+
case 1: return "upperRoman";
|
|
510
|
+
case 2: return "lowerRoman";
|
|
511
|
+
case 3: return "upperLetter";
|
|
512
|
+
case 4: return "lowerLetter";
|
|
513
|
+
default: return;
|
|
514
|
+
}
|
|
515
|
+
}
|
|
425
516
|
function alignmentFromJc(jc) {
|
|
426
517
|
switch (jc) {
|
|
427
518
|
case 1: return "center";
|
|
@@ -6,6 +6,14 @@ export interface DocCharProps {
|
|
|
6
6
|
readonly underlineKul?: number;
|
|
7
7
|
/** Font size in half-points. */
|
|
8
8
|
readonly sizeHalfPts?: number;
|
|
9
|
+
/** The font of ASCII characters, by name — `sprmCRgFtc0`. */
|
|
10
|
+
readonly fontAscii?: string;
|
|
11
|
+
/** The font of East Asian characters — `sprmCRgFtc1`. */
|
|
12
|
+
readonly fontEastAsia?: string;
|
|
13
|
+
/** The font of the characters neither ASCII nor East Asian — `sprmCRgFtc2`. */
|
|
14
|
+
readonly fontOther?: string;
|
|
15
|
+
/** The font of complex-script characters — `sprmCFtcBi`. */
|
|
16
|
+
readonly fontBi?: string;
|
|
9
17
|
/** The character is special — `sprmCFSpec` (e.g. a picture placeholder). */
|
|
10
18
|
readonly fSpec?: boolean;
|
|
11
19
|
/** `sprmCPicLocation` — offset into the `Data` stream of the picture's PICF. */
|
|
@@ -80,6 +88,8 @@ export interface DocParaProps {
|
|
|
80
88
|
readonly keepNext?: boolean;
|
|
81
89
|
/** `sprmPFKeep` — every line on one page. */
|
|
82
90
|
readonly keepLines?: boolean;
|
|
91
|
+
/** `sprmPFPageBreakBefore` — the paragraph starts a new page. */
|
|
92
|
+
readonly pageBreakBefore?: boolean;
|
|
83
93
|
}
|
|
84
94
|
/** One maximal same-formatting run of text, or an embedded picture. */
|
|
85
95
|
export interface DocRun {
|
|
@@ -87,6 +97,15 @@ export interface DocRun {
|
|
|
87
97
|
readonly props: DocCharProps;
|
|
88
98
|
/** Set instead of `text` when the run is an embedded picture. */
|
|
89
99
|
readonly picture?: DocPicture;
|
|
100
|
+
/** The run is a manual page break (0x0C), alone in a run with no text. */
|
|
101
|
+
readonly pageBreak?: boolean;
|
|
102
|
+
/** The run is a manual column break (0x0E), alone in a run whose text is `\n`. */
|
|
103
|
+
readonly columnBreak?: boolean;
|
|
104
|
+
/**
|
|
105
|
+
* The run is the result of a PAGE or NUMPAGES field, whose `text` is the
|
|
106
|
+
* number Word cached when it last saved.
|
|
107
|
+
*/
|
|
108
|
+
readonly field?: 'PAGE' | 'NUMPAGES';
|
|
90
109
|
}
|
|
91
110
|
/** One paragraph: its runs plus the decoded paragraph properties. */
|
|
92
111
|
export interface DocParagraph {
|
|
@@ -97,11 +116,59 @@ export interface DocParagraph {
|
|
|
97
116
|
* closes a table cell. With `props.rowEnd` it also closes the row.
|
|
98
117
|
*/
|
|
99
118
|
readonly cellMark?: boolean;
|
|
119
|
+
/**
|
|
120
|
+
* The paragraph was ended by the section mark of the (zero-based) section it
|
|
121
|
+
* is the last of — the index into {@link DocContent.sections}.
|
|
122
|
+
*/
|
|
123
|
+
readonly sectionEnd?: number;
|
|
124
|
+
/** The paragraph mark's own character properties: what an empty line is set in. */
|
|
125
|
+
readonly mark?: DocCharProps;
|
|
126
|
+
}
|
|
127
|
+
/** One section of the main document (§2.8.26 PlcfSed): what its SEP says. */
|
|
128
|
+
export interface DocSection {
|
|
129
|
+
/**
|
|
130
|
+
* `sprmSBkc` — where the section starts: 0 on the page in hand, 1 in the next
|
|
131
|
+
* column, 2 on a new page, 3 on an even page, 4 on an odd page. Absent when
|
|
132
|
+
* the SEP says nothing, which is a new page.
|
|
133
|
+
*/
|
|
134
|
+
readonly bkc?: number;
|
|
135
|
+
/** The page size in points (xaPage/yaPage), when present and sane. */
|
|
136
|
+
readonly pageSize?: {
|
|
137
|
+
readonly widthPt: number;
|
|
138
|
+
readonly heightPt: number;
|
|
139
|
+
};
|
|
140
|
+
/** `sprmSFTitlePage` — the first page shows the first-page header and footer. */
|
|
141
|
+
readonly titlePage?: boolean;
|
|
142
|
+
/** The margins the SEP states, in twips; each absent one is Word's default. */
|
|
143
|
+
readonly margins?: DocMargins;
|
|
144
|
+
/**
|
|
145
|
+
* `sprmSFPgnRestart` with `sprmSPgnStart97` — the number the section's first
|
|
146
|
+
* page is printed with, when the section starts its count again; absent, the
|
|
147
|
+
* count runs on from the section before.
|
|
148
|
+
*/
|
|
149
|
+
readonly pageNumberStart?: number;
|
|
150
|
+
/** `sprmSNfcPgn` — the MSONFC the section's page numbers print in; absent, arabic. */
|
|
151
|
+
readonly pageNumberNfc?: number;
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* A section's margins in twips (§2.6.4 sprmSDxaLeft … sprmSDzaGutter). A top or
|
|
155
|
+
* bottom margin is stored signed, negative for one the header or footer may not
|
|
156
|
+
* push; its size is the same either way.
|
|
157
|
+
*/
|
|
158
|
+
export interface DocMargins {
|
|
159
|
+
readonly leftTwips?: number;
|
|
160
|
+
readonly rightTwips?: number;
|
|
161
|
+
readonly topTwips?: number;
|
|
162
|
+
readonly bottomTwips?: number;
|
|
163
|
+
readonly headerTwips?: number;
|
|
164
|
+
readonly footerTwips?: number;
|
|
165
|
+
readonly gutterTwips?: number;
|
|
100
166
|
}
|
|
101
167
|
/**
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
*
|
|
168
|
+
* One section's header/footer stories ([MS-DOC] Plcfhdd: six per section, after
|
|
169
|
+
* the six separator stories). `default` is the odd-page (primary) one. A story
|
|
170
|
+
* the section leaves empty is the previous section's, the very same array; one
|
|
171
|
+
* that holds no text is absent.
|
|
105
172
|
*/
|
|
106
173
|
export interface DocHeaderFooters {
|
|
107
174
|
readonly defaultHeader?: ReadonlyArray<DocParagraph>;
|
|
@@ -121,6 +188,8 @@ export interface DocLvl {
|
|
|
121
188
|
readonly nfc: number;
|
|
122
189
|
readonly iStartAt: number;
|
|
123
190
|
readonly xst: ReadonlyArray<number>;
|
|
191
|
+
/** `ixchFollow` — what follows the number text: 0 a tab, 1 a space, 2 nothing. */
|
|
192
|
+
readonly follow: number;
|
|
124
193
|
}
|
|
125
194
|
/**
|
|
126
195
|
* The document's list tables (DOC-10): an LFO → lsid map (a paragraph's 1-based
|
|
@@ -136,23 +205,23 @@ export interface DocListTables {
|
|
|
136
205
|
/** Everything extracted from a `.doc`'s `WordDocument` stream. */
|
|
137
206
|
export interface DocContent {
|
|
138
207
|
readonly paragraphs: ReadonlyArray<DocParagraph>;
|
|
139
|
-
|
|
208
|
+
/** Each section's header/footer stories, in section order. */
|
|
209
|
+
readonly headerFooters?: ReadonlyArray<DocHeaderFooters>;
|
|
210
|
+
/** `Dop.fFacingPages` — even pages show the even header and footer. */
|
|
211
|
+
readonly facingPages?: boolean;
|
|
140
212
|
/** The list tables resolving a paragraph's (ilfo, ilvl) to its number format. */
|
|
141
213
|
readonly listTables?: DocListTables;
|
|
142
214
|
/**
|
|
143
|
-
*
|
|
144
|
-
*
|
|
215
|
+
* Every section of the main document, in order, when the PlcfSed is usable. A
|
|
216
|
+
* section with no page size takes the reader's default (US Letter).
|
|
145
217
|
*/
|
|
146
|
-
readonly
|
|
147
|
-
readonly widthPt: number;
|
|
148
|
-
readonly heightPt: number;
|
|
149
|
-
};
|
|
218
|
+
readonly sections?: ReadonlyArray<DocSection>;
|
|
150
219
|
/** The file is encrypted/obfuscated — text cannot be read without the key. */
|
|
151
220
|
readonly encrypted: boolean;
|
|
152
221
|
}
|
|
153
222
|
/**
|
|
154
223
|
* Extract a `.doc`'s `WordDocument` stream into its main-document paragraphs plus
|
|
155
|
-
* the section's header/footer stories, list tables and
|
|
224
|
+
* the section's header/footer stories, list tables and sections.
|
|
156
225
|
* Joins the piece table (CLX → CPs/FCs), CHPX run formatting and PAPX paragraph
|
|
157
226
|
* formatting, splitting at the CR / cell marks. Every offset is bounds-checked, so
|
|
158
227
|
* a structurally odd file yields no text rather than throwing.
|