Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 53 additions & 11 deletions crates/conversion/vec2svg/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ pub trait NotifyPaint {
fn notify_paint(&mut self, url_ref: ImmutStr) -> (u8, Fingerprint, Option<Transform>);
}

pub trait NotifyImage {
fn notify_image(&mut self, image: &Arc<ir::Image>) -> Fingerprint;
}

pub trait DynExportFeature {
fn should_render_text_element(&self) -> bool;

Expand Down Expand Up @@ -434,6 +438,7 @@ impl<C: BuildClipPath> TransformContext<C> for SvgTextBuilder {
impl<
'm,
C: NotifyPaint
+ NotifyImage
+ RenderVm<'m, Resultant = Arc<SvgTextNode>>
+ FontIndice<'m>
+ DynExportFeature,
Expand Down Expand Up @@ -539,8 +544,21 @@ impl<
self.content.push(content);
}

fn render_image(&mut self, _ctx: &mut C, image_item: &ir::ImageItem) {
self.content.push(render_image_item(image_item))
fn render_image(&mut self, ctx: &mut C, image_item: &ir::ImageItem) {
if matches!(
image_item.image.format.as_ref(),
"png" | "jpeg" | "gif" | "webp"
) {
let image_id = ctx.notify_image(&image_item.image);
self.content.push(render_image_item(image_item, image_id));
} else {
self.content.push(SvgText::Plain(render_image(
&image_item.image,
image_item.size,
true,
"",
)));
}
}

fn render_content_hint(&mut self, _ctx: &mut C, ch: char) {
Expand Down Expand Up @@ -762,8 +780,30 @@ fn render_path(

/// Render a [`ir::ImageItem`] into svg text.
#[comemo::memoize]
fn render_image_item(img: &ir::ImageItem) -> SvgText {
SvgText::Plain(render_image(&img.image, img.size, true, ""))
fn render_image_item(img: &ir::ImageItem, image_id: Fingerprint) -> SvgText {
let styles = image_attrs(&img.image);
let w = img.size.x.0;
let h = img.size.y.0;
let id = image_id.as_svg_id("i");
SvgText::Plain(format!(
r##"<use class="typst-image" href="#{id}" xlink:href="#{id}" transform="scale({w} {h})"{styles}/>"##,
))
}

fn image_attrs(image: &ir::Image) -> String {
image
.attrs
.iter()
.map(|attr| match attr {
ir::ImageAttr::Alt(alt) => {
format!(r#" alt="{}""#, escape::escape_str::<AttributeEscapes>(alt))
}
ir::ImageAttr::ImageRendering(rendering) => {
format!(r#" image-rendering="{rendering}""#)
}
})
.collect::<Vec<_>>()
.join(" ")
}

/// Render a raster or SVG image into svg text.
Expand All @@ -774,13 +814,7 @@ fn render_image_item(img: &ir::ImageItem) -> SvgText {
pub fn render_image(image: &ir::Image, size: Size, is_image_elem: bool, style: &str) -> String {
let image_url = embed_as_image_url(image).unwrap();

let styles = image.attrs.iter().map(|attr| match attr {
ir::ImageAttr::Alt(alt) => {
format!(r#" alt="{}""#, escape::escape_str::<AttributeEscapes>(alt))
}
ir::ImageAttr::ImageRendering(rendering) => format!(r#" image-rendering="{rendering}""#),
});
let styles = styles.collect::<Vec<_>>().join(" ");
let styles = image_attrs(image);

let w = size.x.0;
let h = size.y.0;
Expand All @@ -803,6 +837,14 @@ fn embed_as_image_url(image: &ir::Image) -> Option<String> {
Some(data)
}

pub(crate) fn render_image_def(id: Fingerprint, image: &ir::Image) -> SvgText {
let image_url = embed_as_image_url(image).unwrap();
SvgText::Plain(format!(
r#"<image id="{}" width="1" height="1" xlink:href="{image_url}" preserveAspectRatio="none"/>"#,
id.as_svg_id("i"),
))
}

fn glyph_aspect_ratio(font: &FontItem, glyph: u32) -> Option<f32> {
let (width, height) = match font.get_glyph(glyph)?.as_ref() {
ir::FlatGlyphItem::Outline(outline) => {
Expand Down
17 changes: 14 additions & 3 deletions crates/conversion/vec2svg/src/frontend/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ use std::{
};

use reflexo::{
hash::{item_hash128, Fingerprint, FingerprintBuilder},
hash::{hash128, item_hash128, Fingerprint, FingerprintBuilder},
vector::{
ir::{
self, FontIndice, FontRef, GroupRef, ImmutStr, Module, PathItem, Scalar, TextItem,
Expand All @@ -15,9 +15,11 @@ use reflexo::{
};
use reflexo_typst2vec::ir::Axes;

use super::{GradientDefMap, GradientDefRef};
use super::{GradientDefMap, GradientDefRef, ImageDefMap};
use crate::{
backend::{BuildClipPath, DynExportFeature, NotifyPaint, SvgTextBuilder, SvgTextNode},
backend::{
BuildClipPath, DynExportFeature, NotifyImage, NotifyPaint, SvgTextBuilder, SvgTextNode,
},
ExportFeature,
};

Expand Down Expand Up @@ -53,6 +55,7 @@ pub struct RenderContext<'m, 't, Feat: ExportFeature> {
pub(crate) gradients: &'t mut GradientDefMap,
/// Stores the patterns used in the document.
pub(crate) patterns: &'t mut PaintFillMap,
pub(crate) images: &'t mut ImageDefMap,

/// See [`ExportFeature`].
pub should_render_text_element: bool,
Expand Down Expand Up @@ -177,6 +180,14 @@ impl<Feat: ExportFeature> NotifyPaint for RenderContext<'_, '_, Feat> {
}
}

impl<Feat: ExportFeature> NotifyImage for RenderContext<'_, '_, Feat> {
fn notify_image(&mut self, image: &Arc<ir::Image>) -> Fingerprint {
let id = Fingerprint::from_u128(hash128(&(image.format.as_ref(), image.data.as_ref())));
self.images.entry(id).or_insert_with(|| image.clone());
id
}
}

/// Example of how to implement a FlatRenderVm.
impl<'m, Feat: ExportFeature> RenderVm<'m> for RenderContext<'m, '_, Feat> {
// type Resultant = String;
Expand Down
16 changes: 15 additions & 1 deletion crates/conversion/vec2svg/src/frontend/incremental.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ use reflexo_typst2vec::{
};

use crate::{
backend::{SvgText, SvgTextNode},
backend::{render_image_def, SvgText, SvgTextNode},
ExportFeature, SvgExporter, SvgTask,
};

Expand Down Expand Up @@ -230,6 +230,20 @@ impl IncrSvgDocClient {
IncrExporter::patterns(patterns.into_iter(), &mut svg);
svg.push("</defs>".into());

if !t.images.is_empty() {
svg.push(r#"<defs class="image">"#.into());
let mut images = std::mem::take(&mut t.images)
.into_iter()
.collect::<Vec<_>>();
images.sort_by_key(|(id, _)| *id);
svg.extend(
images
.into_iter()
.map(|(id, image)| render_image_def(id, &image)),
);
svg.push("</defs>".into());
}

IncrExporter::style_defs(t.style_defs, &mut svg);

// body
Expand Down
Loading