@docx4j/core-ts 0.1.0 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (172) hide show
  1. package/README.md +410 -10
  2. package/dist/model/content/Body.d.mts +109 -4
  3. package/dist/model/content/Body.mjs +300 -29
  4. package/dist/model/content/Comment.d.mts +78 -0
  5. package/dist/model/content/Comment.mjs +429 -0
  6. package/dist/model/content/ContentControl.d.mts +173 -0
  7. package/dist/model/content/ContentControl.mjs +638 -0
  8. package/dist/model/content/Font.d.mts +48 -8
  9. package/dist/model/content/Font.mjs +55 -13
  10. package/dist/model/content/InlinePicture.d.mts +103 -0
  11. package/dist/model/content/InlinePicture.mjs +361 -0
  12. package/dist/model/content/List.d.mts +253 -0
  13. package/dist/model/content/List.mjs +778 -0
  14. package/dist/model/content/Paragraph.d.mts +213 -16
  15. package/dist/model/content/Paragraph.mjs +611 -57
  16. package/dist/model/content/Range.d.mts +48 -4
  17. package/dist/model/content/Range.mjs +161 -4
  18. package/dist/model/content/Table.d.mts +130 -0
  19. package/dist/model/content/Table.mjs +429 -0
  20. package/dist/model/content/TrackedChange.d.mts +96 -0
  21. package/dist/model/content/TrackedChange.mjs +352 -0
  22. package/dist/model/content/comments.d.mts +122 -0
  23. package/dist/model/content/comments.mjs +248 -0
  24. package/dist/model/content/index.d.mts +13 -3
  25. package/dist/model/content/index.mjs +15 -3
  26. package/dist/model/content/ooxml.d.mts +17 -0
  27. package/dist/model/content/ooxml.mjs +214 -0
  28. package/dist/model/content/styles.d.mts +21 -0
  29. package/dist/model/content/styles.mjs +86 -0
  30. package/dist/model/content/tracking.d.mts +104 -0
  31. package/dist/model/content/tracking.mjs +313 -0
  32. package/dist/model/content/tree.d.mts +56 -9
  33. package/dist/model/content/tree.mjs +104 -31
  34. package/dist/model/customxml/CustomXmlPart.d.mts +158 -0
  35. package/dist/model/customxml/CustomXmlPart.mjs +415 -0
  36. package/dist/model/customxml/CustomXmlPartCollection.d.mts +68 -0
  37. package/dist/model/customxml/CustomXmlPartCollection.mjs +183 -0
  38. package/dist/model/customxml/XmlMapping.d.mts +46 -0
  39. package/dist/model/customxml/XmlMapping.mjs +98 -0
  40. package/dist/model/customxml/bindings.d.mts +41 -0
  41. package/dist/model/customxml/bindings.mjs +230 -0
  42. package/dist/model/customxml/index.d.mts +7 -0
  43. package/dist/model/customxml/index.mjs +9 -0
  44. package/dist/model/customxml/insert.d.mts +20 -0
  45. package/dist/model/customxml/insert.mjs +22 -0
  46. package/dist/model/customxml/kinds.d.mts +103 -0
  47. package/dist/model/customxml/kinds.mjs +267 -0
  48. package/dist/model/customxml/xpath.d.mts +52 -0
  49. package/dist/model/customxml/xpath.mjs +205 -0
  50. package/dist/model/fonts/CJKToEnglish.d.mts +2 -0
  51. package/dist/model/fonts/CJKToEnglish.mjs +16 -0
  52. package/dist/model/fonts/FontFallback.d.mts +58 -0
  53. package/dist/model/fonts/FontFallback.mjs +219 -0
  54. package/dist/model/fonts/IdentityPlusMapper.d.mts +7 -0
  55. package/dist/model/fonts/IdentityPlusMapper.mjs +19 -0
  56. package/dist/model/fonts/LanguageTagToScriptMapping.d.mts +7 -0
  57. package/dist/model/fonts/LanguageTagToScriptMapping.mjs +67 -0
  58. package/dist/model/fonts/Mapper.d.mts +190 -0
  59. package/dist/model/fonts/Mapper.mjs +481 -0
  60. package/dist/model/fonts/PhysicalFont.d.mts +34 -0
  61. package/dist/model/fonts/PhysicalFont.mjs +62 -0
  62. package/dist/model/fonts/RunFontSelector.d.mts +194 -0
  63. package/dist/model/fonts/RunFontSelector.mjs +441 -0
  64. package/dist/model/fonts/ThemeFonts.d.mts +17 -0
  65. package/dist/model/fonts/ThemeFonts.mjs +76 -0
  66. package/dist/model/fonts/defaultTheme.d.mts +37 -0
  67. package/dist/model/fonts/defaultTheme.mjs +57 -0
  68. package/dist/model/fonts/families.generated.d.mts +11 -0
  69. package/dist/model/fonts/families.generated.mjs +17 -0
  70. package/dist/model/fonts/fontsInUse.d.mts +39 -0
  71. package/dist/model/fonts/fontsInUse.mjs +183 -0
  72. package/dist/model/fonts/index.d.mts +14 -0
  73. package/dist/model/fonts/index.mjs +29 -0
  74. package/dist/model/fonts/lookup.d.mts +5 -0
  75. package/dist/model/fonts/lookup.mjs +12 -0
  76. package/dist/model/fonts/registry.d.mts +31 -0
  77. package/dist/model/fonts/registry.mjs +103 -0
  78. package/dist/model/fonts/scripts.d.mts +2 -0
  79. package/dist/model/fonts/scripts.mjs +78 -0
  80. package/dist/model/fonts/substitutions.generated.d.mts +30 -0
  81. package/dist/model/fonts/substitutions.generated.mjs +12 -0
  82. package/dist/model/fonts/themes.generated.d.mts +6 -0
  83. package/dist/model/fonts/themes.generated.mjs +11 -0
  84. package/dist/model/index.d.mts +5 -0
  85. package/dist/model/index.mjs +13 -1
  86. package/dist/model/listnumbering/Emulator.d.mts +187 -0
  87. package/dist/model/listnumbering/Emulator.mjs +332 -0
  88. package/dist/model/listnumbering/definitions.d.mts +251 -0
  89. package/dist/model/listnumbering/definitions.mjs +571 -0
  90. package/dist/model/listnumbering/formats.d.mts +25 -0
  91. package/dist/model/listnumbering/formats.mjs +320 -0
  92. package/dist/model/listnumbering/index.d.mts +4 -0
  93. package/dist/model/listnumbering/index.mjs +15 -0
  94. package/dist/model/listnumbering/state.d.mts +97 -0
  95. package/dist/model/listnumbering/state.mjs +179 -0
  96. package/dist/model/properties/PropertyResolver.d.mts +216 -0
  97. package/dist/model/properties/PropertyResolver.mjs +524 -0
  98. package/dist/model/properties/catalogue.d.mts +179 -0
  99. package/dist/model/properties/catalogue.mjs +860 -0
  100. package/dist/model/properties/index.d.mts +4 -0
  101. package/dist/model/properties/index.mjs +9 -0
  102. package/dist/model/properties/log.d.mts +7 -0
  103. package/dist/model/properties/log.mjs +25 -0
  104. package/dist/model/properties/styleUtil.d.mts +112 -0
  105. package/dist/model/properties/styleUtil.mjs +359 -0
  106. package/dist/node/index.d.mts +1 -0
  107. package/dist/node/index.mjs +12 -0
  108. package/dist/office-js/document.d.mts +111 -0
  109. package/dist/office-js/document.mjs +267 -0
  110. package/dist/office-js/enums.d.mts +187 -0
  111. package/dist/office-js/enums.mjs +176 -0
  112. package/dist/office-js/errors.d.mts +29 -0
  113. package/dist/office-js/errors.mjs +39 -0
  114. package/dist/office-js/extras.d.mts +5 -0
  115. package/dist/office-js/extras.mjs +29 -0
  116. package/dist/office-js/fragment.d.mts +10 -0
  117. package/dist/office-js/fragment.mjs +23 -0
  118. package/dist/office-js/index.d.mts +183 -0
  119. package/dist/office-js/index.mjs +54 -0
  120. package/dist/office-js/proxy.d.mts +72 -0
  121. package/dist/office-js/proxy.mjs +279 -0
  122. package/dist/office-js/run.d.mts +32 -0
  123. package/dist/office-js/run.mjs +66 -0
  124. package/dist/office-js/supported.d.mts +7 -0
  125. package/dist/office-js/supported.generated.d.mts +1 -0
  126. package/dist/office-js/supported.generated.mjs +235 -0
  127. package/dist/office-js/supported.mjs +53 -0
  128. package/dist/office-js/toApiScript.d.mts +25 -0
  129. package/dist/office-js/toApiScript.mjs +394 -0
  130. package/dist/opc/ContentTypes.d.mts +11 -0
  131. package/dist/opc/ContentTypes.mjs +14 -0
  132. package/dist/opc/DirectoryPartStore.d.mts +47 -0
  133. package/dist/opc/DirectoryPartStore.mjs +123 -0
  134. package/dist/opc/exceptions.d.mts +30 -0
  135. package/dist/opc/exceptions.mjs +38 -0
  136. package/dist/opc/index.d.mts +1 -1
  137. package/dist/opc/index.mjs +1 -1
  138. package/dist/packages/OpcPackage.d.mts +19 -1
  139. package/dist/packages/OpcPackage.mjs +29 -0
  140. package/dist/packages/PresentationMLPackage.d.mts +71 -1
  141. package/dist/packages/PresentationMLPackage.mjs +179 -0
  142. package/dist/packages/SpreadsheetMLPackage.d.mts +30 -1
  143. package/dist/packages/SpreadsheetMLPackage.mjs +78 -0
  144. package/dist/packages/WordprocessingMLPackage.d.mts +124 -2
  145. package/dist/packages/WordprocessingMLPackage.mjs +309 -2
  146. package/dist/packages/index.d.mts +1 -1
  147. package/dist/packages/index.mjs +1 -1
  148. package/dist/parts/DefaultXmlPart.d.mts +18 -0
  149. package/dist/parts/DefaultXmlPart.mjs +34 -5
  150. package/dist/parts/Namespaces.d.mts +18 -0
  151. package/dist/parts/Namespaces.mjs +20 -0
  152. package/dist/parts/Part.d.mts +6 -0
  153. package/dist/parts/Part.mjs +13 -0
  154. package/dist/parts/PartRegistry.mjs +13 -1
  155. package/dist/parts/XmlPart.d.mts +33 -0
  156. package/dist/parts/XmlPart.mjs +105 -2
  157. package/dist/parts/pml/defaults.d.mts +6 -0
  158. package/dist/parts/pml/defaults.mjs +53 -0
  159. package/dist/parts/pml/index.d.mts +66 -3
  160. package/dist/parts/pml/index.mjs +206 -7
  161. package/dist/parts/pml/placeholders.d.mts +71 -0
  162. package/dist/parts/pml/placeholders.mjs +141 -0
  163. package/dist/parts/sml/index.d.mts +80 -1
  164. package/dist/parts/sml/index.mjs +154 -1
  165. package/dist/parts/wml/comments.d.mts +1 -0
  166. package/dist/parts/wml/comments.mjs +179 -0
  167. package/dist/parts/wml/defaultNumbering.d.mts +1 -0
  168. package/dist/parts/wml/defaultNumbering.mjs +216 -0
  169. package/dist/parts/wml/index.d.mts +121 -0
  170. package/dist/parts/wml/index.mjs +291 -0
  171. package/dist/xml/dom.mjs +1 -2
  172. package/package.json +25 -4
