mirror of
https://github.com/GraphiteEditor/Graphite.git
synced 2026-09-20 03:18:06 +08:00
Merge branch 'master' into grid_shape
This commit is contained in:
@@ -835,6 +835,20 @@ impl Color {
|
||||
[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8, (gamma.alpha * 255.) as u8]
|
||||
}
|
||||
|
||||
/// Return the all RGB components as a u8 slice, first component is red, followed by green, followed by blue. Use this if the [`Color`] is in linear space.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use graphene_core::color::Color;
|
||||
/// let color = Color::from_rgbaf32(0.114, 0.103, 0.98, 0.97).unwrap();
|
||||
/// // TODO: Add test
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn to_rgb8_srgb(&self) -> [u8; 3] {
|
||||
let gamma = self.to_gamma_srgb();
|
||||
[(gamma.red * 255.) as u8, (gamma.green * 255.) as u8, (gamma.blue * 255.) as u8]
|
||||
}
|
||||
|
||||
// https://www.niwa.nu/2013/05/math-behind-colorspace-conversions-rgb-hsl/
|
||||
/// Convert a [Color] to a hue, saturation, lightness and alpha (all between 0 and 1)
|
||||
///
|
||||
|
||||
@@ -27,6 +27,24 @@ impl<T> Instances<T> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_instance(instance: Instance<T>) -> Self {
|
||||
Self {
|
||||
instance: vec![instance.instance],
|
||||
transform: vec![instance.transform],
|
||||
alpha_blending: vec![instance.alpha_blending],
|
||||
source_node_id: vec![instance.source_node_id],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self {
|
||||
instance: Vec::with_capacity(capacity),
|
||||
transform: Vec::with_capacity(capacity),
|
||||
alpha_blending: Vec::with_capacity(capacity),
|
||||
source_node_id: Vec::with_capacity(capacity),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push(&mut self, instance: Instance<T>) {
|
||||
self.instance.push(instance.instance);
|
||||
self.transform.push(instance.transform);
|
||||
@@ -161,6 +179,18 @@ unsafe impl<T: StaticType + 'static> StaticType for Instances<T> {
|
||||
type Static = Instances<T>;
|
||||
}
|
||||
|
||||
impl<T> FromIterator<Instance<T>> for Instances<T> {
|
||||
fn from_iter<I: IntoIterator<Item = Instance<T>>>(iter: I) -> Self {
|
||||
let iter = iter.into_iter();
|
||||
let (lower, _) = iter.size_hint();
|
||||
let mut instances = Self::with_capacity(lower);
|
||||
for instance in iter {
|
||||
instances.push(instance);
|
||||
}
|
||||
instances
|
||||
}
|
||||
}
|
||||
|
||||
fn one_daffine2_default() -> Vec<DAffine2> {
|
||||
vec![DAffine2::IDENTITY]
|
||||
}
|
||||
|
||||
@@ -10,10 +10,18 @@ use crate::{Context, Ctx};
|
||||
use glam::{DAffine2, DVec2};
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, VectorDataTable, DAffine2)] value: T) -> String {
|
||||
fn to_string<T: std::fmt::Debug>(_: impl Ctx, #[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, VectorDataTable)] value: T) -> String {
|
||||
format!("{:?}", value)
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn serialize<T: serde::Serialize>(
|
||||
_: impl Ctx,
|
||||
#[implementations(String, bool, f64, u32, u64, DVec2, DAffine2, Color, Option<Color>, GraphicGroupTable, VectorDataTable, RasterDataTable<CPU>)] value: T,
|
||||
) -> String {
|
||||
serde_json::to_string(&value).unwrap_or_else(|_| "Serialization Error".to_string())
|
||||
}
|
||||
|
||||
#[node_macro::node(category("Text"))]
|
||||
fn string_concatenate(_: impl Ctx, #[implementations(String)] first: String, second: TextArea) -> String {
|
||||
first.clone() + &second
|
||||
|
||||
@@ -50,16 +50,14 @@ impl PathBuilder {
|
||||
}
|
||||
|
||||
if per_glyph_instances {
|
||||
if !self.glyph_subpaths.is_empty() {
|
||||
self.vector_table.push(Instance {
|
||||
instance: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
|
||||
transform: DAffine2::from_translation(glyph_offset),
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
} else if !self.glyph_subpaths.is_empty() {
|
||||
for subpath in self.glyph_subpaths.iter() {
|
||||
// Unwrapping here is ok, since the check above guarantees there is at least one `VectorData`
|
||||
self.vector_table.push(Instance {
|
||||
instance: VectorData::from_subpaths(core::mem::take(&mut self.glyph_subpaths), false),
|
||||
transform: DAffine2::from_translation(glyph_offset),
|
||||
..Default::default()
|
||||
});
|
||||
} else {
|
||||
for subpath in self.glyph_subpaths.drain(..) {
|
||||
// Unwrapping here is ok because `self.vector_table` is initialized with a single `VectorData`
|
||||
self.vector_table.get_mut(0).unwrap().instance.append_subpath(subpath, false);
|
||||
}
|
||||
}
|
||||
@@ -128,18 +126,26 @@ fn render_glyph_run(glyph_run: &GlyphRun<'_, ()>, path_builder: &mut PathBuilder
|
||||
|
||||
// User-requested tilt applied around baseline to avoid vertical displacement
|
||||
// Translation ensures rotation point is at the baseline, not origin
|
||||
let skew = DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
||||
* DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.])
|
||||
* DAffine2::from_translation(DVec2::new(0., -run_y as f64));
|
||||
let skew = if per_glyph_instances {
|
||||
DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.])
|
||||
} else {
|
||||
DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
||||
* DAffine2::from_cols_array(&[1., 0., -tilt.to_radians().tan(), 1., 0., 0.])
|
||||
* DAffine2::from_translation(DVec2::new(0., -run_y as f64))
|
||||
};
|
||||
|
||||
let synthesis = run.synthesis();
|
||||
|
||||
// Font synthesis (e.g., synthetic italic) applied separately from user transforms
|
||||
// This preserves the distinction between font styling and user transformations
|
||||
let style_skew = synthesis.skew().map(|angle| {
|
||||
DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
||||
* DAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.])
|
||||
* DAffine2::from_translation(DVec2::new(0., -run_y as f64))
|
||||
if per_glyph_instances {
|
||||
DAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.])
|
||||
} else {
|
||||
DAffine2::from_translation(DVec2::new(0., run_y as f64))
|
||||
* DAffine2::from_cols_array(&[1., 0., -angle.to_radians().tan() as f64, 1., 0., 0.])
|
||||
* DAffine2::from_translation(DVec2::new(0., -run_y as f64))
|
||||
}
|
||||
});
|
||||
|
||||
let font = run.font();
|
||||
|
||||
@@ -182,7 +182,7 @@ where
|
||||
A::Item: Clone,
|
||||
B::Item: Clone,
|
||||
{
|
||||
a.flat_map(move |i| (b.clone().map(move |j| (i.clone(), j))))
|
||||
a.flat_map(move |i| b.clone().map(move |j| (i.clone(), j)))
|
||||
}
|
||||
|
||||
/// A square (represented by its top left corner position and width/height of `square_size`) that is currently a candidate for targetting by the dart throwing process.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
use bezier_rs::BezierHandles;
|
||||
use bezier_rs::{BezierHandles, ManipulatorGroup, Subpath};
|
||||
use dyn_any::DynAny;
|
||||
use glam::DVec2;
|
||||
use kurbo::{CubicBez, Line, PathSeg, Point, QuadBez};
|
||||
use kurbo::{BezPath, CubicBez, Line, PathSeg, Point, QuadBez};
|
||||
|
||||
use super::PointId;
|
||||
|
||||
/// Represents different ways of calculating the centroid.
|
||||
#[derive(Default, Debug, Clone, Copy, PartialEq, Eq, serde::Serialize, serde::Deserialize, Hash, DynAny, specta::Type, node_macro::ChoiceType)]
|
||||
@@ -138,3 +140,39 @@ pub fn handles_to_segment(start: DVec2, handles: BezierHandles, end: DVec2) -> P
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn subpath_to_kurbo_bezpath(subpath: Subpath<PointId>) -> BezPath {
|
||||
let maniputor_groups = subpath.manipulator_groups();
|
||||
let closed = subpath.closed();
|
||||
bezpath_from_manipulator_groups(maniputor_groups, closed)
|
||||
}
|
||||
|
||||
pub fn bezpath_from_manipulator_groups(manipulator_groups: &[ManipulatorGroup<PointId>], closed: bool) -> BezPath {
|
||||
let mut bezpath = kurbo::BezPath::new();
|
||||
let mut out_handle;
|
||||
|
||||
let Some(first) = manipulator_groups.first() else { return bezpath };
|
||||
bezpath.move_to(dvec2_to_point(first.anchor));
|
||||
out_handle = first.out_handle;
|
||||
|
||||
for manipulator in manipulator_groups.iter().skip(1) {
|
||||
match (out_handle, manipulator.in_handle) {
|
||||
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(manipulator.anchor)),
|
||||
(None, None) => bezpath.line_to(dvec2_to_point(manipulator.anchor)),
|
||||
(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
|
||||
(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(manipulator.anchor)),
|
||||
}
|
||||
out_handle = manipulator.out_handle;
|
||||
}
|
||||
|
||||
if closed {
|
||||
match (out_handle, first.in_handle) {
|
||||
(Some(handle_start), Some(handle_end)) => bezpath.curve_to(dvec2_to_point(handle_start), dvec2_to_point(handle_end), dvec2_to_point(first.anchor)),
|
||||
(None, None) => bezpath.line_to(dvec2_to_point(first.anchor)),
|
||||
(None, Some(handle)) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
|
||||
(Some(handle), None) => bezpath.quad_to(dvec2_to_point(handle), dvec2_to_point(first.anchor)),
|
||||
}
|
||||
bezpath.close_path();
|
||||
}
|
||||
bezpath
|
||||
}
|
||||
|
||||
@@ -337,7 +337,7 @@ impl VectorData {
|
||||
/// Returns the number of linear segments connected to the given point.
|
||||
pub fn connected_linear_segments(&self, point_id: PointId) -> usize {
|
||||
self.segment_bezier_iter()
|
||||
.filter(|(_, bez, start, end)| ((*start == point_id || *end == point_id) && matches!(bez.handles, BezierHandles::Linear)))
|
||||
.filter(|(_, bez, start, end)| (*start == point_id || *end == point_id) && matches!(bez.handles, BezierHandles::Linear))
|
||||
.count()
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user