refactor: simplify world font impl and add docs (#1645)

* fix: remove unnecessary &mut in FontResolverImpl::append_font()

* refactor: better readablity in FontResolverImpl::rebuild()

* feat: FontResovlerImpl::clone_and_rebuild()

Change FontSlot to store Arc to QueryRef<Font, FontLoader>, so we can
clone FontSlot and share the cached loaded font between different slots.

Since CompilerUniverse only holds a immutable Arc to FontResovlerImpl,
adding clone_and_rebuild method allow us to append and rebuild without
mutable access to FontResovlerImpl.

* refactor: remove partial_book and modify methods from FontResolverImpl

partial_book is removed from FontResolverImpl, all the modifying methods
are also removed.
new method get_fonts and new_with_fonts are added.
If you want to modify fonts, you can get all fonts and modify them, then
call new_with_fonts to create a new FontResolverImpl.

* refactor: SystemFontSearch, BrowserFontSearch

- Both font searcher now is just a wrapper for Vec<(FontInfo,
  FontSlot)>, typst::FontBook will be built when converting into
  FontResolverImpl.

- Provide a method with_fonts_mut() so user can get a direct mutable
  access to the under lying fonts vector to do any changes.

- Support searcher -> resolver and resolver -> searcher conversion.

* refactor: api change to biulder pattern

Method new_with_resolver() is added since CompilerUniverse only holds
Arc reference to resolver, and we can't move it out.

* fix: add fontdb::Database to SystemFontSearcher

Store fonts info in db, load them in bulk when calling flush()

* chore: merge `FontResolverImpl` and `TinymistFontResolver`

* feat: remove font profile feature

* feat: remove project font

* docs: comment the font resolver trait

* docs: comment the font slot struct

* docs: comment the crate

* test: move system tests

* dev: clean resolver

* dev: clean searchers

* dev: clean par iters

* feat: enrich memory font resolver

* todo: useless reuse

* dev: remove reuse api

* dev: build font book in `fn build`

* fix: make clippy happy

* fix: bad use

---------

Co-authored-by: c0per <60544162+c0per@users.noreply.github.com>
This commit is contained in:
Myriad-Dreamin 2025-04-15 22:23:13 +08:00 committed by GitHub
parent 031b56205b
commit 39d13c83f6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 449 additions and 760 deletions

View file

@ -1,188 +0,0 @@
//! Font resolver implementation.
pub use crate::world::base::font::*;
use core::fmt;
use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};
use tinymist_world::debug_loc::DataSource;
use tinymist_world::font::system::SystemFontSearcher;
use typst::text::{Font, FontBook, FontInfo};
use typst::utils::LazyHash;
use crate::world::vfs::Bytes;
#[derive(Debug)]
/// The default FontResolver implementation.
pub struct TinymistFontResolver {
font_paths: Vec<PathBuf>,
book: LazyHash<FontBook>,
partial_book: Arc<Mutex<PartialFontBook>>,
fonts: Vec<FontSlot>,
}
impl TinymistFontResolver {
/// Create a new TinymistFontResolver.
pub fn new(
font_paths: Vec<PathBuf>,
book: FontBook,
partial_book: Arc<Mutex<PartialFontBook>>,
fonts: Vec<FontSlot>,
) -> Self {
Self {
font_paths,
book: LazyHash::new(book),
partial_book,
fonts,
}
}
/// Get the number of fonts.
pub fn len(&self) -> usize {
self.fonts.len()
}
/// Check if the font resolver is empty.
pub fn is_empty(&self) -> bool {
self.fonts.is_empty()
}
/// Get the configured font paths.
pub fn font_paths(&self) -> &[PathBuf] {
&self.font_paths
}
/// Get the loaded fonts.
pub fn loaded_fonts(&self) -> impl Iterator<Item = (usize, Font)> + '_ {
let slots_with_index = self.fonts.iter().enumerate();
slots_with_index.flat_map(|(idx, slot)| {
let maybe_font = slot.get_uninitialized().flatten();
maybe_font.map(|font| (idx, font))
})
}
/// Describe a font.
pub fn describe_font(&self, font: &Font) -> Option<Arc<DataSource>> {
let f = Some(Some(font.clone()));
for slot in &self.fonts {
if slot.get_uninitialized() == f {
return slot.description.clone();
}
}
None
}
/// Describe a font by id.
pub fn describe_font_by_id(&self, id: usize) -> Option<Arc<DataSource>> {
self.fonts[id].description.clone()
}
/// Change the font data.
pub fn modify_font_data(&mut self, idx: usize, buffer: Bytes) {
let mut font_book = self.partial_book.lock().unwrap();
for (i, info) in FontInfo::iter(buffer.as_slice()).enumerate() {
let buffer = buffer.clone();
let modify_idx = if i > 0 { None } else { Some(idx) };
font_book.push((
modify_idx,
info,
FontSlot::new(Box::new(BufferFontLoader {
buffer: Some(buffer),
index: i as u32,
})),
));
}
}
/// Append a font.
pub fn append_font(&mut self, info: FontInfo, slot: FontSlot) {
let mut font_book = self.partial_book.lock().unwrap();
font_book.push((None, info, slot));
}
/// Rebuild the font resolver.
pub fn rebuild(&mut self) {
let mut partial_book = self.partial_book.lock().unwrap();
if !partial_book.partial_hit {
return;
}
partial_book.revision += 1;
let mut book = FontBook::default();
let mut font_changes = HashMap::new();
let mut new_fonts = vec![];
for (idx, info, slot) in partial_book.changes.drain(..) {
if let Some(idx) = idx {
font_changes.insert(idx, (info, slot));
} else {
new_fonts.push((info, slot));
}
}
partial_book.changes.clear();
partial_book.partial_hit = false;
let mut font_slots = Vec::new();
font_slots.append(&mut self.fonts);
self.fonts.clear();
for (i, slot_ref) in font_slots.iter_mut().enumerate() {
let (info, slot) = if let Some((_, v)) = font_changes.remove_entry(&i) {
v
} else {
book.push(self.book.info(i).unwrap().clone());
continue;
};
book.push(info);
*slot_ref = slot;
}
for (info, slot) in new_fonts.drain(..) {
book.push(info);
font_slots.push(slot);
}
self.book = LazyHash::new(book);
self.fonts = font_slots;
}
}
impl FontResolver for TinymistFontResolver {
fn font_book(&self) -> &LazyHash<FontBook> {
&self.book
}
fn font(&self, idx: usize) -> Option<Font> {
self.fonts.get(idx)?.get_or_init()
}
fn get_by_info(&self, info: &FontInfo) -> Option<Font> {
FontResolver::default_get_by_info(self, info)
}
}
impl fmt::Display for TinymistFontResolver {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (idx, slot) in self.fonts.iter().enumerate() {
writeln!(f, "{:?} -> {:?}", idx, slot.get_uninitialized())?;
}
Ok(())
}
}
impl From<SystemFontSearcher> for TinymistFontResolver {
fn from(searcher: SystemFontSearcher) -> Self {
TinymistFontResolver::new(
searcher.font_paths,
searcher.book,
Arc::new(Mutex::new(PartialFontBook::default())),
searcher.fonts,
)
}
}