@@ -0,0 +1,481 @@
1
+ import { DEFAULT_FONT_REGISTRY } from './registry.mjs';
2
+ import { SUBSTITUTE_ROWS } from './substitutions.generated.mjs';
3
+ import { MS_FONT_NAMES, MS_FONTS_WITH_BOLD, LINE_METRIC_FAMILIES, EAST_ASIAN_FAMILIES } from './families.generated.mjs';
4
+ import { leftToTheDocumentDefault, selectByClass, substitutionClass } from './FontFallback.mjs';
5
+ /** What the report says where nothing was measured. */
6
+ export const UNKNOWN_ERROR = 'unknown';
7
+ /** Word's own fallback where a document font resolves to nothing at all. */
8
+ export const FONT_FALLBACK = 'Times New Roman';
9
+ const MS_FONT_NAME_SET = new Set(MS_FONT_NAMES);
10
+ const MS_FONTS_WITH_BOLD_SET = new Set(MS_FONTS_WITH_BOLD);
11
+ const LINE_METRIC_FAMILY_SET = new Set(LINE_METRIC_FAMILIES);
12
+ /** docx4j `WordLineMetrics.ALIASES`: families the table reaches through a built-in alias. */
13
+ const LINE_METRIC_ALIASES = new Map([
14
+ ['helvetica', 'arial'], ['helvetica neue', 'arial'], ['helveticaneue', 'arial'],
15
+ ['helv', 'arial'], ['arialmt', 'arial'], ['timesnewromanpsmt', 'times new roman'],
16
+ ]);
17
+ const EAST_ASIAN_FAMILY_SET = new Set(EAST_ASIAN_FAMILIES);
18
+ /** docx4j `WordLineMetrics.isEastAsianFamily`: the table's optional field 7. */
19
+ export function isEastAsianFamily(documentFont) {
20
+ if (documentFont === undefined)
21
+ return false;
22
+ const key = documentFont.trim().toLowerCase();
23
+ const alias = LINE_METRIC_ALIASES.get(key);
24
+ return EAST_ASIAN_FAMILY_SET.has(key) || (alias !== undefined && EAST_ASIAN_FAMILY_SET.has(alias));
25
+ }
26
+ /** docx4j `WordLineMetrics.isTableFamily`: the table's own families and its built-in aliases. */
27
+ function isLineMetricFamily(documentFontName) {
28
+ if (documentFontName === undefined)
29
+ return false;
30
+ const key = documentFontName.trim().toLowerCase();
31
+ if (LINE_METRIC_FAMILY_SET.has(key))
32
+ return true;
33
+ const alias = LINE_METRIC_ALIASES.get(key);
34
+ return alias !== undefined && LINE_METRIC_FAMILY_SET.has(alias);
35
+ }
36
+ /**
37
+ * Whether docx4j's tables know this family: `MicrosoftFonts.xml`, `word-line-metrics` (512
38
+ * Microsoft and Office cloud families) or `FontSubstitutions.xml`.
39
+ *
40
+ * A known family the environment merely lacks is substituted for its widths (the passes before
41
+ * `addWordDefaultSubstitutes`), since Word on the author's machine had the font; an unknown one
42
+ * is what Word itself could not find either. "Known" is `substitutionClass`, **not** `classOf`,
43
+ * which will also guess a class from a name that merely ends in "Sans": docx4j measured a
44
+ * corporate face that fell between the two and was drawn in the document default's serif
45
+ * throughout. A family deliberately left to the document default is "known" for this purpose
46
+ * too, the measurement that put it there being that the default beat a stand-in.
47
+ */
48
+ export function isKnownFamily(documentFontName) {
49
+ if (documentFontName === undefined || documentFontName === null)
50
+ return false;
51
+ const name = documentFontName.trim();
52
+ if (MS_FONT_NAME_SET.has(name))
53
+ return true;
54
+ if (isLineMetricFamily(name))
55
+ return true;
56
+ if (substitutionClass(name) !== 'UNKNOWN')
57
+ return true;
58
+ return leftToTheDocumentDefault(name);
59
+ }
60
+ const WEIGHT_WORDS = ['light', 'semilight', 'semibold', 'demibold', 'medium', 'black', 'thin',
61
+ 'extralight', 'ultralight', 'heavy'];
62
+ /**
63
+ * Whether this document font has a bold face of its own: the `MicrosoftFonts.xml` entry where
64
+ * there is one (Franklin Gothic Book has none), else a family whose name ends in a weight word
65
+ * (Calibri Light, Segoe UI Semibold, Arial Black) is a single weight in Windows' font model and
66
+ * has none; any other family is taken to have one.
67
+ */
68
+ export function hasBoldFace(documentFontName) {
69
+ if (documentFontName === undefined || documentFontName === null)
70
+ return true;
71
+ const name = documentFontName.trim();
72
+ if (MS_FONT_NAME_SET.has(name))
73
+ return MS_FONTS_WITH_BOLD_SET.has(name);
74
+ let last = name.toLowerCase();
75
+ const sp = last.lastIndexOf(' ');
76
+ if (sp > 0)
77
+ last = last.slice(sp + 1);
78
+ return !WEIGHT_WORDS.includes(last);
79
+ }
80
+ /** The source a table row's quality word says. */
81
+ function sourceOf(substitute) {
82
+ if (substitute === undefined)
83
+ return 'CLASS';
84
+ if (substitute.quality === 'metric')
85
+ return 'METRIC_CLONE';
86
+ if (substitute.quality === 'measured')
87
+ return 'MEASURED_STAND_IN';
88
+ return 'CLASS';
89
+ }
90
+ /** The font table keyed by lower-cased name. */
91
+ export function fontTableOf(fonts) {
92
+ var _a;
93
+ const table = new Map();
94
+ for (const font of (_a = fonts === null || fonts === void 0 ? void 0 : fonts.font) !== null && _a !== void 0 ? _a : []) {
95
+ if ((font === null || font === void 0 ? void 0 : font.name) !== undefined)
96
+ table.set(font.name.trim().toLowerCase(), font);
97
+ }
98
+ return table;
99
+ }
100
+ /**
101
+ * The font Word draws an unknown font in, by its fontTable entry: measured (docx4j CR-016
102
+ * probe `fonts-unresolvable`, Word 365) a face whose entry says `w:family="roman"` is drawn in
103
+ * **Cambria**, one saying `swiss` in **Calibri**, one with an entry that names no family in
104
+ * Calibri, one with no fontTable entry at all in Cambria; an exact panose does not change that
105
+ * (Arial's gave Calibri), and the document's default font is never used.
106
+ */
107
+ export function wordDefaultFor(documentFontName, table) {
108
+ var _a, _b;
109
+ let entry = table.get(documentFontName.trim().toLowerCase());
110
+ if (entry === undefined)
111
+ return 'Cambria';
112
+ // the family: the entry's own, else the first along its altName chain
113
+ const seen = new Set();
114
+ while (entry !== undefined && entry.name !== undefined && !seen.has(entry.name.trim().toLowerCase())) {
115
+ seen.add(entry.name.trim().toLowerCase());
116
+ const family = (_a = entry.family) === null || _a === void 0 ? void 0 : _a.val;
117
+ if (family !== undefined) {
118
+ const f = family.trim().toLowerCase();
119
+ if (f === 'roman')
120
+ return 'Cambria';
121
+ if (f === 'modern')
122
+ return 'Courier New';
123
+ return 'Calibri'; // swiss, script, decorative, auto
124
+ }
125
+ const alt = (_b = entry.altName) === null || _b === void 0 ? void 0 : _b.val;
126
+ entry = alt === undefined ? undefined : table.get(alt.trim().toLowerCase());
127
+ }
128
+ return 'Calibri';
129
+ }
130
+ /**
131
+ * Maps the document's font names to physical fonts, over a {@link FontRegistry} the caller
132
+ * supplies. One per package, as in docx4j.
133
+ */
134
+ export class Mapper {
135
+ constructor(
136
+ /** The fonts the consumer has; the docx4j font jars' faces where nothing is given, which
137
+ * is the environment the parity goldens were made in. */
138
+ registry = DEFAULT_FONT_REGISTRY) {
139
+ this.registry = registry;
140
+ /** The mapping, keyed by the lower-cased document font name. */
141
+ this.fontMappings = new Map();
142
+ /** The document's embedded faces, keyed by the name the font table part uses. */
143
+ this.regularForms = new Map();
144
+ this.boldForms = new Map();
145
+ this.italicForms = new Map();
146
+ this.boldItalicForms = new Map();
147
+ this.decisions = new Map();
148
+ /** The document fonts `addWordDefaultSubstitutes` mapped: Word substitutes such a font whole,
149
+ * bold face included, so `addNoBoldFaceAliases` leaves them alone. */
150
+ this.wordDefaulted = new Set();
151
+ this.lineMetricsAliases = new Map();
152
+ }
153
+ /** The physical font a document font maps to, by case-insensitive name; undefined for a slot
154
+ * nothing names (docx4j `Mapper.get`, which tolerates null since 17.1.1). */
155
+ get(key) {
156
+ if (key === undefined || key === null)
157
+ return undefined;
158
+ return this.fontMappings.get(key.toLowerCase());
159
+ }
160
+ put(key, pf) {
161
+ this.fontMappings.set(key.toLowerCase(), pf);
162
+ }
163
+ get size() {
164
+ return this.fontMappings.size;
165
+ }
166
+ /** What was decided for this document font, or undefined where no pass met it. */
167
+ getDecision(documentFont) {
168
+ var _a;
169
+ if (documentFont === undefined || documentFont === null)
170
+ return undefined;
171
+ const record = this.decisions.get(documentFont.trim().toLowerCase());
172
+ if (record === undefined)
173
+ return undefined;
174
+ return { ...record, physicalFont: (_a = this.get(record.documentFont)) !== null && _a !== void 0 ? _a : null };
175
+ }
176
+ /** Every decision, ordered by the document font's name (case-insensitively), so that two
177
+ * runs over the same document give the same list. */
178
+ getDecisions() {
179
+ return [...this.decisions.values()]
180
+ .map((r) => { var _a; return ({ ...r, physicalFont: (_a = this.get(r.documentFont)) !== null && _a !== void 0 ? _a : null }); })
181
+ .sort((a, b) => a.documentFont.toLowerCase().localeCompare(b.documentFont.toLowerCase()));
182
+ }
183
+ /** Whose Word line metrics a document font takes, where a pass resolved it through another
184
+ * family; undefined where it takes its own. Per mapper, i.e. per package: docx4j's was a
185
+ * JVM-wide map until 17.1.1, and one document's `w:altName` answered for every later one. */
186
+ lineMetricsFamily(documentFont) {
187
+ if (documentFont === undefined)
188
+ return undefined;
189
+ return this.lineMetricsAliases.get(documentFont.trim().toLowerCase());
190
+ }
191
+ registerLineMetricsAlias(documentFont, family) {
192
+ if (documentFont === undefined || family === undefined)
193
+ return;
194
+ const key = documentFont.trim().toLowerCase();
195
+ // a font with metrics of its own takes them; there is nothing to alias
196
+ if (isLineMetricFamily(documentFont))
197
+ return;
198
+ this.lineMetricsAliases.set(key, family.trim());
199
+ }
200
+ decide(documentFont, source, via = null, widthError = null) {
201
+ this.decisions.set(documentFont.trim().toLowerCase(), { documentFont: documentFont.trim(), source, via, widthError });
202
+ }
203
+ // --- embedded faces ----------------------------------------------------------------
204
+ registerRegularForm(name, pf) { set(this.regularForms, name, pf); }
205
+ registerBoldForm(name, pf) { set(this.boldForms, name, pf); }
206
+ registerItalicForm(name, pf) { set(this.italicForms, name, pf); }
207
+ registerBoldItalicForm(name, pf) { set(this.boldItalicForms, name, pf); }
208
+ /** Whether the document embeds this font. */
209
+ isEmbedded(name) {
210
+ return this.regularForms.has(name) || this.boldForms.has(name)
211
+ || this.italicForms.has(name) || this.boldItalicForms.has(name);
212
+ }
213
+ /** The installed font of this name, else the document's embedded form of it (docx4j
214
+ * CR-016 Decisions 2: the installed font wins, for all four faces and in both mappers). */
215
+ installedOrEmbedded(name) {
216
+ var _a, _b, _c, _d;
217
+ return (_d = (_c = (_b = (_a = this.registry.get(name)) !== null && _a !== void 0 ? _a : this.regularForms.get(name)) !== null && _b !== void 0 ? _b : this.boldForms.get(name)) !== null && _c !== void 0 ? _c : this.italicForms.get(name)) !== null && _d !== void 0 ? _d : this.boldItalicForms.get(name);
218
+ }
219
+ // --- the passes --------------------------------------------------------------------
220
+ /**
221
+ * The mapper's own answer for a document font the environment has neither under that name
222
+ * nor embedded; undefined where it has none. {@link resolvedVia} says how it was reached.
223
+ */
224
+ resolveDocumentFont(_documentFontName, _fontTableEntry) {
225
+ return undefined;
226
+ }
227
+ /** An entry for each of the document's fonts: steps 1 to 3 of the precedence. */
228
+ populateFontMappings(documentFontNames, fonts) {
229
+ var _a;
230
+ const table = fontTableOf(fonts);
231
+ for (const documentFontName of documentFontNames) {
232
+ if (documentFontName === undefined || documentFontName.trim().length === 0)
233
+ continue;
234
+ if (this.get(documentFontName) !== undefined)
235
+ continue; // already mapped; its decision stands
236
+ let pf = this.installedOrEmbedded(documentFontName);
237
+ let source = pf === undefined ? undefined
238
+ : (this.registry.get(documentFontName) !== undefined ? 'INSTALLED' : 'EMBEDDED');
239
+ if (pf === undefined) {
240
+ this.resolvedVia = undefined;
241
+ pf = this.resolveDocumentFont(documentFontName, table.get(documentFontName.trim().toLowerCase()));
242
+ if (pf !== undefined)
243
+ source = 'MAPPER_OWN';
244
+ }
245
+ if (pf === undefined) {
246
+ // recorded, not merely logged: a later pass overwrites this where it maps the font,
247
+ // and what is left says which fonts reached the consumer with nothing
248
+ this.decide(documentFontName, 'UNMAPPED');
249
+ }
250
+ else {
251
+ this.put(documentFontName, pf);
252
+ this.decide(documentFontName, source, source === 'MAPPER_OWN'
253
+ ? ((_a = this.resolvedVia) !== null && _a !== void 0 ? _a : `the mapper's own answer: ${pf.name}`)
254
+ : null);
255
+ }
256
+ }
257
+ }
258
+ /**
259
+ * `font-substitutes.xml`: for each document font the table knows, the first of its open
260
+ * substitutes this environment has. The measurements that chose each substitute are in
261
+ * docx4j's `Mapper.addMetricallyCompatibleSubstitutes` javadoc, and the table's `error`
262
+ * attributes quote them.
263
+ */
264
+ addMetricallyCompatibleSubstitutes() {
265
+ var _a;
266
+ for (const row of SUBSTITUTE_ROWS) {
267
+ const before = this.get(row.documentFont);
268
+ let taken;
269
+ for (const candidate of row.substitutes) {
270
+ if (this.registry.get(candidate.font) !== undefined) {
271
+ taken = candidate;
272
+ break;
273
+ }
274
+ }
275
+ if (taken === undefined)
276
+ continue;
277
+ this.addMetricallyCompatibleSubstitute(row.documentFont, taken.font);
278
+ const after = this.get(row.documentFont);
279
+ if (after !== undefined && after !== before) {
280
+ this.decide(row.documentFont, sourceOf(taken), null, (_a = taken.error) !== null && _a !== void 0 ? _a : (taken.quality === 'class' ? UNKNOWN_ERROR : null));
281
+ }
282
+ }
283
+ }
284
+ /** Map this document font to an open substitute, unless the environment has the font itself
285
+ * or the document embeds it (the embedded font is what the author intended, and it is the
286
+ * only thing certain to be available). */
287
+ addMetricallyCompatibleSubstitute(documentFont, substitute) {
288
+ if (this.isEmbedded(documentFont))
289
+ return;
290
+ if (this.registry.get(documentFont) !== undefined)
291
+ return;
292
+ const pf = this.registry.get(substitute);
293
+ if (pf !== undefined)
294
+ this.put(documentFont, pf);
295
+ }
296
+ /**
297
+ * `w:altName` in `word/fontTable.xml` (ECMA-376 17.8.3.1): "the name of an alternate font
298
+ * which shall be used if the font specified is not available". That is the document author's
299
+ * own answer to a missing font, and Word takes it.
300
+ *
301
+ * After the metric clones - a clone of the document's own font is a better answer than any
302
+ * alias - and before the class pass, which is only a guess from the name. The chain is
303
+ * followed: the alternate may itself be absent and name an alternate (CR-016 probe
304
+ * `fonts-unresolvable` (e), (f)); a few hops, never a cycle.
305
+ */
306
+ addAltNameSubstitutes(documentFontNames, fonts) {
307
+ var _a, _b, _c, _d;
308
+ const altNames = new Map();
309
+ for (const font of (_a = fonts === null || fonts === void 0 ? void 0 : fonts.font) !== null && _a !== void 0 ? _a : []) {
310
+ const alt = (_b = font === null || font === void 0 ? void 0 : font.altName) === null || _b === void 0 ? void 0 : _b.val;
311
+ if ((font === null || font === void 0 ? void 0 : font.name) === undefined || alt === undefined || alt.trim().length === 0)
312
+ continue;
313
+ altNames.set(font.name.trim().toLowerCase(), alt.trim());
314
+ }
315
+ if (altNames.size === 0)
316
+ return;
317
+ const names = documentFontNames !== null && documentFontNames !== void 0 ? documentFontNames : altNames.keys();
318
+ for (const documentFontName of names) {
319
+ if (documentFontName === undefined || documentFontName.trim().length === 0)
320
+ continue;
321
+ if (this.get(documentFontName) !== undefined)
322
+ continue; // already mapped
323
+ if (this.isEmbedded(documentFontName))
324
+ continue;
325
+ if (this.registry.get(documentFontName) !== undefined)
326
+ continue; // installed; identity
327
+ const first = altNames.get(documentFontName.trim().toLowerCase());
328
+ let alt = first;
329
+ const seen = new Set([documentFontName.trim().toLowerCase()]);
330
+ let pf;
331
+ let resolvedAlt;
332
+ let eastAsianHop;
333
+ const chain = [];
334
+ while (alt !== undefined && !seen.has(alt.trim().toLowerCase())) {
335
+ seen.add(alt.trim().toLowerCase());
336
+ chain.push(alt);
337
+ /* A hop this environment lacks but whose family the line-metrics table flags East
338
+ * Asian: Word had that font and drew it, and its line is 1.3 x its usWin box, which no
339
+ * substitute comes near. The chain goes on for the *width* substitution; only the line
340
+ * box is taken here. */
341
+ if (eastAsianHop === undefined && isEastAsianFamily(alt) && this.registry.get(alt) === undefined) {
342
+ eastAsianHop = alt;
343
+ }
344
+ pf = (_d = (_c = this.registry.get(alt)) !== null && _c !== void 0 ? _c : this.registry.get(`${alt} Regular`)) !== null && _d !== void 0 ? _d : this.get(alt);
345
+ if (pf !== undefined) {
346
+ resolvedAlt = alt;
347
+ break;
348
+ }
349
+ alt = altNames.get(alt.trim().toLowerCase());
350
+ }
351
+ /* The line box is registered even where the chain found no face: Word had the alternate
352
+ * and drew it, so the line is its whatever renders the glyphs. */
353
+ if (eastAsianHop !== undefined)
354
+ this.registerLineMetricsAlias(documentFontName, eastAsianHop);
355
+ if (pf === undefined)
356
+ continue;
357
+ this.put(documentFontName, pf);
358
+ if (eastAsianHop === undefined)
359
+ this.registerLineMetricsAlias(documentFontName, resolvedAlt);
360
+ this.decide(documentFontName, 'ALT_NAME', `w:altName ${chain.join(' -> ')}`);
361
+ }
362
+ }
363
+ /** Whether {@link addClassBasedSubstitutes} applies to this mapper. True for every mapper
364
+ * since docx4j 17.1.1. */
365
+ wantsClassBasedSubstitutes() {
366
+ return true;
367
+ }
368
+ /**
369
+ * Map whatever is still unmapped to a font of the same class. Without it an unmapped font
370
+ * falls back to whatever the document's *default* font maps to, which is a Times clone
371
+ * standing in for a sans as often as not. Deliberately conservative: a condensed face is
372
+ * left unmapped, its widths being further from an ordinary condensed face's than the
373
+ * default's are.
374
+ */
375
+ addClassBasedSubstitutes(documentFontNames) {
376
+ if (documentFontNames === undefined)
377
+ return;
378
+ for (const documentFontName of documentFontNames) {
379
+ if (documentFontName === undefined || documentFontName.trim().length === 0)
380
+ continue;
381
+ if (this.get(documentFontName) !== undefined)
382
+ continue;
383
+ if (this.isEmbedded(documentFontName))
384
+ continue;
385
+ if (this.registry.get(documentFontName) !== undefined)
386
+ continue;
387
+ const pf = selectByClass(this.registry, documentFontName);
388
+ if (pf !== undefined) {
389
+ this.put(documentFontName, pf);
390
+ this.decide(documentFontName, 'CLASS', `a face of the same class: ${substitutionClass(documentFontName)}`, UNKNOWN_ERROR);
391
+ }
392
+ }
393
+ }
394
+ /** The mapper's own guesses for what the measured passes left (`BestMatchingMapper`'s panose
395
+ * match and `FontSubstitutions.xml`). Nothing here: that mapper needs font files. */
396
+ addMapperSubstitutes(_documentFontNames, _fonts) {
397
+ }
398
+ /**
399
+ * Word's own answer for a font it cannot find, for whatever is still unmapped: Cambria or
400
+ * Calibri (or Courier New) by the fontTable entry's family ({@link wordDefaultFor}), mapped
401
+ * to whatever this mapper maps that font to. Applied only to a family none of docx4j's
402
+ * tables know ({@link isKnownFamily}).
403
+ */
404
+ addWordDefaultSubstitutes(documentFontNames, fonts) {
405
+ var _a, _b;
406
+ if (documentFontNames === undefined)
407
+ return;
408
+ const table = fontTableOf(fonts);
409
+ for (const documentFontName of documentFontNames) {
410
+ if (documentFontName === undefined || documentFontName.trim().length === 0)
411
+ continue;
412
+ if (this.get(documentFontName) !== undefined)
413
+ continue;
414
+ if (this.isEmbedded(documentFontName))
415
+ continue;
416
+ if (this.registry.get(documentFontName) !== undefined)
417
+ continue;
418
+ if (isKnownFamily(documentFontName))
419
+ continue;
420
+ const wordFont = wordDefaultFor(documentFontName, table);
421
+ const pf = (_b = (_a = this.get(wordFont)) !== null && _a !== void 0 ? _a : this.registry.get(wordFont)) !== null && _b !== void 0 ? _b : selectByClass(this.registry, wordFont);
422
+ if (pf === undefined)
423
+ continue;
424
+ this.put(documentFontName, pf);
425
+ this.wordDefaulted.add(documentFontName.trim().toLowerCase());
426
+ this.registerLineMetricsAlias(documentFontName, wordFont);
427
+ this.decide(documentFontName, 'WORD_DEFAULT', `Word's own default for a font it cannot find: ${wordFont}`, UNKNOWN_ERROR);
428
+ }
429
+ }
430
+ /**
431
+ * For each document font with no bold face of its own, an alias of its mapped physical font
432
+ * reporting none, so that the consumer synthesises bold at the regular advances as Word does.
433
+ * Last of the passes: it re-maps, and it is not a source of its own - the decision says so by
434
+ * reporting a synthetic bold face.
435
+ */
436
+ addNoBoldFaceAliases(documentFontNames) {
437
+ if (documentFontNames === undefined)
438
+ return;
439
+ for (const documentFontName of documentFontNames) {
440
+ if (documentFontName === undefined || documentFontName.trim().length === 0)
441
+ continue;
442
+ if (hasBoldFace(documentFontName))
443
+ continue;
444
+ if (this.isEmbedded(documentFontName))
445
+ continue; // the embedded forms say what the document has
446
+ /* A font Word itself could not find is substituted whole - Calibri's real bold for its
447
+ * w:b - however its name reads: "EnBW DIN Pro Light" is Calibri Bold in Word, not a
448
+ * synthesised Calibri. Measured (CR-016 phase 4 gate): with the alias that document
449
+ * scored 0.8441, without it 0.8783. */
450
+ if (this.wordDefaulted.has(documentFontName.trim().toLowerCase()))
451
+ continue;
452
+ const pf = this.get(documentFontName);
453
+ if (pf === undefined || pf.noBoldFace)
454
+ continue;
455
+ this.fontMappings.set(documentFontName.toLowerCase(), pf.noBoldFaceAlias());
456
+ }
457
+ }
458
+ /**
459
+ * Every pass in order, as docx4j's `WordprocessingMLPackage.setFontMapper` runs them.
460
+ *
461
+ * @param documentFontNames the document's font names (`MainDocumentPart.fontsInUse()`)
462
+ * @param fonts the font table part's content, for `w:altName` and `w:family`
463
+ */
464
+ populate(documentFontNames, fonts) {
465
+ const names = [...documentFontNames];
466
+ this.populateFontMappings(names, fonts);
467
+ this.addMetricallyCompatibleSubstitutes();
468
+ this.addAltNameSubstitutes(names, fonts);
469
+ if (this.wantsClassBasedSubstitutes())
470
+ this.addClassBasedSubstitutes(names);
471
+ this.addMapperSubstitutes(names, fonts);
472
+ this.addWordDefaultSubstitutes(names, fonts);
473
+ this.addNoBoldFaceAliases(names);
474
+ }
475
+ }
476
+ function set(map, name, pf) {
477
+ if (pf === undefined)
478
+ map.delete(name);
479
+ else
480
+ map.set(name, pf);
481
+ }
@@ -0,0 +1,34 @@
1
+ /** The suffix of a no-bold-face alias; a registry lookup strips it (docx4j `PhysicalFonts.get`). */
2
+ export declare const NOBOLD_SUFFIX = "+nobold";
3
+ /**
4
+ * A font available to the consumer: the full name a font registry keys it by (docx4j registers
5
+ * the first of FOP's triplets, the name table's id 4 - "Carlito Regular", "Arimo Bold"), and
6
+ * the family a `font-family` names ("Carlito").
7
+ */
8
+ export declare class PhysicalFont {
9
+ /** The face's full name, as a registry keys it. */
10
+ readonly name: string;
11
+ /** True for a {@link noBoldFaceAlias}. */
12
+ readonly noBoldFace: boolean;
13
+ constructor(
14
+ /** The face's full name, as a registry keys it. */
15
+ name: string,
16
+ /** The family name, as CSS would use it; the name with its twin suffixes stripped when the
17
+ * registry does not say. */
18
+ familyName?: string,
19
+ /** True for a {@link noBoldFaceAlias}. */
20
+ noBoldFace?: boolean);
21
+ readonly familyName: string;
22
+ /**
23
+ * This font again under `name + '+nobold'`, reporting no bold face of its own, so that a
24
+ * consumer synthesises bold from this file at its own advances rather than taking the
25
+ * family's real bold - which is what Word does for a family that has none (docx4j CR-016
26
+ * probe `fonts-light-bold`: Word draws a `w:b` run in Calibri Light with the Calibri Light
27
+ * font object, 378.55pt for a sentence its regular sets in 377.50, where Carlito Bold set it
28
+ * in 388.70). Held by the mapper, never put in a registry - it stands for one document's font.
29
+ */
30
+ noBoldFaceAlias(): PhysicalFont;
31
+ toString(): string;
32
+ }
33
+ /** The physical font name without the suffixes a FO layer adds; they stack, so all are stripped. */
34
+ export declare function stripSuffixes(name: string): string;
@@ -0,0 +1,62 @@
1
+ // docx4j `org.docx4j.fonts.PhysicalFont`, without the files.
2
+ //
3
+ // docx4j's class wraps FOP's `EmbedFontInfo` and carries the font's panose and its file URI, so
4
+ // that FOP can be told where to read the outlines. CR-001 Phase B step 4 ports the *decision*
5
+ // half of `org.docx4j.fonts` and not the rendering half (CR-001 section 14.2), so a physical
6
+ // font here is a name, the family a CSS `font-family` would use, and the no-bold-face flag -
7
+ // everything a `Mapper` answer means to a consumer that draws with a browser, measures with
8
+ // `fontkit`, or only reports. Reading font files (`PhysicalFonts.discover`) is a later CR.
9
+ /** The suffix of a no-bold-face alias; a registry lookup strips it (docx4j `PhysicalFonts.get`). */
10
+ export const NOBOLD_SUFFIX = '+nobold';
11
+ /** The suffixes the docx4j FO layer stacks on a font-family for a twin declared from the same
12
+ * file: the kerned twin, the twin with no OpenType features, and the no-bold-face alias.
13
+ * Nothing here produces the first two; they are stripped so that a name written by docx4j's
14
+ * FO output resolves against a registry built here. */
15
+ const SUFFIXES = ['+kerned', '+noliga', NOBOLD_SUFFIX];
16
+ /**
17
+ * A font available to the consumer: the full name a font registry keys it by (docx4j registers
18
+ * the first of FOP's triplets, the name table's id 4 - "Carlito Regular", "Arimo Bold"), and
19
+ * the family a `font-family` names ("Carlito").
20
+ */
21
+ export class PhysicalFont {
22
+ constructor(
23
+ /** The face's full name, as a registry keys it. */
24
+ name,
25
+ /** The family name, as CSS would use it; the name with its twin suffixes stripped when the
26
+ * registry does not say. */
27
+ familyName,
28
+ /** True for a {@link noBoldFaceAlias}. */
29
+ noBoldFace = false) {
30
+ this.name = name;
31
+ this.noBoldFace = noBoldFace;
32
+ this.familyName = familyName !== null && familyName !== void 0 ? familyName : stripSuffixes(name);
33
+ }
34
+ /**
35
+ * This font again under `name + '+nobold'`, reporting no bold face of its own, so that a
36
+ * consumer synthesises bold from this file at its own advances rather than taking the
37
+ * family's real bold - which is what Word does for a family that has none (docx4j CR-016
38
+ * probe `fonts-light-bold`: Word draws a `w:b` run in Calibri Light with the Calibri Light
39
+ * font object, 378.55pt for a sentence its regular sets in 377.50, where Carlito Bold set it
40
+ * in 388.70). Held by the mapper, never put in a registry - it stands for one document's font.
41
+ */
42
+ noBoldFaceAlias() {
43
+ return new PhysicalFont(this.name + NOBOLD_SUFFIX, this.familyName, true);
44
+ }
45
+ toString() {
46
+ return this.name;
47
+ }
48
+ }
49
+ /** The physical font name without the suffixes a FO layer adds; they stack, so all are stripped. */
50
+ export function stripSuffixes(name) {
51
+ let key = name;
52
+ for (let stripped = true; stripped;) {
53
+ stripped = false;
54
+ for (const suffix of SUFFIXES) {
55
+ if (key.endsWith(suffix)) {
56
+ key = key.slice(0, -suffix.length);
57
+ stripped = true;
58
+ }
59
+ }
60
+ }
61
+ return key;
62
+ }