mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Add non-zero
and even-odd
fill rules to path
and polygon
(#4580)
Co-authored-by: Laurenz <laurmaedje@gmail.com>
This commit is contained in:
parent
684efa2e0e
commit
1d74c8e8bf
@ -16,7 +16,8 @@ use typst::model::Destination;
|
|||||||
use typst::text::{color::is_color_glyph, Font, TextItem, TextItemView};
|
use typst::text::{color::is_color_glyph, Font, TextItem, TextItemView};
|
||||||
use typst::utils::{Deferred, Numeric, SliceExt};
|
use typst::utils::{Deferred, Numeric, SliceExt};
|
||||||
use typst::visualize::{
|
use typst::visualize::{
|
||||||
FixedStroke, Geometry, Image, LineCap, LineJoin, Paint, Path, PathItem, Shape,
|
FillRule, FixedStroke, Geometry, Image, LineCap, LineJoin, Paint, Path, PathItem,
|
||||||
|
Shape,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::color_font::ColorFontMap;
|
use crate::color_font::ColorFontMap;
|
||||||
@ -636,11 +637,13 @@ fn write_shape(ctx: &mut Builder, pos: Point, shape: &Shape) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match (&shape.fill, stroke) {
|
match (&shape.fill, &shape.fill_rule, stroke) {
|
||||||
(None, None) => unreachable!(),
|
(None, _, None) => unreachable!(),
|
||||||
(Some(_), None) => ctx.content.fill_nonzero(),
|
(Some(_), FillRule::NonZero, None) => ctx.content.fill_nonzero(),
|
||||||
(None, Some(_)) => ctx.content.stroke(),
|
(Some(_), FillRule::EvenOdd, None) => ctx.content.fill_even_odd(),
|
||||||
(Some(_), Some(_)) => ctx.content.fill_nonzero_and_stroke(),
|
(None, _, Some(_)) => ctx.content.stroke(),
|
||||||
|
(Some(_), FillRule::NonZero, Some(_)) => ctx.content.fill_nonzero_and_stroke(),
|
||||||
|
(Some(_), FillRule::EvenOdd, Some(_)) => ctx.content.fill_even_odd_and_stroke(),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
use tiny_skia as sk;
|
use tiny_skia as sk;
|
||||||
use typst::layout::{Abs, Axes, Point, Ratio, Size};
|
use typst::layout::{Abs, Axes, Point, Ratio, Size};
|
||||||
use typst::visualize::{
|
use typst::visualize::{
|
||||||
DashPattern, FixedStroke, Geometry, LineCap, LineJoin, Path, PathItem, Shape,
|
DashPattern, FillRule, FixedStroke, Geometry, LineCap, LineJoin, Path, PathItem,
|
||||||
|
Shape,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{paint, AbsExt, State};
|
use crate::{paint, AbsExt, State};
|
||||||
@ -51,7 +52,10 @@ pub fn render_shape(canvas: &mut sk::Pixmap, state: State, shape: &Shape) -> Opt
|
|||||||
paint.anti_alias = false;
|
paint.anti_alias = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
let rule = sk::FillRule::default();
|
let rule = match shape.fill_rule {
|
||||||
|
FillRule::NonZero => sk::FillRule::Winding,
|
||||||
|
FillRule::EvenOdd => sk::FillRule::EvenOdd,
|
||||||
|
};
|
||||||
canvas.fill_path(&path, &paint, rule, ts, state.mask);
|
canvas.fill_path(&path, &paint, rule, ts, state.mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@ use ttf_parser::OutlineBuilder;
|
|||||||
use typst::foundations::Repr;
|
use typst::foundations::Repr;
|
||||||
use typst::layout::{Angle, Axes, Frame, Quadrant, Ratio, Size, Transform};
|
use typst::layout::{Angle, Axes, Frame, Quadrant, Ratio, Size, Transform};
|
||||||
use typst::utils::hash128;
|
use typst::utils::hash128;
|
||||||
use typst::visualize::{Color, Gradient, Paint, Pattern, RatioOrAngle};
|
use typst::visualize::{Color, FillRule, Gradient, Paint, Pattern, RatioOrAngle};
|
||||||
use xmlwriter::XmlWriter;
|
use xmlwriter::XmlWriter;
|
||||||
|
|
||||||
use crate::{Id, SVGRenderer, State, SvgMatrix, SvgPathBuilder};
|
use crate::{Id, SVGRenderer, State, SvgMatrix, SvgPathBuilder};
|
||||||
@ -31,7 +31,13 @@ impl SVGRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Write a fill attribute.
|
/// Write a fill attribute.
|
||||||
pub(super) fn write_fill(&mut self, fill: &Paint, size: Size, ts: Transform) {
|
pub(super) fn write_fill(
|
||||||
|
&mut self,
|
||||||
|
fill: &Paint,
|
||||||
|
fill_rule: FillRule,
|
||||||
|
size: Size,
|
||||||
|
ts: Transform,
|
||||||
|
) {
|
||||||
match fill {
|
match fill {
|
||||||
Paint::Solid(color) => self.xml.write_attribute("fill", &color.encode()),
|
Paint::Solid(color) => self.xml.write_attribute("fill", &color.encode()),
|
||||||
Paint::Gradient(gradient) => {
|
Paint::Gradient(gradient) => {
|
||||||
@ -43,6 +49,10 @@ impl SVGRenderer {
|
|||||||
self.xml.write_attribute_fmt("fill", format_args!("url(#{id})"));
|
self.xml.write_attribute_fmt("fill", format_args!("url(#{id})"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
match fill_rule {
|
||||||
|
FillRule::NonZero => self.xml.write_attribute("fill-rule", "nonzero"),
|
||||||
|
FillRule::EvenOdd => self.xml.write_attribute("fill-rule", "evenodd"),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Pushes a gradient to the list of gradients to write SVG file.
|
/// Pushes a gradient to the list of gradients to write SVG file.
|
||||||
|
@ -17,6 +17,7 @@ impl SVGRenderer {
|
|||||||
if let Some(paint) = &shape.fill {
|
if let Some(paint) = &shape.fill {
|
||||||
self.write_fill(
|
self.write_fill(
|
||||||
paint,
|
paint,
|
||||||
|
shape.fill_rule,
|
||||||
self.shape_fill_size(state, paint, shape),
|
self.shape_fill_size(state, paint, shape),
|
||||||
self.shape_paint_transform(state, paint, shape),
|
self.shape_paint_transform(state, paint, shape),
|
||||||
);
|
);
|
||||||
|
@ -6,7 +6,7 @@ use ttf_parser::GlyphId;
|
|||||||
use typst::layout::{Abs, Point, Ratio, Size, Transform};
|
use typst::layout::{Abs, Point, Ratio, Size, Transform};
|
||||||
use typst::text::{Font, TextItem};
|
use typst::text::{Font, TextItem};
|
||||||
use typst::utils::hash128;
|
use typst::utils::hash128;
|
||||||
use typst::visualize::{Image, Paint, RasterFormat, RelativeTo};
|
use typst::visualize::{FillRule, Image, Paint, RasterFormat, RelativeTo};
|
||||||
|
|
||||||
use crate::{SVGRenderer, State, SvgMatrix, SvgPathBuilder};
|
use crate::{SVGRenderer, State, SvgMatrix, SvgPathBuilder};
|
||||||
|
|
||||||
@ -138,6 +138,7 @@ impl SVGRenderer {
|
|||||||
self.xml.write_attribute_fmt("x", format_args!("{x_offset}"));
|
self.xml.write_attribute_fmt("x", format_args!("{x_offset}"));
|
||||||
self.write_fill(
|
self.write_fill(
|
||||||
&text.fill,
|
&text.fill,
|
||||||
|
FillRule::default(),
|
||||||
Size::new(Abs::pt(width), Abs::pt(height)),
|
Size::new(Abs::pt(width), Abs::pt(height)),
|
||||||
self.text_paint_transform(state, &text.fill),
|
self.text_paint_transform(state, &text.fill),
|
||||||
);
|
);
|
||||||
|
@ -18,7 +18,7 @@ use crate::symbols::Symbol;
|
|||||||
use crate::syntax::{Span, Spanned};
|
use crate::syntax::{Span, Spanned};
|
||||||
use crate::text::TextElem;
|
use crate::text::TextElem;
|
||||||
use crate::utils::Numeric;
|
use crate::utils::Numeric;
|
||||||
use crate::visualize::{FixedStroke, Geometry, LineCap, Shape, Stroke};
|
use crate::visualize::{FillRule, FixedStroke, Geometry, LineCap, Shape, Stroke};
|
||||||
|
|
||||||
use super::delimiter_alignment;
|
use super::delimiter_alignment;
|
||||||
|
|
||||||
@ -597,6 +597,7 @@ fn line_item(length: Abs, vertical: bool, stroke: FixedStroke, span: Span) -> Fr
|
|||||||
Shape {
|
Shape {
|
||||||
geometry: line_geom,
|
geometry: line_geom,
|
||||||
fill: None,
|
fill: None,
|
||||||
|
fill_rule: FillRule::default(),
|
||||||
stroke: Some(stroke),
|
stroke: Some(stroke),
|
||||||
},
|
},
|
||||||
span,
|
span,
|
||||||
|
@ -10,7 +10,7 @@ use crate::introspection::Locator;
|
|||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Axes, BlockElem, Frame, FrameItem, Length, Point, Region, Rel, Size,
|
Abs, Axes, BlockElem, Frame, FrameItem, Length, Point, Region, Rel, Size,
|
||||||
};
|
};
|
||||||
use crate::visualize::{FixedStroke, Geometry, Paint, Shape, Stroke};
|
use crate::visualize::{FillRule, FixedStroke, Geometry, Paint, Shape, Stroke};
|
||||||
|
|
||||||
use PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
|
use PathVertex::{AllControlPoints, MirroredControlPoint, Vertex};
|
||||||
|
|
||||||
@ -33,11 +33,12 @@ pub struct PathElem {
|
|||||||
///
|
///
|
||||||
/// When setting a fill, the default stroke disappears. To create a
|
/// When setting a fill, the default stroke disappears. To create a
|
||||||
/// rectangle with both fill and stroke, you have to configure both.
|
/// rectangle with both fill and stroke, you have to configure both.
|
||||||
///
|
|
||||||
/// Currently all paths are filled according to the [non-zero winding
|
|
||||||
/// rule](https://en.wikipedia.org/wiki/Nonzero-rule).
|
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
|
/// The rule used to fill the path.
|
||||||
|
#[default]
|
||||||
|
pub fill_rule: FillRule,
|
||||||
|
|
||||||
/// How to [stroke] the path. This can be:
|
/// How to [stroke] the path. This can be:
|
||||||
///
|
///
|
||||||
/// Can be set to `{none}` to disable the stroke or to `{auto}` for a
|
/// Can be set to `{none}` to disable the stroke or to `{auto}` for a
|
||||||
@ -147,6 +148,7 @@ fn layout_path(
|
|||||||
|
|
||||||
// Prepare fill and stroke.
|
// Prepare fill and stroke.
|
||||||
let fill = elem.fill(styles);
|
let fill = elem.fill(styles);
|
||||||
|
let fill_rule = elem.fill_rule(styles);
|
||||||
let stroke = match elem.stroke(styles) {
|
let stroke = match elem.stroke(styles) {
|
||||||
Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
|
Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
|
||||||
Smart::Auto => None,
|
Smart::Auto => None,
|
||||||
@ -154,7 +156,12 @@ fn layout_path(
|
|||||||
};
|
};
|
||||||
|
|
||||||
let mut frame = Frame::soft(size);
|
let mut frame = Frame::soft(size);
|
||||||
let shape = Shape { geometry: Geometry::Path(path), stroke, fill };
|
let shape = Shape {
|
||||||
|
geometry: Geometry::Path(path),
|
||||||
|
stroke,
|
||||||
|
fill,
|
||||||
|
fill_rule,
|
||||||
|
};
|
||||||
frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
|
frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
|
||||||
Ok(frame)
|
Ok(frame)
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ use crate::introspection::Locator;
|
|||||||
use crate::layout::{Axes, BlockElem, Em, Frame, FrameItem, Length, Point, Region, Rel};
|
use crate::layout::{Axes, BlockElem, Em, Frame, FrameItem, Length, Point, Region, Rel};
|
||||||
use crate::syntax::Span;
|
use crate::syntax::Span;
|
||||||
use crate::utils::Numeric;
|
use crate::utils::Numeric;
|
||||||
use crate::visualize::{FixedStroke, Geometry, Paint, Path, Shape, Stroke};
|
use crate::visualize::{FillRule, FixedStroke, Geometry, Paint, Path, Shape, Stroke};
|
||||||
|
|
||||||
/// A closed polygon.
|
/// A closed polygon.
|
||||||
///
|
///
|
||||||
@ -32,11 +32,12 @@ pub struct PolygonElem {
|
|||||||
///
|
///
|
||||||
/// When setting a fill, the default stroke disappears. To create a
|
/// When setting a fill, the default stroke disappears. To create a
|
||||||
/// rectangle with both fill and stroke, you have to configure both.
|
/// rectangle with both fill and stroke, you have to configure both.
|
||||||
///
|
|
||||||
/// Currently all polygons are filled according to the
|
|
||||||
/// [non-zero winding rule](https://en.wikipedia.org/wiki/Nonzero-rule).
|
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
|
||||||
|
/// The rule used to fill the polygon.
|
||||||
|
#[default]
|
||||||
|
pub fill_rule: FillRule,
|
||||||
|
|
||||||
/// How to [stroke] the polygon. This can be:
|
/// How to [stroke] the polygon. This can be:
|
||||||
///
|
///
|
||||||
/// Can be set to `{none}` to disable the stroke or to `{auto}` for a
|
/// Can be set to `{none}` to disable the stroke or to `{auto}` for a
|
||||||
@ -161,6 +162,7 @@ fn layout_polygon(
|
|||||||
|
|
||||||
// Prepare fill and stroke.
|
// Prepare fill and stroke.
|
||||||
let fill = elem.fill(styles);
|
let fill = elem.fill(styles);
|
||||||
|
let fill_rule = elem.fill_rule(styles);
|
||||||
let stroke = match elem.stroke(styles) {
|
let stroke = match elem.stroke(styles) {
|
||||||
Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
|
Smart::Auto if fill.is_none() => Some(FixedStroke::default()),
|
||||||
Smart::Auto => None,
|
Smart::Auto => None,
|
||||||
@ -175,7 +177,12 @@ fn layout_polygon(
|
|||||||
}
|
}
|
||||||
path.close_path();
|
path.close_path();
|
||||||
|
|
||||||
let shape = Shape { geometry: Geometry::Path(path), stroke, fill };
|
let shape = Shape {
|
||||||
|
geometry: Geometry::Path(path),
|
||||||
|
stroke,
|
||||||
|
fill,
|
||||||
|
fill_rule,
|
||||||
|
};
|
||||||
frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
|
frame.push(Point::zero(), FrameItem::Shape(shape, elem.span()));
|
||||||
Ok(frame)
|
Ok(frame)
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,9 @@ use std::f64::consts::SQRT_2;
|
|||||||
|
|
||||||
use crate::diag::SourceResult;
|
use crate::diag::SourceResult;
|
||||||
use crate::engine::Engine;
|
use crate::engine::Engine;
|
||||||
use crate::foundations::{elem, Content, NativeElement, Packed, Show, Smart, StyleChain};
|
use crate::foundations::{
|
||||||
|
elem, Cast, Content, NativeElement, Packed, Show, Smart, StyleChain,
|
||||||
|
};
|
||||||
use crate::introspection::Locator;
|
use crate::introspection::Locator;
|
||||||
use crate::layout::{
|
use crate::layout::{
|
||||||
Abs, Axes, BlockElem, Corner, Corners, Frame, FrameItem, Length, Point, Ratio,
|
Abs, Axes, BlockElem, Corner, Corners, Frame, FrameItem, Length, Point, Ratio,
|
||||||
@ -583,10 +585,22 @@ pub struct Shape {
|
|||||||
pub geometry: Geometry,
|
pub geometry: Geometry,
|
||||||
/// The shape's background fill.
|
/// The shape's background fill.
|
||||||
pub fill: Option<Paint>,
|
pub fill: Option<Paint>,
|
||||||
|
/// The shape's fill rule.
|
||||||
|
pub fill_rule: FillRule,
|
||||||
/// The shape's border stroke.
|
/// The shape's border stroke.
|
||||||
pub stroke: Option<FixedStroke>,
|
pub stroke: Option<FixedStroke>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A path filling rule.
|
||||||
|
#[derive(Debug, Default, Copy, Clone, Eq, PartialEq, Hash, Cast)]
|
||||||
|
pub enum FillRule {
|
||||||
|
/// Specifies that "inside" is computed by a non-zero sum of signed edge crossings.
|
||||||
|
#[default]
|
||||||
|
NonZero,
|
||||||
|
/// Specifies that "inside" is computed by an odd number of edge crossings.
|
||||||
|
EvenOdd,
|
||||||
|
}
|
||||||
|
|
||||||
/// A shape's geometry.
|
/// A shape's geometry.
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash)]
|
||||||
pub enum Geometry {
|
pub enum Geometry {
|
||||||
@ -601,12 +615,22 @@ pub enum Geometry {
|
|||||||
impl Geometry {
|
impl Geometry {
|
||||||
/// Fill the geometry without a stroke.
|
/// Fill the geometry without a stroke.
|
||||||
pub fn filled(self, fill: Paint) -> Shape {
|
pub fn filled(self, fill: Paint) -> Shape {
|
||||||
Shape { geometry: self, fill: Some(fill), stroke: None }
|
Shape {
|
||||||
|
geometry: self,
|
||||||
|
fill: Some(fill),
|
||||||
|
fill_rule: FillRule::default(),
|
||||||
|
stroke: None,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Stroke the geometry without a fill.
|
/// Stroke the geometry without a fill.
|
||||||
pub fn stroked(self, stroke: FixedStroke) -> Shape {
|
pub fn stroked(self, stroke: FixedStroke) -> Shape {
|
||||||
Shape { geometry: self, fill: None, stroke: Some(stroke) }
|
Shape {
|
||||||
|
geometry: self,
|
||||||
|
fill: None,
|
||||||
|
fill_rule: FillRule::default(),
|
||||||
|
stroke: Some(stroke),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The bounding box of the geometry.
|
/// The bounding box of the geometry.
|
||||||
@ -641,7 +665,12 @@ pub(crate) fn ellipse(
|
|||||||
path.cubic_to(point(rx, my), point(mx, ry), point(z, ry));
|
path.cubic_to(point(rx, my), point(mx, ry), point(z, ry));
|
||||||
path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z));
|
path.cubic_to(point(-mx, ry), point(-rx, my), point(-rx, z));
|
||||||
|
|
||||||
Shape { geometry: Geometry::Path(path), stroke, fill }
|
Shape {
|
||||||
|
geometry: Geometry::Path(path),
|
||||||
|
stroke,
|
||||||
|
fill,
|
||||||
|
fill_rule: FillRule::default(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new rectangle as a path.
|
/// Creates a new rectangle as a path.
|
||||||
@ -704,7 +733,12 @@ fn simple_rect(
|
|||||||
fill: Option<Paint>,
|
fill: Option<Paint>,
|
||||||
stroke: Option<FixedStroke>,
|
stroke: Option<FixedStroke>,
|
||||||
) -> Vec<Shape> {
|
) -> Vec<Shape> {
|
||||||
vec![Shape { geometry: Geometry::Rect(size), fill, stroke }]
|
vec![Shape {
|
||||||
|
geometry: Geometry::Rect(size),
|
||||||
|
fill,
|
||||||
|
stroke,
|
||||||
|
fill_rule: FillRule::default(),
|
||||||
|
}]
|
||||||
}
|
}
|
||||||
|
|
||||||
fn corners_control_points(
|
fn corners_control_points(
|
||||||
@ -779,6 +813,7 @@ fn segmented_rect(
|
|||||||
res.push(Shape {
|
res.push(Shape {
|
||||||
geometry: Geometry::Path(path),
|
geometry: Geometry::Path(path),
|
||||||
fill: Some(fill),
|
fill: Some(fill),
|
||||||
|
fill_rule: FillRule::default(),
|
||||||
stroke: None,
|
stroke: None,
|
||||||
});
|
});
|
||||||
stroke_insert += 1;
|
stroke_insert += 1;
|
||||||
@ -916,6 +951,7 @@ fn stroke_segment(
|
|||||||
geometry: Geometry::Path(path),
|
geometry: Geometry::Path(path),
|
||||||
stroke: Some(stroke),
|
stroke: Some(stroke),
|
||||||
fill: None,
|
fill: None,
|
||||||
|
fill_rule: FillRule::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1014,6 +1050,7 @@ fn fill_segment(
|
|||||||
geometry: Geometry::Path(path),
|
geometry: Geometry::Path(path),
|
||||||
stroke: None,
|
stroke: None,
|
||||||
fill: Some(stroke.paint.clone()),
|
fill: Some(stroke.paint.clone()),
|
||||||
|
fill_rule: FillRule::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 3.1 KiB After Width: | Height: | Size: 4.3 KiB |
Binary file not shown.
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.6 KiB |
@ -1,10 +1,10 @@
|
|||||||
// Test paths.
|
// Test paths.
|
||||||
|
|
||||||
--- path ---
|
--- path ---
|
||||||
#set page(height: 200pt, width: 200pt)
|
#set page(height: 300pt, width: 200pt)
|
||||||
#table(
|
#table(
|
||||||
columns: (1fr, 1fr),
|
columns: (1fr, 1fr),
|
||||||
rows: (1fr, 1fr),
|
rows: (1fr, 1fr, 1fr),
|
||||||
align: center + horizon,
|
align: center + horizon,
|
||||||
path(
|
path(
|
||||||
fill: red,
|
fill: red,
|
||||||
@ -37,6 +37,26 @@
|
|||||||
(30pt, 30pt),
|
(30pt, 30pt),
|
||||||
(15pt, 0pt),
|
(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),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
--- path-bad-vertex ---
|
--- path-bad-vertex ---
|
||||||
|
@ -27,6 +27,8 @@
|
|||||||
|
|
||||||
// Self-intersections
|
// Self-intersections
|
||||||
#polygon((0pt, 10pt), (30pt, 20pt), (0pt, 30pt), (20pt, 0pt), (20pt, 35pt))
|
#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
|
// Regular polygon; should have equal side lengths
|
||||||
#for k in range(3, 9) {polygon.regular(size: 30pt, vertices: k,)}
|
#for k in range(3, 9) {polygon.regular(size: 30pt, vertices: k,)}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user