reflexo_typst2vec/
convert.rs

1use std::sync::Arc;
2
3use image::codecs::png::PngEncoder;
4use image::ImageEncoder;
5pub use reflexo::vector::ir::*;
6
7use reflexo::hash::{item_hash128, Fingerprint};
8use reflexo::typst::Bytes;
9use typst::foundations::Smart;
10use typst::layout::{
11    Abs as TypstAbs, Angle as TypstAngle, Axes as TypstAxes, Point as TypstPoint,
12    Ratio as TypstRatio, Transform as TypstTransform,
13};
14use typst::text::Font;
15use typst::utils::Scalar as TypstScalar;
16use typst::visualize::{ExchangeFormat, ImageFormat, ImageKind, RasterFormat, VectorFormat};
17
18use crate::hash::typst_affinite_hash;
19use crate::{FromTypst, IntoTypst, TryFromTypst};
20
21pub trait ImageExt {
22    fn data(&self) -> &Bytes;
23}
24
25impl ImageExt for typst::visualize::Image {
26    fn data(&self) -> &Bytes {
27        match self.kind() {
28            typst::visualize::ImageKind::Raster(raster_image) => raster_image.data(),
29            typst::visualize::ImageKind::Svg(svg_image) => svg_image.data(),
30        }
31    }
32}
33
34impl FromTypst<Rgba8Item> for typst::visualize::Color {
35    fn from_typst(v: Rgba8Item) -> Self {
36        typst::visualize::Color::from_u8(v.r, v.g, v.b, v.a)
37    }
38}
39
40impl FromTypst<typst::visualize::ColorSpace> for ColorSpace {
41    fn from_typst(value: typst::visualize::ColorSpace) -> Self {
42        use typst::visualize::ColorSpace::*;
43        match value {
44            Oklab => Self::Oklab,
45            Oklch => Self::Oklch,
46            Srgb => Self::Srgb,
47            D65Gray => Self::D65Gray,
48            LinearRgb => Self::LinearRgb,
49            Hsl => Self::Hsl,
50            Hsv => Self::Hsv,
51            Cmyk => Self::Cmyk,
52        }
53    }
54}
55
56impl TryFromTypst<ColorSpace> for typst::visualize::ColorSpace {
57    type Error = ();
58
59    fn try_from_typst(value: ColorSpace) -> Result<Self, ()> {
60        use typst::visualize::ColorSpace::*;
61        Ok(match value {
62            ColorSpace::Luma => return Err(()),
63            ColorSpace::Oklab => Oklab,
64            ColorSpace::Oklch => Oklch,
65            ColorSpace::Srgb => Srgb,
66            ColorSpace::D65Gray => D65Gray,
67            ColorSpace::LinearRgb => LinearRgb,
68            ColorSpace::Hsl => Hsl,
69            ColorSpace::Hsv => Hsv,
70            ColorSpace::Cmyk => Cmyk,
71        })
72    }
73}
74
75impl FromTypst<TypstScalar> for Scalar {
76    fn from_typst(scalar: TypstScalar) -> Self {
77        Self(scalar.get() as f32)
78    }
79}
80
81impl FromTypst<Scalar> for TypstScalar {
82    fn from_typst(scalar: Scalar) -> Self {
83        <TypstScalar as std::convert::From<f64>>::from(scalar.0 as f64)
84    }
85}
86
87impl FromTypst<TypstRatio> for Scalar {
88    fn from_typst(ratio: TypstRatio) -> Self {
89        Self(ratio.get() as f32)
90    }
91}
92
93impl FromTypst<TypstAbs> for Scalar {
94    fn from_typst(ratio: TypstAbs) -> Self {
95        Self(ratio.to_pt() as f32)
96    }
97}
98
99impl FromTypst<TypstAngle> for Scalar {
100    fn from_typst(scalar: TypstAngle) -> Self {
101        Self(scalar.to_rad() as f32)
102    }
103}
104
105impl<U, T> FromTypst<TypstAxes<U>> for Axes<T>
106where
107    U: IntoTypst<T>,
108{
109    fn from_typst(typst_axes: TypstAxes<U>) -> Self {
110        Self {
111            x: typst_axes.x.into_typst(),
112            y: typst_axes.y.into_typst(),
113        }
114    }
115}
116
117impl<T, U> FromTypst<Axes<T>> for TypstAxes<U>
118where
119    T: IntoTypst<U>,
120{
121    fn from_typst(axes: Axes<T>) -> Self {
122        Self {
123            x: axes.x.into_typst(),
124            y: axes.y.into_typst(),
125        }
126    }
127}
128
129impl FromTypst<TypstPoint> for Point {
130    fn from_typst(p: TypstPoint) -> Self {
131        Self {
132            x: p.x.into_typst(),
133            y: p.y.into_typst(),
134        }
135    }
136}
137
138impl FromTypst<TypstTransform> for Transform {
139    fn from_typst(typst_transform: TypstTransform) -> Self {
140        Self {
141            sx: typst_transform.sx.into_typst(),
142            ky: typst_transform.ky.into_typst(),
143            kx: typst_transform.kx.into_typst(),
144            sy: typst_transform.sy.into_typst(),
145            tx: typst_transform.tx.into_typst(),
146            ty: typst_transform.ty.into_typst(),
147        }
148    }
149}
150
151impl FromTypst<Font> for FontItem {
152    fn from_typst(font: Font) -> Self {
153        let hash = reflexo::hash::hash32(&font);
154        let fingerprint = Fingerprint::from_u128(item_hash128(&font));
155
156        let metrics = font.metrics();
157        Self {
158            fingerprint,
159            hash,
160            family: font.info().family.clone().into(),
161            cap_height: Scalar(metrics.cap_height.get() as f32),
162            ascender: Scalar(metrics.ascender.get() as f32),
163            descender: Scalar(metrics.descender.get() as f32),
164            units_per_em: Scalar(font.units_per_em() as f32),
165            vertical: false, // todo: check vertical
166            glyphs: Vec::new(),
167            glyph_cov: bitvec::vec::BitVec::new(),
168        }
169    }
170}
171
172/// Collect image data from [`typst::visualize::Image`].
173impl FromTypst<typst::visualize::Image> for Image {
174    fn from_typst(image: typst::visualize::Image) -> Self {
175        let format = match image.format() {
176            ImageFormat::Raster(RasterFormat::Exchange(ExchangeFormat::Jpg)) => "jpeg",
177            ImageFormat::Raster(RasterFormat::Exchange(ExchangeFormat::Png)) => "png",
178            ImageFormat::Raster(RasterFormat::Exchange(ExchangeFormat::Gif)) => "gif",
179            ImageFormat::Raster(RasterFormat::Pixel(..)) => "png",
180            ImageFormat::Vector(VectorFormat::Svg) => "svg+xml",
181        };
182        let attrs = {
183            let mut attrs = Vec::new();
184            if let Some(alt) = image.alt() {
185                attrs.push(ImageAttr::Alt(alt.into()));
186            }
187
188            if let Smart::Custom(rendering) = image.scaling() {
189                attrs.push(ImageAttr::ImageRendering(match rendering {
190                    typst::visualize::ImageScaling::Pixelated => "pixelated".into(),
191                    typst::visualize::ImageScaling::Smooth => "smooth".into(),
192                }));
193            }
194
195            attrs
196        };
197
198        let (hash, data) = encode_image(&image);
199        Image {
200            data,
201            format: format.into(),
202            size: Axes::new(image.width() as u32, image.height() as u32),
203            hash,
204            attrs,
205        }
206    }
207}
208
209#[comemo::memoize]
210fn encode_image(image: &typst::visualize::Image) -> (Fingerprint, Arc<[u8]>) {
211    // steal prehash from [`typst::image::Image`]
212    let hash = Fingerprint::from_u128(typst_affinite_hash(&image));
213
214    let data = match image.kind() {
215        ImageKind::Raster(raster) => match raster.format() {
216            RasterFormat::Exchange(..) => raster.data().as_slice().into(),
217            RasterFormat::Pixel(_) => {
218                let mut buf = vec![];
219                let mut encoder = PngEncoder::new(&mut buf);
220                if let Some(icc_profile) = raster.icc() {
221                    encoder.set_icc_profile(icc_profile.to_vec()).ok();
222                }
223                raster.dynamic().write_with_encoder(encoder).unwrap();
224                buf.as_slice().into()
225            }
226        },
227        ImageKind::Svg(svg) => svg.data().as_slice().into(),
228    };
229
230    (hash, data)
231}