summaryrefslogtreecommitdiff
path: root/crates/typst-pdf/src/shape.rs
blob: 3b52939dab663757ec3da649692006713bcda44a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
use krilla::geom::{Path, PathBuilder, Rect};
use krilla::surface::Surface;
use typst_library::diag::SourceResult;
use typst_library::visualize::{Geometry, Shape};
use typst_syntax::Span;

use crate::convert::{FrameContext, GlobalContext};
use crate::util::{convert_path, AbsExt, TransformExt};
use crate::{paint, tags};

#[typst_macros::time(name = "handle shape")]
pub(crate) fn handle_shape(
    fc: &mut FrameContext,
    shape: &Shape,
    surface: &mut Surface,
    gc: &mut GlobalContext,
    span: Span,
) -> SourceResult<()> {
    let mut handle = tags::start_marked(gc, surface);
    let surface = handle.surface();

    surface.set_location(span.into_raw().get());
    surface.push_transform(&fc.state().transform().to_krilla());

    if let Some(path) = convert_geometry(&shape.geometry) {
        let fill = if let Some(paint) = &shape.fill {
            Some(paint::convert_fill(
                gc,
                paint,
                shape.fill_rule,
                false,
                surface,
                fc.state(),
                shape.geometry.bbox_size(),
            )?)
        } else {
            None
        };

        let stroke = shape.stroke.as_ref().and_then(|stroke| {
            if stroke.thickness.to_f32() > 0.0 {
                Some(stroke)
            } else {
                None
            }
        });

        let stroke = if let Some(stroke) = &stroke {
            let stroke = paint::convert_stroke(
                gc,
                stroke,
                false,
                surface,
                fc.state(),
                shape.geometry.bbox_size(),
            )?;

            Some(stroke)
        } else {
            None
        };

        // Otherwise, krilla will by default fill with a black paint.
        if fill.is_some() || stroke.is_some() {
            surface.set_fill(fill);
            surface.set_stroke(stroke);
            surface.draw_path(&path);
        }
    }

    surface.pop();
    surface.reset_location();

    Ok(())
}

fn convert_geometry(geometry: &Geometry) -> Option<Path> {
    let mut path_builder = PathBuilder::new();

    match geometry {
        Geometry::Line(l) => {
            path_builder.move_to(0.0, 0.0);
            path_builder.line_to(l.x.to_f32(), l.y.to_f32());
        }
        Geometry::Rect(size) => {
            let w = size.x.to_f32();
            let h = size.y.to_f32();
            let rect = if w < 0.0 || h < 0.0 {
                // krilla doesn't normally allow for negative dimensions, but
                // Typst supports them, so we apply a transform if needed.
                let transform =
                    krilla::geom::Transform::from_scale(w.signum(), h.signum());
                Rect::from_xywh(0.0, 0.0, w.abs(), h.abs())
                    .and_then(|rect| rect.transform(transform))
            } else {
                Rect::from_xywh(0.0, 0.0, w, h)
            };

            if let Some(rect) = rect {
                path_builder.push_rect(rect);
            }
        }
        Geometry::Curve(c) => {
            convert_path(c, &mut path_builder);
        }
    }

    path_builder.finish()
}