@franken-suite/franken-markdown 0.4.2 → 0.4.4
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/fmd-view.d.ts +40 -0
- package/fmd-view.js +137 -0
- package/franken_markdown.d.ts +89 -1
- package/franken_markdown.js +267 -56
- package/package.json +11 -3
- package/pkg/franken_markdown.d.ts +85 -3
- package/pkg/franken_markdown.js +409 -3
- package/pkg/franken_markdown_bg.wasm +0 -0
- package/pkg/franken_markdown_bg.wasm.d.ts +12 -1
package/fmd-view.d.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `<fmd-view>` web component type surface (bead uito).
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
export interface FmdRenderedDetail {
|
|
6
|
+
bytes: number;
|
|
7
|
+
sourceLength: number;
|
|
8
|
+
diagnostics: Array<{
|
|
9
|
+
severity: "warning" | "error";
|
|
10
|
+
start: number;
|
|
11
|
+
end: number;
|
|
12
|
+
message: string;
|
|
13
|
+
}>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export interface FmdViewEventMap {
|
|
17
|
+
"fmd-rendered": CustomEvent<FmdRenderedDetail>;
|
|
18
|
+
"fmd-error": CustomEvent<{ error: string }>;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface FmdView extends HTMLElement {
|
|
22
|
+
addEventListener<K extends keyof FmdViewEventMap>(
|
|
23
|
+
type: K,
|
|
24
|
+
listener: (this: FmdView, ev: FmdViewEventMap[K]) => void,
|
|
25
|
+
options?: boolean | AddEventListenerOptions
|
|
26
|
+
): void;
|
|
27
|
+
addEventListener(
|
|
28
|
+
type: string,
|
|
29
|
+
listener: EventListenerOrEventListenerObject,
|
|
30
|
+
options?: boolean | AddEventListenerOptions
|
|
31
|
+
): void;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Register `<fmd-view>` (idempotent). Auto-registered in browser globals on
|
|
36
|
+
* import; call this explicitly for a non-global registry.
|
|
37
|
+
*/
|
|
38
|
+
export function registerFmdView(
|
|
39
|
+
registry?: Pick<CustomElementRegistry, "define" | "get">
|
|
40
|
+
): "fmd-view";
|
package/fmd-view.js
ADDED
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
// <fmd-view> — drop-in Markdown renderer web component (bead uito).
|
|
2
|
+
//
|
|
3
|
+
// Framework-free custom element over the package's createRenderer surface:
|
|
4
|
+
//
|
|
5
|
+
// <fmd-view src="./README.md"></fmd-view>
|
|
6
|
+
// <fmd-view>Six **bytes** of markdown</fmd-view>
|
|
7
|
+
//
|
|
8
|
+
// Attributes:
|
|
9
|
+
// src - fetch this URL as the Markdown source (slot content is the
|
|
10
|
+
// fallback when src is absent/unset)
|
|
11
|
+
// font - "sans" | "serif"
|
|
12
|
+
// dark-mode - "auto" | "disabled"
|
|
13
|
+
//
|
|
14
|
+
// The rendered document lands in a shadow root (style-isolated; the engine's
|
|
15
|
+
// self-contained HTML carries its own CSS). A "fmd-rendered" CustomEvent with
|
|
16
|
+
// { bytes, sourceLength, diagnostics } fires after each successful render;
|
|
17
|
+
// "fmd-error" fires on fetch/render failure. Bytes parity with renderHtml()
|
|
18
|
+
// is exact: the same call is used.
|
|
19
|
+
//
|
|
20
|
+
// Zero dependencies; the shared wasm payload is the package's.
|
|
21
|
+
|
|
22
|
+
import { createRenderer } from "./franken_markdown.js";
|
|
23
|
+
|
|
24
|
+
let rendererPromise = null;
|
|
25
|
+
|
|
26
|
+
function sharedRenderer(wasmInput) {
|
|
27
|
+
if (!rendererPromise) {
|
|
28
|
+
rendererPromise = createRenderer(wasmInput);
|
|
29
|
+
}
|
|
30
|
+
return rendererPromise;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const SLOT_RENDER = Symbol("slot source");
|
|
34
|
+
|
|
35
|
+
class FmdView extends HTMLElement {
|
|
36
|
+
static get observedAttributes() {
|
|
37
|
+
return ["src", "font", "dark-mode"];
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
#root = null;
|
|
41
|
+
#lastSrc = undefined;
|
|
42
|
+
|
|
43
|
+
constructor() {
|
|
44
|
+
super();
|
|
45
|
+
this.#root = this.attachShadow({ mode: "open" });
|
|
46
|
+
this.#root.innerHTML = "<style>:host{display:block}</style><slot></slot>";
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
connectedCallback() {
|
|
50
|
+
this.#render();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
attributeChangedCallback(name, oldV, newV) {
|
|
54
|
+
if (oldV === newV || !this.#root) {
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
if (name === "src" && newV !== this.#lastSrc) {
|
|
58
|
+
this.#render();
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
if (name === "font" || name === "dark-mode") {
|
|
62
|
+
this.#render();
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
async #source() {
|
|
67
|
+
const src = this.getAttribute("src");
|
|
68
|
+
if (src) {
|
|
69
|
+
const res = await fetch(src);
|
|
70
|
+
if (!res.ok) {
|
|
71
|
+
throw new Error(`fmd-view: fetch ${src} -> HTTP ${res.status}`);
|
|
72
|
+
}
|
|
73
|
+
return await res.text();
|
|
74
|
+
}
|
|
75
|
+
// Slot content as inline Markdown. Preserve text exactly.
|
|
76
|
+
const slot = this.querySelector("script[type='text/markdown']");
|
|
77
|
+
if (slot) {
|
|
78
|
+
return slot.textContent ?? "";
|
|
79
|
+
}
|
|
80
|
+
return this.textContent ?? "";
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async #render() {
|
|
84
|
+
const srcAttr = this.getAttribute("src");
|
|
85
|
+
this.#lastSrc = srcAttr;
|
|
86
|
+
let markdown;
|
|
87
|
+
try {
|
|
88
|
+
markdown = await this.#source();
|
|
89
|
+
} catch (err) {
|
|
90
|
+
this.dispatchEvent(new CustomEvent("fmd-error", { detail: { error: String(err) } }));
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
let renderer;
|
|
94
|
+
try {
|
|
95
|
+
// If the host already initialized the engine with explicit wasm bytes,
|
|
96
|
+
// a package-level export would be needed; default path resolves the
|
|
97
|
+
// bundled artifact via the wrapper's own URL resolution.
|
|
98
|
+
renderer = await sharedRenderer(undefined);
|
|
99
|
+
} catch (err) {
|
|
100
|
+
this.dispatchEvent(new CustomEvent("fmd-error", { detail: { error: String(err) } }));
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const out = await renderer.renderHtml(markdown, {
|
|
104
|
+
font: this.getAttribute("font") === "serif" ? "serif" : "sans",
|
|
105
|
+
darkMode: this.getAttribute("dark-mode") === "disabled" ? "disabled" : "auto",
|
|
106
|
+
});
|
|
107
|
+
const html = out.text();
|
|
108
|
+
this.#root.innerHTML = `<style>:host{display:block}</style>${html}`;
|
|
109
|
+
this.dispatchEvent(
|
|
110
|
+
new CustomEvent("fmd-rendered", {
|
|
111
|
+
detail: {
|
|
112
|
+
bytes: out.bytes.byteLength,
|
|
113
|
+
sourceLength: out.sourceLength,
|
|
114
|
+
diagnostics: out.diagnostics,
|
|
115
|
+
},
|
|
116
|
+
})
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const ELEMENT_NAME = "fmd-view";
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Register <fmd-view> on a customElements registry (defaults to global).
|
|
125
|
+
* Idempotent; returns the element name.
|
|
126
|
+
*/
|
|
127
|
+
export function registerFmdView(registry = globalThis.customElements) {
|
|
128
|
+
if (!registry.get(ELEMENT_NAME)) {
|
|
129
|
+
registry.define(ELEMENT_NAME, FmdView);
|
|
130
|
+
}
|
|
131
|
+
return ELEMENT_NAME;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Auto-register in browser globals (no-op under Node/bundlers without it).
|
|
135
|
+
if (typeof globalThis.customElements !== "undefined") {
|
|
136
|
+
registerFmdView();
|
|
137
|
+
}
|
package/franken_markdown.d.ts
CHANGED
|
@@ -1,4 +1,12 @@
|
|
|
1
|
-
export type FmdOutputFormat =
|
|
1
|
+
export type FmdOutputFormat =
|
|
2
|
+
| "html"
|
|
3
|
+
| "pdf"
|
|
4
|
+
| "svg"
|
|
5
|
+
| "epub"
|
|
6
|
+
| "interactive-html"
|
|
7
|
+
| "diff-html"
|
|
8
|
+
| "book-site"
|
|
9
|
+
| "book-pdf";
|
|
2
10
|
export type FmdFont = "sans" | "serif";
|
|
3
11
|
export type FmdDarkMode = "auto" | "disabled";
|
|
4
12
|
|
|
@@ -58,6 +66,20 @@ export interface FmdRenderOptions {
|
|
|
58
66
|
headingScale?: number;
|
|
59
67
|
/** Nominal table cell size override in points; clamped to [5, base]. */
|
|
60
68
|
tableFontSize?: number;
|
|
69
|
+
/** BCP-47 language tag used for HTML metadata and language-aware hyphenation. */
|
|
70
|
+
lang?: "en" | "de" | "fr" | "es" | "nl" | string;
|
|
71
|
+
/** Generate a document table of contents. */
|
|
72
|
+
toc?: boolean;
|
|
73
|
+
/** Maximum generated TOC heading depth (1...6). */
|
|
74
|
+
tocDepth?: number;
|
|
75
|
+
/** Adaptive PDF page-count target. */
|
|
76
|
+
fitToPages?: number;
|
|
77
|
+
/** Enable optical-margin punctuation protrusion for justified PDF text. */
|
|
78
|
+
microtype?: "disabled" | "protrusion";
|
|
79
|
+
/** Boolean alias for microtype: "protrusion". */
|
|
80
|
+
microtypeProtrusion?: boolean;
|
|
81
|
+
/** Standalone SVG poster width in points. */
|
|
82
|
+
maxWidthPt?: number;
|
|
61
83
|
/** Host-supplied image bytes (HTML data URIs and PDF embedding); any number per render. */
|
|
62
84
|
pdfImages?: FmdPdfImageAsset[];
|
|
63
85
|
/** Host-supplied TrueType font bytes by renderer slot. */
|
|
@@ -110,14 +132,80 @@ export interface FmdCapabilities {
|
|
|
110
132
|
|
|
111
133
|
export interface FmdRenderer {
|
|
112
134
|
capabilities(): Promise<FmdCapabilities>;
|
|
135
|
+
accessibilityAudit(markdown: string): Promise<FmdAccessibilityReport>;
|
|
136
|
+
documentStats(markdown: string): Promise<FmdDocumentStats>;
|
|
137
|
+
renderBookPdf(files: FmdBookFile[], options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
138
|
+
renderBookSite(files: FmdBookFile[], options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
139
|
+
renderEpub(markdown: string, options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
113
140
|
renderHtml(markdown: string, options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
141
|
+
renderInteractiveHtml(markdown: string, options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
114
142
|
renderPdf(markdown: string, options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
143
|
+
renderSemanticDiff(oldMarkdown: string, newMarkdown: string, options?: FmdDiffOptions): Promise<FmdRenderOutput>;
|
|
144
|
+
renderSvg(markdown: string, options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
145
|
+
searchIndex(markdown: string): Promise<FmdSearchIndex>;
|
|
146
|
+
semanticDiff(oldMarkdown: string, newMarkdown: string, options?: FmdDiffOptions): Promise<FmdSemanticDiff>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
export interface FmdBookFile { path: string; source: string; }
|
|
150
|
+
export interface FmdDiffOptions { oldName?: string; newName?: string; }
|
|
151
|
+
export interface FmdFinding { severity?: string; code: string; message?: string; detail?: string; }
|
|
152
|
+
export interface FmdDocumentStats {
|
|
153
|
+
schema: "fmd-document-stats-v1";
|
|
154
|
+
bytes: number;
|
|
155
|
+
lines: number;
|
|
156
|
+
words: number;
|
|
157
|
+
characters: number;
|
|
158
|
+
sentences: number;
|
|
159
|
+
syllables: number;
|
|
160
|
+
reading_time_secs: number;
|
|
161
|
+
speaking_time_secs: number;
|
|
162
|
+
flesch_reading_ease: number;
|
|
163
|
+
flesch_kincaid_grade: number;
|
|
164
|
+
reading_ease_label: string;
|
|
165
|
+
structure: Record<string, unknown>;
|
|
166
|
+
outline: Array<{ level: number; text: string; slug: string }>;
|
|
167
|
+
findings: FmdFinding[];
|
|
168
|
+
}
|
|
169
|
+
export interface FmdAccessibilityReport {
|
|
170
|
+
schema_version: "1";
|
|
171
|
+
target: "pdf";
|
|
172
|
+
findings: FmdFinding[];
|
|
173
|
+
[key: string]: unknown;
|
|
174
|
+
}
|
|
175
|
+
export interface FmdSearchIndex {
|
|
176
|
+
schema: "fmd-search-index-v1";
|
|
177
|
+
entries: Array<{ kind: "heading" | "paragraph"; level?: number; anchor: string; text: string }>;
|
|
178
|
+
}
|
|
179
|
+
export interface FmdSemanticDiff {
|
|
180
|
+
schema: "fmd-diff-v1";
|
|
181
|
+
old_name: string;
|
|
182
|
+
new_name: string;
|
|
183
|
+
stats: {
|
|
184
|
+
unchanged_blocks: number;
|
|
185
|
+
inserted_blocks: number;
|
|
186
|
+
deleted_blocks: number;
|
|
187
|
+
modified_blocks: number;
|
|
188
|
+
words_inserted: number;
|
|
189
|
+
words_deleted: number;
|
|
190
|
+
similarity_ratio: number;
|
|
191
|
+
};
|
|
192
|
+
[key: string]: unknown;
|
|
115
193
|
}
|
|
116
194
|
|
|
117
195
|
export function init(input?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module): Promise<void>;
|
|
118
196
|
export function capabilities(): Promise<FmdCapabilities>;
|
|
197
|
+
export function accessibilityAudit(markdown: string): Promise<FmdAccessibilityReport>;
|
|
198
|
+
export function documentStats(markdown: string): Promise<FmdDocumentStats>;
|
|
199
|
+
export function renderBookPdf(files: FmdBookFile[], options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
200
|
+
export function renderBookSite(files: FmdBookFile[], options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
201
|
+
export function renderEpub(markdown: string, options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
119
202
|
export function renderHtml(markdown: string, options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
203
|
+
export function renderInteractiveHtml(markdown: string, options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
120
204
|
export function renderPdf(markdown: string, options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
205
|
+
export function renderSemanticDiff(oldMarkdown: string, newMarkdown: string, options?: FmdDiffOptions): Promise<FmdRenderOutput>;
|
|
206
|
+
export function renderSvg(markdown: string, options?: FmdRenderOptions): Promise<FmdRenderOutput>;
|
|
207
|
+
export function searchIndex(markdown: string): Promise<FmdSearchIndex>;
|
|
208
|
+
export function semanticDiff(oldMarkdown: string, newMarkdown: string, options?: FmdDiffOptions): Promise<FmdSemanticDiff>;
|
|
121
209
|
export function createRenderer(
|
|
122
210
|
input?: RequestInfo | URL | Response | BufferSource | WebAssembly.Module
|
|
123
211
|
): Promise<FmdRenderer>;
|
package/franken_markdown.js
CHANGED
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import initWasm, {
|
|
2
|
+
accessibilityAudit as wasmAccessibilityAudit,
|
|
2
3
|
capabilities as wasmCapabilities,
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
documentStats as wasmDocumentStats,
|
|
5
|
+
renderBookPdf as wasmRenderBookPdf,
|
|
6
|
+
renderBookSite as wasmRenderBookSite,
|
|
7
|
+
renderEpubConfigured,
|
|
8
|
+
renderHtmlConfiguredAdvanced,
|
|
9
|
+
renderInteractiveHtmlConfigured,
|
|
10
|
+
renderPdfConfiguredMulti,
|
|
11
|
+
renderSemanticDiffHtml as wasmRenderSemanticDiffHtml,
|
|
12
|
+
renderSvgConfigured,
|
|
13
|
+
searchIndex as wasmSearchIndex,
|
|
14
|
+
semanticDiff as wasmSemanticDiff
|
|
7
15
|
} from "./pkg/franken_markdown.js";
|
|
8
16
|
|
|
9
17
|
let initPromise = null;
|
|
@@ -29,62 +37,37 @@ export async function renderHtml(markdown, options = {}) {
|
|
|
29
37
|
await init();
|
|
30
38
|
const pdfImages = pdfImagesOption(options.pdfImages);
|
|
31
39
|
const fontAssets = fontAssetsOption(options.fontAssets);
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
}
|
|
42
|
-
return normalizeResult(
|
|
43
|
-
renderHtmlConfiguredMulti(
|
|
44
|
-
String(markdown),
|
|
45
|
-
stringOption(options.font),
|
|
46
|
-
darkModeOption(options.darkMode),
|
|
47
|
-
verbatimOption(options.title),
|
|
48
|
-
verbatimOption(options.customCss),
|
|
49
|
-
Boolean(options.allowRawHtml),
|
|
50
|
-
fontBytesForSlot(fontAssets, "body-regular"),
|
|
51
|
-
fontBytesForSlot(fontAssets, "body-bold"),
|
|
52
|
-
fontBytesForSlot(fontAssets, "body-italic"),
|
|
53
|
-
fontBytesForSlot(fontAssets, "body-bold-italic"),
|
|
54
|
-
fontBytesForSlot(fontAssets, "mono-regular"),
|
|
55
|
-
fontWeightsForSlots(fontAssets),
|
|
56
|
-
destinations,
|
|
57
|
-
flatBytes,
|
|
58
|
-
lengths
|
|
59
|
-
)
|
|
60
|
-
);
|
|
61
|
-
}
|
|
62
|
-
if (fontAssets.length > 0) {
|
|
63
|
-
return normalizeResult(
|
|
64
|
-
renderHtmlConfiguredWithFonts(
|
|
65
|
-
String(markdown),
|
|
66
|
-
stringOption(options.font),
|
|
67
|
-
darkModeOption(options.darkMode),
|
|
68
|
-
verbatimOption(options.title),
|
|
69
|
-
verbatimOption(options.customCss),
|
|
70
|
-
Boolean(options.allowRawHtml),
|
|
71
|
-
fontBytesForSlot(fontAssets, "body-regular"),
|
|
72
|
-
fontBytesForSlot(fontAssets, "body-bold"),
|
|
73
|
-
fontBytesForSlot(fontAssets, "body-italic"),
|
|
74
|
-
fontBytesForSlot(fontAssets, "body-bold-italic"),
|
|
75
|
-
fontBytesForSlot(fontAssets, "mono-regular"),
|
|
76
|
-
fontWeightsForSlots(fontAssets)
|
|
77
|
-
)
|
|
78
|
-
);
|
|
40
|
+
const fontScale = fontScaleOption(options.fontScale ?? options.typeSize);
|
|
41
|
+
const destinations = pdfImages.map((image) => image.destination);
|
|
42
|
+
const lengths = new Uint32Array(pdfImages.map((image) => image.bytes.length));
|
|
43
|
+
const totalBytes = pdfImages.reduce((sum, image) => sum + image.bytes.length, 0);
|
|
44
|
+
const flatBytes = new Uint8Array(totalBytes);
|
|
45
|
+
let offset = 0;
|
|
46
|
+
for (const image of pdfImages) {
|
|
47
|
+
flatBytes.set(image.bytes, offset);
|
|
48
|
+
offset += image.bytes.length;
|
|
79
49
|
}
|
|
80
50
|
return normalizeResult(
|
|
81
|
-
|
|
51
|
+
renderHtmlConfiguredAdvanced(
|
|
82
52
|
String(markdown),
|
|
83
53
|
stringOption(options.font),
|
|
84
54
|
darkModeOption(options.darkMode),
|
|
85
55
|
verbatimOption(options.title),
|
|
86
56
|
verbatimOption(options.customCss),
|
|
87
|
-
Boolean(options.allowRawHtml)
|
|
57
|
+
Boolean(options.allowRawHtml),
|
|
58
|
+
fontScale,
|
|
59
|
+
fontBytesForSlot(fontAssets, "body-regular"),
|
|
60
|
+
fontBytesForSlot(fontAssets, "body-bold"),
|
|
61
|
+
fontBytesForSlot(fontAssets, "body-italic"),
|
|
62
|
+
fontBytesForSlot(fontAssets, "body-bold-italic"),
|
|
63
|
+
fontBytesForSlot(fontAssets, "mono-regular"),
|
|
64
|
+
fontWeightsForSlots(fontAssets),
|
|
65
|
+
destinations,
|
|
66
|
+
flatBytes,
|
|
67
|
+
lengths,
|
|
68
|
+
stringOption(options.lang),
|
|
69
|
+
Boolean(options.toc),
|
|
70
|
+
integerOption(options.tocDepth, "tocDepth")
|
|
88
71
|
)
|
|
89
72
|
);
|
|
90
73
|
}
|
|
@@ -93,6 +76,8 @@ export async function renderPdf(markdown, options = {}) {
|
|
|
93
76
|
await init();
|
|
94
77
|
const pdfImages = pdfImagesOption(options.pdfImages);
|
|
95
78
|
const fontAssets = fontAssetsOption(options.fontAssets);
|
|
79
|
+
const fontScale = fontScaleOption(options.fontScale ?? options.typeSize);
|
|
80
|
+
const baseFontSize = numberOption(options.baseFontSize) ?? (fontScale !== undefined ? 11 * fontScale : undefined);
|
|
96
81
|
|
|
97
82
|
// Flatten any number of images into the three parallel arrays the core ABI
|
|
98
83
|
// accepts (wasm-bindgen cannot pass a Vec<Vec<u8>>): a destination per image,
|
|
@@ -126,20 +111,134 @@ export async function renderPdf(markdown, options = {}) {
|
|
|
126
111
|
fontBytesForSlot(fontAssets, "body-bold-italic"),
|
|
127
112
|
fontBytesForSlot(fontAssets, "mono-regular"),
|
|
128
113
|
fontWeightsForSlots(fontAssets),
|
|
129
|
-
|
|
114
|
+
baseFontSize,
|
|
130
115
|
numberOption(options.headingScale),
|
|
131
116
|
numberOption(options.tableFontSize),
|
|
132
|
-
Boolean(options.pageNumbers)
|
|
117
|
+
Boolean(options.pageNumbers),
|
|
118
|
+
fontScale,
|
|
119
|
+
stringOption(options.lang),
|
|
120
|
+
Boolean(options.toc),
|
|
121
|
+
integerOption(options.tocDepth, "tocDepth"),
|
|
122
|
+
integerOption(options.fitToPages, "fitToPages"),
|
|
123
|
+
options.microtype === "protrusion" || options.microtypeProtrusion === true
|
|
133
124
|
)
|
|
134
125
|
);
|
|
135
126
|
}
|
|
136
127
|
|
|
128
|
+
export async function renderSvg(markdown, options = {}) {
|
|
129
|
+
await init();
|
|
130
|
+
return normalizeResult(renderSvgConfigured(
|
|
131
|
+
String(markdown),
|
|
132
|
+
stringOption(options.font),
|
|
133
|
+
darkModeOption(options.darkMode),
|
|
134
|
+
fontScaleOption(options.fontScale ?? options.typeSize),
|
|
135
|
+
numberOption(options.maxWidthPt)
|
|
136
|
+
));
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
export async function renderEpub(markdown, options = {}) {
|
|
140
|
+
await init();
|
|
141
|
+
return normalizeResult(renderEpubConfigured(
|
|
142
|
+
String(markdown),
|
|
143
|
+
stringOption(options.font),
|
|
144
|
+
darkModeOption(options.darkMode),
|
|
145
|
+
verbatimOption(options.title),
|
|
146
|
+
stringOption(options.lang),
|
|
147
|
+
fontScaleOption(options.fontScale ?? options.typeSize)
|
|
148
|
+
));
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export async function renderInteractiveHtml(markdown, options = {}) {
|
|
152
|
+
await init();
|
|
153
|
+
return normalizeResult(renderInteractiveHtmlConfigured(
|
|
154
|
+
String(markdown),
|
|
155
|
+
stringOption(options.font),
|
|
156
|
+
darkModeOption(options.darkMode),
|
|
157
|
+
verbatimOption(options.title),
|
|
158
|
+
stringOption(options.lang),
|
|
159
|
+
fontScaleOption(options.fontScale ?? options.typeSize)
|
|
160
|
+
));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export async function documentStats(markdown) {
|
|
164
|
+
await init();
|
|
165
|
+
return parseJson(wasmDocumentStats(String(markdown)), "document stats JSON");
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export async function searchIndex(markdown) {
|
|
169
|
+
await init();
|
|
170
|
+
return parseJson(wasmSearchIndex(String(markdown)), "search index JSON");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export async function accessibilityAudit(markdown) {
|
|
174
|
+
await init();
|
|
175
|
+
return parseJson(wasmAccessibilityAudit(String(markdown)), "accessibility audit JSON");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export async function semanticDiff(oldMarkdown, newMarkdown, options = {}) {
|
|
179
|
+
await init();
|
|
180
|
+
return parseJson(wasmSemanticDiff(
|
|
181
|
+
String(oldMarkdown),
|
|
182
|
+
String(newMarkdown),
|
|
183
|
+
verbatimOption(options.oldName),
|
|
184
|
+
verbatimOption(options.newName)
|
|
185
|
+
), "semantic diff JSON");
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
export async function renderSemanticDiff(oldMarkdown, newMarkdown, options = {}) {
|
|
189
|
+
await init();
|
|
190
|
+
return normalizeResult(wasmRenderSemanticDiffHtml(
|
|
191
|
+
String(oldMarkdown),
|
|
192
|
+
String(newMarkdown),
|
|
193
|
+
verbatimOption(options.oldName),
|
|
194
|
+
verbatimOption(options.newName)
|
|
195
|
+
));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
export async function renderBookSite(files, options = {}) {
|
|
199
|
+
await init();
|
|
200
|
+
const normalized = bookFilesOption(files);
|
|
201
|
+
return normalizeResult(wasmRenderBookSite(
|
|
202
|
+
normalized.map((file) => file.path),
|
|
203
|
+
normalized.map((file) => file.source),
|
|
204
|
+
verbatimOption(options.title),
|
|
205
|
+
stringOption(options.font),
|
|
206
|
+
darkModeOption(options.darkMode),
|
|
207
|
+
fontScaleOption(options.fontScale ?? options.typeSize)
|
|
208
|
+
));
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
export async function renderBookPdf(files, options = {}) {
|
|
212
|
+
await init();
|
|
213
|
+
const normalized = bookFilesOption(files);
|
|
214
|
+
return normalizeResult(wasmRenderBookPdf(
|
|
215
|
+
normalized.map((file) => file.path),
|
|
216
|
+
normalized.map((file) => file.source),
|
|
217
|
+
verbatimOption(options.title),
|
|
218
|
+
verbatimOption(options.author),
|
|
219
|
+
stringOption(options.font),
|
|
220
|
+
darkModeOption(options.darkMode),
|
|
221
|
+
fontScaleOption(options.fontScale ?? options.typeSize),
|
|
222
|
+
options.pageNumbers !== false
|
|
223
|
+
));
|
|
224
|
+
}
|
|
225
|
+
|
|
137
226
|
export async function createRenderer(input) {
|
|
138
227
|
await init(input);
|
|
139
228
|
return Object.freeze({
|
|
140
229
|
capabilities,
|
|
230
|
+
accessibilityAudit,
|
|
231
|
+
documentStats,
|
|
232
|
+
renderBookPdf,
|
|
233
|
+
renderBookSite,
|
|
234
|
+
renderEpub,
|
|
141
235
|
renderHtml,
|
|
142
|
-
|
|
236
|
+
renderInteractiveHtml,
|
|
237
|
+
renderPdf,
|
|
238
|
+
renderSemanticDiff,
|
|
239
|
+
renderSvg,
|
|
240
|
+
searchIndex,
|
|
241
|
+
semanticDiff
|
|
143
242
|
});
|
|
144
243
|
}
|
|
145
244
|
|
|
@@ -267,6 +366,118 @@ function numberOption(value) {
|
|
|
267
366
|
}
|
|
268
367
|
return value;
|
|
269
368
|
}
|
|
369
|
+
|
|
370
|
+
function integerOption(value, label) {
|
|
371
|
+
if (value === undefined || value === null) {
|
|
372
|
+
return undefined;
|
|
373
|
+
}
|
|
374
|
+
if (!Number.isSafeInteger(value) || value <= 0) {
|
|
375
|
+
throw new TypeError(`${label} must be a positive integer`);
|
|
376
|
+
}
|
|
377
|
+
return value;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function bookFilesOption(value) {
|
|
381
|
+
if (!Array.isArray(value) || value.length === 0) {
|
|
382
|
+
throw new TypeError("book files must be a non-empty array of { path, source } objects");
|
|
383
|
+
}
|
|
384
|
+
return value.map((file, index) => {
|
|
385
|
+
if (file === null || typeof file !== "object") {
|
|
386
|
+
throw new TypeError(`book files[${index}] must be an object`);
|
|
387
|
+
}
|
|
388
|
+
const path = stringOption(file.path);
|
|
389
|
+
if (path === undefined) {
|
|
390
|
+
throw new TypeError(`book files[${index}].path must be a non-empty string`);
|
|
391
|
+
}
|
|
392
|
+
return Object.freeze({ path, source: String(file.source ?? "") });
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function fontScaleOption(value) {
|
|
397
|
+
if (value === undefined || value === null) {
|
|
398
|
+
return undefined;
|
|
399
|
+
}
|
|
400
|
+
if (typeof value === "number") {
|
|
401
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
402
|
+
throw new TypeError("fontScale must be a positive finite number");
|
|
403
|
+
}
|
|
404
|
+
return Math.min(3.0, Math.max(0.5, value));
|
|
405
|
+
}
|
|
406
|
+
if (typeof value === "string") {
|
|
407
|
+
const trimmed = value.trim().toLowerCase();
|
|
408
|
+
switch (trimmed) {
|
|
409
|
+
case "xs":
|
|
410
|
+
case "x-small":
|
|
411
|
+
case "extra-small":
|
|
412
|
+
case "extrasmall":
|
|
413
|
+
case "tiny":
|
|
414
|
+
return 0.75;
|
|
415
|
+
case "sm":
|
|
416
|
+
case "small":
|
|
417
|
+
case "compact":
|
|
418
|
+
return 0.875;
|
|
419
|
+
case "md":
|
|
420
|
+
case "medium":
|
|
421
|
+
case "normal":
|
|
422
|
+
case "default":
|
|
423
|
+
case "regular":
|
|
424
|
+
case "standard":
|
|
425
|
+
return 1.0;
|
|
426
|
+
case "lg":
|
|
427
|
+
case "large":
|
|
428
|
+
case "comfortable":
|
|
429
|
+
return 1.125;
|
|
430
|
+
case "xl":
|
|
431
|
+
case "x-large":
|
|
432
|
+
case "extra-large":
|
|
433
|
+
case "extralarge":
|
|
434
|
+
return 1.25;
|
|
435
|
+
case "2xl":
|
|
436
|
+
case "xxl":
|
|
437
|
+
case "huge":
|
|
438
|
+
case "display":
|
|
439
|
+
return 1.5;
|
|
440
|
+
default:
|
|
441
|
+
break;
|
|
442
|
+
}
|
|
443
|
+
if (trimmed.endsWith("%")) {
|
|
444
|
+
const parsed = parseFloat(trimmed.slice(0, -1));
|
|
445
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
446
|
+
return Math.min(3.0, Math.max(0.5, parsed / 100));
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
if (trimmed.endsWith("rem")) {
|
|
450
|
+
const parsed = parseFloat(trimmed.slice(0, -3));
|
|
451
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
452
|
+
return Math.min(3.0, Math.max(0.5, parsed));
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (trimmed.endsWith("em")) {
|
|
456
|
+
const parsed = parseFloat(trimmed.slice(0, -2));
|
|
457
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
458
|
+
return Math.min(3.0, Math.max(0.5, parsed));
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (trimmed.endsWith("px")) {
|
|
462
|
+
const parsed = parseFloat(trimmed.slice(0, -2));
|
|
463
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
464
|
+
return Math.min(3.0, Math.max(0.5, parsed / 16));
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
if (trimmed.endsWith("pt")) {
|
|
468
|
+
const parsed = parseFloat(trimmed.slice(0, -2));
|
|
469
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
470
|
+
return Math.min(3.0, Math.max(0.5, parsed / 11));
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
const parsed = parseFloat(trimmed);
|
|
474
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
475
|
+
return Math.min(3.0, Math.max(0.5, parsed));
|
|
476
|
+
}
|
|
477
|
+
throw new TypeError(`unknown fontScale '${value}'. Valid choices: xs, sm, md, lg, xl, 2xl, or a number/percentage.`);
|
|
478
|
+
}
|
|
479
|
+
throw new TypeError("fontScale must be a number or string");
|
|
480
|
+
}
|
|
270
481
|
function pdfImagesOption(value) {
|
|
271
482
|
if (value === undefined || value === null) {
|
|
272
483
|
return [];
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@franken-suite/franken-markdown",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
4
4
|
"description": "Browser/WASM package for franken_markdown Markdown to HTML/PDF rendering.",
|
|
5
5
|
"type": "module",
|
|
6
|
-
"sideEffects":
|
|
6
|
+
"sideEffects": [
|
|
7
|
+
"./fmd-view.js"
|
|
8
|
+
],
|
|
7
9
|
"license": "LicenseRef-MIT-OpenAI-Anthropic-Rider",
|
|
8
10
|
"author": "Jeffrey Emanuel",
|
|
9
11
|
"homepage": "https://github.com/Dicklesworthstone/franken_markdown#readme",
|
|
@@ -35,6 +37,10 @@
|
|
|
35
37
|
".": {
|
|
36
38
|
"types": "./franken_markdown.d.ts",
|
|
37
39
|
"import": "./franken_markdown.js"
|
|
40
|
+
},
|
|
41
|
+
"./web-component": {
|
|
42
|
+
"types": "./fmd-view.d.ts",
|
|
43
|
+
"import": "./fmd-view.js"
|
|
38
44
|
}
|
|
39
45
|
},
|
|
40
46
|
"files": [
|
|
@@ -45,6 +51,8 @@
|
|
|
45
51
|
"pkg/franken_markdown.js",
|
|
46
52
|
"pkg/franken_markdown_bg.wasm",
|
|
47
53
|
"pkg/franken_markdown_bg.wasm.d.ts",
|
|
48
|
-
"pkg/franken_markdown.d.ts"
|
|
54
|
+
"pkg/franken_markdown.d.ts",
|
|
55
|
+
"fmd-view.js",
|
|
56
|
+
"fmd-view.d.ts"
|
|
49
57
|
]
|
|
50
58
|
}
|