mirror of
https://github.com/mii443/obsidian-typst.git
synced 2025-08-22 16:15:34 +00:00
Fix system fonts (#15)
Co-authored-by: paulaingate <49568567+paulaingate@users.noreply.github.com>
This commit is contained in:
@ -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
|
||||
|
@ -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<CompileCommand | true>) => {
|
||||
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))
|
||||
|
84
main.ts
84
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<Blob>; }) => 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<ImageData> {
|
||||
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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
|
49
src/fonts.rs
49
src/fonts.rs
@ -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<Font>,
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
}
|
71
src/lib.rs
71
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<u8>) {
|
||||
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<String> {
|
||||
let f = |_e: JsValue| FileError::Other;
|
||||
Ok(self
|
||||
@ -276,4 +290,37 @@ impl SystemWorld {
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
fn start_embedded_fonts() -> (FontBook, Vec<Font>) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
42
styles.css
42
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);
|
||||
}
|
2
types.d.ts
vendored
2
types.d.ts
vendored
@ -10,4 +10,4 @@ export interface CompileCommand {
|
||||
export interface WorkerRequest {
|
||||
buffer: Int32Array,
|
||||
path: string
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user