View file

@ -5,8 +5,6 @@ mod compiler;
mod entry;
mod model;
#[cfg(feature = "lsp")]
pub mod font;
#[cfg(feature = "lsp")]
mod lock;
#[cfg(feature = "lsp")]

View file

@ -18,7 +18,7 @@ use typst::Features;
use crate::ProjectInput;
use crate::font::TinymistFontResolver;
use crate::world::font::FontResolverImpl;
use crate::{CompiledArtifact, Interrupt};
/// Compiler feature for LSP universe and worlds without typst.ts to implement
@ -27,8 +27,8 @@ use crate::{CompiledArtifact, Interrupt};
pub struct LspCompilerFeat;
impl CompilerFeat for LspCompilerFeat {
/// Uses [`TinymistFontResolver`] directly.
type FontResolver = TinymistFontResolver;
/// Uses [`FontResolverImpl`] directly.
type FontResolver = FontResolverImpl;
/// It accesses a physical file system.
type AccessModel = SystemAccessModel;
/// It performs native HTTP requests for fetching package data.
@ -214,7 +214,7 @@ impl LspUniverseBuilder {
features: Features,
inputs: ImmutDict,
package_registry: HttpRegistry,
font_resolver: Arc<TinymistFontResolver>,
font_resolver: Arc<FontResolverImpl>,
) -> LspUniverse {
let package_registry = Arc::new(package_registry);
let resolver = Arc::new(RegistryPathMapper::new(package_registry.clone()));
@ -237,27 +237,25 @@ impl LspUniverseBuilder {
}
/// Resolve fonts from given options.
pub fn only_embedded_fonts() -> Result<TinymistFontResolver> {
pub fn only_embedded_fonts() -> Result<FontResolverImpl> {
let mut searcher = SystemFontSearcher::new();
searcher.resolve_opts(CompileFontOpts {
font_profile_cache_path: Default::default(),
font_paths: vec![],
no_system_fonts: true,
with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(),
})?;
Ok(searcher.into())
Ok(searcher.build())
}
/// Resolve fonts from given options.
pub fn resolve_fonts(args: CompileFontArgs) -> Result<TinymistFontResolver> {
pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
let mut searcher = SystemFontSearcher::new();
searcher.resolve_opts(CompileFontOpts {
font_profile_cache_path: Default::default(),
font_paths: args.font_paths,
no_system_fonts: args.ignore_system_fonts,
with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(),
})?;
Ok(searcher.into())
Ok(searcher.build())
}
/// Resolve package registry from given options.

View file

@ -45,6 +45,7 @@ tinymist-std.workspace = true
tinymist-vfs.workspace = true
typst.workspace = true
typst-assets.workspace = true
ttf-parser.workspace = true
wasm-bindgen = { workspace = true, optional = true }
web-sys = { workspace = true, optional = true, features = ["console"] }
@ -73,5 +74,8 @@ system = [
"tinymist-vfs/system",
]
[package.metadata.docs.rs]
features = ["browser", "system"]
[lints]
workspace = true

View file

@ -38,10 +38,6 @@ pub struct CompileOpts {
#[serde_as]
#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
pub struct CompileFontOpts {
/// Path to font profile for cache
#[serde(rename = "fontProfileCachePath")]
pub font_profile_cache_path: PathBuf,
/// will remove later
#[serde(rename = "fontPaths")]
pub font_paths: Vec<PathBuf>,
@ -59,7 +55,6 @@ pub struct CompileFontOpts {
impl From<CompileOpts> for CompileFontOpts {
fn from(opts: CompileOpts) -> Self {
Self {
font_profile_cache_path: opts.font_profile_cache_path,
font_paths: opts.font_paths,
no_system_fonts: opts.no_system_fonts,
with_embedded_fonts: opts.with_embedded_fonts,

View file

@ -3,12 +3,12 @@ use typst::text::Font;
use crate::Bytes;
/// A FontLoader would help load a font from somewhere.
/// A FontLoader helps load a font from somewhere.
pub trait FontLoader {
fn load(&mut self) -> Option<Font>;
}
/// Load font from a buffer.
/// Loads font from a buffer.
pub struct BufferFontLoader {
pub buffer: Option<Bytes>,
pub index: u32,
@ -20,6 +20,7 @@ impl FontLoader for BufferFontLoader {
}
}
/// Loads font from a reader.
pub struct LazyBufferFontLoader<R> {
pub read: Option<R>,
pub index: u32,

View file

@ -0,0 +1,88 @@
use std::sync::Arc;
use rayon::iter::ParallelIterator;
use typst::foundations::Bytes;
use typst::text::{FontBook, FontInfo};
use crate::debug_loc::{DataSource, MemoryDataSource};
use crate::font::{BufferFontLoader, FontResolverImpl, FontSlot};
/// A memory font searcher.
#[derive(Debug)]
pub struct MemoryFontSearcher {
pub fonts: Vec<(FontInfo, FontSlot)>,
}
impl Default for MemoryFontSearcher {
fn default() -> Self {
Self::new()
}
}
impl MemoryFontSearcher {
/// Creates an in-memory searcher.
pub fn new() -> Self {
Self { fonts: vec![] }
}
/// Adds an in-memory font.
pub fn add_memory_font(&mut self, data: Bytes) {
self.add_memory_fonts(rayon::iter::once(data));
}
/// Adds in-memory fonts.
pub fn add_memory_fonts(&mut self, data: impl ParallelIterator<Item = Bytes>) {
let source = DataSource::Memory(MemoryDataSource {
name: "<memory>".to_owned(),
});
self.extend_bytes(data.map(|data| (data, Some(source.clone()))));
}
/// Adds a number of raw font resources.
///
/// Note: if you would like to reuse font resources across builds, use
/// [`Self::extend_bytes`] instead.
pub fn extend(&mut self, items: impl IntoIterator<Item = (FontInfo, FontSlot)>) {
self.fonts.extend(items);
}
/// Adds a number of font data to the font resolver. The builder will reuse
/// the existing font resources according to the bytes.
pub fn extend_bytes(
&mut self,
items: impl ParallelIterator<Item = (Bytes, Option<DataSource>)>,
) {
let loaded = items.flat_map(|(data, desc)| {
let count = ttf_parser::fonts_in_collection(&data).unwrap_or(1);
let desc = desc.map(Arc::new);
(0..count)
.flat_map(|index| {
let info = FontInfo::new(&data, index)?;
let mut slot = FontSlot::new(BufferFontLoader {
buffer: Some(data.clone()),
index,
});
if let Some(desc) = desc.clone() {
slot = slot.with_describe_arc(desc);
}
Some((info, slot))
})
.collect::<Vec<_>>()
});
self.extend(loaded.collect::<Vec<_>>());
}
/// Builds a FontResolverImpl.
pub fn build(self) -> FontResolverImpl {
let slots = self.fonts.iter().map(|(_, slot)| slot.clone()).collect();
let book = FontBook::from_infos(self.fonts.into_iter().map(|(info, _)| info));
FontResolverImpl::new(Vec::new(), book, slots)
}
}
#[deprecated(note = "use [`MemoryFontSearcher`] instead")]
pub type MemoryFontBuilder = MemoryFontSearcher;

View file

@ -1,25 +1,37 @@
//! The font implementations for typst worlds.
//!
//! The core concept is the [`FontResolver`], implemented by
//! [`FontResolverImpl`].
//!
//! You can construct a [`FontResolverImpl`] on systems, browsers or simply
//! without touching any external environment. See the [`system`], [`web`] and
//! [`pure`] crates for more details.
//!
//! The [`FontResolverImpl`] has a lot of [`FontSlot`] objects and allow to load
//! font resources lazily.
//!
//! There are also other structs, which help store and load [`FontInfo`] objects
//! in the local file system or the remote machine. See the [`cache`] and
//! [`profile`] crates for more details.
pub mod cache;
pub(crate) mod info;
pub(crate) mod loader;
pub(crate) mod profile;
pub(crate) mod resolver;
pub(crate) mod slot;
pub use loader::*;
pub use profile::*;
pub use resolver::*;
pub use slot::*;
#[cfg(feature = "system")]
pub mod system;
#[cfg(feature = "web")]
pub mod web;
pub mod cache;
pub(crate) mod info;
pub mod pure;
pub(crate) mod profile;
pub use profile::*;
pub(crate) mod loader;
pub use loader::*;
pub(crate) mod slot;
pub use slot::*;
pub(crate) mod resolver;
pub use resolver::*;
pub(crate) mod partial_book;
pub use partial_book::*;
pub mod memory;
#[deprecated(note = "use memory module instead")]
pub use memory as pure;

View file

@ -1,52 +0,0 @@
use core::fmt;
use typst::text::{FontFlags, FontInfo, FontVariant};
use crate::font::FontSlot;
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub struct FontInfoKey {
pub family: String,
pub variant: FontVariant,
pub flags: FontFlags,
}
impl From<&FontInfo> for FontInfoKey {
fn from(info: &FontInfo) -> Self {
Self {
family: info.family.clone(),
variant: info.variant,
flags: info.flags,
}
}
}
#[derive(Debug, Default)]
pub struct PartialFontBook {
pub revision: usize,
pub partial_hit: bool,
pub changes: Vec<(Option<usize>, FontInfo, FontSlot)>,
}
impl PartialFontBook {
pub fn push(&mut self, change: (Option<usize>, FontInfo, FontSlot)) {
self.partial_hit = true;
self.changes.push(change);
}
}
impl fmt::Display for PartialFontBook {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (idx, info, slot) in &self.changes {
writeln!(
f,
"{:?}: {} -> {:?}\n",
idx,
info.family,
slot.get_uninitialized()
)?;
}
Ok(())
}
}

View file

@ -1,56 +0,0 @@
use typst::foundations::Bytes;
use typst::text::{FontBook, FontInfo};
use crate::debug_loc::{DataSource, MemoryDataSource};
use crate::font::{BufferFontLoader, FontResolverImpl, FontSlot};
/// memory font builder.
#[derive(Debug)]
pub struct MemoryFontBuilder {
pub book: FontBook,
pub fonts: Vec<FontSlot>,
}
impl Default for MemoryFontBuilder {
fn default() -> Self {
Self::new()
}
}
impl From<MemoryFontBuilder> for FontResolverImpl {
fn from(searcher: MemoryFontBuilder) -> Self {
FontResolverImpl::new(
Vec::new(),
searcher.book,
Default::default(),
searcher.fonts,
Default::default(),
)
}
}
impl MemoryFontBuilder {
/// Create a new, empty system searcher.
pub fn new() -> Self {
Self {
book: FontBook::new(),
fonts: vec![],
}
}
/// Add an in-memory font.
pub fn add_memory_font(&mut self, data: Bytes) {
for (index, info) in FontInfo::iter(&data).enumerate() {
self.book.push(info.clone());
self.fonts.push(
FontSlot::new_boxed(BufferFontLoader {
buffer: Some(data.clone()),
index: index as u32,
})
.describe(DataSource::Memory(MemoryDataSource {
name: "<memory>".to_owned(),
})),
);
}
}
}

View file

@ -1,106 +1,176 @@
use core::fmt;
use std::{
collections::HashMap,
num::NonZeroUsize,
path::PathBuf,
sync::{Arc, Mutex},
};
use std::{num::NonZeroUsize, path::PathBuf, sync::Arc};
use typst::text::{Font, FontBook, FontInfo};
use typst::utils::LazyHash;
use super::{BufferFontLoader, FontProfile, FontSlot, PartialFontBook};
use super::FontSlot;
use crate::debug_loc::DataSource;
use crate::Bytes;
/// A FontResolver can resolve a font by index.
/// It also reuse FontBook for font-related query.
/// The index is the index of the font in the `FontBook.infos`.
/// A [`FontResolver`] can resolve a font by index.
/// It also provides FontBook for typst to query fonts.
pub trait FontResolver {
/// An optionally implemented revision function for users, e.g. the `World`.
///
/// A user of [`FontResolver`] will differentiate the `prev` and `next`
/// revisions to determine if the underlying state of fonts has changed.
///
/// - If either `prev` or `next` is `None`, the world's revision is always
/// increased.
/// - Otherwise, the world's revision is increased if `prev != next`.
///
/// If the revision of fonts is changed, the world will invalidate all
/// related caches and increase its revision.
fn revision(&self) -> Option<NonZeroUsize> {
None
}
/// The font book interface for typst.
fn font_book(&self) -> &LazyHash<FontBook>;
fn font(&self, idx: usize) -> Option<Font>;
/// Gets the font slot by index.
/// The index parameter is the index of the font in the `FontBook.infos`.
fn slot(&self, index: usize) -> Option<&FontSlot>;
/// Gets the font by index.
/// The index parameter is the index of the font in the `FontBook.infos`.
fn font(&self, index: usize) -> Option<Font>;
/// Gets a font by its info.
fn get_by_info(&self, info: &FontInfo) -> Option<Font> {
self.default_get_by_info(info)
}
/// The default implementation of [`FontResolver::get_by_info`].
fn default_get_by_info(&self, info: &FontInfo) -> Option<Font> {
// todo: font alternative
// The selected font should at least has the first codepoint in the
// coverage. We achieve it by querying the font book with `alternative_text`.
// todo: better font alternative
let mut alternative_text = 'c';
if let Some(codepoint) = info.coverage.iter().next() {
alternative_text = std::char::from_u32(codepoint).unwrap();
};
let idx = self
let index = self
.font_book()
.select_fallback(Some(info), info.variant, &alternative_text.to_string())
.unwrap();
self.font(idx)
}
fn get_by_info(&self, info: &FontInfo) -> Option<Font> {
self.default_get_by_info(info)
self.font(index)
}
}
impl<T: FontResolver> FontResolver for Arc<T> {
fn font_book(&self) -> &LazyHash<FontBook> {
self.as_ref().font_book()
}
fn slot(&self, index: usize) -> Option<&FontSlot> {
self.as_ref().slot(index)
}
fn font(&self, index: usize) -> Option<Font> {
self.as_ref().font(index)
}
fn get_by_info(&self, info: &FontInfo) -> Option<Font> {
self.as_ref().get_by_info(info)
}
}
pub trait ReusableFontResolver: FontResolver {
/// Reuses the font resolver.
fn slots(&self) -> impl Iterator<Item = FontSlot>;
}
impl<T: ReusableFontResolver> ReusableFontResolver for Arc<T> {
fn slots(&self) -> impl Iterator<Item = FontSlot> {
self.as_ref().slots()
}
}
#[derive(Debug)]
/// The default FontResolver implementation.
///
/// This is constructed by:
/// - The [`crate::font::system::SystemFontSearcher`] on operating systems.
/// - The [`crate::font::web::BrowserFontSearcher`] on browsers.
/// - Otherwise, [`crate::font::pure::MemoryFontBuilder`] in memory.
#[derive(Debug)]
pub struct FontResolverImpl {
font_paths: Vec<PathBuf>,
book: LazyHash<FontBook>,
partial_book: Arc<Mutex<PartialFontBook>>,
fonts: Vec<FontSlot>,
profile: FontProfile,
pub(crate) font_paths: Vec<PathBuf>,
pub(crate) book: LazyHash<FontBook>,
pub(crate) slots: Vec<FontSlot>,
}
impl FontResolverImpl {
pub fn new(
font_paths: Vec<PathBuf>,
book: FontBook,
partial_book: Arc<Mutex<PartialFontBook>>,
fonts: Vec<FontSlot>,
profile: FontProfile,
) -> Self {
/// Creates a new font resolver.
pub fn new(font_paths: Vec<PathBuf>, book: FontBook, slots: Vec<FontSlot>) -> Self {
Self {
font_paths,
book: LazyHash::new(book),
partial_book,
fonts,
profile,
slots,
}
}
pub fn new_with_fonts(
font_paths: Vec<PathBuf>,
fonts: impl Iterator<Item = (FontInfo, FontSlot)>,
) -> Self {
let mut book = FontBook::new();
let mut slots = Vec::<FontSlot>::new();
for (info, slot) in fonts {
book.push(info);
slots.push(slot);
}
Self {
font_paths,
book: LazyHash::new(book),
slots,
}
}
/// Gets the number of fonts in the resolver.
pub fn len(&self) -> usize {
self.fonts.len()
self.slots.len()
}
/// Tests whether the resolver doesn't hold any fonts.
pub fn is_empty(&self) -> bool {
self.fonts.is_empty()
}
pub fn profile(&self) -> &FontProfile {
&self.profile
self.slots.is_empty()
}
/// Gets the user-specified font paths.
pub fn font_paths(&self) -> &[PathBuf] {
&self.font_paths
}
pub fn partial_resolved(&self) -> bool {
self.partial_book.lock().unwrap().partial_hit
/// Returns an iterator over all fonts in the resolver.
#[deprecated(note = "use `fonts` instead")]
pub fn get_fonts(&self) -> impl Iterator<Item = (&FontInfo, &FontSlot)> {
self.fonts()
}
pub fn loaded_fonts(&self) -> impl Iterator<Item = (usize, Font)> + '_ {
let slots_with_index = self.fonts.iter().enumerate();
/// Returns an iterator over all fonts in the resolver.
pub fn fonts(&self) -> impl Iterator<Item = (&FontInfo, &FontSlot)> {
self.slots.iter().enumerate().map(|(idx, slot)| {
let info = self.book.info(idx).unwrap();
(info, slot)
})
}
slots_with_index.flat_map(|(idx, slot)| {
/// Returns an iterator over all loaded fonts in the resolver.
pub fn loaded_fonts(&self) -> impl Iterator<Item = (usize, Font)> + '_ {
self.slots.iter().enumerate().flat_map(|(idx, slot)| {
let maybe_font = slot.get_uninitialized().flatten();
maybe_font.map(|font| (idx, font))
})
}
/// Describes the source of a font.
pub fn describe_font(&self, font: &Font) -> Option<Arc<DataSource>> {
let f = Some(Some(font.clone()));
for slot in &self.fonts {
for slot in &self.slots {
if slot.get_uninitialized() == f {
return slot.description.clone();
}
@ -108,76 +178,14 @@ impl FontResolverImpl {
None
}
pub fn modify_font_data(&mut self, idx: usize, buffer: Bytes) {
let mut font_book = self.partial_book.lock().unwrap();
for (i, info) in FontInfo::iter(buffer.as_slice()).enumerate() {
let buffer = buffer.clone();
let modify_idx = if i > 0 { None } else { Some(idx) };
font_book.push((
modify_idx,
info,
FontSlot::new(Box::new(BufferFontLoader {
buffer: Some(buffer),
index: i as u32,
})),
));
}
/// Describes the source of a font by id.
pub fn describe_font_by_id(&self, id: usize) -> Option<Arc<DataSource>> {
self.slots[id].description.clone()
}
pub fn append_font(&mut self, info: FontInfo, slot: FontSlot) {
let mut font_book = self.partial_book.lock().unwrap();
font_book.push((None, info, slot));
}
pub fn rebuild(&mut self) {
let mut partial_book = self.partial_book.lock().unwrap();
if !partial_book.partial_hit {
return;
}
partial_book.revision += 1;
let mut book = FontBook::default();
let mut font_changes = HashMap::new();
let mut new_fonts = vec![];
for (idx, info, slot) in partial_book.changes.drain(..) {
if let Some(idx) = idx {
font_changes.insert(idx, (info, slot));
} else {
new_fonts.push((info, slot));
}
}
partial_book.changes.clear();
partial_book.partial_hit = false;
let mut font_slots = Vec::new();
font_slots.append(&mut self.fonts);
self.fonts.clear();
for (i, slot_ref) in font_slots.iter_mut().enumerate() {
let (info, slot) = if let Some((_, v)) = font_changes.remove_entry(&i) {
v
} else {
book.push(self.book.info(i).unwrap().clone());
continue;
};
book.push(info);
*slot_ref = slot;
}
for (info, slot) in new_fonts.drain(..) {
book.push(info);
font_slots.push(slot);
}
self.book = LazyHash::new(book);
self.fonts = font_slots;
}
pub fn add_glyph_packs(&mut self) {
todo!()
pub fn with_font_paths(mut self, font_paths: Vec<PathBuf>) -> Self {
self.font_paths = font_paths;
self
}
}
@ -186,8 +194,12 @@ impl FontResolver for FontResolverImpl {
&self.book
}
fn slot(&self, idx: usize) -> Option<&FontSlot> {
self.slots.get(idx)
}
fn font(&self, idx: usize) -> Option<Font> {
self.fonts[idx].get_or_init()
self.slots[idx].get_or_init()
}
fn get_by_info(&self, info: &FontInfo) -> Option<Font> {
@ -197,10 +209,16 @@ impl FontResolver for FontResolverImpl {
impl fmt::Display for FontResolverImpl {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (idx, slot) in self.fonts.iter().enumerate() {
for (idx, slot) in self.slots.iter().enumerate() {
writeln!(f, "{:?} -> {:?}", idx, slot.get_uninitialized())?;
}
Ok(())
}
}
impl ReusableFontResolver for FontResolverImpl {
fn slots(&self) -> impl Iterator<Item = FontSlot> {
self.slots.iter().cloned()
}
}

View file

@ -9,38 +9,57 @@ use crate::font::FontLoader;
type FontSlotInner = QueryRef<Option<Font>, (), Box<dyn FontLoader + Send>>;
/// Lazy Font Reference, load as needed.
/// A font slot holds a reference to a font resource. It can be created from
/// - either a callback to load the font lazily, using [`Self::new`] or
/// [`Self::new_boxed`],
/// - or a loaded font, using [`Self::new_loaded`].
#[derive(Clone)]
pub struct FontSlot {
inner: FontSlotInner,
inner: Arc<FontSlotInner>,
pub description: Option<Arc<DataSource>>,
}
impl FontSlot {
pub fn with_value(f: Option<Font>) -> Self {
/// Creates a font slot to load.
pub fn new<F: FontLoader + Send + 'static>(f: F) -> Self {
Self::new_boxed(Box::new(f))
}
/// Creates a font slot from a boxed font loader trait object.
pub fn new_boxed(f: Box<dyn FontLoader + Send>) -> Self {
Self {
inner: FontSlotInner::with_value(f),
inner: Arc::new(FontSlotInner::with_context(f)),
description: None,
}
}
pub fn new(f: Box<dyn FontLoader + Send>) -> Self {
/// Creates a font slot with a loaded font.
pub fn new_loaded(f: Option<Font>) -> Self {
Self {
inner: FontSlotInner::with_context(f),
inner: Arc::new(FontSlotInner::with_value(f)),
description: None,
}
}
pub fn new_boxed<F: FontLoader + Send + 'static>(f: F) -> Self {
Self::new(Box::new(f))
/// Attaches a description to the font slot.
pub fn with_describe(self, desc: DataSource) -> Self {
self.with_describe_arc(Arc::new(desc))
}
pub fn describe(self, desc: DataSource) -> Self {
/// Attaches a description to the font slot.
pub fn with_describe_arc(self, desc: Arc<DataSource>) -> Self {
Self {
inner: self.inner,
description: Some(Arc::new(desc)),
description: Some(desc),
}
}
/// Gets or make the font load result.
pub fn get_or_init(&self) -> Option<Font> {
let res = self.inner.compute_with_context(|mut c| Ok(c.load()));
res.unwrap().clone()
}
/// Gets the reference to the font load result (possible uninitialized).
///
/// Returns `None` if the cell is empty, or being initialized. This
@ -51,12 +70,6 @@ impl FontSlot {
.cloned()
.map(|e| e.ok().flatten())
}
/// Gets or make the font load result.
pub fn get_or_init(&self) -> Option<Font> {
let res = self.inner.compute_with_context(|mut c| Ok(c.load()));
res.unwrap().clone()
}
}
impl fmt::Debug for FontSlot {

View file

@ -1,121 +1,50 @@
use std::{
borrow::Cow,
collections::HashMap,
fs::File,
path::{Path, PathBuf},
sync::{Arc, Mutex},
};
use std::borrow::Cow;
use std::path::{Path, PathBuf};
use fontdb::Database;
use rayon::iter::{IntoParallelIterator, ParallelIterator};
use sha2::{Digest, Sha256};
use tinymist_std::error::prelude::*;
use tinymist_vfs::system::LazyFile;
use typst::{
diag::{FileError, FileResult},
foundations::Bytes,
text::{FontBook, FontInfo},
};
use typst::diag::{FileError, FileResult};
use typst::foundations::Bytes;
use typst::text::FontInfo;
use super::{
BufferFontLoader, FontProfile, FontProfileItem, FontResolverImpl, FontSlot,
LazyBufferFontLoader, PartialFontBook,
};
use crate::debug_loc::{DataSource, FsDataSource, MemoryDataSource};
use crate::{build_info, config::CompileFontOpts};
use super::memory::MemoryFontSearcher;
use super::{FontResolverImpl, FontSlot, LazyBufferFontLoader};
use crate::config::CompileFontOpts;
use crate::debug_loc::{DataSource, FsDataSource};
#[derive(Debug, Default)]
struct FontProfileRebuilder {
path_items: HashMap<PathBuf, FontProfileItem>,
pub profile: FontProfile,
can_profile: bool,
}
impl FontProfileRebuilder {
/// Index the fonts in the file at the given path.
#[allow(dead_code)]
fn search_file(&mut self, path: impl AsRef<Path>) -> Option<&FontProfileItem> {
let path = path.as_ref().canonicalize().unwrap();
if let Some(item) = self.path_items.get(&path) {
return Some(item);
}
if let Ok(mut file) = File::open(&path) {
let hash = if self.can_profile {
let mut hasher = Sha256::new();
let _bytes_written = std::io::copy(&mut file, &mut hasher).unwrap();
let hash = hasher.finalize();
format!("sha256:{}", hex::encode(hash))
} else {
"".to_owned()
};
let mut profile_item = FontProfileItem::new("path", hash);
profile_item.set_path(path.to_str().unwrap().to_owned());
profile_item.set_mtime(file.metadata().unwrap().modified().unwrap());
// eprintln!("searched font: {:?}", path);
// if let Ok(mmap) = unsafe { Mmap::map(&file) } {
// for (i, info) in FontInfo::iter(&mmap).enumerate() {
// let coverage_hash = get_font_coverage_hash(&info.coverage);
// let mut ff = FontInfoItem::new(info);
// ff.set_coverage_hash(coverage_hash);
// if i != 0 {
// ff.set_index(i as u32);
// }
// profile_item.add_info(ff);
// }
// }
self.profile.items.push(profile_item);
return self.profile.items.last();
}
None
}
}
/// Searches for fonts.
/// Searches for fonts in system.
#[derive(Debug)]
pub struct SystemFontSearcher {
db: Database,
pub book: FontBook,
pub fonts: Vec<FontSlot>,
/// The base font searcher.
base: MemoryFontSearcher,
/// Records user-specific font path when loading from directory or file for
/// debug.
pub font_paths: Vec<PathBuf>,
profile_rebuilder: FontProfileRebuilder,
/// Stores font data loaded from file
db: Database,
}
impl SystemFontSearcher {
/// Create a new, empty system searcher.
/// Creates a system searcher.
pub fn new() -> Self {
let mut profile_rebuilder = FontProfileRebuilder::default();
"v1beta".clone_into(&mut profile_rebuilder.profile.version);
profile_rebuilder.profile.build_info = build_info::VERSION.to_string();
let db = Database::new();
Self {
base: MemoryFontSearcher::default(),
font_paths: vec![],
db,
book: FontBook::new(),
fonts: vec![],
profile_rebuilder,
db: Database::new(),
}
}
/// Builds a FontResolverImpl.
pub fn build(self) -> FontResolverImpl {
self.base.build().with_font_paths(self.font_paths)
}
}
impl SystemFontSearcher {
/// Resolve fonts from given options.
pub fn resolve_opts(&mut self, opts: CompileFontOpts) -> Result<()> {
if opts
.font_profile_cache_path
.to_str()
.map(|e| !e.is_empty())
.unwrap_or_default()
{
self.set_can_profile(true);
}
// Note: the order of adding fonts is important.
// See: https://github.com/typst/typst/blob/9c7f31870b4e1bf37df79ebbe1df9a56df83d878/src/font/book.rs#L151-L154
// Source1: add the fonts specified by the user.
@ -126,12 +55,13 @@ impl SystemFontSearcher {
let _ = self.search_file(&path);
}
}
// Source2: add the fonts from system paths.
if !opts.no_system_fonts {
self.search_system();
}
// flush source1 and source2 before adding source3
// Flush font db before adding fonts in memory
self.flush();
// Source3: add the fonts in memory.
@ -145,49 +75,11 @@ impl SystemFontSearcher {
Ok(())
}
pub fn set_can_profile(&mut self, can_profile: bool) {
self.profile_rebuilder.can_profile = can_profile;
}
pub fn add_profile_by_path(&mut self, profile_path: &Path) {
// let begin = std::time::Instant::now();
// profile_path is in format of json.gz
let profile_file = File::open(profile_path).unwrap();
let profile_gunzip = flate2::read::GzDecoder::new(profile_file);
let profile: FontProfile = serde_json::from_reader(profile_gunzip).unwrap();
if self.profile_rebuilder.profile.version != profile.version
|| self.profile_rebuilder.profile.build_info != profile.build_info
{
return;
}
for item in profile.items {
let path = match item.path() {
Some(path) => path,
None => continue,
};
let path = PathBuf::from(path);
if let Ok(m) = std::fs::metadata(&path) {
let modified = m.modified().ok();
if !modified.map(|m| item.mtime_is_exact(m)).unwrap_or_default() {
continue;
}
}
self.profile_rebuilder.path_items.insert(path, item.clone());
self.profile_rebuilder.profile.items.push(item);
}
// let end = std::time::Instant::now();
// eprintln!("profile_rebuilder init took {:?}", end - begin);
}
pub fn flush(&mut self) {
use fontdb::Source;
let face = self.db.faces().collect::<Vec<_>>();
let info = face.into_par_iter().map(|face| {
let info = face.into_par_iter().flat_map(|face| {
let path = match &face.source {
Source::File(path) | Source::SharedFile(path, _) => path,
// We never add binary sources to the database, so there
@ -195,33 +87,19 @@ impl SystemFontSearcher {
Source::Binary(_) => unreachable!(),
};
let info = self
.db
.with_face_data(face.id, FontInfo::new)
.expect("database must contain this font");
let info = self.db.with_face_data(face.id, FontInfo::new)??;
let slot = FontSlot::new(LazyBufferFontLoader::new(
LazyFile::new(path.clone()),
face.index,
))
.with_describe(DataSource::Fs(FsDataSource {
path: path.to_str().unwrap_or_default().to_owned(),
}));
info.map(|info| {
let slot = FontSlot::new_boxed(LazyBufferFontLoader::new(
LazyFile::new(path.clone()),
face.index,
))
.describe(DataSource::Fs(FsDataSource {
path: path.to_str().unwrap_or_default().to_owned(),
}));
(info, slot)
})
Some((info, slot))
});
for face in info.collect::<Vec<_>>() {
let Some((info, slot)) = face else {
continue;
};
self.book.push(info);
self.fonts.push(slot);
}
self.base.extend(info.collect::<Vec<_>>());
self.db = Database::new();
}
@ -231,53 +109,16 @@ impl SystemFontSearcher {
panic!("dirty font search state, please flush the searcher before adding memory fonts");
}
for (index, info) in FontInfo::iter(&data).enumerate() {
self.book.push(info.clone());
self.fonts.push(
FontSlot::new_boxed(BufferFontLoader {
buffer: Some(data.clone()),
index: index as u32,
})
.describe(DataSource::Memory(MemoryDataSource {
name: "<memory>".to_owned(),
})),
);
}
self.base.add_memory_font(data);
}
// /// Add an in-memory font.
// pub fn add_memory_font(&mut self, data: Bytes) {
// self.add_memory_fonts([data].into_par_iter());
// }
/// Adds in-memory fonts.
pub fn add_memory_fonts(&mut self, data: impl ParallelIterator<Item = Bytes>) {
if !self.db.is_empty() {
panic!("dirty font search state, please flush the searcher before adding memory fonts");
}
let loaded = data.flat_map(|data| {
FontInfo::iter(&data)
.enumerate()
.map(|(index, info)| {
(
info,
FontSlot::new_boxed(BufferFontLoader {
buffer: Some(data.clone()),
index: index as u32,
})
.describe(DataSource::Memory(MemoryDataSource {
name: "<memory>".to_owned(),
})),
)
})
.collect::<Vec<_>>()
});
for (info, slot) in loaded.collect::<Vec<_>>() {
self.book.push(info);
self.fonts.push(slot);
}
self.base.add_memory_fonts(data);
}
pub fn search_system(&mut self) {
@ -299,12 +140,14 @@ impl SystemFontSearcher {
/// Search for all fonts in a directory recursively.
pub fn search_dir(&mut self, path: impl AsRef<Path>) {
self.record_path(path.as_ref());
self.db.load_fonts_dir(path);
}
/// Index the fonts in the file at the given path.
pub fn search_file(&mut self, path: impl AsRef<Path>) -> FileResult<()> {
self.record_path(path.as_ref());
self.db
.load_font_file(path.as_ref())
.map_err(|e| FileError::from_io(e, path.as_ref()))
@ -317,28 +160,23 @@ impl Default for SystemFontSearcher {
}
}
impl From<SystemFontSearcher> for FontResolverImpl {
fn from(searcher: SystemFontSearcher) -> Self {
// let profile_item = match
// self.profile_rebuilder.search_file(path.as_ref()) {
// Some(profile_item) => profile_item,
// None => return,
// };
#[cfg(test)]
mod tests {
// for info in profile_item.info.iter() {
// self.book.push(info.info.clone());
// self.fonts
// .push(FontSlot::new_boxed(LazyBufferFontLoader::new(
// LazyFile::new(path.as_ref().to_owned()),
// info.index().unwrap_or_default(),
// )));
// }
FontResolverImpl::new(
searcher.font_paths,
searcher.book,
Arc::new(Mutex::new(PartialFontBook::default())),
searcher.fonts,
searcher.profile_rebuilder.profile,
)
#[test]
fn edit_fonts() {
use clap::Parser as _;
use crate::args::CompileOnceArgs;
let args = CompileOnceArgs::parse_from(["tinymist", "main.typ"]);
let mut verse = args
.resolve_system()
.expect("failed to resolve system universe");
// todo: a good way to edit fonts
let new_fonts = verse.font_resolver.clone();
verse.increment_revision(|verse| verse.set_fonts(new_fonts));
}
}

View file

@ -1,5 +1,3 @@
use std::sync::{Arc, Mutex};
use js_sys::ArrayBuffer;
use tinymist_std::error::prelude::*;
use typst::foundations::Bytes;
@ -8,9 +6,7 @@ use typst::text::{
};
use wasm_bindgen::prelude::*;
use super::{
BufferFontLoader, FontLoader, FontProfile, FontResolverImpl, FontSlot, PartialFontBook,
};
use super::{BufferFontLoader, FontLoader, FontResolverImpl, FontSlot};
use crate::font::cache::FontInfoCache;
use crate::font::info::typst_typographic_family;
@ -230,13 +226,6 @@ fn infer_info_from_web_font(
}
impl FontBuilder {
// fn to_f64(&self, field: &str, val: &JsValue) -> Result<f64, JsValue> {
// Ok(val
// .as_f64()
// .ok_or_else(|| JsValue::from_str(&format!("expected f64 for {}, got
// {:?}", field, val))) .unwrap())
// }
fn to_string(&self, field: &str, val: &JsValue) -> Result<String> {
Ok(val
.as_string()
@ -376,25 +365,13 @@ impl FontLoader for WebFontLoader {
/// Searches for fonts.
pub struct BrowserFontSearcher {
pub book: FontBook,
pub fonts: Vec<FontSlot>,
pub profile: FontProfile,
pub partial_book: Arc<Mutex<PartialFontBook>>,
pub fonts: Vec<(FontInfo, FontSlot)>,
}
impl BrowserFontSearcher {
/// Create a new, empty system searcher.
/// Create a new, empty browser searcher.
pub fn new() -> Self {
let profile = FontProfile {
version: "v1beta".to_owned(),
..Default::default()
};
let mut searcher = Self {
book: FontBook::new(),
fonts: vec![],
profile,
partial_book: Arc::new(Mutex::new(PartialFontBook::default())),
};
let mut searcher = Self { fonts: vec![] };
if cfg!(feature = "browser-embedded-fonts") {
searcher.add_embedded();
@ -403,14 +380,69 @@ impl BrowserFontSearcher {
searcher
}
/// Create a new browser searcher with fonts in a FontResolverImpl.
pub fn from_resolver(resolver: FontResolverImpl) -> Self {
let fonts = resolver
.slots
.into_iter()
.enumerate()
.map(|(idx, slot)| {
(
resolver
.book
.info(idx)
.expect("font should be in font book")
.clone(),
slot,
)
})
.collect();
Self { fonts }
}
/// Create a new browser searcher with fonts cloned from a FontResolverImpl.
/// Since FontSlot only holds QueryRef to font data, cloning is cheap.
pub fn new_with_resolver(resolver: &FontResolverImpl) -> Self {
let fonts = resolver
.slots
.iter()
.enumerate()
.map(|(idx, slot)| {
(
resolver
.book
.info(idx)
.expect("font should be in font book")
.clone(),
slot.clone(),
)
})
.collect();
Self { fonts }
}
/// Build a FontResolverImpl.
pub fn build(self) -> FontResolverImpl {
let (info, slots): (Vec<FontInfo>, Vec<FontSlot>) = self.fonts.into_iter().unzip();
let book = FontBook::from_infos(info);
FontResolverImpl::new(vec![], book, slots)
}
}
impl BrowserFontSearcher {
/// Add fonts that are embedded in the binary.
pub fn add_embedded(&mut self) {
for font_data in typst_assets::fonts() {
let buffer = Bytes::new(font_data);
for font in Font::iter(buffer) {
self.book.push(font.info().clone());
self.fonts.push(FontSlot::with_value(Some(font)));
}
self.fonts.extend(
Font::iter(buffer)
.map(|font| (font.info().clone(), FontSlot::new_loaded(Some(font)))),
);
}
}
@ -421,18 +453,19 @@ impl BrowserFontSearcher {
let (font_ref, font_blob_loader, font_info) = font_builder.font_web_to_typst(&v)?;
for (i, info) in font_info.into_iter().enumerate() {
self.book.push(info.clone());
let index = self.fonts.len();
self.fonts.push(FontSlot::new(Box::new(WebFontLoader {
font: WebFont {
info,
context: font_ref.clone(),
blob: font_blob_loader.clone(),
index: index as u32,
},
index: i as u32,
})))
self.fonts.push((
info.clone(),
FontSlot::new(WebFontLoader {
font: WebFont {
info,
context: font_ref.clone(),
blob: font_blob_loader.clone(),
index: index as u32,
},
index: i as u32,
}),
))
}
}
@ -441,20 +474,19 @@ impl BrowserFontSearcher {
pub fn add_font_data(&mut self, buffer: Bytes) {
for (i, info) in FontInfo::iter(buffer.as_slice()).enumerate() {
self.book.push(info);
let buffer = buffer.clone();
self.fonts.push(FontSlot::new(Box::new(BufferFontLoader {
buffer: Some(buffer),
index: i as u32,
})))
self.fonts.push((
info,
FontSlot::new(BufferFontLoader {
buffer: Some(buffer),
index: i as u32,
}),
))
}
}
pub async fn add_glyph_pack(&mut self) -> Result<()> {
Err(error_once!(
"BrowserFontSearcher.add_glyph_pack is not implemented"
))
pub fn with_fonts_mut(&mut self, func: impl FnOnce(&mut Vec<(FontInfo, FontSlot)>)) {
func(&mut self.fonts);
}
}
@ -463,15 +495,3 @@ impl Default for BrowserFontSearcher {
Self::new()
}
}
impl From<BrowserFontSearcher> for FontResolverImpl {
fn from(value: BrowserFontSearcher) -> Self {
FontResolverImpl::new(
vec![],
value.book,
value.partial_book,
value.fonts,
value.profile,
)
}
}

View file

@ -59,7 +59,7 @@ impl TypstSystemUniverse {
fn resolve_fonts(opts: CompileOpts) -> Result<FontResolverImpl> {
let mut searcher = SystemFontSearcher::new();
searcher.resolve_opts(opts.into())?;
Ok(searcher.into())
Ok(searcher.build())
}
}
@ -93,12 +93,11 @@ impl SystemUniverseBuilder {
pub fn resolve_fonts(args: CompileFontArgs) -> Result<FontResolverImpl> {
let mut searcher = SystemFontSearcher::new();
searcher.resolve_opts(CompileFontOpts {
font_profile_cache_path: Default::default(),
font_paths: args.font_paths,
no_system_fonts: args.ignore_system_fonts,
with_embedded_fonts: typst_assets::fonts().map(Cow::Borrowed).collect(),
})?;
Ok(searcher.into())
Ok(searcher.build())
}
/// Resolve package registry from given options.

View file

@ -23,11 +23,11 @@ use typst::Features;
use typst_shim::utils::LazyHash;
use super::*;
use crate::project::font::TinymistFontResolver;
use crate::project::{
EntryResolver, ExportPdfTask, ExportTask, ImmutDict, PathPattern, ProjectResolutionKind,
ProjectTask, TaskWhen,
};
use crate::world::font::FontResolverImpl;
// region Configuration Items
const CONFIG_ITEMS: &[&str] = &[
@ -96,7 +96,7 @@ pub struct Config {
/// Specifies the font paths
pub font_paths: Vec<PathBuf>,
/// Computed fonts based on configuration.
pub fonts: OnceLock<Derived<Arc<TinymistFontResolver>>>,
pub fonts: OnceLock<Derived<Arc<FontResolverImpl>>>,
/// Whether to use system fonts.
pub system_fonts: Option<bool>,
@ -554,7 +554,7 @@ impl Config {
}
/// Determines the font resolver.
pub fn fonts(&self) -> Arc<TinymistFontResolver> {
pub fn fonts(&self) -> Arc<FontResolverImpl> {
// todo: on font resolving failure, downgrade to a fake font book
let font = || {
let opts = self.font_opts();

View file

@ -5,14 +5,14 @@ use std::sync::{LazyLock, OnceLock};
use regex::Regex;
use tinymist_project::{
font::TinymistFontResolver, CompileFontArgs, EntryState, ExportTarget, LspUniverseBuilder,
font::FontResolverImpl, CompileFontArgs, EntryState, ExportTarget, LspUniverseBuilder,
};
use typst_syntax::Source;
use super::*;
fn conv_(s: &str, for_docs: bool) -> EcoString {
static FONT_RESOLVER: LazyLock<Arc<TinymistFontResolver>> = LazyLock::new(|| {
static FONT_RESOLVER: LazyLock<Arc<FontResolverImpl>> = LazyLock::new(|| {
Arc::new(
LspUniverseBuilder::resolve_fonts(CompileFontArgs::default())
.expect("cannot resolve default fonts"),