diff options
| author | Laurenz <laurmaedje@gmail.com> | 2021-03-20 20:19:30 +0100 |
|---|---|---|
| committer | Laurenz <laurmaedje@gmail.com> | 2021-03-20 20:19:30 +0100 |
| commit | 898728f260923a91444eb23b522d0abf01a4299b (patch) | |
| tree | 64fdd3f78e16e6428e765a8e2d99c3cd910bd9df /src/export | |
| parent | 6cb9fe9064a037224b6560b69b441b72e787fa94 (diff) | |
Square, circle and ellipse 🔵
Diffstat (limited to 'src/export')
| -rw-r--r-- | src/export/pdf.rs | 210 |
1 files changed, 121 insertions, 89 deletions
diff --git a/src/export/pdf.rs b/src/export/pdf.rs index d8391e2d..6881188d 100644 --- a/src/export/pdf.rs +++ b/src/export/pdf.rs @@ -15,8 +15,8 @@ use ttf_parser::{name_id, GlyphId}; use crate::color::Color; use crate::env::{Env, ImageResource, ResourceId}; -use crate::geom::Length; -use crate::layout::{Element, Fill, Frame, Shape}; +use crate::geom::{self, Length, Size}; +use crate::layout::{Element, Fill, Frame, Image, Shape}; /// Export a collection of frames into a _PDF_ document. /// @@ -134,63 +134,59 @@ impl<'a> PdfExporter<'a> { let mut face = FaceId::MAX; let mut size = Length::ZERO; let mut fill: Option<Fill> = None; - let mut change_color = |content: &mut Content, new_fill: Fill| { - if fill != Some(new_fill) { - match new_fill { - Fill::Color(Color::Rgba(c)) => { - content.fill_rgb( - c.r as f32 / 255.0, - c.g as f32 / 255.0, - c.b as f32 / 255.0, - ); - } - Fill::Image(_) => todo!(), - } - fill = Some(new_fill); - } - }; for (pos, element) in &page.elements { let x = pos.x.to_pt() as f32; + let y = (page.size.height - pos.y).to_pt() as f32; + match element { - Element::Image(image) => { - let name = format!("Im{}", self.images.map(image.res)); - let size = image.size; - let y = (page.size.height - pos.y - size.height).to_pt() as f32; - let w = size.width.to_pt() as f32; - let h = size.height.to_pt() as f32; + &Element::Image(Image { res, size: Size { width, height } }) => { + let name = format!("Im{}", self.images.map(res)); + let w = width.to_pt() as f32; + let h = height.to_pt() as f32; content.save_state(); - content.matrix(w, 0.0, 0.0, h, x, y); + content.matrix(w, 0.0, 0.0, h, x, y - h); content.x_object(Name(name.as_bytes())); content.restore_state(); } Element::Geometry(geometry) => { content.save_state(); - change_color(&mut content, geometry.fill); + write_fill(&mut content, geometry.fill); - match &geometry.shape { - Shape::Rect(r) => { - let w = r.width.to_pt() as f32; - let h = r.height.to_pt() as f32; - let y = (page.size.height - pos.y - r.height).to_pt() as f32; + match geometry.shape { + Shape::Rect(Size { width, height }) => { + let w = width.to_pt() as f32; + let h = height.to_pt() as f32; if w > 0.0 && h > 0.0 { - content.rect(x, y, w, h, false, true); + content.rect(x, y - h, w, h, false, true); } } + + Shape::Ellipse(size) => { + let path = geom::ellipse_path(size); + write_path(&mut content, x, y, &path, false, true); + } + + Shape::Path(ref path) => { + write_path(&mut content, x, y, path, false, true) + } } content.restore_state(); } Element::Text(shaped) => { - change_color(&mut content, shaped.color); + if fill != Some(shaped.color) { + write_fill(&mut content, shaped.color); + fill = Some(shaped.color); + } let mut text = content.text(); - // Then, also check if we need to - // issue a font switching action. + // Then, also check if we need to issue a font switching + // action. if shaped.face != face || shaped.font_size != size { face = shaped.face; size = shaped.font_size; @@ -199,8 +195,6 @@ impl<'a> PdfExporter<'a> { text.font(Name(name.as_bytes()), size.to_pt() as f32); } - let x = pos.x.to_pt() as f32; - let y = (page.size.height - pos.y).to_pt() as f32; text.matrix(1.0, 0.0, 0.0, 1.0, x, y); text.show(&shaped.encode_glyphs_be()); } @@ -365,6 +359,97 @@ impl<'a> PdfExporter<'a> { } } +/// Write a fill change into a content stream. +fn write_fill(content: &mut Content, fill: Fill) { + match fill { + Fill::Color(Color::Rgba(c)) => { + content.fill_rgb(c.r as f32 / 255.0, c.g as f32 / 255.0, c.b as f32 / 255.0); + } + Fill::Image(_) => todo!(), + } +} + +/// Write a path into a content stream. +fn write_path( + content: &mut Content, + x: f32, + y: f32, + path: &geom::Path, + stroke: bool, + fill: bool, +) { + let f = |length: Length| length.to_pt() as f32; + let mut builder = content.path(stroke, fill); + for elem in &path.0 { + match elem { + geom::PathElement::MoveTo(p) => builder.move_to(x + f(p.x), y + f(p.y)), + geom::PathElement::LineTo(p) => builder.line_to(x + f(p.x), y + f(p.y)), + geom::PathElement::CubicTo(p1, p2, p3) => builder.cubic_to( + x + f(p1.x), + y + f(p1.y), + x + f(p2.x), + y + f(p2.y), + x + f(p3.x), + y + f(p3.y), + ), + geom::PathElement::ClosePath => builder.close_path(), + }; + } +} + +/// The compression level for the deflating. +const DEFLATE_LEVEL: u8 = 6; + +/// Encode an image with a suitable filter. +/// +/// Skips the alpha channel as that's encoded separately. +fn encode_image(img: &ImageResource) -> ImageResult<(Vec<u8>, Filter, ColorSpace)> { + let mut data = vec![]; + let (filter, space) = match (img.format, &img.buf) { + // 8-bit gray JPEG. + (ImageFormat::Jpeg, DynamicImage::ImageLuma8(_)) => { + img.buf.write_to(&mut data, img.format)?; + (Filter::DctDecode, ColorSpace::DeviceGray) + } + + // 8-bit Rgb JPEG (Cmyk JPEGs get converted to Rgb earlier). + (ImageFormat::Jpeg, DynamicImage::ImageRgb8(_)) => { + img.buf.write_to(&mut data, img.format)?; + (Filter::DctDecode, ColorSpace::DeviceRgb) + } + + // TODO: Encode flate streams with PNG-predictor? + + // 8-bit gray PNG. + (ImageFormat::Png, DynamicImage::ImageLuma8(luma)) => { + data = deflate::compress_to_vec_zlib(&luma.as_raw(), DEFLATE_LEVEL); + (Filter::FlateDecode, ColorSpace::DeviceGray) + } + + // Anything else (including Rgb(a) PNGs). + (_, buf) => { + let (width, height) = buf.dimensions(); + let mut pixels = Vec::with_capacity(3 * width as usize * height as usize); + for (_, _, Rgba([r, g, b, _])) in buf.pixels() { + pixels.push(r); + pixels.push(g); + pixels.push(b); + } + + data = deflate::compress_to_vec_zlib(&pixels, DEFLATE_LEVEL); + (Filter::FlateDecode, ColorSpace::DeviceRgb) + } + }; + Ok((data, filter, space)) +} + +/// Encode an image's alpha channel if present. +fn encode_alpha(img: &ImageResource) -> (Vec<u8>, Filter) { + let pixels: Vec<_> = img.buf.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect(); + let data = deflate::compress_to_vec_zlib(&pixels, DEFLATE_LEVEL); + (data, Filter::FlateDecode) +} + /// We need to know exactly which indirect reference id will be used for which /// objects up-front to correctly declare the document catalogue, page tree and /// so on. These offsets are computed in the beginning and stored here. @@ -485,56 +570,3 @@ where self.to_layout.iter().copied() } } - -/// The compression level for the deflating. -const DEFLATE_LEVEL: u8 = 6; - -/// Encode an image with a suitable filter. -/// -/// Skips the alpha channel as that's encoded separately. -fn encode_image(img: &ImageResource) -> ImageResult<(Vec<u8>, Filter, ColorSpace)> { - let mut data = vec![]; - let (filter, space) = match (img.format, &img.buf) { - // 8-bit gray JPEG. - (ImageFormat::Jpeg, DynamicImage::ImageLuma8(_)) => { - img.buf.write_to(&mut data, img.format)?; - (Filter::DctDecode, ColorSpace::DeviceGray) - } - - // 8-bit Rgb JPEG (Cmyk JPEGs get converted to Rgb earlier). - (ImageFormat::Jpeg, DynamicImage::ImageRgb8(_)) => { - img.buf.write_to(&mut data, img.format)?; - (Filter::DctDecode, ColorSpace::DeviceRgb) - } - - // TODO: Encode flate streams with PNG-predictor? - - // 8-bit gray PNG. - (ImageFormat::Png, DynamicImage::ImageLuma8(luma)) => { - data = deflate::compress_to_vec_zlib(&luma.as_raw(), DEFLATE_LEVEL); - (Filter::FlateDecode, ColorSpace::DeviceGray) - } - - // Anything else (including Rgb(a) PNGs). - (_, buf) => { - let (width, height) = buf.dimensions(); - let mut pixels = Vec::with_capacity(3 * width as usize * height as usize); - for (_, _, Rgba([r, g, b, _])) in buf.pixels() { - pixels.push(r); - pixels.push(g); - pixels.push(b); - } - - data = deflate::compress_to_vec_zlib(&pixels, DEFLATE_LEVEL); - (Filter::FlateDecode, ColorSpace::DeviceRgb) - } - }; - Ok((data, filter, space)) -} - -/// Encode an image's alpha channel if present. -fn encode_alpha(img: &ImageResource) -> (Vec<u8>, Filter) { - let pixels: Vec<_> = img.buf.pixels().map(|(_, _, Rgba([_, _, _, a]))| a).collect(); - let data = deflate::compress_to_vec_zlib(&pixels, DEFLATE_LEVEL); - (data, Filter::FlateDecode) -} |
