From 2a682f0008b91e7c33c6e65b3ecfc690268ab405 Mon Sep 17 00:00:00 2001 From: Martin Haug Date: Thu, 20 Apr 2023 11:23:03 +0200 Subject: Add alt text to image function and PDF (#823) --- src/export/pdf/page.rs | 16 ++++++++++- src/export/render.rs | 2 +- src/image.rs | 78 ++++++++++++++++++++++++++------------------------ 3 files changed, 56 insertions(+), 40 deletions(-) (limited to 'src') diff --git a/src/export/pdf/page.rs b/src/export/pdf/page.rs index d6ead124..94650a3f 100644 --- a/src/export/pdf/page.rs +++ b/src/export/pdf/page.rs @@ -483,7 +483,21 @@ fn write_image(ctx: &mut PageContext, x: f32, y: f32, image: &Image, size: Size) let h = size.y.to_f32(); ctx.content.save_state(); ctx.content.transform([w, 0.0, 0.0, -h, x, y + h]); - ctx.content.x_object(Name(name.as_bytes())); + + if let Some(alt) = image.alt() { + let mut image_span = + ctx.content.begin_marked_content_with_properties(Name(b"Span")); + let mut image_alt = image_span.properties_direct(); + image_alt.pair(Name(b"Alt"), pdf_writer::Str(alt.as_bytes())); + image_alt.finish(); + image_span.finish(); + + ctx.content.x_object(Name(name.as_bytes())); + ctx.content.end_marked_content(); + } else { + ctx.content.x_object(Name(name.as_bytes())); + } + ctx.content.restore_state(); } diff --git a/src/export/render.rs b/src/export/render.rs index 3c2cea8d..b3a8f5ce 100644 --- a/src/export/render.rs +++ b/src/export/render.rs @@ -238,7 +238,7 @@ fn render_bitmap_glyph( let size = text.size.to_f32(); let ppem = size * ts.sy; let raster = text.font.ttf().glyph_raster_image(id, ppem as u16)?; - let image = Image::new(raster.data.into(), raster.format.into()).ok()?; + let image = Image::new(raster.data.into(), raster.format.into(), None).ok()?; // FIXME: Vertical alignment isn't quite right for Apple Color Emoji, // and maybe also for Noto Color Emoji. And: Is the size calculation diff --git a/src/image.rs b/src/image.rs index 09aaf24d..49d91908 100644 --- a/src/image.rs +++ b/src/image.rs @@ -17,25 +17,30 @@ use crate::World; /// /// Values of this type are cheap to clone and hash. #[derive(Clone)] -pub struct Image(Arc); - -/// The internal representation. -struct Repr { +pub struct Image { /// The raw, undecoded image data. data: Buffer, /// The format of the encoded `buffer`. format: ImageFormat, /// The decoded image. - decoded: DecodedImage, + decoded: Arc, + /// A text describing the image. + alt: Option, } impl Image { /// Create an image from a buffer and a format. - pub fn new(data: Buffer, format: ImageFormat) -> StrResult { - match format { - ImageFormat::Raster(format) => decode_raster(data, format), - ImageFormat::Vector(VectorFormat::Svg) => decode_svg(data), - } + pub fn new( + data: Buffer, + format: ImageFormat, + alt: Option, + ) -> StrResult { + let decoded = match format { + ImageFormat::Raster(format) => decode_raster(&data, format)?, + ImageFormat::Vector(VectorFormat::Svg) => decode_svg(&data)?, + }; + + Ok(Self { data, format, decoded, alt }) } /// Create a font-dependant image from a buffer and a format. @@ -44,28 +49,31 @@ impl Image { format: ImageFormat, world: Tracked, fallback_family: Option<&str>, + alt: Option, ) -> StrResult { - match format { - ImageFormat::Raster(format) => decode_raster(data, format), + let decoded = match format { + ImageFormat::Raster(format) => decode_raster(&data, format)?, ImageFormat::Vector(VectorFormat::Svg) => { - decode_svg_with_fonts(data, world, fallback_family) + decode_svg_with_fonts(&data, world, fallback_family)? } - } + }; + + Ok(Self { data, format, decoded, alt }) } /// The raw image data. pub fn data(&self) -> &Buffer { - &self.0.data + &self.data } /// The format of the image. pub fn format(&self) -> ImageFormat { - self.0.format + self.format } /// The decoded version of the image. pub fn decoded(&self) -> &DecodedImage { - &self.0.decoded + &self.decoded } /// The width of the image in pixels. @@ -77,6 +85,11 @@ impl Image { pub fn height(&self) -> u32 { self.decoded().height() } + + /// A text describing the image. + pub fn alt(&self) -> Option<&str> { + self.alt.as_deref() + } } impl Debug for Image { @@ -85,6 +98,7 @@ impl Debug for Image { .field("format", &self.format()) .field("width", &self.width()) .field("height", &self.height()) + .field("alt", &self.alt()) .finish() } } @@ -183,38 +197,30 @@ impl DecodedImage { /// Decode a raster image. #[comemo::memoize] -fn decode_raster(data: Buffer, format: RasterFormat) -> StrResult { +fn decode_raster(data: &Buffer, format: RasterFormat) -> StrResult> { let cursor = io::Cursor::new(&data); let reader = image::io::Reader::with_format(cursor, format.into()); let dynamic = reader.decode().map_err(format_image_error)?; - Ok(Image(Arc::new(Repr { - data, - format: ImageFormat::Raster(format), - decoded: DecodedImage::Raster(dynamic, format), - }))) + Ok(Arc::new(DecodedImage::Raster(dynamic, format))) } /// Decode an SVG image. #[comemo::memoize] -fn decode_svg(data: Buffer) -> StrResult { +fn decode_svg(data: &Buffer) -> StrResult> { let opts = usvg::Options::default(); - let tree = usvg::Tree::from_data(&data, &opts.to_ref()).map_err(format_usvg_error)?; - Ok(Image(Arc::new(Repr { - data, - format: ImageFormat::Vector(VectorFormat::Svg), - decoded: DecodedImage::Svg(tree), - }))) + let tree = usvg::Tree::from_data(data, &opts.to_ref()).map_err(format_usvg_error)?; + Ok(Arc::new(DecodedImage::Svg(tree))) } /// Decode an SVG image with access to fonts. #[comemo::memoize] fn decode_svg_with_fonts( - data: Buffer, + data: &Buffer, world: Tracked, fallback_family: Option<&str>, -) -> StrResult { +) -> StrResult> { // Parse XML. - let xml = std::str::from_utf8(&data) + let xml = std::str::from_utf8(data) .map_err(|_| format_usvg_error(usvg::Error::NotAnUtf8Str))?; let document = roxmltree::Document::parse(xml) .map_err(|err| format_xml_like_error("svg", err))?; @@ -239,11 +245,7 @@ fn decode_svg_with_fonts( let tree = usvg::Tree::from_xmltree(&document, &opts.to_ref()).map_err(format_usvg_error)?; - Ok(Image(Arc::new(Repr { - data, - format: ImageFormat::Vector(VectorFormat::Svg), - decoded: DecodedImage::Svg(tree), - }))) + Ok(Arc::new(DecodedImage::Svg(tree))) } /// Discover and load the fonts referenced by an SVG. -- cgit v1.2.3