1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
use ::image::{DynamicImage, GenericImageView};
use miniz_oxide::deflate::{CompressionLevel, compress_to_vec_zlib};
use pdf_writer::Filter;
use utils::mm_to_pt;
use crate::{image::Image, *};
use super::svg::Svg;
const INCH_TO_MM: f32 = 25.4;
/// An image element that can render both pixel images and SVG graphics.
///
/// The image is automatically scaled to fit the available width while maintaining
/// aspect ratio. Supports various image formats through the `Image` enum.
pub struct ImageElement<'a> {
/// Reference to the image data (pixel or SVG)
pub image: &'a Image,
}
impl<'a> Element for ImageElement<'a> {
fn first_location_usage(&self, ctx: FirstLocationUsageCtx) -> FirstLocationUsage {
match self.image {
Image::Svg(svg) => Svg { data: svg }.first_location_usage(ctx),
Image::Pixel(image) => {
let (height, _) = calculate_size(image, ctx.width);
if ctx.break_appropriate_for_min_height(height) {
FirstLocationUsage::WillSkip
} else {
FirstLocationUsage::WillUse
}
}
}
}
fn measure(&self, mut ctx: MeasureCtx) -> ElementSize {
match self.image {
Image::Svg(svg) => Svg { data: svg }.measure(ctx),
Image::Pixel(image) => {
let (height, element_size) = calculate_size(image, ctx.width);
ctx.break_if_appropriate_for_min_height(height);
element_size
}
}
}
fn draw(&self, mut ctx: DrawCtx) -> ElementSize {
match self.image {
Image::Svg(svg) => Svg { data: svg }.draw(ctx),
Image::Pixel(image) => {
let (height, element_size) = calculate_size(image, ctx.width);
ctx.break_if_appropriate_for_min_height(height);
// a bit of a copy-paste from
// https://github.com/typst/pdf-writer/blob/main/examples/image.rs
// Define some indirect reference ids we'll use.
let image_id = ctx.pdf.alloc();
let s_mask_id = ctx.pdf.alloc();
let image_name = ctx.pdf.pages[ctx.location.page_idx].add_x_object(image_id);
let dynamic = image;
// Now, there are multiple considerations:
// - Writing an XObject with just the raw samples would work, but lead to
// huge file sizes since the image would be embedded without any
// compression.
// - We can encode the samples with a filter. However, which filter is best
// depends on the file format. For example, for JPEGs you should use
// DCT-Decode and for PNGs you should use Deflate.
// - When the image has transparency, we need to provide that separately
// through an extra linked SMask image.
let level = CompressionLevel::DefaultLevel as u8;
let encoded = compress_to_vec_zlib(dynamic.to_rgb8().as_raw(), level);
// If there's an alpha channel, extract the pixel alpha values.
let mask = dynamic.color().has_alpha().then(|| {
let alphas: Vec<_> = dynamic.pixels().map(|p| (p.2).0[3]).collect();
compress_to_vec_zlib(&alphas, level)
});
let (filter, encoded, mask) = (Filter::FlateDecode, encoded, mask);
// Write the stream for the image we want to embed.
let mut image = ctx.pdf.pdf.image_xobject(image_id, &encoded);
image.filter(filter);
image.width(dynamic.width() as i32);
image.height(dynamic.height() as i32);
image.color_space().device_rgb();
image.bits_per_component(8);
if mask.is_some() {
image.s_mask(s_mask_id);
}
drop(image);
// Add SMask if the image has transparency.
if let Some(encoded) = &mask {
let mut s_mask = ctx.pdf.pdf.image_xobject(s_mask_id, encoded);
s_mask.filter(filter);
s_mask.width(dynamic.width() as i32);
s_mask.height(dynamic.height() as i32);
s_mask.color_space().device_gray();
s_mask.bits_per_component(8);
}
ctx.location
.layer(ctx.pdf)
.save_state()
.transform([
mm_to_pt(element_size.width.unwrap()),
0.,
0.,
mm_to_pt(element_size.height.unwrap()),
mm_to_pt(ctx.location.pos.0),
mm_to_pt(ctx.location.pos.1 - element_size.height.unwrap()),
])
.x_object(Name(image_name.as_bytes()))
.restore_state();
element_size
}
}
}
}
#[inline]
fn calculate_size(image: &DynamicImage, width: WidthConstraint) -> (f32, ElementSize) {
let dimensions = {
let (x, y) = image.dimensions();
(x as f32 * INCH_TO_MM, y as f32 * INCH_TO_MM)
};
let width = width.constrain(dimensions.0);
let size = (width, dimensions.1 * width / dimensions.0);
(
size.1,
ElementSize {
width: Some(size.0),
height: Some(size.1),
},
)
}