Skip to content

Commit

Permalink
Minimum PDF/A support
Browse files Browse the repository at this point in the history
  • Loading branch information
laurmaedje committed Sep 27, 2024
1 parent 5963e1e commit 2641470
Show file tree
Hide file tree
Showing 13 changed files with 130 additions and 55 deletions.
5 changes: 2 additions & 3 deletions Cargo.lock

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

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ 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 = { git = "https://github.com/typst/pdf-writer", rev = "b8f07d6" }
pdfium-render = "=0.8.20"
termcolor = "1.2"
usvg = { version = "0.43", default-features = false }
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
19 changes: 15 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,9 @@ pub struct ConversionOptions {
///
/// _Default:_ `true`.
pub embed_text: bool,

/// Whether to enforce PDF/A rules.
pub pdfa: bool,
}

impl Default for ConversionOptions {
Expand All @@ -156,6 +166,7 @@ impl Default for ConversionOptions {
compress: true,
raster_scale: 1.5,
embed_text: true,
pdfa: false,
}
}
}
Expand Down Expand Up @@ -190,7 +201,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 +221,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 +358,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()?;

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

0 comments on commit 2641470

Please sign in to comment.