Skip to content

Commit

Permalink
dev: support fill rule
Browse files Browse the repository at this point in the history
  • Loading branch information
Myriad-Dreamin committed Oct 10, 2024
1 parent ff59d3e commit 3d765af
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 3 deletions.
5 changes: 5 additions & 0 deletions crates/conversion/typst2vec/src/pass/typst2vec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -774,6 +774,11 @@ impl<const ENABLE_REF_CNT: bool> Typst2VecPassImpl<ENABLE_REF_CNT> {
self.stroke(stateful_stroke, stroke, &mut styles);
}

match shape.fill_rule {
FillRule::NonZero => styles.push(PathStyle::FillRule("nonzero".into())),
FillRule::EvenOdd => styles.push(PathStyle::FillRule("evenodd".into())),
}

let mut shape_size = shape.geometry.bbox_size();
// Edge cases for strokes.
if shape_size.x.to_pt() == 0.0 {
Expand Down
1 change: 1 addition & 0 deletions crates/conversion/vec2canvas/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ web-sys = { workspace = true, features = [
"Document",
"TextMetrics",
"DedicatedWorkerGlobalScope",
"CanvasWindingRule",
] }

[features]
Expand Down
14 changes: 13 additions & 1 deletion crates/conversion/vec2canvas/src/device.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use web_sys::{ImageBitmap, ImageData, OffscreenCanvas, Path2d};
use web_sys::{CanvasWindingRule, ImageBitmap, ImageData, OffscreenCanvas, Path2d};

pub trait CanvasDevice {
#[doc = "The `restore()` method."]
Expand Down Expand Up @@ -104,6 +104,10 @@ pub trait CanvasDevice {
#[doc = ""]
#[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/CanvasRenderingContext2D/fill)"]
fn fill_with_path_2d(&self, path: &Path2d);
#[doc = "The `fill()` method."]
#[doc = ""]
#[doc = "[MDN Documentation](https://developer.mozilla.org/en-US/docs/Web/API/OffscreenCanvasRenderingContext2D/fill)"]
fn fill_with_path_2d_and_winding(&self, path: &Path2d, winding: CanvasWindingRule);
}

impl CanvasDevice for web_sys::CanvasRenderingContext2d {
Expand Down Expand Up @@ -208,6 +212,10 @@ impl CanvasDevice for web_sys::CanvasRenderingContext2d {
fn fill_with_path_2d(&self, path: &Path2d) {
self.fill_with_path_2d(path);
}

fn fill_with_path_2d_and_winding(&self, path: &Path2d, winding: CanvasWindingRule) {
self.fill_with_path_2d_and_winding(path, winding);
}
}

impl CanvasDevice for web_sys::OffscreenCanvasRenderingContext2d {
Expand Down Expand Up @@ -311,4 +319,8 @@ impl CanvasDevice for web_sys::OffscreenCanvasRenderingContext2d {
fn fill_with_path_2d(&self, path: &Path2d) {
self.fill_with_path_2d(path);
}

fn fill_with_path_2d_and_winding(&self, path: &Path2d, winding: CanvasWindingRule) {
self.fill_with_path_2d_and_winding(path, winding);
}
}
19 changes: 17 additions & 2 deletions crates/conversion/vec2canvas/src/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ use js_sys::Promise;
use tiny_skia as sk;

use wasm_bindgen::{prelude::Closure, JsCast, JsValue};
use web_sys::{ImageBitmap, OffscreenCanvas, Path2d};
use web_sys::{CanvasWindingRule, ImageBitmap, OffscreenCanvas, Path2d};

use reflexo::vector::ir::{
self, FlatGlyphItem, Image, ImageItem, ImmutStr, PathStyle, Rect, Scalar,
Expand Down Expand Up @@ -249,6 +249,7 @@ impl CanvasOp for CanvasPathElem {

let mut fill_color = "none".into();
let mut fill = false;
let mut fill_rule = None;
let mut stroke_color = "none".into();
let mut stroke = false;
let mut stroke_width = 0.;
Expand Down Expand Up @@ -285,6 +286,13 @@ impl CanvasOp for CanvasPathElem {
PathStyle::StrokeDashOffset(offset) => {
canvas.set_line_dash_offset(offset.0 as f64);
}
PathStyle::FillRule(rule) => {
fill_rule = match rule.as_ref() {
"nonzero" => Some(CanvasWindingRule::Nonzero),
"evenodd" => Some(CanvasWindingRule::Evenodd),
_ => None,
};
}
}
}

Expand All @@ -294,7 +302,14 @@ impl CanvasOp for CanvasPathElem {
fill_color = "black".into()
}
canvas.set_fill_style(&fill_color.as_ref().into());
canvas.fill_with_path_2d(&Path2d::new_with_path_string(&self.path_data.d).unwrap());
if let Some(rule) = fill_rule {
canvas.fill_with_path_2d_and_winding(
&Path2d::new_with_path_string(&self.path_data.d).unwrap(),
rule,
);
} else {
canvas.fill_with_path_2d(&Path2d::new_with_path_string(&self.path_data.d).unwrap());
}
}

if stroke && stroke_width.abs() > 1e-5 {
Expand Down
3 changes: 3 additions & 0 deletions crates/conversion/vec2svg/src/backend/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -629,6 +629,9 @@ fn attach_path_styles<'a>(
(offset.0 * scale.unwrap_or(1.)).to_string(),
);
}
PathStyle::FillRule(rule) => {
p("fill-rule", rule.to_string());
}
}
}

Expand Down
4 changes: 4 additions & 0 deletions crates/reflexo/src/vector/ir/visualize.rs
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,10 @@ pub enum PathStyle {
/// `stroke-width` attribute.
/// See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/stroke-width>
StrokeWidth(Abs),

/// `fill-rule` attribute.
/// See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/fill-rule>
FillRule(ImmutStr),
}

/// Item representing an `<pattern/>` element.
Expand Down
59 changes: 59 additions & 0 deletions fuzzers/corpora/visualize/path_04.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#set page(height: 300pt, width: 200pt)
#table(
columns: (1fr, 1fr),
rows: (1fr, 1fr, 1fr),
align: center + horizon,
path(
fill: red,
closed: true,
((0%, 0%), (4%, -4%)),
((50%, 50%), (4%, -4%)),
((0%, 50%), (4%, 4%)),
((50%, 0%), (4%, 4%)),
),
path(
fill: purple,
stroke: 1pt,
(0pt, 0pt),
(30pt, 30pt),
(0pt, 30pt),
(30pt, 0pt),
),

path(
fill: blue,
stroke: 1pt,
closed: true,
((30%, 0%), (35%, 30%), (-20%, 0%)),
((30%, 60%), (-20%, 0%), (0%, 0%)),
((50%, 30%), (60%, -30%), (60%, 0%)),
),
path(
stroke: 5pt,
closed: true,
(0pt, 30pt),
(30pt, 30pt),
(15pt, 0pt),
),

path(
fill: red,
fill-rule: "non-zero",
closed: true,
(25pt, 0pt),
(10pt, 50pt),
(50pt, 20pt),
(0pt, 20pt),
(40pt, 50pt),
),
path(
fill: red,
fill-rule: "even-odd",
closed: true,
(25pt, 0pt),
(10pt, 50pt),
(50pt, 20pt),
(0pt, 20pt),
(40pt, 50pt),
),
)
39 changes: 39 additions & 0 deletions fuzzers/corpora/visualize/polygon_02.typ
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@

