Skip to content
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
6 changes: 2 additions & 4 deletions crates/pdf-canvas/src/truetype_font_renderer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -153,9 +153,7 @@ impl<B: CanvasBackend> TextRenderer for TrueTypeFontRenderer<'_, '_, B> {

#[cfg(test)]
mod tests {
use pdf_font::{
encoding::Encoding, flags::FontFlags, font_data::FontData, standard14::Standard14Font,
};
use pdf_font::{encoding::Encoding, flags::FontFlags, standard14::Standard14Font};

use super::*;

Expand Down Expand Up @@ -234,7 +232,7 @@ mod tests {
#[test]
fn unmappable_simple_truetype_returns_notdef() {
let font = Font::TrueType(pdf_font::true_type_font::TrueTypeFont {
font_file: FontData::Owned(vec![]),
font_file: Vec::new().into(),
widths: None,
encoding: None,
to_unicode: None,
Expand Down
12 changes: 7 additions & 5 deletions crates/pdf-color-space/src/cal_gray_color_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use pdf_object::{
object_lookup::ObjectLookupExt, object_resolver::ObjectResolver, object_variant::ObjectVariant,
};

use crate::{color_space::ColorSpace, error::ColorSpaceError};
use crate::{
cie_color_space::CieColorSpaceParams, color_space::ColorSpace, error::ColorSpaceError,
};

/// Calibrated Gray color space.
///
Expand All @@ -30,10 +32,10 @@ pub(crate) fn parse_cal_gray_color_space(
});
};
let dict = dict_obj.try_dictionary(objects)?;
let white_point = dict.required_array_of::<f32, 3>("WhitePoint", objects)?;
let black_point = dict
.optional_array_of::<f32, 3>("BlackPoint", objects)?
.unwrap_or([0.0, 0.0, 0.0]);
let CieColorSpaceParams {
white_point,
black_point,
} = CieColorSpaceParams::from_dictionary(dict, objects)?;
let gamma = dict
.optional_number::<f32>("Gamma", objects)?
.unwrap_or(1.0);
Expand Down
12 changes: 7 additions & 5 deletions crates/pdf-color-space/src/cal_rgb_color_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use pdf_object::object_lookup::ObjectLookupExt;
use pdf_object::{object_resolver::ObjectResolver, object_variant::ObjectVariant};

use crate::cal_gray_color_space::xyz_to_srgb;
use crate::{color_space::ColorSpace, error::ColorSpaceError};
use crate::{
cie_color_space::CieColorSpaceParams, color_space::ColorSpace, error::ColorSpaceError,
};

/// Calibrated RGB color space.
///
Expand Down Expand Up @@ -35,10 +37,10 @@ pub(crate) fn parse_cal_rgb_color_space(
});
};
let dict = dict_obj.try_dictionary(objects)?;
let white_point = dict.required_array_of::<f32, 3>("WhitePoint", objects)?;
let black_point = dict
.optional_array_of::<f32, 3>("BlackPoint", objects)?
.unwrap_or_default();
let CieColorSpaceParams {
white_point,
black_point,
} = CieColorSpaceParams::from_dictionary(dict, objects)?;
let gamma = dict
.optional_array_of::<f32, 3>("Gamma", objects)?
.unwrap_or([1.0, 1.0, 1.0]);
Expand Down
79 changes: 79 additions & 0 deletions crates/pdf-color-space/src/cie_color_space.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
use pdf_object::{
dictionary::Dictionary, error::ObjectError, object_lookup::ObjectLookupExt,
object_resolver::ObjectResolver,
};

/// Parameters shared by the CalGray, CalRGB, and Lab color spaces.
#[derive(Debug, PartialEq)]
pub(crate) struct CieColorSpaceParams {
/// Reference white point in CIE XYZ coordinates `[Xw, Yw, Zw]`.
pub(crate) white_point: [f32; 3],
/// Reference black point in CIE XYZ coordinates `[Xb, Yb, Zb]`.
///
/// Defaults to `[0.0, 0.0, 0.0]` when `/BlackPoint` is absent.
pub(crate) black_point: [f32; 3],
}

impl CieColorSpaceParams {
pub(crate) fn from_dictionary(
dictionary: &Dictionary,
objects: &dyn ObjectResolver,
) -> Result<Self, ObjectError> {
let white_point = dictionary.required_array_of::<f32, 3>("WhitePoint", objects)?;
let black_point = dictionary
.optional_array_of::<f32, 3>("BlackPoint", objects)?
.unwrap_or_default();

Ok(Self {
white_point,
black_point,
})
}
}

#[cfg(test)]
mod tests {
use std::collections::BTreeMap;

use pdf_object::{
dictionary::Dictionary, object_resolver::PassthroughResolver, object_variant::ObjectVariant,
};

use super::CieColorSpaceParams;

fn array(values: &[f64]) -> ObjectVariant {
ObjectVariant::Array(values.iter().copied().map(ObjectVariant::Real).collect())
}

#[test]
fn parses_white_and_black_points() {
let dictionary = Dictionary::new(BTreeMap::from([
("BlackPoint".to_owned(), array(&[0.1, 0.2, 0.3])),
("WhitePoint".to_owned(), array(&[0.9, 1.0, 0.8])),
]));

let params =
CieColorSpaceParams::from_dictionary(&dictionary, &PassthroughResolver).unwrap();

assert_eq!(
params,
CieColorSpaceParams {
white_point: [0.9, 1.0, 0.8],
black_point: [0.1, 0.2, 0.3],
}
);
}

#[test]
fn defaults_missing_black_point_to_zero() {
let dictionary = Dictionary::new(BTreeMap::from([(
"WhitePoint".to_owned(),
array(&[0.9, 1.0, 0.8]),
)]));

let params =
CieColorSpaceParams::from_dictionary(&dictionary, &PassthroughResolver).unwrap();

assert_eq!(params.black_point, [0.0, 0.0, 0.0]);
}
}
12 changes: 7 additions & 5 deletions crates/pdf-color-space/src/lab_color_space.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ use pdf_object::{
object_lookup::ObjectLookupExt, object_resolver::ObjectResolver, object_variant::ObjectVariant,
};

use crate::{color_space::ColorSpace, error::ColorSpaceError};
use crate::{
cie_color_space::CieColorSpaceParams, color_space::ColorSpace, error::ColorSpaceError,
};

/// CIE 1976 L*a*b* color space.
///
Expand Down Expand Up @@ -34,10 +36,10 @@ pub(crate) fn parse_lab_color_space(
});
};
let dict = dict_obj.try_dictionary(objects)?;
let white_point = dict.required_array_of::<f32, 3>("WhitePoint", objects)?;
let black_point = dict
.optional_array_of::<f32, 3>("BlackPoint", objects)?
.unwrap_or([0.0, 0.0, 0.0]);
let CieColorSpaceParams {
white_point,
black_point,
} = CieColorSpaceParams::from_dictionary(dict, objects)?;
let range = dict
.optional_array_of::<f32, 4>("Range", objects)?
.unwrap_or([-100.0, 100.0, -100.0, 100.0]);
Expand Down
1 change: 1 addition & 0 deletions crates/pdf-color-space/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
pub mod cal_gray_color_space;
pub mod cal_rgb_color_space;
mod cie_color_space;
pub mod color_space;
pub mod color_space_reader;
pub mod device_n_color_space;
Expand Down
8 changes: 3 additions & 5 deletions crates/pdf-font/src/fallback.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
use std::borrow::Cow;

