mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Extract typst-pdf
crate
This commit is contained in:
parent
80b4ca4c04
commit
46846a337e
35
Cargo.lock
generated
35
Cargo.lock
generated
@ -2892,22 +2892,17 @@ checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
|
|||||||
name = "typst"
|
name = "typst"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"base64",
|
|
||||||
"bitflags 2.4.1",
|
"bitflags 2.4.1",
|
||||||
"bytemuck",
|
|
||||||
"comemo",
|
"comemo",
|
||||||
"ecow",
|
"ecow",
|
||||||
"flate2",
|
|
||||||
"fontdb",
|
"fontdb",
|
||||||
"image",
|
"image",
|
||||||
"indexmap 2.0.2",
|
"indexmap 2.0.2",
|
||||||
"kurbo",
|
"kurbo",
|
||||||
"lasso",
|
"lasso",
|
||||||
"log",
|
"log",
|
||||||
"miniz_oxide",
|
|
||||||
"once_cell",
|
"once_cell",
|
||||||
"palette",
|
"palette",
|
||||||
"pdf-writer",
|
|
||||||
"regex",
|
"regex",
|
||||||
"roxmltree",
|
"roxmltree",
|
||||||
"rustybuzz",
|
"rustybuzz",
|
||||||
@ -2915,22 +2910,16 @@ dependencies = [
|
|||||||
"siphasher",
|
"siphasher",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
"stacker",
|
"stacker",
|
||||||
"subsetter",
|
|
||||||
"svg2pdf",
|
|
||||||
"time",
|
"time",
|
||||||
"toml",
|
"toml",
|
||||||
"tracing",
|
"tracing",
|
||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
"typst-macros",
|
"typst-macros",
|
||||||
"typst-syntax",
|
"typst-syntax",
|
||||||
"unicode-ident",
|
|
||||||
"unicode-math-class",
|
"unicode-math-class",
|
||||||
"unicode-properties",
|
|
||||||
"unicode-segmentation",
|
"unicode-segmentation",
|
||||||
"unscanny",
|
|
||||||
"usvg",
|
"usvg",
|
||||||
"wasmi",
|
"wasmi",
|
||||||
"xmp-writer",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -2971,6 +2960,7 @@ dependencies = [
|
|||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
"typst",
|
"typst",
|
||||||
"typst-library",
|
"typst-library",
|
||||||
|
"typst-pdf",
|
||||||
"typst-render",
|
"typst-render",
|
||||||
"typst-svg",
|
"typst-svg",
|
||||||
"ureq",
|
"ureq",
|
||||||
@ -3062,6 +3052,28 @@ dependencies = [
|
|||||||
"syn 2.0.38",
|
"syn 2.0.38",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "typst-pdf"
|
||||||
|
version = "0.9.0"
|
||||||
|
dependencies = [
|
||||||
|
"base64",
|
||||||
|
"bytemuck",
|
||||||
|
"comemo",
|
||||||
|
"ecow",
|
||||||
|
"image",
|
||||||
|
"miniz_oxide",
|
||||||
|
"once_cell",
|
||||||
|
"pdf-writer",
|
||||||
|
"subsetter",
|
||||||
|
"svg2pdf",
|
||||||
|
"tracing",
|
||||||
|
"ttf-parser",
|
||||||
|
"typst",
|
||||||
|
"unicode-properties",
|
||||||
|
"unscanny",
|
||||||
|
"xmp-writer",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "typst-render"
|
name = "typst-render"
|
||||||
version = "0.9.0"
|
version = "0.9.0"
|
||||||
@ -3124,6 +3136,7 @@ dependencies = [
|
|||||||
"ttf-parser",
|
"ttf-parser",
|
||||||
"typst",
|
"typst",
|
||||||
"typst-library",
|
"typst-library",
|
||||||
|
"typst-pdf",
|
||||||
"typst-render",
|
"typst-render",
|
||||||
"typst-svg",
|
"typst-svg",
|
||||||
"unscanny",
|
"unscanny",
|
||||||
|
@ -17,8 +17,12 @@ keywords = ["typst"]
|
|||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
typst = { path = "crates/typst" }
|
typst = { path = "crates/typst" }
|
||||||
|
typst-cli = { path = "crates/typst-cli" }
|
||||||
|
typst-docs = { path = "crates/typst-docs" }
|
||||||
|
typst-ide = { path = "crates/typst-ide" }
|
||||||
typst-library = { path = "crates/typst-library" }
|
typst-library = { path = "crates/typst-library" }
|
||||||
typst-macros = { path = "crates/typst-macros" }
|
typst-macros = { path = "crates/typst-macros" }
|
||||||
|
typst-pdf = { path = "crates/typst-pdf" }
|
||||||
typst-render = { path = "crates/typst-render" }
|
typst-render = { path = "crates/typst-render" }
|
||||||
typst-svg = { path = "crates/typst-svg" }
|
typst-svg = { path = "crates/typst-svg" }
|
||||||
typst-syntax = { path = "crates/typst-syntax" }
|
typst-syntax = { path = "crates/typst-syntax" }
|
||||||
@ -86,6 +90,7 @@ serde_json = "1"
|
|||||||
serde_yaml = "0.9"
|
serde_yaml = "0.9"
|
||||||
siphasher = "0.3"
|
siphasher = "0.3"
|
||||||
smallvec = { version = "1.11.1", features = ["union", "const_generics", "const_new"] }
|
smallvec = { version = "1.11.1", features = ["union", "const_generics", "const_new"] }
|
||||||
|
stacker = "0.1.15"
|
||||||
subsetter = "0.1.1"
|
subsetter = "0.1.1"
|
||||||
svg2pdf = "0.9"
|
svg2pdf = "0.9"
|
||||||
syn = { version = "2", features = ["full", "extra-traits"] }
|
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||||
|
@ -22,6 +22,7 @@ doc = false
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
typst = { workspace = true }
|
typst = { workspace = true }
|
||||||
typst-library = { workspace = true }
|
typst-library = { workspace = true }
|
||||||
|
typst-pdf = { workspace = true }
|
||||||
typst-render = { workspace = true }
|
typst-render = { workspace = true }
|
||||||
typst-svg = { workspace = true }
|
typst-svg = { workspace = true }
|
||||||
chrono = { workspace = true }
|
chrono = { workspace = true }
|
||||||
|
@ -153,7 +153,7 @@ fn export_pdf(
|
|||||||
world: &SystemWorld,
|
world: &SystemWorld,
|
||||||
) -> StrResult<()> {
|
) -> StrResult<()> {
|
||||||
let ident = world.input().to_string_lossy();
|
let ident = world.input().to_string_lossy();
|
||||||
let buffer = typst::export::pdf(document, Some(&ident), now());
|
let buffer = typst_pdf::pdf(document, Some(&ident), now());
|
||||||
let output = command.output();
|
let output = command.output();
|
||||||
fs::write(output, buffer)
|
fs::write(output, buffer)
|
||||||
.map_err(|err| eco_format!("failed to write PDF file ({err})"))?;
|
.map_err(|err| eco_format!("failed to write PDF file ({err})"))?;
|
||||||
|
@ -2,7 +2,7 @@ use std::str::FromStr;
|
|||||||
|
|
||||||
use chinese_number::{ChineseCase, ChineseCountMethod, ChineseVariant, NumberToChinese};
|
use chinese_number::{ChineseCase, ChineseCountMethod, ChineseVariant, NumberToChinese};
|
||||||
use ecow::EcoVec;
|
use ecow::EcoVec;
|
||||||
use typst::export::{PdfPageLabel, PdfPageLabelStyle};
|
use typst::doc::{PdfPageLabel, PdfPageLabelStyle};
|
||||||
|
|
||||||
use crate::prelude::*;
|
use crate::prelude::*;
|
||||||
use crate::text::Case;
|
use crate::text::Case;
|
||||||
|
34
crates/typst-pdf/Cargo.toml
Normal file
34
crates/typst-pdf/Cargo.toml
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
[package]
|
||||||
|
name = "typst-pdf"
|
||||||
|
description = "PDF exporter for Typst."
|
||||||
|
version.workspace = true
|
||||||
|
rust-version.workspace = true
|
||||||
|
authors.workspace = true
|
||||||
|
edition.workspace = true
|
||||||
|
homepage.workspace = true
|
||||||
|
repository.workspace = true
|
||||||
|
license.workspace = true
|
||||||
|
categories.workspace = true
|
||||||
|
keywords.workspace = true
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
doctest = false
|
||||||
|
bench = false
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
typst = { workspace = true }
|
||||||
|
base64 = { workspace = true }
|
||||||
|
bytemuck = { workspace = true }
|
||||||
|
comemo = { workspace = true }
|
||||||
|
ecow = { workspace = true}
|
||||||
|
image = { workspace = true }
|
||||||
|
miniz_oxide = { workspace = true }
|
||||||
|
once_cell = { workspace = true }
|
||||||
|
pdf-writer = { workspace = true }
|
||||||
|
subsetter = { workspace = true }
|
||||||
|
svg2pdf = { workspace = true }
|
||||||
|
tracing = { workspace = true }
|
||||||
|
ttf-parser = { workspace = true }
|
||||||
|
unicode-properties = { workspace = true }
|
||||||
|
unscanny = { workspace = true }
|
||||||
|
xmp-writer = { workspace = true }
|
@ -1,10 +1,10 @@
|
|||||||
use once_cell::sync::Lazy;
|
use once_cell::sync::Lazy;
|
||||||
use pdf_writer::types::DeviceNSubtype;
|
use pdf_writer::types::DeviceNSubtype;
|
||||||
use pdf_writer::{writers, Chunk, Dict, Filter, Name, Ref};
|
use pdf_writer::{writers, Chunk, Dict, Filter, Name, Ref};
|
||||||
|
use typst::geom::{Color, ColorSpace, Paint};
|
||||||
|
|
||||||
use super::page::{PageContext, Transforms};
|
use crate::deflate;
|
||||||
use crate::export::pdf::deflate;
|
use crate::page::{PageContext, Transforms};
|
||||||
use crate::geom::{Color, ColorSpace, Paint};
|
|
||||||
|
|
||||||
// The names of the color spaces.
|
// The names of the color spaces.
|
||||||
pub const SRGB: Name<'static> = Name(b"srgb");
|
pub const SRGB: Name<'static> = Name(b"srgb");
|
@ -5,11 +5,11 @@ use ecow::{eco_format, EcoString};
|
|||||||
use pdf_writer::types::{CidFontType, FontFlags, SystemInfo, UnicodeCmap};
|
use pdf_writer::types::{CidFontType, FontFlags, SystemInfo, UnicodeCmap};
|
||||||
use pdf_writer::{Filter, Finish, Name, Rect, Str};
|
use pdf_writer::{Filter, Finish, Name, Rect, Str};
|
||||||
use ttf_parser::{name_id, GlyphId, Tag};
|
use ttf_parser::{name_id, GlyphId, Tag};
|
||||||
|
use typst::font::Font;
|
||||||
|
use typst::util::SliceExt;
|
||||||
use unicode_properties::{GeneralCategory, UnicodeGeneralCategory};
|
use unicode_properties::{GeneralCategory, UnicodeGeneralCategory};
|
||||||
|
|
||||||
use super::{deflate, EmExt, PdfContext};
|
use crate::{deflate, EmExt, PdfContext};
|
||||||
use crate::font::Font;
|
|
||||||
use crate::util::SliceExt;
|
|
||||||
|
|
||||||
const CFF: Tag = Tag::from_bytes(b"CFF ");
|
const CFF: Tag = Tag::from_bytes(b"CFF ");
|
||||||
const CFF2: Tag = Tag::from_bytes(b"CFF2");
|
const CFF2: Tag = Tag::from_bytes(b"CFF2");
|
||||||
@ -187,7 +187,7 @@ fn subset_font(font: &Font, glyphs: &[u16]) -> Arc<Vec<u8>> {
|
|||||||
fn subset_tag(glyphs: &BTreeMap<u16, EcoString>) -> EcoString {
|
fn subset_tag(glyphs: &BTreeMap<u16, EcoString>) -> EcoString {
|
||||||
const LEN: usize = 6;
|
const LEN: usize = 6;
|
||||||
const BASE: u128 = 26;
|
const BASE: u128 = 26;
|
||||||
let mut hash = crate::util::hash128(&glyphs);
|
let mut hash = typst::util::hash128(&glyphs);
|
||||||
let mut letter = [b'A'; LEN];
|
let mut letter = [b'A'; LEN];
|
||||||
for l in letter.iter_mut() {
|
for l in letter.iter_mut() {
|
||||||
*l = b'A' + (hash % BASE) as u8;
|
*l = b'A' + (hash % BASE) as u8;
|
@ -6,16 +6,15 @@ use pdf_writer::types::FunctionShadingType;
|
|||||||
use pdf_writer::writers::StreamShadingType;
|
use pdf_writer::writers::StreamShadingType;
|
||||||
use pdf_writer::{types::ColorSpaceOperand, Name};
|
use pdf_writer::{types::ColorSpaceOperand, Name};
|
||||||
use pdf_writer::{Filter, Finish, Ref};
|
use pdf_writer::{Filter, Finish, Ref};
|
||||||
|
use typst::geom::{
|
||||||
use super::color::{ColorSpaceExt, PaintEncode, QuantizedColor};
|
|
||||||
use super::page::{PageContext, Transforms};
|
|
||||||
use super::{AbsExt, PdfContext};
|
|
||||||
use crate::export::pdf::deflate;
|
|
||||||
use crate::geom::{
|
|
||||||
Abs, Angle, Color, ColorSpace, ConicGradient, Gradient, Numeric, Point, Quadrant,
|
Abs, Angle, Color, ColorSpace, ConicGradient, Gradient, Numeric, Point, Quadrant,
|
||||||
Ratio, Relative, Transform, WeightedColor,
|
Ratio, Relative, Transform, WeightedColor,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
use crate::color::{ColorSpaceExt, PaintEncode, QuantizedColor};
|
||||||
|
use crate::page::{PageContext, Transforms};
|
||||||
|
use crate::{deflate, AbsExt, PdfContext};
|
||||||
|
|
||||||
/// A unique-transform-aspect-ratio combination that will be encoded into the
|
/// A unique-transform-aspect-ratio combination that will be encoded into the
|
||||||
/// PDF.
|
/// PDF.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
@ -4,10 +4,10 @@ use std::sync::Arc;
|
|||||||
|
|
||||||
use image::{DynamicImage, GenericImageView, Rgba};
|
use image::{DynamicImage, GenericImageView, Rgba};
|
||||||
use pdf_writer::{Chunk, Filter, Finish, Ref};
|
use pdf_writer::{Chunk, Filter, Finish, Ref};
|
||||||
|
use typst::geom::ColorSpace;
|
||||||
|
use typst::image::{ImageKind, RasterFormat, RasterImage, SvgImage};
|
||||||
|
|
||||||
use super::{deflate, PdfContext};
|
use crate::{deflate, PdfContext};
|
||||||
use crate::geom::ColorSpace;
|
|
||||||
use crate::image::{ImageKind, RasterFormat, RasterImage, SvgImage};
|
|
||||||
|
|
||||||
/// Embed all used images into the PDF.
|
/// Embed all used images into the PDF.
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
@ -8,31 +8,26 @@ mod image;
|
|||||||
mod outline;
|
mod outline;
|
||||||
mod page;
|
mod page;
|
||||||
|
|
||||||
pub use self::color::{ColorEncode, ColorSpaces};
|
|
||||||
pub use self::page::{PdfPageLabel, PdfPageLabelStyle};
|
|
||||||
|
|
||||||
use std::cmp::Eq;
|
use std::cmp::Eq;
|
||||||
use std::collections::{BTreeMap, HashMap};
|
use std::collections::{BTreeMap, HashMap};
|
||||||
use std::hash::Hash;
|
use std::hash::Hash;
|
||||||
use std::num::NonZeroUsize;
|
|
||||||
|
|
||||||
use base64::Engine;
|
use base64::Engine;
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
use pdf_writer::types::Direction;
|
use pdf_writer::types::Direction;
|
||||||
use pdf_writer::writers::PageLabel;
|
|
||||||
use pdf_writer::{Finish, Name, Pdf, Ref, TextStr};
|
use pdf_writer::{Finish, Name, Pdf, Ref, TextStr};
|
||||||
|
use typst::doc::{Document, Lang};
|
||||||
|
use typst::eval::Datetime;
|
||||||
|
use typst::font::Font;
|
||||||
|
use typst::geom::{Abs, Dir, Em};
|
||||||
|
use typst::image::Image;
|
||||||
|
use typst::model::Introspector;
|
||||||
use xmp_writer::{DateTime, LangId, RenditionClass, Timezone, XmpWriter};
|
use xmp_writer::{DateTime, LangId, RenditionClass, Timezone, XmpWriter};
|
||||||
|
|
||||||
use self::gradient::PdfGradient;
|
use crate::color::ColorSpaces;
|
||||||
use self::page::Page;
|
use crate::extg::ExtGState;
|
||||||
use crate::doc::{Document, Lang};
|
use crate::gradient::PdfGradient;
|
||||||
use crate::eval::Datetime;
|
use crate::page::Page;
|
||||||
use crate::font::Font;
|
|
||||||
use crate::geom::{Abs, Dir, Em};
|
|
||||||
use crate::image::Image;
|
|
||||||
use crate::model::Introspector;
|
|
||||||
|
|
||||||
use extg::ExtGState;
|
|
||||||
|
|
||||||
/// Export a document into a PDF file.
|
/// Export a document into a PDF file.
|
||||||
///
|
///
|
||||||
@ -161,7 +156,7 @@ fn write_catalog(ctx: &mut PdfContext, ident: Option<&str>, timestamp: Option<Da
|
|||||||
let outline_root_id = outline::write_outline(ctx);
|
let outline_root_id = outline::write_outline(ctx);
|
||||||
|
|
||||||
// Write the page labels.
|
// Write the page labels.
|
||||||
let page_labels = write_page_labels(ctx);
|
let page_labels = page::write_page_labels(ctx);
|
||||||
|
|
||||||
// Write the document information.
|
// Write the document information.
|
||||||
let mut info = ctx.pdf.document_info(ctx.alloc.bump());
|
let mut info = ctx.pdf.document_info(ctx.alloc.bump());
|
||||||
@ -256,55 +251,6 @@ fn write_catalog(ctx: &mut PdfContext, ident: Option<&str>, timestamp: Option<Da
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Write the page labels.
|
|
||||||
#[tracing::instrument(skip_all)]
|
|
||||||
fn write_page_labels(ctx: &mut PdfContext) -> Vec<(NonZeroUsize, Ref)> {
|
|
||||||
let mut result = vec![];
|
|
||||||
let mut prev: Option<&PdfPageLabel> = None;
|
|
||||||
|
|
||||||
for (i, page) in ctx.pages.iter().enumerate() {
|
|
||||||
let nr = NonZeroUsize::new(1 + i).unwrap();
|
|
||||||
let Some(label) = &page.label else { continue };
|
|
||||||
|
|
||||||
// Don't create a label if neither style nor prefix are specified.
|
|
||||||
if label.prefix.is_none() && label.style.is_none() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(pre) = prev {
|
|
||||||
if label.prefix == pre.prefix
|
|
||||||
&& label.style == pre.style
|
|
||||||
&& label.offset == pre.offset.map(|n| n.saturating_add(1))
|
|
||||||
{
|
|
||||||
prev = Some(label);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let id = ctx.alloc.bump();
|
|
||||||
let mut entry = ctx.pdf.indirect(id).start::<PageLabel>();
|
|
||||||
|
|
||||||
// Only add what is actually provided. Don't add empty prefix string if
|
|
||||||
// it wasn't given for example.
|
|
||||||
if let Some(prefix) = &label.prefix {
|
|
||||||
entry.prefix(TextStr(prefix));
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(style) = label.style {
|
|
||||||
entry.style(style.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(offset) = label.offset {
|
|
||||||
entry.offset(offset.get() as i32);
|
|
||||||
}
|
|
||||||
|
|
||||||
result.push((nr, id));
|
|
||||||
prev = Some(label);
|
|
||||||
}
|
|
||||||
|
|
||||||
result
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Compress data with the DEFLATE algorithm.
|
/// Compress data with the DEFLATE algorithm.
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
fn deflate(data: &[u8]) -> Vec<u8> {
|
fn deflate(data: &[u8]) -> Vec<u8> {
|
||||||
@ -315,7 +261,7 @@ fn deflate(data: &[u8]) -> Vec<u8> {
|
|||||||
/// Create a base64-encoded hash of the value.
|
/// Create a base64-encoded hash of the value.
|
||||||
fn hash_base64<T: Hash>(value: &T) -> String {
|
fn hash_base64<T: Hash>(value: &T) -> String {
|
||||||
base64::engine::general_purpose::STANDARD
|
base64::engine::general_purpose::STANDARD
|
||||||
.encode(crate::util::hash128(value).to_be_bytes())
|
.encode(typst::util::hash128(value).to_be_bytes())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts a datetime to a pdf-writer date.
|
/// Converts a datetime to a pdf-writer date.
|
@ -1,10 +1,11 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
|
|
||||||
use pdf_writer::{Finish, Ref, TextStr};
|
use pdf_writer::{Finish, Ref, TextStr};
|
||||||
|
use typst::eval::item;
|
||||||
|
use typst::geom::{Abs, Smart};
|
||||||
|
use typst::model::Content;
|
||||||
|
|
||||||
use super::{AbsExt, PdfContext};
|
use crate::{AbsExt, PdfContext};
|
||||||
use crate::geom::{Abs, Smart};
|
|
||||||
use crate::model::Content;
|
|
||||||
|
|
||||||
/// Construct the outline for the document.
|
/// Construct the outline for the document.
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
@ -1,24 +1,27 @@
|
|||||||
use std::num::NonZeroUsize;
|
use std::num::NonZeroUsize;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use ecow::{eco_format, EcoString};
|
use ecow::eco_format;
|
||||||
use pdf_writer::types::{
|
use pdf_writer::types::{
|
||||||
ActionType, AnnotationType, ColorSpaceOperand, LineCapStyle, LineJoinStyle,
|
ActionType, AnnotationType, ColorSpaceOperand, LineCapStyle, LineJoinStyle,
|
||||||
NumberingStyle,
|
NumberingStyle,
|
||||||
};
|
};
|
||||||
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str};
|
use pdf_writer::writers::PageLabel;
|
||||||
|
use pdf_writer::{Content, Filter, Finish, Name, Rect, Ref, Str, TextStr};
|
||||||
use super::color::PaintEncode;
|
use typst::doc::{
|
||||||
use super::extg::ExtGState;
|
Destination, Frame, FrameItem, GroupItem, Meta, PdfPageLabel, PdfPageLabelStyle,
|
||||||
use super::{deflate, AbsExt, EmExt, PdfContext};
|
TextItem,
|
||||||
use crate::doc::{Destination, Frame, FrameItem, GroupItem, Meta, TextItem};
|
};
|
||||||
use crate::eval::Repr;
|
use typst::font::Font;
|
||||||
use crate::font::Font;
|
use typst::geom::{
|
||||||
use crate::geom::{
|
|
||||||
self, Abs, Em, FixedStroke, Geometry, LineCap, LineJoin, Numeric, Paint, Point,
|
self, Abs, Em, FixedStroke, Geometry, LineCap, LineJoin, Numeric, Paint, Point,
|
||||||
Ratio, Shape, Size, Transform,
|
Ratio, Shape, Size, Transform,
|
||||||
};
|
};
|
||||||
use crate::image::Image;
|
use typst::image::Image;
|
||||||
|
|
||||||
|
use crate::color::PaintEncode;
|
||||||
|
use crate::extg::ExtGState;
|
||||||
|
use crate::{deflate, AbsExt, EmExt, PdfContext};
|
||||||
|
|
||||||
/// Construct page objects.
|
/// Construct page objects.
|
||||||
#[tracing::instrument(skip_all)]
|
#[tracing::instrument(skip_all)]
|
||||||
@ -189,6 +192,55 @@ fn write_page(ctx: &mut PdfContext, i: usize) {
|
|||||||
ctx.pdf.stream(content_id, &data).filter(Filter::FlateDecode);
|
ctx.pdf.stream(content_id, &data).filter(Filter::FlateDecode);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Write the page labels.
|
||||||
|
#[tracing::instrument(skip_all)]
|
||||||
|
pub fn write_page_labels(ctx: &mut PdfContext) -> Vec<(NonZeroUsize, Ref)> {
|
||||||
|
let mut result = vec![];
|
||||||
|
let mut prev: Option<&PdfPageLabel> = None;
|
||||||
|
|
||||||
|
for (i, page) in ctx.pages.iter().enumerate() {
|
||||||
|
let nr = NonZeroUsize::new(1 + i).unwrap();
|
||||||
|
let Some(label) = &page.label else { continue };
|
||||||
|
|
||||||
|
// Don't create a label if neither style nor prefix are specified.
|
||||||
|
if label.prefix.is_none() && label.style.is_none() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(pre) = prev {
|
||||||
|
if label.prefix == pre.prefix
|
||||||
|
&& label.style == pre.style
|
||||||
|
&& label.offset == pre.offset.map(|n| n.saturating_add(1))
|
||||||
|
{
|
||||||
|
prev = Some(label);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let id = ctx.alloc.bump();
|
||||||
|
let mut entry = ctx.pdf.indirect(id).start::<PageLabel>();
|
||||||
|
|
||||||
|
// Only add what is actually provided. Don't add empty prefix string if
|
||||||
|
// it wasn't given for example.
|
||||||
|
if let Some(prefix) = &label.prefix {
|
||||||
|
entry.prefix(TextStr(prefix));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(style) = label.style {
|
||||||
|
entry.style(to_pdf_numbering_style(style));
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(offset) = label.offset {
|
||||||
|
entry.offset(offset.get() as i32);
|
||||||
|
}
|
||||||
|
|
||||||
|
result.push((nr, id));
|
||||||
|
prev = Some(label);
|
||||||
|
}
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
||||||
/// Memoized version of [`deflate`] specialized for a page's content stream.
|
/// Memoized version of [`deflate`] specialized for a page's content stream.
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
fn deflate_content(content: &[u8]) -> Arc<Vec<u8>> {
|
fn deflate_content(content: &[u8]) -> Arc<Vec<u8>> {
|
||||||
@ -401,10 +453,10 @@ impl PageContext<'_, '_> {
|
|||||||
|
|
||||||
self.content.set_line_width(thickness.to_f32());
|
self.content.set_line_width(thickness.to_f32());
|
||||||
if self.state.stroke.as_ref().map(|s| &s.line_cap) != Some(line_cap) {
|
if self.state.stroke.as_ref().map(|s| &s.line_cap) != Some(line_cap) {
|
||||||
self.content.set_line_cap(line_cap.into());
|
self.content.set_line_cap(to_pdf_line_cap(*line_cap));
|
||||||
}
|
}
|
||||||
if self.state.stroke.as_ref().map(|s| &s.line_join) != Some(line_join) {
|
if self.state.stroke.as_ref().map(|s| &s.line_join) != Some(line_join) {
|
||||||
self.content.set_line_join(line_join.into());
|
self.content.set_line_join(to_pdf_line_join(*line_join));
|
||||||
}
|
}
|
||||||
if self.state.stroke.as_ref().map(|s| &s.dash_pattern) != Some(dash_pattern) {
|
if self.state.stroke.as_ref().map(|s| &s.dash_pattern) != Some(dash_pattern) {
|
||||||
if let Some(pattern) = dash_pattern {
|
if let Some(pattern) = dash_pattern {
|
||||||
@ -680,73 +732,28 @@ fn write_link(ctx: &mut PageContext, pos: Point, dest: &Destination, size: Size)
|
|||||||
ctx.links.push((dest.clone(), rect));
|
ctx.links.push((dest.clone(), rect));
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&LineCap> for LineCapStyle {
|
fn to_pdf_line_cap(cap: LineCap) -> LineCapStyle {
|
||||||
fn from(line_cap: &LineCap) -> Self {
|
match cap {
|
||||||
match line_cap {
|
|
||||||
LineCap::Butt => LineCapStyle::ButtCap,
|
LineCap::Butt => LineCapStyle::ButtCap,
|
||||||
LineCap::Round => LineCapStyle::RoundCap,
|
LineCap::Round => LineCapStyle::RoundCap,
|
||||||
LineCap::Square => LineCapStyle::ProjectingSquareCap,
|
LineCap::Square => LineCapStyle::ProjectingSquareCap,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&LineJoin> for LineJoinStyle {
|
fn to_pdf_line_join(join: LineJoin) -> LineJoinStyle {
|
||||||
fn from(line_join: &LineJoin) -> Self {
|
match join {
|
||||||
match line_join {
|
|
||||||
LineJoin::Miter => LineJoinStyle::MiterJoin,
|
LineJoin::Miter => LineJoinStyle::MiterJoin,
|
||||||
LineJoin::Round => LineJoinStyle::RoundJoin,
|
LineJoin::Round => LineJoinStyle::RoundJoin,
|
||||||
LineJoin::Bevel => LineJoinStyle::BevelJoin,
|
LineJoin::Bevel => LineJoinStyle::BevelJoin,
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Specification for a PDF page label.
|
fn to_pdf_numbering_style(style: PdfPageLabelStyle) -> NumberingStyle {
|
||||||
#[derive(Debug, Clone, PartialEq, Hash, Default)]
|
match style {
|
||||||
pub struct PdfPageLabel {
|
PdfPageLabelStyle::Arabic => NumberingStyle::Arabic,
|
||||||
/// Can be any string or none. Will always be prepended to the numbering style.
|
PdfPageLabelStyle::LowerRoman => NumberingStyle::LowerRoman,
|
||||||
pub prefix: Option<EcoString>,
|
PdfPageLabelStyle::UpperRoman => NumberingStyle::UpperRoman,
|
||||||
/// Based on the numbering pattern.
|
PdfPageLabelStyle::LowerAlpha => NumberingStyle::LowerAlpha,
|
||||||
///
|
PdfPageLabelStyle::UpperAlpha => NumberingStyle::UpperAlpha,
|
||||||
/// If `None` or numbering is a function, the field will be empty.
|
|
||||||
pub style: Option<PdfPageLabelStyle>,
|
|
||||||
/// Offset for the page label start.
|
|
||||||
///
|
|
||||||
/// Describes where to start counting from when setting a style.
|
|
||||||
/// (Has to be greater or equal than 1)
|
|
||||||
pub offset: Option<NonZeroUsize>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Repr for PdfPageLabel {
|
|
||||||
fn repr(&self) -> EcoString {
|
|
||||||
eco_format!("{self:?}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A PDF page label number style.
|
|
||||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
|
||||||
pub enum PdfPageLabelStyle {
|
|
||||||
/// Decimal arabic numerals (1, 2, 3).
|
|
||||||
Arabic,
|
|
||||||
/// Lowercase roman numerals (i, ii, iii).
|
|
||||||
LowerRoman,
|
|
||||||
/// Uppercase roman numerals (I, II, III).
|
|
||||||
UpperRoman,
|
|
||||||
/// Lowercase letters (`a` to `z` for the first 26 pages,
|
|
||||||
/// `aa` to `zz` and so on for the next).
|
|
||||||
LowerAlpha,
|
|
||||||
/// Uppercase letters (`A` to `Z` for the first 26 pages,
|
|
||||||
/// `AA` to `ZZ` and so on for the next).
|
|
||||||
UpperAlpha,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<PdfPageLabelStyle> for NumberingStyle {
|
|
||||||
fn from(value: PdfPageLabelStyle) -> Self {
|
|
||||||
match value {
|
|
||||||
PdfPageLabelStyle::Arabic => Self::Arabic,
|
|
||||||
PdfPageLabelStyle::LowerRoman => Self::LowerRoman,
|
|
||||||
PdfPageLabelStyle::UpperRoman => Self::UpperRoman,
|
|
||||||
PdfPageLabelStyle::LowerAlpha => Self::LowerAlpha,
|
|
||||||
PdfPageLabelStyle::UpperAlpha => Self::UpperAlpha,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -18,42 +18,31 @@ bench = false
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
typst-macros = { workspace = true }
|
typst-macros = { workspace = true }
|
||||||
typst-syntax = { workspace = true }
|
typst-syntax = { workspace = true }
|
||||||
base64 = { workspace = true }
|
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
bytemuck = { workspace = true }
|
|
||||||
comemo = { workspace = true }
|
comemo = { workspace = true }
|
||||||
ecow = { workspace = true}
|
ecow = { workspace = true}
|
||||||
flate2 = { workspace = true }
|
|
||||||
fontdb = { workspace = true }
|
fontdb = { workspace = true }
|
||||||
image = { workspace = true }
|
image = { workspace = true }
|
||||||
indexmap = { workspace = true }
|
indexmap = { workspace = true }
|
||||||
kurbo = { workspace = true }
|
kurbo = { workspace = true }
|
||||||
lasso = { workspace = true }
|
lasso = { workspace = true }
|
||||||
log = { workspace = true }
|
log = { workspace = true }
|
||||||
miniz_oxide = { workspace = true }
|
|
||||||
once_cell = { workspace = true }
|
once_cell = { workspace = true }
|
||||||
palette = { workspace = true }
|
palette = { workspace = true }
|
||||||
pdf-writer = { workspace = true }
|
|
||||||
regex = { workspace = true }
|
regex = { workspace = true }
|
||||||
roxmltree = { workspace = true }
|
roxmltree = { workspace = true }
|
||||||
rustybuzz = { workspace = true }
|
rustybuzz = { workspace = true }
|
||||||
serde = { workspace = true }
|
serde = { workspace = true }
|
||||||
siphasher = { workspace = true }
|
siphasher = { workspace = true }
|
||||||
smallvec = { workspace = true }
|
smallvec = { workspace = true }
|
||||||
subsetter = { workspace = true }
|
|
||||||
svg2pdf = { workspace = true }
|
|
||||||
time = { workspace = true }
|
time = { workspace = true }
|
||||||
toml = { workspace = true }
|
toml = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
ttf-parser = { workspace = true }
|
ttf-parser = { workspace = true }
|
||||||
unicode-ident = { workspace = true }
|
|
||||||
unicode-math-class = { workspace = true }
|
unicode-math-class = { workspace = true }
|
||||||
unicode-properties = { workspace = true }
|
|
||||||
unicode-segmentation = { workspace = true }
|
unicode-segmentation = { workspace = true }
|
||||||
unscanny = { workspace = true }
|
|
||||||
usvg = { workspace = true }
|
usvg = { workspace = true }
|
||||||
wasmi = { workspace = true }
|
wasmi = { workspace = true }
|
||||||
xmp-writer = { workspace = true }
|
|
||||||
|
|
||||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
stacker = "0.1.15"
|
stacker = { workspace = true }
|
||||||
|
@ -9,7 +9,6 @@ use std::sync::Arc;
|
|||||||
use ecow::{eco_format, EcoString};
|
use ecow::{eco_format, EcoString};
|
||||||
|
|
||||||
use crate::eval::{cast, dict, ty, Datetime, Dict, Repr, Value};
|
use crate::eval::{cast, dict, ty, Datetime, Dict, Repr, Value};
|
||||||
use crate::export::PdfPageLabel;
|
|
||||||
use crate::font::Font;
|
use crate::font::Font;
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
self, styled_rect, Abs, Axes, Color, Corners, Dir, Em, FixedAlign, FixedStroke,
|
self, styled_rect, Abs, Axes, Color, Corners, Dir, Em, FixedAlign, FixedStroke,
|
||||||
@ -835,6 +834,45 @@ impl From<Position> for Dict {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Specification for a PDF page label.
|
||||||
|
#[derive(Debug, Clone, PartialEq, Hash, Default)]
|
||||||
|
pub struct PdfPageLabel {
|
||||||
|
/// Can be any string or none. Will always be prepended to the numbering style.
|
||||||
|
pub prefix: Option<EcoString>,
|
||||||
|
/// Based on the numbering pattern.
|
||||||
|
///
|
||||||
|
/// If `None` or numbering is a function, the field will be empty.
|
||||||
|
pub style: Option<PdfPageLabelStyle>,
|
||||||
|
/// Offset for the page label start.
|
||||||
|
///
|
||||||
|
/// Describes where to start counting from when setting a style.
|
||||||
|
/// (Has to be greater or equal than 1)
|
||||||
|
pub offset: Option<NonZeroUsize>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Repr for PdfPageLabel {
|
||||||
|
fn repr(&self) -> EcoString {
|
||||||
|
eco_format!("{self:?}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A PDF page label number style.
|
||||||
|
#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
|
||||||
|
pub enum PdfPageLabelStyle {
|
||||||
|
/// Decimal arabic numerals (1, 2, 3).
|
||||||
|
Arabic,
|
||||||
|
/// Lowercase roman numerals (i, ii, iii).
|
||||||
|
LowerRoman,
|
||||||
|
/// Uppercase roman numerals (I, II, III).
|
||||||
|
UpperRoman,
|
||||||
|
/// Lowercase letters (`a` to `z` for the first 26 pages,
|
||||||
|
/// `aa` to `zz` and so on for the next).
|
||||||
|
LowerAlpha,
|
||||||
|
/// Uppercase letters (`A` to `Z` for the first 26 pages,
|
||||||
|
/// `AA` to `ZZ` and so on for the next).
|
||||||
|
UpperAlpha,
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
@ -167,8 +167,13 @@ pub fn set_lang_items(items: LangItems) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Access a lang item.
|
/// Access a lang item.
|
||||||
macro_rules! item {
|
#[macro_export]
|
||||||
|
#[doc(hidden)]
|
||||||
|
macro_rules! __item {
|
||||||
($name:ident) => {
|
($name:ident) => {
|
||||||
$crate::eval::LANG_ITEMS.get().unwrap().$name
|
$crate::eval::LANG_ITEMS.get().unwrap().$name
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[doc(inline)]
|
||||||
|
pub use crate::__item as item;
|
||||||
|
@ -55,7 +55,7 @@ pub use self::fields::fields_on;
|
|||||||
pub use self::func::{
|
pub use self::func::{
|
||||||
func, CapturesVisitor, Func, NativeFunc, NativeFuncData, ParamInfo,
|
func, CapturesVisitor, Func, NativeFunc, NativeFuncData, ParamInfo,
|
||||||
};
|
};
|
||||||
pub use self::library::{set_lang_items, LangItems, Library};
|
pub use self::library::{item, set_lang_items, LangItems, Library};
|
||||||
pub use self::methods::mutable_methods_on;
|
pub use self::methods::mutable_methods_on;
|
||||||
pub use self::module::Module;
|
pub use self::module::Module;
|
||||||
pub use self::none::NoneValue;
|
pub use self::none::NoneValue;
|
||||||
|
@ -6,6 +6,7 @@ use ecow::eco_format;
|
|||||||
|
|
||||||
use super::{format_str, IntoValue, Regex, Repr, Value};
|
use super::{format_str, IntoValue, Regex, Repr, Value};
|
||||||
use crate::diag::{bail, StrResult};
|
use crate::diag::{bail, StrResult};
|
||||||
|
use crate::eval::item;
|
||||||
use crate::geom::{Align, Length, Numeric, Rel, Smart, Stroke};
|
use crate::geom::{Align, Length, Numeric, Rel, Smart, Stroke};
|
||||||
use Value::*;
|
use Value::*;
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ use super::{
|
|||||||
Version,
|
Version,
|
||||||
};
|
};
|
||||||
use crate::diag::StrResult;
|
use crate::diag::StrResult;
|
||||||
use crate::eval::Datetime;
|
use crate::eval::{item, Datetime};
|
||||||
use crate::geom::{Abs, Angle, Color, Em, Fr, Gradient, Length, Ratio, Rel};
|
use crate::geom::{Abs, Angle, Color, Em, Fr, Gradient, Length, Ratio, Rel};
|
||||||
use crate::model::{Label, Styles};
|
use crate::model::{Label, Styles};
|
||||||
use crate::syntax::{ast, Span};
|
use crate::syntax::{ast, Span};
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
//! Exporting into external formats.
|
|
||||||
|
|
||||||
mod pdf;
|
|
||||||
|
|
||||||
pub use self::pdf::{pdf, PdfPageLabel, PdfPageLabelStyle};
|
|
@ -1,4 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::eval::item;
|
||||||
|
|
||||||
/// Where to [align]($align) something along an axis.
|
/// Where to [align]($align) something along an axis.
|
||||||
///
|
///
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
use super::*;
|
use super::*;
|
||||||
|
use crate::eval::item;
|
||||||
|
|
||||||
/// A length that is relative to the font size.
|
/// A length that is relative to the font size.
|
||||||
///
|
///
|
||||||
|
@ -43,7 +43,6 @@ pub mod util;
|
|||||||
pub mod eval;
|
pub mod eval;
|
||||||
pub mod diag;
|
pub mod diag;
|
||||||
pub mod doc;
|
pub mod doc;
|
||||||
pub mod export;
|
|
||||||
pub mod font;
|
pub mod font;
|
||||||
pub mod geom;
|
pub mod geom;
|
||||||
pub mod image;
|
pub mod image;
|
||||||
|
@ -7,6 +7,7 @@ use super::{
|
|||||||
};
|
};
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::doc::Meta;
|
use crate::doc::Meta;
|
||||||
|
use crate::eval::item;
|
||||||
use crate::util::hash128;
|
use crate::util::hash128;
|
||||||
|
|
||||||
/// Whether the target is affected by show rules in the given style chain.
|
/// Whether the target is affected by show rules in the given style chain.
|
||||||
|
@ -8,8 +8,8 @@ use smallvec::SmallVec;
|
|||||||
use super::{Content, Element, Label, Locatable, Location};
|
use super::{Content, Element, Label, Locatable, Location};
|
||||||
use crate::diag::{bail, StrResult};
|
use crate::diag::{bail, StrResult};
|
||||||
use crate::eval::{
|
use crate::eval::{
|
||||||
cast, func, scope, ty, CastInfo, Dict, FromValue, Func, Reflect, Regex, Repr, Str,
|
cast, func, item, scope, ty, CastInfo, Dict, FromValue, Func, Reflect, Regex, Repr,
|
||||||
Symbol, Type, Value,
|
Str, Symbol, Type, Value,
|
||||||
};
|
};
|
||||||
use crate::util::pretty_array_like;
|
use crate::util::pretty_array_like;
|
||||||
|
|
||||||
|
@ -9,6 +9,7 @@ publish = false
|
|||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
typst = { workspace = true }
|
typst = { workspace = true }
|
||||||
typst-library = { workspace = true }
|
typst-library = { workspace = true }
|
||||||
|
typst-pdf = { workspace = true }
|
||||||
typst-render = { workspace = true }
|
typst-render = { workspace = true }
|
||||||
typst-svg = { workspace = true }
|
typst-svg = { workspace = true }
|
||||||
clap = { workspace = true }
|
clap = { workspace = true }
|
||||||
|
@ -420,7 +420,7 @@ fn test(
|
|||||||
let document = Document { pages: frames, ..Default::default() };
|
let document = Document { pages: frames, ..Default::default() };
|
||||||
if compare_ever {
|
if compare_ever {
|
||||||
if let Some(pdf_path) = pdf_path {
|
if let Some(pdf_path) = pdf_path {
|
||||||
let pdf_data = typst::export::pdf(
|
let pdf_data = typst_pdf::pdf(
|
||||||
&document,
|
&document,
|
||||||
Some(&format!("typst-test: {}", name.display())),
|
Some(&format!("typst-test: {}", name.display())),
|
||||||
world.today(Some(0)),
|
world.today(Some(0)),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user