Add document metadata

This commit is contained in:
Laurenz Stampfl 2024-12-15 20:41:07 +01:00
parent 17abfb3a76
commit 9a4bd9be25
2 changed files with 104 additions and 8 deletions

View File

@ -1,6 +1,7 @@
use crate::primitive::{PointExt, SizeExt, TransformExt}; use crate::primitive::{PointExt, SizeExt, TransformExt};
use crate::{paint, AbsExt}; use crate::{paint, AbsExt, PdfOptions};
use bytemuck::TransparentWrapper; use bytemuck::TransparentWrapper;
use ecow::EcoString;
use krilla::action::{Action, LinkAction}; use krilla::action::{Action, LinkAction};
use krilla::annotation::{LinkAnnotation, Target}; use krilla::annotation::{LinkAnnotation, Target};
use krilla::destination::XyzDestination; use krilla::destination::XyzDestination;
@ -10,16 +11,18 @@ use krilla::surface::Surface;
use krilla::validation::Validator; use krilla::validation::Validator;
use krilla::version::PdfVersion; use krilla::version::PdfVersion;
use krilla::{PageSettings, SerializeSettings, SvgSettings}; use krilla::{PageSettings, SerializeSettings, SvgSettings};
use std::collections::HashMap; use std::collections::{BTreeMap, HashMap};
use std::fs::metadata;
use std::ops::Range; use std::ops::Range;
use std::sync::Arc; use std::sync::Arc;
use svg2pdf::usvg::Rect; use svg2pdf::usvg::Rect;
use typst_library::diag::{bail, SourceResult}; use typst_library::diag::{bail, SourceResult};
use typst_library::foundations::Datetime;
use typst_library::layout::{ use typst_library::layout::{
Abs, Frame, FrameItem, GroupItem, PagedDocument, Point, Size, Transform, Abs, Frame, FrameItem, GroupItem, PagedDocument, Point, Size, Transform,
}; };
use typst_library::model::Destination; use typst_library::model::Destination;
use typst_library::text::{Font, Glyph, TextItem}; use typst_library::text::{Font, Glyph, Lang, TextItem};
use typst_library::visualize::{ use typst_library::visualize::{
FillRule, Geometry, Image, ImageKind, Paint, Path, PathItem, Shape, FillRule, Geometry, Image, ImageKind, Paint, Path, PathItem, Shape,
}; };
@ -155,18 +158,26 @@ pub struct GlobalContext {
// if it appears in the document multiple times. We just store the // if it appears in the document multiple times. We just store the
// first appearance, though. // first appearance, though.
image_spans: HashMap<krilla::image::Image, Span>, image_spans: HashMap<krilla::image::Image, Span>,
languages: BTreeMap<Lang, usize>,
} }
impl GlobalContext { impl GlobalContext {
pub fn new() -> Self { pub fn new() -> Self {
Self { fonts: HashMap::new(), image_spans: HashMap::new() } Self {
fonts: HashMap::new(),
image_spans: HashMap::new(),
languages: BTreeMap::new(),
}
} }
} }
// TODO: Change rustybuzz cluster behavior so it works with ActualText // TODO: Change rustybuzz cluster behavior so it works with ActualText
#[typst_macros::time(name = "write pdf")] #[typst_macros::time(name = "write pdf")]
pub fn pdf(typst_document: &PagedDocument) -> SourceResult<Vec<u8>> { pub fn pdf(
typst_document: &PagedDocument,
options: &PdfOptions,
) -> SourceResult<Vec<u8>> {
let settings = SerializeSettings { let settings = SerializeSettings {
compress_content_streams: true, compress_content_streams: true,
no_device_cs: true, no_device_cs: true,
@ -179,7 +190,7 @@ pub fn pdf(typst_document: &PagedDocument) -> SourceResult<Vec<u8>> {
}; };
let mut document = krilla::Document::new_with(settings); let mut document = krilla::Document::new_with(settings);
let mut context = GlobalContext::new(); let mut gc = GlobalContext::new();
for typst_page in &typst_document.pages { for typst_page in &typst_document.pages {
let settings = PageSettings::new( let settings = PageSettings::new(
@ -195,7 +206,7 @@ pub fn pdf(typst_document: &PagedDocument) -> SourceResult<Vec<u8>> {
&typst_page.frame, &typst_page.frame,
typst_page.fill_or_transparent(), typst_page.fill_or_transparent(),
&mut surface, &mut surface,
&mut context, &mut gc,
)?; )?;
surface.finish(); surface.finish();
@ -204,9 +215,91 @@ pub fn pdf(typst_document: &PagedDocument) -> SourceResult<Vec<u8>> {
} }
} }
let metadata = {
let creator = format!("Typst {}", env!("CARGO_PKG_VERSION"));
let mut metadata = krilla::metadata::Metadata::new()
.creator(creator)
.keywords(
typst_document
.info
.keywords
.iter()
.map(EcoString::to_string)
.collect(),
)
.authors(
typst_document.info.author.iter().map(EcoString::to_string).collect(),
);
let lang = gc.languages.iter().max_by_key(|(_, &count)| count).map(|(&l, _)| l);
if let Some(lang) = lang {
metadata = metadata.language(lang.as_str().to_string());
}
if let Some(title) = &typst_document.info.title {
metadata = metadata.title(title.to_string());
}
if let Some(subject) = &typst_document.info.description {
metadata = metadata.subject(subject.to_string());
}
if let Some(ident) = options.ident.custom() {
metadata = metadata.subject(ident.to_string());
}
let tz = typst_document.info.date.is_auto();
if let Some(date) = typst_document
.info
.date
.unwrap_or(options.timestamp)
.and_then(|d| krilla_date(d, tz))
{
metadata = metadata.modification_date(date).creation_date(date);
}
metadata
};
document.set_metadata(metadata);
Ok(document.finish().unwrap()) Ok(document.finish().unwrap())
} }
fn krilla_date(datetime: Datetime, tz: bool) -> Option<krilla::metadata::DateTime> {
let year = datetime.year().filter(|&y| y >= 0)? as u16;
let mut krilla_date = krilla::metadata::DateTime::new(year);
if let Some(month) = datetime.month() {
krilla_date = krilla_date.month(month);
}
if let Some(day) = datetime.day() {
krilla_date = krilla_date.day(day);
}
if let Some(h) = datetime.hour() {
krilla_date = krilla_date.hour(h);
}
if let Some(m) = datetime.minute() {
krilla_date = krilla_date.minute(m);
}
if let Some(s) = datetime.second() {
krilla_date = krilla_date.second(s);
}
if tz {
krilla_date = krilla_date.utc_offset_hour(0).utc_offset_minute(0);
}
Some(krilla_date)
}
pub fn process_frame( pub fn process_frame(
fc: &mut FrameContext, fc: &mut FrameContext,
frame: &Frame, frame: &Frame,
@ -344,6 +437,9 @@ pub fn handle_text(
.unwrap() .unwrap()
}) })
.clone(); .clone();
*gc.languages.entry(t.lang).or_insert(0) += t.glyphs.len();
let fill = paint::fill( let fill = paint::fill(
gc, gc,
&t.fill, &t.fill,

View File

@ -53,7 +53,7 @@ use crate::resources_old::{
/// Returns the raw bytes making up the PDF file. /// Returns the raw bytes making up the PDF file.
#[typst_macros::time(name = "pdf")] #[typst_macros::time(name = "pdf")]
pub fn pdf(document: &PagedDocument, options: &PdfOptions) -> SourceResult<Vec<u8>> { pub fn pdf(document: &PagedDocument, options: &PdfOptions) -> SourceResult<Vec<u8>> {
krilla::pdf(document) krilla::pdf(document, options)
// PdfBuilder::new(document, options) // PdfBuilder::new(document, options)
// .phase(|builder| builder.run(traverse_pages))? // .phase(|builder| builder.run(traverse_pages))?
// .phase(|builder| { // .phase(|builder| {