Skip to content

Commit a1b9a7d

Browse files
codexVelli20
authored andcommitted
pdf: simplify shared color and font data
Extract common CIE white and black point parsing for CalGray, CalRGB, and Lab color spaces. Represent bundled fallback fonts as static slices and store them in a dedicated FontData variant without Cow. Co-authored-by: Codex <codex@openai.com>
1 parent 1a71090 commit a1b9a7d

12 files changed

Lines changed: 144 additions & 58 deletions

File tree

crates/pdf-canvas/src/truetype_font_renderer.rs

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -153,9 +153,7 @@ impl<B: CanvasBackend> TextRenderer for TrueTypeFontRenderer<'_, '_, B> {
153153

154154
#[cfg(test)]
155155
mod tests {
156-
use pdf_font::{
157-
encoding::Encoding, flags::FontFlags, font_data::FontData, standard14::Standard14Font,
158-
};
156+
use pdf_font::{encoding::Encoding, flags::FontFlags, standard14::Standard14Font};
159157

160158
use super::*;
161159

@@ -234,7 +232,7 @@ mod tests {
234232
#[test]
235233
fn unmappable_simple_truetype_returns_notdef() {
236234
let font = Font::TrueType(pdf_font::true_type_font::TrueTypeFont {
237-
font_file: FontData::Owned(vec![]),
235+
font_file: Vec::new().into(),
238236
widths: None,
239237
encoding: None,
240238
to_unicode: None,

crates/pdf-color-space/src/cal_gray_color_space.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use pdf_object::{
33
object_lookup::ObjectLookupExt, object_resolver::ObjectResolver, object_variant::ObjectVariant,
44
};
55

6-
use crate::{color_space::ColorSpace, error::ColorSpaceError};
6+
use crate::{
7+
cie_color_space::CieColorSpaceParams, color_space::ColorSpace, error::ColorSpaceError,
8+
};
79

810
/// Calibrated Gray color space.
911
///
@@ -30,10 +32,10 @@ pub(crate) fn parse_cal_gray_color_space(
3032
});
3133
};
3234
let dict = dict_obj.try_dictionary(objects)?;
33-
let white_point = dict.required_array_of::<f32, 3>("WhitePoint", objects)?;
34-
let black_point = dict
35-
.optional_array_of::<f32, 3>("BlackPoint", objects)?
36-
.unwrap_or([0.0, 0.0, 0.0]);
35+
let CieColorSpaceParams {
36+
white_point,
37+
black_point,
38+
} = CieColorSpaceParams::from_dictionary(dict, objects)?;
3739
let gamma = dict
3840
.optional_number::<f32>("Gamma", objects)?
3941
.unwrap_or(1.0);

crates/pdf-color-space/src/cal_rgb_color_space.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use pdf_object::object_lookup::ObjectLookupExt;
33
use pdf_object::{object_resolver::ObjectResolver, object_variant::ObjectVariant};
44

55
use crate::cal_gray_color_space::xyz_to_srgb;
6-
use crate::{color_space::ColorSpace, error::ColorSpaceError};
6+
use crate::{
7+
cie_color_space::CieColorSpaceParams, color_space::ColorSpace, error::ColorSpaceError,
8+
};
79

810
/// Calibrated RGB color space.
911
///
@@ -35,10 +37,10 @@ pub(crate) fn parse_cal_rgb_color_space(
3537
});
3638
};
3739
let dict = dict_obj.try_dictionary(objects)?;
38-
let white_point = dict.required_array_of::<f32, 3>("WhitePoint", objects)?;
39-
let black_point = dict
40-
.optional_array_of::<f32, 3>("BlackPoint", objects)?
41-
.unwrap_or_default();
40+
let CieColorSpaceParams {
41+
white_point,
42+
black_point,
43+
} = CieColorSpaceParams::from_dictionary(dict, objects)?;
4244
let gamma = dict
4345
.optional_array_of::<f32, 3>("Gamma", objects)?
4446
.unwrap_or([1.0, 1.0, 1.0]);
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
use pdf_object::{
2+
dictionary::Dictionary, error::ObjectError, object_lookup::ObjectLookupExt,
3+
object_resolver::ObjectResolver,
4+
};
5+
6+
/// Parameters shared by the CalGray, CalRGB, and Lab color spaces.
7+
#[derive(Debug, PartialEq)]
8+
pub(crate) struct CieColorSpaceParams {
9+
/// Reference white point in CIE XYZ coordinates `[Xw, Yw, Zw]`.
10+
pub(crate) white_point: [f32; 3],
11+
/// Reference black point in CIE XYZ coordinates `[Xb, Yb, Zb]`.
12+
///
13+
/// Defaults to `[0.0, 0.0, 0.0]` when `/BlackPoint` is absent.
14+
pub(crate) black_point: [f32; 3],
15+
}
16+
17+
impl CieColorSpaceParams {
18+
pub(crate) fn from_dictionary(
19+
dictionary: &Dictionary,
20+
objects: &dyn ObjectResolver,
21+
) -> Result<Self, ObjectError> {
22+
let white_point = dictionary.required_array_of::<f32, 3>("WhitePoint", objects)?;
23+
let black_point = dictionary
24+
.optional_array_of::<f32, 3>("BlackPoint", objects)?
25+
.unwrap_or_default();
26+
27+
Ok(Self {
28+
white_point,
29+
black_point,
30+
})
31+
}
32+
}
33+
34+
#[cfg(test)]
35+
mod tests {
36+
use std::collections::BTreeMap;
37+
38+
use pdf_object::{
39+
dictionary::Dictionary, object_resolver::PassthroughResolver, object_variant::ObjectVariant,
40+
};
41+
42+
use super::CieColorSpaceParams;
43+
44+
fn array(values: &[f64]) -> ObjectVariant {
45+
ObjectVariant::Array(values.iter().copied().map(ObjectVariant::Real).collect())
46+
}
47+
48+
#[test]
49+
fn parses_white_and_black_points() {
50+
let dictionary = Dictionary::new(BTreeMap::from([
51+
("BlackPoint".to_owned(), array(&[0.1, 0.2, 0.3])),
52+
("WhitePoint".to_owned(), array(&[0.9, 1.0, 0.8])),
53+
]));
54+
55+
let params =
56+
CieColorSpaceParams::from_dictionary(&dictionary, &PassthroughResolver).unwrap();
57+
58+
assert_eq!(
59+
params,
60+
CieColorSpaceParams {
61+
white_point: [0.9, 1.0, 0.8],
62+
black_point: [0.1, 0.2, 0.3],
63+
}
64+
);
65+
}
66+
67+
#[test]
68+
fn defaults_missing_black_point_to_zero() {
69+
let dictionary = Dictionary::new(BTreeMap::from([(
70+
"WhitePoint".to_owned(),
71+
array(&[0.9, 1.0, 0.8]),
72+
)]));
73+
74+
let params =
75+
CieColorSpaceParams::from_dictionary(&dictionary, &PassthroughResolver).unwrap();
76+
77+
assert_eq!(params.black_point, [0.0, 0.0, 0.0]);
78+
}
79+
}

crates/pdf-color-space/src/lab_color_space.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ use pdf_object::{
33
object_lookup::ObjectLookupExt, object_resolver::ObjectResolver, object_variant::ObjectVariant,
44
};
55

6-
use crate::{color_space::ColorSpace, error::ColorSpaceError};
6+
use crate::{
7+
cie_color_space::CieColorSpaceParams, color_space::ColorSpace, error::ColorSpaceError,
8+
};
79

810
/// CIE 1976 L*a*b* color space.
911
///
@@ -34,10 +36,10 @@ pub(crate) fn parse_lab_color_space(
3436
});
3537
};
3638
let dict = dict_obj.try_dictionary(objects)?;
37-
let white_point = dict.required_array_of::<f32, 3>("WhitePoint", objects)?;
38-
let black_point = dict
39-
.optional_array_of::<f32, 3>("BlackPoint", objects)?
40-
.unwrap_or([0.0, 0.0, 0.0]);
39+
let CieColorSpaceParams {
40+
white_point,
41+
black_point,
42+
} = CieColorSpaceParams::from_dictionary(dict, objects)?;
4143
let range = dict
4244
.optional_array_of::<f32, 4>("Range", objects)?
4345
.unwrap_or([-100.0, 100.0, -100.0, 100.0]);

