Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Minimum PDF/A support #81

Merged
merged 4 commits into from
Sep 30, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ image = { version = "0.25", default-features = false, features = ["jpeg", "png",
miniz_oxide = "0.8"
once_cell = "1.18"
oxipng = { version = "9", default-features = false, features = ["filetime", "parallel", "zopfli"] }
pdf-writer = "0.10"
pdf-writer = "0.12"
pdfium-render = "=0.8.20"
termcolor = "1.2"
usvg = { version = "0.43", default-features = false }
tiny-skia = "0.11.4"
resvg = { version = "0.43", default-features = false }
subsetter = { git = "https://github.com/typst/subsetter", rev = "4e0058b" }
subsetter = "0.2"
ttf-parser = { version = "0.24.1" }
siphasher = { version = "1.0.1"}

Expand Down
1 change: 1 addition & 0 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ fn run() -> Result<(), String> {
compress: true,
embed_text: !args.text_to_paths,
raster_scale: args.raster_scale,
pdfa: false,
};

let page_options = PageOptions { dpi: args.dpi };
Expand Down
23 changes: 19 additions & 4 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ use usvg::{Size, Transform, Tree};

use crate::render::{tree_to_stream, tree_to_xobject};
use crate::util::context::Context;
use crate::util::helper::{deflate, RectExt, TransformExt};
use crate::util::helper::{deflate, ContentExt, RectExt, TransformExt};
use crate::util::resources::ResourceContainer;

// The ICC profiles.
Expand Down Expand Up @@ -96,6 +96,11 @@ impl Default for PageOptions {
pub enum ConversionError {
/// The SVG image contains an unrecognized type of image.
InvalidImage,
/// Text shaping resulted in a .notdef glyph. Can only occur if PDF/A
/// processing is enabled.
MissingGlyphs,
/// Converting the SVG would require too much nesting depth.
TooMuchNesting,
/// An unknown error occurred during the conversion. This could indicate a bug in the
/// svg2pdf.
UnknownError,
Expand All @@ -111,6 +116,8 @@ impl Display for ConversionError {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
Self::InvalidImage => f.write_str("An unknown type of image appears in the SVG."),
Self::MissingGlyphs => f.write_str("A piece of text could not be displayed with any font."),
Self::TooMuchNesting => f.write_str("The SVG's nesting depth is too high."),
Self::UnknownError => f.write_str("An unknown error occurred during the conversion. This could indicate a bug in svg2pdf"),
#[cfg(feature = "text")]
Self::SubsetError(_) => f.write_str("An error occurred while subsetting a font."),
Expand Down Expand Up @@ -148,6 +155,13 @@ pub struct ConversionOptions {
///
/// _Default:_ `true`.
pub embed_text: bool,

/// Whether to write chunks in PDF/A-2b compliant mode.
///
/// **Note:** This currently only ensures that `to_chunk` does not generate
/// anything that is forbidden by PDF/A. It does _not_ turn the
/// free-standing PDF generated by `to_pdf` into a valid PDF/A.
pub pdfa: bool,
}

impl Default for ConversionOptions {
Expand All @@ -156,6 +170,7 @@ impl Default for ConversionOptions {
compress: true,
raster_scale: 1.5,
embed_text: true,
pdfa: false,
}
}
}
Expand Down Expand Up @@ -190,7 +205,7 @@ pub fn to_pdf(
conversion_options: ConversionOptions,
page_options: PageOptions,
) -> Result<Vec<u8>> {
let mut ctx = Context::new(tree, conversion_options);
let mut ctx = Context::new(tree, conversion_options)?;
let mut pdf = Pdf::new();

let dpi_ratio = 72.0 / page_options.dpi;
Expand All @@ -210,7 +225,7 @@ pub fn to_pdf(
// Generate main content
let mut rc = ResourceContainer::new();
let mut content = Content::new();
content.save_state();
content.save_state_checked()?;
content.transform(dpi_transform.to_pdf_transform());
tree_to_stream(tree, &mut pdf, &mut content, &mut ctx, &mut rc)?;
content.restore_state();
Expand Down Expand Up @@ -347,7 +362,7 @@ pub fn to_chunk(
) -> Result<(Chunk, Ref)> {
let mut chunk = Chunk::new();

let mut ctx = Context::new(tree, conversion_options);
let mut ctx = Context::new(tree, conversion_options)?;
let x_ref = tree_to_xobject(tree, &mut chunk, &mut ctx)?;
ctx.write_global_objects(&mut chunk)?;
Ok((chunk, x_ref))
Expand Down
6 changes: 4 additions & 2 deletions src/render/clip_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ use usvg::{ClipPath, FillRule, Group, Node, Transform};
use super::group;
use super::path::draw_path;
use crate::util::context::Context;
use crate::util::helper::{bbox_to_non_zero_rect, NameExt, RectExt, TransformExt};
use crate::util::helper::{
bbox_to_non_zero_rect, ContentExt, NameExt, RectExt, TransformExt,
};
use crate::util::resources::ResourceContainer;
use crate::Result;

Expand Down Expand Up @@ -181,7 +183,7 @@ fn create_complex_clip_path(
let x_ref = ctx.alloc_ref();

let mut content = Content::new();
content.save_state();
content.save_state_checked()?;

if let Some(clip_path) = clip_path.clip_path() {
render(parent, clip_path, chunk, &mut content, ctx, &mut rc)?;
Expand Down
8 changes: 5 additions & 3 deletions src/render/group.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,9 @@ use usvg::{Opacity, Transform};
use super::filter;
use super::{clip_path, mask, Render};
use crate::util::context::Context;
use crate::util::helper::{BlendModeExt, GroupExt, NameExt, RectExt, TransformExt};
use crate::util::helper::{
BlendModeExt, ContentExt, GroupExt, NameExt, RectExt, TransformExt,
};
use crate::util::resources::ResourceContainer;
use crate::Result;

Expand Down Expand Up @@ -36,7 +38,7 @@ pub fn render(
let initial_opacity = initial_opacity.unwrap_or(Opacity::ONE);

if group.is_isolated() || initial_opacity.get() != 1.0 {
content.save_state();
content.save_state_checked()?;
let gs_ref = ctx.alloc_ref();
let mut gs = chunk.ext_graphics(gs_ref);
gs.non_stroking_alpha(group.opacity().mul(initial_opacity).get())
Expand Down Expand Up @@ -126,7 +128,7 @@ fn create_to_stream(
accumulated_transform: Transform,
rc: &mut ResourceContainer,
) -> Result<()> {
content.save_state();
content.save_state_checked()?;
content.transform(group.transform().to_pdf_transform());
let accumulated_transform = accumulated_transform.pre_concat(group.transform());

Expand Down
4 changes: 2 additions & 2 deletions src/render/image.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use usvg::{ImageKind, Rect, Size, Transform, Tree};

use crate::render::tree_to_xobject;
use crate::util::context::Context;
use crate::util::helper::{NameExt, TransformExt};
use crate::util::helper::{ContentExt, NameExt, TransformExt};
use crate::util::resources::ResourceContainer;
use crate::Result;

Expand Down Expand Up @@ -59,7 +59,7 @@ pub fn render(
Rect::from_xywh(0.0, 0.0, image_size.width(), image_size.height()).unwrap(),
);

content.save_state();
content.save_state_checked()?;

// Account for the x/y of the viewbox.
content.transform(
Expand Down
4 changes: 2 additions & 2 deletions src/render/mask.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use usvg::{Group, Mask, Transform};

use super::group;
use crate::util::context::Context;
use crate::util::helper::{clip_to_rect, MaskTypeExt, NameExt, RectExt};
use crate::util::helper::{clip_to_rect, ContentExt, MaskTypeExt, NameExt, RectExt};
use crate::util::resources::ResourceContainer;
use crate::Result;

Expand Down Expand Up @@ -34,7 +34,7 @@ pub fn create(
let mut rc = ResourceContainer::new();

let mut content = Content::new();
content.save_state();
content.save_state_checked()?;
LaurenzV marked this conversation as resolved.
Show resolved Hide resolved

if let Some(mask) = mask.mask() {
render(parent, mask, chunk, &mut content, ctx, &mut rc)?;
Expand Down
4 changes: 2 additions & 2 deletions src/render/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use pdf_writer::{Chunk, Content, Filter, Finish, Ref};
use usvg::{Node, Transform, Tree};

use crate::util::context::Context;
use crate::util::helper::{RectExt, TransformExt};
use crate::util::helper::{ContentExt, RectExt, TransformExt};
use crate::util::resources::ResourceContainer;
use crate::Result;

Expand All @@ -28,7 +28,7 @@ pub fn tree_to_stream(
ctx: &mut Context,
rc: &mut ResourceContainer,
) -> Result<()> {
content.save_state();
content.save_state_checked()?;

// From PDF coordinate system to SVG coordinate system
let initial_transform =
Expand Down
16 changes: 9 additions & 7 deletions src/render/path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use usvg::{Stroke, Transform};

use super::{gradient, pattern};
use crate::util::context::Context;
use crate::util::helper::{ColorExt, LineCapExt, LineJoinExt, NameExt};
use crate::util::helper::{ColorExt, ContentExt, LineCapExt, LineJoinExt, NameExt};
use crate::util::resources::ResourceContainer;
use crate::Result;

Expand Down Expand Up @@ -104,6 +104,7 @@ pub(crate) fn stroke_path(
let operation = |content: &mut Content, stroke: &Stroke| {
draw_path(path.data().segments(), content);
finish_path(Some(stroke), None, content);
Ok(())
};

if let Some(path_stroke) = path.stroke() {
Expand Down Expand Up @@ -131,13 +132,13 @@ pub(crate) fn stroke(
content: &mut Content,
ctx: &mut Context,
rc: &mut ResourceContainer,
operation: impl Fn(&mut Content, &Stroke),
operation: impl Fn(&mut Content, &Stroke) -> Result<()>,
accumulated_transform: Transform,
bbox: Rect,
) -> Result<()> {
let paint = &stroke.paint();

content.save_state();
content.save_state_checked()?;

match paint {
Paint::Color(c) => {
Expand Down Expand Up @@ -206,7 +207,7 @@ pub(crate) fn stroke(
content.set_dash_pattern(vec![], 0.0);
}

operation(content, stroke);
operation(content, stroke)?;

content.restore_state();

Expand All @@ -229,6 +230,7 @@ pub(crate) fn fill_path(
let operation = |content: &mut Content, fill: &Fill| {
draw_path(path.data().segments(), content);
finish_path(None, Some(fill), content);
Ok(())
};

if let Some(path_fill) = path.fill() {
Expand Down Expand Up @@ -256,13 +258,13 @@ pub(crate) fn fill(
content: &mut Content,
ctx: &mut Context,
rc: &mut ResourceContainer,
operation: impl Fn(&mut Content, &Fill),
operation: impl Fn(&mut Content, &Fill) -> Result<()>,
accumulated_transform: Transform,
bbox: Rect,
) -> Result<()> {
let paint = &fill.paint();

content.save_state();
content.save_state_checked()?;

match paint {
Paint::Color(c) => {
Expand Down Expand Up @@ -307,7 +309,7 @@ pub(crate) fn fill(
}
}

operation(content, fill);
operation(content, fill)?;
content.restore_state();

Ok(())
Expand Down
Loading