@veltra/desktop 1.7.13 → 1.7.15
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/dist/components/batch-edit/batch-edit-form.js +9 -3
- package/dist/components/batch-edit/batch-edit-form.js.map +1 -1
- package/dist/components/batch-edit/batch-edit-list.js +2 -1
- package/dist/components/batch-edit/batch-edit-list.js.map +1 -1
- package/dist/components/batch-edit/batch-edit.js +57 -30
- package/dist/components/batch-edit/batch-edit.js.map +1 -1
- package/dist/components/batch-edit/style.d.ts +1 -0
- package/dist/components/batch-edit/style.js +1 -0
- package/dist/components/batch-edit/style2.css +14 -0
- package/dist/components/batch-edit/use-dialog-effect.js +20 -0
- package/dist/components/batch-edit/use-dialog-effect.js.map +1 -0
- package/dist/components/batch-edit/use-edit-state.js +22 -3
- package/dist/components/batch-edit/use-edit-state.js.map +1 -1
- package/dist/components/batch-edit/use-handlers.js +5 -0
- package/dist/components/batch-edit/use-handlers.js.map +1 -1
- package/dist/components/batch-edit/use-shortcut-key.js +5 -2
- package/dist/components/batch-edit/use-shortcut-key.js.map +1 -1
- package/dist/components/file-viewer/file-viewer.js +21 -14
- package/dist/components/file-viewer/file-viewer.js.map +1 -1
- package/dist/components/file-viewer/helper.js +2 -0
- package/dist/components/file-viewer/helper.js.map +1 -1
- package/dist/components/file-viewer/previewers/ofd-previewer.js +196 -0
- package/dist/components/file-viewer/previewers/ofd-previewer.js.map +1 -0
- package/dist/components/file-viewer/style2.css +27 -0
- package/dist/components/table-editor/style2.css +23 -3
- package/dist/components/table-editor/table-editor.js +213 -63
- package/dist/components/table-editor/table-editor.js.map +1 -1
- package/dist/components/tip/style2.css +63 -0
- package/dist/components/tip/tip.js +6 -1
- package/dist/components/tip/tip.js.map +1 -1
- package/dist/index.d.ts +3 -3
- package/dist/packages/ofd-core/dist/color.js +69 -0
- package/dist/packages/ofd-core/dist/color.js.map +1 -0
- package/dist/packages/ofd-core/dist/ctm.js +55 -0
- package/dist/packages/ofd-core/dist/ctm.js.map +1 -0
- package/dist/packages/ofd-core/dist/error.js +14 -0
- package/dist/packages/ofd-core/dist/error.js.map +1 -0
- package/dist/packages/ofd-core/dist/font.js +384 -0
- package/dist/packages/ofd-core/dist/font.js.map +1 -0
- package/dist/packages/ofd-core/dist/index.js +6 -0
- package/dist/packages/ofd-core/dist/number.js +13 -0
- package/dist/packages/ofd-core/dist/number.js.map +1 -0
- package/dist/packages/ofd-core/dist/page-objects.js +181 -0
- package/dist/packages/ofd-core/dist/page-objects.js.map +1 -0
- package/dist/packages/ofd-core/dist/parse.js +65 -0
- package/dist/packages/ofd-core/dist/parse.js.map +1 -0
- package/dist/packages/ofd-core/dist/render.js +361 -0
- package/dist/packages/ofd-core/dist/render.js.map +1 -0
- package/dist/packages/ofd-core/dist/xml.js +199 -0
- package/dist/packages/ofd-core/dist/xml.js.map +1 -0
- package/dist/packages/ofd-core/dist/zip.js +93 -0
- package/dist/packages/ofd-core/dist/zip.js.map +1 -0
- package/dist/style.d.ts +1 -1
- package/dist/style.js +1 -1
- package/dist/types/batch-edit.d.ts +6 -0
- package/dist/types/file-viewer.d.ts +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/table-editor.d.ts +28 -7
- package/dist/types/tip.d.ts +5 -1
- package/package.json +8 -7
- package/src/components/batch-edit/__test__/batch-edit.test.ts +191 -4
- package/src/components/batch-edit/batch-edit-form.vue +17 -3
- package/src/components/batch-edit/batch-edit-list.vue +2 -1
- package/src/components/batch-edit/batch-edit.vue +22 -4
- package/src/components/batch-edit/style.scss +20 -0
- package/src/components/batch-edit/style.ts +1 -0
- package/src/components/batch-edit/use-dialog-effect.ts +9 -3
- package/src/components/batch-edit/use-edit-state.ts +32 -4
- package/src/components/batch-edit/use-handlers.ts +10 -0
- package/src/components/batch-edit/use-shortcut-key.ts +6 -2
- package/src/components/file-viewer/__test__/helper.test.ts +13 -0
- package/src/components/file-viewer/file-viewer.vue +23 -16
- package/src/components/file-viewer/helper.ts +2 -0
- package/src/components/file-viewer/previewers/ofd-previewer.vue +216 -0
- package/src/components/file-viewer/style.scss +33 -0
- package/src/components/table-editor/__test__/table-editor.test.ts +471 -0
- package/src/components/table-editor/style.scss +42 -4
- package/src/components/table-editor/table-editor.vue +265 -38
- package/src/components/tip/style.scss +15 -0
- package/src/components/tip/tip.vue +1 -1
- package/src/types/batch-edit.ts +6 -0
- package/src/types/file-viewer.ts +1 -1
- package/src/types/table-editor.ts +29 -7
- package/src/types/tip.ts +6 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { parseNumberList } from "./ctm.js";
|
|
2
|
+
//#region ../ofd-core/dist/color.js
|
|
3
|
+
/**
|
|
4
|
+
* OFD 颜色解析(GB/T 33190 颜色描述)转 CSS 颜色。
|
|
5
|
+
*
|
|
6
|
+
* 支持的声明形式:
|
|
7
|
+
* - `#RRGGBB`:CSS 十六进制,原样返回
|
|
8
|
+
* - 灰度:`g 128` / `gray 128` / 单个数值 `128`
|
|
9
|
+
* - RGB:`rgb 255 0 0` / `255 0 0` / 逗号分隔 `255,0,0`
|
|
10
|
+
* - CMYK:`cmyk 0 255 255 0` / `0 255 255 0`,转 RGB 输出
|
|
11
|
+
* - 末尾可带一个透明度通道(0-255),有则输出 `rgba()`
|
|
12
|
+
*/
|
|
13
|
+
/** 解析 OFD 颜色声明为 CSS 颜色;无法识别返回 null,由调用方决定默认值 */
|
|
14
|
+
function parseOfdColor(value) {
|
|
15
|
+
const text = value?.trim();
|
|
16
|
+
if (!text) return null;
|
|
17
|
+
if (text.startsWith("#")) return text;
|
|
18
|
+
const tokens = text.toLowerCase().split(/[\s,]+/).filter((token) => token !== "");
|
|
19
|
+
if (tokens.length === 0) return null;
|
|
20
|
+
const named = CHANNEL_COUNT_BY_NAME[tokens[0]];
|
|
21
|
+
if (named !== void 0) return toCssColor(tokens.slice(1), named);
|
|
22
|
+
const channelCount = UNNAMED_CHANNEL_COUNT[tokens.length];
|
|
23
|
+
return channelCount === void 0 ? null : toCssColor(tokens, channelCount);
|
|
24
|
+
}
|
|
25
|
+
const CHANNEL_COUNT_BY_NAME = {
|
|
26
|
+
g: 1,
|
|
27
|
+
gray: 1,
|
|
28
|
+
rgb: 3,
|
|
29
|
+
cmyk: 4
|
|
30
|
+
};
|
|
31
|
+
/** 无前缀时按数值个数推断通道数;末位可能是透明度通道 */
|
|
32
|
+
const UNNAMED_CHANNEL_COUNT = {
|
|
33
|
+
1: 1,
|
|
34
|
+
2: 1,
|
|
35
|
+
3: 3,
|
|
36
|
+
4: 4,
|
|
37
|
+
5: 4
|
|
38
|
+
};
|
|
39
|
+
/** 按通道数把 0-255 通道值列表转成 CSS 颜色;末位之后的透明度通道可选 */
|
|
40
|
+
function toCssColor(tokens, channels) {
|
|
41
|
+
const values = parseNumberList(tokens.join(" "));
|
|
42
|
+
if (values.length < channels || values.length > channels + 1) return null;
|
|
43
|
+
const clamped = values.map((value) => Math.round(Math.min(255, Math.max(0, value))));
|
|
44
|
+
const alpha = values.length === channels + 1 ? clamped[channels] / 255 : null;
|
|
45
|
+
const [r, g, b] = channels === 1 ? [
|
|
46
|
+
clamped[0],
|
|
47
|
+
clamped[0],
|
|
48
|
+
clamped[0]
|
|
49
|
+
] : toRgb(clamped, channels);
|
|
50
|
+
return alpha === null ? `rgb(${r} ${g} ${b})` : `rgba(${r} ${g} ${b} / ${+alpha.toFixed(3)})`;
|
|
51
|
+
}
|
|
52
|
+
/** CMYK 转 RGB:r = 255·(1−c)·(1−k);channels 为 3 时直取 RGB */
|
|
53
|
+
function toRgb(clamped, channels) {
|
|
54
|
+
if (channels === 3) return [
|
|
55
|
+
clamped[0],
|
|
56
|
+
clamped[1],
|
|
57
|
+
clamped[2]
|
|
58
|
+
];
|
|
59
|
+
const [c, m, y, k] = clamped;
|
|
60
|
+
return [
|
|
61
|
+
Math.round(255 * (1 - c / 255) * (1 - k / 255)),
|
|
62
|
+
Math.round(255 * (1 - m / 255) * (1 - k / 255)),
|
|
63
|
+
Math.round(255 * (1 - y / 255) * (1 - k / 255))
|
|
64
|
+
];
|
|
65
|
+
}
|
|
66
|
+
//#endregion
|
|
67
|
+
export { parseOfdColor };
|
|
68
|
+
|
|
69
|
+
//# sourceMappingURL=color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"color.js","names":[],"sources":["../../../../../ofd-core/dist/color.js"],"sourcesContent":["import { parseNumberList } from \"./ctm.js\";\n//#region src/color.ts\n/**\n* OFD 颜色解析(GB/T 33190 颜色描述)转 CSS 颜色。\n*\n* 支持的声明形式:\n* - `#RRGGBB`:CSS 十六进制,原样返回\n* - 灰度:`g 128` / `gray 128` / 单个数值 `128`\n* - RGB:`rgb 255 0 0` / `255 0 0` / 逗号分隔 `255,0,0`\n* - CMYK:`cmyk 0 255 255 0` / `0 255 255 0`,转 RGB 输出\n* - 末尾可带一个透明度通道(0-255),有则输出 `rgba()`\n*/\n/** 解析 OFD 颜色声明为 CSS 颜色;无法识别返回 null,由调用方决定默认值 */\nfunction parseOfdColor(value) {\n\tconst text = value?.trim();\n\tif (!text) return null;\n\tif (text.startsWith(\"#\")) return text;\n\tconst tokens = text.toLowerCase().split(/[\\s,]+/).filter((token) => token !== \"\");\n\tif (tokens.length === 0) return null;\n\tconst named = CHANNEL_COUNT_BY_NAME[tokens[0]];\n\tif (named !== void 0) return toCssColor(tokens.slice(1), named);\n\tconst channelCount = UNNAMED_CHANNEL_COUNT[tokens.length];\n\treturn channelCount === void 0 ? null : toCssColor(tokens, channelCount);\n}\nconst CHANNEL_COUNT_BY_NAME = {\n\tg: 1,\n\tgray: 1,\n\trgb: 3,\n\tcmyk: 4\n};\n/** 无前缀时按数值个数推断通道数;末位可能是透明度通道 */\nconst UNNAMED_CHANNEL_COUNT = {\n\t1: 1,\n\t2: 1,\n\t3: 3,\n\t4: 4,\n\t5: 4\n};\n/** 按通道数把 0-255 通道值列表转成 CSS 颜色;末位之后的透明度通道可选 */\nfunction toCssColor(tokens, channels) {\n\tconst values = parseNumberList(tokens.join(\" \"));\n\tif (values.length < channels || values.length > channels + 1) return null;\n\tconst clamped = values.map((value) => Math.round(Math.min(255, Math.max(0, value))));\n\tconst alpha = values.length === channels + 1 ? clamped[channels] / 255 : null;\n\tconst [r, g, b] = channels === 1 ? [\n\t\tclamped[0],\n\t\tclamped[0],\n\t\tclamped[0]\n\t] : toRgb(clamped, channels);\n\treturn alpha === null ? `rgb(${r} ${g} ${b})` : `rgba(${r} ${g} ${b} / ${+alpha.toFixed(3)})`;\n}\n/** CMYK 转 RGB:r = 255·(1−c)·(1−k);channels 为 3 时直取 RGB */\nfunction toRgb(clamped, channels) {\n\tif (channels === 3) return [\n\t\tclamped[0],\n\t\tclamped[1],\n\t\tclamped[2]\n\t];\n\tconst [c, m, y, k] = clamped;\n\treturn [\n\t\tMath.round(255 * (1 - c / 255) * (1 - k / 255)),\n\t\tMath.round(255 * (1 - m / 255) * (1 - k / 255)),\n\t\tMath.round(255 * (1 - y / 255) * (1 - k / 255))\n\t];\n}\n//#endregion\nexport { parseOfdColor };\n\n//# sourceMappingURL=color.js.map"],"mappings":";;;;;;;;;;;;;AAaA,SAAS,cAAc,OAAO;CAC7B,MAAM,OAAO,OAAO,KAAK;CACzB,IAAI,CAAC,MAAM,OAAO;CAClB,IAAI,KAAK,WAAW,GAAG,GAAG,OAAO;CACjC,MAAM,SAAS,KAAK,YAAY,CAAC,CAAC,MAAM,QAAQ,CAAC,CAAC,QAAQ,UAAU,UAAU,EAAE;CAChF,IAAI,OAAO,WAAW,GAAG,OAAO;CAChC,MAAM,QAAQ,sBAAsB,OAAO;CAC3C,IAAI,UAAU,KAAK,GAAG,OAAO,WAAW,OAAO,MAAM,CAAC,GAAG,KAAK;CAC9D,MAAM,eAAe,sBAAsB,OAAO;CAClD,OAAO,iBAAiB,KAAK,IAAI,OAAO,WAAW,QAAQ,YAAY;AACxE;AACA,MAAM,wBAAwB;CAC7B,GAAG;CACH,MAAM;CACN,KAAK;CACL,MAAM;AACP;;AAEA,MAAM,wBAAwB;CAC7B,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;CACH,GAAG;AACJ;;AAEA,SAAS,WAAW,QAAQ,UAAU;CACrC,MAAM,SAAS,gBAAgB,OAAO,KAAK,GAAG,CAAC;CAC/C,IAAI,OAAO,SAAS,YAAY,OAAO,SAAS,WAAW,GAAG,OAAO;CACrE,MAAM,UAAU,OAAO,KAAK,UAAU,KAAK,MAAM,KAAK,IAAI,KAAK,KAAK,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC;CACnF,MAAM,QAAQ,OAAO,WAAW,WAAW,IAAI,QAAQ,YAAY,MAAM;CACzE,MAAM,CAAC,GAAG,GAAG,KAAK,aAAa,IAAI;EAClC,QAAQ;EACR,QAAQ;EACR,QAAQ;CACT,IAAI,MAAM,SAAS,QAAQ;CAC3B,OAAO,UAAU,OAAO,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,QAAQ,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,CAAC,MAAM,QAAQ,CAAC,EAAE;AAC5F;;AAEA,SAAS,MAAM,SAAS,UAAU;CACjC,IAAI,aAAa,GAAG,OAAO;EAC1B,QAAQ;EACR,QAAQ;EACR,QAAQ;CACT;CACA,MAAM,CAAC,GAAG,GAAG,GAAG,KAAK;CACrB,OAAO;EACN,KAAK,MAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,IAAI,IAAI;EAC9C,KAAK,MAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,IAAI,IAAI;EAC9C,KAAK,MAAM,OAAO,IAAI,IAAI,QAAQ,IAAI,IAAI,IAAI;CAC/C;AACD"}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
//#region ../ofd-core/dist/ctm.js
|
|
2
|
+
/** 单位矩阵:不改变任何坐标 */
|
|
3
|
+
const IDENTITY_MATRIX = [
|
|
4
|
+
1,
|
|
5
|
+
0,
|
|
6
|
+
0,
|
|
7
|
+
1,
|
|
8
|
+
0,
|
|
9
|
+
0
|
|
10
|
+
];
|
|
11
|
+
/** 矩阵乘法:`multiplyMatrix(outer, inner)` 等价于先 inner 后 outer */
|
|
12
|
+
function multiplyMatrix(outer, inner) {
|
|
13
|
+
const [a1, b1, c1, d1, e1, f1] = outer;
|
|
14
|
+
const [a2, b2, c2, d2, e2, f2] = inner;
|
|
15
|
+
return [
|
|
16
|
+
a1 * a2 + c1 * b2,
|
|
17
|
+
b1 * a2 + d1 * b2,
|
|
18
|
+
a1 * c2 + c1 * d2,
|
|
19
|
+
b1 * c2 + d1 * d2,
|
|
20
|
+
a1 * e2 + c1 * f2 + e1,
|
|
21
|
+
b1 * e2 + d1 * f2 + f1
|
|
22
|
+
];
|
|
23
|
+
}
|
|
24
|
+
/** 解析 `CTM` 属性;缺失或不合法返回 null(渲染时按单位矩阵处理) */
|
|
25
|
+
function parseMatrix(value) {
|
|
26
|
+
const numbers = parseNumberList(value);
|
|
27
|
+
return numbers.length === 6 ? [
|
|
28
|
+
numbers[0],
|
|
29
|
+
numbers[1],
|
|
30
|
+
numbers[2],
|
|
31
|
+
numbers[3],
|
|
32
|
+
numbers[4],
|
|
33
|
+
numbers[5]
|
|
34
|
+
] : null;
|
|
35
|
+
}
|
|
36
|
+
/** 解析 `Boundary="x y w h"` 属性;缺失或不完整返回 null */
|
|
37
|
+
function parseBoundary(value) {
|
|
38
|
+
const numbers = parseNumberList(value);
|
|
39
|
+
if (numbers.length !== 4) return null;
|
|
40
|
+
return {
|
|
41
|
+
x: numbers[0],
|
|
42
|
+
y: numbers[1],
|
|
43
|
+
width: numbers[2],
|
|
44
|
+
height: numbers[3]
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
/** 按空白或逗号切分数值列表(CTM / Boundary / DeltaX / DashPattern 共用) */
|
|
48
|
+
function parseNumberList(value) {
|
|
49
|
+
if (!value) return [];
|
|
50
|
+
return value.trim().split(/[\s,]+/).filter((token) => token !== "").map((token) => Number(token)).filter((number) => Number.isFinite(number));
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
export { IDENTITY_MATRIX, multiplyMatrix, parseBoundary, parseMatrix, parseNumberList };
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=ctm.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ctm.js","names":[],"sources":["../../../../../ofd-core/dist/ctm.js"],"sourcesContent":["//#region src/ctm.ts\n/** 单位矩阵:不改变任何坐标 */\nconst IDENTITY_MATRIX = [\n\t1,\n\t0,\n\t0,\n\t1,\n\t0,\n\t0\n];\n/** 矩阵乘法:`multiplyMatrix(outer, inner)` 等价于先 inner 后 outer */\nfunction multiplyMatrix(outer, inner) {\n\tconst [a1, b1, c1, d1, e1, f1] = outer;\n\tconst [a2, b2, c2, d2, e2, f2] = inner;\n\treturn [\n\t\ta1 * a2 + c1 * b2,\n\t\tb1 * a2 + d1 * b2,\n\t\ta1 * c2 + c1 * d2,\n\t\tb1 * c2 + d1 * d2,\n\t\ta1 * e2 + c1 * f2 + e1,\n\t\tb1 * e2 + d1 * f2 + f1\n\t];\n}\n/** 解析 `CTM` 属性;缺失或不合法返回 null(渲染时按单位矩阵处理) */\nfunction parseMatrix(value) {\n\tconst numbers = parseNumberList(value);\n\treturn numbers.length === 6 ? [\n\t\tnumbers[0],\n\t\tnumbers[1],\n\t\tnumbers[2],\n\t\tnumbers[3],\n\t\tnumbers[4],\n\t\tnumbers[5]\n\t] : null;\n}\n/** 解析 `Boundary=\"x y w h\"` 属性;缺失或不完整返回 null */\nfunction parseBoundary(value) {\n\tconst numbers = parseNumberList(value);\n\tif (numbers.length !== 4) return null;\n\treturn {\n\t\tx: numbers[0],\n\t\ty: numbers[1],\n\t\twidth: numbers[2],\n\t\theight: numbers[3]\n\t};\n}\n/** 按空白或逗号切分数值列表(CTM / Boundary / DeltaX / DashPattern 共用) */\nfunction parseNumberList(value) {\n\tif (!value) return [];\n\treturn value.trim().split(/[\\s,]+/).filter((token) => token !== \"\").map((token) => Number(token)).filter((number) => Number.isFinite(number));\n}\n//#endregion\nexport { IDENTITY_MATRIX, multiplyMatrix, parseBoundary, parseMatrix, parseNumberList };\n\n//# sourceMappingURL=ctm.js.map"],"mappings":";;AAEA,MAAM,kBAAkB;CACvB;CACA;CACA;CACA;CACA;CACA;AACD;;AAEA,SAAS,eAAe,OAAO,OAAO;CACrC,MAAM,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,MAAM;CACjC,MAAM,CAAC,IAAI,IAAI,IAAI,IAAI,IAAI,MAAM;CACjC,OAAO;EACN,KAAK,KAAK,KAAK;EACf,KAAK,KAAK,KAAK;EACf,KAAK,KAAK,KAAK;EACf,KAAK,KAAK,KAAK;EACf,KAAK,KAAK,KAAK,KAAK;EACpB,KAAK,KAAK,KAAK,KAAK;CACrB;AACD;;AAEA,SAAS,YAAY,OAAO;CAC3B,MAAM,UAAU,gBAAgB,KAAK;CACrC,OAAO,QAAQ,WAAW,IAAI;EAC7B,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;EACR,QAAQ;CACT,IAAI;AACL;;AAEA,SAAS,cAAc,OAAO;CAC7B,MAAM,UAAU,gBAAgB,KAAK;CACrC,IAAI,QAAQ,WAAW,GAAG,OAAO;CACjC,OAAO;EACN,GAAG,QAAQ;EACX,GAAG,QAAQ;EACX,OAAO,QAAQ;EACf,QAAQ,QAAQ;CACjB;AACD;;AAEA,SAAS,gBAAgB,OAAO;CAC/B,IAAI,CAAC,OAAO,OAAO,CAAC;CACpB,OAAO,MAAM,KAAK,CAAC,CAAC,MAAM,QAAQ,CAAC,CAAC,QAAQ,UAAU,UAAU,EAAE,CAAC,CAAC,KAAK,UAAU,OAAO,KAAK,CAAC,CAAC,CAAC,QAAQ,WAAW,OAAO,SAAS,MAAM,CAAC;AAC7I"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region ../ofd-core/dist/error.js
|
|
2
|
+
/** OFD 解析失败抛出的类型化错误:靠 `reason` 分类,不靠解析 message */
|
|
3
|
+
var OfdParseError = class extends Error {
|
|
4
|
+
reason;
|
|
5
|
+
constructor(reason, message, options) {
|
|
6
|
+
super(message, options);
|
|
7
|
+
this.name = "OfdParseError";
|
|
8
|
+
this.reason = reason;
|
|
9
|
+
}
|
|
10
|
+
};
|
|
11
|
+
//#endregion
|
|
12
|
+
export { OfdParseError };
|
|
13
|
+
|
|
14
|
+
//# sourceMappingURL=error.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"error.js","names":[],"sources":["../../../../../ofd-core/dist/error.js"],"sourcesContent":["//#region src/error.ts\n/** OFD 解析失败抛出的类型化错误:靠 `reason` 分类,不靠解析 message */\nvar OfdParseError = class extends Error {\n\treason;\n\tconstructor(reason, message, options) {\n\t\tsuper(message, options);\n\t\tthis.name = \"OfdParseError\";\n\t\tthis.reason = reason;\n\t}\n};\n//#endregion\nexport { OfdParseError };\n\n//# sourceMappingURL=error.js.map"],"mappings":";;AAEA,IAAI,gBAAgB,cAAc,MAAM;CACvC;CACA,YAAY,QAAQ,SAAS,SAAS;EACrC,MAAM,SAAS,OAAO;EACtB,KAAK,OAAO;EACZ,KAAK,SAAS;CACf;AACD"}
|
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
import { OfdParseError } from "./error.js";
|
|
2
|
+
import { formatNumber } from "./number.js";
|
|
3
|
+
//#region ../ofd-core/dist/font.js
|
|
4
|
+
/**
|
|
5
|
+
* 内嵌 TTF 字体解析与 glyph 轮廓输出(OFD Font@FontFile 声明的内嵌字体)。
|
|
6
|
+
*
|
|
7
|
+
* 读取 TrueType 的 head/maxp/cmap/loca/glyf/hhea/hmtx 表:cmap 把字符码点映射到
|
|
8
|
+
* glyph id(可省,省略时字符映射由渲染层 CGTransform 字形序号提供),glyf 提取
|
|
9
|
+
* 轮廓点(on/off curve,复合 glyph 递归展开并平移/缩放)。
|
|
10
|
+
* 二次贝塞尔轮廓按 unitsPerEm → 字号缩放、基线定位并翻转 y 轴后转成 SVG path
|
|
11
|
+
* `d`。二进制结构非法时抛 OfdParseError('invalid-font')。
|
|
12
|
+
*/
|
|
13
|
+
/** 解析 TTF 二进制:结构非法抛 OfdParseError('invalid-font') */
|
|
14
|
+
function parseEmbeddedFont(data, source) {
|
|
15
|
+
if (data.length < 12) throw new OfdParseError("invalid-font", `字体 ${source} 数据太短,不是 TTF 文件`);
|
|
16
|
+
const tables = parseTableDirectory(data, source);
|
|
17
|
+
for (const tag of REQUIRED_TABLES) {
|
|
18
|
+
const table = tables.get(tag);
|
|
19
|
+
if (!table) throw new OfdParseError("invalid-font", `字体 ${source} 缺少 ${tag} 表`);
|
|
20
|
+
if (table.offset + table.length > data.length) throw new OfdParseError("invalid-font", `字体 ${source} 的 ${tag} 表越界,数据被截断`);
|
|
21
|
+
}
|
|
22
|
+
const headOffset = tables.get("head").offset;
|
|
23
|
+
const unitsPerEm = new FontReader(data, source, headOffset + 18).u16();
|
|
24
|
+
if (unitsPerEm === 0) throw new OfdParseError("invalid-font", `字体 ${source} 的 unitsPerEm 为 0`);
|
|
25
|
+
const indexToLocFormat = new FontReader(data, source, headOffset + 50).i16();
|
|
26
|
+
if (indexToLocFormat !== 0 && indexToLocFormat !== 1) throw new OfdParseError("invalid-font", `字体 ${source} 的 indexToLocFormat 不合法:${indexToLocFormat}`);
|
|
27
|
+
const numGlyphs = new FontReader(data, source, tables.get("maxp").offset + 4).u16();
|
|
28
|
+
const cmapTable = tables.get("cmap");
|
|
29
|
+
const cmap = cmapTable ? parseCmap(data, cmapTable, source) : /* @__PURE__ */ new Map();
|
|
30
|
+
const glyfTable = tables.get("glyf");
|
|
31
|
+
const locaView = new FontReader(data, source, tables.get("loca").offset);
|
|
32
|
+
const loca = [];
|
|
33
|
+
for (let i = 0; i <= numGlyphs; i++) loca.push(indexToLocFormat === 0 ? locaView.u16() * 2 : locaView.u32());
|
|
34
|
+
for (let i = 0; i < numGlyphs; i++) if (loca[i + 1] < loca[i] || loca[i + 1] > glyfTable.length) throw new OfdParseError("invalid-font", `字体 ${source} 的 loca 表越界`);
|
|
35
|
+
const numberOfHMetrics = Math.min(new FontReader(data, source, tables.get("hhea").offset + 34).u16(), numGlyphs);
|
|
36
|
+
const hmtxView = new FontReader(data, source, tables.get("hmtx").offset);
|
|
37
|
+
const advances = [];
|
|
38
|
+
for (let i = 0; i < numberOfHMetrics; i++) {
|
|
39
|
+
advances.push(hmtxView.u16());
|
|
40
|
+
hmtxView.i16();
|
|
41
|
+
}
|
|
42
|
+
const glyf = data.subarray(glyfTable.offset, glyfTable.offset + glyfTable.length);
|
|
43
|
+
const outlineCache = /* @__PURE__ */ new Map();
|
|
44
|
+
return {
|
|
45
|
+
unitsPerEm,
|
|
46
|
+
numGlyphs,
|
|
47
|
+
glyphIndexOf: (codePoint) => cmap.get(codePoint) ?? null,
|
|
48
|
+
advanceWidth: (gid) => advances[Math.min(Math.max(gid, 0), advances.length - 1)] ?? 0,
|
|
49
|
+
glyphOutline: (gid) => {
|
|
50
|
+
const cached = outlineCache.get(gid);
|
|
51
|
+
if (cached) return cached;
|
|
52
|
+
const outline = readGlyphOutline(glyf, gid, /* @__PURE__ */ new Set([gid]), numGlyphs, loca, source);
|
|
53
|
+
outlineCache.set(gid, outline);
|
|
54
|
+
return outline;
|
|
55
|
+
}
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* glyph 轮廓转 SVG path `d`:unitsPerEm → 字号缩放,基线原点定位并翻转
|
|
60
|
+
* y 轴(字体坐标向上,SVG 向下)。无轮廓返回空串。
|
|
61
|
+
*/
|
|
62
|
+
function glyphPathD(font, gid, fontSize, originX, originY) {
|
|
63
|
+
if (!(fontSize > 0)) return "";
|
|
64
|
+
const scale = fontSize / font.unitsPerEm;
|
|
65
|
+
return font.glyphOutline(gid).map((contour) => contourPathD(contour, scale, originX, originY)).filter((d) => d !== "").join(" ");
|
|
66
|
+
}
|
|
67
|
+
const REQUIRED_TABLES = [
|
|
68
|
+
"glyf",
|
|
69
|
+
"head",
|
|
70
|
+
"hhea",
|
|
71
|
+
"hmtx",
|
|
72
|
+
"loca",
|
|
73
|
+
"maxp"
|
|
74
|
+
];
|
|
75
|
+
/** 带越界检查的大端读取器:越界抛 invalid-font */
|
|
76
|
+
var FontReader = class {
|
|
77
|
+
data;
|
|
78
|
+
source;
|
|
79
|
+
offset;
|
|
80
|
+
constructor(data, source, offset = 0) {
|
|
81
|
+
this.data = data;
|
|
82
|
+
this.source = source;
|
|
83
|
+
this.offset = offset;
|
|
84
|
+
}
|
|
85
|
+
seek(offset) {
|
|
86
|
+
this.offset = offset;
|
|
87
|
+
}
|
|
88
|
+
u8() {
|
|
89
|
+
this.take(1);
|
|
90
|
+
return this.data[this.offset++];
|
|
91
|
+
}
|
|
92
|
+
i8() {
|
|
93
|
+
return this.u8() << 24 >> 24;
|
|
94
|
+
}
|
|
95
|
+
u16() {
|
|
96
|
+
this.take(2);
|
|
97
|
+
const value = this.data[this.offset] << 8 | this.data[this.offset + 1];
|
|
98
|
+
this.offset += 2;
|
|
99
|
+
return value;
|
|
100
|
+
}
|
|
101
|
+
i16() {
|
|
102
|
+
return this.u16() << 16 >> 16;
|
|
103
|
+
}
|
|
104
|
+
u32() {
|
|
105
|
+
return (this.u16() << 16 | this.u16()) >>> 0;
|
|
106
|
+
}
|
|
107
|
+
/** 读指定绝对偏移的 u16(cmap format 4 的 glyphIdArray 寻址用) */
|
|
108
|
+
u16At(offset) {
|
|
109
|
+
if (offset < 0 || offset + 2 > this.data.length) this.overrun();
|
|
110
|
+
return this.data[offset] << 8 | this.data[offset + 1];
|
|
111
|
+
}
|
|
112
|
+
take(size) {
|
|
113
|
+
if (this.offset + size > this.data.length) this.overrun();
|
|
114
|
+
}
|
|
115
|
+
overrun() {
|
|
116
|
+
throw new OfdParseError("invalid-font", `字体 ${this.source} 数据被截断`);
|
|
117
|
+
}
|
|
118
|
+
};
|
|
119
|
+
function parseTableDirectory(data, source) {
|
|
120
|
+
const view = new FontReader(data, source);
|
|
121
|
+
view.u32();
|
|
122
|
+
const numTables = view.u16();
|
|
123
|
+
view.seek(view.offset + 6);
|
|
124
|
+
const tables = /* @__PURE__ */ new Map();
|
|
125
|
+
for (let i = 0; i < numTables; i++) {
|
|
126
|
+
const tag = String.fromCharCode(view.u8(), view.u8(), view.u8(), view.u8());
|
|
127
|
+
view.u32();
|
|
128
|
+
const offset = view.u32();
|
|
129
|
+
const length = view.u32();
|
|
130
|
+
tables.set(tag, {
|
|
131
|
+
offset,
|
|
132
|
+
length
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
return tables;
|
|
136
|
+
}
|
|
137
|
+
function parseCmap(data, table, source) {
|
|
138
|
+
const header = new FontReader(data, source, table.offset);
|
|
139
|
+
header.u16();
|
|
140
|
+
const recordCount = header.u16();
|
|
141
|
+
const records = [];
|
|
142
|
+
for (let i = 0; i < recordCount; i++) records.push({
|
|
143
|
+
platform: header.u16(),
|
|
144
|
+
encoding: header.u16(),
|
|
145
|
+
offset: table.offset + header.u32()
|
|
146
|
+
});
|
|
147
|
+
const score = (record) => record.platform === 3 && record.encoding === 10 ? 3 : record.platform === 0 ? 2 : record.platform === 3 && record.encoding === 1 ? 1 : 0;
|
|
148
|
+
records.sort((a, b) => score(b) - score(a));
|
|
149
|
+
for (const record of records) try {
|
|
150
|
+
return parseCmapSubtable(new FontReader(data, source, record.offset), source);
|
|
151
|
+
} catch {}
|
|
152
|
+
throw new OfdParseError("invalid-font", `字体 ${source} 没有可用的 cmap 子表`);
|
|
153
|
+
}
|
|
154
|
+
function parseCmapSubtable(view, source) {
|
|
155
|
+
const format = view.u16();
|
|
156
|
+
switch (format) {
|
|
157
|
+
case 4: return parseCmapFormat4(view);
|
|
158
|
+
case 12: return parseCmapFormat12(view);
|
|
159
|
+
case 0: return parseCmapFormat0(view);
|
|
160
|
+
case 6: return parseCmapFormat6(view);
|
|
161
|
+
default: throw new OfdParseError("invalid-font", `字体 ${source} 的 cmap 子表格式 ${format} 不支持`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
function parseCmapFormat4(view) {
|
|
165
|
+
view.u16();
|
|
166
|
+
view.u16();
|
|
167
|
+
const segCount = view.u16() / 2;
|
|
168
|
+
view.u16();
|
|
169
|
+
view.u16();
|
|
170
|
+
view.u16();
|
|
171
|
+
if (segCount === 0) throw new OfdParseError("invalid-font", "cmap format 4 段数为 0");
|
|
172
|
+
const ends = readU16Array(view, segCount);
|
|
173
|
+
view.u16();
|
|
174
|
+
const starts = readU16Array(view, segCount);
|
|
175
|
+
const deltas = readU16Array(view, segCount);
|
|
176
|
+
const rangeOffsetBase = view.offset;
|
|
177
|
+
const rangeOffsets = readU16Array(view, segCount);
|
|
178
|
+
const map = /* @__PURE__ */ new Map();
|
|
179
|
+
for (let s = 0; s < segCount; s++) for (let code = starts[s]; code <= ends[s]; code++) {
|
|
180
|
+
let gid;
|
|
181
|
+
if (rangeOffsets[s] === 0) gid = code + deltas[s] & 65535;
|
|
182
|
+
else {
|
|
183
|
+
const address = rangeOffsetBase + 2 * s + rangeOffsets[s] + 2 * (code - starts[s]);
|
|
184
|
+
gid = view.u16At(address);
|
|
185
|
+
if (gid === 0) continue;
|
|
186
|
+
gid = gid + deltas[s] & 65535;
|
|
187
|
+
}
|
|
188
|
+
if (gid !== 0) map.set(code, gid);
|
|
189
|
+
}
|
|
190
|
+
return map;
|
|
191
|
+
}
|
|
192
|
+
function parseCmapFormat12(view) {
|
|
193
|
+
view.u16();
|
|
194
|
+
view.u32();
|
|
195
|
+
view.u32();
|
|
196
|
+
const groupCount = view.u32();
|
|
197
|
+
const map = /* @__PURE__ */ new Map();
|
|
198
|
+
for (let g = 0; g < groupCount; g++) {
|
|
199
|
+
const start = view.u32();
|
|
200
|
+
const end = Math.min(view.u32(), 1114111);
|
|
201
|
+
const startGlyph = view.u32();
|
|
202
|
+
for (let code = start; code <= end; code++) {
|
|
203
|
+
const gid = startGlyph + (code - start);
|
|
204
|
+
if (gid !== 0) map.set(code, gid);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
return map;
|
|
208
|
+
}
|
|
209
|
+
function parseCmapFormat0(view) {
|
|
210
|
+
view.u16();
|
|
211
|
+
view.u16();
|
|
212
|
+
const map = /* @__PURE__ */ new Map();
|
|
213
|
+
for (let code = 0; code < 256; code++) {
|
|
214
|
+
const gid = view.u8();
|
|
215
|
+
if (gid !== 0) map.set(code, gid);
|
|
216
|
+
}
|
|
217
|
+
return map;
|
|
218
|
+
}
|
|
219
|
+
function parseCmapFormat6(view) {
|
|
220
|
+
view.u16();
|
|
221
|
+
view.u16();
|
|
222
|
+
const firstCode = view.u16();
|
|
223
|
+
const entryCount = view.u16();
|
|
224
|
+
const map = /* @__PURE__ */ new Map();
|
|
225
|
+
for (let i = 0; i < entryCount; i++) {
|
|
226
|
+
const gid = view.u16();
|
|
227
|
+
if (gid !== 0) map.set(firstCode + i, gid);
|
|
228
|
+
}
|
|
229
|
+
return map;
|
|
230
|
+
}
|
|
231
|
+
function readU16Array(view, count) {
|
|
232
|
+
return Array.from({ length: count }, () => view.u16());
|
|
233
|
+
}
|
|
234
|
+
const COMPONENT_WORDS_ARGS = 1;
|
|
235
|
+
const COMPONENT_MORE = 32;
|
|
236
|
+
const COMPONENT_SCALE = 8;
|
|
237
|
+
const COMPONENT_XY_SCALE = 64;
|
|
238
|
+
const COMPONENT_TWO_BY_TWO = 128;
|
|
239
|
+
/** 读单个 glyph 轮廓;复合 glyph 递归展开组件(带环检测),越界/空 glyph 返回空 */
|
|
240
|
+
function readGlyphOutline(glyf, gid, visiting, numGlyphs, loca, source) {
|
|
241
|
+
if (gid < 0 || gid >= numGlyphs) return [];
|
|
242
|
+
const start = loca[gid];
|
|
243
|
+
if (start === loca[gid + 1]) return [];
|
|
244
|
+
const view = new FontReader(glyf, source, start);
|
|
245
|
+
const numberOfContours = view.i16();
|
|
246
|
+
view.seek(view.offset + 8);
|
|
247
|
+
if (numberOfContours >= 0) return readSimpleGlyph(view, numberOfContours);
|
|
248
|
+
return readCompositeGlyph(view, (componentGid) => {
|
|
249
|
+
if (visiting.has(componentGid)) return [];
|
|
250
|
+
const next = new Set(visiting);
|
|
251
|
+
next.add(componentGid);
|
|
252
|
+
return readGlyphOutline(glyf, componentGid, next, numGlyphs, loca, source);
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
/** 简单 glyph:标志位驱动的增量坐标解码 */
|
|
256
|
+
function readSimpleGlyph(view, numberOfContours) {
|
|
257
|
+
if (numberOfContours === 0) return [];
|
|
258
|
+
const endPoints = [];
|
|
259
|
+
for (let i = 0; i < numberOfContours; i++) endPoints.push(view.u16());
|
|
260
|
+
const pointCount = endPoints[endPoints.length - 1] + 1;
|
|
261
|
+
const instructionLength = view.u16();
|
|
262
|
+
view.seek(view.offset + instructionLength);
|
|
263
|
+
const flags = [];
|
|
264
|
+
while (flags.length < pointCount) {
|
|
265
|
+
const flag = view.u8();
|
|
266
|
+
flags.push(flag);
|
|
267
|
+
if (flag & 8) {
|
|
268
|
+
const repeat = view.u8();
|
|
269
|
+
for (let r = 0; r < repeat && flags.length < pointCount; r++) flags.push(flag);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
const xs = readDeltaCoords(view, flags, 2, 16);
|
|
273
|
+
const ys = readDeltaCoords(view, flags, 4, 32);
|
|
274
|
+
const points = [];
|
|
275
|
+
for (let i = 0; i < pointCount; i++) points.push({
|
|
276
|
+
x: xs[i],
|
|
277
|
+
y: ys[i],
|
|
278
|
+
onCurve: (flags[i] & 1) !== 0
|
|
279
|
+
});
|
|
280
|
+
const contours = [];
|
|
281
|
+
let start = 0;
|
|
282
|
+
for (const end of endPoints) {
|
|
283
|
+
contours.push(points.slice(start, end + 1));
|
|
284
|
+
start = end + 1;
|
|
285
|
+
}
|
|
286
|
+
return contours;
|
|
287
|
+
}
|
|
288
|
+
/** x/y 增量坐标共用解码:短格式 1 字节(符号由 same 位决定),否则 0 或 i16 */
|
|
289
|
+
function readDeltaCoords(view, flags, shortBit, sameBit) {
|
|
290
|
+
const coords = [];
|
|
291
|
+
let value = 0;
|
|
292
|
+
for (const flag of flags) {
|
|
293
|
+
if (flag & shortBit) {
|
|
294
|
+
const delta = view.u8();
|
|
295
|
+
value += (flag & sameBit) !== 0 ? delta : -delta;
|
|
296
|
+
} else if ((flag & sameBit) === 0) value += view.i16();
|
|
297
|
+
coords.push(value);
|
|
298
|
+
}
|
|
299
|
+
return coords;
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* 复合 glyph:组件递归展开,先按组件矩阵变换再平移。
|
|
303
|
+
* 极少见「点对齐」组件(args 为点序号而非 XY 偏移)按偏移量处理,不做点匹配。
|
|
304
|
+
*/
|
|
305
|
+
function readCompositeGlyph(view, resolve) {
|
|
306
|
+
const contours = [];
|
|
307
|
+
let flags;
|
|
308
|
+
do {
|
|
309
|
+
flags = view.u16();
|
|
310
|
+
const glyphIndex = view.u16();
|
|
311
|
+
const wide = (flags & COMPONENT_WORDS_ARGS) !== 0;
|
|
312
|
+
const dx = wide ? view.i16() : view.i8();
|
|
313
|
+
const dy = wide ? view.i16() : view.i8();
|
|
314
|
+
let a = 1;
|
|
315
|
+
let b = 0;
|
|
316
|
+
let c = 0;
|
|
317
|
+
let d = 1;
|
|
318
|
+
if (flags & COMPONENT_SCALE) a = d = readFixed214(view);
|
|
319
|
+
else if (flags & COMPONENT_XY_SCALE) {
|
|
320
|
+
a = readFixed214(view);
|
|
321
|
+
d = readFixed214(view);
|
|
322
|
+
} else if (flags & COMPONENT_TWO_BY_TWO) {
|
|
323
|
+
a = readFixed214(view);
|
|
324
|
+
b = readFixed214(view);
|
|
325
|
+
c = readFixed214(view);
|
|
326
|
+
d = readFixed214(view);
|
|
327
|
+
}
|
|
328
|
+
for (const contour of resolve(glyphIndex)) contours.push(contour.map((point) => ({
|
|
329
|
+
x: a * point.x + c * point.y + dx,
|
|
330
|
+
y: b * point.x + d * point.y + dy,
|
|
331
|
+
onCurve: point.onCurve
|
|
332
|
+
})));
|
|
333
|
+
} while (flags & COMPONENT_MORE);
|
|
334
|
+
return contours;
|
|
335
|
+
}
|
|
336
|
+
function readFixed214(view) {
|
|
337
|
+
return view.u16() / 16384;
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* TrueType 轮廓转 path:相邻 off-curve 点之间补隐式中点(on-curve),
|
|
341
|
+
* 旋转到 on-curve 点开头后逐段输出 L / Q,Z 闭合。
|
|
342
|
+
*/
|
|
343
|
+
function contourPathD(contour, scale, originX, originY) {
|
|
344
|
+
const points = normalizeContour(contour);
|
|
345
|
+
if (points.length === 0) return "";
|
|
346
|
+
const count = points.length;
|
|
347
|
+
const at = (index) => points[index % count];
|
|
348
|
+
const x = (point) => formatNumber(point.x * scale + originX);
|
|
349
|
+
const y = (point) => formatNumber(originY - point.y * scale);
|
|
350
|
+
const parts = [`M ${x(points[0])} ${y(points[0])}`];
|
|
351
|
+
for (let i = 1; i < count; i++) {
|
|
352
|
+
const point = at(i);
|
|
353
|
+
if (point.onCurve) parts.push(`L ${x(point)} ${y(point)}`);
|
|
354
|
+
else {
|
|
355
|
+
const next = at(i + 1);
|
|
356
|
+
parts.push(`Q ${x(point)} ${y(point)} ${x(next)} ${y(next)}`);
|
|
357
|
+
i++;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
parts.push("Z");
|
|
361
|
+
return parts.join(" ");
|
|
362
|
+
}
|
|
363
|
+
function normalizeContour(contour) {
|
|
364
|
+
const count = contour.length;
|
|
365
|
+
if (count === 0) return [];
|
|
366
|
+
const expanded = [];
|
|
367
|
+
for (let i = 0; i < count; i++) {
|
|
368
|
+
const point = contour[i];
|
|
369
|
+
expanded.push(point);
|
|
370
|
+
const next = contour[(i + 1) % count];
|
|
371
|
+
if (!point.onCurve && !next.onCurve) expanded.push({
|
|
372
|
+
x: (point.x + next.x) / 2,
|
|
373
|
+
y: (point.y + next.y) / 2,
|
|
374
|
+
onCurve: true
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
const start = expanded.findIndex((point) => point.onCurve);
|
|
378
|
+
if (start <= 0) return expanded;
|
|
379
|
+
return [...expanded.slice(start), ...expanded.slice(0, start)];
|
|
380
|
+
}
|
|
381
|
+
//#endregion
|
|
382
|
+
export { glyphPathD, parseEmbeddedFont };
|
|
383
|
+
|
|
384
|
+
//# sourceMappingURL=font.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"font.js","names":[],"sources":["../../../../../ofd-core/dist/font.js"],"sourcesContent":["import { OfdParseError } from \"./error.js\";\nimport { formatNumber } from \"./number.js\";\n//#region src/font.ts\n/**\n* 内嵌 TTF 字体解析与 glyph 轮廓输出(OFD Font@FontFile 声明的内嵌字体)。\n*\n* 读取 TrueType 的 head/maxp/cmap/loca/glyf/hhea/hmtx 表:cmap 把字符码点映射到\n* glyph id(可省,省略时字符映射由渲染层 CGTransform 字形序号提供),glyf 提取\n* 轮廓点(on/off curve,复合 glyph 递归展开并平移/缩放)。\n* 二次贝塞尔轮廓按 unitsPerEm → 字号缩放、基线定位并翻转 y 轴后转成 SVG path\n* `d`。二进制结构非法时抛 OfdParseError('invalid-font')。\n*/\n/** 解析 TTF 二进制:结构非法抛 OfdParseError('invalid-font') */\nfunction parseEmbeddedFont(data, source) {\n\tif (data.length < 12) throw new OfdParseError(\"invalid-font\", `字体 ${source} 数据太短,不是 TTF 文件`);\n\tconst tables = parseTableDirectory(data, source);\n\tfor (const tag of REQUIRED_TABLES) {\n\t\tconst table = tables.get(tag);\n\t\tif (!table) throw new OfdParseError(\"invalid-font\", `字体 ${source} 缺少 ${tag} 表`);\n\t\tif (table.offset + table.length > data.length) throw new OfdParseError(\"invalid-font\", `字体 ${source} 的 ${tag} 表越界,数据被截断`);\n\t}\n\tconst headOffset = tables.get(\"head\").offset;\n\tconst unitsPerEm = new FontReader(data, source, headOffset + 18).u16();\n\tif (unitsPerEm === 0) throw new OfdParseError(\"invalid-font\", `字体 ${source} 的 unitsPerEm 为 0`);\n\tconst indexToLocFormat = new FontReader(data, source, headOffset + 50).i16();\n\tif (indexToLocFormat !== 0 && indexToLocFormat !== 1) throw new OfdParseError(\"invalid-font\", `字体 ${source} 的 indexToLocFormat 不合法:${indexToLocFormat}`);\n\tconst numGlyphs = new FontReader(data, source, tables.get(\"maxp\").offset + 4).u16();\n\tconst cmapTable = tables.get(\"cmap\");\n\tconst cmap = cmapTable ? parseCmap(data, cmapTable, source) : /* @__PURE__ */ new Map();\n\tconst glyfTable = tables.get(\"glyf\");\n\tconst locaView = new FontReader(data, source, tables.get(\"loca\").offset);\n\tconst loca = [];\n\tfor (let i = 0; i <= numGlyphs; i++) loca.push(indexToLocFormat === 0 ? locaView.u16() * 2 : locaView.u32());\n\tfor (let i = 0; i < numGlyphs; i++) if (loca[i + 1] < loca[i] || loca[i + 1] > glyfTable.length) throw new OfdParseError(\"invalid-font\", `字体 ${source} 的 loca 表越界`);\n\tconst numberOfHMetrics = Math.min(new FontReader(data, source, tables.get(\"hhea\").offset + 34).u16(), numGlyphs);\n\tconst hmtxView = new FontReader(data, source, tables.get(\"hmtx\").offset);\n\tconst advances = [];\n\tfor (let i = 0; i < numberOfHMetrics; i++) {\n\t\tadvances.push(hmtxView.u16());\n\t\thmtxView.i16();\n\t}\n\tconst glyf = data.subarray(glyfTable.offset, glyfTable.offset + glyfTable.length);\n\tconst outlineCache = /* @__PURE__ */ new Map();\n\treturn {\n\t\tunitsPerEm,\n\t\tnumGlyphs,\n\t\tglyphIndexOf: (codePoint) => cmap.get(codePoint) ?? null,\n\t\tadvanceWidth: (gid) => advances[Math.min(Math.max(gid, 0), advances.length - 1)] ?? 0,\n\t\tglyphOutline: (gid) => {\n\t\t\tconst cached = outlineCache.get(gid);\n\t\t\tif (cached) return cached;\n\t\t\tconst outline = readGlyphOutline(glyf, gid, /* @__PURE__ */ new Set([gid]), numGlyphs, loca, source);\n\t\t\toutlineCache.set(gid, outline);\n\t\t\treturn outline;\n\t\t}\n\t};\n}\n/**\n* glyph 轮廓转 SVG path `d`:unitsPerEm → 字号缩放,基线原点定位并翻转\n* y 轴(字体坐标向上,SVG 向下)。无轮廓返回空串。\n*/\nfunction glyphPathD(font, gid, fontSize, originX, originY) {\n\tif (!(fontSize > 0)) return \"\";\n\tconst scale = fontSize / font.unitsPerEm;\n\treturn font.glyphOutline(gid).map((contour) => contourPathD(contour, scale, originX, originY)).filter((d) => d !== \"\").join(\" \");\n}\nconst REQUIRED_TABLES = [\n\t\"glyf\",\n\t\"head\",\n\t\"hhea\",\n\t\"hmtx\",\n\t\"loca\",\n\t\"maxp\"\n];\n/** 带越界检查的大端读取器:越界抛 invalid-font */\nvar FontReader = class {\n\tdata;\n\tsource;\n\toffset;\n\tconstructor(data, source, offset = 0) {\n\t\tthis.data = data;\n\t\tthis.source = source;\n\t\tthis.offset = offset;\n\t}\n\tseek(offset) {\n\t\tthis.offset = offset;\n\t}\n\tu8() {\n\t\tthis.take(1);\n\t\treturn this.data[this.offset++];\n\t}\n\ti8() {\n\t\treturn this.u8() << 24 >> 24;\n\t}\n\tu16() {\n\t\tthis.take(2);\n\t\tconst value = this.data[this.offset] << 8 | this.data[this.offset + 1];\n\t\tthis.offset += 2;\n\t\treturn value;\n\t}\n\ti16() {\n\t\treturn this.u16() << 16 >> 16;\n\t}\n\tu32() {\n\t\treturn (this.u16() << 16 | this.u16()) >>> 0;\n\t}\n\t/** 读指定绝对偏移的 u16(cmap format 4 的 glyphIdArray 寻址用) */\n\tu16At(offset) {\n\t\tif (offset < 0 || offset + 2 > this.data.length) this.overrun();\n\t\treturn this.data[offset] << 8 | this.data[offset + 1];\n\t}\n\ttake(size) {\n\t\tif (this.offset + size > this.data.length) this.overrun();\n\t}\n\toverrun() {\n\t\tthrow new OfdParseError(\"invalid-font\", `字体 ${this.source} 数据被截断`);\n\t}\n};\nfunction parseTableDirectory(data, source) {\n\tconst view = new FontReader(data, source);\n\tview.u32();\n\tconst numTables = view.u16();\n\tview.seek(view.offset + 6);\n\tconst tables = /* @__PURE__ */ new Map();\n\tfor (let i = 0; i < numTables; i++) {\n\t\tconst tag = String.fromCharCode(view.u8(), view.u8(), view.u8(), view.u8());\n\t\tview.u32();\n\t\tconst offset = view.u32();\n\t\tconst length = view.u32();\n\t\ttables.set(tag, {\n\t\t\toffset,\n\t\t\tlength\n\t\t});\n\t}\n\treturn tables;\n}\nfunction parseCmap(data, table, source) {\n\tconst header = new FontReader(data, source, table.offset);\n\theader.u16();\n\tconst recordCount = header.u16();\n\tconst records = [];\n\tfor (let i = 0; i < recordCount; i++) records.push({\n\t\tplatform: header.u16(),\n\t\tencoding: header.u16(),\n\t\toffset: table.offset + header.u32()\n\t});\n\tconst score = (record) => record.platform === 3 && record.encoding === 10 ? 3 : record.platform === 0 ? 2 : record.platform === 3 && record.encoding === 1 ? 1 : 0;\n\trecords.sort((a, b) => score(b) - score(a));\n\tfor (const record of records) try {\n\t\treturn parseCmapSubtable(new FontReader(data, source, record.offset), source);\n\t} catch {}\n\tthrow new OfdParseError(\"invalid-font\", `字体 ${source} 没有可用的 cmap 子表`);\n}\nfunction parseCmapSubtable(view, source) {\n\tconst format = view.u16();\n\tswitch (format) {\n\t\tcase 4: return parseCmapFormat4(view);\n\t\tcase 12: return parseCmapFormat12(view);\n\t\tcase 0: return parseCmapFormat0(view);\n\t\tcase 6: return parseCmapFormat6(view);\n\t\tdefault: throw new OfdParseError(\"invalid-font\", `字体 ${source} 的 cmap 子表格式 ${format} 不支持`);\n\t}\n}\nfunction parseCmapFormat4(view) {\n\tview.u16();\n\tview.u16();\n\tconst segCount = view.u16() / 2;\n\tview.u16();\n\tview.u16();\n\tview.u16();\n\tif (segCount === 0) throw new OfdParseError(\"invalid-font\", \"cmap format 4 段数为 0\");\n\tconst ends = readU16Array(view, segCount);\n\tview.u16();\n\tconst starts = readU16Array(view, segCount);\n\tconst deltas = readU16Array(view, segCount);\n\tconst rangeOffsetBase = view.offset;\n\tconst rangeOffsets = readU16Array(view, segCount);\n\tconst map = /* @__PURE__ */ new Map();\n\tfor (let s = 0; s < segCount; s++) for (let code = starts[s]; code <= ends[s]; code++) {\n\t\tlet gid;\n\t\tif (rangeOffsets[s] === 0) gid = code + deltas[s] & 65535;\n\t\telse {\n\t\t\tconst address = rangeOffsetBase + 2 * s + rangeOffsets[s] + 2 * (code - starts[s]);\n\t\t\tgid = view.u16At(address);\n\t\t\tif (gid === 0) continue;\n\t\t\tgid = gid + deltas[s] & 65535;\n\t\t}\n\t\tif (gid !== 0) map.set(code, gid);\n\t}\n\treturn map;\n}\nfunction parseCmapFormat12(view) {\n\tview.u16();\n\tview.u32();\n\tview.u32();\n\tconst groupCount = view.u32();\n\tconst map = /* @__PURE__ */ new Map();\n\tfor (let g = 0; g < groupCount; g++) {\n\t\tconst start = view.u32();\n\t\tconst end = Math.min(view.u32(), 1114111);\n\t\tconst startGlyph = view.u32();\n\t\tfor (let code = start; code <= end; code++) {\n\t\t\tconst gid = startGlyph + (code - start);\n\t\t\tif (gid !== 0) map.set(code, gid);\n\t\t}\n\t}\n\treturn map;\n}\nfunction parseCmapFormat0(view) {\n\tview.u16();\n\tview.u16();\n\tconst map = /* @__PURE__ */ new Map();\n\tfor (let code = 0; code < 256; code++) {\n\t\tconst gid = view.u8();\n\t\tif (gid !== 0) map.set(code, gid);\n\t}\n\treturn map;\n}\nfunction parseCmapFormat6(view) {\n\tview.u16();\n\tview.u16();\n\tconst firstCode = view.u16();\n\tconst entryCount = view.u16();\n\tconst map = /* @__PURE__ */ new Map();\n\tfor (let i = 0; i < entryCount; i++) {\n\t\tconst gid = view.u16();\n\t\tif (gid !== 0) map.set(firstCode + i, gid);\n\t}\n\treturn map;\n}\nfunction readU16Array(view, count) {\n\treturn Array.from({ length: count }, () => view.u16());\n}\nconst COMPONENT_WORDS_ARGS = 1;\nconst COMPONENT_MORE = 32;\nconst COMPONENT_SCALE = 8;\nconst COMPONENT_XY_SCALE = 64;\nconst COMPONENT_TWO_BY_TWO = 128;\n/** 读单个 glyph 轮廓;复合 glyph 递归展开组件(带环检测),越界/空 glyph 返回空 */\nfunction readGlyphOutline(glyf, gid, visiting, numGlyphs, loca, source) {\n\tif (gid < 0 || gid >= numGlyphs) return [];\n\tconst start = loca[gid];\n\tif (start === loca[gid + 1]) return [];\n\tconst view = new FontReader(glyf, source, start);\n\tconst numberOfContours = view.i16();\n\tview.seek(view.offset + 8);\n\tif (numberOfContours >= 0) return readSimpleGlyph(view, numberOfContours);\n\treturn readCompositeGlyph(view, (componentGid) => {\n\t\tif (visiting.has(componentGid)) return [];\n\t\tconst next = new Set(visiting);\n\t\tnext.add(componentGid);\n\t\treturn readGlyphOutline(glyf, componentGid, next, numGlyphs, loca, source);\n\t});\n}\n/** 简单 glyph:标志位驱动的增量坐标解码 */\nfunction readSimpleGlyph(view, numberOfContours) {\n\tif (numberOfContours === 0) return [];\n\tconst endPoints = [];\n\tfor (let i = 0; i < numberOfContours; i++) endPoints.push(view.u16());\n\tconst pointCount = endPoints[endPoints.length - 1] + 1;\n\tconst instructionLength = view.u16();\n\tview.seek(view.offset + instructionLength);\n\tconst flags = [];\n\twhile (flags.length < pointCount) {\n\t\tconst flag = view.u8();\n\t\tflags.push(flag);\n\t\tif (flag & 8) {\n\t\t\tconst repeat = view.u8();\n\t\t\tfor (let r = 0; r < repeat && flags.length < pointCount; r++) flags.push(flag);\n\t\t}\n\t}\n\tconst xs = readDeltaCoords(view, flags, 2, 16);\n\tconst ys = readDeltaCoords(view, flags, 4, 32);\n\tconst points = [];\n\tfor (let i = 0; i < pointCount; i++) points.push({\n\t\tx: xs[i],\n\t\ty: ys[i],\n\t\tonCurve: (flags[i] & 1) !== 0\n\t});\n\tconst contours = [];\n\tlet start = 0;\n\tfor (const end of endPoints) {\n\t\tcontours.push(points.slice(start, end + 1));\n\t\tstart = end + 1;\n\t}\n\treturn contours;\n}\n/** x/y 增量坐标共用解码:短格式 1 字节(符号由 same 位决定),否则 0 或 i16 */\nfunction readDeltaCoords(view, flags, shortBit, sameBit) {\n\tconst coords = [];\n\tlet value = 0;\n\tfor (const flag of flags) {\n\t\tif (flag & shortBit) {\n\t\t\tconst delta = view.u8();\n\t\t\tvalue += (flag & sameBit) !== 0 ? delta : -delta;\n\t\t} else if ((flag & sameBit) === 0) value += view.i16();\n\t\tcoords.push(value);\n\t}\n\treturn coords;\n}\n/**\n* 复合 glyph:组件递归展开,先按组件矩阵变换再平移。\n* 极少见「点对齐」组件(args 为点序号而非 XY 偏移)按偏移量处理,不做点匹配。\n*/\nfunction readCompositeGlyph(view, resolve) {\n\tconst contours = [];\n\tlet flags;\n\tdo {\n\t\tflags = view.u16();\n\t\tconst glyphIndex = view.u16();\n\t\tconst wide = (flags & COMPONENT_WORDS_ARGS) !== 0;\n\t\tconst dx = wide ? view.i16() : view.i8();\n\t\tconst dy = wide ? view.i16() : view.i8();\n\t\tlet a = 1;\n\t\tlet b = 0;\n\t\tlet c = 0;\n\t\tlet d = 1;\n\t\tif (flags & COMPONENT_SCALE) a = d = readFixed214(view);\n\t\telse if (flags & COMPONENT_XY_SCALE) {\n\t\t\ta = readFixed214(view);\n\t\t\td = readFixed214(view);\n\t\t} else if (flags & COMPONENT_TWO_BY_TWO) {\n\t\t\ta = readFixed214(view);\n\t\t\tb = readFixed214(view);\n\t\t\tc = readFixed214(view);\n\t\t\td = readFixed214(view);\n\t\t}\n\t\tfor (const contour of resolve(glyphIndex)) contours.push(contour.map((point) => ({\n\t\t\tx: a * point.x + c * point.y + dx,\n\t\t\ty: b * point.x + d * point.y + dy,\n\t\t\tonCurve: point.onCurve\n\t\t})));\n\t} while (flags & COMPONENT_MORE);\n\treturn contours;\n}\nfunction readFixed214(view) {\n\treturn view.u16() / 16384;\n}\n/**\n* TrueType 轮廓转 path:相邻 off-curve 点之间补隐式中点(on-curve),\n* 旋转到 on-curve 点开头后逐段输出 L / Q,Z 闭合。\n*/\nfunction contourPathD(contour, scale, originX, originY) {\n\tconst points = normalizeContour(contour);\n\tif (points.length === 0) return \"\";\n\tconst count = points.length;\n\tconst at = (index) => points[index % count];\n\tconst x = (point) => formatNumber(point.x * scale + originX);\n\tconst y = (point) => formatNumber(originY - point.y * scale);\n\tconst parts = [`M ${x(points[0])} ${y(points[0])}`];\n\tfor (let i = 1; i < count; i++) {\n\t\tconst point = at(i);\n\t\tif (point.onCurve) parts.push(`L ${x(point)} ${y(point)}`);\n\t\telse {\n\t\t\tconst next = at(i + 1);\n\t\t\tparts.push(`Q ${x(point)} ${y(point)} ${x(next)} ${y(next)}`);\n\t\t\ti++;\n\t\t}\n\t}\n\tparts.push(\"Z\");\n\treturn parts.join(\" \");\n}\nfunction normalizeContour(contour) {\n\tconst count = contour.length;\n\tif (count === 0) return [];\n\tconst expanded = [];\n\tfor (let i = 0; i < count; i++) {\n\t\tconst point = contour[i];\n\t\texpanded.push(point);\n\t\tconst next = contour[(i + 1) % count];\n\t\tif (!point.onCurve && !next.onCurve) expanded.push({\n\t\t\tx: (point.x + next.x) / 2,\n\t\t\ty: (point.y + next.y) / 2,\n\t\t\tonCurve: true\n\t\t});\n\t}\n\tconst start = expanded.findIndex((point) => point.onCurve);\n\tif (start <= 0) return expanded;\n\treturn [...expanded.slice(start), ...expanded.slice(0, start)];\n}\n//#endregion\nexport { glyphPathD, parseEmbeddedFont };\n\n//# sourceMappingURL=font.js.map"],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { OfdParseError } from "./error.js";
|
|
2
|
+
import { openOfdZip } from "./zip.js";
|
|
3
|
+
import { parseOfd, parseOfdContainer } from "./parse.js";
|
|
4
|
+
import { glyphPathD, parseEmbeddedFont } from "./font.js";
|
|
5
|
+
import { pageToSvg } from "./render.js";
|
|
6
|
+
export { OfdParseError, glyphPathD, openOfdZip, pageToSvg, parseEmbeddedFont, parseOfd, parseOfdContainer };
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
//#region ../ofd-core/dist/number.js
|
|
2
|
+
/** SVG 输出共用的小数处理(render 与 font 两处调用) */
|
|
3
|
+
function roundTo(value, digits) {
|
|
4
|
+
return Math.round(value * 10 ** digits) / 10 ** digits;
|
|
5
|
+
}
|
|
6
|
+
/** 数值转 SVG 属性文本:截掉浮点噪声(如 0.30000000000000004 → 0.3) */
|
|
7
|
+
function formatNumber(value) {
|
|
8
|
+
return String(roundTo(value, 4));
|
|
9
|
+
}
|
|
10
|
+
//#endregion
|
|
11
|
+
export { formatNumber, roundTo };
|
|
12
|
+
|
|
13
|
+
//# sourceMappingURL=number.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"number.js","names":[],"sources":["../../../../../ofd-core/dist/number.js"],"sourcesContent":["//#region src/number.ts\n/** SVG 输出共用的小数处理(render 与 font 两处调用) */\nfunction roundTo(value, digits) {\n\treturn Math.round(value * 10 ** digits) / 10 ** digits;\n}\n/** 数值转 SVG 属性文本:截掉浮点噪声(如 0.30000000000000004 → 0.3) */\nfunction formatNumber(value) {\n\treturn String(roundTo(value, 4));\n}\n//#endregion\nexport { formatNumber, roundTo };\n\n//# sourceMappingURL=number.js.map"],"mappings":";;AAEA,SAAS,QAAQ,OAAO,QAAQ;CAC/B,OAAO,KAAK,MAAM,QAAQ,MAAM,MAAM,IAAI,MAAM;AACjD;;AAEA,SAAS,aAAa,OAAO;CAC5B,OAAO,OAAO,QAAQ,OAAO,CAAC,CAAC;AAChC"}
|