reamkit 1.33.1 → 1.34.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 +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/crypto/index.js +1 -2
- 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/slide-parser.js +22 -7
- package/dist/esm/pptx/sp-helpers.d.ts +3 -2
- package/dist/esm/pptx/sp-helpers.js +9 -4
- 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 +3 -3
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { resolveFamilyStyle } from "../core/fonts/remote-fonts.js";
|
|
2
|
-
import { scriptForCodepoint } from "../core/fonts/scripts.js";
|
|
3
|
-
import { shapeText } from "../core/font/opentype-layout.js";
|
|
2
|
+
import { fontSlotOf, scriptForCodepoint } from "../core/fonts/scripts.js";
|
|
4
3
|
import { hasSubstitutable, substituteLetters } from "../core/font/ligatures.js";
|
|
5
|
-
import {
|
|
4
|
+
import { hasIndic, syllableStarts } from "../core/font/indic-shaper.js";
|
|
5
|
+
import { createFontMeasure, shapeGlyphs } from "../core/font/measure.js";
|
|
6
|
+
import "../core/font/index.js";
|
|
6
7
|
import { pageItemBounds } from "./page-doc.js";
|
|
7
8
|
import { halfPtToPt, pt } from "../core/ir/units.js";
|
|
8
9
|
import { ResourceStore } from "../core/ir/resources.js";
|
|
@@ -31,6 +32,7 @@ import { rectPath } from "../core/drawingml/preset-geometry.js";
|
|
|
31
32
|
import { buildShapePaths, buildShapeTransform, buildStroke, gradientToSolid, lineEndPaths } from "../core/drawingml/shape-render.js";
|
|
32
33
|
import { StructTreeBuilder } from "../pdf/struct-tree.js";
|
|
33
34
|
import { floatIntoFrame, turnOntoSheet } from "./turned-section.js";
|
|
35
|
+
import { undrawnLoss } from "./undrawn.js";
|
|
34
36
|
//#region src/layout/styled-layout.ts
|
|
35
37
|
function parsePdfAProfile(pdfA) {
|
|
36
38
|
const part = Number(pdfA.charAt(6));
|
|
@@ -584,7 +586,7 @@ function layoutStyledDocument(body, options) {
|
|
|
584
586
|
const group = bodyFrames.starts.get(idx);
|
|
585
587
|
if (group) return layoutFrameBlock(group, ctx.options ?? options, fontResources, imageResources, width);
|
|
586
588
|
if (bodyFrames.continued.has(idx)) return emptyBlock(options);
|
|
587
|
-
return layoutBodyElement(own, ctx.options ?? options, fontResources, imageResources, width, ctx.pageContentHeight, box);
|
|
589
|
+
return layoutBodyElement(own, ctx.options ?? options, fontResources, imageResources, width, ctx.pageContentHeight, box, true);
|
|
588
590
|
});
|
|
589
591
|
for (let i = 0; i < blocks.length; i++) {
|
|
590
592
|
const here = blocks[i];
|
|
@@ -696,9 +698,14 @@ function layoutStyledDocument(body, options) {
|
|
|
696
698
|
}
|
|
697
699
|
const bookmarks = /* @__PURE__ */ new Map();
|
|
698
700
|
const commentNotes = options.commentAnnotations && options.comments ? buildCommentNotes(noteAssigned.comments, options.comments) : void 0;
|
|
699
|
-
const reflowParagraph = (paragraph, width, widths) => layoutParagraphBlock(paragraph, options, fontResources, imageResources, width, widths);
|
|
701
|
+
const reflowParagraph = (paragraph, width, widths) => layoutParagraphBlock(paragraph, options, fontResources, imageResources, width, widths, true);
|
|
702
|
+
const pages = paginateSections(blocks, sectionCtxs, structBuilder, options.language ?? "en-US", notePlan, bookmarks, reflowParagraph, backgroundImageOf(options, imageResources), collapsesSpacing(options), options.compatibilityMode);
|
|
703
|
+
if (options.losses) {
|
|
704
|
+
const undrawn = undrawnLoss(pages);
|
|
705
|
+
if (undrawn) options.losses.push(undrawn);
|
|
706
|
+
}
|
|
700
707
|
return {
|
|
701
|
-
pages
|
|
708
|
+
pages,
|
|
702
709
|
resources: options.resources ?? new ResourceStore(),
|
|
703
710
|
fontResources,
|
|
704
711
|
imageResources,
|
|
@@ -1328,8 +1335,8 @@ function laidOutBlocksFor(elements, options, fontResources, contentWidth, imageR
|
|
|
1328
1335
|
return layoutBodyElement(el, options, fontResources, imageResources, contentWidth, void 0, box);
|
|
1329
1336
|
});
|
|
1330
1337
|
}
|
|
1331
|
-
function layoutBodyElement(el, options, fontResources, imageResources, contentWidth, maxHeight, box) {
|
|
1332
|
-
if (el.kind === "paragraph") return layoutParagraphBlock(el.paragraph, options, fontResources, imageResources, contentWidth);
|
|
1338
|
+
function layoutBodyElement(el, options, fontResources, imageResources, contentWidth, maxHeight, box, pageFlow = false) {
|
|
1339
|
+
if (el.kind === "paragraph") return layoutParagraphBlock(el.paragraph, options, fontResources, imageResources, contentWidth, void 0, pageFlow);
|
|
1333
1340
|
if (el.kind === "table") return layoutTableBlock(el.table, options, fontResources, imageResources, contentWidth);
|
|
1334
1341
|
if (el.kind === "image") {
|
|
1335
1342
|
const res = el.image.resource ? imageResources?.get(el.image.resource) : void 0;
|
|
@@ -2891,13 +2898,13 @@ function runFontKeyAndParsed(options, ascii, bold, italic) {
|
|
|
2891
2898
|
/**
|
|
2892
2899
|
* The face a CHARACTER falls back to when the run's own has no glyph for it.
|
|
2893
2900
|
*
|
|
2894
|
-
* The curated substitutes are Latin families: Han, Kana, Hangul, Arabic, Thai
|
|
2895
|
-
* and the geometric symbols are a notdef box in every one
|
|
2896
|
-
* characters of the corpus are exactly those. A face for the
|
|
2897
|
-
* writing system is fetched when the document holds one (see
|
|
2898
|
-
* and this is where a run reaches it — followed by every
|
|
2899
|
-
* loaded, because a glyph one of them happens to carry is
|
|
2900
|
-
* box.
|
|
2901
|
+
* The curated substitutes are Latin families: Han, Kana, Hangul, Arabic, Thai,
|
|
2902
|
+
* the scripts of India and the geometric symbols are a notdef box in every one
|
|
2903
|
+
* of them, and 2145 characters of the corpus are exactly those. A face for the
|
|
2904
|
+
* character's writing system is fetched when the document holds one (see
|
|
2905
|
+
* `addScriptFonts`), and this is where a run reaches it — followed by every
|
|
2906
|
+
* other family already loaded, because a glyph one of them happens to carry is
|
|
2907
|
+
* still better than a box.
|
|
2901
2908
|
*
|
|
2902
2909
|
* Called from BOTH walks that need it — the glyph-subset collection and the
|
|
2903
2910
|
* tokenizer — so the face a character is measured, subset and drawn with is one
|
|
@@ -2943,19 +2950,17 @@ function fallbackFaceKey(options, cp, bold, italic) {
|
|
|
2943
2950
|
* Only where nobody can draw the character itself: one a face DOES carry is
|
|
2944
2951
|
* drawn as written.
|
|
2945
2952
|
*
|
|
2946
|
-
* @param
|
|
2947
|
-
* @param
|
|
2948
|
-
* @param resolved Its resolved properties (weight and slant).
|
|
2949
|
-
* @param text The run's text.
|
|
2953
|
+
* @param faces The face each character of the run is drawn with ({@link runFaces}).
|
|
2954
|
+
* @param text The run's text.
|
|
2950
2955
|
* @returns The text to set, which is `text` itself unless something was undrawable.
|
|
2951
2956
|
*/
|
|
2952
|
-
function drawableText(
|
|
2957
|
+
function drawableText(faces, text) {
|
|
2953
2958
|
if (!hasSubstitutable(text)) return text;
|
|
2954
2959
|
let out = "";
|
|
2955
2960
|
for (const ch of text) {
|
|
2956
2961
|
const cp = ch.codePointAt(0);
|
|
2957
2962
|
const letters = substituteLetters(cp);
|
|
2958
|
-
const drawable = letters === void 0 ||
|
|
2963
|
+
const drawable = letters === void 0 || faces(cp).parsed.glyphForCodepoint(cp) !== 0;
|
|
2959
2964
|
out += drawable ? ch : letters;
|
|
2960
2965
|
}
|
|
2961
2966
|
return out;
|
|
@@ -2994,7 +2999,7 @@ function syntheticFace(variant, bold, italic, widthScale) {
|
|
|
2994
2999
|
*/
|
|
2995
3000
|
function splitByFace(plan, text) {
|
|
2996
3001
|
const first = faceOf(plan, text.codePointAt(0) ?? 0);
|
|
2997
|
-
if (!plan.faceFor || plan.faceFor.size === 0) return [{
|
|
3002
|
+
if (text === "" || !plan.faceFor || plan.faceFor.size === 0) return [{
|
|
2998
3003
|
text,
|
|
2999
3004
|
face: first
|
|
3000
3005
|
}];
|
|
@@ -3029,24 +3034,120 @@ function faceOf(plan, cp) {
|
|
|
3029
3034
|
...plan.synthetic ? { synthetic: plan.synthetic } : {}
|
|
3030
3035
|
};
|
|
3031
3036
|
}
|
|
3037
|
+
var INDIC_JOINS = /* @__PURE__ */ new Set([
|
|
3038
|
+
8204,
|
|
3039
|
+
8205,
|
|
3040
|
+
9676,
|
|
3041
|
+
160,
|
|
3042
|
+
2404,
|
|
3043
|
+
2405
|
|
3044
|
+
]);
|
|
3045
|
+
/**
|
|
3046
|
+
* Whether a family has a face of its OWN on hand — embedded, or a substitute
|
|
3047
|
+
* fetched for it — rather than the first registry, which is where
|
|
3048
|
+
* {@link runFontKeyAndParsed} sends a family nothing was fetched for.
|
|
3049
|
+
*
|
|
3050
|
+
* @param options The render options (the loaded faces).
|
|
3051
|
+
* @param family The family's name.
|
|
3052
|
+
*/
|
|
3053
|
+
function hasOwnFace(options, family) {
|
|
3054
|
+
if (options.embeddedFonts?.has(family.trim().toLowerCase()) === true) return true;
|
|
3055
|
+
return options.registriesByFamily?.has(resolveFamilyStyle(family).key) === true;
|
|
3056
|
+
}
|
|
3057
|
+
/**
|
|
3058
|
+
* §17.3.2.26 — the faces a run's other slots name, where one is on hand and is
|
|
3059
|
+
* not the face its ascii slot names.
|
|
3060
|
+
*
|
|
3061
|
+
* @param options The render options (the loaded faces).
|
|
3062
|
+
* @param resolved The run's resolved properties.
|
|
3063
|
+
* @param primary The run's own face, its ascii slot's.
|
|
3064
|
+
* @returns The face per slot, or `undefined` when every slot draws in `primary`.
|
|
3065
|
+
*/
|
|
3066
|
+
function slotFaces(options, resolved, primary) {
|
|
3067
|
+
let out;
|
|
3068
|
+
for (const slot of [
|
|
3069
|
+
"hAnsi",
|
|
3070
|
+
"eastAsia",
|
|
3071
|
+
"cs"
|
|
3072
|
+
]) {
|
|
3073
|
+
const family = resolved.fontFamily[slot];
|
|
3074
|
+
if (family === void 0 || !hasOwnFace(options, family)) continue;
|
|
3075
|
+
const face = runFontKeyAndParsed(options, family, resolved.bold, resolved.italic);
|
|
3076
|
+
if (face.fontKey !== primary.fontKey) (out ??= /* @__PURE__ */ new Map()).set(slot, face);
|
|
3077
|
+
}
|
|
3078
|
+
return out;
|
|
3079
|
+
}
|
|
3080
|
+
/**
|
|
3081
|
+
* {@link fallbackFaceKey}, with what the face lacks of the run's weight and
|
|
3082
|
+
* slant: a script face is fetched in the regular weight only, and a bold run in
|
|
3083
|
+
* it is stroked, which is what F3's synthesis is for.
|
|
3084
|
+
*/
|
|
3085
|
+
function fallbackFace(options, cp, resolved) {
|
|
3086
|
+
const face = fallbackFaceKey(options, cp, resolved.bold, resolved.italic);
|
|
3087
|
+
if (!face) return void 0;
|
|
3088
|
+
const variant = face.fontKey.split(":")[1];
|
|
3089
|
+
const got = variant === "bold" || variant === "boldItalic" || variant === "italic" ? variant : "regular";
|
|
3090
|
+
return {
|
|
3091
|
+
...face,
|
|
3092
|
+
...syntheticFace(got, resolved.bold, resolved.italic)
|
|
3093
|
+
};
|
|
3094
|
+
}
|
|
3095
|
+
/**
|
|
3096
|
+
* §17.3.2.26 — the face each character of a run is drawn with: the one its
|
|
3097
|
+
* slot names ({@link fontSlotOf}), else the run's own, else whichever face on
|
|
3098
|
+
* hand has the glyph ({@link fallbackFaceKey}).
|
|
3099
|
+
*
|
|
3100
|
+
* A run names a face per slot, and Word sets each character in its slot's:
|
|
3101
|
+
* "split" in a run's ascii Verdana and "Кириллица" beside it in its hAnsi
|
|
3102
|
+
* Courier New. We drew both in Verdana's substitute, and the hAnsi face was
|
|
3103
|
+
* read for nothing but the line's height. A slot whose family has no face on
|
|
3104
|
+
* hand leaves its characters to the run's own, as before.
|
|
3105
|
+
*
|
|
3106
|
+
* Called from every walk that needs it — the glyph-subset collection, the
|
|
3107
|
+
* tokenizer, a table cell's measure — so the face a character is measured,
|
|
3108
|
+
* subset and drawn with is one decision made once.
|
|
3109
|
+
*
|
|
3110
|
+
* @param options The render options (the loaded faces).
|
|
3111
|
+
* @param resolved The run's resolved properties (its faces, weight, slant, direction).
|
|
3112
|
+
* @param primary The run's own face, the one its ascii slot names.
|
|
3113
|
+
* @param text The run's text, for the scripts of India (see {@link INDIC_JOINS}).
|
|
3114
|
+
* @returns The face a code point of the run is drawn with.
|
|
3115
|
+
*/
|
|
3116
|
+
function runFaces(options, resolved, primary, text) {
|
|
3117
|
+
const slots = slotFaces(options, resolved, primary);
|
|
3118
|
+
const drawing = (cp) => {
|
|
3119
|
+
const own = slots?.get(fontSlotOf(cp, resolved.rtl)) ?? primary;
|
|
3120
|
+
if (own.parsed.glyphForCodepoint(cp) !== 0) return own;
|
|
3121
|
+
if (own !== primary && primary.parsed.glyphForCodepoint(cp) !== 0) return primary;
|
|
3122
|
+
return fallbackFace(options, cp, resolved) ?? own;
|
|
3123
|
+
};
|
|
3124
|
+
const indic = indicFace(drawing, text);
|
|
3125
|
+
if (!indic) return drawing;
|
|
3126
|
+
return (cp) => INDIC_JOINS.has(cp) && indic.parsed.glyphForCodepoint(cp) !== 0 ? indic : drawing(cp);
|
|
3127
|
+
}
|
|
3128
|
+
/** The face that draws the first letter of the scripts of India in `text`. */
|
|
3129
|
+
function indicFace(drawing, text) {
|
|
3130
|
+
for (const ch of text) {
|
|
3131
|
+
const cp = ch.codePointAt(0);
|
|
3132
|
+
if (cp < 2304 || cp > 3455 || cp === 2404 || cp === 2405) continue;
|
|
3133
|
+
return drawing(cp);
|
|
3134
|
+
}
|
|
3135
|
+
}
|
|
3032
3136
|
/**
|
|
3033
3137
|
* `text` cut into the stretches ONE face draws, by the same decision the
|
|
3034
|
-
* tokenizer makes — the whole string as a single span when
|
|
3035
|
-
*
|
|
3138
|
+
* tokenizer makes — the whole string as a single span when one face draws all
|
|
3139
|
+
* of it, which is nearly always.
|
|
3036
3140
|
*
|
|
3037
|
-
* @param
|
|
3038
|
-
* @param
|
|
3039
|
-
* @param resolved Its resolved properties (weight and slant).
|
|
3040
|
-
* @param text The run's text.
|
|
3141
|
+
* @param faces The face each character of the run is drawn with ({@link runFaces}).
|
|
3142
|
+
* @param text The run's text.
|
|
3041
3143
|
*/
|
|
3042
|
-
function faceSpans(
|
|
3144
|
+
function faceSpans(faces, text) {
|
|
3043
3145
|
const out = [];
|
|
3044
|
-
let cur
|
|
3146
|
+
let cur;
|
|
3045
3147
|
let buf = "";
|
|
3046
3148
|
for (const ch of text) {
|
|
3047
|
-
const
|
|
3048
|
-
|
|
3049
|
-
if (buf !== "" && face.fontKey !== cur.fontKey) {
|
|
3149
|
+
const face = faces(ch.codePointAt(0));
|
|
3150
|
+
if (cur && face.fontKey !== cur.fontKey) {
|
|
3050
3151
|
out.push({
|
|
3051
3152
|
...cur,
|
|
3052
3153
|
text: buf
|
|
@@ -3056,37 +3157,34 @@ function faceSpans(options, primary, resolved, text) {
|
|
|
3056
3157
|
cur = face;
|
|
3057
3158
|
buf += ch;
|
|
3058
3159
|
}
|
|
3059
|
-
if (
|
|
3160
|
+
if (cur) out.push({
|
|
3060
3161
|
...cur,
|
|
3061
3162
|
text: buf
|
|
3062
3163
|
});
|
|
3063
3164
|
return out;
|
|
3064
3165
|
}
|
|
3065
3166
|
/**
|
|
3066
|
-
* The face for every character of `text` the run's own
|
|
3167
|
+
* The face for every character of `text` the run's own does not draw.
|
|
3067
3168
|
*
|
|
3068
|
-
* @param options The render options (the loaded registries).
|
|
3069
3169
|
* @param fontResources The faces the document embeds, by key.
|
|
3070
3170
|
* @param primary The run's own face.
|
|
3071
|
-
* @param
|
|
3171
|
+
* @param faces The face each character is drawn with ({@link runFaces}).
|
|
3072
3172
|
* @param text The run's text.
|
|
3073
3173
|
* @returns A map from code point to face, or `undefined` when the run's own
|
|
3074
3174
|
* face draws all of it.
|
|
3075
3175
|
*/
|
|
3076
|
-
function facesForRun(
|
|
3176
|
+
function facesForRun(fontResources, primary, faces, text) {
|
|
3077
3177
|
let out;
|
|
3078
3178
|
for (const ch of text) {
|
|
3079
3179
|
const cp = ch.codePointAt(0);
|
|
3080
|
-
if (out?.has(cp) === true
|
|
3081
|
-
const
|
|
3082
|
-
if (
|
|
3083
|
-
const font = fontResources.get(
|
|
3180
|
+
if (out?.has(cp) === true) continue;
|
|
3181
|
+
const face = faces(cp);
|
|
3182
|
+
if (face.fontKey === primary.fontKey) continue;
|
|
3183
|
+
const font = fontResources.get(face.fontKey);
|
|
3084
3184
|
if (!font) continue;
|
|
3085
|
-
out ??= /* @__PURE__ */ new Map()
|
|
3086
|
-
const { variant } = { variant: fallback.fontKey.split(":")[1] ?? "" };
|
|
3087
|
-
out.set(cp, {
|
|
3185
|
+
(out ??= /* @__PURE__ */ new Map()).set(cp, {
|
|
3088
3186
|
font,
|
|
3089
|
-
...
|
|
3187
|
+
...face.synthetic ? { synthetic: face.synthetic } : {}
|
|
3090
3188
|
});
|
|
3091
3189
|
}
|
|
3092
3190
|
return out;
|
|
@@ -3099,7 +3197,8 @@ function collectFontResources(body, headersFooters, options, imageResources) {
|
|
|
3099
3197
|
const numerals = [...new Set([options.section, ...(options.sections ?? []).map((sec) => sec.properties)].flatMap((p) => p?.pageNumberFormat !== void 0 ? [p.pageNumberFormat] : []))].map((fmt) => Array.from({ length: 200 }, (_, i) => formatCounter(fmt, i + 1)).join("")).join("");
|
|
3100
3198
|
const addRun = (run, para) => {
|
|
3101
3199
|
const resolved = resolveRunProperties(run.properties, para.properties, options.styles);
|
|
3102
|
-
const
|
|
3200
|
+
const primary = runFontKeyAndParsed(options, resolved.fontFamily.ascii, resolved.bold, resolved.italic);
|
|
3201
|
+
const { fontKey, parsed } = primary;
|
|
3103
3202
|
let bucket = used.get(fontKey);
|
|
3104
3203
|
if (!bucket) {
|
|
3105
3204
|
bucket = {
|
|
@@ -3108,11 +3207,9 @@ function collectFontResources(body, headersFooters, options, imageResources) {
|
|
|
3108
3207
|
};
|
|
3109
3208
|
used.set(fontKey, bucket);
|
|
3110
3209
|
}
|
|
3111
|
-
const
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
parsed
|
|
3115
|
-
}, resolved, text)) {
|
|
3210
|
+
const faces = runFaces(options, resolved, primary, run.text);
|
|
3211
|
+
const text = drawableText(faces, run.text);
|
|
3212
|
+
for (const span of faceSpans(faces, text)) {
|
|
3116
3213
|
let sink = used.get(span.fontKey);
|
|
3117
3214
|
if (!sink) {
|
|
3118
3215
|
sink = {
|
|
@@ -3121,8 +3218,13 @@ function collectFontResources(body, headersFooters, options, imageResources) {
|
|
|
3121
3218
|
};
|
|
3122
3219
|
used.set(span.fontKey, sink);
|
|
3123
3220
|
}
|
|
3124
|
-
const
|
|
3221
|
+
const shown = run.listMarker === true ? markerText(span.text, span.parsed) : span.text;
|
|
3222
|
+
const shaped = shapeGlyphs(span.parsed, shown);
|
|
3125
3223
|
for (const g of shaped.gids) sink.gids.add(g);
|
|
3224
|
+
for (const [g, t] of shaped.glyphText ?? []) {
|
|
3225
|
+
sink.glyphText ??= /* @__PURE__ */ new Map();
|
|
3226
|
+
if (!sink.glyphText.has(g)) sink.glyphText.set(g, t);
|
|
3227
|
+
}
|
|
3126
3228
|
}
|
|
3127
3229
|
if (run.text.includes(" ")) for (const stop of resolveParagraphProperties(para.properties, options.styles).tabs) {
|
|
3128
3230
|
const ch = stop.leader === void 0 ? void 0 : TAB_LEADER_CHARS.get(stop.leader);
|
|
@@ -3287,7 +3389,8 @@ function collectFontResources(body, headersFooters, options, imageResources) {
|
|
|
3287
3389
|
resourceName,
|
|
3288
3390
|
parsed: info.parsed,
|
|
3289
3391
|
measure: createFontMeasure(info.parsed, options.layoutProfile !== "word"),
|
|
3290
|
-
gids: info.gids
|
|
3392
|
+
gids: info.gids,
|
|
3393
|
+
...info.glyphText ? { glyphText: info.glyphText } : {}
|
|
3291
3394
|
});
|
|
3292
3395
|
}
|
|
3293
3396
|
return out;
|
|
@@ -3342,7 +3445,7 @@ function paragraphLeading(pp, options) {
|
|
|
3342
3445
|
markLinePt: m.ascentPt + m.descentPt + m.lineGapPt
|
|
3343
3446
|
};
|
|
3344
3447
|
}
|
|
3345
|
-
function layoutParagraphBlock(paragraph, options, fontResources, imageResources, contentWidth, lineWidths) {
|
|
3448
|
+
function layoutParagraphBlock(paragraph, options, fontResources, imageResources, contentWidth, lineWidths, pageFlow = false) {
|
|
3346
3449
|
const cascaded = resolveParagraphProperties(paragraph.properties, options.styles);
|
|
3347
3450
|
const baseResolved = options.gridLinePitchPt !== void 0 && cascaded.snapToGrid !== false ? {
|
|
3348
3451
|
...cascaded,
|
|
@@ -3367,20 +3470,24 @@ function layoutParagraphBlock(paragraph, options, fontResources, imageResources,
|
|
|
3367
3470
|
...baseResolved,
|
|
3368
3471
|
alignment: "right"
|
|
3369
3472
|
} : baseResolved;
|
|
3370
|
-
const
|
|
3473
|
+
const all = tokenizeParagraph(paragraph, options, fontResources, imageResources, contentWidth, baseDir);
|
|
3474
|
+
const tokens = pageFlow ? all : all.filter((t) => !(t.kind === "text" && t.pageBreak));
|
|
3371
3475
|
const firstLineWidth = paragraphMaxWidth(resolved, contentWidth, true);
|
|
3372
3476
|
const otherWidth = paragraphMaxWidth(resolved, contentWidth, false);
|
|
3373
3477
|
const leading = paragraphLeading(paragraph.properties, options);
|
|
3374
3478
|
const runWrap = (part, firstWidth, widths) => wrap(part, firstWidth, otherWidth, resolved, options.hyphenator, options.layoutProfile ?? "ream", leading, options.doNotExpandShiftReturn === true, widths);
|
|
3375
|
-
const
|
|
3479
|
+
const flow = splitAtBreaks(tokens);
|
|
3376
3480
|
const columnBreakLines = /* @__PURE__ */ new Set();
|
|
3481
|
+
const pageBreakLines = /* @__PURE__ */ new Set();
|
|
3377
3482
|
let wrapped;
|
|
3378
|
-
if (parts.length === 1) wrapped = runWrap(tokens, firstLineWidth, lineWidths);
|
|
3483
|
+
if (flow.parts.length === 1) wrapped = runWrap(flow.parts[0].tokens, firstLineWidth, lineWidths);
|
|
3379
3484
|
else {
|
|
3380
3485
|
wrapped = [];
|
|
3381
|
-
for (const [i, part] of parts.entries()) {
|
|
3382
|
-
const
|
|
3383
|
-
|
|
3486
|
+
for (const [i, part] of flow.parts.entries()) {
|
|
3487
|
+
const width = i === 0 ? firstLineWidth : otherWidth;
|
|
3488
|
+
const lines = runWrap(part.tokens, width, lineWidths?.slice(wrapped.length));
|
|
3489
|
+
if (lines.length === 0 && (part.opens === "page" || part.endsPage)) lines.push(emptyLine(paragraph, options, resolved, Math.max(width, 0)));
|
|
3490
|
+
if (i > 0 && lines.length > 0) (part.opens === "page" ? pageBreakLines : columnBreakLines).add(wrapped.length);
|
|
3384
3491
|
wrapped.push(...i === 0 ? lines : lines.map((l) => ({
|
|
3385
3492
|
...l,
|
|
3386
3493
|
firstLine: false
|
|
@@ -3391,10 +3498,6 @@ function layoutParagraphBlock(paragraph, options, fontResources, imageResources,
|
|
|
3391
3498
|
let heightPt = 0;
|
|
3392
3499
|
for (const line of lines) heightPt += computeLineHeight(line, resolved);
|
|
3393
3500
|
const numbering = paragraph.properties.numbering;
|
|
3394
|
-
const breakIdx = paragraph.runs.findIndex((r) => r.pageBreak === true);
|
|
3395
|
-
const holds = (r) => r.text !== "" || r.inlineImage !== void 0 || r.math !== void 0;
|
|
3396
|
-
const leadingPageBreak = breakIdx >= 0 && paragraph.runs.slice(0, breakIdx + 1).every((r) => r.text === "" && r.inlineImage === void 0) && paragraph.runs.slice(breakIdx + 1).some(holds);
|
|
3397
|
-
const trailingPageBreak = paragraph.runs.some((r, i) => r.pageBreak === true && (!leadingPageBreak || i > breakIdx));
|
|
3398
3501
|
return {
|
|
3399
3502
|
kind: "paragraph",
|
|
3400
3503
|
resolved,
|
|
@@ -3406,9 +3509,10 @@ function layoutParagraphBlock(paragraph, options, fontResources, imageResources,
|
|
|
3406
3509
|
numId: numbering.numId,
|
|
3407
3510
|
level: numbering.ilvl
|
|
3408
3511
|
} } : {},
|
|
3409
|
-
...
|
|
3410
|
-
...
|
|
3512
|
+
...flow.pageBreakBefore ? { pageBreakBefore: true } : {},
|
|
3513
|
+
...flow.pageBreakAfter ? { pageBreakAfter: true } : {},
|
|
3411
3514
|
...columnBreakLines.size > 0 ? { columnBreakLines } : {},
|
|
3515
|
+
...pageBreakLines.size > 0 ? { pageBreakLines } : {},
|
|
3412
3516
|
...paragraph.bookmarks && paragraph.bookmarks.length > 0 ? { bookmarks: paragraph.bookmarks } : {},
|
|
3413
3517
|
source: paragraph
|
|
3414
3518
|
};
|
|
@@ -3500,7 +3604,132 @@ function displayCapsRuns(paragraph, options) {
|
|
|
3500
3604
|
start = i;
|
|
3501
3605
|
}
|
|
3502
3606
|
}
|
|
3503
|
-
return any ? out : paragraph.runs;
|
|
3607
|
+
return mendSplitSyllables(any ? out : paragraph.runs);
|
|
3608
|
+
}
|
|
3609
|
+
/** A run of plain text: no picture, equation, field, note mark or list marker. */
|
|
3610
|
+
function plainTextRun(run) {
|
|
3611
|
+
return run.text.length > 0 && run.inlineImage === void 0 && run.math === void 0 && run.field === void 0 && run.footnoteRef === void 0 && run.endnoteRef === void 0 && run.noteNumber !== true && run.listMarker !== true;
|
|
3612
|
+
}
|
|
3613
|
+
var SYLLABLE_REACH = 32;
|
|
3614
|
+
/** Per code point of a run's text, the properties of the run each character was typed in. */
|
|
3615
|
+
function charPropertiesOf(run) {
|
|
3616
|
+
return run.charProperties ?? [...run.text].map(() => run.properties);
|
|
3617
|
+
}
|
|
3618
|
+
/**
|
|
3619
|
+
* A syllable of the scripts of India that a run boundary cuts, given whole to
|
|
3620
|
+
* the run it begins in. Word sets runs apart for a colour, a weight, a tracked
|
|
3621
|
+
* change or a spelling mark, and nothing stops one from starting at a vowel
|
|
3622
|
+
* sign: font issues.docx colours each sign of कृष्ण apart, and
|
|
3623
|
+
* Untitled 2.docx ends a bold run on the र् of a reph. Shaped run by run,
|
|
3624
|
+
* each sign stood alone on a dotted circle and the reph stayed a Ra and a
|
|
3625
|
+
* virama. The syllable now takes the formatting of the run it begins in…
|
|
3626
|
+
*
|
|
3627
|
+
* …all but its colour. Word draws each sign of कृष्ण in the colour of its
|
|
3628
|
+
* own run, on the one shaped syllable, so each character brought along keeps
|
|
3629
|
+
* the properties of the run it came from (`charProperties`), and the glyphs
|
|
3630
|
+
* are painted from them.
|
|
3631
|
+
*/
|
|
3632
|
+
function mendSplitSyllables(runs) {
|
|
3633
|
+
if (!runs.some((r) => hasIndic(r.text))) return runs;
|
|
3634
|
+
const out = [...runs];
|
|
3635
|
+
let prev = -1;
|
|
3636
|
+
for (let k = 0; k < out.length; k++) {
|
|
3637
|
+
const run = out[k];
|
|
3638
|
+
if (!plainTextRun(run)) {
|
|
3639
|
+
if (run.text.length > 0 || run.inlineImage !== void 0 || run.math !== void 0) prev = -1;
|
|
3640
|
+
continue;
|
|
3641
|
+
}
|
|
3642
|
+
const before = prev >= 0 ? out[prev] : void 0;
|
|
3643
|
+
if (before && hasIndic(before.text + run.text)) {
|
|
3644
|
+
const tail = [...before.text].slice(-32);
|
|
3645
|
+
const head = [...run.text].slice(0, SYLLABLE_REACH);
|
|
3646
|
+
const starts = syllableStarts([...tail, ...head].join(""));
|
|
3647
|
+
let moved = 0;
|
|
3648
|
+
while (moved < head.length && !starts[tail.length + moved]) moved++;
|
|
3649
|
+
if (moved > 0) {
|
|
3650
|
+
const take = head.slice(0, moved).join("");
|
|
3651
|
+
const brought = charPropertiesOf(run);
|
|
3652
|
+
out[prev] = {
|
|
3653
|
+
...before,
|
|
3654
|
+
text: before.text + take,
|
|
3655
|
+
charProperties: [...charPropertiesOf(before), ...brought.slice(0, moved)]
|
|
3656
|
+
};
|
|
3657
|
+
out[k] = {
|
|
3658
|
+
...run,
|
|
3659
|
+
text: run.text.slice(take.length),
|
|
3660
|
+
...run.charProperties ? { charProperties: brought.slice(moved) } : {}
|
|
3661
|
+
};
|
|
3662
|
+
}
|
|
3663
|
+
}
|
|
3664
|
+
if (out[k].text.length > 0) prev = k;
|
|
3665
|
+
}
|
|
3666
|
+
return out;
|
|
3667
|
+
}
|
|
3668
|
+
/**
|
|
3669
|
+
* The colour each character of a run is drawn in, where a split syllable
|
|
3670
|
+
* brought characters of other runs into it: each takes its own run's colour,
|
|
3671
|
+
* resolved through the styles as that run's would be.
|
|
3672
|
+
*
|
|
3673
|
+
* @param options The render options (for the style cascade).
|
|
3674
|
+
* @param paragraph The paragraph the run is in.
|
|
3675
|
+
* @param faces The face each character of the run is drawn with ({@link runFaces}).
|
|
3676
|
+
* @param resolved The run's resolved properties.
|
|
3677
|
+
* @param run The run, as {@link mendSplitSyllables} left it.
|
|
3678
|
+
* @param drawn Its text as drawn ({@link drawableText}).
|
|
3679
|
+
* @returns Per code point of `drawn`, its colour — or nothing when every
|
|
3680
|
+
* character is in the run's own.
|
|
3681
|
+
*/
|
|
3682
|
+
function charColours(options, paragraph, faces, resolved, run, drawn) {
|
|
3683
|
+
const from = run.charProperties;
|
|
3684
|
+
if (!from) return void 0;
|
|
3685
|
+
const hexOf = /* @__PURE__ */ new Map([[run.properties, resolved.colorHex]]);
|
|
3686
|
+
const out = [];
|
|
3687
|
+
let i = 0;
|
|
3688
|
+
for (const ch of run.text) {
|
|
3689
|
+
const props = from[i++] ?? run.properties;
|
|
3690
|
+
let hex = hexOf.get(props);
|
|
3691
|
+
if (hex === void 0) {
|
|
3692
|
+
hex = resolveRunProperties(props, paragraph.properties, options.styles).colorHex;
|
|
3693
|
+
hexOf.set(props, hex);
|
|
3694
|
+
}
|
|
3695
|
+
const letters = drawn === run.text ? 1 : [...drawableText(faces, ch)].length;
|
|
3696
|
+
for (let k = 0; k < letters; k++) out.push(hex);
|
|
3697
|
+
}
|
|
3698
|
+
return out.some((hex) => hex !== resolved.colorHex) ? out : void 0;
|
|
3699
|
+
}
|
|
3700
|
+
/**
|
|
3701
|
+
* A token's share of the per-character colours it was cut from: the code
|
|
3702
|
+
* points [from, to) of them, kept only when one differs from the token's own.
|
|
3703
|
+
*
|
|
3704
|
+
* @param colours The colours of what the token was cut from, if any.
|
|
3705
|
+
* @param ownHex The token's own colour.
|
|
3706
|
+
* @param from The first code point the token takes.
|
|
3707
|
+
* @param to The code point after its last.
|
|
3708
|
+
* @returns `{ charColorsHex }` when the token needs them, `{}` when not.
|
|
3709
|
+
*/
|
|
3710
|
+
function charColoursOf(colours, ownHex, from, to) {
|
|
3711
|
+
if (!colours) return {};
|
|
3712
|
+
const own = colours.slice(from, to);
|
|
3713
|
+
return own.some((hex) => hex !== ownHex) ? { charColorsHex: own } : {};
|
|
3714
|
+
}
|
|
3715
|
+
/**
|
|
3716
|
+
* A piece cut out of a text token — its code points [from, from + its text's
|
|
3717
|
+
* length) — with the token's formatting and its own share of the per-character
|
|
3718
|
+
* colours.
|
|
3719
|
+
*/
|
|
3720
|
+
function tokenPiece(tok, text, from, widthPt) {
|
|
3721
|
+
const { charColorsHex, ...rest } = tok;
|
|
3722
|
+
if (!charColorsHex) return {
|
|
3723
|
+
...tok,
|
|
3724
|
+
text,
|
|
3725
|
+
widthPt
|
|
3726
|
+
};
|
|
3727
|
+
return {
|
|
3728
|
+
...rest,
|
|
3729
|
+
text,
|
|
3730
|
+
widthPt,
|
|
3731
|
+
...charColoursOf(charColorsHex, tok.resolvedRun.colorHex, from, from + [...text].length)
|
|
3732
|
+
};
|
|
3504
3733
|
}
|
|
3505
3734
|
function tokenizeParagraph(paragraph, options, fontResources, imageResources, contentWidth, baseDir) {
|
|
3506
3735
|
const plans = displayCapsRuns(paragraph, options).map((run) => {
|
|
@@ -3570,22 +3799,26 @@ function tokenizeParagraph(paragraph, options, fontResources, imageResources, co
|
|
|
3570
3799
|
};
|
|
3571
3800
|
}
|
|
3572
3801
|
const resolvedRun = resolveRunProperties(run.properties, paragraph.properties, options.styles);
|
|
3573
|
-
const
|
|
3802
|
+
const primary = runFontKeyAndParsed(options, resolvedRun.fontFamily.ascii, resolvedRun.bold, resolvedRun.italic);
|
|
3803
|
+
const { fontKey, synthetic } = primary;
|
|
3574
3804
|
const script = SCRIPT_OFFSET[resolvedRun.verticalAlign] ?? 0;
|
|
3575
3805
|
const own = lookupFont(fontResources, fontKey);
|
|
3806
|
+
const faces = runFaces(options, resolvedRun, primary, run.text);
|
|
3576
3807
|
const drawn = (() => {
|
|
3577
|
-
const text = drawableText(
|
|
3808
|
+
const text = drawableText(faces, run.text);
|
|
3578
3809
|
return text === run.text ? run : {
|
|
3579
3810
|
...run,
|
|
3580
3811
|
text
|
|
3581
3812
|
};
|
|
3582
3813
|
})();
|
|
3583
|
-
const faceFor = facesForRun(
|
|
3814
|
+
const faceFor = facesForRun(fontResources, primary, faces, drawn.text);
|
|
3815
|
+
const charColorsHex = charColours(options, paragraph, faces, resolvedRun, run, drawn.text);
|
|
3584
3816
|
return {
|
|
3585
3817
|
run: drawn,
|
|
3586
3818
|
resolvedRun,
|
|
3587
3819
|
font: own,
|
|
3588
3820
|
...faceFor ? { faceFor } : {},
|
|
3821
|
+
...charColorsHex ? { charColorsHex } : {},
|
|
3589
3822
|
...synthetic ? { synthetic } : {},
|
|
3590
3823
|
fontSizePt: script === 0 ? resolvedRun.fontSizePt : resolvedRun.fontSizePt * SCRIPT_SCALE,
|
|
3591
3824
|
...script === 0 ? {} : { risePt: resolvedRun.fontSizePt * script },
|
|
@@ -3605,17 +3838,62 @@ function tokenizeParagraph(paragraph, options, fontResources, imageResources, co
|
|
|
3605
3838
|
})), baseDir));
|
|
3606
3839
|
}
|
|
3607
3840
|
/**
|
|
3608
|
-
* Split a paragraph's tokens at every §17.3.3.1 column break, dropping
|
|
3609
|
-
* break token itself (its line ends there anyway). One part ⇒ no break.
|
|
3841
|
+
* Split a paragraph's tokens at every §17.3.3.1 column or page break, dropping
|
|
3842
|
+
* the break token itself (its line ends there anyway). One part ⇒ no break.
|
|
3843
|
+
*
|
|
3844
|
+
* A forced page break happens WHERE IT STANDS. One that precedes every word of
|
|
3845
|
+
* the paragraph moves the paragraph itself to the next page, one that follows
|
|
3846
|
+
* every word sends what FOLLOWS the paragraph there, and neither splits it. One
|
|
3847
|
+
* between two of its words splits it there: Word sets "Latin", a page break and
|
|
3848
|
+
* "Second" on two pages, and read as a break after the paragraph, both words
|
|
3849
|
+
* stood on the first.
|
|
3850
|
+
*
|
|
3851
|
+
* A PICTURE is not nothing, however empty its run's text: the break after it
|
|
3852
|
+
* sends what follows to the next page, it does not move the paragraph. Counted
|
|
3853
|
+
* as empty, tdf125657.docx — a full-page image and then a break — kept the
|
|
3854
|
+
* break's page to itself and lost the one the mark after it stands on.
|
|
3855
|
+
*
|
|
3856
|
+
* …and a break is before the paragraph only when something FOLLOWS it. A
|
|
3857
|
+
* paragraph that is nothing but the break — a page break typed on a line of its
|
|
3858
|
+
* own — keeps its mark with the break, on the page the break ends, and what
|
|
3859
|
+
* follows starts the next page at its very top. Word for Mac, in every mode: a
|
|
3860
|
+
* document ending in one is one page, two in a row leave the second alone at
|
|
3861
|
+
* the top of page 2, and the paragraph after the break stands at the page's
|
|
3862
|
+
* first line. Moved to the next page with its mark, it stood an empty line down
|
|
3863
|
+
* every such page.
|
|
3610
3864
|
*
|
|
3611
3865
|
* @param tokens The paragraph's tokens in order.
|
|
3612
|
-
* @returns The parts, in order
|
|
3866
|
+
* @returns The parts, in order (always at least one), and whether the
|
|
3867
|
+
* paragraph breaks the page before itself and after itself.
|
|
3613
3868
|
*/
|
|
3614
|
-
function
|
|
3615
|
-
const
|
|
3616
|
-
|
|
3617
|
-
|
|
3618
|
-
|
|
3869
|
+
function splitAtBreaks(tokens) {
|
|
3870
|
+
const isPage = (t) => t.kind === "text" && t.pageBreak === true;
|
|
3871
|
+
const pageBreakBefore = tokens.length > 0 && isPage(tokens[0]) && tokens.some((t) => !isPage(t));
|
|
3872
|
+
const pageBreakAfter = tokens.length > 0 && isPage(tokens[tokens.length - 1]);
|
|
3873
|
+
const parts = [{
|
|
3874
|
+
tokens: [],
|
|
3875
|
+
endsPage: false
|
|
3876
|
+
}];
|
|
3877
|
+
const end = pageBreakAfter ? tokens.length - 1 : tokens.length;
|
|
3878
|
+
for (const t of tokens.slice(pageBreakBefore ? 1 : 0, end)) if (isPage(t)) {
|
|
3879
|
+
parts[parts.length - 1].endsPage = true;
|
|
3880
|
+
parts.push({
|
|
3881
|
+
tokens: [],
|
|
3882
|
+
opens: "page",
|
|
3883
|
+
endsPage: false
|
|
3884
|
+
});
|
|
3885
|
+
} else if (t.kind === "text" && t.columnBreak) parts.push({
|
|
3886
|
+
tokens: [],
|
|
3887
|
+
opens: "column",
|
|
3888
|
+
endsPage: false
|
|
3889
|
+
});
|
|
3890
|
+
else parts[parts.length - 1].tokens.push(t);
|
|
3891
|
+
if (pageBreakAfter) parts[parts.length - 1].endsPage = true;
|
|
3892
|
+
return {
|
|
3893
|
+
parts: parts.filter((p, i) => i === 0 || p.tokens.length > 0 || p.opens === "page" || p.endsPage),
|
|
3894
|
+
pageBreakBefore,
|
|
3895
|
+
pageBreakAfter
|
|
3896
|
+
};
|
|
3619
3897
|
}
|
|
3620
3898
|
function tokenizePlansLtr(plans) {
|
|
3621
3899
|
const tokens = [];
|
|
@@ -3652,13 +3930,21 @@ function tokenizePlansLtr(plans) {
|
|
|
3652
3930
|
});
|
|
3653
3931
|
continue;
|
|
3654
3932
|
}
|
|
3933
|
+
if (plan.run.listMarkerSpace === true) {
|
|
3934
|
+
tokens.push(markerSpaceToken(plan, 0));
|
|
3935
|
+
continue;
|
|
3936
|
+
}
|
|
3655
3937
|
const highlight = (plan.run.commentRangeRefs?.length ?? 0) > 0;
|
|
3938
|
+
let at = 0;
|
|
3656
3939
|
for (const t of tokenizeText(plan.run.text)) for (const piece of splitByFace(plan, t.text)) {
|
|
3657
|
-
const text = plan.run.listMarker === true ? markerText(piece.text,
|
|
3940
|
+
const text = plan.run.listMarker === true ? markerText(piece.text, piece.face.font.parsed) : piece.text;
|
|
3941
|
+
const from = at;
|
|
3942
|
+
if (plan.charColorsHex) at += t.tab ? 1 : [...piece.text].length;
|
|
3658
3943
|
tokens.push({
|
|
3659
3944
|
kind: "text",
|
|
3660
3945
|
text,
|
|
3661
3946
|
isSpace: t.isSpace,
|
|
3947
|
+
...charColoursOf(plan.charColorsHex, plan.resolvedRun.colorHex, from, at),
|
|
3662
3948
|
...t.tab ? { tab: true } : {},
|
|
3663
3949
|
...plan.run.href !== void 0 ? { href: plan.run.href } : {},
|
|
3664
3950
|
...plan.run.footnoteRef !== void 0 ? { footnoteRef: plan.run.footnoteRef } : {},
|
|
@@ -3675,10 +3961,58 @@ function tokenizePlansLtr(plans) {
|
|
|
3675
3961
|
bidiLevel: 0
|
|
3676
3962
|
});
|
|
3677
3963
|
}
|
|
3964
|
+
if (plan.run.pageBreak) tokens.push(pageBreakToken(plan));
|
|
3678
3965
|
}
|
|
3679
3966
|
return tokens;
|
|
3680
3967
|
}
|
|
3681
3968
|
/**
|
|
3969
|
+
* §17.3.3.1 — the mark a `w:br w:type="page"` leaves among its paragraph's
|
|
3970
|
+
* tokens: nothing to draw, only where the break stands. A run that holds text
|
|
3971
|
+
* as well breaks after it, the way the writer writes one. It never reaches a
|
|
3972
|
+
* line: the paragraph is split at it before it is wrapped.
|
|
3973
|
+
*
|
|
3974
|
+
* @param plan The break's run.
|
|
3975
|
+
* @returns An empty text token flagged `pageBreak`.
|
|
3976
|
+
*/
|
|
3977
|
+
function pageBreakToken(plan) {
|
|
3978
|
+
return {
|
|
3979
|
+
kind: "text",
|
|
3980
|
+
text: "",
|
|
3981
|
+
isSpace: false,
|
|
3982
|
+
pageBreak: true,
|
|
3983
|
+
resolvedRun: plan.resolvedRun,
|
|
3984
|
+
font: plan.font,
|
|
3985
|
+
fontSizePt: plan.fontSizePt,
|
|
3986
|
+
widthPt: 0,
|
|
3987
|
+
bidiLevel: 0
|
|
3988
|
+
};
|
|
3989
|
+
}
|
|
3990
|
+
/** Arial's space, as a fraction of the em: 569 of its 2048 units. */
|
|
3991
|
+
var MARKER_SPACE_EM = 569 / 2048;
|
|
3992
|
+
/**
|
|
3993
|
+
* §17.9.29 — the space a list level's `w:suff="space"` puts after its marker,
|
|
3994
|
+
* as Word sets it: a gap of 0.278 em of the marker's size, whatever the
|
|
3995
|
+
* marker's face — Arial's space, though the marker is in none of it. Measured
|
|
3996
|
+
* in Word for Mac at 48pt, Courier New, Verdana, Times New Roman, Georgia,
|
|
3997
|
+
* Calibri, Tahoma, Impact and Songti markers all leave the same 13.4pt before
|
|
3998
|
+
* the text; a space of each face would have ranged from 8.5 to 28.8. Nothing is
|
|
3999
|
+
* drawn in it, and a justified line does not stretch it.
|
|
4000
|
+
*/
|
|
4001
|
+
function markerSpaceToken(plan, bidiLevel) {
|
|
4002
|
+
return {
|
|
4003
|
+
kind: "text",
|
|
4004
|
+
text: "",
|
|
4005
|
+
isSpace: false,
|
|
4006
|
+
listMarker: true,
|
|
4007
|
+
listMarkerSpace: true,
|
|
4008
|
+
resolvedRun: plan.resolvedRun,
|
|
4009
|
+
font: plan.font,
|
|
4010
|
+
fontSizePt: plan.fontSizePt,
|
|
4011
|
+
widthPt: MARKER_SPACE_EM * plan.fontSizePt,
|
|
4012
|
+
bidiLevel
|
|
4013
|
+
};
|
|
4014
|
+
}
|
|
4015
|
+
/**
|
|
3682
4016
|
* A LIST MARKER the font has no glyph for, swapped for the dot every text font
|
|
3683
4017
|
* carries.
|
|
3684
4018
|
*
|
|
@@ -3689,8 +4023,15 @@ function tokenizePlansLtr(plans) {
|
|
|
3689
4023
|
* eleven slides. The marker is the one place where the shape matters more than
|
|
3690
4024
|
* the code point, so it takes the nearest thing the font can draw.
|
|
3691
4025
|
*
|
|
3692
|
-
*
|
|
3693
|
-
*
|
|
4026
|
+
* Asked of the face that DRAWS the character, not of the run's own: a marker
|
|
4027
|
+
* may be set in two faces, and a character another face draws is no business
|
|
4028
|
+
* of the run's. chtoutline.docx numbers its chapters "第 1 章" in a Latin
|
|
4029
|
+
* heading face with the Han in a face of their own; asked of the Latin one, 第
|
|
4030
|
+
* and 章 became dots set in the Han face's subset, which had reserved neither,
|
|
4031
|
+
* and the heading printed nothing but its "1".
|
|
4032
|
+
*
|
|
4033
|
+
* @param text The marker's text.
|
|
4034
|
+
* @param parsed The face that draws it.
|
|
3694
4035
|
* @returns The text to draw.
|
|
3695
4036
|
*/
|
|
3696
4037
|
function markerText(text, parsed) {
|
|
@@ -3718,7 +4059,7 @@ function measureRunText(plan, text, face) {
|
|
|
3718
4059
|
};
|
|
3719
4060
|
const base = drawn.font.measure.textWidthPt(text, plan.fontSizePt) * (drawn.synthetic?.widthScale ?? 1) * (plan.resolvedRun.widthScale ?? 1);
|
|
3720
4061
|
const extra = plan.resolvedRun.letterSpacingPt;
|
|
3721
|
-
return extra === void 0 || extra === 0 ? base : base + extra *
|
|
4062
|
+
return extra === void 0 || extra === 0 ? base : base + extra * drawn.font.measure.spacingSlots(text);
|
|
3722
4063
|
}
|
|
3723
4064
|
function tokenizePlansBidi(plans, realLevels) {
|
|
3724
4065
|
const tokens = [];
|
|
@@ -3759,6 +4100,11 @@ function tokenizePlansBidi(plans, realLevels) {
|
|
|
3759
4100
|
realIdx++;
|
|
3760
4101
|
continue;
|
|
3761
4102
|
}
|
|
4103
|
+
if (plan.run.listMarkerSpace === true) {
|
|
4104
|
+
tokens.push(markerSpaceToken(plan, realLevels[realIdx] ?? 0));
|
|
4105
|
+
realIdx += [...plan.run.text].length;
|
|
4106
|
+
continue;
|
|
4107
|
+
}
|
|
3762
4108
|
const chars = [...plan.run.text];
|
|
3763
4109
|
let bufStart = 0;
|
|
3764
4110
|
let curSpace = false;
|
|
@@ -3767,11 +4113,12 @@ function tokenizePlansBidi(plans, realLevels) {
|
|
|
3767
4113
|
const flush = (endExclusive) => {
|
|
3768
4114
|
if (endExclusive <= bufStart) return;
|
|
3769
4115
|
const raw = chars.slice(bufStart, endExclusive).join("");
|
|
3770
|
-
const text = plan.run.listMarker === true ? markerText(raw,
|
|
4116
|
+
const text = plan.run.listMarker === true ? markerText(raw, curFace.font.parsed) : raw;
|
|
3771
4117
|
tokens.push({
|
|
3772
4118
|
kind: "text",
|
|
3773
4119
|
text,
|
|
3774
4120
|
isSpace: curSpace,
|
|
4121
|
+
...charColoursOf(plan.charColorsHex, plan.resolvedRun.colorHex, bufStart, endExclusive),
|
|
3775
4122
|
...plan.run.href !== void 0 ? { href: plan.run.href } : {},
|
|
3776
4123
|
...plan.run.footnoteRef !== void 0 ? { footnoteRef: plan.run.footnoteRef } : {},
|
|
3777
4124
|
...plan.run.anchor !== void 0 ? { anchor: plan.run.anchor } : {},
|
|
@@ -3806,6 +4153,7 @@ function tokenizePlansBidi(plans, realLevels) {
|
|
|
3806
4153
|
realIdx++;
|
|
3807
4154
|
}
|
|
3808
4155
|
flush(chars.length);
|
|
4156
|
+
if (plan.run.pageBreak) tokens.push(pageBreakToken(plan));
|
|
3809
4157
|
}
|
|
3810
4158
|
return tokens;
|
|
3811
4159
|
}
|
|
@@ -3976,7 +4324,8 @@ function paragraphItemStream(tokens, hyphenator, maxLineWidthPt) {
|
|
|
3976
4324
|
font: tok.font,
|
|
3977
4325
|
fontSizePt: tok.fontSizePt,
|
|
3978
4326
|
widthPt: fragWidth,
|
|
3979
|
-
bidiLevel: tok.bidiLevel
|
|
4327
|
+
bidiLevel: tok.bidiLevel,
|
|
4328
|
+
...tok.charColorsHex ? charColoursOf(tok.charColorsHex, tok.resolvedRun.colorHex, [...tok.text.slice(0, prev)].length, [...tok.text.slice(0, end)].length) : {}
|
|
3980
4329
|
};
|
|
3981
4330
|
entries.push({
|
|
3982
4331
|
item: {
|
|
@@ -4128,11 +4477,12 @@ function splitAtHyphens(tok) {
|
|
|
4128
4477
|
}
|
|
4129
4478
|
if (parts.length === 0) return void 0;
|
|
4130
4479
|
parts.push(tok.text.slice(start));
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
text,
|
|
4134
|
-
|
|
4135
|
-
|
|
4480
|
+
let from = 0;
|
|
4481
|
+
return parts.map((text) => {
|
|
4482
|
+
const piece = tokenPiece(tok, text, from, tok.font.measure.textWidthPt(text, tok.fontSizePt));
|
|
4483
|
+
if (tok.charColorsHex) from += [...text].length;
|
|
4484
|
+
return piece;
|
|
4485
|
+
});
|
|
4136
4486
|
}
|
|
4137
4487
|
function splitToWidth(tok, widthPt) {
|
|
4138
4488
|
if (tok.kind !== "text") return void 0;
|
|
@@ -4153,11 +4503,7 @@ function splitToWidth(tok, widthPt) {
|
|
|
4153
4503
|
width = nextWidth;
|
|
4154
4504
|
end++;
|
|
4155
4505
|
}
|
|
4156
|
-
out.push(
|
|
4157
|
-
...tok,
|
|
4158
|
-
text,
|
|
4159
|
-
widthPt: width
|
|
4160
|
-
});
|
|
4506
|
+
out.push(tokenPiece(tok, text, start, width));
|
|
4161
4507
|
start = end;
|
|
4162
4508
|
}
|
|
4163
4509
|
return out.length > 1 ? out : void 0;
|
|
@@ -4293,10 +4639,11 @@ function wrap(tokens, firstLineWidth, otherWidth, resolved, hyphenator, profile,
|
|
|
4293
4639
|
/**
|
|
4294
4640
|
* §17.3.2.26 — the family Word sets a token's characters in: East Asian text
|
|
4295
4641
|
* in the run's `eastAsia` font, complex-script text (and a `w:rtl` run) in its
|
|
4296
|
-
* `cs` one, the rest in `ascii` — `hAnsi` above ASCII
|
|
4297
|
-
*
|
|
4298
|
-
*
|
|
4299
|
-
*
|
|
4642
|
+
* `cs` one, the rest in `ascii` — `hAnsi` above ASCII (see {@link fontSlotOf}).
|
|
4643
|
+
* The layout draws each character in its slot's face where that face is
|
|
4644
|
+
* loaded, and in what it falls back to where not, so the name the run gives for
|
|
4645
|
+
* THESE characters is the one Word's line is looked up by: a Chinese sentence
|
|
4646
|
+
* in a Calibri run does not stand on Calibri's line.
|
|
4300
4647
|
*
|
|
4301
4648
|
* @param t The text token.
|
|
4302
4649
|
* @returns The family's name, when the run gives one for that slot.
|
|
@@ -4306,12 +4653,9 @@ function wordFamilyOf(t) {
|
|
|
4306
4653
|
if (t.resolvedRun.rtl) return fonts.cs;
|
|
4307
4654
|
let high = false;
|
|
4308
4655
|
for (const ch of t.text) {
|
|
4309
|
-
const
|
|
4310
|
-
if (
|
|
4311
|
-
high = true;
|
|
4312
|
-
const script = scriptForCodepoint(cp);
|
|
4313
|
-
if (script === "hebrew" || script === "arabic" || script === "thai") return fonts.cs;
|
|
4314
|
-
if (script === "kr" || script === "jp" || script === "sc") return fonts.eastAsia;
|
|
4656
|
+
const slot = fontSlotOf(ch.codePointAt(0) ?? 0);
|
|
4657
|
+
if (slot === "cs" || slot === "eastAsia") return fonts[slot];
|
|
4658
|
+
if (slot === "hAnsi") high = true;
|
|
4315
4659
|
}
|
|
4316
4660
|
return high ? fonts.hAnsi ?? fonts.ascii : fonts.ascii;
|
|
4317
4661
|
}
|
|
@@ -4765,10 +5109,8 @@ function measureSingleLine(paragraph, options, fontResources) {
|
|
|
4765
5109
|
let total = 0;
|
|
4766
5110
|
for (const run of paragraph.runs) {
|
|
4767
5111
|
const resolved = resolveRunProperties(run.properties, paragraph.properties, options.styles);
|
|
4768
|
-
const
|
|
4769
|
-
const
|
|
4770
|
-
const fontSizePt = resolved.fontSizePt;
|
|
4771
|
-
total += font.measure.textWidthPt(run.text, fontSizePt) * (synthetic?.widthScale ?? 1) * (resolved.widthScale ?? 1);
|
|
5112
|
+
const primary = runFontKeyAndParsed(options, resolved.fontFamily.ascii, resolved.bold, resolved.italic);
|
|
5113
|
+
for (const span of faceSpans(runFaces(options, resolved, primary, run.text), run.text)) total += lookupFont(fontResources, span.fontKey).measure.textWidthPt(span.text, resolved.fontSizePt) * (span.synthetic?.widthScale ?? 1) * (resolved.widthScale ?? 1);
|
|
4772
5114
|
}
|
|
4773
5115
|
return total;
|
|
4774
5116
|
}
|
|
@@ -5633,8 +5975,8 @@ var PageAssembler = class PageAssembler {
|
|
|
5633
5975
|
/**
|
|
5634
5976
|
* The first of a paragraph's lines, from `from` on, that the current column
|
|
5635
5977
|
* no longer holds: each line is counted with the notes it brings onto the
|
|
5636
|
-
* page, the way the line loop places it, and an explicit column break
|
|
5637
|
-
* the column where it stands.
|
|
5978
|
+
* page, the way the line loop places it, and an explicit column or page break
|
|
5979
|
+
* ends the column where it stands.
|
|
5638
5980
|
*
|
|
5639
5981
|
* @param pb The paragraph being placed.
|
|
5640
5982
|
* @param from The first line not yet placed.
|
|
@@ -5647,7 +5989,7 @@ var PageAssembler = class PageAssembler {
|
|
|
5647
5989
|
let notesOnPage = this.pageNotes.length > 0;
|
|
5648
5990
|
const counted = /* @__PURE__ */ new Set();
|
|
5649
5991
|
for (let i = from; i < pb.lines.length; i++) {
|
|
5650
|
-
if (i > from && pb.columnBreakLines?.has(i)) return i;
|
|
5992
|
+
if (i > from && (pb.columnBreakLines?.has(i) || pb.pageBreakLines?.has(i))) return i;
|
|
5651
5993
|
const line = pb.lines[i];
|
|
5652
5994
|
const h = computeLineHeight(line, pb.resolved);
|
|
5653
5995
|
const notes = this.lineFootnotes(line).filter((x) => !counted.has(x.id));
|
|
@@ -6183,6 +6525,7 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
6183
6525
|
if (!pb.resolved.widowControl || n < 2 || asm.exclusions.length > 0) return void 0;
|
|
6184
6526
|
let end = asm.firstLineOverflowing(pb, from);
|
|
6185
6527
|
if (end >= n) return void 0;
|
|
6528
|
+
if (pb.columnBreakLines?.has(end) || pb.pageBreakLines?.has(end)) return void 0;
|
|
6186
6529
|
if (n - end === 1) end--;
|
|
6187
6530
|
if (from === 0 && end === 1) end = 0;
|
|
6188
6531
|
if (end <= from) return asm.colHasContent() ? from : from + 1;
|
|
@@ -6212,6 +6555,10 @@ function paginateSections(blocks, sectionCtxs, builder, defaultLang = "en-US", n
|
|
|
6212
6555
|
let firstLineOfBlock = true;
|
|
6213
6556
|
let pairPending;
|
|
6214
6557
|
for (const [lineIdx, line] of pb.lines.entries()) {
|
|
6558
|
+
if (pb.pageBreakLines?.has(lineIdx) && asm.pageHasContent()) {
|
|
6559
|
+
asm.flushPage();
|
|
6560
|
+
columnEnd = keptColumnEnd(lineIdx);
|
|
6561
|
+
}
|
|
6215
6562
|
if (pb.columnBreakLines?.has(lineIdx) && asm.colHasContent()) {
|
|
6216
6563
|
advanceBefore(lineIdx);
|
|
6217
6564
|
columnEnd = keptColumnEnd(lineIdx);
|