mirror of
https://github.com/Myriad-Dreamin/tinymist.git
synced 2025-11-24 05:06:41 +00:00
fix: remove system time deps from crates (#1621)
* fix: remove system time deps from crates * fix: remove system time deps from crates * fix: smater feature gate * docs: add some todos * Update time.rs * Update Cargo.toml * build: remove hard dep chrono
This commit is contained in:
parent
35b718452e
commit
769fc93df9
12 changed files with 190 additions and 90 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -4192,7 +4192,6 @@ name = "tinymist-project"
|
||||||
version = "0.13.12-rc1"
|
version = "0.13.12-rc1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
|
||||||
"clap",
|
"clap",
|
||||||
"comemo",
|
"comemo",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
|
@ -4223,7 +4222,6 @@ dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"base64",
|
"base64",
|
||||||
"biblatex",
|
"biblatex",
|
||||||
"chrono",
|
|
||||||
"comemo",
|
"comemo",
|
||||||
"dashmap",
|
"dashmap",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
|
@ -4312,6 +4310,7 @@ dependencies = [
|
||||||
"serde_with",
|
"serde_with",
|
||||||
"siphasher",
|
"siphasher",
|
||||||
"tempfile",
|
"tempfile",
|
||||||
|
"time",
|
||||||
"typst",
|
"typst",
|
||||||
"typst-shim",
|
"typst-shim",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
|
|
@ -4324,7 +4323,6 @@ name = "tinymist-task"
|
||||||
version = "0.13.12-rc1"
|
version = "0.13.12-rc1"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"chrono",
|
|
||||||
"clap",
|
"clap",
|
||||||
"comemo",
|
"comemo",
|
||||||
"dirs",
|
"dirs",
|
||||||
|
|
|
||||||
|
|
@ -41,6 +41,7 @@ open = { version = "5.1.3" }
|
||||||
parking_lot = "0.12.1"
|
parking_lot = "0.12.1"
|
||||||
walkdir = "2"
|
walkdir = "2"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
|
time = "0.3"
|
||||||
dirs = "6"
|
dirs = "6"
|
||||||
fontdb = "0.21"
|
fontdb = "0.21"
|
||||||
notify = "6"
|
notify = "6"
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
chrono.workspace = true
|
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
comemo.workspace = true
|
comemo.workspace = true
|
||||||
dirs.workspace = true
|
dirs.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,6 @@ walkdir.workspace = true
|
||||||
indexmap.workspace = true
|
indexmap.workspace = true
|
||||||
ecow.workspace = true
|
ecow.workspace = true
|
||||||
siphasher.workspace = true
|
siphasher.workspace = true
|
||||||
chrono.workspace = true
|
|
||||||
rpds.workspace = true
|
rpds.workspace = true
|
||||||
rayon.workspace = true
|
rayon.workspace = true
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,8 @@ serde_repr.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
serde_with.workspace = true
|
serde_with.workspace = true
|
||||||
siphasher.workspace = true
|
siphasher.workspace = true
|
||||||
web-time.workspace = true
|
web-time = { workspace = true, optional = true }
|
||||||
|
time.workspace = true
|
||||||
lsp-types.workspace = true
|
lsp-types.workspace = true
|
||||||
tempfile = { workspace = true, optional = true }
|
tempfile = { workspace = true, optional = true }
|
||||||
same-file = { workspace = true, optional = true }
|
same-file = { workspace = true, optional = true }
|
||||||
|
|
@ -74,7 +75,7 @@ typst = ["dep:typst", "dep:typst-shim"]
|
||||||
rkyv = ["rkyv/alloc", "rkyv/archive_le"]
|
rkyv = ["rkyv/alloc", "rkyv/archive_le"]
|
||||||
rkyv-validation = ["rkyv/validation"]
|
rkyv-validation = ["rkyv/validation"]
|
||||||
|
|
||||||
__web = ["wasm-bindgen", "js-sys"]
|
__web = ["wasm-bindgen", "js-sys", "web-time"]
|
||||||
web = ["__web"]
|
web = ["__web"]
|
||||||
system = ["tempfile", "same-file"]
|
system = ["tempfile", "same-file"]
|
||||||
bi-hash = []
|
bi-hash = []
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,18 @@
|
||||||
//! Cross platform time utilities.
|
//! Cross platform time utilities.
|
||||||
|
|
||||||
pub use std::time::SystemTime as Time;
|
pub use std::time::SystemTime as Time;
|
||||||
|
pub use time::UtcDateTime;
|
||||||
|
|
||||||
|
#[cfg(not(feature = "web"))]
|
||||||
|
pub use std::time::{Duration, Instant};
|
||||||
|
#[cfg(feature = "web")]
|
||||||
pub use web_time::{Duration, Instant};
|
pub use web_time::{Duration, Instant};
|
||||||
|
|
||||||
|
/// Returns the current datetime in utc (UTC+0).
|
||||||
|
pub fn utc_now() -> UtcDateTime {
|
||||||
|
now().into()
|
||||||
|
}
|
||||||
|
|
||||||
/// Returns the current system time (UTC+0).
|
/// Returns the current system time (UTC+0).
|
||||||
#[cfg(any(feature = "system", feature = "web"))]
|
#[cfg(any(feature = "system", feature = "web"))]
|
||||||
pub fn now() -> Time {
|
pub fn now() -> Time {
|
||||||
|
|
@ -22,3 +32,30 @@ pub fn now() -> Time {
|
||||||
pub fn now() -> Time {
|
pub fn now() -> Time {
|
||||||
Time::UNIX_EPOCH
|
Time::UNIX_EPOCH
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The trait helping convert to a [`UtcDateTime`].
|
||||||
|
pub trait ToUtcDateTime {
|
||||||
|
/// Converts to a [`UtcDateTime`].
|
||||||
|
fn to_utc_datetime(self) -> Option<UtcDateTime>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToUtcDateTime for i64 {
|
||||||
|
/// Converts a UNIX timestamp to a [`UtcDateTime`].
|
||||||
|
fn to_utc_datetime(self) -> Option<UtcDateTime> {
|
||||||
|
UtcDateTime::from_unix_timestamp(self).ok()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ToUtcDateTime for Time {
|
||||||
|
/// Converts a system time to a [`UtcDateTime`].
|
||||||
|
fn to_utc_datetime(self) -> Option<UtcDateTime> {
|
||||||
|
Some(UtcDateTime::from(self))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts a [`UtcDateTime`] to typst's datetime.
|
||||||
|
#[cfg(feature = "typst")]
|
||||||
|
pub fn to_typst_time(timestamp: UtcDateTime) -> typst::foundations::Datetime {
|
||||||
|
let datetime = ::time::PrimitiveDateTime::new(timestamp.date(), timestamp.time());
|
||||||
|
typst::foundations::Datetime::Datetime(datetime)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@ rust-version.workspace = true
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
chrono.workspace = true
|
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
comemo.workspace = true
|
comemo.workspace = true
|
||||||
dirs.workspace = true
|
dirs.workspace = true
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
|
use tinymist_std::time::ToUtcDateTime;
|
||||||
pub use typst_pdf::pdf;
|
pub use typst_pdf::pdf;
|
||||||
pub use typst_pdf::PdfStandard as TypstPdfStandard;
|
pub use typst_pdf::PdfStandard as TypstPdfStandard;
|
||||||
|
|
||||||
use tinymist_world::args::convert_source_date_epoch;
|
|
||||||
use typst::foundations::Datetime;
|
|
||||||
use typst_pdf::{PdfOptions, PdfStandards, Timestamp};
|
use typst_pdf::{PdfOptions, PdfStandards, Timestamp};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
@ -19,13 +18,14 @@ impl<F: CompilerFeat> ExportComputation<F, TypstPagedDocument> for PdfExport {
|
||||||
doc: &Arc<TypstPagedDocument>,
|
doc: &Arc<TypstPagedDocument>,
|
||||||
config: &ExportPdfTask,
|
config: &ExportPdfTask,
|
||||||
) -> Result<Bytes> {
|
) -> Result<Bytes> {
|
||||||
// todo: timestamp world.now()
|
|
||||||
let creation_timestamp = config
|
let creation_timestamp = config
|
||||||
.creation_timestamp
|
.creation_timestamp
|
||||||
.map(convert_source_date_epoch)
|
.map(|ts| ts.to_utc_datetime().context("timestamp is out of range"))
|
||||||
.transpose()
|
.transpose()?
|
||||||
.context_ut("prepare pdf creation timestamp")?
|
.unwrap_or_else(tinymist_std::time::utc_now);
|
||||||
.unwrap_or_else(chrono::Utc::now);
|
// todo: this seems different from `Timestamp::new_local` which also embeds the
|
||||||
|
// timezone information.
|
||||||
|
let timestamp = Timestamp::new_utc(tinymist_std::time::to_typst_time(creation_timestamp));
|
||||||
|
|
||||||
let standards = PdfStandards::new(
|
let standards = PdfStandards::new(
|
||||||
&config
|
&config
|
||||||
|
|
@ -45,23 +45,10 @@ impl<F: CompilerFeat> ExportComputation<F, TypstPagedDocument> for PdfExport {
|
||||||
Ok(Bytes::new(typst_pdf::pdf(
|
Ok(Bytes::new(typst_pdf::pdf(
|
||||||
doc,
|
doc,
|
||||||
&PdfOptions {
|
&PdfOptions {
|
||||||
timestamp: convert_datetime(creation_timestamp),
|
timestamp: Some(timestamp),
|
||||||
standards,
|
standards,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
},
|
},
|
||||||
)?))
|
)?))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Convert [`chrono::DateTime`] to [`Timestamp`]
|
|
||||||
pub fn convert_datetime(date_time: chrono::DateTime<chrono::Utc>) -> Option<Timestamp> {
|
|
||||||
use chrono::{Datelike, Timelike};
|
|
||||||
Some(Timestamp::new_utc(Datetime::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()?,
|
|
||||||
)?))
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,10 @@ rust-version.workspace = true
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
chrono.workspace = true
|
chrono = { workspace = true, default-features = false, optional = true, features = [
|
||||||
|
"std",
|
||||||
|
"clock",
|
||||||
|
] }
|
||||||
clap.workspace = true
|
clap.workspace = true
|
||||||
codespan-reporting.workspace = true
|
codespan-reporting.workspace = true
|
||||||
comemo.workspace = true
|
comemo.workspace = true
|
||||||
|
|
@ -49,8 +52,10 @@ web-sys = { workspace = true, optional = true, features = ["console"] }
|
||||||
|
|
||||||
default = []
|
default = []
|
||||||
browser-embedded-fonts = ["typst-assets/fonts"]
|
browser-embedded-fonts = ["typst-assets/fonts"]
|
||||||
http-registry = ["dep:reqwest"]
|
http-registry = ["reqwest"]
|
||||||
web = [
|
web = [
|
||||||
|
"chrono",
|
||||||
|
"chrono/wasmbind",
|
||||||
"wasm-bindgen",
|
"wasm-bindgen",
|
||||||
"web-sys",
|
"web-sys",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
|
@ -60,8 +65,9 @@ web = [
|
||||||
]
|
]
|
||||||
browser = ["tinymist-vfs/browser", "web"]
|
browser = ["tinymist-vfs/browser", "web"]
|
||||||
system = [
|
system = [
|
||||||
"dep:dirs",
|
"dirs",
|
||||||
"dep:fontdb",
|
"fontdb",
|
||||||
|
"chrono",
|
||||||
"http-registry",
|
"http-registry",
|
||||||
"tinymist-std/system",
|
"tinymist-std/system",
|
||||||
"tinymist-vfs/system",
|
"tinymist-vfs/system",
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ use std::{
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, Utc};
|
|
||||||
use clap::{builder::ValueParser, ArgAction, Parser, ValueEnum};
|
use clap::{builder::ValueParser, ArgAction, Parser, ValueEnum};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use tinymist_std::{bail, error::prelude::*};
|
use tinymist_std::{bail, error::prelude::*};
|
||||||
|
|
@ -211,11 +210,6 @@ pub fn parse_source_date_epoch(raw: &str) -> Result<i64, String> {
|
||||||
.map_err(|err| format!("timestamp must be decimal integer ({err})"))
|
.map_err(|err| format!("timestamp must be decimal integer ({err})"))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Parses a UNIX timestamp according to <https://reproducible-builds.org/specs/source-date-epoch/>
|
|
||||||
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 {
|
macro_rules! display_possible_values {
|
||||||
($ty:ty) => {
|
($ty:ty) => {
|
||||||
impl fmt::Display for $ty {
|
impl fmt::Display for $ty {
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,6 @@ use std::{
|
||||||
sync::{Arc, LazyLock, OnceLock},
|
sync::{Arc, LazyLock, OnceLock},
|
||||||
};
|
};
|
||||||
|
|
||||||
use chrono::{DateTime, Datelike, Local};
|
|
||||||
use tinymist_std::error::prelude::*;
|
use tinymist_std::error::prelude::*;
|
||||||
use tinymist_vfs::{
|
use tinymist_vfs::{
|
||||||
FsProvider, PathResolution, RevisingVfs, SourceCache, TypstFileId, Vfs, WorkspaceResolver,
|
FsProvider, PathResolution, RevisingVfs, SourceCache, TypstFileId, Vfs, WorkspaceResolver,
|
||||||
|
|
@ -432,6 +431,11 @@ fn is_revision_changed(a: Option<NonZeroUsize>, b: Option<NonZeroUsize>) -> bool
|
||||||
a.is_none() || b.is_none() || a != b
|
a.is_none() || b.is_none() || a != b
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "web", feature = "system"))]
|
||||||
|
type NowStorage = chrono::DateTime<chrono::Local>;
|
||||||
|
#[cfg(not(any(feature = "web", feature = "system")))]
|
||||||
|
type NowStorage = tinymist_std::time::UtcDateTime;
|
||||||
|
|
||||||
pub struct CompilerWorld<F: CompilerFeat> {
|
pub struct CompilerWorld<F: CompilerFeat> {
|
||||||
/// State for the *root & entry* of compilation.
|
/// State for the *root & entry* of compilation.
|
||||||
/// The world forbids direct access to files outside this directory.
|
/// The world forbids direct access to files outside this directory.
|
||||||
|
|
@ -455,7 +459,7 @@ pub struct CompilerWorld<F: CompilerFeat> {
|
||||||
source_db: SourceDb,
|
source_db: SourceDb,
|
||||||
/// The current datetime if requested. This is stored here to ensure it is
|
/// The current datetime if requested. This is stored here to ensure it is
|
||||||
/// always the same within one compilation. Reset between compilations.
|
/// always the same within one compilation. Reset between compilations.
|
||||||
now: OnceLock<DateTime<Local>>,
|
now: OnceLock<NowStorage>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: CompilerFeat> Clone for CompilerWorld<F> {
|
impl<F: CompilerFeat> Clone for CompilerWorld<F> {
|
||||||
|
|
@ -707,12 +711,15 @@ impl<F: CompilerFeat> World for CompilerWorld<F> {
|
||||||
///
|
///
|
||||||
/// If this function returns `None`, Typst's `datetime` function will
|
/// If this function returns `None`, Typst's `datetime` function will
|
||||||
/// return an error.
|
/// return an error.
|
||||||
|
#[cfg(any(feature = "web", feature = "system"))]
|
||||||
fn today(&self, offset: Option<i64>) -> Option<Datetime> {
|
fn today(&self, offset: Option<i64>) -> Option<Datetime> {
|
||||||
|
use chrono::{Datelike, Duration};
|
||||||
|
// todo: typst respects creation_timestamp, but we don't...
|
||||||
let now = self.now.get_or_init(|| tinymist_std::time::now().into());
|
let now = self.now.get_or_init(|| tinymist_std::time::now().into());
|
||||||
|
|
||||||
let naive = match offset {
|
let naive = match offset {
|
||||||
None => now.naive_local(),
|
None => now.naive_local(),
|
||||||
Some(o) => now.naive_utc() + chrono::Duration::try_hours(o)?,
|
Some(o) => now.naive_utc() + Duration::try_hours(o)?,
|
||||||
};
|
};
|
||||||
|
|
||||||
Datetime::from_ymd(
|
Datetime::from_ymd(
|
||||||
|
|
@ -721,6 +728,31 @@ impl<F: CompilerFeat> World for CompilerWorld<F> {
|
||||||
naive.day().try_into().ok()?,
|
naive.day().try_into().ok()?,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the current date.
|
||||||
|
///
|
||||||
|
/// If no offset is specified, the local date should be chosen. Otherwise,
|
||||||
|
/// the UTC date should be chosen with the corresponding offset in hours.
|
||||||
|
///
|
||||||
|
/// If this function returns `None`, Typst's `datetime` function will
|
||||||
|
/// return an error.
|
||||||
|
#[cfg(not(any(feature = "web", feature = "system")))]
|
||||||
|
fn today(&self, offset: Option<i64>) -> Option<Datetime> {
|
||||||
|
use tinymist_std::time::{now, to_typst_time, Duration};
|
||||||
|
// todo: typst respects creation_timestamp, but we don't...
|
||||||
|
let now = self.now.get_or_init(|| now().into());
|
||||||
|
|
||||||
|
let now = offset
|
||||||
|
.and_then(|offset| {
|
||||||
|
let dur = Duration::from_secs(offset.checked_mul(3600)? as u64)
|
||||||
|
.try_into()
|
||||||
|
.ok()?;
|
||||||
|
now.checked_add(dur)
|
||||||
|
})
|
||||||
|
.unwrap_or(*now);
|
||||||
|
|
||||||
|
Some(to_typst_time(now))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: CompilerFeat> EntryReader for CompilerWorld<F> {
|
impl<F: CompilerFeat> EntryReader for CompilerWorld<F> {
|
||||||
|
|
|
||||||
|
|
@ -6,24 +6,22 @@ use std::sync::atomic::AtomicUsize;
|
||||||
use std::sync::{Arc, OnceLock};
|
use std::sync::{Arc, OnceLock};
|
||||||
|
|
||||||
use reflexo::ImmutPath;
|
use reflexo::ImmutPath;
|
||||||
use reflexo_typst::CompilationTask;
|
use reflexo_typst::{Bytes, CompilationTask, ExportComputation};
|
||||||
use tinymist_project::LspWorld;
|
use tinymist_project::LspWorld;
|
||||||
use tinymist_std::error::prelude::*;
|
use tinymist_std::error::prelude::*;
|
||||||
use tinymist_std::fs::paths::write_atomic;
|
use tinymist_std::fs::paths::write_atomic;
|
||||||
use tinymist_std::typst::TypstDocument;
|
use tinymist_std::typst::TypstDocument;
|
||||||
use tinymist_task::{convert_datetime, get_page_selection, ExportTarget, TextExport};
|
use tinymist_task::{get_page_selection, ExportTarget, PdfExport, TextExport};
|
||||||
use tokio::sync::mpsc;
|
use tokio::sync::mpsc;
|
||||||
use typlite::Typlite;
|
use typlite::Typlite;
|
||||||
use typst::foundations::IntoValue;
|
use typst::foundations::IntoValue;
|
||||||
use typst::visualize::Color;
|
use typst::visualize::Color;
|
||||||
use typst_pdf::PdfOptions;
|
|
||||||
|
|
||||||
use super::{FutureFolder, SyncTaskFactory};
|
use super::{FutureFolder, SyncTaskFactory};
|
||||||
use crate::project::{
|
use crate::project::{
|
||||||
convert_source_date_epoch, ApplyProjectTask, CompiledArtifact, EntryReader, ExportHtmlTask,
|
ApplyProjectTask, CompiledArtifact, EntryReader, ExportHtmlTask, ExportMarkdownTask,
|
||||||
ExportMarkdownTask, ExportPdfTask, ExportPngTask, ExportSvgTask,
|
ExportPdfTask, ExportPngTask, ExportSvgTask, ExportTask as ProjectExportTask, ExportTextTask,
|
||||||
ExportTask as ProjectExportTask, ExportTextTask, LspCompiledArtifact, ProjectTask, QueryTask,
|
LspCompiledArtifact, ProjectTask, QueryTask, TaskWhen,
|
||||||
TaskWhen,
|
|
||||||
};
|
};
|
||||||
use crate::{actor::editor::EditorRequest, tool::word_count};
|
use crate::{actor::editor::EditorRequest, tool::word_count};
|
||||||
|
|
||||||
|
|
@ -186,7 +184,7 @@ impl ExportTask {
|
||||||
|
|
||||||
// Prepare data.
|
// Prepare data.
|
||||||
let kind2 = task.clone();
|
let kind2 = task.clone();
|
||||||
let data = FutureFolder::compute(move |_| -> Result<Vec<u8>> {
|
let data = FutureFolder::compute(move |_| -> Result<Bytes> {
|
||||||
let doc = &doc;
|
let doc = &doc;
|
||||||
|
|
||||||
// static BLANK: Lazy<Page> = Lazy::new(Page::default);
|
// static BLANK: Lazy<Page> = Lazy::new(Page::default);
|
||||||
|
|
@ -222,28 +220,9 @@ impl ExportTask {
|
||||||
.context("no first page to export")
|
.context("no first page to export")
|
||||||
};
|
};
|
||||||
Ok(match kind2 {
|
Ok(match kind2 {
|
||||||
Preview(..) => vec![],
|
Preview(..) => Bytes::new([]),
|
||||||
// todo: more pdf flags
|
// todo: more pdf flags
|
||||||
ExportPdf(ExportPdfTask {
|
ExportPdf(config) => PdfExport::run(&graph, paged_doc()?, &config)?,
|
||||||
creation_timestamp, ..
|
|
||||||
}) => {
|
|
||||||
// todo: timestamp world.now()
|
|
||||||
let creation_timestamp = creation_timestamp
|
|
||||||
.map(convert_source_date_epoch)
|
|
||||||
.transpose()
|
|
||||||
.context_ut("parse pdf creation timestamp")?
|
|
||||||
.unwrap_or_else(chrono::Utc::now);
|
|
||||||
|
|
||||||
// todo: Some(pdf_uri.as_str())
|
|
||||||
typst_pdf::pdf(
|
|
||||||
paged_doc()?,
|
|
||||||
&PdfOptions {
|
|
||||||
timestamp: convert_datetime(creation_timestamp),
|
|
||||||
..PdfOptions::default()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
.map_err(|e| anyhow::anyhow!("failed to convert to pdf: {e:?}"))?
|
|
||||||
}
|
|
||||||
Query(QueryTask {
|
Query(QueryTask {
|
||||||
export: _,
|
export: _,
|
||||||
output_extension: _,
|
output_extension: _,
|
||||||
|
|
@ -271,37 +250,37 @@ impl ExportTask {
|
||||||
let Some(value) = mapped.first() else {
|
let Some(value) = mapped.first() else {
|
||||||
bail!("no such field found for element");
|
bail!("no such field found for element");
|
||||||
};
|
};
|
||||||
serialize(value, &format, pretty).map(String::into_bytes)?
|
serialize(value, &format, pretty).map(Bytes::from_string)?
|
||||||
} else {
|
} else {
|
||||||
serialize(&mapped, &format, pretty).map(String::into_bytes)?
|
serialize(&mapped, &format, pretty).map(Bytes::from_string)?
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ExportHtml(ExportHtmlTask { export: _ }) => typst_html::html(html_doc()?)
|
ExportHtml(ExportHtmlTask { export: _ }) => Bytes::from_string(
|
||||||
.map_err(|e| format!("export error: {e:?}"))
|
typst_html::html(html_doc()?)
|
||||||
.context_ut("failed to export to html")?
|
.map_err(|e| format!("export error: {e:?}"))
|
||||||
.into_bytes(),
|
.context_ut("failed to export to html")?,
|
||||||
ExportSvgHtml(ExportHtmlTask { export: _ }) => {
|
),
|
||||||
reflexo_vec2svg::render_svg_html::<DefaultExportFeature>(paged_doc()?)
|
ExportSvgHtml(ExportHtmlTask { export: _ }) => Bytes::from_string(
|
||||||
.into_bytes()
|
reflexo_vec2svg::render_svg_html::<DefaultExportFeature>(paged_doc()?),
|
||||||
}
|
),
|
||||||
ExportText(ExportTextTask { export: _ }) => {
|
ExportText(ExportTextTask { export: _ }) => {
|
||||||
TextExport::run_on_doc(doc)?.into_bytes()
|
Bytes::from_string(TextExport::run_on_doc(doc)?)
|
||||||
}
|
}
|
||||||
ExportMd(ExportMarkdownTask { export: _ }) => {
|
ExportMd(ExportMarkdownTask { export: _ }) => {
|
||||||
let conv = Typlite::new(Arc::new(graph.world().clone()))
|
let conv = Typlite::new(Arc::new(graph.world().clone()))
|
||||||
.convert()
|
.convert()
|
||||||
.map_err(|e| anyhow::anyhow!("failed to convert to markdown: {e}"))?;
|
.map_err(|e| anyhow::anyhow!("failed to convert to markdown: {e}"))?;
|
||||||
|
|
||||||
conv.as_bytes().to_owned()
|
Bytes::from_string(conv)
|
||||||
}
|
}
|
||||||
ExportSvg(ExportSvgTask { export }) => {
|
ExportSvg(ExportSvgTask { export }) => {
|
||||||
let (is_first, merged_gap) = get_page_selection(&export)?;
|
let (is_first, merged_gap) = get_page_selection(&export)?;
|
||||||
|
|
||||||
if is_first {
|
Bytes::from_string(if is_first {
|
||||||
typst_svg::svg(first_page()?).into_bytes()
|
typst_svg::svg(first_page()?)
|
||||||
} else {
|
} else {
|
||||||
typst_svg::svg_merged(paged_doc()?, merged_gap).into_bytes()
|
typst_svg::svg_merged(paged_doc()?, merged_gap)
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
ExportPng(ExportPngTask { export, ppi, fill }) => {
|
ExportPng(ExportPngTask { export, ppi, fill }) => {
|
||||||
let ppi = ppi.to_f32();
|
let ppi = ppi.to_f32();
|
||||||
|
|
@ -323,9 +302,11 @@ impl ExportTask {
|
||||||
typst_render::render_merged(paged_doc()?, ppi / 72., merged_gap, Some(fill))
|
typst_render::render_merged(paged_doc()?, ppi / 72., merged_gap, Some(fill))
|
||||||
};
|
};
|
||||||
|
|
||||||
pixmap
|
Bytes::new(
|
||||||
.encode_png()
|
pixmap
|
||||||
.map_err(|err| anyhow::anyhow!("failed to encode PNG ({err})"))?
|
.encode_png()
|
||||||
|
.map_err(|err| anyhow::anyhow!("failed to encode PNG ({err})"))?,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
@ -510,4 +491,70 @@ mod tests {
|
||||||
|
|
||||||
assert!(needs_run);
|
assert!(needs_run);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
use chrono::{DateTime, Utc};
|
||||||
|
use tinymist_std::time::*;
|
||||||
|
|
||||||
|
/// Parses a UNIX timestamp according to <https://reproducible-builds.org/specs/source-date-epoch/>
|
||||||
|
pub fn convert_source_date_epoch(seconds: i64) -> Result<DateTime<Utc>, String> {
|
||||||
|
DateTime::from_timestamp(seconds, 0).ok_or_else(|| "timestamp out of range".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parses a UNIX timestamp according to <https://reproducible-builds.org/specs/source-date-epoch/>
|
||||||
|
pub fn convert_system_time(seconds: i64) -> Result<Time, String> {
|
||||||
|
if seconds < 0 {
|
||||||
|
return Err("negative timestamp since unix epoch".to_string());
|
||||||
|
}
|
||||||
|
|
||||||
|
Time::UNIX_EPOCH
|
||||||
|
.checked_add(Duration::new(seconds as u64, 0))
|
||||||
|
.ok_or_else(|| "timestamp out of range".to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_timestamp_chrono() {
|
||||||
|
let timestamp = 1_000_000_000;
|
||||||
|
let date_time = convert_source_date_epoch(timestamp).unwrap();
|
||||||
|
assert_eq!(date_time.timestamp(), timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_timestamp_system() {
|
||||||
|
let timestamp = 1_000_000_000;
|
||||||
|
let date_time = convert_system_time(timestamp).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
date_time
|
||||||
|
.duration_since(Time::UNIX_EPOCH)
|
||||||
|
.unwrap()
|
||||||
|
.as_secs(),
|
||||||
|
timestamp as u64
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
use typst::foundations::Datetime as TypstDatetime;
|
||||||
|
|
||||||
|
fn convert_datetime_chrono(date_time: DateTime<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()?,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_timestamp_pdf() {
|
||||||
|
let timestamp = 1_000_000_000;
|
||||||
|
let date_time = convert_source_date_epoch(timestamp).unwrap();
|
||||||
|
assert_eq!(date_time.timestamp(), timestamp);
|
||||||
|
let chrono_pdf_ts = convert_datetime_chrono(date_time).unwrap();
|
||||||
|
|
||||||
|
let timestamp = 1_000_000_000;
|
||||||
|
let date_time = convert_system_time(timestamp).unwrap();
|
||||||
|
let system_pdf_ts = tinymist_std::time::to_typst_time(date_time.into());
|
||||||
|
assert_eq!(chrono_pdf_ts, system_pdf_ts);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue