mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2025-12-23 10:11:54 +00:00
Merge branch 'master' into fix-rgb-decimal-sliding
This commit is contained in:
commit
b04d8c057b
5 changed files with 328 additions and 11 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -6716,6 +6716,7 @@ dependencies = [
|
|||
"bytemuck",
|
||||
"core-types",
|
||||
"dyn-any",
|
||||
"fixedbitset",
|
||||
"glam",
|
||||
"kurbo 0.12.0",
|
||||
"log",
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@ use graphic_types::vector_types::vector::click_target::{ClickTarget, FreePoint};
|
|||
use graphic_types::vector_types::vector::style::{Fill, PaintOrder, RenderMode, Stroke, StrokeAlign};
|
||||
use graphic_types::{Artboard, Graphic};
|
||||
use kurbo::Affine;
|
||||
use kurbo::Shape;
|
||||
use num_traits::Zero;
|
||||
use std::collections::{HashMap, HashSet};
|
||||
use std::fmt::Write;
|
||||
|
|
@ -729,10 +730,10 @@ impl Render for Table<Vector> {
|
|||
let can_draw_aligned_stroke = path_is_closed && vector.style.stroke().is_some_and(|stroke| stroke.has_renderable_stroke() && stroke.align.is_not_centered());
|
||||
let can_use_paint_order = !(row.element.style.fill().is_none() || !row.element.style.fill().is_opaque() || mask_type == MaskType::Clip);
|
||||
|
||||
let needs_separate_fill = can_draw_aligned_stroke && !can_use_paint_order;
|
||||
let needs_separate_alignment_fill = can_draw_aligned_stroke && !can_use_paint_order;
|
||||
let wants_stroke_below = vector.style.stroke().map(|s| s.paint_order) == Some(PaintOrder::StrokeBelow);
|
||||
|
||||
if needs_separate_fill && !wants_stroke_below {
|
||||
if needs_separate_alignment_fill && !wants_stroke_below {
|
||||
render.leaf_tag("path", |attributes| {
|
||||
attributes.push("d", path.clone());
|
||||
let matrix = format_transform_matrix(element_transform);
|
||||
|
|
@ -753,7 +754,7 @@ impl Render for Table<Vector> {
|
|||
});
|
||||
}
|
||||
|
||||
let push_id = needs_separate_fill.then_some({
|
||||
let push_id = needs_separate_alignment_fill.then_some({
|
||||
let id = format!("alignment-{}", generate_uuid());
|
||||
|
||||
let mut element = row.element.clone();
|
||||
|
|
@ -770,6 +771,32 @@ impl Render for Table<Vector> {
|
|||
(id, mask_type, vector_row)
|
||||
});
|
||||
|
||||
if vector.is_branching() {
|
||||
for mut face_path in vector.construct_faces().filter(|face| !(face.area() < 0.0)) {
|
||||
face_path.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
|
||||
|
||||
let face_d = face_path.to_svg();
|
||||
render.leaf_tag("path", |attributes| {
|
||||
attributes.push("d", face_d.clone());
|
||||
let matrix = format_transform_matrix(element_transform);
|
||||
if !matrix.is_empty() {
|
||||
attributes.push("transform", matrix);
|
||||
}
|
||||
let mut style = row.element.style.clone();
|
||||
style.clear_stroke();
|
||||
let fill_only = style.render(
|
||||
&mut attributes.0.svg_defs,
|
||||
element_transform,
|
||||
applied_stroke_transform,
|
||||
bounds_matrix,
|
||||
transformed_bounds_matrix,
|
||||
render_params,
|
||||
);
|
||||
attributes.push_val(fill_only);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
render.leaf_tag("path", |attributes| {
|
||||
attributes.push("d", path.clone());
|
||||
let matrix = format_transform_matrix(element_transform);
|
||||
|
|
@ -807,7 +834,7 @@ impl Render for Table<Vector> {
|
|||
render_params.override_paint_order = can_draw_aligned_stroke && can_use_paint_order;
|
||||
|
||||
let mut style = row.element.style.clone();
|
||||
if needs_separate_fill {
|
||||
if needs_separate_alignment_fill || vector.is_branching() {
|
||||
style.clear_fill();
|
||||
}
|
||||
|
||||
|
|
@ -830,7 +857,7 @@ impl Render for Table<Vector> {
|
|||
});
|
||||
|
||||
// When splitting passes and stroke is below, draw the fill after the stroke.
|
||||
if needs_separate_fill && wants_stroke_below {
|
||||
if needs_separate_alignment_fill && wants_stroke_below {
|
||||
render.leaf_tag("path", |attributes| {
|
||||
attributes.push("d", path);
|
||||
let matrix = format_transform_matrix(element_transform);
|
||||
|
|
@ -916,10 +943,10 @@ impl Render for Table<Vector> {
|
|||
let wants_stroke_below = row.element.style.stroke().is_some_and(|s| s.paint_order == vector::style::PaintOrder::StrokeBelow);
|
||||
|
||||
// Closures to avoid duplicated fill/stroke drawing logic
|
||||
let do_fill = |scene: &mut Scene| match row.element.style.fill() {
|
||||
let do_fill_path = |scene: &mut Scene, path: &kurbo::BezPath| match row.element.style.fill() {
|
||||
Fill::Solid(color) => {
|
||||
let fill = peniko::Brush::Solid(peniko::Color::new([color.r(), color.g(), color.b(), color.a()]));
|
||||
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, &path);
|
||||
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, None, path);
|
||||
}
|
||||
Fill::Gradient(gradient) => {
|
||||
let mut stops = peniko::ColorStops::new();
|
||||
|
|
@ -971,11 +998,28 @@ impl Render for Table<Vector> {
|
|||
Default::default()
|
||||
};
|
||||
let brush_transform = kurbo::Affine::new((inverse_element_transform * parent_transform).to_cols_array());
|
||||
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), &path);
|
||||
scene.fill(peniko::Fill::NonZero, kurbo::Affine::new(element_transform.to_cols_array()), &fill, Some(brush_transform), path);
|
||||
}
|
||||
Fill::None => {}
|
||||
};
|
||||
|
||||
let do_fill = |scene: &mut Scene| {
|
||||
if row.element.is_branching() {
|
||||
// For branching paths, fill each face separately
|
||||
for mut face_path in row.element.construct_faces().filter(|face| !(face.area() < 0.0)) {
|
||||
face_path.apply_affine(Affine::new(applied_stroke_transform.to_cols_array()));
|
||||
let mut kurbo_path = kurbo::BezPath::new();
|
||||
for element in face_path {
|
||||
kurbo_path.push(element);
|
||||
}
|
||||
do_fill_path(scene, &kurbo_path);
|
||||
}
|
||||
} else {
|
||||
// Simple fill of the entire path
|
||||
do_fill_path(scene, &path);
|
||||
}
|
||||
};
|
||||
|
||||
let do_stroke = |scene: &mut Scene, width_scale: f64| {
|
||||
if let Some(stroke) = row.element.style.stroke() {
|
||||
let color = match stroke.color {
|
||||
|
|
@ -1090,7 +1134,7 @@ impl Render for Table<Vector> {
|
|||
false => [Op::Fill, Op::Stroke], // Default
|
||||
};
|
||||
|
||||
for operation in order {
|
||||
for operation in &order {
|
||||
match operation {
|
||||
Op::Fill => do_fill(scene),
|
||||
Op::Stroke => do_stroke(scene, 1.),
|
||||
|
|
|
|||
|
|
@ -31,3 +31,4 @@ tinyvec = { workspace = true }
|
|||
|
||||
# Optional workspace dependencies
|
||||
serde = { workspace = true, optional = true }
|
||||
fixedbitset = "0.5.7"
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ use crate::subpath::{BezierHandles, ManipulatorGroup};
|
|||
use crate::vector::{SegmentId, Vector};
|
||||
use dyn_any::DynAny;
|
||||
use glam::DVec2;
|
||||
use kurbo::{BezPath, CubicBez, Line, ParamCurve, PathSeg, Point, QuadBez};
|
||||
use kurbo::{BezPath, CubicBez, Line, ParamCurve, ParamCurveDeriv, PathSeg, Point, QuadBez};
|
||||
use std::ops::Sub;
|
||||
|
||||
/// Represents different geometric interpretations of calculating the centroid (center of mass).
|
||||
|
|
@ -246,6 +246,93 @@ pub fn pathseg_abs_diff_eq(seg1: PathSeg, seg2: PathSeg, max_abs_diff: f64) -> b
|
|||
|
||||
seg1_points.len() == seg2_points.len() && seg1_points.into_iter().zip(seg2_points).all(|(a, b)| cmp(a.x, b.x) && cmp(a.y, b.y))
|
||||
}
|
||||
pub trait Tangent {
|
||||
fn tangent_at(&self, t: f64) -> DVec2;
|
||||
|
||||
fn tangent_at_start(&self) -> DVec2 {
|
||||
self.tangent_at(0.0)
|
||||
}
|
||||
|
||||
fn tangent_at_end(&self) -> DVec2 {
|
||||
self.tangent_at(1.0)
|
||||
}
|
||||
}
|
||||
|
||||
trait ControlPoints {
|
||||
type Points: AsRef<[Point]>;
|
||||
fn control_points(&self) -> Self::Points;
|
||||
}
|
||||
|
||||
impl ControlPoints for kurbo::Line {
|
||||
type Points = [Point; 2];
|
||||
fn control_points(&self) -> Self::Points {
|
||||
[self.p0, self.p1]
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlPoints for kurbo::QuadBez {
|
||||
type Points = [Point; 3];
|
||||
fn control_points(&self) -> Self::Points {
|
||||
[self.p0, self.p1, self.p2]
|
||||
}
|
||||
}
|
||||
|
||||
impl ControlPoints for kurbo::CubicBez {
|
||||
type Points = [Point; 4];
|
||||
fn control_points(&self) -> Self::Points {
|
||||
[self.p0, self.p1, self.p2, self.p3]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: ControlPoints + ParamCurveDeriv> Tangent for T {
|
||||
fn tangent_at(&self, t: f64) -> DVec2 {
|
||||
point_to_dvec2(self.deriv().eval(t))
|
||||
}
|
||||
|
||||
fn tangent_at_start(&self) -> DVec2 {
|
||||
let pts = self.control_points();
|
||||
let pts = pts.as_ref();
|
||||
let mut iter = pts.iter();
|
||||
iter.next()
|
||||
.and_then(|&start| iter.find(|&&p| p != start).map(|&p| DVec2 { x: p.x - start.x, y: p.y - start.y }))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
|
||||
fn tangent_at_end(&self) -> DVec2 {
|
||||
let pts = self.control_points();
|
||||
let pts = pts.as_ref();
|
||||
let mut iter = pts.iter().rev();
|
||||
iter.next()
|
||||
.and_then(|&end| iter.find(|&&p| p != end).map(|&p| DVec2 { x: end.x - p.x, y: end.y - p.y }))
|
||||
.unwrap_or_default()
|
||||
}
|
||||
}
|
||||
|
||||
impl Tangent for kurbo::PathSeg {
|
||||
fn tangent_at(&self, t: f64) -> DVec2 {
|
||||
match self {
|
||||
PathSeg::Line(line) => line.tangent_at(t),
|
||||
PathSeg::Quad(quad) => quad.tangent_at(t),
|
||||
PathSeg::Cubic(cubic) => cubic.tangent_at(t),
|
||||
}
|
||||
}
|
||||
|
||||
fn tangent_at_start(&self) -> DVec2 {
|
||||
match self {
|
||||
PathSeg::Line(line) => line.tangent_at_start(),
|
||||
PathSeg::Quad(quad) => quad.tangent_at_start(),
|
||||
PathSeg::Cubic(cubic) => cubic.tangent_at_start(),
|
||||
}
|
||||
}
|
||||
|
||||
fn tangent_at_end(&self) -> DVec2 {
|
||||
match self {
|
||||
PathSeg::Line(line) => line.tangent_at_end(),
|
||||
PathSeg::Quad(quad) => quad.tangent_at_end(),
|
||||
PathSeg::Cubic(cubic) => cubic.tangent_at_end(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A selectable part of a curve, either an anchor (start or end of a bézier) or a handle (doesn't necessarily go through the bézier but influences curvature).
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, DynAny, serde::Serialize, serde::Deserialize)]
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
use crate::subpath::{Bezier, BezierHandles, Identifier, ManipulatorGroup, Subpath};
|
||||
use crate::vector::misc::{HandleId, dvec2_to_point};
|
||||
use crate::vector::misc::{HandleId, Tangent, dvec2_to_point};
|
||||
use crate::vector::vector_types::Vector;
|
||||
use dyn_any::DynAny;
|
||||
use fixedbitset::FixedBitSet;
|
||||
use glam::{DAffine2, DVec2};
|
||||
use kurbo::{CubicBez, Line, PathSeg, QuadBez};
|
||||
use std::collections::HashMap;
|
||||
|
|
@ -684,6 +685,113 @@ impl FoundSubpath {
|
|||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
|
||||
struct FaceSide {
|
||||
segment_index: usize,
|
||||
reversed: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct FaceSideSet {
|
||||
set: FixedBitSet,
|
||||
}
|
||||
impl FaceSideSet {
|
||||
fn new(size: usize) -> Self {
|
||||
Self {
|
||||
set: FixedBitSet::with_capacity(size * 2),
|
||||
}
|
||||
}
|
||||
|
||||
fn index(&self, side: FaceSide) -> usize {
|
||||
(side.segment_index << 1) | (side.reversed as usize)
|
||||
}
|
||||
|
||||
fn insert(&mut self, side: FaceSide) {
|
||||
self.set.insert(self.index(side));
|
||||
}
|
||||
|
||||
fn contains(&self, side: FaceSide) -> bool {
|
||||
self.set.contains(self.index(side))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
struct Faces {
|
||||
sides: Vec<FaceSide>,
|
||||
face_start: Vec<usize>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct FaceIterator<'a, Upstream> {
|
||||
vector: &'a Vector<Upstream>,
|
||||
faces: Faces,
|
||||
current_face: usize,
|
||||
}
|
||||
|
||||
impl<Upstream> FaceIterator<'_, Upstream> {
|
||||
fn new<'a>(faces: Faces, vector: &'a Vector<Upstream>) -> FaceIterator<'a, Upstream> {
|
||||
FaceIterator { vector, faces, current_face: 0 }
|
||||
}
|
||||
|
||||
fn get_point(&self, point: usize) -> kurbo::Point {
|
||||
dvec2_to_point(self.vector.point_domain.positions()[point])
|
||||
}
|
||||
}
|
||||
|
||||
impl<Upstream> Iterator for FaceIterator<'_, Upstream> {
|
||||
type Item = kurbo::BezPath;
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let start_side = self.faces.face_start.get(self.current_face).copied()?;
|
||||
self.current_face += 1;
|
||||
let end_side = self.faces.face_start.get(self.current_face).copied().unwrap_or(self.faces.sides.len());
|
||||
|
||||
let mut path = kurbo::BezPath::new();
|
||||
|
||||
let segment_domain = &self.vector.segment_domain;
|
||||
let first_side = self.faces.sides.get(start_side)?;
|
||||
let start_point_index = if first_side.reversed {
|
||||
segment_domain.end_point[first_side.segment_index]
|
||||
} else {
|
||||
segment_domain.start_point[first_side.segment_index]
|
||||
};
|
||||
path.move_to(self.get_point(start_point_index));
|
||||
for side in &self.faces.sides[start_side..end_side] {
|
||||
let (handle, end_index) = match side.reversed {
|
||||
false => (segment_domain.handles[side.segment_index], segment_domain.end_point[side.segment_index]),
|
||||
true => (segment_domain.handles[side.segment_index].reversed(), segment_domain.start_point[side.segment_index]),
|
||||
};
|
||||
let path_element = match handle {
|
||||
BezierHandles::Linear => kurbo::PathEl::LineTo(self.get_point(end_index)),
|
||||
BezierHandles::Quadratic { handle } => kurbo::PathEl::QuadTo(dvec2_to_point(handle), self.get_point(end_index)),
|
||||
BezierHandles::Cubic { handle_start, handle_end } => kurbo::PathEl::CurveTo(dvec2_to_point(handle_start), dvec2_to_point(handle_end), self.get_point(end_index)),
|
||||
};
|
||||
path.push(path_element);
|
||||
}
|
||||
|
||||
Some(path)
|
||||
}
|
||||
}
|
||||
|
||||
impl Faces {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
sides: Vec::new(),
|
||||
face_start: Vec::new(),
|
||||
}
|
||||
}
|
||||
pub fn add_side(&mut self, side: FaceSide) {
|
||||
self.sides.push(side);
|
||||
}
|
||||
pub fn start_new_face(&mut self) {
|
||||
self.face_start.push(self.sides.len());
|
||||
}
|
||||
pub fn backtrack(&mut self) {
|
||||
if let Some(last_start) = self.face_start.pop() {
|
||||
self.sides.truncate(last_start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Upstream> Vector<Upstream> {
|
||||
/// Construct a [`kurbo::PathSeg`] by resolving the points from their ids.
|
||||
fn path_segment_from_index(&self, start: usize, end: usize, handles: BezierHandles) -> PathSeg {
|
||||
|
|
@ -989,6 +1097,82 @@ impl<Upstream> Vector<Upstream> {
|
|||
self.segment_domain.map_ids(&id_map);
|
||||
self.region_domain.map_ids(&id_map);
|
||||
}
|
||||
|
||||
pub fn is_branching(&self) -> bool {
|
||||
(0..self.point_domain.len()).any(|point_index| self.segment_domain.connected_count(point_index) > 2)
|
||||
}
|
||||
|
||||
pub fn construct_faces(&self) -> FaceIterator<'_, Upstream> {
|
||||
let mut adjacency: Vec<Vec<FaceSide>> = vec![Vec::new(); self.point_domain.len()];
|
||||
for (segment_index, (&start, &end)) in self.segment_domain.start_point.iter().zip(&self.segment_domain.end_point).enumerate() {
|
||||
adjacency[start].push(FaceSide { segment_index, reversed: false });
|
||||
adjacency[end].push(FaceSide { segment_index, reversed: true });
|
||||
}
|
||||
|
||||
for neighbors in &mut adjacency {
|
||||
neighbors.sort_by(|a, b| {
|
||||
let angle = [a, b].map(|side| {
|
||||
let curve = PathSeg::from(self.path_segment_from_index(
|
||||
self.segment_domain.start_point[side.segment_index],
|
||||
self.segment_domain.end_point[side.segment_index],
|
||||
self.segment_domain.handles[side.segment_index],
|
||||
));
|
||||
let curve = if side.reversed { curve.reverse() } else { curve };
|
||||
let tangent = curve.tangent_at_start();
|
||||
tangent.y.atan2(tangent.x)
|
||||
});
|
||||
angle[0].partial_cmp(&angle[1]).unwrap_or(std::cmp::Ordering::Equal)
|
||||
})
|
||||
}
|
||||
|
||||
let mut faces: Faces = Faces::new();
|
||||
let mut seen = FaceSideSet::new(self.segment_domain.id.len());
|
||||
|
||||
for segment_index in 0..self.segment_domain.id.len() {
|
||||
for &reversed in &[false, true] {
|
||||
let side = FaceSide { segment_index, reversed };
|
||||
if seen.contains(side) {
|
||||
continue;
|
||||
}
|
||||
if (self.construct_face(&adjacency, side, &mut faces, &mut seen)).is_none() {
|
||||
faces.backtrack();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return FaceIterator::new(faces, self);
|
||||
}
|
||||
|
||||
fn construct_face(&self, adjacency: &Vec<Vec<FaceSide>>, first: FaceSide, faces: &mut Faces, seen: &mut FaceSideSet) -> Option<()> {
|
||||
faces.start_new_face();
|
||||
let max_iterations = self.segment_domain.id.len() * 2;
|
||||
let mut side = first;
|
||||
for _iteration in 1..max_iterations {
|
||||
if seen.contains(side) {
|
||||
log::debug!("Encountered seen side {:?}, aborting face construction", side);
|
||||
return None;
|
||||
}
|
||||
seen.insert(side);
|
||||
faces.add_side(side.clone());
|
||||
let next_vertex = if side.reversed {
|
||||
self.segment_domain.start_point[side.segment_index]
|
||||
} else {
|
||||
self.segment_domain.end_point[side.segment_index]
|
||||
};
|
||||
let neighbors = &adjacency[next_vertex];
|
||||
let side_index = neighbors.iter().position(|s| {
|
||||
FaceSide {
|
||||
segment_index: s.segment_index,
|
||||
reversed: !s.reversed,
|
||||
} == side
|
||||
})?;
|
||||
side = neighbors[(side_index + 1) % neighbors.len()];
|
||||
if side == first {
|
||||
return Some(());
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue