mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-08-03 17:58:17 +00:00
feat: support --feature
and --pdf-standard
. (#1596)
This commit is contained in:
parent
e443c3172b
commit
90f9949f8c
15 changed files with 289 additions and 229 deletions
|
@ -8,6 +8,7 @@ use std::sync::{Arc, OnceLock};
|
|||
use ecow::{eco_vec, EcoVec};
|
||||
use tinymist_std::error::prelude::Result;
|
||||
use tinymist_std::{typst::TypstDocument, ImmutPath};
|
||||
use tinymist_task::ExportTarget;
|
||||
use tinymist_world::vfs::notify::{
|
||||
FilesystemEvent, MemoryEvent, NotifyDeps, NotifyMessage, UpstreamUpdateEvent,
|
||||
};
|
||||
|
@ -84,9 +85,7 @@ impl<F: CompilerFeat> CompiledArtifact<F> {
|
|||
}
|
||||
|
||||
/// Runs the compiler and returns the compiled document.
|
||||
pub fn from_graph(graph: Arc<WorldComputeGraph<F>>) -> CompiledArtifact<F> {
|
||||
let is_html = graph.library().features.is_enabled(typst::Feature::Html);
|
||||
|
||||
pub fn from_graph(graph: Arc<WorldComputeGraph<F>>, is_html: bool) -> CompiledArtifact<F> {
|
||||
let _ = graph.provide::<FlagTask<HtmlCompilationTask>>(Ok(FlagTask::flag(is_html)));
|
||||
let _ = graph.provide::<FlagTask<PagedCompilationTask>>(Ok(FlagTask::flag(!is_html)));
|
||||
let doc = if is_html {
|
||||
|
@ -355,6 +354,8 @@ pub struct CompileServerOpts<F: CompilerFeat, Ext> {
|
|||
pub handler: Arc<dyn CompileHandler<F, Ext>>,
|
||||
/// Whether to enable file system watching.
|
||||
pub enable_watch: bool,
|
||||
/// Specifies the current export target.
|
||||
pub export_target: ExportTarget,
|
||||
}
|
||||
|
||||
impl<F: CompilerFeat + Send + Sync + 'static, Ext: 'static> Default for CompileServerOpts<F, Ext> {
|
||||
|
@ -362,6 +363,7 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: 'static> Default for CompileS
|
|||
Self {
|
||||
handler: Arc::new(std::marker::PhantomData),
|
||||
enable_watch: false,
|
||||
export_target: ExportTarget::Paged,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -370,6 +372,8 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: 'static> Default for CompileS
|
|||
pub struct ProjectCompiler<F: CompilerFeat, Ext> {
|
||||
/// The compilation handle.
|
||||
pub handler: Arc<dyn CompileHandler<F, Ext>>,
|
||||
/// Specifies the current export target.
|
||||
export_target: ExportTarget,
|
||||
/// Channel for sending interrupts to the compiler actor.
|
||||
dep_tx: mpsc::UnboundedSender<NotifyMessage>,
|
||||
/// Whether to enable file system watching.
|
||||
|
@ -398,13 +402,20 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
|
|||
CompileServerOpts {
|
||||
handler,
|
||||
enable_watch,
|
||||
export_target,
|
||||
}: CompileServerOpts<F, Ext>,
|
||||
) -> Self {
|
||||
let primary = Self::create_project(ProjectInsId("primary".into()), verse, handler.clone());
|
||||
let primary = Self::create_project(
|
||||
ProjectInsId("primary".into()),
|
||||
verse,
|
||||
export_target,
|
||||
handler.clone(),
|
||||
);
|
||||
Self {
|
||||
handler,
|
||||
dep_tx,
|
||||
enable_watch,
|
||||
export_target,
|
||||
|
||||
logical_tick: 1,
|
||||
dirty_shadow_logical_tick: 0,
|
||||
|
@ -425,7 +436,7 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
|
|||
/// Compiles the document once.
|
||||
pub fn compile_once(&mut self) -> CompiledArtifact<F> {
|
||||
let snap = self.primary.make_snapshot();
|
||||
ProjectInsState::run_compile(self.handler.clone(), snap)()
|
||||
ProjectInsState::run_compile(self.handler.clone(), snap, self.export_target)()
|
||||
}
|
||||
|
||||
/// Gets the iterator of all projects.
|
||||
|
@ -436,6 +447,7 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
|
|||
fn create_project(
|
||||
id: ProjectInsId,
|
||||
verse: CompilerUniverse<F>,
|
||||
export_target: ExportTarget,
|
||||
handler: Arc<dyn CompileHandler<F, Ext>>,
|
||||
) -> ProjectInsState<F, Ext> {
|
||||
ProjectInsState {
|
||||
|
@ -445,6 +457,7 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
|
|||
reason: no_reason(),
|
||||
snapshot: None,
|
||||
handler,
|
||||
export_target,
|
||||
compilation: OnceLock::default(),
|
||||
latest_success_doc: None,
|
||||
deps: Default::default(),
|
||||
|
@ -471,24 +484,20 @@ impl<F: CompilerFeat + Send + Sync + 'static, Ext: Default + 'static> ProjectCom
|
|||
}
|
||||
|
||||
/// Restart a dedicate project.
|
||||
pub fn restart_dedicate(
|
||||
&mut self,
|
||||
group: &str,
|
||||
entry: EntryState,
|
||||
enable_html: bool,
|
||||
) -> Result<ProjectInsId> {
|
||||
pub fn restart_dedicate(&mut self, group: &str, entry: EntryState) -> Result<ProjectInsId> {
|
||||
let id = ProjectInsId(group.into());
|
||||
|
||||
let verse = CompilerUniverse::<F>::new_raw(
|
||||
entry,
|
||||
enable_html,
|
||||
self.primary.verse.features.clone(),
|
||||
Some(self.primary.verse.inputs().clone()),
|
||||
self.primary.verse.vfs().fork(),
|
||||
self.primary.verse.registry.clone(),
|
||||
self.primary.verse.font_resolver.clone(),
|
||||
);
|
||||
|
||||
let mut proj = Self::create_project(id.clone(), verse, self.handler.clone());
|
||||
let mut proj =
|
||||
Self::create_project(id.clone(), verse, self.export_target, self.handler.clone());
|
||||
proj.reason.see(reason_by_entry_change());
|
||||
|
||||
self.remove_dedicates(&id);
|
||||
|
@ -739,6 +748,8 @@ pub struct ProjectInsState<F: CompilerFeat, Ext> {
|
|||
pub ext: Ext,
|
||||
/// The underlying universe.
|
||||
pub verse: CompilerUniverse<F>,
|
||||
/// Specifies the current export target.
|
||||
pub export_target: ExportTarget,
|
||||
/// The reason to compile.
|
||||
pub reason: CompileReasons,
|
||||
/// The latest compute graph (snapshot).
|
||||
|
@ -817,13 +828,14 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
|
|||
let snap = self.snapshot();
|
||||
self.reason = Default::default();
|
||||
|
||||
Some(Self::run_compile(handler.clone(), snap))
|
||||
Some(Self::run_compile(handler.clone(), snap, self.export_target))
|
||||
}
|
||||
|
||||
/// Compile the document once.
|
||||
fn run_compile(
|
||||
h: Arc<dyn CompileHandler<F, Ext>>,
|
||||
graph: Arc<WorldComputeGraph<F>>,
|
||||
export_target: ExportTarget,
|
||||
) -> impl FnOnce() -> CompiledArtifact<F> {
|
||||
let start = tinymist_std::time::now();
|
||||
|
||||
|
@ -838,7 +850,8 @@ impl<F: CompilerFeat, Ext: 'static> ProjectInsState<F, Ext> {
|
|||
);
|
||||
|
||||
move || {
|
||||
let compiled = CompiledArtifact::from_graph(graph);
|
||||
let compiled =
|
||||
CompiledArtifact::from_graph(graph, matches!(export_target, ExportTarget::Html));
|
||||
|
||||
let elapsed = start.elapsed().unwrap_or_default();
|
||||
let rep = match &compiled.doc {
|
||||
|
|
|
@ -14,6 +14,7 @@ use tinymist_world::{
|
|||
};
|
||||
use typst::foundations::{Dict, Str, Value};
|
||||
use typst::utils::LazyHash;
|
||||
use typst::Features;
|
||||
|
||||
use crate::ProjectInput;
|
||||
|
||||
|
@ -62,7 +63,7 @@ impl WorldProvider for CompileOnceArgs {
|
|||
let entry = self.entry()?.try_into()?;
|
||||
let inputs = self.resolve_inputs().unwrap_or_default();
|
||||
let fonts = Arc::new(LspUniverseBuilder::resolve_fonts(self.font.clone())?);
|
||||
let package = LspUniverseBuilder::resolve_package(
|
||||
let packages = LspUniverseBuilder::resolve_package(
|
||||
self.cert.as_deref().map(From::from),
|
||||
Some(&self.package),
|
||||
);
|
||||
|
@ -71,9 +72,10 @@ impl WorldProvider for CompileOnceArgs {
|
|||
Ok(LspUniverseBuilder::build(
|
||||
entry,
|
||||
ExportTarget::Paged,
|
||||
self.resolve_features(),
|
||||
inputs,
|
||||
packages,
|
||||
fonts,
|
||||
package,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -142,7 +144,7 @@ impl WorldProvider for (ProjectInput, ImmutPath) {
|
|||
},
|
||||
ignore_system_fonts: !proj.system_fonts,
|
||||
})?;
|
||||
let package = LspUniverseBuilder::resolve_package(
|
||||
let packages = LspUniverseBuilder::resolve_package(
|
||||
// todo: recover certificate path
|
||||
None,
|
||||
Some(&CompilePackageArgs {
|
||||
|
@ -161,9 +163,11 @@ impl WorldProvider for (ProjectInput, ImmutPath) {
|
|||
Ok(LspUniverseBuilder::build(
|
||||
entry,
|
||||
ExportTarget::Paged,
|
||||
// todo: features
|
||||
Features::default(),
|
||||
Arc::new(LazyHash::new(inputs)),
|
||||
packages,
|
||||
Arc::new(fonts),
|
||||
package,
|
||||
))
|
||||
}
|
||||
|
||||
|
@ -207,19 +211,27 @@ impl LspUniverseBuilder {
|
|||
pub fn build(
|
||||
entry: EntryState,
|
||||
export_target: ExportTarget,
|
||||
features: Features,
|
||||
inputs: ImmutDict,
|
||||
font_resolver: Arc<TinymistFontResolver>,
|
||||
package_registry: HttpRegistry,
|
||||
font_resolver: Arc<TinymistFontResolver>,
|
||||
) -> LspUniverse {
|
||||
let registry = Arc::new(package_registry);
|
||||
let resolver = Arc::new(RegistryPathMapper::new(registry.clone()));
|
||||
let package_registry = Arc::new(package_registry);
|
||||
let resolver = Arc::new(RegistryPathMapper::new(package_registry.clone()));
|
||||
|
||||
// todo: typst doesn't allow to merge features
|
||||
let features = if matches!(export_target, ExportTarget::Html) {
|
||||
Features::from_iter([typst::Feature::Html])
|
||||
} else {
|
||||
features
|
||||
};
|
||||
|
||||
LspUniverse::new_raw(
|
||||
entry,
|
||||
matches!(export_target, ExportTarget::Html),
|
||||
features,
|
||||
Some(inputs),
|
||||
Vfs::new(resolver, SystemAccessModel {}),
|
||||
registry,
|
||||
package_registry,
|
||||
font_resolver,
|
||||
)
|
||||
}
|
||||
|
|
|
@ -154,6 +154,8 @@ pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut LspUniverse, PathBu
|
|||
EntryState::new_rooted(root.as_path().into(), None),
|
||||
ExportTarget::Paged,
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
LspUniverseBuilder::resolve_package(None, None),
|
||||
Arc::new(
|
||||
LspUniverseBuilder::resolve_fonts(CompileFontArgs {
|
||||
ignore_system_fonts: true,
|
||||
|
@ -161,7 +163,6 @@ pub fn run_with_sources<T>(source: &str, f: impl FnOnce(&mut LspUniverse, PathBu
|
|||
})
|
||||
.unwrap(),
|
||||
),
|
||||
LspUniverseBuilder::resolve_package(None, None),
|
||||
);
|
||||
let sources = source.split("-----");
|
||||
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
use super::*;
|
||||
pub use typst_pdf::pdf;
|
||||
pub use typst_pdf::PdfStandard as TypstPdfStandard;
|
||||
|
||||
use crate::model::ExportPdfTask;
|
||||
use tinymist_world::args::convert_source_date_epoch;
|
||||
use typst::foundations::Datetime;
|
||||
pub use typst_pdf::pdf;
|
||||
use typst_pdf::PdfOptions;
|
||||
pub use typst_pdf::PdfStandard as TypstPdfStandard;
|
||||
use typst_pdf::Timestamp;
|
||||
use typst_pdf::{PdfOptions, PdfStandards, Timestamp};
|
||||
|
||||
use super::*;
|
||||
use crate::model::ExportPdfTask;
|
||||
|
||||
pub struct PdfExport;
|
||||
|
||||
impl<F: CompilerFeat> ExportComputation<F, TypstPagedDocument> for PdfExport {
|
||||
|
@ -23,15 +24,29 @@ impl<F: CompilerFeat> ExportComputation<F, TypstPagedDocument> for PdfExport {
|
|||
.creation_timestamp
|
||||
.map(convert_source_date_epoch)
|
||||
.transpose()
|
||||
.context_ut("parse pdf creation timestamp")?
|
||||
.context_ut("prepare pdf creation timestamp")?
|
||||
.unwrap_or_else(chrono::Utc::now);
|
||||
|
||||
// todo: Some(pdf_uri.as_str())
|
||||
let standards = PdfStandards::new(
|
||||
&config
|
||||
.pdf_standards
|
||||
.iter()
|
||||
.map(|standard| match standard {
|
||||
tinymist_world::args::PdfStandard::V_1_7 => typst_pdf::PdfStandard::V_1_7,
|
||||
tinymist_world::args::PdfStandard::A_2b => typst_pdf::PdfStandard::A_2b,
|
||||
tinymist_world::args::PdfStandard::A_3b => typst_pdf::PdfStandard::A_3b,
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
)
|
||||
.context_ut("prepare pdf standards")?;
|
||||
|
||||
// todo: Some(pdf_uri.as_str())
|
||||
// todo: ident option
|
||||
Ok(Bytes::new(typst_pdf::pdf(
|
||||
doc,
|
||||
&PdfOptions {
|
||||
timestamp: convert_datetime(creation_timestamp),
|
||||
standards,
|
||||
..Default::default()
|
||||
},
|
||||
)?))
|
||||
|
@ -50,50 +65,3 @@ pub fn convert_datetime(date_time: chrono::DateTime<chrono::Utc>) -> Option<Time
|
|||
date_time.second().try_into().ok()?,
|
||||
)?))
|
||||
}
|
||||
|
||||
// impl<F: CompilerFeat> WorldComputable<F> for PdfExport {
|
||||
// type Output = Option<Bytes>;
|
||||
|
||||
// fn compute(graph: &Arc<WorldComputeGraph<F>>) -> Result<Self::Output> {
|
||||
// OptionDocumentTask::run_export::<F, Self>(graph)
|
||||
// }
|
||||
// }
|
||||
|
||||
// use std::sync::Arc;
|
||||
|
||||
// use reflexo::typst::TypstPagedDocument;
|
||||
// use typst::{diag:: World;
|
||||
// use typst_pdf::{PdfOptions, PdfStandard, PdfStandards, Timestamp};
|
||||
|
||||
// #[derive(Debug, Clone, Default)]
|
||||
// pub struct PdfDocExporter {
|
||||
// ctime: Option<Timestamp>,
|
||||
// standards: Option<PdfStandards>,
|
||||
// }
|
||||
|
||||
// impl PdfDocExporter {
|
||||
// pub fn with_ctime(mut self, v: Option<Timestamp>) -> Self {
|
||||
// self.ctime = v;
|
||||
// self
|
||||
// }
|
||||
|
||||
// pub fn with_standard(mut self, v: Option<PdfStandard>) -> Self {
|
||||
// self.standards = v.map(|v| PdfStandards::new(&[v]).unwrap());
|
||||
// self
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl Exporter<TypstPagedDocument, Vec<u8>> for PdfDocExporter {
|
||||
// fn export(&self, _world: &dyn World, output: Arc<TypstPagedDocument>) ->
|
||||
// Vecu8>> { // todo: ident option
|
||||
|
||||
// typst_pdf::pdf(
|
||||
// output.as_ref(),
|
||||
// &PdfOptions {
|
||||
// timestamp: self.ctime,
|
||||
// standards: self.standards.clone().unwrap_or_default(),
|
||||
// ..Default::default()
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
pub use tinymist_world::args::{ExportTarget, OutputFormat, PdfStandard, TaskWhen};
|
||||
|
||||
use core::fmt;
|
||||
use std::hash::{Hash, Hasher};
|
||||
use std::num::NonZeroUsize;
|
||||
|
@ -5,7 +7,6 @@ use std::ops::RangeInclusive;
|
|||
use std::path::PathBuf;
|
||||
use std::{path::Path, str::FromStr};
|
||||
|
||||
use clap::ValueEnum;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tinymist_std::error::prelude::*;
|
||||
use tinymist_std::path::{unix_slash, PathClean};
|
||||
|
@ -97,74 +98,6 @@ impl fmt::Display for Id {
|
|||
}
|
||||
}
|
||||
|
||||
macro_rules! display_possible_values {
|
||||
($ty:ty) => {
|
||||
impl fmt::Display for $ty {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.to_possible_value()
|
||||
.expect("no values are skipped")
|
||||
.get_name()
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// When to export an output file.
|
||||
///
|
||||
/// By default, a `tinymist compile` only provides input information and
|
||||
/// doesn't change the `when` field. However, you can still specify a `when`
|
||||
/// argument to override the default behavior for specific tasks.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```bash
|
||||
/// tinymist compile --when onSave main.typ
|
||||
/// alias typst="tinymist compile --when=onSave"
|
||||
/// typst compile main.typ
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash, ValueEnum, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[clap(rename_all = "camelCase")]
|
||||
pub enum TaskWhen {
|
||||
/// Never watch to run task.
|
||||
#[default]
|
||||
Never,
|
||||
/// Run task on saving the document, i.e. on `textDocument/didSave` events.
|
||||
OnSave,
|
||||
/// Run task on typing, i.e. on `textDocument/didChange` events.
|
||||
OnType,
|
||||
/// *DEPRECATED* Run task when a document has a title and on saved, which is
|
||||
/// useful to filter out template files.
|
||||
///
|
||||
/// Note: this is deprecating.
|
||||
OnDocumentHasTitle,
|
||||
}
|
||||
|
||||
impl TaskWhen {
|
||||
/// Returns `true` if the task should never be run automatically.
|
||||
pub fn is_never(&self) -> bool {
|
||||
matches!(self, TaskWhen::Never)
|
||||
}
|
||||
}
|
||||
|
||||
display_possible_values!(TaskWhen);
|
||||
|
||||
/// Which format to use for the generated output file.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)]
|
||||
pub enum OutputFormat {
|
||||
/// Export to PDF.
|
||||
Pdf,
|
||||
/// Export to PNG.
|
||||
Png,
|
||||
/// Export to SVG.
|
||||
Svg,
|
||||
/// Export to HTML.
|
||||
Html,
|
||||
}
|
||||
|
||||
display_possible_values!(OutputFormat);
|
||||
|
||||
/// The path pattern that could be substituted.
|
||||
///
|
||||
/// # Examples
|
||||
|
@ -236,37 +169,6 @@ impl PathPattern {
|
|||
}
|
||||
}
|
||||
|
||||
/// Specifies the current export target.
|
||||
///
|
||||
/// The design of this configuration is not yet finalized and for this reason it
|
||||
/// is guarded behind the html feature. Visit the HTML documentation page for
|
||||
/// more details.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ExportTarget {
|
||||
/// The current export target is for PDF, PNG, and SVG export.
|
||||
#[default]
|
||||
Paged,
|
||||
/// The current export target is for Html export.
|
||||
Html,
|
||||
}
|
||||
|
||||
/// A PDF standard that Typst can enforce conformance with.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, ValueEnum, Serialize, Deserialize)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum PdfStandard {
|
||||
/// PDF 1.7.
|
||||
#[value(name = "1.7")]
|
||||
#[serde(rename = "1.7")]
|
||||
V_1_7,
|
||||
/// PDF/A-2b.
|
||||
#[value(name = "a-2b")]
|
||||
#[serde(rename = "a-2b")]
|
||||
A_2b,
|
||||
}
|
||||
|
||||
display_possible_values!(PdfStandard);
|
||||
|
||||
/// Implements parsing of page ranges (`1-3`, `4`, `5-`, `-2`), used by the
|
||||
/// `CompileCommand.pages` argument, through the `FromStr` trait instead of a
|
||||
/// value parser, in order to generate better errors.
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
use core::fmt;
|
||||
use std::{
|
||||
path::{Path, PathBuf},
|
||||
sync::Arc,
|
||||
};
|
||||
|
||||
use chrono::{DateTime, Utc};
|
||||
use clap::{builder::ValueParser, ArgAction, Parser};
|
||||
use clap::{builder::ValueParser, ArgAction, Parser, ValueEnum};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tinymist_std::{bail, error::prelude::*};
|
||||
use tinymist_vfs::ImmutDict;
|
||||
|
@ -61,6 +62,19 @@ pub struct CompileOnceArgs {
|
|||
#[clap(long = "root", value_name = "DIR")]
|
||||
pub root: Option<PathBuf>,
|
||||
|
||||
/// Font related arguments.
|
||||
#[clap(flatten)]
|
||||
pub font: CompileFontArgs,
|
||||
|
||||
/// Package related arguments.
|
||||
#[clap(flatten)]
|
||||
pub package: CompilePackageArgs,
|
||||
|
||||
/// Enables in-development features that may be changed or removed at any
|
||||
/// time.
|
||||
#[arg(long = "features", value_delimiter = ',', env = "TYPST_FEATURES")]
|
||||
pub features: Vec<Feature>,
|
||||
|
||||
/// Add a string key-value pair visible through `sys.inputs`
|
||||
#[clap(
|
||||
long = "input",
|
||||
|
@ -70,14 +84,6 @@ pub struct CompileOnceArgs {
|
|||
)]
|
||||
pub inputs: Vec<(String, String)>,
|
||||
|
||||
/// Font related arguments.
|
||||
#[clap(flatten)]
|
||||
pub font: CompileFontArgs,
|
||||
|
||||
/// Package related arguments.
|
||||
#[clap(flatten)]
|
||||
pub package: CompilePackageArgs,
|
||||
|
||||
/// The document's creation date formatted as a UNIX timestamp (in seconds).
|
||||
///
|
||||
/// For more information, see <https://reproducible-builds.org/specs/source-date-epoch/>.
|
||||
|
@ -90,6 +96,11 @@ pub struct CompileOnceArgs {
|
|||
)]
|
||||
pub creation_timestamp: Option<i64>,
|
||||
|
||||
/// One (or multiple comma-separated) PDF standards that Typst will enforce
|
||||
/// conformance with.
|
||||
#[arg(long = "pdf-standard", value_delimiter = ',')]
|
||||
pub pdf_standard: Vec<PdfStandard>,
|
||||
|
||||
/// Path to CA certificate file for network access, especially for
|
||||
/// downloading typst packages.
|
||||
#[clap(long = "cert", env = "TYPST_CERT", value_name = "CERT_PATH")]
|
||||
|
@ -97,6 +108,10 @@ pub struct CompileOnceArgs {
|
|||
}
|
||||
|
||||
impl CompileOnceArgs {
|
||||
pub fn resolve_features(&self) -> typst::Features {
|
||||
typst::Features::from_iter(self.features.iter().map(|f| (*f).into()))
|
||||
}
|
||||
|
||||
pub fn resolve_inputs(&self) -> Option<ImmutDict> {
|
||||
if self.inputs.is_empty() {
|
||||
return None;
|
||||
|
@ -200,3 +215,122 @@ pub fn parse_source_date_epoch(raw: &str) -> Result<i64, String> {
|
|||
pub fn convert_source_date_epoch(seconds: i64) -> Result<chrono::DateTime<Utc>, String> {
|
||||
DateTime::from_timestamp(seconds, 0).ok_or_else(|| "timestamp out of range".to_string())
|
||||
}
|
||||
|
||||
macro_rules! display_possible_values {
|
||||
($ty:ty) => {
|
||||
impl fmt::Display for $ty {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.to_possible_value()
|
||||
.expect("no values are skipped")
|
||||
.get_name()
|
||||
.fmt(f)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/// When to export an output file.
|
||||
///
|
||||
/// By default, a `tinymist compile` only provides input information and
|
||||
/// doesn't change the `when` field. However, you can still specify a `when`
|
||||
/// argument to override the default behavior for specific tasks.
|
||||
///
|
||||
/// ## Examples
|
||||
///
|
||||
/// ```bash
|
||||
/// tinymist compile --when onSave main.typ
|
||||
/// alias typst="tinymist compile --when=onSave"
|
||||
/// typst compile main.typ
|
||||
/// ```
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Default, Hash, ValueEnum, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
#[clap(rename_all = "camelCase")]
|
||||
pub enum TaskWhen {
|
||||
/// Never watch to run task.
|
||||
#[default]
|
||||
Never,
|
||||
/// Run task on saving the document, i.e. on `textDocument/didSave` events.
|
||||
OnSave,
|
||||
/// Run task on typing, i.e. on `textDocument/didChange` events.
|
||||
OnType,
|
||||
/// *DEPRECATED* Run task when a document has a title and on saved, which is
|
||||
/// useful to filter out template files.
|
||||
///
|
||||
/// Note: this is deprecating.
|
||||
OnDocumentHasTitle,
|
||||
}
|
||||
|
||||
impl TaskWhen {
|
||||
/// Returns `true` if the task should never be run automatically.
|
||||
pub fn is_never(&self) -> bool {
|
||||
matches!(self, TaskWhen::Never)
|
||||
}
|
||||
}
|
||||
|
||||
display_possible_values!(TaskWhen);
|
||||
|
||||
/// Which format to use for the generated output file.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, ValueEnum)]
|
||||
pub enum OutputFormat {
|
||||
/// Export to PDF.
|
||||
Pdf,
|
||||
/// Export to PNG.
|
||||
Png,
|
||||
/// Export to SVG.
|
||||
Svg,
|
||||
/// Export to HTML.
|
||||
Html,
|
||||
}
|
||||
|
||||
display_possible_values!(OutputFormat);
|
||||
|
||||
/// Specifies the current export target.
|
||||
///
|
||||
/// The design of this configuration is not yet finalized and for this reason it
|
||||
/// is guarded behind the html feature. Visit the HTML documentation page for
|
||||
/// more details.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub enum ExportTarget {
|
||||
/// The current export target is for PDF, PNG, and SVG export.
|
||||
#[default]
|
||||
Paged,
|
||||
/// The current export target is for HTML export.
|
||||
Html,
|
||||
}
|
||||
|
||||
/// A PDF standard that Typst can enforce conformance with.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash, ValueEnum, Serialize, Deserialize)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum PdfStandard {
|
||||
/// PDF 1.7.
|
||||
#[value(name = "1.7")]
|
||||
#[serde(rename = "1.7")]
|
||||
V_1_7,
|
||||
/// PDF/A-2b.
|
||||
#[value(name = "a-2b")]
|
||||
#[serde(rename = "a-2b")]
|
||||
A_2b,
|
||||
/// PDF/A-3b.
|
||||
#[value(name = "a-3b")]
|
||||
#[serde(rename = "a-3b")]
|
||||
A_3b,
|
||||
}
|
||||
|
||||
display_possible_values!(PdfStandard);
|
||||
|
||||
/// An in-development feature that may be changed or removed at any time.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)]
|
||||
pub enum Feature {
|
||||
Html,
|
||||
}
|
||||
|
||||
display_possible_values!(Feature);
|
||||
|
||||
impl From<Feature> for typst::Feature {
|
||||
fn from(f: Feature) -> typst::Feature {
|
||||
match f {
|
||||
Feature::Html => typst::Feature::Html,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ use std::{path::PathBuf, sync::Arc};
|
|||
use tinymist_vfs::browser::ProxyAccessModel;
|
||||
use typst::foundations::Dict as TypstDict;
|
||||
use typst::utils::LazyHash;
|
||||
use typst::Features;
|
||||
|
||||
use crate::entry::EntryState;
|
||||
use crate::font::FontResolverImpl;
|
||||
|
@ -48,7 +49,7 @@ impl TypstBrowserUniverse {
|
|||
// todo: enable html
|
||||
Self::new_raw(
|
||||
EntryState::new_rooted(root_dir.into(), None),
|
||||
false,
|
||||
Features::default(),
|
||||
inputs,
|
||||
vfs,
|
||||
registry,
|
||||
|
|
|
@ -2,7 +2,7 @@ use std::{borrow::Cow, sync::Arc};
|
|||
|
||||
use tinymist_std::{error::prelude::*, ImmutPath};
|
||||
use tinymist_vfs::{system::SystemAccessModel, ImmutDict, Vfs};
|
||||
use typst::utils::LazyHash;
|
||||
use typst::{utils::LazyHash, Features};
|
||||
|
||||
use crate::{
|
||||
args::{CompileFontArgs, CompilePackageArgs},
|
||||
|
@ -47,7 +47,7 @@ impl TypstSystemUniverse {
|
|||
// todo: enable html
|
||||
Ok(Self::new_raw(
|
||||
opts.entry.clone().try_into()?,
|
||||
false,
|
||||
Features::default(),
|
||||
Some(Arc::new(LazyHash::new(inputs))),
|
||||
Vfs::new(resolver, SystemAccessModel {}),
|
||||
registry,
|
||||
|
@ -81,7 +81,7 @@ impl SystemUniverseBuilder {
|
|||
// todo: enable html
|
||||
TypstSystemUniverse::new_raw(
|
||||
entry,
|
||||
false,
|
||||
Features::default(),
|
||||
Some(inputs),
|
||||
Vfs::new(resolver, SystemAccessModel {}),
|
||||
registry,
|
||||
|
|
|
@ -17,7 +17,7 @@ use typst::{
|
|||
syntax::{FileId, Source, Span, VirtualPath},
|
||||
text::{Font, FontBook},
|
||||
utils::LazyHash,
|
||||
Library, World,
|
||||
Features, Library, World,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
|
@ -50,10 +50,10 @@ pub struct CompilerUniverse<F: CompilerFeat> {
|
|||
/// State for the *root & entry* of compilation.
|
||||
/// The world forbids direct access to files outside this directory.
|
||||
entry: EntryState,
|
||||
/// Whether to enable HTML features.
|
||||
enable_html: bool,
|
||||
/// Additional input arguments to compile the entry file.
|
||||
inputs: Arc<LazyHash<Dict>>,
|
||||
/// Features enabled for the compiler.
|
||||
pub features: Features,
|
||||
|
||||
/// Provides font management for typst compiler.
|
||||
pub font_resolver: Arc<F::FontResolver>,
|
||||
|
@ -76,21 +76,21 @@ impl<F: CompilerFeat> CompilerUniverse<F> {
|
|||
/// + See [`crate::TypstBrowserUniverse::new`] for browser environment.
|
||||
pub fn new_raw(
|
||||
entry: EntryState,
|
||||
enable_html: bool,
|
||||
features: Features,
|
||||
inputs: Option<Arc<LazyHash<Dict>>>,
|
||||
vfs: Vfs<F::AccessModel>,
|
||||
registry: Arc<F::Registry>,
|
||||
package_registry: Arc<F::Registry>,
|
||||
font_resolver: Arc<F::FontResolver>,
|
||||
) -> Self {
|
||||
Self {
|
||||
entry,
|
||||
enable_html,
|
||||
inputs: inputs.unwrap_or_default(),
|
||||
features,
|
||||
|
||||
revision: NonZeroUsize::new(1).expect("initial revision is 1"),
|
||||
|
||||
font_resolver,
|
||||
registry,
|
||||
registry: package_registry,
|
||||
vfs,
|
||||
}
|
||||
}
|
||||
|
@ -156,9 +156,9 @@ impl<F: CompilerFeat> CompilerUniverse<F> {
|
|||
pub fn snapshot_with(&self, mutant: Option<TaskInputs>) -> CompilerWorld<F> {
|
||||
let w = CompilerWorld {
|
||||
entry: self.entry.clone(),
|
||||
enable_html: self.enable_html,
|
||||
features: self.features.clone(),
|
||||
inputs: self.inputs.clone(),
|
||||
library: create_library(self.inputs.clone(), self.enable_html),
|
||||
library: create_library(self.inputs.clone(), self.features.clone()),
|
||||
font_resolver: self.font_resolver.clone(),
|
||||
registry: self.registry.clone(),
|
||||
vfs: self.vfs.snapshot(),
|
||||
|
@ -436,10 +436,10 @@ pub struct CompilerWorld<F: CompilerFeat> {
|
|||
/// State for the *root & entry* of compilation.
|
||||
/// The world forbids direct access to files outside this directory.
|
||||
entry: EntryState,
|
||||
/// Whether to enable HTML features.
|
||||
enable_html: bool,
|
||||
/// Additional input arguments to compile the entry file.
|
||||
inputs: Arc<LazyHash<Dict>>,
|
||||
/// A selection of in-development features that should be enabled.
|
||||
features: Features,
|
||||
|
||||
/// Provides library for typst compiler.
|
||||
pub library: Arc<LazyHash<Library>>,
|
||||
|
@ -478,7 +478,7 @@ impl<F: CompilerFeat> CompilerWorld<F> {
|
|||
let library = mutant
|
||||
.inputs
|
||||
.clone()
|
||||
.map(|inputs| create_library(inputs, self.enable_html));
|
||||
.map(|inputs| create_library(inputs, self.features.clone()));
|
||||
|
||||
let root_changed = if let Some(e) = mutant.entry.as_ref() {
|
||||
self.entry.workspace_root() != e.workspace_root()
|
||||
|
@ -487,7 +487,7 @@ impl<F: CompilerFeat> CompilerWorld<F> {
|
|||
};
|
||||
|
||||
let mut world = CompilerWorld {
|
||||
enable_html: self.enable_html,
|
||||
features: self.features.clone(),
|
||||
inputs: mutant.inputs.unwrap_or_else(|| self.inputs.clone()),
|
||||
library: library.unwrap_or_else(|| self.library.clone()),
|
||||
entry: mutant.entry.unwrap_or_else(|| self.entry.clone()),
|
||||
|
@ -572,14 +572,15 @@ impl<F: CompilerFeat> CompilerWorld<F> {
|
|||
}
|
||||
|
||||
pub fn paged_task(&self) -> Cow<'_, CompilerWorld<F>> {
|
||||
let enabled_paged = !self.library.features.is_enabled(typst::Feature::Html);
|
||||
let force_html = self.features.is_enabled(typst::Feature::Html);
|
||||
let enabled_paged = !self.library.features.is_enabled(typst::Feature::Html) || force_html;
|
||||
|
||||
if enabled_paged {
|
||||
return Cow::Borrowed(self);
|
||||
}
|
||||
|
||||
let mut world = self.clone();
|
||||
world.library = create_library(world.inputs.clone(), false);
|
||||
world.library = create_library(world.inputs.clone(), self.features.clone());
|
||||
|
||||
Cow::Owned(world)
|
||||
}
|
||||
|
@ -591,8 +592,12 @@ impl<F: CompilerFeat> CompilerWorld<F> {
|
|||
return Cow::Borrowed(self);
|
||||
}
|
||||
|
||||
// todo: We need some way to enable html features based on the features but
|
||||
// typst doesn't give one.
|
||||
let features = typst::Features::from_iter([typst::Feature::Html]);
|
||||
|
||||
let mut world = self.clone();
|
||||
world.library = create_library(world.inputs.clone(), true);
|
||||
world.library = create_library(world.inputs.clone(), features);
|
||||
|
||||
Cow::Owned(world)
|
||||
}
|
||||
|
@ -905,13 +910,7 @@ impl<'a, F: CompilerFeat> codespan_reporting::files::Files<'a> for CompilerWorld
|
|||
}
|
||||
|
||||
#[comemo::memoize]
|
||||
fn create_library(inputs: Arc<LazyHash<Dict>>, enable_html: bool) -> Arc<LazyHash<Library>> {
|
||||
let features = if enable_html {
|
||||
typst::Features::from_iter([typst::Feature::Html])
|
||||
} else {
|
||||
typst::Features::default()
|
||||
};
|
||||
|
||||
fn create_library(inputs: Arc<LazyHash<Dict>>, features: Features) -> Arc<LazyHash<Library>> {
|
||||
let lib = typst::Library::builder()
|
||||
.with_inputs(inputs.deref().deref().clone())
|
||||
.with_features(features)
|
||||
|
|
|
@ -27,13 +27,16 @@ use crate::tool::package::InitTask;
|
|||
/// See [`ProjectTask`].
|
||||
#[derive(Debug, Clone, Default, Deserialize)]
|
||||
struct ExportOpts {
|
||||
creation_timestamp: Option<String>,
|
||||
fill: Option<String>,
|
||||
ppi: Option<f32>,
|
||||
#[serde(default)]
|
||||
page: PageSelection,
|
||||
/// Whether to open the exported file(s) after the export is done.
|
||||
open: Option<bool>,
|
||||
/// The creation timestamp for various outputs (in seconds).
|
||||
creation_timestamp: Option<String>,
|
||||
/// A PDF standard that Typst can enforce conformance with.
|
||||
pdf_standard: Option<Vec<PdfStandard>>,
|
||||
}
|
||||
|
||||
/// See [`ProjectTask`].
|
||||
|
@ -71,13 +74,14 @@ impl ServerState {
|
|||
} else {
|
||||
self.config.creation_timestamp()
|
||||
};
|
||||
let pdf_standards = opts.pdf_standard.or_else(|| self.config.pdf_standards());
|
||||
|
||||
let export = self.config.export_task();
|
||||
self.export(
|
||||
req_id,
|
||||
ProjectTask::ExportPdf(ExportPdfTask {
|
||||
export,
|
||||
pdf_standards: vec![],
|
||||
pdf_standards: pdf_standards.unwrap_or_default(),
|
||||
creation_timestamp,
|
||||
}),
|
||||
opts.open.unwrap_or_default(),
|
||||
|
|
|
@ -20,6 +20,7 @@ use tinymist_render::PeriscopeArgs;
|
|||
use tinymist_std::error::prelude::*;
|
||||
use tinymist_task::ExportTarget;
|
||||
use typst::foundations::IntoValue;
|
||||
use typst::Features;
|
||||
use typst_shim::utils::LazyHash;
|
||||
|
||||
use super::*;
|
||||
|
@ -410,6 +411,8 @@ impl Config {
|
|||
root_dir: args.root.as_ref().map(|r| r.as_path().into()),
|
||||
font: args.font,
|
||||
package: args.package,
|
||||
pdf_standard: args.pdf_standard,
|
||||
features: args.features,
|
||||
creation_timestamp: args.creation_timestamp,
|
||||
cert: args.cert.as_deref().map(From::from),
|
||||
});
|
||||
|
@ -504,7 +507,7 @@ impl Config {
|
|||
// },
|
||||
task: ProjectTask::ExportPdf(ExportPdfTask {
|
||||
export,
|
||||
pdf_standards: vec![],
|
||||
pdf_standards: self.pdf_standards().unwrap_or_default(),
|
||||
creation_timestamp: self.creation_timestamp(),
|
||||
}),
|
||||
count_words: self.notify_status,
|
||||
|
@ -592,6 +595,17 @@ impl Config {
|
|||
EMPTY.clone()
|
||||
}
|
||||
|
||||
/// Determines the typst features
|
||||
pub fn typst_features(&self) -> Option<Features> {
|
||||
let features = &self.typst_extra_args.as_ref()?.features;
|
||||
Some(Features::from_iter(features.iter().map(|f| (*f).into())))
|
||||
}
|
||||
|
||||
/// Determines the pdf standards.
|
||||
pub fn pdf_standards(&self) -> Option<Vec<PdfStandard>> {
|
||||
Some(self.typst_extra_args.as_ref()?.pdf_standard.clone())
|
||||
}
|
||||
|
||||
/// Determines the creation timestamp.
|
||||
pub fn creation_timestamp(&self) -> Option<i64> {
|
||||
self.typst_extra_args.as_ref()?.creation_timestamp
|
||||
|
@ -808,6 +822,12 @@ pub struct TypstExtraArgs {
|
|||
pub font: CompileFontArgs,
|
||||
/// The package related arguments.
|
||||
pub package: CompilePackageArgs,
|
||||
/// One (or multiple comma-separated) PDF standards that Typst will enforce
|
||||
/// conformance with.
|
||||
pub features: Vec<Feature>,
|
||||
/// One (or multiple comma-separated) PDF standards that Typst will enforce
|
||||
/// conformance with.
|
||||
pub pdf_standard: Vec<PdfStandard>,
|
||||
/// The creation timestamp for various outputs (in seconds).
|
||||
pub creation_timestamp: Option<i64>,
|
||||
/// The path to the certification file.
|
||||
|
|
|
@ -124,8 +124,7 @@ impl ServerState {
|
|||
entry: Option<ImmutPath>,
|
||||
) -> Result<ProjectInsId> {
|
||||
let entry = self.config.entry_resolver.resolve(entry);
|
||||
let enable_html = matches!(self.config.export_target, ExportTarget::Html);
|
||||
self.project.restart_dedicate(dedicate, entry, enable_html)
|
||||
self.project.restart_dedicate(dedicate, entry)
|
||||
}
|
||||
|
||||
/// Create a fresh [`ProjectState`].
|
||||
|
@ -184,14 +183,14 @@ impl ServerState {
|
|||
let inputs = config.inputs();
|
||||
let cert_path = config.certification_path();
|
||||
let package = config.package_opts();
|
||||
let features = config.typst_features().unwrap_or_default();
|
||||
|
||||
log::info!("ServerState: creating ProjectState, entry: {entry:?}, inputs: {inputs:?}");
|
||||
|
||||
let fonts = config.fonts();
|
||||
let package_registry =
|
||||
LspUniverseBuilder::resolve_package(cert_path.clone(), Some(&package));
|
||||
let packages = LspUniverseBuilder::resolve_package(cert_path.clone(), Some(&package));
|
||||
let verse =
|
||||
LspUniverseBuilder::build(entry, export_target, inputs, fonts, package_registry);
|
||||
LspUniverseBuilder::build(entry, export_target, features, inputs, packages, fonts);
|
||||
|
||||
// todo: unify filesystem watcher
|
||||
let (dep_tx, dep_rx) = mpsc::unbounded_channel();
|
||||
|
@ -208,6 +207,7 @@ impl ServerState {
|
|||
dep_tx,
|
||||
CompileServerOpts {
|
||||
handler: compile_handle,
|
||||
export_target: config.export_target,
|
||||
enable_watch: true,
|
||||
},
|
||||
);
|
||||
|
@ -337,9 +337,8 @@ impl ProjectState {
|
|||
&mut self,
|
||||
group: &str,
|
||||
entry: EntryState,
|
||||
enable_html: bool,
|
||||
) -> Result<ProjectInsId> {
|
||||
self.compiler.restart_dedicate(group, entry, enable_html)
|
||||
self.compiler.restart_dedicate(group, entry)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -9,6 +9,7 @@ use sync_ls::*;
|
|||
use tinymist_query::{LspWorldExt, OnExportRequest, ServerInfoResponse};
|
||||
use tinymist_std::error::prelude::*;
|
||||
use tinymist_std::ImmutPath;
|
||||
use tinymist_task::ProjectTask;
|
||||
use tokio::sync::mpsc;
|
||||
use typst::syntax::Source;
|
||||
|
||||
|
@ -442,7 +443,8 @@ impl ServerState {
|
|||
..TaskInputs::default()
|
||||
});
|
||||
|
||||
let artifact = CompiledArtifact::from_graph(snap.clone());
|
||||
let is_html = matches!(task, ProjectTask::ExportHtml { .. });
|
||||
let artifact = CompiledArtifact::from_graph(snap.clone(), is_html);
|
||||
let res = ExportTask::do_export(task, artifact, lock_dir).await?;
|
||||
if let Some(update_dep) = update_dep {
|
||||
tokio::spawn(update_dep(snap));
|
||||
|
|
|
@ -141,7 +141,8 @@ pub async fn compile_main(args: CompileArgs) -> Result<()> {
|
|||
let graph = WorldComputeGraph::from_world(world);
|
||||
|
||||
// Compiles the project
|
||||
let compiled = CompiledArtifact::from_graph(graph);
|
||||
let is_html = matches!(output.task, ProjectTask::ExportHtml(..));
|
||||
let compiled = CompiledArtifact::from_graph(graph, is_html);
|
||||
|
||||
let diag = compiled.diagnostics();
|
||||
print_diagnostics(compiled.world(), diag, DiagnosticFormat::Human)
|
||||
|
@ -410,6 +411,8 @@ pub(crate) struct ProjectOpts {
|
|||
pub config: Config,
|
||||
/// The shared preview state.
|
||||
pub preview: ProjectPreviewState,
|
||||
/// The export target.
|
||||
pub export_target: ExportTarget,
|
||||
}
|
||||
|
||||
pub(crate) struct StartProjectResult<F> {
|
||||
|
@ -465,6 +468,7 @@ where
|
|||
dep_tx,
|
||||
CompileServerOpts {
|
||||
handler: compile_handle,
|
||||
export_target: opts.export_target,
|
||||
enable_watch: true,
|
||||
},
|
||||
);
|
||||
|
|
|
@ -25,8 +25,9 @@ fn conv_(s: &str, for_docs: bool) -> EcoString {
|
|||
EntryState::new_rooted_by_id(cwd.as_path().into(), main.id()),
|
||||
ExportTarget::Paged,
|
||||
Default::default(),
|
||||
FONT_RESOLVER.clone(),
|
||||
Default::default(),
|
||||
Default::default(),
|
||||
FONT_RESOLVER.clone(),
|
||||
);
|
||||
let main_id = universe.main_id().unwrap();
|
||||
universe
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue