feat(pdf): add configurable PDF export and improve print rendering
This commit is contained in:
+32
-3
@@ -360,14 +360,43 @@ function renderTableOfContentsList(nodes) {
|
||||
}
|
||||
|
||||
|
||||
function firstRenderedTextRect(element) {
|
||||
const walker = document.createTreeWalker(element, NodeFilter.SHOW_TEXT);
|
||||
let node;
|
||||
while ((node = walker.nextNode())) {
|
||||
const match = node.nodeValue?.match(/\S/);
|
||||
if (!match) continue;
|
||||
const range = document.createRange();
|
||||
const codePoint = node.nodeValue.codePointAt(match.index);
|
||||
const characterLength = codePoint > 0xffff ? 2 : 1;
|
||||
range.setStart(node, match.index);
|
||||
range.setEnd(node, match.index + characterLength);
|
||||
const rect = range.getClientRects()[0];
|
||||
range.detach?.();
|
||||
if (rect?.width || rect?.height) return rect;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
export function alignPreviewLineNumbers(root) {
|
||||
if (!root) return;
|
||||
const styles = getComputedStyle(root);
|
||||
const targetLeft = parseFloat(styles.paddingLeft || "0") - 50;
|
||||
const rootLeft = root.getBoundingClientRect().left;
|
||||
root.querySelectorAll(".preview-source-line").forEach(line => {
|
||||
const lineLeft = line.getBoundingClientRect().left - rootLeft + root.scrollLeft;
|
||||
const lineRect = line.getBoundingClientRect();
|
||||
const lineLeft = lineRect.left - rootLeft + root.scrollLeft;
|
||||
line.style.setProperty("--preview-line-left", `${targetLeft - lineLeft}px`);
|
||||
|
||||
const textRect = firstRenderedTextRect(line);
|
||||
const lineStyles = getComputedStyle(line);
|
||||
const gutterStyles = getComputedStyle(line, "::before");
|
||||
const gutterLineHeight = parseFloat(gutterStyles.lineHeight) || parseFloat(gutterStyles.fontSize) || 0;
|
||||
const textLineHeight = textRect?.height || 0;
|
||||
const borderTop = parseFloat(lineStyles.borderTopWidth) || 0;
|
||||
const textTop = textRect ? textRect.top - lineRect.top - borderTop : 0;
|
||||
const gutterTop = Math.max(0, textTop + (textLineHeight - gutterLineHeight) / 2);
|
||||
line.style.setProperty("--preview-line-top", `${gutterTop}px`);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -399,7 +428,7 @@ export function renderMarkdown(source, lineOffset = 0) {
|
||||
if (lang === "mermaid") {
|
||||
html += `<div class="mermaid preview-source-line" data-source-line="${codeStart + lineOffset + 1}">${body}</div>`;
|
||||
} else if (codeLineStart !== null) {
|
||||
const numbered = body.split("\n").map((line, index) => `<span class="code-line" data-line="${codeLineStart + index}">${line || " "}</span>`).join("\n");
|
||||
const numbered = body.split("\n").map((line, index) => `<span class="code-line" data-line="${codeLineStart + index}">${line || " "}</span>`).join("");
|
||||
const languageClass = lang ? ` class="language-${escapeHtml(lang)}"` : "";
|
||||
html += `<pre${attrs(codeStart, false, "", "", lineOffset).replace(' class="', ' class="code-with-lines ')}><code${languageClass}>${numbered}</code></pre>`;
|
||||
} else {
|
||||
@@ -524,7 +553,7 @@ export function renderMarkdown(source, lineOffset = 0) {
|
||||
html += `<dd data-source-line="${index + lineOffset + 1}">${inline(lines[index].replace(/^:\s+/, ""))}</dd>`;
|
||||
}
|
||||
html += `</dl>`;
|
||||
} else if (/^---+$/.test(line.trim())) html += `<hr${attrs(index, false, "", "", lineOffset)}>`;
|
||||
} else if (/^---+$/.test(line.trim())) html += `<hr${attrs(index, false, "", "", lineOffset).replace(' class="', ' class="page-break-marker ')} data-page-break="true">`;
|
||||
else if (line.startsWith("> ")) html += `<blockquote${attrs(index, true, "> ", "", lineOffset)}>${inline(line.slice(2))}</blockquote>`;
|
||||
else if (line.trim()) html += `<p${attrs(index, true, "", "", lineOffset)}>${inline(line)}</p>`;
|
||||
else html += `<div${attrs(index, true, "", "", lineOffset)}><br></div>`;
|
||||
|
||||
Reference in New Issue
Block a user