feat: support --feature and --pdf-standard. (#1596)

This commit is contained in:
Myriad-Dreamin 2025-03-28 17:42:03 +08:00 committed by GitHub
parent e443c3172b
commit 90f9949f8c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
15 changed files with 289 additions and 229 deletions

View file

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

View file

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

View file

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

View file

@ -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()
// },
// )
// }
// }

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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(),

View file

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

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

View file

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

View file

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

View file

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