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