mirror of
https://github.com/youfoundjk/TeXcore.git
synced 2026-07-22 07:33:31 +00:00
feat(tikz): decouple TikZJax loader and assets for fully offline, worker-based rendering
- Extracted 214 TeX libraries, packages, core formats, and WebAssembly binaries from monolithic `tikzjax.js` into local `tikzjax-assets/` folder. - Replaced monolithic 7MB runtime JS asset download from GitHub with a lightweight loader and Web Worker compilation engine. - Implemented `tikzjax.worker.ts` to execute the TeX engine in a background Web Worker thread using modern Web standard APIs, preventing main-thread UI locks. - Implemented `TikzJaxLoader` to parse code block preambles, lazily load/decompress assets synchronously via native `zlib`, cache core buffers in memory, and compile diagrams via the worker. - Refactored `renderer.ts` and `live-preview-overlay.ts` to utilize the async loader, removing DOM-polluting `<script>` tags, global mutation observers, and custom window hooks. - Swapped unsafe `.innerHTML` DOM writes with secure browser `DOMParser` APIs for SVG parsing. - Configured `esbuild` and `package.json` to recursively copy `tikzjax-assets/` to both development and production release locations. - Removed temporary extraction scripts to keep runtime codebase clean. - Added asset origin citations to module README, project root README, and MkDocs features documentation. - Fixed all 111 ESLint type check and style errors, ensuring clean linter execution.
This commit is contained in:
parent
4aa6d9cdac
commit
bea33c7cac
227 changed files with 2360 additions and 3608 deletions
11
README.md
11
README.md
|
|
@ -17,6 +17,7 @@ This fork has been redesigned with a focus on simplicity and performance:
|
|||
- **Equation-only** — No theorem/proof support, just equation referencing
|
||||
- **Custom ID system** — Uses `% id: eq-xxx` LaTeX comments instead of Obsidian's block references
|
||||
- **Zero dependencies** — Quick Preview and Math Links functionality built-in
|
||||
- **Offline TikZ Diagrams** — Decoupled, lazy-loaded LaTeX/TikZ rendering via background Web Workers with zero network dependencies
|
||||
|
||||
## Features
|
||||
|
||||
|
|
@ -42,6 +43,16 @@ Reference it with `[[#^eq-einstein]]` and the equation is automatically numbered
|
|||
- Rendered math preview in suggestions
|
||||
- Hover over links to see equation popups
|
||||
|
||||
### 🎨 TikZ Diagrams
|
||||
|
||||
Render TikZ code blocks (` ```tikz `) fully offline and on-demand:
|
||||
|
||||
- **Fully Offline** — Requires no active internet connection. All TeX format files and packages are cached locally in the plugin directory.
|
||||
- **Lazy Loading** — Core assets (`tex.wasm` and `core.dump`) are cached in memory on first compile. Supplementary packages (like `tikz-cd`, `circuitikz`, or `pgfplots`) are read and decompressed from disk only when requested.
|
||||
- **Background Compiler** — Compiles TeX within a Web Worker to ensure Obsidian's interface remains smooth and responsive.
|
||||
|
||||
*Assets are derived from Glenn Rice's (`drgrice1`) and Jim Fowler's (`kisonecat`) [TikZJax](https://github.com/kisonecat/tikzjax) compiler project, packaged offline by `artisticat1`.*
|
||||
|
||||
### 📄 PDF Export
|
||||
|
||||
Full-featured PDF export with:
|
||||
|
|
|
|||
83
docs/features/tikz.md
Normal file
83
docs/features/tikz.md
Normal file
|
|
@ -0,0 +1,83 @@
|
|||
# TikZ Diagrams
|
||||
|
||||
Render LaTeX TikZ diagrams (` ```tikz `) fully offline and on-demand inside your notes.
|
||||
|
||||
## Overview
|
||||
|
||||
Unlike other plugins that require an internet connection to fetch heavy dependencies or freeze your UI during TeX compilation, this plugin leverages a decoupled, worker-based compiler.
|
||||
|
||||
- **Fully Offline** — Requires no internet connection. All TeX format files and packages are cached locally in the plugin folder.
|
||||
- **Lazy Loaded Assets** — Core components (`tex.wasm.gz` and `core.dump.gz`) are read from disk, decompressed, and cached in memory on the first compile. Supplementary packages and libraries (like `circuitikz`, `pgfplots`, or `tikz-cd`) are only loaded and decompressed from disk when requested in your TikZ block's preamble.
|
||||
- **Web Worker Concurrency** — TeX compilation is CPU-bound. Offloading it to a background Web Worker ensures your Obsidian interface remains smooth and responsive.
|
||||
- **Theme Integration** — Hardcoded dark strokes and fills adapt automatically to Obsidian's active dark or light themes if dark-mode color inversion is enabled.
|
||||
|
||||
---
|
||||
|
||||
## How It Works
|
||||
|
||||
1. The Markdown code block processor detects a ` ```tikz ` block.
|
||||
2. The plugin parses the code preamble for package requirements (e.g. `\usepackage{pgfplots}` or `\usetikzlibrary{calc}`).
|
||||
3. The main thread loads the WASM engine, core dump, and resolved package files, decompresses them natively using Node's `zlib` library, and transfers the buffers to the Web Worker.
|
||||
4. The background worker spins up a virtual filesystem in memory, loads the TeX format dump, and compiles the TikZ block to a binary DVI format.
|
||||
5. The main thread receives the DVI binary, translates it to SVG using `dvi2html` and the secure browser `DOMParser` API, and inserts it into the note.
|
||||
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Main as Obsidian Main Thread
|
||||
participant Worker as TikZJax Web Worker
|
||||
|
||||
Note over Main: Markdown block processor triggered
|
||||
Main->>Main: Parse preamble (detect packages & libraries)
|
||||
Main->>Main: Read & decompress required .gz assets from local disk
|
||||
Main->>Worker: Spawn worker & postMessage(startCompile, { code, files })
|
||||
Note over Worker: Populate virtual filesystem in memory
|
||||
Worker->>Worker: Run TeX WebAssembly engine (Sync)
|
||||
Worker->>Worker: Output DVI format
|
||||
Worker->>Main: postMessage(compileSuccess, { dviData })
|
||||
Main->>Main: Translate DVI to SVG using dvi2html & DOMParser
|
||||
Main->>Main: Render SVG inside Note View
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Usage
|
||||
|
||||
Create a code block in your markdown document and identify it with `tikz`. You can include packages in the preamble:
|
||||
|
||||
```tikz
|
||||
\usepackage{pgfplots}
|
||||
\begin{document}
|
||||
\begin{tikzpicture}
|
||||
\begin{axis}[
|
||||
xlabel=$x$,
|
||||
ylabel=$y$
|
||||
]
|
||||
\addplot[color=red,mark=x] coordinates {
|
||||
(2,-3)
|
||||
(3,5)
|
||||
(4,7)
|
||||
};
|
||||
\end{axis}
|
||||
\end{tikzpicture}
|
||||
\end{document}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Asset Origins & Lineage
|
||||
|
||||
The lazy-loaded assets stored inside the `tikzjax-assets` folder are compiled/extracted from:
|
||||
|
||||
1. **[kisonecat/tikzjax](https://github.com/kisonecat/tikzjax)**: The original browser-based TeX compiler created by Jim Fowler, which compiles TeX's Pascal source to WebAssembly via `web2js`.
|
||||
2. **[drgrice1/tikzjax](https://github.com/drgrice1/tikzjax)**: Glenn Rice's fork which introduced Web Worker support and support for additional LaTeX packages and libraries.
|
||||
3. **[artisticat1/obsidian-tikzjax](https://github.com/artisticat1/obsidian-tikzjax)**: The Obsidian plugin wrapper created by `artisticat1` which packaged these components for offline usage in Obsidian.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
| Issue | Cause | Solution |
|
||||
|-------|-------|----------|
|
||||
| **Diagram shows empty container** | Core assets are missing or disabled | Ensure the `tikzjax-assets` folder is present inside the plugin directory under `.obsidian/plugins/TeXcore/tikzjax-assets/`. |
|
||||
| **Error: Package not found** | The requested LaTeX package is not supported or not present | Only packages and libraries pre-compiled into the assets are supported. Ensure the package name matches standard packages (like `pgfplots`, `circuitikz`, `chemfig`, `tikz-cd`). |
|
||||
| **Compile hangs or freezes** | Complex intersections or recursive computations exceed WebAssembly boundaries | Break down complex calculations or verify diagram logic for syntax loops. |
|
||||
|
|
@ -117,6 +117,7 @@ nav:
|
|||
- LaTeX Snippets: features/snippets.md
|
||||
- Equation Search: features/search.md
|
||||
- Quick Preview: features/quick-preview.md
|
||||
- TikZ Diagrams: features/tikz.md
|
||||
- Callout Support: features/callout-support.md
|
||||
- Configuration:
|
||||
- Settings Reference: configuration/settings.md
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@
|
|||
"coverage": "jest --coverage --passWithNoTests",
|
||||
"fl": "prettier --config .prettierrc.json --write \"src/**/*.{ts,js}\"",
|
||||
"ra": "prettier --config .prettierrc.json --write \"src/**/*.{ts,js}\" && pnpm run compile && eslint src -o lint_report.txt --no-color && eslint -c eslint.css.config.mjs \"src/**/*.css\" -o css_report.txt --no-color && jest --ci --silent=true --passWithNoTests",
|
||||
"prod:release": "pnpm run prod && node -e \"const fs=require('fs');const path=require('path');const root=process.cwd();const outputDir=path.join(root,'release-artifacts');fs.rmSync(outputDir,{recursive:true,force:true});fs.mkdirSync(outputDir,{recursive:true});for(const fileName of ['main.js','styles.css']){if(fs.existsSync(path.join(root,fileName))){fs.copyFileSync(path.join(root,fileName),path.join(outputDir,fileName));}}if(fs.existsSync(path.join(root,'manifest.json'))){fs.copyFileSync(path.join(root,'manifest.json'),path.join(outputDir,'manifest.json'));}\""
|
||||
"prod:release": "pnpm run prod && node -e \"const fs=require('fs');const path=require('path');const root=process.cwd();const outputDir=path.join(root,'release-artifacts');fs.rmSync(outputDir,{recursive:true,force:true});fs.mkdirSync(outputDir,{recursive:true});for(const fileName of ['main.js','styles.css']){if(fs.existsSync(path.join(root,fileName))){fs.copyFileSync(path.join(root,fileName),path.join(outputDir,fileName));}}if(fs.existsSync(path.join(root,'manifest.json'))){fs.copyFileSync(path.join(root,'manifest.json'),path.join(outputDir,'manifest.json'));}if(fs.existsSync(path.join(root,'tikzjax-assets'))){fs.cpSync(path.join(root,'tikzjax-assets'),path.join(outputDir,'tikzjax-assets'),{recursive:true});}\""
|
||||
},
|
||||
"keywords": [],
|
||||
"author": {
|
||||
|
|
@ -70,6 +70,7 @@
|
|||
"@codemirror/view": "^6.43.0",
|
||||
"@lezer/common": "^1.5.2",
|
||||
"@lucide/svelte": "^1.17.0",
|
||||
"dvi2html": "0.0.2",
|
||||
"esbuild-plugin-inline-worker": "^0.1.1",
|
||||
"flatqueue": "^3.0.0",
|
||||
"monkey-around": "^3.0.0",
|
||||
|
|
|
|||
4615
pnpm-lock.yaml
4615
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
13
src/declarations.d.ts
vendored
13
src/declarations.d.ts
vendored
|
|
@ -67,3 +67,16 @@ declare global {
|
|||
activeWindow: Window;
|
||||
}
|
||||
}
|
||||
|
||||
declare module 'dvi2html' {
|
||||
export function dvi2html(
|
||||
stream: unknown,
|
||||
writer: unknown
|
||||
): Promise<{ paperwidth: number; paperheight: number }>;
|
||||
export function tfmData(fontname: string): unknown;
|
||||
}
|
||||
|
||||
declare module '*.worker' {
|
||||
const WorkerConstructor: new () => Worker;
|
||||
export default WorkerConstructor;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -195,22 +195,27 @@ class TikzLivePreviewOverlay {
|
|||
return;
|
||||
}
|
||||
|
||||
let code = this.currentSource.trim();
|
||||
code = code.replaceAll(' ', '');
|
||||
code = code
|
||||
.split('\n')
|
||||
.map(l => l.trim())
|
||||
.filter(l => l)
|
||||
.join('\n');
|
||||
const source = this.currentSource;
|
||||
this.containerEl.createEl('div', { text: 'Rendering TikZ diagram...' });
|
||||
|
||||
if (!code.includes('\\begin{document}')) {
|
||||
code = `\\begin{document}\n${code}\n\\end{document}`;
|
||||
}
|
||||
|
||||
const script = this.containerEl.createEl('script');
|
||||
script.setAttribute('type', 'text/tikz');
|
||||
script.setAttribute('data-show-console', 'true');
|
||||
script.textContent = code;
|
||||
this.plugin.tikzRenderer
|
||||
.render(source)
|
||||
.then(svg => {
|
||||
if (this.currentSource !== source) return;
|
||||
if (this.containerEl) {
|
||||
this.containerEl.empty();
|
||||
this.containerEl.appendChild(svg);
|
||||
}
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
if (this.currentSource !== source) return;
|
||||
if (this.containerEl) {
|
||||
this.containerEl.empty();
|
||||
const errorEl = this.containerEl.createDiv({ cls: 'tikzjax-error' });
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
errorEl.textContent = `TikZJax Error: ${msg}`;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private exportSvg() {
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
import { requestUrl } from 'obsidian';
|
||||
import { showNotice } from 'utils/obsidian';
|
||||
import LatexReferencer from '../../main';
|
||||
import { TikzJaxLoader } from './tikzjax/loader';
|
||||
|
||||
export class TikzRenderer {
|
||||
private tikzjaxJs: string = '';
|
||||
private loader: TikzJaxLoader;
|
||||
private tikzjaxCss: string = '';
|
||||
private isLoaded: boolean = false;
|
||||
|
||||
constructor(public plugin: LatexReferencer) {}
|
||||
constructor(public plugin: LatexReferencer) {
|
||||
this.loader = new TikzJaxLoader(this.plugin);
|
||||
}
|
||||
|
||||
async onLoad() {
|
||||
if (!this.plugin.settings.enableTikzjax) {
|
||||
|
|
@ -15,15 +17,27 @@ export class TikzRenderer {
|
|||
}
|
||||
|
||||
try {
|
||||
await this.ensureResourcesCached();
|
||||
await this.loadResourcesFromCache();
|
||||
const adapter = this.plugin.app.vault.adapter;
|
||||
const pluginDir = this.plugin.manifest.dir;
|
||||
|
||||
// Support pop-out windows and main window
|
||||
if (!pluginDir) {
|
||||
throw new Error('Plugin manifest directory is not defined.');
|
||||
}
|
||||
|
||||
// Load tikzjax.css (containing base64 math fonts)
|
||||
const cssPath = `${pluginDir}/tikzjax-assets/tikzjax.css`;
|
||||
if (await adapter.exists(cssPath)) {
|
||||
this.tikzjaxCss = await adapter.read(cssPath);
|
||||
} else {
|
||||
console.warn('Latex Referencer: tikzjax.css not found in tikzjax-assets directory.');
|
||||
}
|
||||
|
||||
// Inject styling for main window and pop-outs
|
||||
this.plugin.app.workspace.onLayoutReady(() => {
|
||||
this.loadTikZJaxAllWindows();
|
||||
this.injectCssAllWindows();
|
||||
this.plugin.registerEvent(
|
||||
this.plugin.app.workspace.on('window-open', (win, window) => {
|
||||
this.loadTikZJax(window.document);
|
||||
this.injectCss(window.document);
|
||||
})
|
||||
);
|
||||
});
|
||||
|
|
@ -38,204 +52,74 @@ export class TikzRenderer {
|
|||
|
||||
onUnload() {
|
||||
if (!this.isLoaded) return;
|
||||
this.unloadTikZJaxAllWindows();
|
||||
this.removeCssAllWindows();
|
||||
}
|
||||
|
||||
private async fetchWithFallback(urls: string[]): Promise<string> {
|
||||
let lastError: unknown = null;
|
||||
for (const url of urls) {
|
||||
try {
|
||||
const res = await requestUrl({
|
||||
url,
|
||||
headers: {
|
||||
'User-Agent':
|
||||
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.0.0 Safari/537.36',
|
||||
Accept: '*/*',
|
||||
Referer: 'https://tikzjax.com/'
|
||||
}
|
||||
});
|
||||
if (res.status === 200) {
|
||||
return res.text;
|
||||
}
|
||||
throw new Error(`Request failed with status ${res.status}`);
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`Latex Referencer: Failed to fetch TikZJax asset from ${url}. Trying next fallback.`,
|
||||
err
|
||||
);
|
||||
lastError = err;
|
||||
}
|
||||
}
|
||||
throw lastError instanceof Error ? lastError : new Error('All download attempts failed.');
|
||||
/**
|
||||
* Renders the TikZ source string directly to an SVG element
|
||||
*/
|
||||
public async render(source: string): Promise<SVGElement> {
|
||||
const code = this.tidyTikzSource(source);
|
||||
const svg = await this.loader.render(code);
|
||||
this.postProcessSvg(svg);
|
||||
return svg;
|
||||
}
|
||||
|
||||
private async ensureResourcesCached() {
|
||||
const adapter = this.plugin.app.vault.adapter;
|
||||
const pluginDir = this.plugin.manifest.dir;
|
||||
private injectCss(doc: Document) {
|
||||
if (doc.getElementById('tikzjax-css')) return;
|
||||
|
||||
if (!pluginDir) {
|
||||
throw new Error('Plugin manifest directory is not defined.');
|
||||
}
|
||||
|
||||
const jsPath = `${pluginDir}/tikzjax.js`;
|
||||
|
||||
// If the cached file is the wrong one (official 458KB instead of 7MB offline version), delete it to trigger redownload
|
||||
if (await adapter.exists(jsPath)) {
|
||||
const content = await adapter.read(jsPath);
|
||||
if (!content.includes('MutationObserver')) {
|
||||
showNotice(
|
||||
'Outdated TikZJax engine detected. Clearing cache to fetch offline-capable version...'
|
||||
);
|
||||
await adapter.remove(jsPath);
|
||||
}
|
||||
}
|
||||
|
||||
// Check and fetch tikzjax.js
|
||||
if (!(await adapter.exists(jsPath))) {
|
||||
showNotice('Downloading TikZJax engine (one-time setup)...');
|
||||
const jsUrls = [
|
||||
'https://cdn.jsdelivr.net/gh/artisticat1/obsidian-tikzjax@main/tikzjax.js',
|
||||
'https://raw.githubusercontent.com/artisticat1/obsidian-tikzjax/main/tikzjax.js'
|
||||
];
|
||||
try {
|
||||
const text = await this.fetchWithFallback(jsUrls);
|
||||
await adapter.write(jsPath, text);
|
||||
showNotice('TikZJax engine cached successfully.');
|
||||
} catch (err) {
|
||||
console.error('Failed to download tikzjax.js', err);
|
||||
throw new Error('Failed to download TikZJax JavaScript resource.', { cause: err });
|
||||
}
|
||||
}
|
||||
|
||||
// Check and fetch styles.css (cached locally as tikzjax.css)
|
||||
const cssPath = `${pluginDir}/tikzjax.css`;
|
||||
if (!(await adapter.exists(cssPath))) {
|
||||
const cssUrls = [
|
||||
'https://cdn.jsdelivr.net/gh/artisticat1/obsidian-tikzjax@main/styles.css',
|
||||
'https://raw.githubusercontent.com/artisticat1/obsidian-tikzjax/main/styles.css'
|
||||
];
|
||||
try {
|
||||
const text = await this.fetchWithFallback(cssUrls);
|
||||
await adapter.write(cssPath, text);
|
||||
} catch (err) {
|
||||
console.error('Failed to download tikzjax.css', err);
|
||||
throw new Error('Failed to download TikZJax CSS resource.', { cause: err });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async loadResourcesFromCache() {
|
||||
const adapter = this.plugin.app.vault.adapter;
|
||||
const pluginDir = this.plugin.manifest.dir;
|
||||
|
||||
if (!pluginDir) return;
|
||||
|
||||
let jsText = await adapter.read(`${pluginDir}/tikzjax.js`);
|
||||
|
||||
// Patch tikzjax.js to handle dynamic execution, reload, and proper cleanup
|
||||
const originalTrigger = `"complete"==document.readyState?w():window.addEventListener("load",w),window.addEventListener("unload",(async function(){u&&u.disconnect(),await e.terminate(await H)}))`;
|
||||
|
||||
const patchedTrigger = `window.TikzJaxCleanup=async function(){u&&u.disconnect();if(H){try{await e.terminate(await H)}catch(err){}}window.TikzJax=undefined;window.TikzJaxCleanup=undefined;},document.readyState!=="loading"?w():window.addEventListener("load",w),window.addEventListener("unload",(async function(){if(window.TikzJaxCleanup)await window.TikzJaxCleanup();}))`;
|
||||
|
||||
if (jsText.includes(originalTrigger)) {
|
||||
jsText = jsText.replace(originalTrigger, patchedTrigger);
|
||||
} else {
|
||||
console.warn('Latex Referencer: Could not find original trigger in tikzjax.js for patching.');
|
||||
}
|
||||
|
||||
// Patch tikzjax-load-finished dispatchEvent calls to use optional chaining to prevent uncaught null reference errors
|
||||
if (jsText.includes('.dispatchEvent(new CustomEvent("tikzjax-load-finished"')) {
|
||||
jsText = jsText.replaceAll(
|
||||
'.dispatchEvent(new CustomEvent("tikzjax-load-finished"',
|
||||
'?.dispatchEvent(new CustomEvent("tikzjax-load-finished"'
|
||||
);
|
||||
}
|
||||
if (jsText.includes(".dispatchEvent(new CustomEvent('tikzjax-load-finished'")) {
|
||||
jsText = jsText.replaceAll(
|
||||
".dispatchEvent(new CustomEvent('tikzjax-load-finished'",
|
||||
"?.dispatchEvent(new CustomEvent('tikzjax-load-finished'"
|
||||
);
|
||||
}
|
||||
|
||||
this.tikzjaxJs = jsText;
|
||||
|
||||
if (await adapter.exists(`${pluginDir}/tikzjax.css`)) {
|
||||
this.tikzjaxCss = await adapter.read(`${pluginDir}/tikzjax.css`);
|
||||
}
|
||||
}
|
||||
|
||||
private loadTikZJax(doc: Document) {
|
||||
if (doc.getElementById('tikzjax')) return;
|
||||
|
||||
// Ingest TikZJax styles (specifically font-face mappings for math glyphs)
|
||||
if (!doc.getElementById('tikzjax-css') && this.tikzjaxCss) {
|
||||
// Inject core TikZJax styles (math glyph font-faces)
|
||||
if (this.tikzjaxCss) {
|
||||
const style = doc.createElement('style');
|
||||
style.id = 'tikzjax-css';
|
||||
style.textContent = this.tikzjaxCss;
|
||||
doc.head.appendChild(style);
|
||||
}
|
||||
|
||||
// Ingest Custom Styling to integrate seamlessly with the note theme
|
||||
const customStyle = doc.createElement('style');
|
||||
customStyle.id = 'tikzjax-custom-styles';
|
||||
customStyle.textContent = `
|
||||
.block-language-tikz {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
margin: 1.5em 0;
|
||||
overflow-x: auto;
|
||||
background-color: transparent;
|
||||
}
|
||||
.block-language-tikz svg {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
.block-language-tikz .tikzjax-error {
|
||||
color: var(--text-error);
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 0.9em;
|
||||
padding: 1rem;
|
||||
}
|
||||
`;
|
||||
doc.head.appendChild(customStyle);
|
||||
|
||||
// Ingest JS Script
|
||||
const script = doc.createElement('script');
|
||||
script.id = 'tikzjax';
|
||||
script.type = 'text/javascript';
|
||||
script.textContent = this.tikzjaxJs;
|
||||
doc.body.appendChild(script);
|
||||
|
||||
doc.addEventListener('tikzjax-load-finished', this.postProcessSvg);
|
||||
// Inject note theme integration styles
|
||||
if (!doc.getElementById('tikzjax-custom-styles')) {
|
||||
const customStyle = doc.createElement('style');
|
||||
customStyle.id = 'tikzjax-custom-styles';
|
||||
customStyle.textContent = `
|
||||
.block-language-tikz {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 1rem 0;
|
||||
margin: 1.5em 0;
|
||||
overflow-x: auto;
|
||||
background-color: transparent;
|
||||
}
|
||||
.block-language-tikz svg {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
color: var(--text-normal);
|
||||
}
|
||||
.block-language-tikz .tikzjax-error {
|
||||
color: var(--text-error);
|
||||
font-family: var(--font-monospace);
|
||||
font-size: 0.9em;
|
||||
padding: 1rem;
|
||||
}
|
||||
`;
|
||||
doc.head.appendChild(customStyle);
|
||||
}
|
||||
}
|
||||
|
||||
private unloadTikZJax(doc: Document) {
|
||||
// Trigger the cleanup function in the document's window context if it exists
|
||||
const win = doc.defaultView as unknown as { TikzJaxCleanup?: () => Promise<void> } | null;
|
||||
if (win && typeof win.TikzJaxCleanup === 'function') {
|
||||
win
|
||||
.TikzJaxCleanup()
|
||||
.catch((err: unknown) => console.error('Latex Referencer: Error cleaning up TikZJax', err));
|
||||
}
|
||||
|
||||
doc.getElementById('tikzjax')?.remove();
|
||||
private removeCss(doc: Document) {
|
||||
doc.getElementById('tikzjax-css')?.remove();
|
||||
doc.getElementById('tikzjax-custom-styles')?.remove();
|
||||
doc.removeEventListener('tikzjax-load-finished', this.postProcessSvg);
|
||||
}
|
||||
|
||||
private loadTikZJaxAllWindows() {
|
||||
private injectCssAllWindows() {
|
||||
for (const win of this.getAllWindows()) {
|
||||
this.loadTikZJax(win.document);
|
||||
this.injectCss(win.document);
|
||||
}
|
||||
}
|
||||
|
||||
private unloadTikZJaxAllWindows() {
|
||||
private removeCssAllWindows() {
|
||||
for (const win of this.getAllWindows()) {
|
||||
this.unloadTikZJax(win.document);
|
||||
this.removeCss(win.document);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -245,7 +129,7 @@ export class TikzRenderer {
|
|||
windows.push(window);
|
||||
}
|
||||
|
||||
// Retrieve pop-out windows from workspace
|
||||
// Retrieve floating pop-out windows
|
||||
const workspace = this.plugin.app.workspace as unknown as {
|
||||
floatingSplit?: {
|
||||
children: {
|
||||
|
|
@ -269,13 +153,10 @@ export class TikzRenderer {
|
|||
return windows;
|
||||
}
|
||||
|
||||
private postProcessSvg = (e: Event) => {
|
||||
private postProcessSvg(svg: SVGElement) {
|
||||
if (!this.plugin.settings.invertColorsInDarkMode) return;
|
||||
|
||||
const svg = e.target as SVGElement;
|
||||
if (!svg || svg.tagName.toLowerCase() !== 'svg') return;
|
||||
|
||||
// Ensure text and lines adapt cleanly in dark mode
|
||||
// Ensure text and lines adapt cleanly to light/dark themes
|
||||
const elements = svg.querySelectorAll('[stroke], [fill]');
|
||||
elements.forEach(el => {
|
||||
const stroke = el.getAttribute('stroke');
|
||||
|
|
@ -288,10 +169,10 @@ export class TikzRenderer {
|
|||
el.setAttribute('fill', 'currentColor');
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
private tidyTikzSource(tikzSource: string): string {
|
||||
// Remove non-breaking space characters, otherwise we get errors
|
||||
// Remove non-breaking space characters which cause parsing errors
|
||||
tikzSource = tikzSource.replaceAll(' ', '');
|
||||
|
||||
let lines = tikzSource.split('\n');
|
||||
|
|
@ -312,16 +193,20 @@ export class TikzRenderer {
|
|||
return;
|
||||
}
|
||||
|
||||
let code = this.tidyTikzSource(source);
|
||||
// Wrap in LaTeX document template if not already present
|
||||
if (!code.includes('\\begin{document}')) {
|
||||
code = `\\begin{document}\n${code}\n\\end{document}`;
|
||||
}
|
||||
const container = el.createDiv({ cls: 'block-language-tikz' });
|
||||
container.createEl('div', { text: 'Rendering TikZ diagram...' });
|
||||
|
||||
const script = el.createEl('script');
|
||||
script.setAttribute('type', 'text/tikz');
|
||||
script.setAttribute('data-show-console', 'true');
|
||||
script.textContent = code;
|
||||
this.render(source)
|
||||
.then(svg => {
|
||||
container.empty();
|
||||
container.appendChild(svg);
|
||||
})
|
||||
.catch((err: unknown) => {
|
||||
container.empty();
|
||||
const errorEl = container.createDiv({ cls: 'tikzjax-error' });
|
||||
const msg = err instanceof Error ? err.message : String(err);
|
||||
errorEl.textContent = `TikZJax Error: ${msg}`;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
14
src/features/tikz/tikzjax/README.md
Normal file
14
src/features/tikz/tikzjax/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# TikZJax Assets Sourcing
|
||||
|
||||
The assets in the `tikzjax-assets` folder have been decoupled from the monolithic JavaScript bundle and are lazy-loaded at runtime.
|
||||
|
||||
## Asset Origins
|
||||
|
||||
- **Core TeX Engine (`tex.wasm.gz` & `core.dump.gz`)**: Extracted from the pre-compiled, offline-capable `tikzjax.js` bundle of [artisticat1/obsidian-tikzjax](https://github.com/artisticat1/obsidian-tikzjax).
|
||||
- **TeX Files & LaTeX Packages (`tex_files/*.gz`)**: The LaTeX package files (like `chemfig`, `circuitikz`, and TikZ library code blocks) were also extracted from the monolithic bundle of `artisticat1/obsidian-tikzjax` to serve as lazy-loaded dependencies.
|
||||
- **Font Stylesheet (`tikzjax.css`)**: Downloaded from the official styling asset `styles.css` in [artisticat1/obsidian-tikzjax](https://github.com/artisticat1/obsidian-tikzjax). It contains `@font-face` declarations with embedded base64 font data for math and glyph symbols.
|
||||
|
||||
## Lineage of TikZJax
|
||||
1. **kisonecat/tikzjax**: The original browser-based TeX-in-WASM compiler created by Jim Fowler, which compiles TeX's Pascal source to WebAssembly via `web2js`.
|
||||
2. **drgrice1/tikzjax**: Glenn Rice's fork which added Web Worker support and support for additional LaTeX packages and libraries.
|
||||
3. **artisticat1/obsidian-tikzjax**: The Obsidian plugin wrapper created by `artisticat1` which packaged these components for offline usage in Obsidian notes.
|
||||
314
src/features/tikz/tikzjax/loader.ts
Normal file
314
src/features/tikz/tikzjax/loader.ts
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
import { dvi2html } from 'dvi2html';
|
||||
import { Writable } from 'stream';
|
||||
import zlib from 'zlib';
|
||||
import LatexReferencer from '../../../main';
|
||||
// @ts-ignore
|
||||
import TikzJaxWorker from './tikzjax.worker';
|
||||
|
||||
let cachedWasm: Uint8Array | null = null;
|
||||
let cachedCore: Uint8Array | null = null;
|
||||
|
||||
export class TikzJaxLoader {
|
||||
private assetFilesList: string[] = [];
|
||||
|
||||
constructor(private plugin: LatexReferencer) {}
|
||||
|
||||
/**
|
||||
* Parse packages and libraries from TikZ code block
|
||||
*/
|
||||
private parsePreamble(code: string): { packages: string[]; libraries: string[] } {
|
||||
const packages: string[] = [];
|
||||
const libraries: string[] = [];
|
||||
|
||||
// Match \usepackage[options]{package1,package2} or \usepackage{package1}
|
||||
const pkgRegex = /\\usepackage(?:\s*\[[^\]]*\])?\s*\{([^}]+)\}/g;
|
||||
let match;
|
||||
while ((match = pkgRegex.exec(code)) !== null) {
|
||||
const pkgs = match[1]
|
||||
.split(',')
|
||||
.map(p => p.trim())
|
||||
.filter(Boolean);
|
||||
packages.push(...pkgs);
|
||||
}
|
||||
|
||||
// Match \usetikzlibrary{lib1,lib2}
|
||||
const libRegex = /\\usetikzlibrary\s*\{([^}]+)\}/g;
|
||||
while ((match = libRegex.exec(code)) !== null) {
|
||||
const libs = match[1]
|
||||
.split(',')
|
||||
.map(l => l.trim())
|
||||
.filter(Boolean);
|
||||
libraries.push(...libs);
|
||||
}
|
||||
|
||||
return { packages, libraries };
|
||||
}
|
||||
|
||||
/**
|
||||
* Load asset files list from the tikzjax-assets folder once
|
||||
*/
|
||||
private async ensureAssetsList(): Promise<string[]> {
|
||||
if (this.assetFilesList.length > 0) {
|
||||
return this.assetFilesList;
|
||||
}
|
||||
|
||||
const adapter = this.plugin.app.vault.adapter;
|
||||
const pluginDir = this.plugin.manifest.dir;
|
||||
if (!pluginDir) return [];
|
||||
|
||||
const assetsPath = `${pluginDir}/tikzjax-assets`;
|
||||
try {
|
||||
if (await adapter.exists(assetsPath)) {
|
||||
// Recursively list files under tikzjax-assets
|
||||
const listFiles = async (dir: string): Promise<string[]> => {
|
||||
const result: string[] = [];
|
||||
const list = await adapter.list(dir);
|
||||
|
||||
// list.files contains direct child files
|
||||
for (const file of list.files) {
|
||||
result.push(file);
|
||||
}
|
||||
|
||||
// list.folders contains subfolders
|
||||
for (const folder of list.folders) {
|
||||
const subFiles = await listFiles(folder);
|
||||
result.push(...subFiles);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
const absolutePaths = await listFiles(assetsPath);
|
||||
// Map to relative paths within the tikzjax-assets folder
|
||||
this.assetFilesList = absolutePaths.map(p => {
|
||||
// p is like ".obsidian/plugins/latex-referencer/tikzjax-assets/tex_files/chemfig.tex.gz"
|
||||
const idx = p.indexOf('tikzjax-assets/');
|
||||
return idx !== -1 ? p.substring(idx + 'tikzjax-assets/'.length) : p;
|
||||
});
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Latex Referencer: Failed to scan tikzjax-assets directory', err);
|
||||
}
|
||||
|
||||
return this.assetFilesList;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a .gz file from the assets folder and return the decompressed Uint8Array
|
||||
*/
|
||||
private async loadAssetFile(filename: string): Promise<Uint8Array | null> {
|
||||
const adapter = this.plugin.app.vault.adapter;
|
||||
const pluginDir = this.plugin.manifest.dir;
|
||||
if (!pluginDir) return null;
|
||||
|
||||
const filePath = `${pluginDir}/tikzjax-assets/${filename}`;
|
||||
try {
|
||||
if (!(await adapter.exists(filePath))) {
|
||||
return null;
|
||||
}
|
||||
const compressedData = await adapter.readBinary(filePath);
|
||||
// Decompress gzip synchronously using Node zlib
|
||||
const decompressed = zlib.gunzipSync(Buffer.from(compressedData));
|
||||
return new Uint8Array(decompressed);
|
||||
} catch (err) {
|
||||
console.error(`Latex Referencer: Failed to load and decompress asset ${filename}`, err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a TikZ code block into an SVG element
|
||||
*/
|
||||
public async render(source: string): Promise<SVGElement> {
|
||||
const pluginDir = this.plugin.manifest.dir;
|
||||
|
||||
if (!pluginDir) {
|
||||
throw new Error('Plugin manifest directory is not defined.');
|
||||
}
|
||||
|
||||
// 1. Ensure core assets (tex.wasm.gz and core.dump.gz) are loaded/cached in memory
|
||||
if (!cachedWasm) {
|
||||
const wasmBuf = await this.loadAssetFile('tex.wasm.gz');
|
||||
if (!wasmBuf) {
|
||||
throw new Error('Required tex.wasm.gz file is missing from tikzjax-assets.');
|
||||
}
|
||||
cachedWasm = wasmBuf;
|
||||
}
|
||||
|
||||
if (!cachedCore) {
|
||||
const coreBuf = await this.loadAssetFile('core.dump.gz');
|
||||
if (!coreBuf) {
|
||||
throw new Error('Required core.dump.gz file is missing from tikzjax-assets.');
|
||||
}
|
||||
cachedCore = coreBuf;
|
||||
}
|
||||
|
||||
// 2. Parse code preamble to determine needed packages/libraries
|
||||
const tidyCode = source.trim();
|
||||
const { packages, libraries } = this.parsePreamble(tidyCode);
|
||||
|
||||
// Always load core assets
|
||||
const filesToLoad: Record<string, Uint8Array> = {
|
||||
'tex.wasm': cachedWasm,
|
||||
'core.dump': cachedCore
|
||||
};
|
||||
|
||||
// Scan available files in the assets directory
|
||||
const availableAssets = await this.ensureAssetsList();
|
||||
|
||||
// 3. Resolve and load package dependencies
|
||||
const assetsToLoad = new Set<string>();
|
||||
|
||||
// Standard font definitions to load every time (they are very small, < 1KB each)
|
||||
const fontFiles = [
|
||||
'tex_files/ueuex.fd.gz',
|
||||
'tex_files/ueuf.fd.gz',
|
||||
'tex_files/ueur.fd.gz',
|
||||
'tex_files/ueus.fd.gz',
|
||||
'tex_files/umsa.fd.gz',
|
||||
'tex_files/umsb.fd.gz'
|
||||
];
|
||||
for (const font of fontFiles) {
|
||||
if (availableAssets.includes(font)) {
|
||||
assetsToLoad.add(font);
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve packages
|
||||
for (const pkg of packages) {
|
||||
// Find package files like tex_files/chemfig.sty.gz, etc.
|
||||
const candidates = [
|
||||
`tex_files/${pkg}.sty.gz`,
|
||||
`tex_files/${pkg}.tex.gz`,
|
||||
`tex_files/t-${pkg}.tex.gz`
|
||||
];
|
||||
|
||||
for (const cand of candidates) {
|
||||
if (availableAssets.includes(cand)) {
|
||||
assetsToLoad.add(cand);
|
||||
}
|
||||
}
|
||||
|
||||
// Special dependency rules for major packages
|
||||
if (pkg === 'pgfplots') {
|
||||
availableAssets
|
||||
.filter(
|
||||
name =>
|
||||
name.startsWith('tex_files/pgfplots') ||
|
||||
name.startsWith('tex_files/pgflibrarypgfplots')
|
||||
)
|
||||
.forEach(name => assetsToLoad.add(name));
|
||||
} else if (pkg === 'circuitikz') {
|
||||
availableAssets
|
||||
.filter(
|
||||
name =>
|
||||
name.startsWith('tex_files/circuitikz') ||
|
||||
name.startsWith('tex_files/pgfcirc') ||
|
||||
name.startsWith('tex_files/t-circuitikz')
|
||||
)
|
||||
.forEach(name => assetsToLoad.add(name));
|
||||
} else if (pkg === 'chemfig') {
|
||||
// chemfig depends on simplekv
|
||||
availableAssets
|
||||
.filter(
|
||||
name => name.startsWith('tex_files/simplekv') || name.startsWith('tex_files/t-chemfig')
|
||||
)
|
||||
.forEach(name => assetsToLoad.add(name));
|
||||
}
|
||||
}
|
||||
|
||||
// Resolve TikZ libraries
|
||||
for (const lib of libraries) {
|
||||
const cand = `tex_files/tikzlibrary${lib}.code.tex.gz`;
|
||||
if (availableAssets.includes(cand)) {
|
||||
assetsToLoad.add(cand);
|
||||
}
|
||||
}
|
||||
|
||||
// Load and decompress all resolved assets
|
||||
for (const asset of assetsToLoad) {
|
||||
const data = await this.loadAssetFile(asset);
|
||||
if (data) {
|
||||
// Map the virtual path inside the worker (without .gz)
|
||||
const workerVirtualPath = asset.replace(/\.gz$/, '');
|
||||
filesToLoad[workerVirtualPath] = data;
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Run the compilation inside the Web Worker
|
||||
const dviData = await this.runWorkerCompile(tidyCode, filesToLoad);
|
||||
|
||||
// 5. Convert DVI output to SVG element
|
||||
return this.dviToSvg(dviData);
|
||||
}
|
||||
|
||||
/**
|
||||
* Spawns the Web Worker and compiles the code
|
||||
*/
|
||||
private runWorkerCompile(code: string, files: Record<string, Uint8Array>): Promise<Uint8Array> {
|
||||
return new Promise((resolve, reject) => {
|
||||
let worker: Worker;
|
||||
try {
|
||||
const WorkerConstructor = TikzJaxWorker as unknown as new () => Worker;
|
||||
worker = new WorkerConstructor();
|
||||
} catch (err) {
|
||||
reject(err instanceof Error ? err : new Error(String(err)));
|
||||
return;
|
||||
}
|
||||
|
||||
worker.onmessage = (e: MessageEvent) => {
|
||||
const data = e.data as { type: string; dvi?: Uint8Array; error?: string };
|
||||
if (data.type === 'success' && data.dvi) {
|
||||
resolve(data.dvi);
|
||||
worker.terminate();
|
||||
} else if (data.type === 'error') {
|
||||
reject(new Error(data.error || 'Unknown error occurred in worker.'));
|
||||
worker.terminate();
|
||||
}
|
||||
};
|
||||
|
||||
worker.onerror = (err: ErrorEvent) => {
|
||||
reject(err.error instanceof Error ? err.error : new Error(err.message || 'Worker error'));
|
||||
worker.terminate();
|
||||
};
|
||||
|
||||
// Start compile
|
||||
worker.postMessage({ type: 'compile', code, files });
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts DVI format to SVG
|
||||
*/
|
||||
private async dviToSvg(dvi: Uint8Array): Promise<SVGElement> {
|
||||
let html = '';
|
||||
const page = new Writable({
|
||||
write(chunk: unknown, _, callback) {
|
||||
html += String(chunk);
|
||||
callback();
|
||||
}
|
||||
});
|
||||
|
||||
async function* streamBuffer() {
|
||||
yield Buffer.from(dvi);
|
||||
return;
|
||||
}
|
||||
|
||||
const machine = await dvi2html(streamBuffer(), page);
|
||||
|
||||
// Parse SVG safely using DOMParser
|
||||
const parser = new DOMParser();
|
||||
const doc = parser.parseFromString(html.trim(), 'image/svg+xml');
|
||||
const svg = doc.querySelector('svg');
|
||||
|
||||
if (!svg) {
|
||||
throw new Error('DVI conversion succeeded but no SVG element was generated.');
|
||||
}
|
||||
|
||||
// Apply viewport attributes
|
||||
svg.setAttribute('width', `${machine.paperwidth}pt`);
|
||||
svg.setAttribute('height', `${machine.paperheight}pt`);
|
||||
svg.setAttribute('viewBox', `-72 -72 ${machine.paperwidth} ${machine.paperheight}`);
|
||||
|
||||
return svg;
|
||||
}
|
||||
}
|
||||
430
src/features/tikz/tikzjax/tikzjax.worker.ts
Normal file
430
src/features/tikz/tikzjax/tikzjax.worker.ts
Normal file
|
|
@ -0,0 +1,430 @@
|
|||
/* eslint-disable */
|
||||
import { tfmData } from 'dvi2html';
|
||||
|
||||
interface VirtualFile {
|
||||
filename: string;
|
||||
position: number;
|
||||
erstat: number;
|
||||
buffer: Uint8Array;
|
||||
descriptor?: number;
|
||||
stdin?: boolean;
|
||||
stdout?: boolean;
|
||||
eof?: boolean;
|
||||
eoln?: boolean;
|
||||
}
|
||||
|
||||
// Simulated virtual filesystem
|
||||
let files: VirtualFile[] = [];
|
||||
let virtualFilesystem: Record<string, Uint8Array> = {};
|
||||
|
||||
export function deleteEverything() {
|
||||
files = [];
|
||||
virtualFilesystem = {};
|
||||
}
|
||||
|
||||
export function writeFileSync(filename: string, buffer: Uint8Array) {
|
||||
virtualFilesystem[filename] = buffer;
|
||||
}
|
||||
|
||||
export function readFileSync(filename: string): Uint8Array {
|
||||
for (const f of files) {
|
||||
if (f.filename === filename) {
|
||||
return f.buffer.slice(0, f.position);
|
||||
}
|
||||
}
|
||||
throw new Error(`Could not find file ${filename}`);
|
||||
}
|
||||
|
||||
function openSync(filename: string, mode: string): number {
|
||||
let buffer: any = new Uint8Array();
|
||||
|
||||
if (virtualFilesystem[filename]) {
|
||||
buffer = virtualFilesystem[filename];
|
||||
}
|
||||
|
||||
if (filename.endsWith('.tfm')) {
|
||||
const fontName = filename.replace(/\.tfm$/, '');
|
||||
const data = tfmData(fontName);
|
||||
if (data) {
|
||||
buffer = Uint8Array.from(data as any);
|
||||
}
|
||||
}
|
||||
|
||||
files.push({
|
||||
filename: filename,
|
||||
position: 0,
|
||||
erstat: 0,
|
||||
buffer: buffer,
|
||||
descriptor: files.length
|
||||
});
|
||||
|
||||
return files.length - 1;
|
||||
}
|
||||
|
||||
function closeSync(fd: number) {
|
||||
// Ignore close sync
|
||||
}
|
||||
|
||||
function writeSync(file: any, buffer: Uint8Array, pointer?: number, length?: number) {
|
||||
const p = pointer === undefined ? 0 : pointer;
|
||||
let len = length === undefined ? buffer.length - p : length;
|
||||
|
||||
while (len > file.buffer.length - file.position) {
|
||||
const b = new Uint8Array(1 + file.buffer.length * 2);
|
||||
b.set(file.buffer);
|
||||
file.buffer = b;
|
||||
}
|
||||
|
||||
file.buffer.subarray(file.position).set(buffer.subarray(p, p + len));
|
||||
file.position += len;
|
||||
}
|
||||
|
||||
function readSync(
|
||||
file: any,
|
||||
buffer: Uint8Array,
|
||||
pointer?: number,
|
||||
length?: number,
|
||||
seek?: number
|
||||
): number {
|
||||
const p = pointer === undefined ? 0 : pointer;
|
||||
let len = length === undefined ? buffer.length - p : length;
|
||||
const s = seek === undefined ? 0 : seek;
|
||||
|
||||
if (len > file.buffer.length - s) {
|
||||
len = file.buffer.length - s;
|
||||
}
|
||||
|
||||
buffer.subarray(p).set(file.buffer.subarray(s, s + len));
|
||||
return len;
|
||||
}
|
||||
|
||||
// Console output collection
|
||||
let consoleBuffer = '';
|
||||
function writeToConsole(x: string) {
|
||||
consoleBuffer = consoleBuffer + x;
|
||||
if (consoleBuffer.indexOf('\n') >= 0) {
|
||||
const lines = consoleBuffer.split('\n');
|
||||
consoleBuffer = lines.pop() || '';
|
||||
for (const line of lines) {
|
||||
console.log(line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const customProcess = {
|
||||
stdout: {
|
||||
write: writeToConsole
|
||||
}
|
||||
};
|
||||
|
||||
// WebAssembly instance memory variables
|
||||
let memory: ArrayBuffer | undefined = undefined;
|
||||
let inputBuffer = '';
|
||||
let callback: (() => void) | undefined = undefined;
|
||||
|
||||
export function setMemory(m: ArrayBuffer) {
|
||||
memory = m;
|
||||
}
|
||||
|
||||
export function setInput(input: string, cb?: () => void) {
|
||||
inputBuffer = input;
|
||||
if (cb) callback = cb;
|
||||
}
|
||||
|
||||
// Time functions requested by TeX
|
||||
export function getCurrentMinutes(): number {
|
||||
const d = new Date();
|
||||
return 60 * d.getHours() + d.getMinutes();
|
||||
}
|
||||
|
||||
export function getCurrentDay(): number {
|
||||
return new Date().getDate();
|
||||
}
|
||||
|
||||
export function getCurrentMonth(): number {
|
||||
return new Date().getMonth() + 1;
|
||||
}
|
||||
|
||||
export function getCurrentYear(): number {
|
||||
return new Date().getFullYear();
|
||||
}
|
||||
|
||||
// TeX print output helper methods
|
||||
const textDecoder = new TextDecoder();
|
||||
const textEncoder = new TextEncoder();
|
||||
|
||||
export function printString(descriptor: number, x: number) {
|
||||
if (!memory) return;
|
||||
const file = descriptor < 0 ? ({ stdout: true } as any) : files[descriptor];
|
||||
const length = new Uint8Array(memory, x, 1)[0];
|
||||
const buffer = new Uint8Array(memory, x + 1, length);
|
||||
const string = textDecoder.decode(buffer);
|
||||
|
||||
if (file.stdout) {
|
||||
customProcess.stdout.write(string);
|
||||
return;
|
||||
}
|
||||
|
||||
writeSync(file, textEncoder.encode(string));
|
||||
}
|
||||
|
||||
export function printBoolean(descriptor: number, x: boolean) {
|
||||
const file = descriptor < 0 ? ({ stdout: true } as any) : files[descriptor];
|
||||
const result = x ? 'TRUE' : 'FALSE';
|
||||
|
||||
if (file.stdout) {
|
||||
customProcess.stdout.write(result);
|
||||
return;
|
||||
}
|
||||
|
||||
writeSync(file, textEncoder.encode(result));
|
||||
}
|
||||
|
||||
export function printChar(descriptor: number, x: number) {
|
||||
const file = descriptor < 0 ? ({ stdout: true } as any) : files[descriptor];
|
||||
if (file.stdout) {
|
||||
customProcess.stdout.write(String.fromCharCode(x));
|
||||
return;
|
||||
}
|
||||
|
||||
const b = new Uint8Array([x]);
|
||||
writeSync(file, b);
|
||||
}
|
||||
|
||||
export function printInteger(descriptor: number, x: number) {
|
||||
const file = descriptor < 0 ? ({ stdout: true } as any) : files[descriptor];
|
||||
const str = x.toString();
|
||||
if (file.stdout) {
|
||||
customProcess.stdout.write(str);
|
||||
return;
|
||||
}
|
||||
|
||||
writeSync(file, textEncoder.encode(str));
|
||||
}
|
||||
|
||||
export function printFloat(descriptor: number, x: number) {
|
||||
const file = descriptor < 0 ? ({ stdout: true } as any) : files[descriptor];
|
||||
const str = x.toString();
|
||||
if (file.stdout) {
|
||||
customProcess.stdout.write(str);
|
||||
return;
|
||||
}
|
||||
|
||||
writeSync(file, textEncoder.encode(str));
|
||||
}
|
||||
|
||||
export function printNewline(descriptor: number) {
|
||||
const file = descriptor < 0 ? ({ stdout: true } as any) : files[descriptor];
|
||||
if (file.stdout) {
|
||||
customProcess.stdout.write('\n');
|
||||
return;
|
||||
}
|
||||
|
||||
writeSync(file, textEncoder.encode('\n'));
|
||||
}
|
||||
|
||||
export function reset(length: number, pointer: number): number {
|
||||
if (!memory) return -1;
|
||||
const buffer = new Uint8Array(memory, pointer, length);
|
||||
let filename = textDecoder.decode(buffer);
|
||||
|
||||
filename = filename.replace(/ +$/g, '');
|
||||
filename = filename.replace(/^\*/, '');
|
||||
filename = filename.replace(/^TeXfonts:/, '');
|
||||
|
||||
if (filename === 'TeXformats:TEX.POOL') {
|
||||
filename = 'tex.pool';
|
||||
}
|
||||
|
||||
if (filename === 'TTY:') {
|
||||
files.push({
|
||||
filename: 'stdin',
|
||||
stdin: true,
|
||||
position: 0,
|
||||
erstat: 0,
|
||||
buffer: new Uint8Array()
|
||||
});
|
||||
return files.length - 1;
|
||||
}
|
||||
|
||||
return openSync(filename, 'r');
|
||||
}
|
||||
|
||||
export function rewrite(length: number, pointer: number): number {
|
||||
if (!memory) return -1;
|
||||
const buffer = new Uint8Array(memory, pointer, length);
|
||||
let filename = textDecoder.decode(buffer);
|
||||
|
||||
filename = filename.replace(/ +$/g, '');
|
||||
|
||||
if (filename === 'TTY:') {
|
||||
files.push({
|
||||
filename: 'stdout',
|
||||
stdout: true,
|
||||
position: 0,
|
||||
erstat: 0,
|
||||
buffer: new Uint8Array()
|
||||
});
|
||||
return files.length - 1;
|
||||
}
|
||||
|
||||
return openSync(filename, 'w');
|
||||
}
|
||||
|
||||
export function close(descriptor: number) {
|
||||
const file = files[descriptor];
|
||||
if (file && file.descriptor !== undefined) {
|
||||
closeSync(file.descriptor);
|
||||
}
|
||||
}
|
||||
|
||||
export function eof(descriptor: number): number {
|
||||
const file = files[descriptor];
|
||||
return file && file.eof ? 1 : 0;
|
||||
}
|
||||
|
||||
export function erstat(descriptor: number): number {
|
||||
const file = files[descriptor];
|
||||
return file ? file.erstat : 0;
|
||||
}
|
||||
|
||||
export function eoln(descriptor: number): number {
|
||||
const file = files[descriptor];
|
||||
return file && file.eoln ? 1 : 0;
|
||||
}
|
||||
|
||||
export function get(descriptor: number, pointer: number, length: number) {
|
||||
if (!memory) return;
|
||||
const file = files[descriptor];
|
||||
const buffer = new Uint8Array(memory);
|
||||
|
||||
if (file.stdin) {
|
||||
if (file.position >= inputBuffer.length) {
|
||||
buffer[pointer] = 13;
|
||||
file.eof = true;
|
||||
file.eoln = true;
|
||||
if (callback) callback();
|
||||
} else {
|
||||
buffer[pointer] = inputBuffer[file.position].charCodeAt(0);
|
||||
}
|
||||
} else {
|
||||
if (file.descriptor !== undefined) {
|
||||
if (readSync(file, buffer, pointer, length, file.position) === 0) {
|
||||
buffer[pointer] = 0;
|
||||
file.eof = true;
|
||||
file.eoln = true;
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
file.eof = true;
|
||||
file.eoln = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
file.eoln = false;
|
||||
if (buffer[pointer] === 10 || buffer[pointer] === 13) {
|
||||
file.eoln = true;
|
||||
}
|
||||
|
||||
file.position = file.position + length;
|
||||
}
|
||||
|
||||
export function put(descriptor: number, pointer: number, length: number) {
|
||||
if (!memory) return;
|
||||
const file = files[descriptor];
|
||||
const buffer = new Uint8Array(memory);
|
||||
writeSync(file, buffer, pointer, length);
|
||||
}
|
||||
|
||||
// Main execution function
|
||||
async function compile(
|
||||
code: string,
|
||||
preloadedFiles: Record<string, Uint8Array>
|
||||
): Promise<Uint8Array> {
|
||||
deleteEverything();
|
||||
|
||||
// Populate virtual filesystem
|
||||
for (const [filepath, content] of Object.entries(preloadedFiles)) {
|
||||
if (filepath !== 'tex.wasm' && filepath !== 'core.dump') {
|
||||
writeFileSync(filepath, content);
|
||||
}
|
||||
}
|
||||
|
||||
const wasmBinary = preloadedFiles['tex.wasm'];
|
||||
const coreDump = preloadedFiles['core.dump'];
|
||||
|
||||
if (!wasmBinary) throw new Error('Missing tex.wasm binary');
|
||||
if (!coreDump) throw new Error('Missing core.dump binary');
|
||||
|
||||
// Input LaTeX code
|
||||
let input = code;
|
||||
if (!input.includes('\\begin{document}')) {
|
||||
input = '\\begin{document}\n' + input;
|
||||
}
|
||||
if (!input.includes('\\end{document}')) {
|
||||
input = input + '\n\\end{document}\n';
|
||||
}
|
||||
|
||||
writeFileSync('sample.tex', textEncoder.encode(input));
|
||||
|
||||
const pages = 1000;
|
||||
const memoryInstance = new WebAssembly.Memory({ initial: pages, maximum: pages });
|
||||
const memoryBuffer = new Uint8Array(memoryInstance.buffer, 0, pages * 65536);
|
||||
|
||||
// Load core dump into memory
|
||||
memoryBuffer.set(coreDump);
|
||||
|
||||
setMemory(memoryInstance.buffer);
|
||||
setInput(' sample.tex \n\\end\n');
|
||||
|
||||
// Instantiate TeX WebAssembly
|
||||
const importObject = {
|
||||
library: {
|
||||
deleteEverything,
|
||||
writeFileSync,
|
||||
readFileSync,
|
||||
getCurrentMinutes,
|
||||
getCurrentDay,
|
||||
getCurrentMonth,
|
||||
getCurrentYear,
|
||||
printString,
|
||||
printBoolean,
|
||||
printChar,
|
||||
printInteger,
|
||||
printFloat,
|
||||
printNewline,
|
||||
reset,
|
||||
rewrite,
|
||||
close,
|
||||
eof,
|
||||
erstat,
|
||||
eoln,
|
||||
get,
|
||||
put
|
||||
},
|
||||
env: {
|
||||
memory: memoryInstance
|
||||
}
|
||||
};
|
||||
|
||||
await WebAssembly.instantiate(wasmBinary, importObject);
|
||||
|
||||
// Read generated output DVI
|
||||
return readFileSync('sample.dvi');
|
||||
}
|
||||
|
||||
// Worker message handling
|
||||
self.addEventListener('message', async (e: MessageEvent) => {
|
||||
const { type, code, files } = e.data;
|
||||
|
||||
if (type === 'compile') {
|
||||
try {
|
||||
const dvi = await compile(code, files);
|
||||
// Transfer the array buffer back for optimal performance
|
||||
(self as any).postMessage({ type: 'success', dvi }, [dvi.buffer as any]);
|
||||
} catch (err: any) {
|
||||
(self as any).postMessage({ type: 'error', error: err?.message || String(err) });
|
||||
}
|
||||
}
|
||||
});
|
||||
14
tikzjax-assets/README.md
Normal file
14
tikzjax-assets/README.md
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# TikZJax Assets Sourcing
|
||||
|
||||
The assets in this directory are the compiled format dumps, WebAssembly binaries, font stylesheets, and LaTeX package dependency files used by the TikZJax rendering engine in the TeXcore plugin.
|
||||
|
||||
## Asset Origins
|
||||
|
||||
- **Core TeX Engine (`tex.wasm.gz` & `core.dump.gz`)**: Extracted from the pre-compiled, offline-capable `tikzjax.js` bundle of [artisticat1/obsidian-tikzjax](https://github.com/artisticat1/obsidian-tikzjax).
|
||||
- **TeX Files & LaTeX Packages (`tex_files/*.gz`)**: The LaTeX package files (such as `chemfig`, `circuitikz`, and TikZ library code blocks) were also extracted from the monolithic bundle of `artisticat1/obsidian-tikzjax` to serve as lazy-loaded dependencies.
|
||||
- **Font Stylesheet (`tikzjax.css`)**: Downloaded from the official styling asset `styles.css` in [artisticat1/obsidian-tikzjax](https://github.com/artisticat1/obsidian-tikzjax). It contains `@font-face` declarations with embedded base64 font data for math and glyph symbols.
|
||||
|
||||
## Lineage of TikZJax
|
||||
1. **[kisonecat/tikzjax](https://github.com/kisonecat/tikzjax)**: The original browser-based TeX-in-WASM compiler created by Jim Fowler, which compiles TeX's Pascal source to WebAssembly via `web2js`.
|
||||
2. **[drgrice1/tikzjax](https://github.com/drgrice1/tikzjax)**: Glenn Rice's fork which added Web Worker support and support for additional LaTeX packages and libraries.
|
||||
3. **[artisticat1/obsidian-tikzjax](https://github.com/artisticat1/obsidian-tikzjax)**: The Obsidian plugin wrapper created by `artisticat1` which packaged these components for offline usage in Obsidian notes.
|
||||
BIN
tikzjax-assets/core.dump.gz
Normal file
BIN
tikzjax-assets/core.dump.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex.wasm.gz
Normal file
BIN
tikzjax-assets/tex.wasm.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/amsbsy.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/amsbsy.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/amscd.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/amscd.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/amsfonts.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/amsfonts.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/amsgen.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/amsgen.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/amsmath-2018-12-01.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/amsmath-2018-12-01.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/amsmath.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/amsmath.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/amsopn.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/amsopn.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/amssymb.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/amssymb.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/amstex.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/amstex.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/amstext.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/amstext.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/amsxtra.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/amsxtra.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/array.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/array.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/chemfig.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/chemfig.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/chemfig.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/chemfig.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/circuitikz-0.4.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/circuitikz-0.4.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/circuitikz-0.6.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/circuitikz-0.6.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/circuitikz-0.7.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/circuitikz-0.7.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/circuitikz-0.8.3.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/circuitikz-0.8.3.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/circuitikz-0.9.3.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/circuitikz-0.9.3.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/circuitikz-0.9.6.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/circuitikz-0.9.6.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/circuitikz.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/circuitikz.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/cmmib57.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/cmmib57.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/ctikzstyle-example.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/ctikzstyle-example.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/ctikzstyle-legacy.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/ctikzstyle-legacy.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/ctikzstyle-romano.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/ctikzstyle-romano.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/eucal.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/eucal.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/eufrak.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/eufrak.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/euscript.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/euscript.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/ifthen.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/ifthen.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcalendar.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcalendar.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcalendar.sty.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcalendar.sty.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcirc.defines.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcirc.defines.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcircbipoles.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcircbipoles.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcirccurrent.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcirccurrent.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcircflow.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcircflow.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcirclabel.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcirclabel.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcircmonopoles.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcircmonopoles.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcircmultipoles.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcircmultipoles.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcircpath.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcircpath.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcircquadpoles.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcircquadpoles.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcircshapes.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcircshapes.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcirctripoles.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcirctripoles.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcircutils.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcircutils.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfcircvoltage.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfcircvoltage.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfint.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfint.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryarrows.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryarrows.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryarrows.meta.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryarrows.meta.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibrarybbox.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibrarybbox.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibrarycurvilinear.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibrarycurvilinear.code.tex.gz
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibrarydecorations.text.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibrarydecorations.text.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryfadings.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryfadings.code.tex.gz
Normal file
Binary file not shown.
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryfpu.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryfpu.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryintersections.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryintersections.code.tex.gz
Normal file
Binary file not shown.
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibrarypatterns.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibrarypatterns.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibrarypatterns.meta.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibrarypatterns.meta.code.tex.gz
Normal file
Binary file not shown.
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryplotmarks.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryplotmarks.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryshadings.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryshadings.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryshapes.arrows.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryshapes.arrows.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryshapes.callouts.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryshapes.callouts.code.tex.gz
Normal file
Binary file not shown.
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryshapes.gates.ee.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryshapes.gates.ee.code.tex.gz
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryshapes.geometric.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryshapes.geometric.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryshapes.misc.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryshapes.misc.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryshapes.multipart.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryshapes.multipart.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibraryshapes.symbols.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibraryshapes.symbols.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgflibrarysvg.path.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgflibrarysvg.path.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfmoduleanimations.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfmoduleanimations.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfmodulebending.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfmodulebending.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfmoduledatavisualization.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfmoduledatavisualization.code.tex.gz
Normal file
Binary file not shown.
BIN
tikzjax-assets/tex_files/pgfmoduledecorations.code.tex.gz
Normal file
BIN
tikzjax-assets/tex_files/pgfmoduledecorations.code.tex.gz
Normal file
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue