mirror of
https://github.com/typst/typst
synced 2025-06-08 13:16:24 +08:00
Add support for exporting to specific version
This commit is contained in:
parent
9a4bd9be25
commit
880aa4b8a1
@ -241,10 +241,14 @@ pub struct CompileArgs {
|
||||
#[arg(long = "pages", value_delimiter = ',')]
|
||||
pub pages: Option<Vec<Pages>>,
|
||||
|
||||
/// One (or multiple comma-separated) PDF standards that Typst will enforce
|
||||
/// The version of the produced PDF.
|
||||
#[arg(long = "pdf-version")]
|
||||
pub pdf_version: Option<PdfVersion>,
|
||||
|
||||
/// A PDF standard that Typst will enforce
|
||||
/// conformance with.
|
||||
#[arg(long = "pdf-standard", value_delimiter = ',')]
|
||||
pub pdf_standard: Vec<PdfStandard>,
|
||||
#[arg(long = "pdf-standard")]
|
||||
pub pdf_standard: Option<PdfStandard>,
|
||||
|
||||
/// The PPI (pixels per inch) to use for PNG export.
|
||||
#[arg(long = "ppi", default_value_t = 144.0)]
|
||||
@ -463,16 +467,45 @@ pub enum Feature {
|
||||
|
||||
display_possible_values!(Feature);
|
||||
|
||||
/// A PDF version.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum PdfVersion {
|
||||
/// PDF 1.4.
|
||||
#[value(name = "1.4")]
|
||||
V_1_4,
|
||||
/// PDF 1.5.
|
||||
#[value(name = "1.5")]
|
||||
V_1_5,
|
||||
/// PDF 1.5.
|
||||
#[value(name = "1.6")]
|
||||
V_1_6,
|
||||
/// PDF 1.7.
|
||||
#[value(name = "1.7")]
|
||||
V_1_7,
|
||||
}
|
||||
|
||||
display_possible_values!(PdfVersion);
|
||||
|
||||
/// A PDF standard that Typst can enforce conformance with.
|
||||
#[derive(Debug, Copy, Clone, Eq, PartialEq, ValueEnum)]
|
||||
#[allow(non_camel_case_types)]
|
||||
pub enum PdfStandard {
|
||||
/// PDF 1.7.
|
||||
#[value(name = "1.7")]
|
||||
V_1_7,
|
||||
/// PDF/A-2u.
|
||||
#[value(name = "a-1b")]
|
||||
A_1b,
|
||||
/// PDF/A-2b.
|
||||
#[value(name = "a-2b")]
|
||||
A_2b,
|
||||
/// PDF/A-2u.
|
||||
#[value(name = "a-2u")]
|
||||
A_2u,
|
||||
/// PDF/A-3u.
|
||||
#[value(name = "a-3b")]
|
||||
A_3b,
|
||||
/// PDF/A-2b.
|
||||
#[value(name = "a-3u")]
|
||||
A_3u,
|
||||
}
|
||||
|
||||
display_possible_values!(PdfStandard);
|
||||
|
@ -17,11 +17,11 @@ use typst::html::HtmlDocument;
|
||||
use typst::layout::{Frame, Page, PageRanges, PagedDocument};
|
||||
use typst::syntax::{FileId, Source, Span};
|
||||
use typst::WorldExt;
|
||||
use typst_pdf::{PdfOptions, PdfStandards};
|
||||
use typst_pdf::{PdfOptions, PdfStandards, Validator};
|
||||
|
||||
use crate::args::{
|
||||
CompileArgs, CompileCommand, DiagnosticFormat, Input, Output, OutputFormat,
|
||||
PdfStandard, WatchCommand,
|
||||
PdfStandard, PdfVersion, WatchCommand,
|
||||
};
|
||||
#[cfg(feature = "http-server")]
|
||||
use crate::server::HtmlServer;
|
||||
@ -62,9 +62,10 @@ pub struct CompileConfig {
|
||||
/// Opens the output file with the default viewer or a specific program after
|
||||
/// compilation.
|
||||
pub open: Option<Option<String>>,
|
||||
/// One (or multiple comma-separated) PDF standards that Typst will enforce
|
||||
/// conformance with.
|
||||
pub pdf_standards: PdfStandards,
|
||||
/// The version that should be used to export the PDF.
|
||||
pub pdf_version: Option<PdfVersion>,
|
||||
/// A standard the PDF should conform to.
|
||||
pub pdf_standard: Option<PdfStandard>,
|
||||
/// A path to write a Makefile rule describing the current compilation.
|
||||
pub make_deps: Option<PathBuf>,
|
||||
/// The PPI (pixels per inch) to use for PNG export.
|
||||
@ -129,18 +130,6 @@ impl CompileConfig {
|
||||
PageRanges::new(export_ranges.iter().map(|r| r.0.clone()).collect())
|
||||
});
|
||||
|
||||
let pdf_standards = {
|
||||
let list = args
|
||||
.pdf_standard
|
||||
.iter()
|
||||
.map(|standard| match standard {
|
||||
PdfStandard::V_1_7 => typst_pdf::PdfStandard::V_1_7,
|
||||
PdfStandard::A_2b => typst_pdf::PdfStandard::A_2b,
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
PdfStandards::new(&list)?
|
||||
};
|
||||
|
||||
#[cfg(feature = "http-server")]
|
||||
let server = match watch {
|
||||
Some(command)
|
||||
@ -157,15 +146,16 @@ impl CompileConfig {
|
||||
output,
|
||||
output_format,
|
||||
pages,
|
||||
pdf_standards,
|
||||
creation_timestamp: args.world.creation_timestamp,
|
||||
make_deps: args.make_deps.clone(),
|
||||
ppi: args.ppi,
|
||||
diagnostic_format: args.process.diagnostic_format,
|
||||
open: args.open.clone(),
|
||||
pdf_version: args.pdf_version,
|
||||
export_cache: ExportCache::new(),
|
||||
#[cfg(feature = "http-server")]
|
||||
server,
|
||||
pdf_standard: args.pdf_standard,
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -277,7 +267,22 @@ fn export_pdf(document: &PagedDocument, config: &CompileConfig) -> SourceResult<
|
||||
config.creation_timestamp.unwrap_or_else(chrono::Utc::now),
|
||||
),
|
||||
page_ranges: config.pages.clone(),
|
||||
standards: config.pdf_standards.clone(),
|
||||
pdf_version: config.pdf_version.map(|v| match v {
|
||||
PdfVersion::V_1_4 => typst_pdf::PdfVersion::Pdf14,
|
||||
PdfVersion::V_1_5 => typst_pdf::PdfVersion::Pdf15,
|
||||
PdfVersion::V_1_6 => typst_pdf::PdfVersion::Pdf16,
|
||||
PdfVersion::V_1_7 => typst_pdf::PdfVersion::Pdf17,
|
||||
}),
|
||||
validator: config
|
||||
.pdf_standard
|
||||
.map(|s| match s {
|
||||
PdfStandard::A_1b => Validator::A1_B,
|
||||
PdfStandard::A_2b => Validator::A2_B,
|
||||
PdfStandard::A_2u => Validator::A2_U,
|
||||
PdfStandard::A_3b => Validator::A3_B,
|
||||
PdfStandard::A_3u => Validator::A3_U,
|
||||
})
|
||||
.unwrap_or(Validator::None),
|
||||
};
|
||||
let buffer = typst_pdf::pdf(document, &options)?;
|
||||
config
|
||||
|
@ -4,11 +4,10 @@ use ecow::eco_format;
|
||||
use pdf_writer::types::Direction;
|
||||
use pdf_writer::writers::PageLabel;
|
||||
use pdf_writer::{Finish, Name, Pdf, Ref, Str, TextStr};
|
||||
use typst_library::diag::{bail, SourceResult};
|
||||
use typst_library::diag::SourceResult;
|
||||
use typst_library::foundations::{Datetime, Smart};
|
||||
use typst_library::layout::Dir;
|
||||
use typst_library::text::Lang;
|
||||
use typst_syntax::Span;
|
||||
use xmp_writer::{DateTime, LangId, RenditionClass, Timezone, XmpWriter};
|
||||
|
||||
use crate::page_old::PdfPageLabel;
|
||||
@ -128,34 +127,34 @@ pub fn write_catalog(
|
||||
xmp.create_date(xmp_date);
|
||||
xmp.modify_date(xmp_date);
|
||||
|
||||
if ctx.options.standards.pdfa {
|
||||
let mut history = xmp.history();
|
||||
history
|
||||
.add_event()
|
||||
.action(xmp_writer::ResourceEventAction::Saved)
|
||||
.when(xmp_date)
|
||||
.instance_id(&eco_format!("{instance_id}_source"));
|
||||
history
|
||||
.add_event()
|
||||
.action(xmp_writer::ResourceEventAction::Converted)
|
||||
.when(xmp_date)
|
||||
.instance_id(&instance_id)
|
||||
.software_agent(&creator);
|
||||
}
|
||||
// if ctx.options.standards.pdfa {
|
||||
// let mut history = xmp.history();
|
||||
// history
|
||||
// .add_event()
|
||||
// .action(xmp_writer::ResourceEventAction::Saved)
|
||||
// .when(xmp_date)
|
||||
// .instance_id(&eco_format!("{instance_id}_source"));
|
||||
// history
|
||||
// .add_event()
|
||||
// .action(xmp_writer::ResourceEventAction::Converted)
|
||||
// .when(xmp_date)
|
||||
// .instance_id(&instance_id)
|
||||
// .software_agent(&creator);
|
||||
// }
|
||||
}
|
||||
|
||||
// Assert dominance.
|
||||
if ctx.options.standards.pdfa {
|
||||
let mut extension_schemas = xmp.extension_schemas();
|
||||
extension_schemas
|
||||
.xmp_media_management()
|
||||
.properties()
|
||||
.describe_instance_id();
|
||||
extension_schemas.pdf().properties().describe_all();
|
||||
extension_schemas.finish();
|
||||
xmp.pdfa_part(2);
|
||||
xmp.pdfa_conformance("B");
|
||||
}
|
||||
// // Assert dominance.
|
||||
// if ctx.options.standards.pdfa {
|
||||
// let mut extension_schemas = xmp.extension_schemas();
|
||||
// extension_schemas
|
||||
// .xmp_media_management()
|
||||
// .properties()
|
||||
// .describe_instance_id();
|
||||
// extension_schemas.pdf().properties().describe_all();
|
||||
// extension_schemas.finish();
|
||||
// xmp.pdfa_part(2);
|
||||
// xmp.pdfa_conformance("B");
|
||||
// }
|
||||
|
||||
let xmp_buf = xmp.finish(None);
|
||||
let meta_ref = alloc.bump();
|
||||
@ -200,22 +199,22 @@ pub fn write_catalog(
|
||||
catalog.lang(TextStr(lang.as_str()));
|
||||
}
|
||||
|
||||
if ctx.options.standards.pdfa {
|
||||
catalog
|
||||
.output_intents()
|
||||
.push()
|
||||
.subtype(pdf_writer::types::OutputIntentSubtype::PDFA)
|
||||
.output_condition(TextStr("sRGB"))
|
||||
.output_condition_identifier(TextStr("Custom"))
|
||||
.info(TextStr("sRGB IEC61966-2.1"))
|
||||
.dest_output_profile(ctx.globals.color_functions.srgb.unwrap());
|
||||
}
|
||||
// if ctx.options.standards.pdfa {
|
||||
// catalog
|
||||
// .output_intents()
|
||||
// .push()
|
||||
// .subtype(pdf_writer::types::OutputIntentSubtype::PDFA)
|
||||
// .output_condition(TextStr("sRGB"))
|
||||
// .output_condition_identifier(TextStr("Custom"))
|
||||
// .info(TextStr("sRGB IEC61966-2.1"))
|
||||
// .dest_output_profile(ctx.globals.color_functions.srgb.unwrap());
|
||||
// }
|
||||
|
||||
catalog.finish();
|
||||
|
||||
if ctx.options.standards.pdfa && pdf.refs().count() > 8388607 {
|
||||
bail!(Span::detached(), "too many PDF objects");
|
||||
}
|
||||
// if ctx.options.standards.pdfa && pdf.refs().count() > 8388607 {
|
||||
// bail!(Span::detached(), "too many PDF objects");
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@ -244,9 +244,9 @@ impl ColorFontMap<()> {
|
||||
}
|
||||
|
||||
let (frame, tofu) = glyph_frame(font, glyph.id);
|
||||
if options.standards.pdfa && tofu {
|
||||
bail!(failed_to_convert(text, glyph));
|
||||
}
|
||||
// if options.standards.pdfa && tofu {
|
||||
// bail!(failed_to_convert(text, glyph));
|
||||
// }
|
||||
|
||||
let width = font.advance(glyph.id).unwrap_or(Em::new(0.0)).get()
|
||||
* font.units_per_em();
|
||||
|
@ -149,9 +149,9 @@ pub fn alloc_color_functions_refs(
|
||||
let mut chunk = PdfChunk::new();
|
||||
let mut used_color_spaces = ColorSpaces::default();
|
||||
|
||||
if context.options.standards.pdfa {
|
||||
used_color_spaces.mark_as_used(ColorSpace::Srgb);
|
||||
}
|
||||
// if context.options.standards.pdfa {
|
||||
// used_color_spaces.mark_as_used(ColorSpace::Srgb);
|
||||
// }
|
||||
|
||||
context.resources.traverse(&mut |r| {
|
||||
used_color_spaces.merge(&r.colors);
|
||||
@ -384,11 +384,11 @@ impl QuantizedColor for f32 {
|
||||
|
||||
/// Fails with an error if PDF/A processing is enabled.
|
||||
pub(super) fn check_cmyk_allowed(options: &PdfOptions) -> SourceResult<()> {
|
||||
if options.standards.pdfa {
|
||||
bail!(
|
||||
Span::detached(),
|
||||
"cmyk colors are not currently supported by PDF/A export"
|
||||
);
|
||||
}
|
||||
// if options.standards.pdfa {
|
||||
// bail!(
|
||||
// Span::detached(),
|
||||
// "cmyk colors are not currently supported by PDF/A export"
|
||||
// );
|
||||
// }
|
||||
Ok(())
|
||||
}
|
||||
|
@ -418,13 +418,13 @@ fn write_group(ctx: &mut Builder, pos: Point, group: &GroupItem) -> SourceResult
|
||||
|
||||
/// Encode a text run into the content stream.
|
||||
fn write_text(ctx: &mut Builder, pos: Point, text: &TextItem) -> SourceResult<()> {
|
||||
if ctx.options.standards.pdfa && text.font.info().is_last_resort() {
|
||||
bail!(
|
||||
Span::find(text.glyphs.iter().map(|g| g.span.0)),
|
||||
"the text {} could not be displayed with any font",
|
||||
&text.text,
|
||||
);
|
||||
}
|
||||
// if ctx.options.standards.pdfa && text.font.info().is_last_resort() {
|
||||
// bail!(
|
||||
// Span::find(text.glyphs.iter().map(|g| g.span.0)),
|
||||
// "the text {} could not be displayed with any font",
|
||||
// &text.text,
|
||||
// );
|
||||
// }
|
||||
|
||||
let outline_glyphs =
|
||||
text.glyphs.iter().filter(|g| should_outline(&text.font, g)).count();
|
||||
@ -516,9 +516,9 @@ fn write_normal_text(
|
||||
|
||||
// Write the glyphs with kerning adjustments.
|
||||
for glyph in text.glyphs() {
|
||||
if ctx.options.standards.pdfa && glyph.id == 0 {
|
||||
bail!(tofu(&text, glyph));
|
||||
}
|
||||
// if ctx.options.standards.pdfa && glyph.id == 0 {
|
||||
// bail!(tofu(&text, glyph));
|
||||
// }
|
||||
|
||||
adjustment += glyph.x_offset;
|
||||
|
||||
@ -607,9 +607,9 @@ fn write_complex_glyphs(
|
||||
.or_default();
|
||||
|
||||
for glyph in text.glyphs() {
|
||||
if ctx.options.standards.pdfa && glyph.id == 0 {
|
||||
bail!(tofu(&text, glyph));
|
||||
}
|
||||
// if ctx.options.standards.pdfa && glyph.id == 0 {
|
||||
// bail!(tofu(&text, glyph));
|
||||
// }
|
||||
|
||||
// Retrieve the Type3 font reference and the glyph index in the font.
|
||||
let color_fonts = ctx
|
||||
@ -732,8 +732,7 @@ fn write_image(
|
||||
) -> SourceResult<()> {
|
||||
let index = ctx.resources.images.insert(image.clone());
|
||||
ctx.resources.deferred_images.entry(index).or_insert_with(|| {
|
||||
let (image, color_space) =
|
||||
deferred_image(image.clone(), ctx.options.standards.pdfa);
|
||||
let (image, color_space) = deferred_image(image.clone(), false);
|
||||
if let Some(color_space) = color_space {
|
||||
ctx.resources.colors.mark_as_used(color_space);
|
||||
}
|
||||
@ -749,9 +748,9 @@ fn write_image(
|
||||
ctx.content.transform([w, 0.0, 0.0, -h, x, y + h]);
|
||||
|
||||
if let Some(alt) = image.alt() {
|
||||
if ctx.options.standards.pdfa && alt.len() > Str::PDFA_LIMIT {
|
||||
bail!(span, "the image's alt text is too long");
|
||||
}
|
||||
// if ctx.options.standards.pdfa && alt.len() > Str::PDFA_LIMIT {
|
||||
// bail!(span, "the image's alt text is too long");
|
||||
// }
|
||||
|
||||
let mut image_span =
|
||||
ctx.content.begin_marked_content_with_properties(Name(b"Span"));
|
||||
|
@ -1,7 +1,7 @@
|
||||
use crate::primitive::{PointExt, SizeExt, TransformExt};
|
||||
use crate::{paint, AbsExt, PdfOptions};
|
||||
use bytemuck::TransparentWrapper;
|
||||
use ecow::EcoString;
|
||||
use ecow::{eco_format, EcoString};
|
||||
use krilla::action::{Action, LinkAction};
|
||||
use krilla::annotation::{LinkAnnotation, Target};
|
||||
use krilla::destination::XyzDestination;
|
||||
@ -178,15 +178,32 @@ pub fn pdf(
|
||||
typst_document: &PagedDocument,
|
||||
options: &PdfOptions,
|
||||
) -> SourceResult<Vec<u8>> {
|
||||
let version = match options.pdf_version {
|
||||
None => options.validator.recommended_version(),
|
||||
Some(v) => {
|
||||
if !options.validator.compatible_with_version(v) {
|
||||
let v_string = v.as_str();
|
||||
let s_string = options.validator.as_str();
|
||||
let h_message = format!(
|
||||
"export using {} instead",
|
||||
options.validator.recommended_version().as_str()
|
||||
);
|
||||
bail!(Span::detached(), "{v_string} is not compatible with standard {s_string}"; hint: "{h_message}");
|
||||
} else {
|
||||
v
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let settings = SerializeSettings {
|
||||
compress_content_streams: true,
|
||||
no_device_cs: true,
|
||||
ascii_compatible: false,
|
||||
xmp_metadata: true,
|
||||
cmyk_profile: None,
|
||||
validator: Validator::None,
|
||||
validator: options.validator,
|
||||
enable_tagging: false,
|
||||
pdf_version: PdfVersion::Pdf17,
|
||||
pdf_version: version,
|
||||
};
|
||||
|
||||
let mut document = krilla::Document::new_with(settings);
|
||||
|
@ -18,14 +18,13 @@ mod pattern_old;
|
||||
mod primitive;
|
||||
mod resources_old;
|
||||
|
||||
use base64::Engine;
|
||||
use pdf_writer::{Chunk, Name, Pdf, Ref, Str, TextStr};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::fmt::{self, Debug, Formatter};
|
||||
use std::hash::Hash;
|
||||
use std::ops::{Deref, DerefMut};
|
||||
|
||||
use base64::Engine;
|
||||
use pdf_writer::{Chunk, Name, Pdf, Ref, Str, TextStr};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use typst_library::diag::{bail, SourceResult, StrResult};
|
||||
use typst_library::foundations::{Datetime, Smart};
|
||||
use typst_library::layout::{Abs, Em, PageRanges, PagedDocument, Transform};
|
||||
@ -79,6 +78,9 @@ pub fn pdf(document: &PagedDocument, options: &PdfOptions) -> SourceResult<Vec<u
|
||||
// .export_with(write_catalog)
|
||||
}
|
||||
|
||||
pub use ::krilla::validation::Validator;
|
||||
pub use ::krilla::version::PdfVersion;
|
||||
|
||||
/// Settings for PDF export.
|
||||
#[derive(Debug, Default)]
|
||||
pub struct PdfOptions<'a> {
|
||||
@ -100,8 +102,10 @@ pub struct PdfOptions<'a> {
|
||||
/// Specifies which ranges of pages should be exported in the PDF. When
|
||||
/// `None`, all pages should be exported.
|
||||
pub page_ranges: Option<PageRanges>,
|
||||
/// A list of PDF standards that Typst will enforce conformance with.
|
||||
pub standards: PdfStandards,
|
||||
/// The version that should be used to export the PDF.
|
||||
pub pdf_version: Option<PdfVersion>,
|
||||
/// A standard the PDF should conform to.
|
||||
pub validator: Validator,
|
||||
}
|
||||
|
||||
/// Encapsulates a list of compatible PDF standards.
|
||||
|
Loading…
x
Reference in New Issue
Block a user