diff --git a/CHANGELOG.md b/CHANGELOG.md index 2bfc866..87e4a63 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,11 @@ +# 0.5.1 + +(#11) + +- Renable system font support. +- Change font settings to be a text input for selective font loading. + + # 0.5.0 - Update Typst version to 0.6.0 - Attempt to fix bad canvas output diff --git a/compiler.worker.ts b/compiler.worker.ts index 6ecba39..4a75663 100644 --- a/compiler.worker.ts +++ b/compiler.worker.ts @@ -8,7 +8,6 @@ typst.initSync(wasmBin); let canUseSharedArrayBuffer = new Boolean(false); -// let buffer: Int32Array; let decoder = new TextDecoder() function requestData(path: string): string { @@ -27,34 +26,13 @@ function requestData(path: string): string { throw buffer[0] } -let compiler = new typst.SystemWorld("", requestData) -const compileAttempts = 5; - -// function compile(data: CompileCommand) { -// for (let i = 1; i <= compileAttempts; i += 1) { -// try { -// return compiler.compile(data.source, data.path, data.pixel_per_pt, data.fill, data.size, data.display) -// } catch (error) { -// if (i < compileAttempts && ((error.name == "RuntimeError" && error.message == "unreachable") || (error.name == "Uncaught Error"))) { -// console.warn("Typst compiler crashed, attempting to restart: ", i); -// console.log(data); - -// compiler.free() -// compiler = new typst.SystemWorld("", requestData) -// } else { -// console.log("name", error.name); -// console.log("message", error.message); - -// throw error; -// } -// } -// } -// } - +const compiler = new typst.SystemWorld("", requestData) onmessage = (ev: MessageEvent) => { if (ev.data == true) { canUseSharedArrayBuffer = ev.data + } else if (ev.data instanceof Array) { + ev.data.forEach(font => compiler.add_font(new Uint8Array(font))) } else if ("source" in ev.data) { const data: CompileCommand = ev.data; postMessage(compiler.compile(data.source, data.path, data.pixel_per_pt, data.fill, data.size, data.display)) diff --git a/main.ts b/main.ts index 7a3892d..16cb816 100644 --- a/main.ts +++ b/main.ts @@ -1,7 +1,7 @@ import { App, renderMath, HexString, Platform, Plugin, PluginSettingTab, Setting, loadMathJax, normalizePath } from 'obsidian'; // @ts-ignore -import Worker from "./compiler.worker.ts" +import CompilerWorker from "./compiler.worker.ts" import TypstCanvasElement from 'typst-canvas-element'; import { WorkerRequest } from 'types.js'; @@ -12,6 +12,7 @@ interface TypstPluginSettings { pixel_per_pt: number, search_system: boolean, override_math: boolean, + font_families: string[], preamable: { shared: string, math: string, @@ -25,6 +26,7 @@ const DEFAULT_SETTINGS: TypstPluginSettings = { pixel_per_pt: 3, search_system: false, override_math: false, + font_families: [], preamable: { shared: "#set text(fill: white, size: SIZE)\n#set page(width: WIDTH, height: HEIGHT)", math: "#set page(margin: 0pt)\n#set align(horizon)", @@ -44,7 +46,7 @@ export default class TypstPlugin extends Plugin { fs: any; async onload() { - this.compilerWorker = new Worker(); + this.compilerWorker = (new CompilerWorker() as Worker); if (!Platform.isMobileApp) { this.compilerWorker.postMessage(true); this.fs = require("fs") @@ -53,11 +55,23 @@ export default class TypstPlugin extends Plugin { this.textEncoder = new TextEncoder() await this.loadSettings() + let fonts = await Promise.all( + //@ts-expect-error + (await window.queryLocalFonts() as Array) + .filter((font: { family: string; name: string; }) => this.settings.font_families.contains(font.family.toLowerCase())) + .map( + async (font: { blob: () => Promise; }) => await (await font.blob()).arrayBuffer() + ) + ) + this.compilerWorker.postMessage(fonts, fonts) + + // Setup cutom canvas TypstCanvasElement.compile = (a, b, c, d, e) => this.processThenCompileTypst(a, b, c, d, e) if (customElements.get("typst-renderer") == undefined) { customElements.define("typst-renderer", TypstCanvasElement, { extends: "canvas" }) } + // Setup MathJax await loadMathJax() renderMath("", false); // @ts-expect-error @@ -70,8 +84,10 @@ export default class TypstPlugin extends Plugin { callback: () => this.overrideMathJax(!this.settings.override_math) }) - + // Settings this.addSettingTab(new TypstSettingTab(this.app, this)); + + // Code blocks this.registerMarkdownCodeBlockProcessor("typst", async (source, el, ctx) => { el.appendChild(this.createTypstCanvas("/" + ctx.sourcePath, `${this.settings.preamable.code}\n${source}`, true, false)) }) @@ -80,9 +96,6 @@ export default class TypstPlugin extends Plugin { console.log("loaded Typst Renderer"); } - // async loadCompilerWorker() { - // this.compilerWorker. - // } async compileToTypst(path: string, source: string, size: number, display: boolean): Promise { return await navigator.locks.request("typst renderer compiler", async (lock) => { @@ -269,6 +282,7 @@ class TypstSettingTab extends PluginSettingTab { this.plugin = plugin; } + display(): void { const { containerEl } = this; @@ -315,19 +329,6 @@ class TypstSettingTab extends PluginSettingTab { .setDynamicTooltip() ) - new Setting(containerEl) - .setName("Search System Fonts") - .setDesc(`Whether the plugin should search for system fonts. - This is off by default as it takes around 20 seconds to complete but it gives access to more fonts. - Requires reload of plugin.`) - .addToggle((toggle) => { - toggle.setValue(this.plugin.settings.search_system) - .onChange(async (value) => { - this.plugin.settings.search_system = value; - await this.plugin.saveSettings(); - }) - }) - new Setting(containerEl) .setName("Override Math Blocks") .addToggle((toggle) => { @@ -344,5 +345,50 @@ class TypstSettingTab extends PluginSettingTab { new Setting(containerEl) .setName("Math Block Preamable") .addTextArea((c) => c.setValue(this.plugin.settings.preamable.math).onChange(async (value) => { this.plugin.settings.preamable.math = value; await this.plugin.saveSettings() })) + + //Font family settings + const fontSettings = containerEl.createDiv({ cls: "setting-item font-settings" }) + fontSettings.createDiv({ text: "Fonts", cls: "setting-item-name" }) + fontSettings.createDiv({ text: "Font family names that should be loaded for Typst from your system. Requires a reload on change.", cls: "setting-item-description" }) + + const addFontsDiv = fontSettings.createDiv({ cls: "add-fonts-div" }) + const fontsInput = addFontsDiv.createEl('input', { type: "text", placeholder: "Enter a font family", cls: "font-input", }) + const addFontBtn = addFontsDiv.createEl('button', { text: "Add" }) + + const fontTagsDiv = fontSettings.createDiv({ cls: "font-tags-div" }) + + const addFontTag = async () => { + if (!this.plugin.settings.font_families.contains(fontsInput.value)) { + this.plugin.settings.font_families.push(fontsInput.value.toLowerCase()) + await this.plugin.saveSettings() + } + fontsInput.value = '' + this.renderFontTags(fontTagsDiv) + } + + fontsInput.addEventListener('keydown', async (ev) => { + if (ev.key == "Enter") { + addFontTag() + } + }) + addFontBtn.addEventListener('click', async () => addFontTag()) + + this.renderFontTags(fontTagsDiv) } + + + renderFontTags(fontTagsDiv: HTMLDivElement) { + fontTagsDiv.innerHTML = '' + this.plugin.settings.font_families.forEach((fontFamily) => { + const fontTag = fontTagsDiv.createEl('span', { cls: "font-tag" }) + fontTag.createEl('span', { text: fontFamily, cls: "font-tag-text" }) + const removeBtn = fontTag.createEl('span', { text: "x", cls: "tag-btn" }) + removeBtn.addEventListener('click', async () => { + this.plugin.settings.font_families.remove(fontFamily) + await this.plugin.saveSettings() + this.renderFontTags(fontTagsDiv) + }) + }) + } + } diff --git a/src/fonts.rs b/src/fonts.rs deleted file mode 100644 index 3875074..0000000 --- a/src/fonts.rs +++ /dev/null @@ -1,49 +0,0 @@ -use typst::{ - font::{Font, FontBook}, - util::Bytes, -}; - -/// Searches for fonts. -pub struct FontSearcher { - /// Metadata about all discovered fonts. - pub book: FontBook, - /// Slots that the fonts are loaded into. - pub fonts: Vec, -} - -impl FontSearcher { - pub fn new() -> Self { - Self { - book: FontBook::new(), - fonts: Vec::new(), - } - } - - pub fn add_embedded(&mut self) { - let mut process = |bytes: &'static [u8]| { - let buffer = Bytes::from_static(bytes); - for font in Font::iter(buffer) { - self.book.push(font.info().clone()); - self.fonts.push(font); - } - }; - - macro_rules! add { - ($filename:literal) => { - process(include_bytes!(concat!("../assets/fonts/", $filename))); - }; - } - - // Embed default fonts. - add!("LinLibertine_R.ttf"); - add!("LinLibertine_RB.ttf"); - add!("LinLibertine_RBI.ttf"); - add!("LinLibertine_RI.ttf"); - add!("NewCMMath-Book.otf"); - add!("NewCMMath-Regular.otf"); - add!("DejaVuSansMono.ttf"); - add!("DejaVuSansMono-Bold.ttf"); - add!("DejaVuSansMono-Oblique.ttf"); - add!("DejaVuSansMono-BoldOblique.ttf"); - } -} diff --git a/src/lib.rs b/src/lib.rs index 6113963..c9d116d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -20,10 +20,8 @@ use typst::{ use wasm_bindgen::{prelude::*, Clamped}; use web_sys::ImageData; -mod fonts; mod paths; -use crate::fonts::FontSearcher; use crate::paths::{PathHash, PathSlot}; /// A world that provides access to the operating system. @@ -61,15 +59,15 @@ impl SystemWorld { #[wasm_bindgen(constructor)] pub fn new(root: String, js_read_file: &js_sys::Function) -> SystemWorld { console_error_panic_hook::set_once(); - let mut searcher = FontSearcher::new(); - searcher.add_embedded(); + + let (book, fonts) = SystemWorld::start_embedded_fonts(); Self { root: PathBuf::from(root), main: FileId::detached(), library: Prehashed::new(typst_library::build()), - book: Prehashed::new(searcher.book), - fonts: searcher.fonts, + book: Prehashed::new(book), + fonts, hashes: RefCell::default(), paths: RefCell::default(), today: OnceCell::new(), @@ -79,12 +77,6 @@ impl SystemWorld { } } - fn reset(&mut self) { - self.hashes.borrow_mut().clear(); - self.paths.borrow_mut().clear(); - self.today.take(); - } - pub fn compile( &mut self, text: String, @@ -177,6 +169,22 @@ impl SystemWorld { .into()), } } + + pub fn add_font(&mut self, data: Vec) { + let buffer = Bytes::from(data); + let mut font_infos = Vec::new(); + for font in Font::iter(buffer) { + font_infos.push(font.info().clone()); + self.fonts.push(font) + } + if font_infos.len() > 0 { + self.book.update(|b| { + for info in font_infos { + b.push(info) + } + }); + } + } } impl World for SystemWorld { @@ -210,6 +218,12 @@ impl World for SystemWorld { } impl SystemWorld { + fn reset(&mut self) { + self.hashes.borrow_mut().clear(); + self.paths.borrow_mut().clear(); + self.today.take(); + } + fn read_file(&self, path: &Path) -> FileResult { let f = |_e: JsValue| FileError::Other; Ok(self @@ -276,4 +290,37 @@ impl SystemWorld { }) })) } + + fn start_embedded_fonts() -> (FontBook, Vec) { + let mut book = FontBook::new(); + let mut fonts = Vec::new(); + + let mut process = |bytes: &'static [u8]| { + let buffer = Bytes::from_static(bytes); + for font in Font::iter(buffer) { + book.push(font.info().clone()); + fonts.push(font); + } + }; + + macro_rules! add { + ($filename:literal) => { + process(include_bytes!(concat!("../assets/fonts/", $filename))); + }; + } + + // Embed default fonts. + add!("LinLibertine_R.ttf"); + add!("LinLibertine_RB.ttf"); + add!("LinLibertine_RBI.ttf"); + add!("LinLibertine_RI.ttf"); + add!("NewCMMath-Book.otf"); + add!("NewCMMath-Regular.otf"); + add!("DejaVuSansMono.ttf"); + add!("DejaVuSansMono-Bold.ttf"); + add!("DejaVuSansMono-Oblique.ttf"); + add!("DejaVuSansMono-BoldOblique.ttf"); + + return (book, fonts); + } } diff --git a/styles.css b/styles.css index 55e9342..72745ae 100644 --- a/styles.css +++ b/styles.css @@ -4,7 +4,8 @@ textarea { resize: vertical; } -.setting-item:has(textarea) { +.setting-item:has(textarea), +.font-settings { flex-direction: column; gap: 0.5em; align-items: stretch; @@ -12,4 +13,43 @@ textarea { .is-disabled { display: none; +} + +.add-fonts-div { + display: flex; + gap: 0.5em; +} + +.font-input { + flex-grow: 1; +} + +.font-tags-div { + display: flex; + flex-wrap: wrap; + gap: 0.5em; +} + +.font-tag { + background-color: var(--interactive-normal); /* #363636 */ + border-radius: 20px; + padding: 0.5em 0.6em 0.5em 1em; +} + +.tag-btn { + margin-left: 0.6em; + padding: 0.4em; +} + +.font-tag-text { + font-size: var(--font-ui-small); +} + +button, +.tag-btn { + cursor: pointer; +} + +.tag-btn:hover { + color: var(--interactive-accent); } \ No newline at end of file diff --git a/types.d.ts b/types.d.ts index 718344c..7a7384a 100644 --- a/types.d.ts +++ b/types.d.ts @@ -10,4 +10,4 @@ export interface CompileCommand { export interface WorkerRequest { buffer: Int32Array, path: string -} \ No newline at end of file +}