1#![forbid(unsafe_code)]
17#![deny(missing_docs)]
18
19use crate::clip::CachedClipPath;
20use crate::glyph::{CachedOutlineGlyph, CachedType3Glyph};
21use crate::mask::MaskKind;
22use crate::paint::{CachedShading, CachedShadingPattern, CachedTilingPattern};
23use hayro_interpret::font::Glyph;
24use hayro_interpret::hayro_syntax::page::Page;
25use hayro_interpret::util::{Float32Ext, PageExt};
26use hayro_interpret::{
27 BlendMode, CacheKey, ClipPath, Context, Device, GlyphDrawMode, Image, InterpreterSettings,
28 Paint, PathDrawMode, SoftMask, StrokeProps, interpret_page,
29};
30use kurbo::{Affine, BezPath, Cap, Join, Rect};
31use siphasher::sip128::{Hasher128, SipHasher13};
32use std::collections::HashMap;
33use std::fmt;
34use std::fmt::{Display, Formatter};
35use std::hash::Hash;
36use xmlwriter::{Options, XmlWriter};
37
38pub use hayro_interpret;
39pub use hayro_interpret::hayro_syntax;
40
41mod clip;
42mod glyph;
43pub(crate) mod image;
44mod mask;
45pub(crate) mod paint;
46mod path;
47
48pub fn convert(page: &Page<'_>, interpreter_settings: &InterpreterSettings) -> String {
50 let mut state = Context::new(
51 page.initial_transform(true),
52 Rect::new(
53 0.0,
54 0.0,
55 page.render_dimensions().0 as f64,
56 page.render_dimensions().1 as f64,
57 ),
58 page.xref(),
59 interpreter_settings.clone(),
60 );
61 let mut device = SvgRenderer::new(page);
62 device.write_header(page.render_dimensions());
63
64 interpret_page(page, &mut state, &mut device);
65
66 device.finish()
67}
68
69pub(crate) struct SvgRenderer<'a> {
70 pub(crate) xml: XmlWriter,
71 pub(crate) outline_glyphs: Deduplicator<CachedOutlineGlyph>,
72 pub(crate) type3_glyphs: Deduplicator<CachedType3Glyph<'a>>,
73 pub(crate) clip_paths: Deduplicator<CachedClipPath>,
74 pub(crate) masks: Deduplicator<MaskKind<'a>>,
75 pub(crate) shadings: Deduplicator<CachedShading>,
76 pub(crate) shading_patterns: Deduplicator<CachedShadingPattern>,
77 pub(crate) tiling_patterns: Deduplicator<CachedTilingPattern<'a>>,
78 pub(crate) dimensions: (f32, f32),
79 pub(crate) cur_mask: Option<SoftMask<'a>>,
80 pub(crate) cur_blend_mode: BlendMode,
81}
82
83impl<'a> SvgRenderer<'a> {
84 pub(crate) fn write_transform(&mut self, transform: Affine) {
85 let c = transform.as_coeffs();
86 let has_scale = !(c[0] as f32).is_nearly_equal(1.0) || !(c[3] as f32).is_nearly_equal(1.0);
87 let has_skew = !(c[1] as f32).is_nearly_equal(0.0) || !(c[2] as f32).is_nearly_equal(0.0);
88 let has_translate =
89 !(c[4] as f32).is_nearly_equal(0.0) || !(c[5] as f32).is_nearly_equal(0.0);
90 let is_identity = !has_scale && !has_skew && !has_translate;
91
92 if !is_identity {
93 let transform = match (has_scale, has_skew, has_translate) {
94 (true, false, false) => {
95 format!("scale({} {})", c[0] as f32, c[3] as f32)
96 }
97 (false, false, true) => {
98 format!("translate({} {})", c[4] as f32, c[5] as f32)
99 }
100 _ => {
101 format!("matrix({})", &convert_transform(&transform))
102 }
103 };
104
105 self.xml.write_attribute("transform", &transform);
106 }
107 }
108
109 fn push_transparency_group_inner(
110 &mut self,
111 opacity: f32,
112 mask: Option<MaskKind<'a>>,
113 blend_mode: BlendMode,
114 ) {
115 let mask_id = mask.map(|m| self.get_mask_id(m));
116
117 self.xml.start_element("g");
118
119 if let Some(mask_id) = mask_id {
120 self.xml
121 .write_attribute_fmt("mask", format_args!("url(#{mask_id})"));
122 }
123
124 if blend_mode != BlendMode::Normal {
125 let bm_name = match blend_mode {
126 BlendMode::Normal => "normal",
127 BlendMode::Multiply => "multiply",
128 BlendMode::Screen => "screen",
129 BlendMode::Overlay => "overlay",
130 BlendMode::Darken => "darken",
131 BlendMode::Lighten => "lighten",
132 BlendMode::ColorDodge => "color-dodge",
133 BlendMode::ColorBurn => "color-burn",
134 BlendMode::HardLight => "hard-light",
135 BlendMode::SoftLight => "soft-light",
136 BlendMode::Difference => "difference",
137 BlendMode::Exclusion => "exclusion",
138 BlendMode::Hue => "hue",
139 BlendMode::Saturation => "saturation",
140 BlendMode::Color => "color",
141 BlendMode::Luminosity => "luminosity",
142 };
143
144 self.xml
145 .write_attribute("style", &format!("mix-blend-mode:{}", bm_name));
146 }
147
148 if !opacity.is_nearly_equal(1.0) {
149 self.xml.write_attribute("opacity", &opacity.to_string());
150 }
151 }
152
153 pub(crate) fn write_stroke_properties(&mut self, stroke_props: &StrokeProps) {
154 if !stroke_props.line_width.is_nearly_equal(1.0) {
155 self.xml
156 .write_attribute("stroke-width", &stroke_props.line_width);
157 }
158
159 match stroke_props.line_cap {
160 Cap::Butt => {}
161 Cap::Square => self.xml.write_attribute("stroke-linecap", "square"),
162 Cap::Round => self.xml.write_attribute("stroke-linecap", "round"),
163 }
164
165 match stroke_props.line_join {
166 Join::Bevel => self.xml.write_attribute("stroke-linejoin", "bevel"),
167 Join::Miter => {}
168 Join::Round => self.xml.write_attribute("stroke-linejoin", "round"),
169 }
170
171 if !stroke_props.miter_limit.is_nearly_equal(4.0) {
172 self.xml
173 .write_attribute("stroke-miterlimit", &stroke_props.miter_limit);
174 }
175
176 if !stroke_props.dash_offset.is_nearly_equal(0.0) {
177 self.xml
178 .write_attribute("stroke-dashoffset", &stroke_props.dash_offset);
179 }
180
181 if !stroke_props.dash_array.is_empty() {
182 self.xml.write_attribute(
183 "stroke-dasharray",
184 &stroke_props
185 .dash_array
186 .iter()
187 .map(|v| v.to_string())
188 .collect::<Vec<String>>()
189 .join(","),
190 );
191 }
192 }
193
194 fn with_group(&mut self, func: impl FnOnce(&mut Self)) {
195 let push_group = self.cur_mask.is_some() || self.cur_blend_mode != BlendMode::Normal;
196
197 if push_group {
198 self.push_transparency_group(1.0, self.cur_mask.clone(), self.cur_blend_mode);
199 }
200
201 func(self);
202
203 if push_group {
204 self.pop_transparency_group();
205 }
206 }
207}
208
209impl<'a> Device<'a> for SvgRenderer<'a> {
210 fn set_soft_mask(&mut self, mask: Option<SoftMask<'a>>) {
211 self.cur_mask = mask;
212 }
213
214 fn set_blend_mode(&mut self, blend_mode: BlendMode) {
215 self.cur_blend_mode = blend_mode;
216 }
217
218 fn draw_path(
219 &mut self,
220 path: &BezPath,
221 transform: Affine,
222 paint: &Paint<'a>,
223 draw_mode: &PathDrawMode,
224 ) {
225 self.with_group(|r| {
226 Self::draw_path(r, path, transform, paint, draw_mode);
227 });
228 }
229
230 fn push_clip_path(&mut self, clip_path: &ClipPath) {
231 let clip_id = self
232 .clip_paths
233 .insert_with(clip_path.cache_key(), || CachedClipPath {
234 path: clip_path.path.clone(),
235 fill_rule: clip_path.fill,
236 });
237
238 self.xml.start_element("g");
239 self.xml
240 .write_attribute_fmt("clip-path", format_args!("url(#{clip_id})"));
241 }
242
243 fn push_transparency_group(
244 &mut self,
245 opacity: f32,
246 mask: Option<SoftMask<'a>>,
247 blend_mode: BlendMode,
248 ) {
249 self.push_transparency_group_inner(opacity, mask.map(MaskKind::SoftMask), blend_mode);
250 }
251
252 fn draw_glyph(
253 &mut self,
254 glyph: &Glyph<'a>,
255 transform: Affine,
256 glyph_transform: Affine,
257 paint: &Paint<'a>,
258 draw_mode: &GlyphDrawMode,
259 ) {
260 self.with_group(|r| {
261 Self::draw_glyph(r, glyph, transform, glyph_transform, paint, draw_mode);
262 });
263 }
264
265 fn draw_image(&mut self, image: Image<'a, '_>, mut transform: Affine) {
266 match image {
268 Image::Stencil(s) => {
269 s.with_stencil(
270 |s, paint| {
271 transform *= Affine::scale_non_uniform(
272 s.scale_factors.0 as f64,
273 s.scale_factors.1 as f64,
274 );
275 Self::draw_stencil_image(self, s, transform, paint);
276 },
277 None,
278 );
279 }
280 Image::Raster(r) => {
281 r.with_rgba(
282 |rgb, alpha| {
283 transform *= Affine::scale_non_uniform(
284 rgb.scale_factors.0 as f64,
285 rgb.scale_factors.1 as f64,
286 );
287 Self::draw_rgba_image(self, rgb, transform, alpha);
288 },
289 None,
290 );
291 }
292 }
293 }
294
295 fn pop_clip_path(&mut self) {
296 self.xml.end_element();
297 }
298
299 fn pop_transparency_group(&mut self) {
300 self.xml.end_element();
301 }
302}
303
304impl<'a> SvgRenderer<'a> {
305 pub(crate) fn new(page: &'a Page<'a>) -> Self {
306 Self {
307 xml: XmlWriter::new(Options::default()),
308 outline_glyphs: Deduplicator::new('g'),
309 type3_glyphs: Deduplicator::new('e'),
310 clip_paths: Deduplicator::new('c'),
311 masks: Deduplicator::new('m'),
312 shadings: Deduplicator::new('s'),
313 shading_patterns: Deduplicator::new('v'),
314 tiling_patterns: Deduplicator::new('t'),
315 cur_mask: None,
316 dimensions: page.render_dimensions(),
317 cur_blend_mode: BlendMode::default(),
318 }
319 }
320
321 pub(crate) fn write_header(&mut self, size: (f32, f32)) {
322 self.xml.start_element("svg");
323 self.xml
324 .write_attribute_fmt("viewBox", format_args!("0 0 {} {}", size.0, size.1));
325 self.xml
326 .write_attribute_fmt("width", format_args!("{}", size.0));
327 self.xml
328 .write_attribute_fmt("height", format_args!("{}", size.1));
329 self.xml
330 .write_attribute("xmlns", "http://www.w3.org/2000/svg");
331 self.xml
332 .write_attribute("xmlns:xlink", "http://www.w3.org/1999/xlink");
333 }
334
335 pub(crate) fn with_dummy(&mut self, f: impl FnOnce(&mut Self)) {
341 let mut old_xml = std::mem::replace(&mut self.xml, XmlWriter::new(Options::default()));
342 f(self);
343 std::mem::swap(&mut self.xml, &mut old_xml);
344 }
345
346 pub(crate) fn finish(mut self) -> String {
347 self.write_glyph_defs();
348 self.write_mask_defs();
349 self.write_clip_path_defs();
350 self.write_shading_defs();
351 self.write_shading_pattern_defs();
352 self.write_tiling_pattern_defs();
353 self.xml.end_element();
355 self.xml.end_document()
356 }
357}
358
359pub(crate) fn convert_transform(transform: &Affine) -> String {
360 transform
361 .as_coeffs()
362 .iter()
363 .map(|c| (*c as f32).to_string())
364 .collect::<Vec<String>>()
365 .join(" ")
366}
367
368#[derive(Debug, Clone)]
369pub(crate) struct Deduplicator<T> {
370 kind: char,
371 vec: Vec<T>,
372 present: HashMap<u128, Id>,
373}
374
375impl<T> Default for Deduplicator<T> {
376 fn default() -> Self {
377 Self::new('-')
378 }
379}
380
381impl<T> Deduplicator<T> {
382 fn new(kind: char) -> Self {
383 Self {
384 kind,
385 vec: Vec::new(),
386 present: HashMap::new(),
387 }
388 }
389
390 pub(crate) fn contains(&self, hash: u128) -> bool {
391 self.present.contains_key(&hash)
392 }
393
394 pub(crate) fn insert_with<F>(&mut self, hash: u128, f: F) -> Id
395 where
396 F: FnOnce() -> T,
397 {
398 *self.present.entry(hash).or_insert_with(|| {
399 let index = self.vec.len();
400 self.vec.push(f());
401 Id(self.kind, index as u64)
402 })
403 }
404
405 pub(crate) fn insert(&mut self, value: T) -> Id {
406 let index = self.vec.len();
407 self.vec.push(value);
408 Id(self.kind, index as u64)
409 }
410
411 pub(crate) fn iter(&self) -> impl Iterator<Item = (Id, &T)> {
412 self.vec
413 .iter()
414 .enumerate()
415 .map(|(i, v)| (Id(self.kind, i as u64), v))
416 }
417
418 pub(crate) fn is_empty(&self) -> bool {
419 self.vec.is_empty()
420 }
421}
422
423#[derive(Debug, Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
424pub(crate) struct Id(char, u64);
425
426impl Display for Id {
427 fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
428 write!(f, "{}{}", self.0, self.1)
429 }
430}
431
432pub(crate) fn hash128<T: Hash + ?Sized>(value: &T) -> u128 {
433 let mut state = SipHasher13::new();
434 value.hash(&mut state);
435 state.finish128().as_u128()
436}