mirror of
https://github.com/typst/typst
synced 2025-05-14 17:15:28 +08:00
Move rounding logic out of exporters
This commit is contained in:
parent
7b6f3a0ab9
commit
f07395f9a4
@ -16,9 +16,10 @@ use ttf_parser::{name_id, GlyphId, Tag};
|
|||||||
|
|
||||||
use super::subset::subset;
|
use super::subset::subset;
|
||||||
use crate::font::{find_name, FaceId, FontStore};
|
use crate::font::{find_name, FaceId, FontStore};
|
||||||
use crate::frame::{rect_path, rect_paths, Element, Frame, Geometry, Group, Shape, Text};
|
use crate::frame::{Element, Frame, Group, Text};
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
self, Color, Em, Length, Numeric, Paint, Point, Sides, Size, Stroke, Transform,
|
self, Color, Em, Geometry, Length, Numeric, Paint, Point, Shape, Size, Stroke,
|
||||||
|
Transform,
|
||||||
};
|
};
|
||||||
use crate::image::{Image, ImageId, ImageStore, RasterImage};
|
use crate::image::{Image, ImageId, ImageStore, RasterImage};
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
@ -499,16 +500,16 @@ impl<'a> PageExporter<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn write_shape(&mut self, x: f32, y: f32, shape: &Shape) {
|
fn write_shape(&mut self, x: f32, y: f32, shape: &Shape) {
|
||||||
if shape.fill.is_none() && shape.stroke.iter().all(Option::is_none) {
|
if shape.fill.is_none() && shape.stroke.is_none() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
match shape.geometry {
|
match shape.geometry {
|
||||||
Geometry::Rect(size, radius) => {
|
Geometry::Rect(size) => {
|
||||||
let w = size.x.to_f32();
|
let w = size.x.to_f32();
|
||||||
let h = size.y.to_f32();
|
let h = size.y.to_f32();
|
||||||
if w > 0.0 && h > 0.0 {
|
if w > 0.0 && h > 0.0 {
|
||||||
self.write_path(x, y, &rect_path(size, radius));
|
self.content.rect(x, y, w, h);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Geometry::Ellipse(size) => {
|
Geometry::Ellipse(size) => {
|
||||||
@ -530,37 +531,16 @@ impl<'a> PageExporter<'a> {
|
|||||||
self.set_fill(fill);
|
self.set_fill(fill);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The stroke does not exist or is non-uniform.
|
if let Some(stroke) = shape.stroke {
|
||||||
let mut use_stroke = false;
|
|
||||||
if shape.stroke.is_uniform() || !matches!(shape.geometry, Geometry::Rect(_, _)) {
|
|
||||||
if let Some(stroke) = shape.stroke.top {
|
|
||||||
self.set_stroke(stroke);
|
self.set_stroke(stroke);
|
||||||
use_stroke = true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
match (shape.fill, use_stroke) {
|
match (shape.fill, shape.stroke) {
|
||||||
(None, false) => self.content.end_path(),
|
(None, None) => unreachable!(),
|
||||||
(Some(_), false) => self.content.fill_nonzero(),
|
(Some(_), None) => self.content.fill_nonzero(),
|
||||||
(None, true) => self.content.stroke(),
|
(None, Some(_)) => self.content.stroke(),
|
||||||
(Some(_), true) => self.content.fill_nonzero_and_stroke(),
|
(Some(_), Some(_)) => self.content.fill_nonzero_and_stroke(),
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Geometry::Rect(size, radius) = shape.geometry {
|
|
||||||
if !use_stroke {
|
|
||||||
for (path, stroke) in rect_paths(size, radius, Some(shape.stroke)) {
|
|
||||||
if let Some(stroke) = stroke {
|
|
||||||
self.write_shape(x, y, &Shape {
|
|
||||||
geometry: Geometry::Path(path),
|
|
||||||
fill: None,
|
|
||||||
stroke: Sides::splat(Some(stroke)),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_path(&mut self, x: f32, y: f32, path: &geom::Path) {
|
fn write_path(&mut self, x: f32, y: f32, path: &geom::Path) {
|
||||||
|
@ -7,10 +7,11 @@ use tiny_skia as sk;
|
|||||||
use ttf_parser::{GlyphId, OutlineBuilder};
|
use ttf_parser::{GlyphId, OutlineBuilder};
|
||||||
use usvg::FitTo;
|
use usvg::FitTo;
|
||||||
|
|
||||||
use crate::frame::{Element, Frame, Geometry, Group, Shape, Text};
|
use crate::frame::{Element, Frame, Group, Text};
|
||||||
use crate::geom::{self, Length, Paint, PathElement, Sides, Size, Stroke, Transform};
|
use crate::geom::{
|
||||||
|
self, Geometry, Length, Paint, PathElement, Shape, Size, Stroke, Transform,
|
||||||
|
};
|
||||||
use crate::image::{Image, RasterImage, Svg};
|
use crate::image::{Image, RasterImage, Svg};
|
||||||
use crate::library::prelude::{rect_path, rect_paths};
|
|
||||||
use crate::Context;
|
use crate::Context;
|
||||||
|
|
||||||
/// Export a frame into a rendered image.
|
/// Export a frame into a rendered image.
|
||||||
@ -299,7 +300,12 @@ fn render_shape(
|
|||||||
shape: &Shape,
|
shape: &Shape,
|
||||||
) -> Option<()> {
|
) -> Option<()> {
|
||||||
let path = match shape.geometry {
|
let path = match shape.geometry {
|
||||||
Geometry::Rect(size, radius) => convert_path(&rect_path(size, radius))?,
|
Geometry::Rect(size) => {
|
||||||
|
let w = size.x.to_f32();
|
||||||
|
let h = size.y.to_f32();
|
||||||
|
let rect = sk::Rect::from_xywh(0.0, 0.0, w, h)?;
|
||||||
|
sk::PathBuilder::from_rect(rect)
|
||||||
|
}
|
||||||
Geometry::Ellipse(size) => convert_path(&geom::Path::ellipse(size))?,
|
Geometry::Ellipse(size) => convert_path(&geom::Path::ellipse(size))?,
|
||||||
Geometry::Line(target) => {
|
Geometry::Line(target) => {
|
||||||
let mut builder = sk::PathBuilder::new();
|
let mut builder = sk::PathBuilder::new();
|
||||||
@ -311,7 +317,7 @@ fn render_shape(
|
|||||||
|
|
||||||
if let Some(fill) = shape.fill {
|
if let Some(fill) = shape.fill {
|
||||||
let mut paint: sk::Paint = fill.into();
|
let mut paint: sk::Paint = fill.into();
|
||||||
if matches!(shape.geometry, Geometry::Rect(_, _)) {
|
if matches!(shape.geometry, Geometry::Rect(_)) {
|
||||||
paint.anti_alias = false;
|
paint.anti_alias = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,28 +325,12 @@ fn render_shape(
|
|||||||
canvas.fill_path(&path, &paint, rule, ts, mask);
|
canvas.fill_path(&path, &paint, rule, ts, mask);
|
||||||
}
|
}
|
||||||
|
|
||||||
if shape.stroke.is_uniform() || !matches!(shape.geometry, Geometry::Rect(_, _)) {
|
if let Some(Stroke { paint, thickness }) = shape.stroke {
|
||||||
if let Some(Stroke { paint, thickness }) = shape.stroke.top {
|
|
||||||
let paint = paint.into();
|
let paint = paint.into();
|
||||||
let mut stroke = sk::Stroke::default();
|
let mut stroke = sk::Stroke::default();
|
||||||
stroke.width = thickness.to_f32();
|
stroke.width = thickness.to_f32();
|
||||||
canvas.stroke_path(&path, &paint, &stroke, ts, mask);
|
canvas.stroke_path(&path, &paint, &stroke, ts, mask);
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
if let Geometry::Rect(size, radius) = shape.geometry {
|
|
||||||
for (path, stroke) in rect_paths(size, radius, Some(shape.stroke)) {
|
|
||||||
if let Some(stroke) = stroke {
|
|
||||||
render_shape(canvas, ts, mask, &Shape {
|
|
||||||
geometry: Geometry::Path(path),
|
|
||||||
fill: None,
|
|
||||||
stroke: Sides::splat(Some(stroke)),
|
|
||||||
})?;
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(())
|
Some(())
|
||||||
}
|
}
|
||||||
|
211
src/frame.rs
211
src/frame.rs
@ -1,13 +1,11 @@
|
|||||||
//! Finished layouts.
|
//! Finished layouts.
|
||||||
|
|
||||||
use std::fmt::{self, Debug, Formatter, Write};
|
use std::fmt::{self, Debug, Formatter, Write};
|
||||||
use std::mem;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
use crate::font::FaceId;
|
use crate::font::FaceId;
|
||||||
use crate::geom::{
|
use crate::geom::{
|
||||||
Align, Angle, Em, Get, Length, Numeric, Paint, Path, Point, Side, Sides, Size, Spec,
|
Align, Em, Length, Numeric, Paint, Point, Shape, Size, Spec, Transform,
|
||||||
Stroke, Transform,
|
|
||||||
};
|
};
|
||||||
use crate::image::ImageId;
|
use crate::image::ImageId;
|
||||||
use crate::util::{EcoString, MaybeShared};
|
use crate::util::{EcoString, MaybeShared};
|
||||||
@ -299,210 +297,3 @@ pub struct Glyph {
|
|||||||
/// The first character of the glyph's cluster.
|
/// The first character of the glyph's cluster.
|
||||||
pub c: char,
|
pub c: char,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A geometric shape with optional fill and stroke.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
||||||
pub struct Shape {
|
|
||||||
/// The shape's geometry.
|
|
||||||
pub geometry: Geometry,
|
|
||||||
/// The shape's background fill.
|
|
||||||
pub fill: Option<Paint>,
|
|
||||||
/// The shape's border stroke.
|
|
||||||
pub stroke: Sides<Option<Stroke>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A shape's geometry.
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq)]
|
|
||||||
pub enum Geometry {
|
|
||||||
/// A line to a point (relative to its position).
|
|
||||||
Line(Point),
|
|
||||||
/// A rectangle with its origin in the topleft corner and a border radius.
|
|
||||||
Rect(Size, Sides<Length>),
|
|
||||||
/// A ellipse with its origin in the topleft corner.
|
|
||||||
Ellipse(Size),
|
|
||||||
/// A bezier path.
|
|
||||||
Path(Path),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Geometry {
|
|
||||||
/// Fill the geometry without a stroke.
|
|
||||||
pub fn filled(self, fill: Paint) -> Shape {
|
|
||||||
Shape {
|
|
||||||
geometry: self,
|
|
||||||
fill: Some(fill),
|
|
||||||
stroke: Sides::splat(None),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Stroke the geometry without a fill.
|
|
||||||
pub fn stroked(self, stroke: Stroke) -> Shape {
|
|
||||||
Shape {
|
|
||||||
geometry: self,
|
|
||||||
fill: None,
|
|
||||||
stroke: Sides::splat(Some(stroke)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
|
||||||
enum Connection {
|
|
||||||
None,
|
|
||||||
Left,
|
|
||||||
Right,
|
|
||||||
Both,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection {
|
|
||||||
pub fn advance(self, right: bool) -> Self {
|
|
||||||
match self {
|
|
||||||
Self::Right | Self::Both => {
|
|
||||||
if right {
|
|
||||||
Self::Both
|
|
||||||
} else {
|
|
||||||
Self::Left
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Self::Left | Self::None => {
|
|
||||||
if right {
|
|
||||||
Self::Right
|
|
||||||
} else {
|
|
||||||
Self::None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn left(self) -> bool {
|
|
||||||
matches!(self, Self::Left | Self::Both)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn right(self) -> bool {
|
|
||||||
matches!(self, Self::Right | Self::Both)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Draws one side of the rounded rectangle. Will always draw the left arc. The
|
|
||||||
/// right arc will be drawn halfway iff there is no connection.
|
|
||||||
fn draw_side(
|
|
||||||
path: &mut Path,
|
|
||||||
side: Side,
|
|
||||||
size: Size,
|
|
||||||
radius_left: Length,
|
|
||||||
radius_right: Length,
|
|
||||||
connection: Connection,
|
|
||||||
) {
|
|
||||||
let reversed = |angle: Angle, radius, rotate, mirror_x, mirror_y| {
|
|
||||||
let [a, b, c, d] = angle.bezier_arc(radius, rotate, mirror_x, mirror_y);
|
|
||||||
[d, c, b, a]
|
|
||||||
};
|
|
||||||
|
|
||||||
let angle_left = Angle::deg(if connection.left() { 90.0 } else { 45.0 });
|
|
||||||
let angle_right = Angle::deg(if connection.right() { 90.0 } else { 45.0 });
|
|
||||||
|
|
||||||
let (arc1, arc2) = match side {
|
|
||||||
Side::Top => {
|
|
||||||
let arc1 = reversed(angle_left, radius_left, true, true, false)
|
|
||||||
.map(|x| x + Point::with_x(radius_left));
|
|
||||||
let arc2 = (-angle_right)
|
|
||||||
.bezier_arc(radius_right, true, true, false)
|
|
||||||
.map(|x| x + Point::with_x(size.x - radius_right));
|
|
||||||
|
|
||||||
(arc1, arc2)
|
|
||||||
}
|
|
||||||
Side::Right => {
|
|
||||||
let arc1 = reversed(-angle_left, radius_left, false, false, false)
|
|
||||||
.map(|x| x + Point::new(size.x, radius_left));
|
|
||||||
|
|
||||||
let arc2 = angle_right
|
|
||||||
.bezier_arc(radius_right, false, false, false)
|
|
||||||
.map(|x| x + Point::new(size.x, size.y - radius_right));
|
|
||||||
|
|
||||||
(arc1, arc2)
|
|
||||||
}
|
|
||||||
Side::Bottom => {
|
|
||||||
let arc1 = reversed(-angle_left, radius_left, true, false, false)
|
|
||||||
.map(|x| x + Point::new(size.x - radius_left, size.y));
|
|
||||||
|
|
||||||
let arc2 = angle_right
|
|
||||||
.bezier_arc(radius_right, true, false, false)
|
|
||||||
.map(|x| x + Point::new(radius_right, size.y));
|
|
||||||
|
|
||||||
(arc1, arc2)
|
|
||||||
}
|
|
||||||
Side::Left => {
|
|
||||||
let arc1 = reversed(angle_left, radius_left, false, false, true)
|
|
||||||
.map(|x| x + Point::with_y(size.y - radius_left));
|
|
||||||
|
|
||||||
let arc2 = (-angle_right)
|
|
||||||
.bezier_arc(radius_right, false, false, true)
|
|
||||||
.map(|x| x + Point::with_y(radius_right));
|
|
||||||
|
|
||||||
(arc1, arc2)
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if !connection.left() {
|
|
||||||
path.move_to(if radius_left.is_zero() { arc1[3] } else { arc1[0] });
|
|
||||||
}
|
|
||||||
|
|
||||||
if !radius_left.is_zero() {
|
|
||||||
path.cubic_to(arc1[1], arc1[2], arc1[3]);
|
|
||||||
}
|
|
||||||
|
|
||||||
path.line_to(arc2[0]);
|
|
||||||
|
|
||||||
if !connection.right() && !radius_right.is_zero() {
|
|
||||||
path.cubic_to(arc2[1], arc2[2], arc2[3]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rect_paths(
|
|
||||||
size: Size,
|
|
||||||
radius: Sides<Length>,
|
|
||||||
strokes: Option<Sides<Option<Stroke>>>,
|
|
||||||
) -> Vec<(Path, Option<Stroke>)> {
|
|
||||||
let strokes = strokes.unwrap_or_else(|| Sides::splat(None));
|
|
||||||
let mut res = vec![];
|
|
||||||
|
|
||||||
let mut connection = Connection::None;
|
|
||||||
let mut path = Path::new();
|
|
||||||
let sides = [Side::Top, Side::Right, Side::Bottom, Side::Left];
|
|
||||||
let mut always_continuous = true;
|
|
||||||
|
|
||||||
let radius = [
|
|
||||||
radius.left,
|
|
||||||
radius.top,
|
|
||||||
radius.right,
|
|
||||||
radius.bottom,
|
|
||||||
radius.left,
|
|
||||||
];
|
|
||||||
|
|
||||||
for (side, radius) in sides.into_iter().zip(radius.windows(2)) {
|
|
||||||
let stroke_continuity = strokes.get(side) == strokes.get(side.next_cw());
|
|
||||||
connection = connection.advance(stroke_continuity && side != Side::Left);
|
|
||||||
always_continuous &= stroke_continuity;
|
|
||||||
|
|
||||||
draw_side(&mut path, side, size, radius[0], radius[1], connection);
|
|
||||||
|
|
||||||
if !stroke_continuity {
|
|
||||||
res.push((mem::take(&mut path), strokes.get(side)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if always_continuous {
|
|
||||||
path.close_path();
|
|
||||||
}
|
|
||||||
|
|
||||||
if !path.0.is_empty() {
|
|
||||||
res.push((path, strokes.left));
|
|
||||||
}
|
|
||||||
|
|
||||||
res
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn rect_path(size: Size, radius: Sides<Length>) -> Path {
|
|
||||||
let mut paths = rect_paths(size, radius, None);
|
|
||||||
assert_eq!(paths.len(), 1);
|
|
||||||
|
|
||||||
paths.pop().unwrap().0
|
|
||||||
}
|
|
||||||
|
@ -13,6 +13,7 @@ mod paint;
|
|||||||
mod path;
|
mod path;
|
||||||
mod point;
|
mod point;
|
||||||
mod ratio;
|
mod ratio;
|
||||||
|
mod rect;
|
||||||
mod relative;
|
mod relative;
|
||||||
mod scalar;
|
mod scalar;
|
||||||
mod sides;
|
mod sides;
|
||||||
@ -30,6 +31,7 @@ pub use paint::*;
|
|||||||
pub use path::*;
|
pub use path::*;
|
||||||
pub use point::*;
|
pub use point::*;
|
||||||
pub use ratio::*;
|
pub use ratio::*;
|
||||||
|
pub use rect::*;
|
||||||
pub use relative::*;
|
pub use relative::*;
|
||||||
pub use scalar::*;
|
pub use scalar::*;
|
||||||
pub use sides::*;
|
pub use sides::*;
|
||||||
@ -60,6 +62,50 @@ pub trait Get<Index> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A geometric shape with optional fill and stroke.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub struct Shape {
|
||||||
|
/// The shape's geometry.
|
||||||
|
pub geometry: Geometry,
|
||||||
|
/// The shape's background fill.
|
||||||
|
pub fill: Option<Paint>,
|
||||||
|
/// The shape's border stroke.
|
||||||
|
pub stroke: Option<Stroke>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A shape's geometry.
|
||||||
|
#[derive(Debug, Clone, Eq, PartialEq)]
|
||||||
|
pub enum Geometry {
|
||||||
|
/// A line to a point (relative to its position).
|
||||||
|
Line(Point),
|
||||||
|
/// A rectangle with its origin in the topleft corner.
|
||||||
|
Rect(Size),
|
||||||
|
/// A ellipse with its origin in the topleft corner.
|
||||||
|
Ellipse(Size),
|
||||||
|
/// A bezier path.
|
||||||
|
Path(Path),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Geometry {
|
||||||
|
/// Fill the geometry without a stroke.
|
||||||
|
pub fn filled(self, fill: Paint) -> Shape {
|
||||||
|
Shape {
|
||||||
|
geometry: self,
|
||||||
|
fill: Some(fill),
|
||||||
|
stroke: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Stroke the geometry without a fill.
|
||||||
|
pub fn stroked(self, stroke: Stroke) -> Shape {
|
||||||
|
Shape {
|
||||||
|
geometry: self,
|
||||||
|
fill: None,
|
||||||
|
stroke: Some(stroke),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A numeric type.
|
/// A numeric type.
|
||||||
pub trait Numeric:
|
pub trait Numeric:
|
||||||
Sized
|
Sized
|
||||||
|
212
src/geom/rect.rs
Normal file
212
src/geom/rect.rs
Normal file
@ -0,0 +1,212 @@
|
|||||||
|
use super::*;
|
||||||
|
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
/// A rectangle with rounded corners.
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Rect {
|
||||||
|
size: Size,
|
||||||
|
radius: Sides<Length>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rect {
|
||||||
|
/// Create a new rectangle.
|
||||||
|
pub fn new(size: Size, radius: Sides<Length>) -> Self {
|
||||||
|
Self { size, radius }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output all constituent shapes of the rectangle in order. The last one is
|
||||||
|
/// in the foreground. The function will output multiple items if the stroke
|
||||||
|
/// properties differ by side.
|
||||||
|
pub fn shapes(
|
||||||
|
&self,
|
||||||
|
fill: Option<Paint>,
|
||||||
|
stroke: Sides<Option<Stroke>>,
|
||||||
|
) -> Vec<Shape> {
|
||||||
|
let mut res = vec![];
|
||||||
|
if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) {
|
||||||
|
res.push(Shape {
|
||||||
|
geometry: self.fill_geometry(),
|
||||||
|
fill,
|
||||||
|
stroke: stroke.left,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if !stroke.is_uniform() {
|
||||||
|
for (path, stroke) in self.stroke_segments(Some(stroke)) {
|
||||||
|
if !stroke.is_some() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
res.push(Shape {
|
||||||
|
geometry: Geometry::Path(path),
|
||||||
|
fill: None,
|
||||||
|
stroke,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output the minimum number of paths along the rectangles border.
|
||||||
|
fn stroke_segments(
|
||||||
|
&self,
|
||||||
|
strokes: Option<Sides<Option<Stroke>>>,
|
||||||
|
) -> Vec<(Path, Option<Stroke>)> {
|
||||||
|
let strokes = strokes.unwrap_or_else(|| Sides::splat(None));
|
||||||
|
let mut res = vec![];
|
||||||
|
|
||||||
|
let mut connection = Connection::None;
|
||||||
|
let mut path = Path::new();
|
||||||
|
let mut always_continuous = true;
|
||||||
|
|
||||||
|
for side in [Side::Top, Side::Right, Side::Bottom, Side::Left] {
|
||||||
|
let radius = [self.radius.get(side.next_ccw()), self.radius.get(side)];
|
||||||
|
|
||||||
|
let stroke_continuity = strokes.get(side) == strokes.get(side.next_cw());
|
||||||
|
connection = connection.advance(stroke_continuity && side != Side::Left);
|
||||||
|
always_continuous &= stroke_continuity;
|
||||||
|
|
||||||
|
draw_side(&mut path, side, self.size, radius[0], radius[1], connection);
|
||||||
|
|
||||||
|
if !stroke_continuity {
|
||||||
|
res.push((mem::take(&mut path), strokes.get(side)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if always_continuous {
|
||||||
|
path.close_path();
|
||||||
|
}
|
||||||
|
|
||||||
|
if !path.0.is_empty() {
|
||||||
|
res.push((path, strokes.left));
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Output the shape of the rectangle as a path or primitive rectangle,
|
||||||
|
/// depending on whether it is rounded.
|
||||||
|
fn fill_geometry(&self) -> Geometry {
|
||||||
|
if self.radius.iter().copied().all(Length::is_zero) {
|
||||||
|
Geometry::Rect(self.size)
|
||||||
|
} else {
|
||||||
|
let mut paths = self.stroke_segments(None);
|
||||||
|
assert_eq!(paths.len(), 1);
|
||||||
|
|
||||||
|
Geometry::Path(paths.pop().unwrap().0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Draws one side of the rounded rectangle. Will always draw the left arc. The
|
||||||
|
/// right arc will be drawn halfway iff there is no connection.
|
||||||
|
fn draw_side(
|
||||||
|
path: &mut Path,
|
||||||
|
side: Side,
|
||||||
|
size: Size,
|
||||||
|
radius_left: Length,
|
||||||
|
radius_right: Length,
|
||||||
|
connection: Connection,
|
||||||
|
) {
|
||||||
|
let reversed = |angle: Angle, radius, rotate, mirror_x, mirror_y| {
|
||||||
|
let [a, b, c, d] = angle.bezier_arc(radius, rotate, mirror_x, mirror_y);
|
||||||
|
[d, c, b, a]
|
||||||
|
};
|
||||||
|
|
||||||
|
let angle_left = Angle::deg(if connection.left() { 90.0 } else { 45.0 });
|
||||||
|
let angle_right = Angle::deg(if connection.right() { 90.0 } else { 45.0 });
|
||||||
|
|
||||||
|
let (arc1, arc2) = match side {
|
||||||
|
Side::Top => {
|
||||||
|
let arc1 = reversed(angle_left, radius_left, true, true, false)
|
||||||
|
.map(|x| x + Point::with_x(radius_left));
|
||||||
|
let arc2 = (-angle_right)
|
||||||
|
.bezier_arc(radius_right, true, true, false)
|
||||||
|
.map(|x| x + Point::with_x(size.x - radius_right));
|
||||||
|
|
||||||
|
(arc1, arc2)
|
||||||
|
}
|
||||||
|
Side::Right => {
|
||||||
|
let arc1 = reversed(-angle_left, radius_left, false, false, false)
|
||||||
|
.map(|x| x + Point::new(size.x, radius_left));
|
||||||
|
|
||||||
|
let arc2 = angle_right
|
||||||
|
.bezier_arc(radius_right, false, false, false)
|
||||||
|
.map(|x| x + Point::new(size.x, size.y - radius_right));
|
||||||
|
|
||||||
|
(arc1, arc2)
|
||||||
|
}
|
||||||
|
Side::Bottom => {
|
||||||
|
let arc1 = reversed(-angle_left, radius_left, true, false, false)
|
||||||
|
.map(|x| x + Point::new(size.x - radius_left, size.y));
|
||||||
|
|
||||||
|
let arc2 = angle_right
|
||||||
|
.bezier_arc(radius_right, true, false, false)
|
||||||
|
.map(|x| x + Point::new(radius_right, size.y));
|
||||||
|
|
||||||
|
(arc1, arc2)
|
||||||
|
}
|
||||||
|
Side::Left => {
|
||||||
|
let arc1 = reversed(angle_left, radius_left, false, false, true)
|
||||||
|
.map(|x| x + Point::with_y(size.y - radius_left));
|
||||||
|
|
||||||
|
let arc2 = (-angle_right)
|
||||||
|
.bezier_arc(radius_right, false, false, true)
|
||||||
|
.map(|x| x + Point::with_y(radius_right));
|
||||||
|
|
||||||
|
(arc1, arc2)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if !connection.left() {
|
||||||
|
path.move_to(if radius_left.is_zero() { arc1[3] } else { arc1[0] });
|
||||||
|
}
|
||||||
|
|
||||||
|
if !radius_left.is_zero() {
|
||||||
|
path.cubic_to(arc1[1], arc1[2], arc1[3]);
|
||||||
|
}
|
||||||
|
|
||||||
|
path.line_to(arc2[0]);
|
||||||
|
|
||||||
|
if !connection.right() && !radius_right.is_zero() {
|
||||||
|
path.cubic_to(arc2[1], arc2[2], arc2[3]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
|
||||||
|
enum Connection {
|
||||||
|
None,
|
||||||
|
Left,
|
||||||
|
Right,
|
||||||
|
Both,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Connection {
|
||||||
|
pub fn advance(self, right: bool) -> Self {
|
||||||
|
match self {
|
||||||
|
Self::Right | Self::Both => {
|
||||||
|
if right {
|
||||||
|
Self::Both
|
||||||
|
} else {
|
||||||
|
Self::Left
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::Left | Self::None => {
|
||||||
|
if right {
|
||||||
|
Self::Right
|
||||||
|
} else {
|
||||||
|
Self::None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn left(self) -> bool {
|
||||||
|
matches!(self, Self::Left | Self::Both)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn right(self) -> bool {
|
||||||
|
matches!(self, Self::Right | Self::Both)
|
||||||
|
}
|
||||||
|
}
|
@ -150,7 +150,7 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
|
|
||||||
// Add fill and/or stroke.
|
// Add fill and/or stroke.
|
||||||
let fill = styles.get(Self::FILL);
|
let fill = styles.get(Self::FILL);
|
||||||
let mut stroke = match styles.get(Self::STROKE) {
|
let stroke = match styles.get(Self::STROKE) {
|
||||||
Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())),
|
Smart::Auto if fill.is_none() => Sides::splat(Some(Stroke::default())),
|
||||||
Smart::Auto => Sides::splat(None),
|
Smart::Auto => Sides::splat(None),
|
||||||
Smart::Custom(strokes) => {
|
Smart::Custom(strokes) => {
|
||||||
@ -179,15 +179,23 @@ impl<const S: ShapeKind> Layout for ShapeNode<S> {
|
|||||||
bottom: radius.bottom.relative_to(size.y / 2.0),
|
bottom: radius.bottom.relative_to(size.y / 2.0),
|
||||||
};
|
};
|
||||||
|
|
||||||
if fill.is_some() || (stroke.iter().any(Option::is_some) && stroke.is_uniform()) {
|
let pos = Point::new(-outset.left, -outset.top);
|
||||||
let geometry = if is_round(S) {
|
|
||||||
Geometry::Ellipse(size)
|
|
||||||
} else {
|
|
||||||
Geometry::Rect(size, radius)
|
|
||||||
};
|
|
||||||
|
|
||||||
let shape = Shape { geometry, fill, stroke };
|
if fill.is_some() || stroke.iter().any(Option::is_some) {
|
||||||
frame.prepend(Point::new(-outset.left, -outset.top), Element::Shape(shape));
|
if is_round(S) {
|
||||||
|
let shape = Shape {
|
||||||
|
geometry: Geometry::Ellipse(size),
|
||||||
|
fill,
|
||||||
|
stroke: stroke.left,
|
||||||
|
};
|
||||||
|
frame.prepend(pos, Element::Shape(shape));
|
||||||
|
} else {
|
||||||
|
for shape in
|
||||||
|
Rect::new(size, radius).shapes(fill, stroke).into_iter().rev()
|
||||||
|
{
|
||||||
|
frame.prepend(pos, Element::Shape(shape));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply link if it exists.
|
// Apply link if it exists.
|
||||||
|
@ -8,8 +8,10 @@ use std::sync::Arc;
|
|||||||
use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry};
|
use super::{Barrier, NodeId, Resolve, StyleChain, StyleEntry};
|
||||||
use crate::diag::TypResult;
|
use crate::diag::TypResult;
|
||||||
use crate::eval::{RawAlign, RawLength};
|
use crate::eval::{RawAlign, RawLength};
|
||||||
use crate::frame::{Element, Frame, Geometry};
|
use crate::frame::{Element, Frame};
|
||||||
use crate::geom::{Align, Length, Paint, Point, Relative, Sides, Size, Spec, Stroke};
|
use crate::geom::{
|
||||||
|
Align, Geometry, Length, Paint, Point, Relative, Sides, Size, Spec, Stroke,
|
||||||
|
};
|
||||||
use crate::library::graphics::MoveNode;
|
use crate::library::graphics::MoveNode;
|
||||||
use crate::library::layout::{AlignNode, PadNode};
|
use crate::library::layout::{AlignNode, PadNode};
|
||||||
use crate::util::Prehashed;
|
use crate::util::Prehashed;
|
||||||
@ -353,8 +355,7 @@ impl Layout for FillNode {
|
|||||||
) -> TypResult<Vec<Arc<Frame>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let mut frames = self.child.layout(ctx, regions, styles)?;
|
let mut frames = self.child.layout(ctx, regions, styles)?;
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
let shape = Geometry::Rect(frame.size, Sides::splat(Length::zero()))
|
let shape = Geometry::Rect(frame.size).filled(self.fill);
|
||||||
.filled(self.fill);
|
|
||||||
Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
||||||
}
|
}
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
@ -379,8 +380,7 @@ impl Layout for StrokeNode {
|
|||||||
) -> TypResult<Vec<Arc<Frame>>> {
|
) -> TypResult<Vec<Arc<Frame>>> {
|
||||||
let mut frames = self.child.layout(ctx, regions, styles)?;
|
let mut frames = self.child.layout(ctx, regions, styles)?;
|
||||||
for frame in &mut frames {
|
for frame in &mut frames {
|
||||||
let shape = Geometry::Rect(frame.size, Sides::splat(Length::zero()))
|
let shape = Geometry::Rect(frame.size).stroked(self.stroke);
|
||||||
.stroked(self.stroke);
|
|
||||||
Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
Arc::make_mut(frame).prepend(Point::zero(), Element::Shape(shape));
|
||||||
}
|
}
|
||||||
Ok(frames)
|
Ok(frames)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user