Fix system fonts (#15)

Co-authored-by: paulaingate <49568567+paulaingate@users.noreply.github.com>
This commit is contained in:
Jack
2023-07-28 19:14:12 +01:00
committed by GitHub
parent f01148cd8c
commit 9823d33a4f
7 changed files with 177 additions and 107 deletions

View File

@ -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

View File

@ -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
View File

@ -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)
})
})
}
}

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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
View File

@ -10,4 +10,4 @@ export interface CompileCommand {
export interface WorkerRequest {
buffer: Int32Array,
path: string
}
}