mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-07-24 13:13:43 +00:00
feat: support creation-timestamp configuration for exporting PDF (#439)
* feat: support creation-timestamp configuration for exporting PDF * fix: respect config
This commit is contained in:
parent
25c449c2b2
commit
103e0f3b3e
11 changed files with 97 additions and 16 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
@ -439,8 +439,10 @@ checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"android-tzdata",
|
"android-tzdata",
|
||||||
"iana-time-zone",
|
"iana-time-zone",
|
||||||
|
"js-sys",
|
||||||
"num-traits",
|
"num-traits",
|
||||||
"serde",
|
"serde",
|
||||||
|
"wasm-bindgen",
|
||||||
"windows-targets 0.52.6",
|
"windows-targets 0.52.6",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -3740,6 +3742,7 @@ dependencies = [
|
||||||
"async-trait",
|
"async-trait",
|
||||||
"base64 0.22.1",
|
"base64 0.22.1",
|
||||||
"cargo_metadata",
|
"cargo_metadata",
|
||||||
|
"chrono",
|
||||||
"clap",
|
"clap",
|
||||||
"clap_builder",
|
"clap_builder",
|
||||||
"clap_complete",
|
"clap_complete",
|
||||||
|
@ -3807,6 +3810,7 @@ version = "0.11.15"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"biblatex",
|
"biblatex",
|
||||||
|
"chrono",
|
||||||
"comemo 0.4.0",
|
"comemo 0.4.0",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"ecow 0.2.2",
|
"ecow 0.2.2",
|
||||||
|
|
|
@ -40,6 +40,7 @@ tokio-util = { version = "0.7.10", features = ["compat"] }
|
||||||
open = { version = "5.1.3" }
|
open = { version = "5.1.3" }
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
|
chrono = "0.4"
|
||||||
|
|
||||||
# Networking
|
# Networking
|
||||||
hyper = { version = "0.14", features = ["full"] }
|
hyper = { version = "0.14", features = ["full"] }
|
||||||
|
|
|
@ -31,6 +31,7 @@ walkdir.workspace = true
|
||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
ecow.workspace = true
|
ecow.workspace = true
|
||||||
siphasher.workspace = true
|
siphasher.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
|
|
||||||
typst.workspace = true
|
typst.workspace = true
|
||||||
|
|
||||||
|
|
|
@ -143,10 +143,11 @@ mod polymorphic {
|
||||||
Merged,
|
Merged,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Default)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum ExportKind {
|
pub enum ExportKind {
|
||||||
#[default]
|
Pdf {
|
||||||
Pdf,
|
creation_timestamp: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
|
},
|
||||||
Svg {
|
Svg {
|
||||||
page: PageSelection,
|
page: PageSelection,
|
||||||
},
|
},
|
||||||
|
@ -155,10 +156,18 @@ mod polymorphic {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Default for ExportKind {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::Pdf {
|
||||||
|
creation_timestamp: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ExportKind {
|
impl ExportKind {
|
||||||
pub fn extension(&self) -> &str {
|
pub fn extension(&self) -> &str {
|
||||||
match self {
|
match self {
|
||||||
Self::Pdf => "pdf",
|
Self::Pdf { .. } => "pdf",
|
||||||
Self::Svg { .. } => "svg",
|
Self::Svg { .. } => "svg",
|
||||||
Self::Png { .. } => "png",
|
Self::Png { .. } => "png",
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,6 +16,7 @@ tinymist-assets = { workspace = true }
|
||||||
tinymist-query.workspace = true
|
tinymist-query.workspace = true
|
||||||
tinymist-render.workspace = true
|
tinymist-render.workspace = true
|
||||||
sync-lsp.workspace = true
|
sync-lsp.workspace = true
|
||||||
|
chrono.workspace = true
|
||||||
|
|
||||||
once_cell.workspace = true
|
once_cell.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
|
|
|
@ -59,7 +59,9 @@ impl LanguageState {
|
||||||
output: self.compile_config().output_path.clone(),
|
output: self.compile_config().output_path.clone(),
|
||||||
mode: self.compile_config().export_pdf,
|
mode: self.compile_config().export_pdf,
|
||||||
},
|
},
|
||||||
kind: ExportKind::Pdf,
|
kind: ExportKind::Pdf {
|
||||||
|
creation_timestamp: self.config.compile.determine_creation_timestamp(),
|
||||||
|
},
|
||||||
count_words: self.config.compile.notify_status,
|
count_words: self.config.compile.notify_status,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -27,7 +27,13 @@ struct ExportOpts {
|
||||||
impl LanguageState {
|
impl LanguageState {
|
||||||
/// Export the current document as PDF file(s).
|
/// Export the current document as PDF file(s).
|
||||||
pub fn export_pdf(&mut self, req_id: RequestId, args: Vec<JsonValue>) -> ScheduledResult {
|
pub fn export_pdf(&mut self, req_id: RequestId, args: Vec<JsonValue>) -> ScheduledResult {
|
||||||
self.export(req_id, ExportKind::Pdf, args)
|
self.export(
|
||||||
|
req_id,
|
||||||
|
ExportKind::Pdf {
|
||||||
|
creation_timestamp: self.config.compile.determine_creation_timestamp(),
|
||||||
|
},
|
||||||
|
args,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Export the current document as Svg file(s).
|
/// Export the current document as Svg file(s).
|
||||||
|
|
|
@ -500,7 +500,8 @@ impl CompileConfig {
|
||||||
entry: command.input.map(|e| Path::new(&e).into()),
|
entry: command.input.map(|e| Path::new(&e).into()),
|
||||||
root_dir: command.root,
|
root_dir: command.root,
|
||||||
inputs: Arc::new(Prehashed::new(inputs)),
|
inputs: Arc::new(Prehashed::new(inputs)),
|
||||||
font_paths: command.font.font_paths,
|
font: command.font,
|
||||||
|
creation_timestamp: command.creation_timestamp,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -621,13 +622,17 @@ impl CompileConfig {
|
||||||
let font = || {
|
let font = || {
|
||||||
let mut opts = self.font_opts.clone();
|
let mut opts = self.font_opts.clone();
|
||||||
|
|
||||||
if let Some(system_fonts) = self.system_fonts {
|
if let Some(system_fonts) = self.system_fonts.or_else(|| {
|
||||||
|
self.typst_extra_args
|
||||||
|
.as_ref()
|
||||||
|
.map(|x| x.font.ignore_system_fonts)
|
||||||
|
}) {
|
||||||
opts.ignore_system_fonts = !system_fonts;
|
opts.ignore_system_fonts = !system_fonts;
|
||||||
}
|
}
|
||||||
|
|
||||||
let font_paths = (!self.font_paths.is_empty()).then_some(&self.font_paths);
|
let font_paths = (!self.font_paths.is_empty()).then_some(&self.font_paths);
|
||||||
let font_paths =
|
let font_paths =
|
||||||
font_paths.or_else(|| self.typst_extra_args.as_ref().map(|x| &x.font_paths));
|
font_paths.or_else(|| self.typst_extra_args.as_ref().map(|x| &x.font.font_paths));
|
||||||
if let Some(paths) = font_paths {
|
if let Some(paths) = font_paths {
|
||||||
opts.font_paths.clone_from(paths);
|
opts.font_paths.clone_from(paths);
|
||||||
}
|
}
|
||||||
|
@ -669,6 +674,11 @@ impl CompileConfig {
|
||||||
combine(user_inputs, self.lsp_inputs.clone())
|
combine(user_inputs, self.lsp_inputs.clone())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Determines the creation timestamp.
|
||||||
|
pub fn determine_creation_timestamp(&self) -> Option<chrono::DateTime<chrono::Utc>> {
|
||||||
|
self.typst_extra_args.as_ref()?.creation_timestamp
|
||||||
|
}
|
||||||
|
|
||||||
fn determine_user_inputs(&self) -> ImmutDict {
|
fn determine_user_inputs(&self) -> ImmutDict {
|
||||||
static EMPTY: Lazy<ImmutDict> = Lazy::new(ImmutDict::default);
|
static EMPTY: Lazy<ImmutDict> = Lazy::new(ImmutDict::default);
|
||||||
|
|
||||||
|
@ -686,13 +696,13 @@ impl CompileConfig {
|
||||||
) -> (
|
) -> (
|
||||||
Option<bool>,
|
Option<bool>,
|
||||||
&Vec<PathBuf>,
|
&Vec<PathBuf>,
|
||||||
Option<&Vec<PathBuf>>,
|
Option<&CompileFontArgs>,
|
||||||
Option<Arc<Path>>,
|
Option<Arc<Path>>,
|
||||||
) {
|
) {
|
||||||
(
|
(
|
||||||
self.system_fonts,
|
self.system_fonts,
|
||||||
&self.font_paths,
|
&self.font_paths,
|
||||||
self.typst_extra_args.as_ref().map(|e| &e.font_paths),
|
self.typst_extra_args.as_ref().map(|e| &e.font),
|
||||||
self.determine_root(self.determine_default_entry_path().as_ref()),
|
self.determine_root(self.determine_default_entry_path().as_ref()),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -766,8 +776,10 @@ pub struct CompileExtraOpts {
|
||||||
pub entry: Option<ImmutPath>,
|
pub entry: Option<ImmutPath>,
|
||||||
/// Additional input arguments to compile the entry file.
|
/// Additional input arguments to compile the entry file.
|
||||||
pub inputs: ImmutDict,
|
pub inputs: ImmutDict,
|
||||||
/// will remove later
|
/// Additional font paths.
|
||||||
pub font_paths: Vec<PathBuf>,
|
pub font: CompileFontArgs,
|
||||||
|
/// The creation timestamp for various output.
|
||||||
|
pub creation_timestamp: Option<chrono::DateTime<chrono::Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The path pattern that could be substituted.
|
/// The path pattern that could be substituted.
|
||||||
|
|
|
@ -8,6 +8,7 @@ use tinymist_query::{ExportKind, PageSelection};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use typst::{foundations::Smart, layout::Abs, layout::Frame, visualize::Color};
|
use typst::{foundations::Smart, layout::Abs, layout::Frame, visualize::Color};
|
||||||
use typst_ts_compiler::{EntryReader, EntryState, TaskInputs};
|
use typst_ts_compiler::{EntryReader, EntryState, TaskInputs};
|
||||||
|
use typst_ts_core::TypstDatetime;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
actor::{
|
actor::{
|
||||||
|
@ -196,10 +197,12 @@ impl ExportConfig {
|
||||||
static BLANK: Lazy<Frame> = Lazy::new(Frame::default);
|
static BLANK: Lazy<Frame> = Lazy::new(Frame::default);
|
||||||
let first_frame = || doc.pages.first().map(|f| &f.frame).unwrap_or(&*BLANK);
|
let first_frame = || doc.pages.first().map(|f| &f.frame).unwrap_or(&*BLANK);
|
||||||
Ok(match kind2 {
|
Ok(match kind2 {
|
||||||
Pdf => {
|
Pdf { creation_timestamp } => {
|
||||||
|
let timestamp =
|
||||||
|
convert_datetime(creation_timestamp.unwrap_or_else(chrono::Utc::now));
|
||||||
// todo: Some(pdf_uri.as_str())
|
// todo: Some(pdf_uri.as_str())
|
||||||
// todo: timestamp world.now()
|
// todo: timestamp world.now()
|
||||||
typst_pdf::pdf(doc, Smart::Auto, None)
|
typst_pdf::pdf(doc, Smart::Auto, timestamp)
|
||||||
}
|
}
|
||||||
Svg { page: First } => typst_svg::svg(first_frame()).into_bytes(),
|
Svg { page: First } => typst_svg::svg(first_frame()).into_bytes(),
|
||||||
Svg { page: Merged } => typst_svg::svg_merged(doc, Abs::zero()).into_bytes(),
|
Svg { page: Merged } => typst_svg::svg_merged(doc, Abs::zero()).into_bytes(),
|
||||||
|
@ -233,6 +236,19 @@ fn log_err<T>(artifact: anyhow::Result<T>) -> Option<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Convert [`chrono::DateTime`] to [`TypstDatetime`]
|
||||||
|
fn convert_datetime(date_time: chrono::DateTime<chrono::Utc>) -> Option<TypstDatetime> {
|
||||||
|
use chrono::{Datelike, Timelike};
|
||||||
|
TypstDatetime::from_ymd_hms(
|
||||||
|
date_time.year(),
|
||||||
|
date_time.month().try_into().ok()?,
|
||||||
|
date_time.day().try_into().ok()?,
|
||||||
|
date_time.hour().try_into().ok()?,
|
||||||
|
date_time.minute().try_into().ok()?,
|
||||||
|
date_time.second().try_into().ok()?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
use std::{borrow::Cow, path::PathBuf, sync::Arc};
|
use std::{borrow::Cow, path::PathBuf, sync::Arc};
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
use clap::{builder::ValueParser, ArgAction, Parser};
|
use clap::{builder::ValueParser, ArgAction, Parser};
|
||||||
use comemo::Prehashed;
|
use comemo::Prehashed;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
@ -20,7 +21,7 @@ use typst_ts_compiler::{
|
||||||
const ENV_PATH_SEP: char = if cfg!(windows) { ';' } else { ':' };
|
const ENV_PATH_SEP: char = if cfg!(windows) { ';' } else { ':' };
|
||||||
|
|
||||||
/// The font arguments for the compiler.
|
/// The font arguments for the compiler.
|
||||||
#[derive(Debug, Clone, Default, Parser, Serialize, Deserialize)]
|
#[derive(Debug, Clone, Default, Parser, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(rename_all = "camelCase")]
|
#[serde(rename_all = "camelCase")]
|
||||||
pub struct CompileFontArgs {
|
pub struct CompileFontArgs {
|
||||||
/// Font paths
|
/// Font paths
|
||||||
|
@ -62,6 +63,18 @@ pub struct CompileOnceArgs {
|
||||||
/// Font related arguments.
|
/// Font related arguments.
|
||||||
#[clap(flatten)]
|
#[clap(flatten)]
|
||||||
pub font: CompileFontArgs,
|
pub font: CompileFontArgs,
|
||||||
|
|
||||||
|
/// The document's creation date formatted as a UNIX timestamp.
|
||||||
|
///
|
||||||
|
/// For more information, see <https://reproducible-builds.org/specs/source-date-epoch/>.
|
||||||
|
#[clap(
|
||||||
|
long = "creation-timestamp",
|
||||||
|
env = "SOURCE_DATE_EPOCH",
|
||||||
|
value_name = "UNIX_TIMESTAMP",
|
||||||
|
value_parser = parse_source_date_epoch,
|
||||||
|
hide(true),
|
||||||
|
)]
|
||||||
|
pub creation_timestamp: Option<DateTime<Utc>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compiler feature for LSP world.
|
/// Compiler feature for LSP world.
|
||||||
|
@ -121,3 +134,11 @@ fn parse_input_pair(raw: &str) -> Result<(String, String), String> {
|
||||||
let val = val.trim().to_owned();
|
let val = val.trim().to_owned();
|
||||||
Ok((key, val))
|
Ok((key, val))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Parses a UNIX timestamp according to <https://reproducible-builds.org/specs/source-date-epoch/>
|
||||||
|
fn parse_source_date_epoch(raw: &str) -> Result<DateTime<Utc>, String> {
|
||||||
|
let timestamp: i64 = raw
|
||||||
|
.parse()
|
||||||
|
.map_err(|err| format!("timestamp must be decimal integer ({err})"))?;
|
||||||
|
DateTime::from_timestamp(timestamp, 0).ok_or_else(|| "timestamp out of range".to_string())
|
||||||
|
}
|
||||||
|
|
|
@ -116,6 +116,14 @@ There is a **global** configuration `tinymist.typstExtraArgs` to pass extra argu
|
||||||
typst watch --input=awa=1 --input=abaaba=2 main.typ
|
typst watch --input=awa=1 --input=abaaba=2 main.typ
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Supported arguments:
|
||||||
|
- entry file: The last string in the array will be treated as the entry file.
|
||||||
|
- This is used to specify the **default** entry file for the compiler, which may be overridden by other settings.
|
||||||
|
- `--input`: Add a string key-value pair visible through `sys.inputs`.
|
||||||
|
- `--font-path` (environment variable: `TYPST_FONT_PATHS`), Font paths, maybe overriden by `tinymist.fontPaths`.
|
||||||
|
- `--ignore-system-fonts`: Ensures system fonts won't be searched, maybe overriden by `tinymist.systemFonts`.
|
||||||
|
- `--creation-timestamp` (environment variable: `SOURCE_DATE_EPOCH`): The document's creation date formatted as a [UNIX timestamp](https://reproducible-builds.org/specs/source-date-epoch/).
|
||||||
|
|
||||||
**Note:** Fix entry to `main.typ` may help multiple-file projects but you may loss diagnostics and autocompletions in unrelated files.
|
**Note:** Fix entry to `main.typ` may help multiple-file projects but you may loss diagnostics and autocompletions in unrelated files.
|
||||||
|
|
||||||
Note: the arguments has quite low priority, and that may be overridden by other settings.
|
Note: the arguments has quite low priority, and that may be overridden by other settings.
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue