@barzhsieh/nuxt-content-mermaid 3.0.3 → 3.0.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/dist/module.json +1 -1
- package/dist/runtime/built-in-renderer/BuiltInRenderer.vue +4 -2
- package/dist/runtime/direct-mermaid-config.js +1 -1
- package/dist/runtime/png-font-embedding.d.ts +7 -0
- package/dist/runtime/png-font-embedding.js +305 -0
- package/dist/runtime/png-rasterizer.js +8 -2
- package/package.json +4 -4
package/dist/module.json
CHANGED
|
@@ -98,7 +98,8 @@ const hasError = ref(false);
|
|
|
98
98
|
const errorContent = shallowRef(null);
|
|
99
99
|
const committedExportSnapshot = shallowRef(null);
|
|
100
100
|
const isCommittedOutputSvg = ref(false);
|
|
101
|
-
const
|
|
101
|
+
const generatedDownloadDisclosureId = `ncm-download-${useId()}`;
|
|
102
|
+
const downloadDisclosureId = ref();
|
|
102
103
|
const isDownloadDisclosureOpen = ref(false);
|
|
103
104
|
const isPngDownloadPending = ref(false);
|
|
104
105
|
const decodedCode = computed(() => props.code ? decodeURIComponent(props.code) : "");
|
|
@@ -529,6 +530,7 @@ async function copyToClipboard(text) {
|
|
|
529
530
|
return copied;
|
|
530
531
|
}
|
|
531
532
|
onMounted(() => {
|
|
533
|
+
downloadDisclosureId.value = generatedDownloadDisclosureId;
|
|
532
534
|
if (!isEnabled) return;
|
|
533
535
|
document.addEventListener("pointerdown", handleDocumentPointerDown);
|
|
534
536
|
if (!mermaidDefinition.value && mermaidContainer.value)
|
|
@@ -619,7 +621,7 @@ const { cursorVariables } = useMermaidCursors(iconSize, expandEnabled);
|
|
|
619
621
|
<IconDownload :size="iconSize" />
|
|
620
622
|
</button>
|
|
621
623
|
<div
|
|
622
|
-
v-
|
|
624
|
+
v-show="isDownloadDisclosureOpen"
|
|
623
625
|
:id="downloadDisclosureId"
|
|
624
626
|
ref="downloadDisclosure"
|
|
625
627
|
class="ncm-download-disclosure"
|
|
@@ -37,7 +37,7 @@ function validateDirectValue(value, path, ancestors) {
|
|
|
37
37
|
if (isFunctionCapability(value, path) || isOpaqueObjectCapability(value, path))
|
|
38
38
|
return;
|
|
39
39
|
if (typeof value === "function")
|
|
40
|
-
invalidDirectConfig(path, "a supported capability at its exact Mermaid 11.
|
|
40
|
+
invalidDirectConfig(path, "a supported capability at its exact Mermaid 11.17.0 or DOMPurify 3.4.13 path");
|
|
41
41
|
if (typeof value === "symbol" || typeof value === "bigint")
|
|
42
42
|
invalidDirectConfig(path, "structural data");
|
|
43
43
|
if (value === null || typeof value !== "object") return;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { getFontEmbedCSS } from 'html-to-image';
|
|
2
|
+
type FontEmbeddingOptions = NonNullable<Parameters<typeof getFontEmbedCSS>[1]>;
|
|
3
|
+
/** @internal */
|
|
4
|
+
export declare function supportsScopedPngFontEmbedding(document: Document): boolean;
|
|
5
|
+
/** @internal */
|
|
6
|
+
export declare function getScopedPngFontEmbedCSS(captureNode: HTMLElement, options: FontEmbeddingOptions): Promise<string>;
|
|
7
|
+
export {};
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { getFontEmbedCSS } from "html-to-image";
|
|
2
|
+
const ERROR_PREFIX = "[nuxt-content-mermaid]";
|
|
3
|
+
const CSS_IMPORT_RULE = 3;
|
|
4
|
+
const CSS_FONT_FACE_RULE = 5;
|
|
5
|
+
function normalizeFontFamily(value) {
|
|
6
|
+
const trimmed = value.trim();
|
|
7
|
+
const quote = trimmed[0];
|
|
8
|
+
const unquoted = (quote === '"' || quote === "'") && trimmed.at(-1) === quote ? trimmed.slice(1, -1) : trimmed;
|
|
9
|
+
return unquoted.replace(/\\([\\'" ])/g, "$1").toLocaleLowerCase();
|
|
10
|
+
}
|
|
11
|
+
function splitFontFamilies(value) {
|
|
12
|
+
const families = [];
|
|
13
|
+
let current = "";
|
|
14
|
+
let quote = "";
|
|
15
|
+
let escaped = false;
|
|
16
|
+
for (const character of value) {
|
|
17
|
+
if (escaped) {
|
|
18
|
+
current += character;
|
|
19
|
+
escaped = false;
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (character === "\\") {
|
|
23
|
+
current += character;
|
|
24
|
+
escaped = true;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (quote) {
|
|
28
|
+
current += character;
|
|
29
|
+
if (character === quote) quote = "";
|
|
30
|
+
continue;
|
|
31
|
+
}
|
|
32
|
+
if (character === '"' || character === "'") {
|
|
33
|
+
current += character;
|
|
34
|
+
quote = character;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (character === ",") {
|
|
38
|
+
families.push(normalizeFontFamily(current));
|
|
39
|
+
current = "";
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
current += character;
|
|
43
|
+
}
|
|
44
|
+
families.push(normalizeFontFamily(current));
|
|
45
|
+
return families.filter(Boolean);
|
|
46
|
+
}
|
|
47
|
+
function directTextContent(element) {
|
|
48
|
+
return Array.from(element.childNodes).filter((node) => node.nodeType === 3).map((node) => node.textContent ?? "").join("");
|
|
49
|
+
}
|
|
50
|
+
function collectFontUsages(captureNode) {
|
|
51
|
+
const view = captureNode.ownerDocument.defaultView;
|
|
52
|
+
if (!view) return [];
|
|
53
|
+
const elements = [captureNode, ...Array.from(captureNode.querySelectorAll("*"))];
|
|
54
|
+
return elements.flatMap((element) => {
|
|
55
|
+
const text = directTextContent(element);
|
|
56
|
+
if (!text.trim()) return [];
|
|
57
|
+
const style = view.getComputedStyle(element);
|
|
58
|
+
const families = splitFontFamilies(style.fontFamily);
|
|
59
|
+
if (families.length === 0) return [];
|
|
60
|
+
return [{
|
|
61
|
+
families,
|
|
62
|
+
style: style.fontStyle || "normal",
|
|
63
|
+
weight: style.fontWeight || "400",
|
|
64
|
+
stretch: style.fontStretch || "normal",
|
|
65
|
+
text
|
|
66
|
+
}];
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function normalizeFontStyle(value) {
|
|
70
|
+
return value.trim().toLocaleLowerCase().split(/\s+/)[0] || "normal";
|
|
71
|
+
}
|
|
72
|
+
function parseWeight(value) {
|
|
73
|
+
const normalized = value.trim().toLocaleLowerCase();
|
|
74
|
+
if (normalized === "normal") return 400;
|
|
75
|
+
if (normalized === "bold") return 700;
|
|
76
|
+
const parsed = Number.parseFloat(normalized);
|
|
77
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
78
|
+
}
|
|
79
|
+
const stretchKeywords = {
|
|
80
|
+
"ultra-condensed": 50,
|
|
81
|
+
"extra-condensed": 62.5,
|
|
82
|
+
"condensed": 75,
|
|
83
|
+
"semi-condensed": 87.5,
|
|
84
|
+
"normal": 100,
|
|
85
|
+
"semi-expanded": 112.5,
|
|
86
|
+
"expanded": 125,
|
|
87
|
+
"extra-expanded": 150,
|
|
88
|
+
"ultra-expanded": 200
|
|
89
|
+
};
|
|
90
|
+
function parseStretch(value) {
|
|
91
|
+
const normalized = value.trim().toLocaleLowerCase();
|
|
92
|
+
if (stretchKeywords[normalized] !== void 0) return stretchKeywords[normalized];
|
|
93
|
+
const parsed = Number.parseFloat(normalized);
|
|
94
|
+
return Number.isFinite(parsed) ? parsed : null;
|
|
95
|
+
}
|
|
96
|
+
function descriptorBounds(value, parser, fallback) {
|
|
97
|
+
const parts = (value.trim() || fallback).split(/\s+/);
|
|
98
|
+
const first = parser(parts[0]);
|
|
99
|
+
const last = parser(parts[1] ?? parts[0]);
|
|
100
|
+
return first === null || last === null ? null : [Math.min(first, last), Math.max(first, last)];
|
|
101
|
+
}
|
|
102
|
+
function numericDescriptorContains(descriptor, actual, parser, fallback) {
|
|
103
|
+
const bounds = descriptorBounds(descriptor, parser, fallback);
|
|
104
|
+
const value = parser(actual);
|
|
105
|
+
return bounds === null || value === null ? (descriptor.trim() || fallback).toLocaleLowerCase() === actual.trim().toLocaleLowerCase() : value >= bounds[0] && value <= bounds[1];
|
|
106
|
+
}
|
|
107
|
+
function numericDescriptorsEqual(left, right, parser, fallback) {
|
|
108
|
+
const leftBounds = descriptorBounds(left, parser, fallback);
|
|
109
|
+
const rightBounds = descriptorBounds(right, parser, fallback);
|
|
110
|
+
if (!leftBounds || !rightBounds)
|
|
111
|
+
return (left.trim() || fallback).toLocaleLowerCase() === (right.trim() || fallback).toLocaleLowerCase();
|
|
112
|
+
return leftBounds[0] === rightBounds[0] && leftBounds[1] === rightBounds[1];
|
|
113
|
+
}
|
|
114
|
+
function parseUnicodeRanges(value) {
|
|
115
|
+
const source = value.trim() || "U+0-10FFFF";
|
|
116
|
+
const ranges = [];
|
|
117
|
+
for (const rawPart of source.split(",")) {
|
|
118
|
+
const part = rawPart.trim().toLocaleUpperCase();
|
|
119
|
+
const match = /^U\+([0-9A-F?]+)(?:-([0-9A-F]+))?$/.exec(part);
|
|
120
|
+
if (!match) return null;
|
|
121
|
+
const startToken = match[1];
|
|
122
|
+
const endToken = match[2];
|
|
123
|
+
const start = Number.parseInt(startToken.replaceAll("?", "0"), 16);
|
|
124
|
+
const end = Number.parseInt(
|
|
125
|
+
endToken ?? startToken.replaceAll("?", "F"),
|
|
126
|
+
16
|
|
127
|
+
);
|
|
128
|
+
if (!Number.isFinite(start) || !Number.isFinite(end)) return null;
|
|
129
|
+
ranges.push([start, end]);
|
|
130
|
+
}
|
|
131
|
+
return ranges;
|
|
132
|
+
}
|
|
133
|
+
function unicodeRangeKey(value) {
|
|
134
|
+
const ranges = parseUnicodeRanges(value);
|
|
135
|
+
return ranges ? ranges.map(([start, end]) => `${start.toString(16)}-${end.toString(16)}`).join(",") : value.replace(/\s+/g, "").toLocaleLowerCase();
|
|
136
|
+
}
|
|
137
|
+
function unicodeRangeContainsText(value, text) {
|
|
138
|
+
const ranges = parseUnicodeRanges(value);
|
|
139
|
+
if (!ranges) return true;
|
|
140
|
+
return Array.from(text).some((character) => {
|
|
141
|
+
const codePoint = character.codePointAt(0);
|
|
142
|
+
return ranges.some(([start, end]) => codePoint >= start && codePoint <= end);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
function fontFaceMatchesUsage(face, usage) {
|
|
146
|
+
return usage.families.includes(normalizeFontFamily(face.family)) && normalizeFontStyle(face.style) === normalizeFontStyle(usage.style) && numericDescriptorContains(face.weight, usage.weight, parseWeight, "normal") && numericDescriptorContains(face.stretch, usage.stretch, parseStretch, "normal") && unicodeRangeContainsText(face.unicodeRange, usage.text);
|
|
147
|
+
}
|
|
148
|
+
function ruleMatchesUsage(rule, usage) {
|
|
149
|
+
const style = rule.style;
|
|
150
|
+
return usage.families.includes(normalizeFontFamily(style.getPropertyValue("font-family"))) && normalizeFontStyle(style.getPropertyValue("font-style") || "normal") === normalizeFontStyle(usage.style) && numericDescriptorContains(style.getPropertyValue("font-weight"), usage.weight, parseWeight, "normal") && numericDescriptorContains(style.getPropertyValue("font-stretch"), usage.stretch, parseStretch, "normal") && unicodeRangeContainsText(style.getPropertyValue("unicode-range"), usage.text);
|
|
151
|
+
}
|
|
152
|
+
function ruleMatchesFontFace(rule, face) {
|
|
153
|
+
const style = rule.style;
|
|
154
|
+
return normalizeFontFamily(style.getPropertyValue("font-family")) === normalizeFontFamily(face.family) && normalizeFontStyle(style.getPropertyValue("font-style") || "normal") === normalizeFontStyle(face.style) && numericDescriptorsEqual(style.getPropertyValue("font-weight"), face.weight, parseWeight, "normal") && numericDescriptorsEqual(style.getPropertyValue("font-stretch"), face.stretch, parseStretch, "normal") && unicodeRangeKey(style.getPropertyValue("unicode-range")) === unicodeRangeKey(face.unicodeRange);
|
|
155
|
+
}
|
|
156
|
+
function collectFontRules(document) {
|
|
157
|
+
const candidates = [];
|
|
158
|
+
const unreadableStylesheets = [];
|
|
159
|
+
const visited = /* @__PURE__ */ new Set();
|
|
160
|
+
const visitRules = (rules, importHref) => {
|
|
161
|
+
for (const rule of Array.from(rules)) {
|
|
162
|
+
if (rule.type === CSS_FONT_FACE_RULE) {
|
|
163
|
+
candidates.push({ rule, importHref });
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (rule.type === CSS_IMPORT_RULE) {
|
|
167
|
+
const importRule = rule;
|
|
168
|
+
const href = importRule.href || importRule.styleSheet?.href || "<inline stylesheet>";
|
|
169
|
+
if (importRule.styleSheet)
|
|
170
|
+
visitStylesheet(importRule.styleSheet, href);
|
|
171
|
+
else
|
|
172
|
+
unreadableStylesheets.push(href);
|
|
173
|
+
continue;
|
|
174
|
+
}
|
|
175
|
+
if ("cssRules" in rule) {
|
|
176
|
+
try {
|
|
177
|
+
visitRules(rule.cssRules, importHref);
|
|
178
|
+
} catch {
|
|
179
|
+
unreadableStylesheets.push(rule.parentStyleSheet?.href ?? "<inline stylesheet>");
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
};
|
|
184
|
+
const visitStylesheet = (stylesheet, importHref) => {
|
|
185
|
+
if (visited.has(stylesheet)) return;
|
|
186
|
+
visited.add(stylesheet);
|
|
187
|
+
try {
|
|
188
|
+
visitRules(stylesheet.cssRules, importHref);
|
|
189
|
+
} catch {
|
|
190
|
+
unreadableStylesheets.push(stylesheet.href ?? importHref ?? "<inline stylesheet>");
|
|
191
|
+
}
|
|
192
|
+
};
|
|
193
|
+
for (const stylesheet of Array.from(document.styleSheets))
|
|
194
|
+
visitStylesheet(stylesheet);
|
|
195
|
+
return { candidates, unreadableStylesheets };
|
|
196
|
+
}
|
|
197
|
+
function resolveFontSourceUrls(value, baseUrl) {
|
|
198
|
+
const lowerValue = value.toLocaleLowerCase();
|
|
199
|
+
let result = "";
|
|
200
|
+
let searchFrom = 0;
|
|
201
|
+
while (searchFrom < value.length) {
|
|
202
|
+
const urlIndex = lowerValue.indexOf("url", searchFrom);
|
|
203
|
+
if (urlIndex < 0) break;
|
|
204
|
+
const previous = value[urlIndex - 1];
|
|
205
|
+
const afterUrl = urlIndex + 3;
|
|
206
|
+
if (previous !== void 0 && /[\w-]/.test(previous) || value[afterUrl] !== void 0 && /[\w-]/.test(value[afterUrl])) {
|
|
207
|
+
result += value.slice(searchFrom, afterUrl);
|
|
208
|
+
searchFrom = afterUrl;
|
|
209
|
+
continue;
|
|
210
|
+
}
|
|
211
|
+
let cursor = afterUrl;
|
|
212
|
+
while (value[cursor] !== void 0 && /[\t\n\f\r ]/.test(value[cursor]))
|
|
213
|
+
cursor++;
|
|
214
|
+
if (value[cursor] !== "(") {
|
|
215
|
+
result += value.slice(searchFrom, afterUrl);
|
|
216
|
+
searchFrom = afterUrl;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
219
|
+
cursor++;
|
|
220
|
+
while (value[cursor] !== void 0 && /[\t\n\f\r ]/.test(value[cursor]))
|
|
221
|
+
cursor++;
|
|
222
|
+
const quote = value[cursor] === '"' || value[cursor] === "'" ? value[cursor] : null;
|
|
223
|
+
if (quote) cursor++;
|
|
224
|
+
const valueStart = cursor;
|
|
225
|
+
const valueEnd = quote ? value.indexOf(quote, valueStart) : value.indexOf(")", valueStart);
|
|
226
|
+
if (valueEnd < 0) break;
|
|
227
|
+
const rawUrl = value.slice(valueStart, valueEnd).trim();
|
|
228
|
+
cursor = valueEnd + 1;
|
|
229
|
+
if (quote) {
|
|
230
|
+
while (value[cursor] !== void 0 && /[\t\n\f\r ]/.test(value[cursor]))
|
|
231
|
+
cursor++;
|
|
232
|
+
if (value[cursor] !== ")") break;
|
|
233
|
+
cursor++;
|
|
234
|
+
}
|
|
235
|
+
result += value.slice(searchFrom, urlIndex);
|
|
236
|
+
result += `url("${new URL(rawUrl, baseUrl).href}")`;
|
|
237
|
+
searchFrom = cursor;
|
|
238
|
+
}
|
|
239
|
+
return result + value.slice(searchFrom);
|
|
240
|
+
}
|
|
241
|
+
function serializeFontRule(candidate, document) {
|
|
242
|
+
const descriptors = [];
|
|
243
|
+
const style = candidate.rule.style;
|
|
244
|
+
const baseUrl = candidate.rule.parentStyleSheet?.href ?? document.baseURI;
|
|
245
|
+
for (let index = 0; index < style.length; index++) {
|
|
246
|
+
const name = style.item(index);
|
|
247
|
+
const value = name === "src" ? resolveFontSourceUrls(style.getPropertyValue(name), baseUrl) : style.getPropertyValue(name);
|
|
248
|
+
descriptors.push([name, value, style.getPropertyPriority(name)]);
|
|
249
|
+
}
|
|
250
|
+
descriptors.sort(([left], [right]) => left.localeCompare(right));
|
|
251
|
+
return `@font-face { ${descriptors.map(
|
|
252
|
+
([name, value, priority]) => `${name}: ${value}${priority ? ` !${priority}` : ""};`
|
|
253
|
+
).join(" ")} }`;
|
|
254
|
+
}
|
|
255
|
+
export function supportsScopedPngFontEmbedding(document) {
|
|
256
|
+
return Boolean(
|
|
257
|
+
document.defaultView && typeof document.implementation?.createHTMLDocument === "function" && document.fonts && typeof document.fonts[Symbol.iterator] === "function"
|
|
258
|
+
);
|
|
259
|
+
}
|
|
260
|
+
export async function getScopedPngFontEmbedCSS(captureNode, options) {
|
|
261
|
+
const document = captureNode.ownerDocument;
|
|
262
|
+
const usages = collectFontUsages(captureNode);
|
|
263
|
+
const activeFaces = Array.from(document.fonts).filter((face) => face.status !== "unloaded").filter((face) => usages.some((usage) => fontFaceMatchesUsage(face, usage)));
|
|
264
|
+
if (activeFaces.length === 0) return "";
|
|
265
|
+
const { candidates, unreadableStylesheets } = collectFontRules(document);
|
|
266
|
+
const matchingCandidates = candidates.filter(
|
|
267
|
+
(candidate) => usages.some((usage) => ruleMatchesUsage(candidate.rule, usage)) && activeFaces.some((face) => ruleMatchesFontFace(candidate.rule, face))
|
|
268
|
+
);
|
|
269
|
+
for (const face of activeFaces) {
|
|
270
|
+
if (matchingCandidates.some((candidate) => ruleMatchesFontFace(candidate.rule, face))) continue;
|
|
271
|
+
const unreadable = unreadableStylesheets[0];
|
|
272
|
+
if (unreadable) {
|
|
273
|
+
throw new Error(
|
|
274
|
+
`${ERROR_PREFIX} Cannot read stylesheet for PNG rasterization: ${unreadable}`
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
throw new Error(
|
|
278
|
+
`${ERROR_PREFIX} Cannot access @font-face for PNG rasterization: ${face.family}`
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
const uniqueRules = /* @__PURE__ */ new Map();
|
|
282
|
+
const candidatesByPreference = [...matchingCandidates].sort((left, right) => Number(Boolean(left.importHref)) - Number(Boolean(right.importHref)));
|
|
283
|
+
for (const candidate of candidatesByPreference) {
|
|
284
|
+
const cssText = serializeFontRule(candidate, document);
|
|
285
|
+
if (!uniqueRules.has(cssText)) uniqueRules.set(cssText, candidate);
|
|
286
|
+
}
|
|
287
|
+
for (const candidate of uniqueRules.values()) {
|
|
288
|
+
if (!candidate.importHref) continue;
|
|
289
|
+
throw new Error(
|
|
290
|
+
`${ERROR_PREFIX} CSS @import is not supported for PNG rasterization: ${candidate.importHref}`
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
const isolatedDocument = document.implementation.createHTMLDocument("");
|
|
294
|
+
const styleElement = isolatedDocument.createElement("style");
|
|
295
|
+
isolatedDocument.head.appendChild(styleElement);
|
|
296
|
+
const stylesheet = styleElement.sheet;
|
|
297
|
+
if (!stylesheet) {
|
|
298
|
+
throw new Error(`${ERROR_PREFIX} Cannot create isolated stylesheet for PNG rasterization`);
|
|
299
|
+
}
|
|
300
|
+
for (const cssText of uniqueRules.keys())
|
|
301
|
+
stylesheet.insertRule(cssText, stylesheet.cssRules.length);
|
|
302
|
+
const embeddingNode = isolatedDocument.createElement("div");
|
|
303
|
+
isolatedDocument.body.appendChild(embeddingNode);
|
|
304
|
+
return getFontEmbedCSS(embeddingNode, options);
|
|
305
|
+
}
|
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import { getFontEmbedCSS, toBlob } from "html-to-image";
|
|
2
|
+
import {
|
|
3
|
+
getScopedPngFontEmbedCSS,
|
|
4
|
+
supportsScopedPngFontEmbedding
|
|
5
|
+
} from "./png-font-embedding.js";
|
|
2
6
|
const ERROR_PREFIX = "[nuxt-content-mermaid]";
|
|
3
7
|
function validateDimensions(width, height) {
|
|
4
8
|
if (!Number.isFinite(width) || width <= 0 || !Number.isFinite(height) || height <= 0) {
|
|
@@ -112,7 +116,8 @@ export async function rasterizePngSnapshot(input) {
|
|
|
112
116
|
validateDimensions(input.width, input.height);
|
|
113
117
|
const document = input.svg.ownerDocument;
|
|
114
118
|
await document.fonts?.ready;
|
|
115
|
-
|
|
119
|
+
const useScopedFontEmbedding = supportsScopedPngFontEmbedding(document);
|
|
120
|
+
if (!useScopedFontEmbedding) assertStylesheetsReadable(document);
|
|
116
121
|
const captureOptions = {
|
|
117
122
|
width: input.width,
|
|
118
123
|
height: input.height,
|
|
@@ -127,7 +132,8 @@ export async function rasterizePngSnapshot(input) {
|
|
|
127
132
|
input.height
|
|
128
133
|
);
|
|
129
134
|
try {
|
|
130
|
-
|
|
135
|
+
if (useScopedFontEmbedding) await document.fonts.ready;
|
|
136
|
+
const fontEmbedCSS = useScopedFontEmbedding ? await getScopedPngFontEmbedCSS(captureNode, captureOptions) : await getFontEmbedCSS(captureNode, captureOptions);
|
|
131
137
|
assertFontsEmbedded(fontEmbedCSS);
|
|
132
138
|
const blob = await toBlob(captureNode, {
|
|
133
139
|
...captureOptions,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@barzhsieh/nuxt-content-mermaid",
|
|
3
|
-
"version": "3.0.
|
|
3
|
+
"version": "3.0.4",
|
|
4
4
|
"description": "A Nuxt module designed for integrating Mermaid with `@nuxt/content`. It automatically converts `mermaid` code blocks in Markdown into responsive chart components, and supports lazy loading and dark/light theme switching.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"nuxt",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"defu": "^6.1.7",
|
|
53
53
|
"destr": "^2.0.5",
|
|
54
54
|
"html-to-image": "1.11.11",
|
|
55
|
-
"mermaid": "~11.
|
|
55
|
+
"mermaid": "~11.17.0",
|
|
56
56
|
"yaml": "^2.9.0"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
@@ -74,8 +74,8 @@
|
|
|
74
74
|
"nuxt": "4.5.2",
|
|
75
75
|
"playwright": "^1.62.1",
|
|
76
76
|
"typescript": "~5.9.3",
|
|
77
|
-
"vitest": "^4.1.
|
|
78
|
-
"vue-tsc": "^3.3.
|
|
77
|
+
"vitest": "^4.1.11",
|
|
78
|
+
"vue-tsc": "^3.3.10",
|
|
79
79
|
"yaml": "^2.9.0"
|
|
80
80
|
},
|
|
81
81
|
"volta": {
|