use pdf_cmap::ToUnicodeCMap;
use pdf_object::{
dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver,
Expand All @@ -17,7 +15,7 @@ use crate::{
};

pub(crate) struct FallbackFontProgram {
pub(crate) font_file: Cow<'static, [u8]>,
pub(crate) font_file: &'static [u8],
pub(crate) standard14: Standard14Font,
pub(crate) flags: FontFlags,
}
Expand Down Expand Up @@ -120,8 +118,8 @@ fn fallback_program(
standard14: Standard14Font,
is_cjk: bool,
) -> FallbackFontProgram {
let font_file: Cow<'static, [u8]> = if is_cjk {
Cow::Borrowed(include_bytes!("../assets/NotoSansCJKjp-Regular.otf").as_slice())
let font_file = if is_cjk {
include_bytes!("../assets/NotoSansCJKjp-Regular.otf").as_slice()
} else {
standard14.fallback_font_bytes()
};
Expand Down
10 changes: 4 additions & 6 deletions crates/pdf-font/src/font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,9 +245,7 @@ mod tests {
use pdf_cmap::Type0EncodingCMap;

use super::*;
use crate::{
encoding::Encoding, flags::FontFlags, font_data::FontData, true_type_font::TrueTypeFont,
};
use crate::{encoding::Encoding, flags::FontFlags, true_type_font::TrueTypeFont};

#[test]
fn test_truetype_encoding_fallback() {
Expand All @@ -264,7 +262,7 @@ mod tests {
.collect();
let enc = Encoding { names };
let font = Font::TrueType(TrueTypeFont {
font_file: FontData::Owned(vec![]),
font_file: Vec::new().into(),
widths: None,
encoding: Some(enc),
to_unicode: None,
Expand All @@ -281,7 +279,7 @@ mod tests {
let cmap_data = b"beginbfchar\n<01> <FB01FB02>\nendbfchar\n";
let cmap = ToUnicodeCMap::try_from(cmap_data.as_slice()).unwrap();
let font = Font::TrueType(TrueTypeFont {
font_file: FontData::Owned(vec![]),
font_file: Vec::new().into(),
widths: None,
encoding: None,
to_unicode: Some(cmap),
Expand Down Expand Up @@ -336,7 +334,7 @@ mod tests {
program_format: Type0FontProgramFormat::TrueType {
cid_to_unicode: false,
},
font_file: FontData::Owned(vec![]),
font_file: Vec::new().into(),
type1_program_format: None,
widths: None,
encoding: None,
Expand Down
37 changes: 21 additions & 16 deletions crates/pdf-font/src/font_data.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
use std::{borrow::Cow, ops::Deref, sync::Arc};
use std::{ops::Deref, sync::Arc};

/// Storage for a parsed or synthesized font program.
#[derive(Debug, Clone)]
pub enum FontData {
/// Bundled font bytes with static storage duration.
Borrowed(&'static [u8]),
/// Independently owned font bytes.
Owned(Vec<u8>),
Static(&'static [u8]),
/// Font bytes shared with a decoded PDF stream.
Shared(SharedFontData),
}
Expand Down Expand Up @@ -39,8 +37,7 @@ impl Deref for FontData {

fn deref(&self) -> &Self::Target {
match self {
Self::Borrowed(data) => data,
Self::Owned(data) => data.as_slice(),
Self::Static(data) => data,
Self::Shared(data) => data.data.get(..data.visible_len).unwrap_or_default(),
}
}
Expand All @@ -54,22 +51,19 @@ impl AsRef<[u8]> for FontData {

impl From<Vec<u8>> for FontData {
fn from(data: Vec<u8>) -> Self {
Self::Owned(data)
Self::shared(Arc::new(data))
}
}

impl From<Arc<Vec<u8>>> for FontData {
fn from(data: Arc<Vec<u8>>) -> Self {
Self::shared(data)
impl From<&'static [u8]> for FontData {
fn from(data: &'static [u8]) -> Self {
Self::Static(data)
}
}

impl From<Cow<'static, [u8]>> for FontData {
fn from(data: Cow<'static, [u8]>) -> Self {
match data {
Cow::Borrowed(data) => Self::Borrowed(data),
Cow::Owned(data) => Self::Owned(data),
}
impl From<Arc<Vec<u8>>> for FontData {
fn from(data: Arc<Vec<u8>>) -> Self {
Self::shared(data)
}
}

Expand All @@ -89,4 +83,15 @@ mod tests {
let full = FontData::shared_prefix(data, usize::MAX);
assert_eq!(full.as_ref(), [1, 2, 3, 4]);
}

#[test]
fn vec_conversion_reuses_allocation() {
let data = vec![1, 2, 3, 4];
let original = data.as_ptr();

let font_data = FontData::from(data);

assert!(matches!(&font_data, FontData::Shared(_)));
assert_eq!(font_data.as_ptr(), original);
}
}
9 changes: 4 additions & 5 deletions crates/pdf-font/src/standard14.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
/// The PDF spec guarantees that viewers can render these 14 Type 1 fonts
/// without an embedded font program. When a document references one by
/// name alone we substitute a metrically-similar bundled TrueType font.
use std::{borrow::Cow, fmt};
use std::fmt;

use crate::flags::FontFlags;

Expand Down Expand Up @@ -170,8 +170,8 @@ impl Standard14Font {
}

/// Return bundled TrueType font bytes that serve as a visual substitute.
pub fn fallback_font_bytes(&self) -> Cow<'static, [u8]> {
let bytes: &'static [u8] = match self {
pub fn fallback_font_bytes(&self) -> &'static [u8] {
match self {
Self::Courier => include_bytes!("../assets/RobotoMono-Regular.ttf"),
Self::CourierBold => include_bytes!("../assets/RobotoMono-Bold.ttf"),
Self::CourierOblique => include_bytes!("../assets/RobotoMono-Italic.ttf"),
Expand All @@ -189,8 +189,7 @@ impl Standard14Font {
Self::TimesBold => include_bytes!("../assets/Roboto-Bold.ttf"),
Self::TimesItalic => include_bytes!("../assets/Roboto-Italic.ttf"),
Self::TimesBoldItalic => include_bytes!("../assets/Roboto-BoldItalic.ttf"),
};
Cow::Borrowed(bytes)
}
}
}

Expand Down
16 changes: 8 additions & 8 deletions crates/pdf-font/src/true_type_font.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use std::{borrow::Cow, collections::HashMap};
use std::collections::HashMap;

use pdf_cmap::ToUnicodeCMap;
use pdf_object::{
Expand Down Expand Up @@ -108,11 +108,11 @@ impl TrueTypeFont {
/// Creates a minimal `TrueTypeFont` from raw font bytes with no
/// widths or ToUnicode map.
///
/// Used for Standard 14 fallback fonts where the bundled bytes are
/// `Cow::Borrowed` (zero-copy from `include_bytes!`). Those fallback fonts
/// behave like simple Type 1 fonts, so they default to StandardEncoding
/// when the PDF omitted an explicit `/Encoding`.
pub fn from_bytes(font_file: Cow<'static, [u8]>, standard14: Option<Standard14Font>) -> Self {
/// Used for Standard 14 fallback fonts where the bundled bytes have static
/// storage duration. Those fallback fonts behave like simple Type 1 fonts,
/// so they default to StandardEncoding when the PDF omitted an explicit
/// `/Encoding`.
pub fn from_bytes(font_file: &'static [u8], standard14: Option<Standard14Font>) -> Self {
Self {
font_file: font_file.into(),
widths: None,
Expand Down Expand Up @@ -232,12 +232,12 @@ mod tests {
}

#[test]
fn bundled_font_program_borrows_static_bytes() {
fn bundled_font_program_uses_static_bytes() {
let fallback = Standard14Font::Helvetica.fallback_font_bytes();
let fallback_bytes = fallback.as_ptr();
let font = TrueTypeFont::from_bytes(fallback, Some(Standard14Font::Helvetica));

assert!(matches!(&font.font_file, FontData::Borrowed(_)));
assert!(matches!(&font.font_file, FontData::Static(_)));
assert_eq!(font.font_file.as_ptr(), fallback_bytes);
}

Expand Down
2 changes: 1 addition & 1 deletion crates/pdf-font/src/type1_font.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ currentfile eexec
let expected = build_cff_font(&cff_bytes).unwrap();
assert_eq!(format, Type1FontProgramFormat::OpenTypeCff);
assert_eq!(parsed.as_ref(), expected.as_slice());
assert!(matches!(parsed, FontData::Owned(_)));
assert!(matches!(parsed, FontData::Shared(_)));
}

#[test]
Expand Down
Loading