@docx4j/core-ts 0.1.0 → 0.1.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 +410 -10
- package/dist/model/content/Body.d.mts +109 -4
- package/dist/model/content/Body.mjs +300 -29
- package/dist/model/content/Comment.d.mts +78 -0
- package/dist/model/content/Comment.mjs +429 -0
- package/dist/model/content/ContentControl.d.mts +173 -0
- package/dist/model/content/ContentControl.mjs +638 -0
- package/dist/model/content/Font.d.mts +48 -8
- package/dist/model/content/Font.mjs +55 -13
- package/dist/model/content/InlinePicture.d.mts +103 -0
- package/dist/model/content/InlinePicture.mjs +361 -0
- package/dist/model/content/List.d.mts +253 -0
- package/dist/model/content/List.mjs +778 -0
- package/dist/model/content/Paragraph.d.mts +213 -16
- package/dist/model/content/Paragraph.mjs +611 -57
- package/dist/model/content/Range.d.mts +48 -4
- package/dist/model/content/Range.mjs +161 -4
- package/dist/model/content/Table.d.mts +130 -0
- package/dist/model/content/Table.mjs +429 -0
- package/dist/model/content/TrackedChange.d.mts +96 -0
- package/dist/model/content/TrackedChange.mjs +352 -0
- package/dist/model/content/comments.d.mts +122 -0
- package/dist/model/content/comments.mjs +248 -0
- package/dist/model/content/index.d.mts +13 -3
- package/dist/model/content/index.mjs +15 -3
- package/dist/model/content/ooxml.d.mts +17 -0
- package/dist/model/content/ooxml.mjs +214 -0
- package/dist/model/content/styles.d.mts +21 -0
- package/dist/model/content/styles.mjs +86 -0
- package/dist/model/content/tracking.d.mts +104 -0
- package/dist/model/content/tracking.mjs +313 -0
- package/dist/model/content/tree.d.mts +56 -9
- package/dist/model/content/tree.mjs +104 -31
- package/dist/model/customxml/CustomXmlPart.d.mts +158 -0
- package/dist/model/customxml/CustomXmlPart.mjs +415 -0
- package/dist/model/customxml/CustomXmlPartCollection.d.mts +68 -0
- package/dist/model/customxml/CustomXmlPartCollection.mjs +183 -0
- package/dist/model/customxml/XmlMapping.d.mts +46 -0
- package/dist/model/customxml/XmlMapping.mjs +98 -0
- package/dist/model/customxml/bindings.d.mts +41 -0
- package/dist/model/customxml/bindings.mjs +230 -0
- package/dist/model/customxml/index.d.mts +7 -0
- package/dist/model/customxml/index.mjs +9 -0
- package/dist/model/customxml/insert.d.mts +20 -0
- package/dist/model/customxml/insert.mjs +22 -0
- package/dist/model/customxml/kinds.d.mts +103 -0
- package/dist/model/customxml/kinds.mjs +267 -0
- package/dist/model/customxml/xpath.d.mts +52 -0
- package/dist/model/customxml/xpath.mjs +205 -0
- package/dist/model/fonts/CJKToEnglish.d.mts +2 -0
- package/dist/model/fonts/CJKToEnglish.mjs +16 -0
- package/dist/model/fonts/FontFallback.d.mts +58 -0
- package/dist/model/fonts/FontFallback.mjs +219 -0
- package/dist/model/fonts/IdentityPlusMapper.d.mts +7 -0
- package/dist/model/fonts/IdentityPlusMapper.mjs +19 -0
- package/dist/model/fonts/LanguageTagToScriptMapping.d.mts +7 -0
- package/dist/model/fonts/LanguageTagToScriptMapping.mjs +67 -0
- package/dist/model/fonts/Mapper.d.mts +190 -0
- package/dist/model/fonts/Mapper.mjs +481 -0
- package/dist/model/fonts/PhysicalFont.d.mts +34 -0
- package/dist/model/fonts/PhysicalFont.mjs +62 -0
- package/dist/model/fonts/RunFontSelector.d.mts +194 -0
- package/dist/model/fonts/RunFontSelector.mjs +441 -0
- package/dist/model/fonts/ThemeFonts.d.mts +17 -0
- package/dist/model/fonts/ThemeFonts.mjs +76 -0
- package/dist/model/fonts/defaultTheme.d.mts +37 -0
- package/dist/model/fonts/defaultTheme.mjs +57 -0
- package/dist/model/fonts/families.generated.d.mts +11 -0
- package/dist/model/fonts/families.generated.mjs +17 -0
- package/dist/model/fonts/fontsInUse.d.mts +39 -0
- package/dist/model/fonts/fontsInUse.mjs +183 -0
- package/dist/model/fonts/index.d.mts +14 -0
- package/dist/model/fonts/index.mjs +29 -0
- package/dist/model/fonts/lookup.d.mts +5 -0
- package/dist/model/fonts/lookup.mjs +12 -0
- package/dist/model/fonts/registry.d.mts +31 -0
- package/dist/model/fonts/registry.mjs +103 -0
- package/dist/model/fonts/scripts.d.mts +2 -0
- package/dist/model/fonts/scripts.mjs +78 -0
- package/dist/model/fonts/substitutions.generated.d.mts +30 -0
- package/dist/model/fonts/substitutions.generated.mjs +12 -0
- package/dist/model/fonts/themes.generated.d.mts +6 -0
- package/dist/model/fonts/themes.generated.mjs +11 -0
- package/dist/model/index.d.mts +5 -0
- package/dist/model/index.mjs +13 -1
- package/dist/model/listnumbering/Emulator.d.mts +187 -0
- package/dist/model/listnumbering/Emulator.mjs +332 -0
- package/dist/model/listnumbering/definitions.d.mts +251 -0
- package/dist/model/listnumbering/definitions.mjs +571 -0
- package/dist/model/listnumbering/formats.d.mts +25 -0
- package/dist/model/listnumbering/formats.mjs +320 -0
- package/dist/model/listnumbering/index.d.mts +4 -0
- package/dist/model/listnumbering/index.mjs +15 -0
- package/dist/model/listnumbering/state.d.mts +97 -0
- package/dist/model/listnumbering/state.mjs +179 -0
- package/dist/model/properties/PropertyResolver.d.mts +216 -0
- package/dist/model/properties/PropertyResolver.mjs +524 -0
- package/dist/model/properties/catalogue.d.mts +179 -0
- package/dist/model/properties/catalogue.mjs +860 -0
- package/dist/model/properties/index.d.mts +4 -0
- package/dist/model/properties/index.mjs +9 -0
- package/dist/model/properties/log.d.mts +7 -0
- package/dist/model/properties/log.mjs +25 -0
- package/dist/model/properties/styleUtil.d.mts +112 -0
- package/dist/model/properties/styleUtil.mjs +359 -0
- package/dist/node/index.d.mts +1 -0
- package/dist/node/index.mjs +12 -0
- package/dist/office-js/document.d.mts +111 -0
- package/dist/office-js/document.mjs +267 -0
- package/dist/office-js/enums.d.mts +187 -0
- package/dist/office-js/enums.mjs +176 -0
- package/dist/office-js/errors.d.mts +29 -0
- package/dist/office-js/errors.mjs +39 -0
- package/dist/office-js/extras.d.mts +5 -0
- package/dist/office-js/extras.mjs +29 -0
- package/dist/office-js/fragment.d.mts +10 -0
- package/dist/office-js/fragment.mjs +23 -0
- package/dist/office-js/index.d.mts +183 -0
- package/dist/office-js/index.mjs +54 -0
- package/dist/office-js/proxy.d.mts +72 -0
- package/dist/office-js/proxy.mjs +279 -0
- package/dist/office-js/run.d.mts +32 -0
- package/dist/office-js/run.mjs +66 -0
- package/dist/office-js/supported.d.mts +7 -0
- package/dist/office-js/supported.generated.d.mts +1 -0
- package/dist/office-js/supported.generated.mjs +235 -0
- package/dist/office-js/supported.mjs +53 -0
- package/dist/office-js/toApiScript.d.mts +25 -0
- package/dist/office-js/toApiScript.mjs +394 -0
- package/dist/opc/ContentTypes.d.mts +1 -0
- package/dist/opc/ContentTypes.mjs +1 -0
- package/dist/opc/DirectoryPartStore.d.mts +47 -0
- package/dist/opc/DirectoryPartStore.mjs +123 -0
- package/dist/opc/exceptions.d.mts +30 -0
- package/dist/opc/exceptions.mjs +38 -0
- package/dist/opc/index.d.mts +1 -1
- package/dist/opc/index.mjs +1 -1
- package/dist/packages/OpcPackage.d.mts +19 -1
- package/dist/packages/OpcPackage.mjs +29 -0
- package/dist/packages/PresentationMLPackage.d.mts +71 -1
- package/dist/packages/PresentationMLPackage.mjs +179 -0
- package/dist/packages/SpreadsheetMLPackage.d.mts +30 -1
- package/dist/packages/SpreadsheetMLPackage.mjs +78 -0
- package/dist/packages/WordprocessingMLPackage.d.mts +124 -2
- package/dist/packages/WordprocessingMLPackage.mjs +309 -2
- package/dist/packages/index.d.mts +1 -1
- package/dist/packages/index.mjs +1 -1
- package/dist/parts/DefaultXmlPart.d.mts +18 -0
- package/dist/parts/DefaultXmlPart.mjs +34 -5
- package/dist/parts/PartRegistry.mjs +1 -0
- package/dist/parts/XmlPart.d.mts +33 -0
- package/dist/parts/XmlPart.mjs +105 -2
- package/dist/parts/pml/defaults.d.mts +6 -0
- package/dist/parts/pml/defaults.mjs +53 -0
- package/dist/parts/pml/index.d.mts +66 -3
- package/dist/parts/pml/index.mjs +206 -7
- package/dist/parts/pml/placeholders.d.mts +71 -0
- package/dist/parts/pml/placeholders.mjs +141 -0
- package/dist/parts/sml/index.d.mts +19 -1
- package/dist/parts/sml/index.mjs +60 -1
- package/dist/parts/wml/comments.d.mts +1 -0
- package/dist/parts/wml/comments.mjs +179 -0
- package/dist/parts/wml/defaultNumbering.d.mts +1 -0
- package/dist/parts/wml/defaultNumbering.mjs +216 -0
- package/dist/parts/wml/index.d.mts +121 -0
- package/dist/parts/wml/index.mjs +291 -0
- package/dist/xml/dom.mjs +1 -2
- 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
|
+
}
|