@formulaxjs/renderer-kity 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js ADDED
@@ -0,0 +1,162 @@
1
+ // src/renderer.ts
2
+ import {
3
+ createFormulaRenderCacheKey
4
+ } from "@formulaxjs/renderer";
5
+ import { mountKityEditor } from "@formulaxjs/kity-runtime";
6
+
7
+ // src/dom.ts
8
+ function createHiddenRenderHost(doc = document) {
9
+ const host = doc.createElement("div");
10
+ host.style.position = "fixed";
11
+ host.style.left = "-100000px";
12
+ host.style.top = "0";
13
+ host.style.width = "1px";
14
+ host.style.height = "1px";
15
+ host.style.opacity = "0";
16
+ host.style.pointerEvents = "none";
17
+ host.setAttribute("aria-hidden", "true");
18
+ doc.body.appendChild(host);
19
+ return host;
20
+ }
21
+ async function waitForDocumentFonts(doc) {
22
+ if (!doc.fonts?.ready) return;
23
+ try {
24
+ await doc.fonts.ready;
25
+ } catch {
26
+ }
27
+ }
28
+ function waitForAnimationFrame(view) {
29
+ return new Promise((resolve) => {
30
+ view.requestAnimationFrame(() => resolve());
31
+ });
32
+ }
33
+
34
+ // src/serialize.ts
35
+ import {
36
+ readRenderedFormulaSvgBox,
37
+ serializeSvgForInsertion
38
+ } from "@formulaxjs/renderer";
39
+ function findKityFormulaSvg(root) {
40
+ return root.querySelector(
41
+ ".kf-editor-edit-area svg, .kf-editor-canvas-container svg, svg"
42
+ );
43
+ }
44
+ function serializeKityFormulaFromRoot(root) {
45
+ const svg = findKityFormulaSvg(root);
46
+ if (!svg) {
47
+ return "";
48
+ }
49
+ return serializeSvgForInsertion(svg);
50
+ }
51
+ async function waitForKityFormulaSvgLayout(root) {
52
+ const doc = root.ownerDocument ?? document;
53
+ const view = doc.defaultView ?? window;
54
+ await waitForDocumentFonts(doc);
55
+ let previous = readRenderedFormulaBox(root);
56
+ for (let attempt = 0; attempt < 4; attempt += 1) {
57
+ await waitForAnimationFrame(view);
58
+ const current = readRenderedFormulaBox(root);
59
+ if (previous && current && areSvgBoxesClose(previous, current)) {
60
+ return;
61
+ }
62
+ previous = current;
63
+ }
64
+ }
65
+ function readRenderedFormulaBox(root) {
66
+ const svg = findKityFormulaSvg(root);
67
+ if (!svg) {
68
+ return null;
69
+ }
70
+ return readRenderedFormulaSvgBox(svg);
71
+ }
72
+ function areSvgBoxesClose(left, right) {
73
+ return Math.abs(left.x - right.x) < 0.01 && Math.abs(left.y - right.y) < 0.01 && Math.abs(left.width - right.width) < 0.01 && Math.abs(left.height - right.height) < 0.01;
74
+ }
75
+
76
+ // src/renderer.ts
77
+ var renderCache = /* @__PURE__ */ new Map();
78
+ function hasCustomAssetOverrides(assets) {
79
+ return Boolean(assets && Object.keys(assets).length > 0);
80
+ }
81
+ function createKityFormulaRenderer(defaults = {}) {
82
+ return {
83
+ renderLatex(latex, options = {}) {
84
+ return renderLatexToSvgMarkup(latex, {
85
+ ...defaults,
86
+ ...options
87
+ });
88
+ }
89
+ };
90
+ }
91
+ function renderLatexToSvgMarkup(latex, options = {}) {
92
+ const normalizedLatex = latex.trim();
93
+ const shouldUseCache = options.cache !== false && !(hasCustomAssetOverrides(options.assets) && !options.assetsVersion);
94
+ if (!normalizedLatex) {
95
+ return Promise.resolve({
96
+ engine: "kity",
97
+ output: "svg",
98
+ latex: normalizedLatex,
99
+ html: ""
100
+ });
101
+ }
102
+ const cacheKey = createFormulaRenderCacheKey({
103
+ engine: "kity",
104
+ latex: normalizedLatex,
105
+ output: "svg",
106
+ fontSize: options.fontSize,
107
+ displayMode: options.displayMode,
108
+ className: options.className,
109
+ assetsVersion: options.assetsVersion
110
+ });
111
+ if (shouldUseCache) {
112
+ const cached = renderCache.get(cacheKey);
113
+ if (cached) {
114
+ return cached;
115
+ }
116
+ }
117
+ const pending = renderLatexToSvgMarkupUncached(normalizedLatex, options);
118
+ if (shouldUseCache) {
119
+ renderCache.set(cacheKey, pending);
120
+ pending.catch(() => {
121
+ if (renderCache.get(cacheKey) === pending) {
122
+ renderCache.delete(cacheKey);
123
+ }
124
+ });
125
+ }
126
+ return pending;
127
+ }
128
+ async function renderLatexToSvgMarkupUncached(latex, options) {
129
+ if (typeof document === "undefined") {
130
+ throw new Error("Kity renderer requires a browser document.");
131
+ }
132
+ const host = createHiddenRenderHost(document);
133
+ const handle = await mountKityEditor(host, {
134
+ initialLatex: latex,
135
+ height: options.height ?? "100%",
136
+ autofocus: false,
137
+ assets: options.assets,
138
+ render: {
139
+ fontsize: options.fontSize ?? 40
140
+ }
141
+ });
142
+ try {
143
+ await waitForKityFormulaSvgLayout(host);
144
+ return {
145
+ engine: "kity",
146
+ output: "svg",
147
+ latex,
148
+ html: serializeKityFormulaFromRoot(host)
149
+ };
150
+ } finally {
151
+ handle.destroy();
152
+ host.remove();
153
+ }
154
+ }
155
+ export {
156
+ createKityFormulaRenderer,
157
+ findKityFormulaSvg,
158
+ renderLatexToSvgMarkup,
159
+ serializeKityFormulaFromRoot,
160
+ waitForKityFormulaSvgLayout
161
+ };
162
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/renderer.ts","../src/dom.ts","../src/serialize.ts"],"sourcesContent":["import {\n createFormulaRenderCacheKey,\n type FormulaRenderer,\n type FormulaRenderResult,\n} from '@formulaxjs/renderer';\nimport { mountKityEditor, type KityEditorAssets } from '@formulaxjs/kity-runtime';\nimport { createHiddenRenderHost } from './dom';\nimport {\n serializeKityFormulaFromRoot,\n waitForKityFormulaSvgLayout,\n} from './serialize';\nimport type { KityFormulaRenderOptions } from './types';\n\nconst renderCache = new Map<string, Promise<FormulaRenderResult>>();\n\nfunction hasCustomAssetOverrides(assets?: Partial<KityEditorAssets>): boolean {\n return Boolean(assets && Object.keys(assets).length > 0);\n}\n\nexport function createKityFormulaRenderer(\n defaults: KityFormulaRenderOptions = {},\n): FormulaRenderer {\n return {\n renderLatex(latex, options = {}) {\n return renderLatexToSvgMarkup(latex, {\n ...defaults,\n ...options,\n });\n },\n };\n}\n\nexport function renderLatexToSvgMarkup(\n latex: string,\n options: KityFormulaRenderOptions = {},\n): Promise<FormulaRenderResult> {\n const normalizedLatex = latex.trim();\n const shouldUseCache = options.cache !== false\n && !(hasCustomAssetOverrides(options.assets) && !options.assetsVersion);\n\n if (!normalizedLatex) {\n return Promise.resolve({\n engine: 'kity',\n output: 'svg',\n latex: normalizedLatex,\n html: '',\n });\n }\n\n const cacheKey = createFormulaRenderCacheKey({\n engine: 'kity',\n latex: normalizedLatex,\n output: 'svg',\n fontSize: options.fontSize,\n displayMode: options.displayMode,\n className: options.className,\n assetsVersion: options.assetsVersion,\n });\n\n if (shouldUseCache) {\n const cached = renderCache.get(cacheKey);\n if (cached) {\n return cached;\n }\n }\n\n const pending = renderLatexToSvgMarkupUncached(normalizedLatex, options);\n\n if (shouldUseCache) {\n renderCache.set(cacheKey, pending);\n pending.catch(() => {\n if (renderCache.get(cacheKey) === pending) {\n renderCache.delete(cacheKey);\n }\n });\n }\n\n return pending;\n}\n\nasync function renderLatexToSvgMarkupUncached(\n latex: string,\n options: KityFormulaRenderOptions,\n): Promise<FormulaRenderResult> {\n if (typeof document === 'undefined') {\n throw new Error('Kity renderer requires a browser document.');\n }\n\n const host = createHiddenRenderHost(document);\n const handle = await mountKityEditor(host, {\n initialLatex: latex,\n height: options.height ?? '100%',\n autofocus: false,\n assets: options.assets,\n render: {\n fontsize: options.fontSize ?? 40,\n },\n });\n\n try {\n await waitForKityFormulaSvgLayout(host);\n\n return {\n engine: 'kity',\n output: 'svg',\n latex,\n html: serializeKityFormulaFromRoot(host),\n };\n } finally {\n handle.destroy();\n host.remove();\n }\n}\n","export function createHiddenRenderHost(doc: Document = document): HTMLElement {\n const host = doc.createElement('div');\n\n host.style.position = 'fixed';\n host.style.left = '-100000px';\n host.style.top = '0';\n host.style.width = '1px';\n host.style.height = '1px';\n host.style.opacity = '0';\n host.style.pointerEvents = 'none';\n host.setAttribute('aria-hidden', 'true');\n\n doc.body.appendChild(host);\n\n return host;\n}\n\nexport async function waitForDocumentFonts(doc: Document): Promise<void> {\n if (!doc.fonts?.ready) return;\n\n try {\n await doc.fonts.ready;\n } catch {\n // Ignore font readiness failures and fall back to frame-based settling.\n }\n}\n\nexport function waitForAnimationFrame(view: Window): Promise<void> {\n return new Promise((resolve) => {\n view.requestAnimationFrame(() => resolve());\n });\n}\n","import {\n readRenderedFormulaSvgBox,\n serializeSvgForInsertion,\n type SvgBox,\n} from '@formulaxjs/renderer';\nimport { waitForAnimationFrame, waitForDocumentFonts } from './dom';\n\nexport function findKityFormulaSvg(root: HTMLElement): SVGSVGElement | null {\n return root.querySelector<SVGSVGElement>(\n '.kf-editor-edit-area svg, .kf-editor-canvas-container svg, svg',\n );\n}\n\nexport function serializeKityFormulaFromRoot(root: HTMLElement): string {\n const svg = findKityFormulaSvg(root);\n\n if (!svg) {\n return '';\n }\n\n return serializeSvgForInsertion(svg);\n}\n\nexport async function waitForKityFormulaSvgLayout(root: HTMLElement): Promise<void> {\n const doc = root.ownerDocument ?? document;\n const view = doc.defaultView ?? window;\n\n await waitForDocumentFonts(doc);\n\n let previous = readRenderedFormulaBox(root);\n\n for (let attempt = 0; attempt < 4; attempt += 1) {\n await waitForAnimationFrame(view);\n const current = readRenderedFormulaBox(root);\n\n if (previous && current && areSvgBoxesClose(previous, current)) {\n return;\n }\n\n previous = current;\n }\n}\n\nfunction readRenderedFormulaBox(root: HTMLElement): SvgBox | null {\n const svg = findKityFormulaSvg(root);\n\n if (!svg) {\n return null;\n }\n\n return readRenderedFormulaSvgBox(svg);\n}\n\nfunction areSvgBoxesClose(left: SvgBox, right: SvgBox): boolean {\n return Math.abs(left.x - right.x) < 0.01\n && Math.abs(left.y - right.y) < 0.01\n && Math.abs(left.width - right.width) < 0.01\n && Math.abs(left.height - right.height) < 0.01;\n}\n"],"mappings":"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","names":[]}
Binary file
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "@formulaxjs/renderer-kity",
3
+ "version": "0.2.0",
4
+ "private": false,
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "description": "Kity runtime based SVG renderer for FormulaX.",
8
+ "keywords": [
9
+ "formulax",
10
+ "formula",
11
+ "renderer",
12
+ "kity",
13
+ "svg"
14
+ ],
15
+ "main": "./dist/index.cjs",
16
+ "module": "./dist/index.js",
17
+ "types": "./dist/index.d.ts",
18
+ "exports": {
19
+ ".": {
20
+ "types": "./dist/index.d.ts",
21
+ "import": "./dist/index.js",
22
+ "require": "./dist/index.cjs"
23
+ }
24
+ },
25
+ "unpkg": "./dist/browser/index.global.js",
26
+ "files": [
27
+ "dist"
28
+ ],
29
+ "publishConfig": {
30
+ "access": "public"
31
+ },
32
+ "dependencies": {
33
+ "@formulaxjs/renderer": "0.2.0",
34
+ "@formulaxjs/kity-runtime": "0.3.0"
35
+ },
36
+ "scripts": {
37
+ "build": "tsup --config ../../tsup.config.mjs",
38
+ "clean": "Remove-Item -Recurse -Force dist -ErrorAction SilentlyContinue",
39
+ "typecheck": "tsc -p tsconfig.json --noEmit"
40
+ }
41
+ }