Skip to content

Commit 89ac22c

Browse files
committed
feat: Added SwiftLaTeX Engine
1 parent 4d377ef commit 89ac22c

38 files changed

+7276
-5
lines changed

.env.example

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ VITE_ENABLE_RTEX=false
66
# Note: If the host is unreachable you may see DNS/network errors.
77

88
# Enable in-browser LaTeX compilation via WebAssembly
9-
VITE_USE_WASM_LATEX=false
9+
VITE_USE_WASM_LATEX=true
1010

1111
# Optional: ESM module id or URL to import the WASM engine from
1212
# Example: VITE_WASM_LATEX_MODULE=/wasm/your-wasm-engine.js

index.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
</head>
1111
<body class="bg-slate-50">
1212
<div id="root"></div>
13+
<script src="./swiftlatex/PdfTeXEngine.js"></script>
1314
<script type="module" src="/src/main.jsx"></script>
1415
</body>
1516
</html>

public/swiftlatex/PdfTeXEngine.js

Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
"use strict";
2+
/********************************************************************************
3+
* Copyright (C) 2019 Elliott Wen.
4+
*
5+
* This program and the accompanying materials are made available under the
6+
* terms of the Eclipse Public License v. 2.0 which is available at
7+
* http://www.eclipse.org/legal/epl-2.0.
8+
*
9+
* This Source Code may also be made available under the following Secondary
10+
* Licenses when the conditions for such availability set forth in the Eclipse
11+
* Public License v. 2.0 are satisfied: GNU General Public License, version 2
12+
* with the GNU Classpath Exception which is available at
13+
* https://www.gnu.org/software/classpath/license.html.
14+
*
15+
* SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
16+
********************************************************************************/
17+
var exports = {};
18+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
19+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
20+
return new (P || (P = Promise))(function (resolve, reject) {
21+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
22+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
23+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
24+
step((generator = generator.apply(thisArg, _arguments || [])).next());
25+
});
26+
};
27+
var __generator = (this && this.__generator) || function (thisArg, body) {
28+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
29+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
30+
function verb(n) { return function (v) { return step([n, v]); }; }
31+
function step(op) {
32+
if (f) throw new TypeError("Generator is already executing.");
33+
while (_) try {
34+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
35+
if (y = 0, t) op = [op[0] & 2, t.value];
36+
switch (op[0]) {
37+
case 0: case 1: t = op; break;
38+
case 4: _.label++; return { value: op[1], done: false };
39+
case 5: _.label++; y = op[1]; op = [0]; continue;
40+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
41+
default:
42+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
43+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
44+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
45+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
46+
if (t[2]) _.ops.pop();
47+
_.trys.pop(); continue;
48+
}
49+
op = body.call(thisArg, _);
50+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
51+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
52+
}
53+
};
54+
exports.__esModule = true;
55+
exports.PdfTeXEngine = exports.CompileResult = exports.EngineStatus = void 0;
56+
var EngineStatus;
57+
(function (EngineStatus) {
58+
EngineStatus[EngineStatus["Init"] = 1] = "Init";
59+
EngineStatus[EngineStatus["Ready"] = 2] = "Ready";
60+
EngineStatus[EngineStatus["Busy"] = 3] = "Busy";
61+
EngineStatus[EngineStatus["Error"] = 4] = "Error";
62+
})(EngineStatus = exports.EngineStatus || (exports.EngineStatus = {}));
63+
var _scriptBaseURL = (function () {
64+
if (typeof document !== 'undefined' && document.currentScript && document.currentScript.src) {
65+
return document.currentScript.src;
66+
}
67+
if (typeof location !== 'undefined' && location.href) {
68+
return location.href;
69+
}
70+
return 'http://localhost/';
71+
})();
72+
var ENGINE_PATH = new URL('swiftlatexpdftex.js', _scriptBaseURL).toString();
73+
var CompileResult = /** @class */ (function () {
74+
function CompileResult() {
75+
this.pdf = undefined;
76+
this.status = -254;
77+
this.log = 'No log';
78+
}
79+
return CompileResult;
80+
}());
81+
exports.CompileResult = CompileResult;
82+
var PdfTeXEngine = /** @class */ (function () {
83+
function PdfTeXEngine() {
84+
this.latexWorker = undefined;
85+
this.latexWorkerStatus = EngineStatus.Init;
86+
}
87+
PdfTeXEngine.prototype.loadEngine = function () {
88+
return __awaiter(this, void 0, void 0, function () {
89+
var _this = this;
90+
return __generator(this, function (_a) {
91+
switch (_a.label) {
92+
case 0:
93+
if (this.latexWorker !== undefined) {
94+
throw new Error('Other instance is running, abort()');
95+
}
96+
this.latexWorkerStatus = EngineStatus.Init;
97+
return [4 /*yield*/, new Promise(function (resolve, reject) {
98+
_this.latexWorker = new Worker(ENGINE_PATH);
99+
_this.latexWorker.onmessage = function (ev) {
100+
var data = ev['data'];
101+
var cmd = data['result'];
102+
if (cmd === 'ok') {
103+
_this.latexWorkerStatus = EngineStatus.Ready;
104+
resolve();
105+
}
106+
else {
107+
_this.latexWorkerStatus = EngineStatus.Error;
108+
reject();
109+
}
110+
};
111+
})];
112+
case 1:
113+
_a.sent();
114+
this.latexWorker.onmessage = function (_) {
115+
};
116+
this.latexWorker.onerror = function (_) {
117+
};
118+
return [2 /*return*/];
119+
}
120+
});
121+
});
122+
};
123+
PdfTeXEngine.prototype.isReady = function () {
124+
return this.latexWorkerStatus === EngineStatus.Ready;
125+
};
126+
PdfTeXEngine.prototype.checkEngineStatus = function () {
127+
if (!this.isReady()) {
128+
throw Error('Engine is still spinning or not ready yet!');
129+
}
130+
};
131+
PdfTeXEngine.prototype.compileLaTeX = function () {
132+
return __awaiter(this, void 0, void 0, function () {
133+
var start_compile_time, res;
134+
var _this = this;
135+
return __generator(this, function (_a) {
136+
switch (_a.label) {
137+
case 0:
138+
this.checkEngineStatus();
139+
this.latexWorkerStatus = EngineStatus.Busy;
140+
start_compile_time = performance.now();
141+
return [4 /*yield*/, new Promise(function (resolve, _) {
142+
_this.latexWorker.onmessage = function (ev) {
143+
var data = ev['data'];
144+
var cmd = data['cmd'];
145+
if (cmd !== "compile")
146+
return;
147+
var result = data['result'];
148+
var log = data['log'];
149+
var status = data['status'];
150+
_this.latexWorkerStatus = EngineStatus.Ready;
151+
console.log('Engine compilation finish ' + (performance.now() - start_compile_time));
152+
var nice_report = new CompileResult();
153+
nice_report.status = status;
154+
nice_report.log = log;
155+
if (result === 'ok') {
156+
var pdf = new Uint8Array(data['pdf']);
157+
nice_report.pdf = pdf;
158+
}
159+
resolve(nice_report);
160+
};
161+
_this.latexWorker.postMessage({ 'cmd': 'compilelatex' });
162+
console.log('Engine compilation start');
163+
})];
164+
case 1:
165+
res = _a.sent();
166+
this.latexWorker.onmessage = function (_) {
167+
};
168+
return [2 /*return*/, res];
169+
}
170+
});
171+
});
172+
};
173+
/* Internal Use */
174+
PdfTeXEngine.prototype.compileFormat = function () {
175+
return __awaiter(this, void 0, void 0, function () {
176+
var _this = this;
177+
return __generator(this, function (_a) {
178+
switch (_a.label) {
179+
case 0:
180+
this.checkEngineStatus();
181+
this.latexWorkerStatus = EngineStatus.Busy;
182+
return [4 /*yield*/, new Promise(function (resolve, reject) {
183+
_this.latexWorker.onmessage = function (ev) {
184+
var data = ev['data'];
185+
var cmd = data['cmd'];
186+
if (cmd !== "compile")
187+
return;
188+
var result = data['result'];
189+
var log = data['log'];
190+
// const status: number = data['status'] as number;
191+
_this.latexWorkerStatus = EngineStatus.Ready;
192+
if (result === 'ok') {
193+
var formatArray = data['pdf']; /* PDF for result */
194+
var formatBlob = new Blob([formatArray], { type: 'application/octet-stream' });
195+
var formatURL_1 = URL.createObjectURL(formatBlob);
196+
setTimeout(function () { URL.revokeObjectURL(formatURL_1); }, 30000);
197+
console.log('Download format file via ' + formatURL_1);
198+
resolve();
199+
}
200+
else {
201+
reject(log);
202+
}
203+
};
204+
_this.latexWorker.postMessage({ 'cmd': 'compileformat' });
205+
})];
206+
case 1:
207+
_a.sent();
208+
this.latexWorker.onmessage = function (_) {
209+
};
210+
return [2 /*return*/];
211+
}
212+
});
213+
});
214+
};
215+
PdfTeXEngine.prototype.setEngineMainFile = function (filename) {
216+
this.checkEngineStatus();
217+
if (this.latexWorker !== undefined) {
218+
this.latexWorker.postMessage({ 'cmd': 'setmainfile', 'url': filename });
219+
}
220+
};
221+
PdfTeXEngine.prototype.writeMemFSFile = function (filename, srccode) {
222+
this.checkEngineStatus();
223+
if (this.latexWorker !== undefined) {
224+
this.latexWorker.postMessage({ 'cmd': 'writefile', 'url': filename, 'src': srccode });
225+
}
226+
};
227+
PdfTeXEngine.prototype.makeMemFSFolder = function (folder) {
228+
this.checkEngineStatus();
229+
if (this.latexWorker !== undefined) {
230+
if (folder === '' || folder === '/') {
231+
return;
232+
}
233+
this.latexWorker.postMessage({ 'cmd': 'mkdir', 'url': folder });
234+
}
235+
};
236+
PdfTeXEngine.prototype.flushCache = function () {
237+
this.checkEngineStatus();
238+
if (this.latexWorker !== undefined) {
239+
// console.warn('Flushing');
240+
this.latexWorker.postMessage({ 'cmd': 'flushcache' });
241+
}
242+
};
243+
PdfTeXEngine.prototype.setTexliveEndpoint = function (url) {
244+
if (this.latexWorker !== undefined) {
245+
this.latexWorker.postMessage({ 'cmd': 'settexliveurl', 'url': url });
246+
this.latexWorker = undefined;
247+
}
248+
};
249+
PdfTeXEngine.prototype.closeWorker = function () {
250+
if (this.latexWorker !== undefined) {
251+
this.latexWorker.postMessage({ 'cmd': 'grace' });
252+
this.latexWorker = undefined;
253+
}
254+
};
255+
return PdfTeXEngine;
256+
}());
257+
exports.PdfTeXEngine = PdfTeXEngine;

public/swiftlatex/swiftlatexpdftex.js

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
1.7 MB
Binary file not shown.

src/App.jsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
htmlToLatex,
2323
summarizeLatexLog,
2424
isWasmLatexEngineConfigured,
25+
ensureWasmLatexEngineReady,
2526
compileWithWasmLatex,
2627
} from './lib/latex';
2728
import { sanitizeEditorHtml, maybeSanitizeEditorHtml } from './lib/sanitize';
@@ -86,6 +87,7 @@ export default function LiveLatexEditor() {
8687
const [imageOverlayRect, setImageOverlayRect] = useState(null);
8788
const lintTimer = useRef(null);
8889
const lintReqId = useRef(0);
90+
const wasmPrewarmStartedRef = useRef(false);
8991
const katexLinkRef = useRef(null);
9092
const katexScriptRef = useRef(null);
9193
const katexLinkInserted = useRef(false);
@@ -2079,6 +2081,26 @@ export default function LiveLatexEditor() {
20792081
};
20802082
}, [latexCode]);
20812083

2084+
useEffect(() => {
2085+
if (!USE_WASM_LATEX) return;
2086+
if (wasmPrewarmStartedRef.current) return;
2087+
wasmPrewarmStartedRef.current = true;
2088+
2089+
let cancelled = false;
2090+
(async () => {
2091+
try {
2092+
await ensureWasmLatexEngineReady();
2093+
} catch (e) {
2094+
if (cancelled) return;
2095+
console.warn('WASM engine prewarm failed:', e);
2096+
}
2097+
})();
2098+
2099+
return () => {
2100+
cancelled = true;
2101+
};
2102+
}, []);
2103+
20822104
// Compile LaTeX remotely and show compiler diagnostics/log
20832105
const showCompileLog = async () => {
20842106
setLogOpen(true);

0 commit comments

Comments
 (0)