summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMartin Haug <mhaug@live.de>2023-04-20 11:23:03 +0200
committerGitHub <noreply@github.com>2023-04-20 11:23:03 +0200
commit2a682f0008b91e7c33c6e65b3ecfc690268ab405 (patch)
tree1af18b7de6230bafce1a2e7eaff20f614b88f7df /src
parent4524539c2bc5f3a9f53bc57a1902264fc894969b (diff)
Add alt text to image function and PDF (#823)
Diffstat (limited to 'src')
-rw-r--r--src/export/pdf/page.rs16
-rw-r--r--src/export/render.rs2
-rw-r--r--src/image.rs78
3 files changed, 56 insertions, 40 deletions
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<Repr>);
-
-/// 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<DecodedImage>,
+ /// A text describing the image.
+ alt: Option<EcoString>,
}
impl Image {
/// Create an image from a buffer and a format.
- pub fn new(data: Buffer, format: ImageFormat) -> StrResult<Self> {
- 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<EcoString>,
+ ) -> StrResult<Self> {
+ 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<dyn World>,
fallback_family: Option<&str>,
+ alt: Option<EcoString>,
) -> StrResult<Self> {
- 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<Image> {
+fn decode_raster(data: &Buffer, format: RasterFormat) -> StrResult<Arc<DecodedImage>> {
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<Image> {
+fn decode_svg(data: &Buffer) -> StrResult<Arc<DecodedImage>> {
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<dyn World>,
fallback_family: Option<&str>,
-) -> StrResult<Image> {
+) -> StrResult<Arc<DecodedImage>> {
// 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.