#import "/contrib/templates/std-tests/preset.typ": *
#show: test-page

#set page(width: 50pt)
#set polygon(stroke: 0.75pt, fill: blue)

// These are not visible, but should also not give an error
#polygon()
#polygon((0em, 0pt))
#polygon((0pt, 0pt), (10pt, 0pt))
#polygon.regular(size: 0pt, vertices: 9)

#polygon((5pt, 0pt), (0pt, 10pt), (10pt, 10pt))
#polygon(
(0pt, 0pt),
(5pt, 5pt),
(10pt, 0pt),
(15pt, 5pt),
(5pt, 10pt),
)
#polygon(stroke: none, (5pt, 0pt), (0pt, 10pt), (10pt, 10pt))
#polygon(stroke: 3pt, fill: none, (5pt, 0pt), (0pt, 10pt), (10pt, 10pt))

// Relative size
#polygon((0pt, 0pt), (100%, 5pt), (50%, 10pt))

// Antiparallelogram
#polygon((0pt, 5pt), (5pt, 0pt), (0pt, 10pt), (5pt, 15pt))

// Self-intersections
#polygon((0pt, 10pt), (30pt, 20pt), (0pt, 30pt), (20pt, 0pt), (20pt, 35pt))
#polygon(fill-rule: "non-zero", (0pt, 10pt), (30pt, 20pt), (0pt, 30pt), (20pt, 0pt), (20pt, 35pt))
#polygon(fill-rule: "even-odd", (0pt, 10pt), (30pt, 20pt), (0pt, 30pt), (20pt, 0pt), (20pt, 35pt))

// Regular polygon; should have equal side lengths
#for k in range(3, 9) {
polygon.regular(size: 30pt, vertices: k)
}

0 comments on commit 3d765af

Please sign in to comment.