crates/pdf-color-space/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
pub mod cal_gray_color_space;
22
pub mod cal_rgb_color_space;
3+
mod cie_color_space;
34
pub mod color_space;
45
pub mod color_space_reader;
56
pub mod device_n_color_space;

crates/pdf-font/src/fallback.rs

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,3 @@
1-
use std::borrow::Cow;
2-
31
use pdf_cmap::ToUnicodeCMap;
42
use pdf_object::{
53
dictionary::Dictionary, object_lookup::ObjectLookupExt, object_resolver::ObjectResolver,
@@ -17,7 +15,7 @@ use crate::{
1715
};
1816

1917
pub(crate) struct FallbackFontProgram {
20-
pub(crate) font_file: Cow<'static, [u8]>,
18+
pub(crate) font_file: &'static [u8],
2119
pub(crate) standard14: Standard14Font,
2220
pub(crate) flags: FontFlags,
2321
}
@@ -120,8 +118,8 @@ fn fallback_program(
120118
standard14: Standard14Font,
121119
is_cjk: bool,
122120
) -> FallbackFontProgram {
123-
let font_file: Cow<'static, [u8]> = if is_cjk {
124-
Cow::Borrowed(include_bytes!("../assets/NotoSansCJKjp-Regular.otf").as_slice())
121+
let font_file = if is_cjk {
122+
include_bytes!("../assets/NotoSansCJKjp-Regular.otf").as_slice()
125123
} else {
126124
standard14.fallback_font_bytes()
127125
};

crates/pdf-font/src/font.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -246,7 +246,7 @@ mod tests {
246246

247247
use super::*;
248248
use crate::{
249-
encoding::Encoding, flags::FontFlags, font_data::FontData, true_type_font::TrueTypeFont,
249+
encoding::Encoding, flags::FontFlags, true_type_font::TrueTypeFont,
250250
};
251251

252252
#[test]
@@ -264,7 +264,7 @@ mod tests {
264264
.collect();
265265
let enc = Encoding { names };
266266
let font = Font::TrueType(TrueTypeFont {
267-
font_file: FontData::Owned(vec![]),
267+
font_file: Vec::new().into(),
268268
widths: None,
269269
encoding: Some(enc),
270270
to_unicode: None,
@@ -281,7 +281,7 @@ mod tests {
281281
let cmap_data = b"beginbfchar\n<01> <FB01FB02>\nendbfchar\n";
282282
let cmap = ToUnicodeCMap::try_from(cmap_data.as_slice()).unwrap();
283283
let font = Font::TrueType(TrueTypeFont {
284-
font_file: FontData::Owned(vec![]),
284+
font_file: Vec::new().into(),
285285
widths: None,
286286
encoding: None,
287287
to_unicode: Some(cmap),
@@ -336,7 +336,7 @@ mod tests {
336336
program_format: Type0FontProgramFormat::TrueType {
337337
cid_to_unicode: false,
338338
},
339-
font_file: FontData::Owned(vec![]),
339+
font_file: Vec::new().into(),
340340
type1_program_format: None,
341341
widths: None,
342342
encoding: None,

crates/pdf-font/src/font_data.rs

Lines changed: 21 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,10 @@
1-
use std::{borrow::Cow, ops::Deref, sync::Arc};
1+
use std::{ops::Deref, sync::Arc};
22

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

4038
fn deref(&self) -> &Self::Target {
4139
match self {
42-
Self::Borrowed(data) => data,
43-
Self::Owned(data) => data.as_slice(),
40+
Self::Static(data) => data,
4441
Self::Shared(data) => data.data.get(..data.visible_len).unwrap_or_default(),
4542
}
4643
}
@@ -54,22 +51,19 @@ impl AsRef<[u8]> for FontData {
5451

5552
impl From<Vec<u8>> for FontData {
5653
fn from(data: Vec<u8>) -> Self {
57-
Self::Owned(data)
54+
Self::shared(Arc::new(data))
5855
}
5956
}
6057

61-
impl From<Arc<Vec<u8>>> for FontData {
62-
fn from(data: Arc<Vec<u8>>) -> Self {
63-
Self::shared(data)
58+
impl From<&'static [u8]> for FontData {
59+
fn from(data: &'static [u8]) -> Self {
60+
Self::Static(data)
6461
}
6562
}
6663

67-
impl From<Cow<'static, [u8]>> for FontData {
68-
fn from(data: Cow<'static, [u8]>) -> Self {
69-
match data {
70-
Cow::Borrowed(data) => Self::Borrowed(data),
71-
Cow::Owned(data) => Self::Owned(data),
72-
}
64+
impl From<Arc<Vec<u8>>> for FontData {
65+
fn from(data: Arc<Vec<u8>>) -> Self {
66+
Self::shared(data)
7367
}
7468
}
7569

@@ -89,4 +83,15 @@ mod tests {
8983
let full = FontData::shared_prefix(data, usize::MAX);
9084
assert_eq!(full.as_ref(), [1, 2, 3, 4]);
9185
}
86+
87+
#[test]
88+
fn vec_conversion_reuses_allocation() {
89+
let data = vec![1, 2, 3, 4];
90+
let original = data.as_ptr();
91+
92+
let font_data = FontData::from(data);
93+
94+
assert!(matches!(&font_data, FontData::Shared(_)));
95+
assert_eq!(font_data.as_ptr(), original);
96+
}
9297
}

crates/pdf-font/src/standard14.rs

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
/// The PDF spec guarantees that viewers can render these 14 Type 1 fonts
44
/// without an embedded font program. When a document references one by
55
/// name alone we substitute a metrically-similar bundled TrueType font.
6-
use std::{borrow::Cow, fmt};
6+
use std::fmt;
77

88
use crate::flags::FontFlags;
99

@@ -170,8 +170,8 @@ impl Standard14Font {
170170
}
171171

172172
/// Return bundled TrueType font bytes that serve as a visual substitute.
173-
pub fn fallback_font_bytes(&self) -> Cow<'static, [u8]> {
174-
let bytes: &'static [u8] = match self {
173+
pub fn fallback_font_bytes(&self) -> &'static [u8] {
174+
match self {
175175
Self::Courier => include_bytes!("../assets/RobotoMono-Regular.ttf"),
176176
Self::CourierBold => include_bytes!("../assets/RobotoMono-Bold.ttf"),
177177
Self::CourierOblique => include_bytes!("../assets/RobotoMono-Italic.ttf"),
@@ -189,8 +189,7 @@ impl Standard14Font {
189189
Self::TimesBold => include_bytes!("../assets/Roboto-Bold.ttf"),
190190
Self::TimesItalic => include_bytes!("../assets/Roboto-Italic.ttf"),
191191
Self::TimesBoldItalic => include_bytes!("../assets/Roboto-BoldItalic.ttf"),
192-
};
193-
Cow::Borrowed(bytes)
192+
}
194193
}
195194
}
196195

0 commit comments

Comments
 (0)