mirror of
https://github.com/youfoundjk/TeXcore.git
synced 2026-07-22 07:33:31 +00:00
Fix tikz for PDF printing
This commit is contained in:
parent
f1d2e81e85
commit
4fb6142978
2 changed files with 236 additions and 117 deletions
|
|
@ -43,15 +43,28 @@ const context = await esbuild.context({
|
|||
minify: prod,
|
||||
sourcemap: prod ? false : "inline",
|
||||
treeShaking: true,
|
||||
outfile: "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/main.js", // Ensure this path is correct for your setup
|
||||
outfile: "main.js",
|
||||
});
|
||||
|
||||
if (prod) {
|
||||
await context.rebuild();
|
||||
try {
|
||||
fs.copyFileSync("main.js", "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/main.js");
|
||||
} catch (e) {
|
||||
console.warn("Could not copy main.js to dev-vault:", e.message);
|
||||
}
|
||||
process.exit(0);
|
||||
} else {
|
||||
await context.watch();
|
||||
}
|
||||
|
||||
fs.copyFileSync("src/styles/main.css", "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/styles.css");
|
||||
fs.copyFileSync("./manifest.json", "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/manifest.json");
|
||||
try {
|
||||
fs.copyFileSync("src/styles/main.css", "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/styles.css");
|
||||
} catch (e) {
|
||||
console.warn("Could not copy main.css to dev-vault:", e.message);
|
||||
}
|
||||
try {
|
||||
fs.copyFileSync("./manifest.json", "../plugin-full-calendar/obsidian-dev-vault/.obsidian/plugins/Latex-like-equations/manifest.json");
|
||||
} catch (e) {
|
||||
console.warn("Could not copy manifest.json to dev-vault:", e.message);
|
||||
}
|
||||
|
|
@ -27,6 +27,126 @@ function formatWidth(w: string): string {
|
|||
return w;
|
||||
}
|
||||
|
||||
function splitParagraphAtNodeBoundary(p: HTMLParagraphElement, delimiterNode: Node) {
|
||||
const parent = p.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
const newP = document.createElement("p");
|
||||
for (let attr of Array.from(p.attributes)) {
|
||||
newP.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
|
||||
let nextNode = delimiterNode.nextSibling as Node;
|
||||
while (nextNode) {
|
||||
const toMove = nextNode;
|
||||
nextNode = nextNode.nextSibling as Node;
|
||||
newP.appendChild(toMove);
|
||||
}
|
||||
|
||||
if (p.nextSibling) {
|
||||
parent.insertBefore(newP, p.nextSibling);
|
||||
} else {
|
||||
parent.appendChild(newP);
|
||||
}
|
||||
|
||||
const cleanupBr = (el: HTMLElement) => {
|
||||
while (el.firstChild && el.firstChild.nodeName.toLowerCase() === "br") el.removeChild(el.firstChild);
|
||||
while (el.lastChild && el.lastChild.nodeName.toLowerCase() === "br") el.removeChild(el.lastChild);
|
||||
};
|
||||
cleanupBr(p);
|
||||
cleanupBr(newP);
|
||||
}
|
||||
|
||||
function preprocessContainerRows(container: HTMLElement) {
|
||||
let mutated = true;
|
||||
while (mutated) {
|
||||
mutated = false;
|
||||
const paragraphs = Array.from(container.querySelectorAll("p"));
|
||||
|
||||
for (const p of paragraphs) {
|
||||
if (!p.parentElement) continue;
|
||||
|
||||
const childNodes = Array.from(p.childNodes);
|
||||
for (let i = 0; i < childNodes.length; i++) {
|
||||
const node = childNodes[i];
|
||||
if (node.nodeType !== Node.TEXT_NODE) continue;
|
||||
|
||||
const text = node.textContent || "";
|
||||
const lines = text.split("\n");
|
||||
let offset = 0;
|
||||
|
||||
for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {
|
||||
const line = lines[lineIdx].trim();
|
||||
const isStart = line.startsWith(";;;row");
|
||||
const isDelimiter = line === ";;";
|
||||
const isClose = line === ";;;";
|
||||
|
||||
if (isStart || isDelimiter || isClose) {
|
||||
const lineStartIdx = text.indexOf(line, offset);
|
||||
if (lineStartIdx !== -1) {
|
||||
const textNode = node as Text;
|
||||
|
||||
// Split after the delimiter line first
|
||||
const delimEndIdx = lineStartIdx + line.length;
|
||||
if (delimEndIdx < text.length) {
|
||||
textNode.splitText(delimEndIdx);
|
||||
}
|
||||
|
||||
// Split before the delimiter line
|
||||
let delimNode: Node = textNode;
|
||||
if (lineStartIdx > 0) {
|
||||
delimNode = textNode.splitText(lineStartIdx);
|
||||
}
|
||||
|
||||
// Split the paragraph before the delimiter node if it's not the first child
|
||||
if (delimNode.previousSibling) {
|
||||
const newP = document.createElement("p");
|
||||
for (let attr of Array.from(p.attributes)) {
|
||||
newP.setAttribute(attr.name, attr.value);
|
||||
}
|
||||
|
||||
let curr = delimNode;
|
||||
while (curr) {
|
||||
const next = curr.nextSibling;
|
||||
newP.appendChild(curr);
|
||||
curr = next as Node;
|
||||
}
|
||||
|
||||
if (p.nextSibling) {
|
||||
p.parentElement.insertBefore(newP, p.nextSibling);
|
||||
} else {
|
||||
p.parentElement.appendChild(newP);
|
||||
}
|
||||
|
||||
const cleanupBr = (el: HTMLElement) => {
|
||||
while (el.firstChild && el.firstChild.nodeName.toLowerCase() === "br") el.removeChild(el.firstChild);
|
||||
while (el.lastChild && el.lastChild.nodeName.toLowerCase() === "br") el.removeChild(el.lastChild);
|
||||
};
|
||||
cleanupBr(p);
|
||||
cleanupBr(newP);
|
||||
|
||||
mutated = true;
|
||||
break;
|
||||
}
|
||||
|
||||
// Split the paragraph after the delimiter node if there are subsequent siblings
|
||||
if (delimNode.nextSibling) {
|
||||
splitParagraphAtNodeBoundary(p, delimNode);
|
||||
mutated = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
offset += lines[lineIdx].length + 1;
|
||||
}
|
||||
if (mutated) break;
|
||||
}
|
||||
if (mutated) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function tightenColumn(colEl: HTMLElement) {
|
||||
colEl.style.setProperty("margin-top", "0", "important");
|
||||
colEl.style.setProperty("margin-bottom", "0", "important");
|
||||
|
|
@ -74,126 +194,114 @@ function tightenColumn(colEl: HTMLElement) {
|
|||
// ==========================================
|
||||
export const createRowLayoutProcessor = (plugin: LatexReferencer): MarkdownPostProcessor => {
|
||||
return (el, ctx) => {
|
||||
const file = plugin.app.vault.getAbstractFileByPath(ctx.sourcePath);
|
||||
if (!(file instanceof TFile)) return;
|
||||
// Find the main container (e.g. .markdown-preview-view or .markdown-preview-section)
|
||||
let cont: HTMLElement | null = el.parentElement;
|
||||
while (cont &&
|
||||
!cont.classList.contains("markdown-preview-view") &&
|
||||
!cont.classList.contains("markdown-rendered") &&
|
||||
!cont.classList.contains("markdown-preview-section")) {
|
||||
cont = cont.parentElement;
|
||||
}
|
||||
if (!cont) {
|
||||
cont = el;
|
||||
}
|
||||
|
||||
// Split merged paragraphs to isolate delimiters into standalone blocks
|
||||
preprocessContainerRows(cont);
|
||||
|
||||
// Find if this element itself is a row start paragraph, or contains one
|
||||
const startP = el.tagName.toLowerCase() === "p" && el.textContent?.trim().startsWith(";;;row")
|
||||
? el
|
||||
: el.querySelector("p");
|
||||
|
||||
if (!startP) return;
|
||||
|
||||
const text = startP.textContent?.trim() || "";
|
||||
if (!text.startsWith(";;;row")) return;
|
||||
|
||||
const section = ctx.getSectionInfo(el);
|
||||
if (!section) return;
|
||||
|
||||
// Read the entire active page content to build the line index mapping
|
||||
plugin.app.vault.cachedRead(file).then(content => {
|
||||
const lines = content.split(/\r?\n/);
|
||||
const rows: MarkdownRow[] = [];
|
||||
let currentRow: Partial<MarkdownRow> | null = null;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i].trim();
|
||||
if (line.startsWith(";;;row")) {
|
||||
const widthsPart = line.substring(";;;row".length).trim().replace(/^:/, "").trim();
|
||||
let widths: string[] = [];
|
||||
if (widthsPart) {
|
||||
widths = widthsPart.split(/\s*\|\s*|\s*,\s*|\s+/).map(w => w.trim()).filter(w => w).map(formatWidth);
|
||||
}
|
||||
currentRow = {
|
||||
startLine: i,
|
||||
delimiters: [],
|
||||
widths
|
||||
};
|
||||
} else if (line === ";;") {
|
||||
if (currentRow) {
|
||||
currentRow.delimiters?.push(i);
|
||||
}
|
||||
} else if (line === ";;;") {
|
||||
if (currentRow) {
|
||||
currentRow.endLine = i;
|
||||
rows.push(currentRow as MarkdownRow);
|
||||
currentRow = null;
|
||||
}
|
||||
// Find all target paragraphs inside el that start with ;;;row
|
||||
const targetParagraphs: HTMLElement[] = [];
|
||||
if (el.tagName.toLowerCase() === "p" && el.textContent?.trim().startsWith(";;;row")) {
|
||||
targetParagraphs.push(el);
|
||||
} else {
|
||||
el.querySelectorAll("p").forEach((p) => {
|
||||
if (p.textContent?.trim().startsWith(";;;row")) {
|
||||
targetParagraphs.push(p);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Find the row corresponding to this start element
|
||||
const activeRow = rows.find(r => r.startLine === section.lineStart);
|
||||
if (!activeRow) return;
|
||||
if (targetParagraphs.length === 0) return;
|
||||
|
||||
targetParagraphs.forEach((startP) => {
|
||||
const text = startP.textContent?.trim() || "";
|
||||
|
||||
// Defer DOM operations slightly to ensure siblings are attached
|
||||
setTimeout(() => {
|
||||
const parent = el.parentElement;
|
||||
const parent = startP.parentElement;
|
||||
if (!parent) return;
|
||||
|
||||
if (el.dataset.rowProcessed === "true") return;
|
||||
el.dataset.rowProcessed = "true";
|
||||
if (startP.dataset.rowProcessed === "true") return;
|
||||
|
||||
const siblings = Array.from(parent.children) as HTMLElement[];
|
||||
const startIndex = siblings.indexOf(el as HTMLElement);
|
||||
if (startIndex === -1) return;
|
||||
// Find the main container (e.g. .markdown-preview-view or .markdown-preview-section)
|
||||
let container: HTMLElement | null = startP.parentElement;
|
||||
while (container &&
|
||||
!container.classList.contains("markdown-preview-view") &&
|
||||
!container.classList.contains("markdown-rendered") &&
|
||||
!container.classList.contains("markdown-preview-section")) {
|
||||
container = container.parentElement;
|
||||
}
|
||||
if (!container) {
|
||||
container = startP.parentElement;
|
||||
}
|
||||
|
||||
// Group elements into columns based on their source line ranges
|
||||
const numColumns = activeRow.delimiters.length + 1;
|
||||
const columnsElements: HTMLElement[][] = Array.from({ length: numColumns }, () => []);
|
||||
// Find topBlock (direct child of the container)
|
||||
let topBlock: HTMLElement | null = startP;
|
||||
while (topBlock && topBlock.parentElement !== container) {
|
||||
topBlock = topBlock.parentElement;
|
||||
}
|
||||
if (!topBlock) return;
|
||||
|
||||
const getBlockContent = (block: HTMLElement): HTMLElement => {
|
||||
if (block.tagName.toLowerCase() === "div" && block.children.length === 1 && !block.className) {
|
||||
return block.firstElementChild as HTMLElement;
|
||||
}
|
||||
return block;
|
||||
};
|
||||
|
||||
// Traverse next siblings of topBlock under container
|
||||
const columnsElements: HTMLElement[][] = [[]];
|
||||
const delimitersToRemove: HTMLElement[] = [];
|
||||
let closingElement: HTMLElement | null = null;
|
||||
let foundEnd = false;
|
||||
|
||||
siblings.forEach((sib) => {
|
||||
const sibSec = ctx.getSectionInfo(sib);
|
||||
if (!sibSec) return;
|
||||
let sibBlock = topBlock.nextElementSibling as HTMLElement | null;
|
||||
|
||||
while (sibBlock) {
|
||||
const contentEl = getBlockContent(sibBlock);
|
||||
const textVal = contentEl.textContent?.trim() || "";
|
||||
const isDelimiter = contentEl.tagName.toLowerCase() === "p" && textVal === ";;";
|
||||
const isClose = contentEl.tagName.toLowerCase() === "p" && textVal === ";;;";
|
||||
|
||||
const sibLine = sibSec.lineStart;
|
||||
|
||||
if (sibLine === activeRow.startLine) return;
|
||||
|
||||
if (activeRow.delimiters.includes(sibLine)) {
|
||||
delimitersToRemove.push(sib);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sibLine === activeRow.endLine) {
|
||||
closingElement = sib;
|
||||
return;
|
||||
}
|
||||
|
||||
for (let colIdx = 0; colIdx < numColumns; colIdx++) {
|
||||
const startBound = colIdx === 0 ? activeRow.startLine : activeRow.delimiters[colIdx - 1];
|
||||
const endBound = colIdx === numColumns - 1 ? activeRow.endLine : activeRow.delimiters[colIdx];
|
||||
|
||||
if (sibLine > startBound && sibLine < endBound) {
|
||||
columnsElements[colIdx].push(sib);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Extract column markdown texts from lines
|
||||
const columnsMarkdown: string[] = [];
|
||||
const rowLines = lines.slice(activeRow.startLine + 1, activeRow.endLine);
|
||||
let currentColLines: string[] = [];
|
||||
|
||||
for (let rIdx = 0; rIdx < rowLines.length; rIdx++) {
|
||||
const rLine = rowLines[rIdx];
|
||||
if (rLine.trim() === ";;") {
|
||||
columnsMarkdown.push(currentColLines.join("\n"));
|
||||
currentColLines = [];
|
||||
if (isDelimiter) {
|
||||
delimitersToRemove.push(sibBlock);
|
||||
columnsElements.push([]);
|
||||
} else if (isClose) {
|
||||
closingElement = sibBlock;
|
||||
foundEnd = true;
|
||||
break;
|
||||
} else {
|
||||
currentColLines.push(rLine);
|
||||
columnsElements[columnsElements.length - 1].push(sibBlock);
|
||||
}
|
||||
sibBlock = sibBlock.nextElementSibling as HTMLElement | null;
|
||||
}
|
||||
columnsMarkdown.push(currentColLines.join("\n"));
|
||||
|
||||
// Create CSS grid layout columns
|
||||
if (!foundEnd) return;
|
||||
|
||||
startP.dataset.rowProcessed = "true";
|
||||
|
||||
// Parse widths from the start line
|
||||
const widthsPart = text.substring(";;;row".length).trim().replace(/^:/, "").trim();
|
||||
let widths: string[] = [];
|
||||
if (widthsPart) {
|
||||
widths = widthsPart.split(/\s*\|\s*|\s*,\s*|\s+/).map(w => w.trim()).filter(w => w).map(formatWidth);
|
||||
}
|
||||
|
||||
const numColumns = columnsElements.length;
|
||||
const gridTracks: string[] = [];
|
||||
for (let colIdx = 0; colIdx < numColumns; colIdx++) {
|
||||
if (colIdx < activeRow.widths.length) {
|
||||
gridTracks.push(activeRow.widths[colIdx]);
|
||||
if (colIdx < widths.length) {
|
||||
gridTracks.push(widths[colIdx]);
|
||||
} else {
|
||||
gridTracks.push("1fr");
|
||||
}
|
||||
|
|
@ -208,31 +316,29 @@ export const createRowLayoutProcessor = (plugin: LatexReferencer): MarkdownPostP
|
|||
rowEl.style.alignItems = "center";
|
||||
rowEl.style.margin = "-0.5em 0";
|
||||
|
||||
columnsMarkdown.forEach((colMarkdown) => {
|
||||
columnsElements.forEach((colEls) => {
|
||||
const colEl = rowEl.createEl("div", { cls: "latex-referencer-column" });
|
||||
colEl.style.display = "flex";
|
||||
colEl.style.flexDirection = "column";
|
||||
colEl.style.justifyContent = "center";
|
||||
colEl.style.minWidth = "0";
|
||||
|
||||
// Asynchronously render markdown inside layout column
|
||||
const comp = new MarkdownRenderChild(colEl);
|
||||
ctx.addChild(comp);
|
||||
MarkdownRenderer.render(plugin.app, colMarkdown, colEl, ctx.sourcePath, comp)
|
||||
.then(() => {
|
||||
tightenColumn(colEl);
|
||||
setTimeout(() => tightenColumn(colEl), 50);
|
||||
setTimeout(() => tightenColumn(colEl), 150);
|
||||
setTimeout(() => tightenColumn(colEl), 500);
|
||||
})
|
||||
.catch((err) => console.error("Latex Referencer: Failed to render column markdown", err));
|
||||
// Move existing elements into the column
|
||||
colEls.forEach(item => colEl.appendChild(item));
|
||||
|
||||
tightenColumn(colEl);
|
||||
setTimeout(() => tightenColumn(colEl), 50);
|
||||
setTimeout(() => tightenColumn(colEl), 150);
|
||||
setTimeout(() => tightenColumn(colEl), 500);
|
||||
});
|
||||
|
||||
// Replace start element and remove all old intermediate DOM nodes
|
||||
parent.replaceChild(rowEl, el);
|
||||
if (topBlock.parentElement) {
|
||||
topBlock.parentElement.replaceChild(rowEl, topBlock);
|
||||
}
|
||||
|
||||
delimitersToRemove.forEach(sib => sib.remove());
|
||||
if (closingElement) (closingElement as HTMLElement).remove();
|
||||
columnsElements.forEach(colEls => colEls.forEach(sib => sib.remove()));
|
||||
}, 0);
|
||||
});
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue