@portone/docx-editor 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/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @portone/docx-editor
|
|
2
2
|
|
|
3
|
+
## 0.1.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- [#36](https://github.com/portone-io/docx-editor/pull/36) [`ab518b9`](https://github.com/portone-io/docx-editor/commit/ab518b9c551afc1d647ad612b265ec5895601240) Thanks [@Deea222](https://github.com/Deea222)! - Read two Unicode spellings of one font name as the same font.
|
|
8
|
+
|
|
9
|
+
A name a document writes down can be composed or decomposed (NFC or NFD) while the same name elsewhere in the document, or among the toolbar's presets, is written the other way.
|
|
10
|
+
Those spellings used to compare unequal, so the font dropdown listed the same font twice and a selection that all carried one font could report as mixed and blank the dropdown.
|
|
11
|
+
Names are now compared in composed form, and the spelling the document wrote is what stays stored, exported, and shown.
|
|
12
|
+
|
|
13
|
+
A list marker cut to its length cap is also cut between characters now, instead of possibly splitting an emoji or a combining sequence in half and drawing a replacement glyph.
|
|
14
|
+
|
|
3
15
|
## 0.1.0
|
|
4
16
|
|
|
5
17
|
The first release.
|
|
@@ -5,6 +5,7 @@ import {
|
|
|
5
5
|
toRunFormat
|
|
6
6
|
} from "../../../model/format.js";
|
|
7
7
|
import {
|
|
8
|
+
comparableFontName,
|
|
8
9
|
DEFAULT_FONT_FALLBACKS
|
|
9
10
|
} from "../../../styles/fontStack.js";
|
|
10
11
|
import {
|
|
@@ -40,22 +41,30 @@ function defaultFontName(state, fontFallbacks) {
|
|
|
40
41
|
function activeFontFamily(state, fontFallbacks = DEFAULT_FONT_FALLBACKS) {
|
|
41
42
|
const names = activePieces(state).map((target) => fontNameOf(target.format));
|
|
42
43
|
const first = names[0] ?? null;
|
|
43
|
-
|
|
44
|
+
const keys = names.map(
|
|
45
|
+
(name) => name === null ? null : comparableFontName(name)
|
|
46
|
+
);
|
|
47
|
+
if (keys.some((key) => key !== keys[0])) return { kind: "mixed" };
|
|
44
48
|
if (first !== null) return { kind: "font", name: first };
|
|
45
49
|
return { kind: "default", name: defaultFontName(state, fontFallbacks) };
|
|
46
50
|
}
|
|
47
51
|
function documentFontNames(doc, defaults) {
|
|
48
|
-
const names = new
|
|
52
|
+
const names = /* @__PURE__ */ new Map();
|
|
53
|
+
const collect = (name) => {
|
|
54
|
+
const comparable = comparableFontName(name);
|
|
55
|
+
if (!names.has(comparable)) names.set(comparable, name);
|
|
56
|
+
};
|
|
57
|
+
for (const name of fontNamesOf(defaults.fontFamily)) collect(name);
|
|
49
58
|
doc.descendants((node) => {
|
|
50
59
|
const mark = runMarkOf(node.marks);
|
|
51
60
|
for (const name of fontNamesOf(
|
|
52
61
|
toRunFormat(mark?.attrs.format)?.fontFamily
|
|
53
62
|
)) {
|
|
54
|
-
|
|
63
|
+
collect(name);
|
|
55
64
|
}
|
|
56
65
|
return true;
|
|
57
66
|
});
|
|
58
|
-
return Array.from(names).sort((a, b) => a.localeCompare(b));
|
|
67
|
+
return Array.from(names.values()).sort((a, b) => a.localeCompare(b));
|
|
59
68
|
}
|
|
60
69
|
function activeTextColor(state) {
|
|
61
70
|
return sharedValue(
|
|
@@ -70,6 +70,18 @@ function advance(counters, list, ilvl, start) {
|
|
|
70
70
|
if (deeper > ilvl) counters.delete(deeper);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
|
+
var MARKER_SEGMENTER = new Intl.Segmenter();
|
|
74
|
+
function capped(shape) {
|
|
75
|
+
if (shape.length <= MAX_MARKER_CHARS) return shape;
|
|
76
|
+
let text = "";
|
|
77
|
+
for (const { segment } of MARKER_SEGMENTER.segment(
|
|
78
|
+
shape.slice(0, MAX_MARKER_CHARS * 2)
|
|
79
|
+
)) {
|
|
80
|
+
if (text.length + segment.length > MAX_MARKER_CHARS) break;
|
|
81
|
+
text += segment;
|
|
82
|
+
}
|
|
83
|
+
return text;
|
|
84
|
+
}
|
|
73
85
|
function computeMarkers(paragraphs, numbering) {
|
|
74
86
|
const countersByList = /* @__PURE__ */ new Map();
|
|
75
87
|
return paragraphs.map((ref) => {
|
|
@@ -84,7 +96,7 @@ function computeMarkers(paragraphs, numbering) {
|
|
|
84
96
|
}
|
|
85
97
|
advance(counters, list, ref.ilvl, level.start);
|
|
86
98
|
const shape = level.format === "bullet" ? level.text : fillLevels(level.text, list, counters);
|
|
87
|
-
const text = shape
|
|
99
|
+
const text = capped(shape);
|
|
88
100
|
return text ? { text, indent: levelIndentPt(level.indent) } : null;
|
|
89
101
|
});
|
|
90
102
|
}
|
|
@@ -36,6 +36,15 @@ export interface FontFallbacks {
|
|
|
36
36
|
* own set (`fontFallbacks` on `DocxEditor`).
|
|
37
37
|
*/
|
|
38
38
|
export declare const DEFAULT_FONT_FALLBACKS: FontFallbacks;
|
|
39
|
+
/**
|
|
40
|
+
* The form a name is compared in. Quotes, letter case, and Unicode normalization all vary
|
|
41
|
+
* from document to document, so two spellings of one name have to compare equal.
|
|
42
|
+
*
|
|
43
|
+
* NFC only. An NFK* form is lossy and would fold the fullwidth Latin of a name such as
|
|
44
|
+
* `DFKaiShu` into ASCII. The result is a comparison key and is never
|
|
45
|
+
* written into a document or shown: the original spelling is what gets stored and exported.
|
|
46
|
+
*/
|
|
47
|
+
export declare function comparableFontName(name: string): string;
|
|
39
48
|
/**
|
|
40
49
|
* Whether this font name belongs to a script written in Han characters, kana or
|
|
41
50
|
* Hangul.
|
package/dist/styles/fontStack.js
CHANGED
|
@@ -189,15 +189,15 @@ var DEFAULT_FONT_FALLBACKS = {
|
|
|
189
189
|
defaultStack: "Arial, Helvetica, sans-serif",
|
|
190
190
|
defaultFontName: "Arial"
|
|
191
191
|
};
|
|
192
|
-
function
|
|
193
|
-
return name.trim().replace(/^["']|["']$/g, "").toLowerCase();
|
|
192
|
+
function comparableFontName(name) {
|
|
193
|
+
return name.trim().replace(/^["']|["']$/g, "").toLowerCase().normalize("NFC");
|
|
194
194
|
}
|
|
195
195
|
var EAST_ASIAN_CHARACTER = /[\u1100-\u11ff\u3000-\u30ff\u3130-\u318f\u31f0-\u31ff\u3400-\u4dbf\u4e00-\u9fff\ua960-\ua97f\uac00-\ud7ff\uf900-\ufaff\uff00-\uffef\u{20000}-\u{2fa1f}]/u;
|
|
196
196
|
var eastAsianNames = new Set(
|
|
197
|
-
EAST_ASIAN_GROUPS.flatMap((group) => group.names.map(
|
|
197
|
+
EAST_ASIAN_GROUPS.flatMap((group) => group.names.map(comparableFontName))
|
|
198
198
|
);
|
|
199
199
|
function isEastAsianFontName(name) {
|
|
200
|
-
return EAST_ASIAN_CHARACTER.test(name) || eastAsianNames.has(
|
|
200
|
+
return EAST_ASIAN_CHARACTER.test(name) || eastAsianNames.has(comparableFontName(name));
|
|
201
201
|
}
|
|
202
202
|
var stacksByFallbacks = /* @__PURE__ */ new WeakMap();
|
|
203
203
|
function stackByName(fallbacks) {
|
|
@@ -206,7 +206,7 @@ function stackByName(fallbacks) {
|
|
|
206
206
|
const stacks = /* @__PURE__ */ new Map();
|
|
207
207
|
for (const group of fallbacks.groups) {
|
|
208
208
|
for (const name of group.names)
|
|
209
|
-
stacks.set(
|
|
209
|
+
stacks.set(comparableFontName(name), group.stack);
|
|
210
210
|
}
|
|
211
211
|
stacksByFallbacks.set(fallbacks, stacks);
|
|
212
212
|
return stacks;
|
|
@@ -215,7 +215,7 @@ function matchedStacks(cssNames, fallbacks) {
|
|
|
215
215
|
const stacks = stackByName(fallbacks);
|
|
216
216
|
const matched = [];
|
|
217
217
|
for (const name of cssNames.split(",")) {
|
|
218
|
-
const stack = stacks.get(
|
|
218
|
+
const stack = stacks.get(comparableFontName(name));
|
|
219
219
|
if (stack !== void 0 && !matched.includes(stack)) matched.push(stack);
|
|
220
220
|
}
|
|
221
221
|
return matched;
|
|
@@ -235,12 +235,12 @@ var GENERIC_FAMILIES = /* @__PURE__ */ new Set([
|
|
|
235
235
|
"emoji"
|
|
236
236
|
]);
|
|
237
237
|
function isGenericFamily(name) {
|
|
238
|
-
return GENERIC_FAMILIES.has(
|
|
238
|
+
return GENERIC_FAMILIES.has(comparableFontName(name));
|
|
239
239
|
}
|
|
240
240
|
function distinctNames(names) {
|
|
241
241
|
const seen = /* @__PURE__ */ new Set();
|
|
242
242
|
return names.filter((name) => {
|
|
243
|
-
const comparable =
|
|
243
|
+
const comparable = comparableFontName(name);
|
|
244
244
|
if (seen.has(comparable)) return false;
|
|
245
245
|
seen.add(comparable);
|
|
246
246
|
return true;
|
|
@@ -257,6 +257,7 @@ function withFontFallback(cssNames, fallbacks = DEFAULT_FONT_FALLBACKS) {
|
|
|
257
257
|
}
|
|
258
258
|
export {
|
|
259
259
|
DEFAULT_FONT_FALLBACKS,
|
|
260
|
+
comparableFontName,
|
|
260
261
|
isEastAsianFontName,
|
|
261
262
|
withFontFallback
|
|
262
263
|
};
|
|
@@ -3,7 +3,10 @@ import {
|
|
|
3
3
|
setFontFamily
|
|
4
4
|
} from "../editor/commands/formattingCommands.js";
|
|
5
5
|
import { editorClassNames } from "../styles/classNames.js";
|
|
6
|
-
import {
|
|
6
|
+
import {
|
|
7
|
+
comparableFontName,
|
|
8
|
+
withFontFallback
|
|
9
|
+
} from "../styles/fontStack.js";
|
|
7
10
|
import { DEFAULT_FONTS } from "../styles/fonts.js";
|
|
8
11
|
import { Tooltip } from "./Tooltip.js";
|
|
9
12
|
import { jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -11,11 +14,21 @@ var LABEL = "Font";
|
|
|
11
14
|
var CLEAR = ";default";
|
|
12
15
|
var MIXED = "";
|
|
13
16
|
function optionNames(current, fonts, documentFonts) {
|
|
14
|
-
const names = [
|
|
15
|
-
|
|
16
|
-
|
|
17
|
+
const names = [
|
|
18
|
+
...fonts,
|
|
19
|
+
...documentFonts,
|
|
20
|
+
...current.kind === "mixed" ? [] : [current.name]
|
|
21
|
+
];
|
|
22
|
+
const byComparable = /* @__PURE__ */ new Map();
|
|
23
|
+
for (const name of names) {
|
|
24
|
+
const comparable = comparableFontName(name);
|
|
25
|
+
if (!byComparable.has(comparable)) byComparable.set(comparable, name);
|
|
17
26
|
}
|
|
18
|
-
return Array.from(
|
|
27
|
+
return Array.from(byComparable.values());
|
|
28
|
+
}
|
|
29
|
+
function selectedValue(options, name) {
|
|
30
|
+
const comparable = comparableFontName(name);
|
|
31
|
+
return options.find((option) => comparableFontName(option) === comparable) ?? name;
|
|
19
32
|
}
|
|
20
33
|
function FontFamilySelect({
|
|
21
34
|
current,
|
|
@@ -25,7 +38,8 @@ function FontFamilySelect({
|
|
|
25
38
|
disabled,
|
|
26
39
|
run
|
|
27
40
|
}) {
|
|
28
|
-
const
|
|
41
|
+
const options = optionNames(current, fonts, documentFonts);
|
|
42
|
+
const value = current.kind === "mixed" ? MIXED : selectedValue(options, current.name);
|
|
29
43
|
return /* @__PURE__ */ jsx(Tooltip, { label: LABEL, children: /* @__PURE__ */ jsxs(
|
|
30
44
|
"select",
|
|
31
45
|
{
|
|
@@ -41,7 +55,7 @@ function FontFamilySelect({
|
|
|
41
55
|
children: [
|
|
42
56
|
current.kind === "mixed" && /* @__PURE__ */ jsx("option", { value: MIXED }),
|
|
43
57
|
/* @__PURE__ */ jsx("option", { value: CLEAR, children: "Default" }),
|
|
44
|
-
|
|
58
|
+
options.map((name) => /* @__PURE__ */ jsx(
|
|
45
59
|
"option",
|
|
46
60
|
{
|
|
47
61
|
value: name,
|