mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-03 17:58:17 +00:00
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:
parent
031b56205b
commit
39d13c83f6
18 changed files with 449 additions and 760 deletions
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
|
@ -5,8 +5,6 @@ mod compiler;
|
|||
mod entry;
|
||||
mod model;
|
||||
|
||||
#[cfg(feature = "lsp")]
|
||||
pub mod font;
|
||||
#[cfg(feature = "lsp")]
|
||||
mod lock;
|
||||
#[cfg(feature = "lsp")]
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
88
crates/tinymist-world/src/font/memory.rs
Normal file
88
crates/tinymist-world/src/font/memory.rs
Normal 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;
|
|
@ -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;
|
||||
|
|
|
@ -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(())
|
||||
}
|
||||
}
|
|
@ -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(),
|
||||
})),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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"),
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue