Skip to content

Commit

Permalink
Part 1 of refactoring the crate (#75)
Browse files Browse the repository at this point in the history
  • Loading branch information
LaurenzV authored Nov 15, 2024
1 parent 0430500 commit 174edaa
Show file tree
Hide file tree
Showing 27 changed files with 787 additions and 786 deletions.
16 changes: 5 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,11 @@ In addition to that, the library also supports the following PDF features:
- Great subsetting for both, CFF-flavored and TTF-flavored fonts, ensuring small file sizes.
- Creating document outlines.
- Setting page labels.
- Inserting links to webpages or intra-document positions.
- Annotations, links, (named) destinations.
- Adding document metadata.

- Creating accessible PDFs via tagged PDF.
- Support for different PDF versions (1.4, 1.5, 1.6, 1.7)
and specific export modes (PDF/A1, PDF/A2, PDF/A3, PDF/UA1).

## Scope
This crate labels itself as a high-level crate, and this is what it is: It abstracts away most
Expand Down Expand Up @@ -109,14 +111,6 @@ has just been released and hasn't been used on a wide scale yet, so there might
However, I think the current test setup makes it very easy to track future bugs and puts krilla
in a very good spot to ensure that no regressions occur in the future.

## Future work
For the immediate future, I plan to at least add support for:
- Adding document metadata.
- Support for tagged PDFs for accessibility.
- Support for validated PDF export, like for example PDF/UA.

Although it will probably take some time until I get to it.

## Example

The following example shows some of the features of krilla in action.
Expand All @@ -125,7 +119,7 @@ The example creates a PDF file with two pages. On the first page,
we add two small pieces of text, and on the second page we embed a full-page SVG.
Consult the documentation to see all features that are available in krilla.

For more examples, feel free to take a look at the the [examples](https://github.com/LaurenzV/krilla/tree/main/examples) directory of the GitHub repository.
For more examples, feel free to take a look at the [examples](https://github.com/LaurenzV/krilla/tree/main/examples) directory of the GitHub repository.

```rs
// Create a new document.
Expand Down
24 changes: 14 additions & 10 deletions crates/krilla/src/chunk_container.rs
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,10 @@ impl ChunkContainer {
// for the document catalog. This hopefully allows us to avoid re-alloactions in the general
// case, and thus give us better performance.
let mut pdf = Pdf::with_capacity((chunks_len as f32 * 1.1 + 200.0) as usize);
sc.serialize_settings.pdf_version.set_version(&mut pdf);
sc.serialize_settings().pdf_version.set_version(&mut pdf);

if sc.serialize_settings.ascii_compatible
&& !sc.serialize_settings.validator.requires_binary_header()
if sc.serialize_settings().ascii_compatible
&& !sc.serialize_settings().validator.requires_binary_header()
{
pdf.set_binary_marker(b"AAAA")
}
Expand Down Expand Up @@ -163,16 +163,16 @@ impl ChunkContainer {
metadata.serialize_xmp_metadata(&mut xmp);
}

sc.serialize_settings.validator.write_xmp(&mut xmp);
sc.serialize_settings().validator.write_xmp(&mut xmp);

let instance_id = hash_base64(pdf.as_bytes());

let document_id = if let Some(metadata) = &self.metadata {
if let Some(document_id) = &metadata.document_id {
hash_base64(&(sc.serialize_settings.pdf_version.as_str(), document_id))
hash_base64(&(sc.serialize_settings().pdf_version.as_str(), document_id))
} else if metadata.title.is_some() && metadata.authors.is_some() {
hash_base64(&(
sc.serialize_settings.pdf_version.as_str(),
sc.serialize_settings().pdf_version.as_str(),
&metadata.title,
&metadata.authors,
))
Expand All @@ -193,7 +193,7 @@ impl ChunkContainer {
));

xmp.rendition_class(RenditionClass::Proof);
sc.serialize_settings.pdf_version.write_xmp(&mut xmp);
sc.serialize_settings().pdf_version.write_xmp(&mut xmp);

// We only write a catalog if a page tree exists. Every valid PDF must have one
// and krilla ensures that there always is one, but for snapshot tests, it can be
Expand All @@ -204,7 +204,7 @@ impl ChunkContainer {
|| self.destination_profiles.is_some()
|| self.struct_tree_root.is_some()
{
let meta_ref = if sc.serialize_settings.xmp_metadata {
let meta_ref = if sc.serialize_settings().xmp_metadata {
let meta_ref = remapped_ref.bump();
let xmp_buf = xmp.finish(None);
pdf.stream(meta_ref, xmp_buf.as_bytes())
Expand Down Expand Up @@ -245,14 +245,18 @@ impl ChunkContainer {
catalog.pair(Name(b"StructTreeRoot"), st.0);
let mut mark_info = catalog.mark_info();
mark_info.marked(true);
if sc.serialize_settings.pdf_version >= PdfVersion::Pdf16 {
if sc.serialize_settings().pdf_version >= PdfVersion::Pdf16 {
// We always set suspects to false because it's required by PDF/UA
mark_info.suspects(false);
}
mark_info.finish();
}

if sc.serialize_settings.validator.requires_display_doc_title() {
if sc
.serialize_settings()
.validator
.requires_display_doc_title()
{
catalog.viewer_preferences().display_doc_title(true);
}

Expand Down
15 changes: 8 additions & 7 deletions crates/krilla/src/content.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
//! A low-level abstraction over a single content stream.
use crate::color::{Color, ColorSpace, ICCBasedColorSpace, DEVICE_CMYK, DEVICE_GRAY, DEVICE_RGB};
use crate::font::{Font, FontIdentifier, Glyph, GlyphUnits, PaintMode};
use crate::font::{Font, Glyph, GlyphUnits};
use crate::graphics_state::GraphicsStates;
#[cfg(feature = "raster-images")]
use crate::image::Image;
use crate::mask::Mask;
use crate::object::cid_font::CIDFont;
use crate::object::ext_g_state::ExtGState;
use crate::object::font::cid_font::CIDFont;
use crate::object::font::type3_font::{CoveredGlyph, Type3Font};
use crate::object::font::{FontContainer, FontIdentifier, PDFGlyph, PaintMode};
use crate::object::shading_function::{GradientProperties, GradientPropertiesExt, ShadingFunction};
use crate::object::shading_pattern::ShadingPattern;
use crate::object::tiling_pattern::TilingPattern;
use crate::object::type3_font::{CoveredGlyph, Type3Font};
use crate::object::xobject::XObject;
use crate::paint::{InnerPaint, Paint};
use crate::path::{Fill, FillRule, LineCap, LineJoin, Stroke};
use crate::resource::ResourceDictionaryBuilder;
use crate::serialize::{FontContainer, PDFGlyph, SerializerContext};
use crate::serialize::SerializerContext;
use crate::stream::Stream;
use crate::tagging::ContentTag;
use crate::util::{calculate_stroke_bbox, LineCapExt, LineJoinExt, NameExt, RectExt, TransformExt};
Expand Down Expand Up @@ -68,7 +69,7 @@ impl ContentBuilder {

pub fn finish(self, sc: &mut SerializerContext) -> Stream {
let buf = self.content.finish();
sc.limits.merge(buf.limits());
sc.register_limits(buf.limits());

Stream::new(
buf.to_bytes(),
Expand Down Expand Up @@ -676,11 +677,11 @@ impl ContentBuilder {
|color: Color, content_builder: &mut ContentBuilder, sc: &mut SerializerContext| {
match color.color_space(sc) {
ColorSpace::Rgb => content_builder.rd_builder.register_resource(
ICCBasedColorSpace(sc.serialize_settings.pdf_version.rgb_icc()),
ICCBasedColorSpace(sc.serialize_settings().pdf_version.rgb_icc()),
sc,
),
ColorSpace::Gray => content_builder.rd_builder.register_resource(
ICCBasedColorSpace(sc.serialize_settings.pdf_version.grey_icc()),
ICCBasedColorSpace(sc.serialize_settings().pdf_version.grey_icc()),
sc,
),
ColorSpace::Cmyk(p) => content_builder.rd_builder.register_resource(p, sc),
Expand Down
6 changes: 4 additions & 2 deletions crates/krilla/src/font/colr.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
//! Drawing COLR-based glyphs to a surface.
use crate::font::{Font, OutlineBuilder, PaintMode};
use crate::font::outline::OutlineBuilder;
use crate::font::Font;
use crate::object::color::rgb;
use crate::object::font::PaintMode;
use crate::paint::{LinearGradient, RadialGradient, SpreadMethod, Stop, SweepGradient};
use crate::path::{Fill, FillRule};
use crate::surface::Surface;
Expand Down Expand Up @@ -205,7 +207,7 @@ impl ColorPainter for ColrBuilder {
return;
};

let mut glyph_builder = OutlineBuilder(PathBuilder::new());
let mut glyph_builder = OutlineBuilder::new();
let outline_glyphs = self.font.font_ref().outline_glyphs();
let Some(outline_glyph) = outline_glyphs.get(glyph_id) else {
self.error = true;
Expand Down
120 changes: 9 additions & 111 deletions crates/krilla/src/font/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
use crate::serialize::SvgSettings;
use crate::surface::Surface;
use crate::type3_font::Type3ID;
use crate::util::{Prehashed, RectWrapper};
use skrifa::outline::OutlinePen;
use skrifa::prelude::{LocationRef, Size};
use skrifa::raw::types::NameId;
use skrifa::raw::TableProvider;
Expand All @@ -29,7 +27,7 @@ use std::fmt::{Debug, Formatter};
use std::hash::{Hash, Hasher};
use std::ops::Range;
use std::sync::Arc;
use tiny_skia_path::{FiniteF32, Path, PathBuilder, Rect, Transform};
use tiny_skia_path::{FiniteF32, Rect, Transform};
use yoke::{Yoke, Yokeable};

#[cfg(feature = "raster-images")]
Expand All @@ -39,11 +37,9 @@ pub(crate) mod outline;
#[cfg(feature = "svg")]
pub(crate) mod svg;

use crate::path::{Fill, Stroke};
use crate::resource::RegisterableResource;
use crate::object::font::PaintMode;
use skrifa::instance::Location;
pub use skrifa::GlyphId;

/// An OpenType font. Can be a TrueType, OpenType font or a TrueType collection.
/// It holds a reference to the underlying data as well as some basic information
/// about the font.
Expand All @@ -54,7 +50,7 @@ pub use skrifa::GlyphId;
/// While an object of this type is associated with an OTF font, it is only associated
/// with a specific instance, i.e. with specific variation coordinates and with a specific
/// index for TrueType collections. This means that if you want to use the same font with
/// different variation axes, you need to create separate instances.
/// different variation axes, you need to create separate instances of [`Font`].
#[derive(Clone, Hash, Eq, PartialEq)]
pub struct Font(Arc<Prehashed<Repr>>);

Expand All @@ -63,6 +59,8 @@ impl Font {
/// associated with this font for TrueType collections, otherwise this value should be
/// set to 0. The location indicates the variation axes that should be associated with
/// the font.
///
/// Returns `None` if the index is invalid or the font couldn't be read.
pub fn new(
data: Arc<dyn AsRef<[u8]> + Send + Sync>,
index: u32,
Expand Down Expand Up @@ -150,18 +148,15 @@ impl Font {
.map(|v| (v.0.as_str(), v.1.get()))
}

/// Return the `LocationRef` of the font.
pub fn location_ref(&self) -> LocationRef {
pub(crate) fn location_ref(&self) -> LocationRef {
(&self.0.font_info.location).into()
}

/// Return the `FontRef` of the font.
pub fn font_ref(&self) -> &FontRef {
pub(crate) fn font_ref(&self) -> &FontRef {
&self.0.font_ref_yoke.get().font_ref
}

/// Return the underlying data of the font.
pub fn font_data(&self) -> Arc<dyn AsRef<[u8]> + Send + Sync> {
pub(crate) fn font_data(&self) -> Arc<dyn AsRef<[u8]> + Send + Sync> {
self.0.font_data.clone()
}

Expand Down Expand Up @@ -213,7 +208,7 @@ struct Repr {
impl Hash for Repr {
fn hash<H: Hasher>(&self, state: &mut H) {
// We assume that if the font info is distinct, the font itself is distinct as well. This
// strictly doesn't have to be the case, while the font does have a checksum, it's "only" a
// doesn't have to be the case: while the font does have a checksum, it's "only" a
// u32. The proper way would be to hash the whole font data, but this is just too expensive.
// However, the odds of the checksum AND all font metrics (including font name) being the same
// with the font being different is diminishingly low.
Expand Down Expand Up @@ -334,48 +329,6 @@ pub(crate) fn draw_color_glyph(
drawn
}

#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub(crate) enum OwnedPaintMode {
Fill(Fill),
Stroke(Stroke),
}

impl From<Fill> for OwnedPaintMode {
fn from(value: Fill) -> Self {
Self::Fill(value)
}
}

impl From<Stroke> for OwnedPaintMode {
fn from(value: Stroke) -> Self {
Self::Stroke(value)
}
}

impl OwnedPaintMode {
pub fn as_ref(&self) -> PaintMode {
match self {
OwnedPaintMode::Fill(f) => PaintMode::Fill(f),
OwnedPaintMode::Stroke(s) => PaintMode::Stroke(s),
}
}
}

#[derive(Debug, Clone, Copy)]
pub(crate) enum PaintMode<'a> {
Fill(&'a Fill),
Stroke(&'a Stroke),
}

impl PaintMode<'_> {
pub fn to_owned(self) -> OwnedPaintMode {
match self {
PaintMode::Fill(f) => OwnedPaintMode::Fill((*f).clone()),
PaintMode::Stroke(s) => OwnedPaintMode::Stroke((*s).clone()),
}
}
}

/// Draw a color glyph or outline glyph to a surface.
pub(crate) fn draw_glyph(
font: Font,
Expand All @@ -396,61 +349,6 @@ pub(crate) fn draw_glyph(
.or_else(|| outline::draw_glyph(font, glyph, paint_mode, base_transform, surface))
}

/// A unique CID identifier.
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub(crate) struct CIDIdentifer(pub Font);

/// A unique Type3 font identifier. Type3 fonts can only hold 256 glyphs, which
/// means that we might have to create more than one Type3 font. This is why we
/// additionally store an index that indicates which specific Type3Font we are
/// referring to.
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub(crate) struct Type3Identifier(pub Font, pub Type3ID);

/// A font identifier for a PDF font.
#[derive(Clone, Debug, Hash, Eq, PartialEq)]
pub(crate) enum FontIdentifier {
Cid(CIDIdentifer),
Type3(Type3Identifier),
}

impl RegisterableResource<crate::resource::Font> for FontIdentifier {}

/// A wrapper struct for implementing the `OutlinePen` trait.
struct OutlineBuilder(PathBuilder);

impl OutlineBuilder {
pub fn new() -> Self {
Self(PathBuilder::new())
}

pub fn finish(self) -> Option<Path> {
self.0.finish()
}
}

impl OutlinePen for OutlineBuilder {
fn move_to(&mut self, x: f32, y: f32) {
self.0.move_to(x, y);
}

fn line_to(&mut self, x: f32, y: f32) {
self.0.line_to(x, y);
}

fn quad_to(&mut self, cx0: f32, cy0: f32, x: f32, y: f32) {
self.0.quad_to(cx0, cy0, x, y);
}

fn curve_to(&mut self, cx0: f32, cy0: f32, cx1: f32, cy1: f32, x: f32, y: f32) {
self.0.cubic_to(cx0, cy0, cx1, cy1, x, y);
}

fn close(&mut self) {
self.0.close()
}
}

/// A glyph with certain properties.
pub trait Glyph {
/// The glyph ID of the glyph.
Expand Down
Loading

0 comments on commit 174edaa

Please sign in to comment.