mirror of
https://github.com/wez/wezterm.git
synced 2024-12-23 13:21:38 +03:00
font: port over radial gradient fills
This commit is contained in:
parent
c41ae92404
commit
7af61159a8
@ -221,6 +221,38 @@ impl SrgbaPixel {
|
|||||||
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
|
#[cfg_attr(feature = "use_serde", derive(Serialize, Deserialize))]
|
||||||
pub struct SrgbaTuple(pub f32, pub f32, pub f32, pub f32);
|
pub struct SrgbaTuple(pub f32, pub f32, pub f32, pub f32);
|
||||||
|
|
||||||
|
impl SrgbaTuple {
|
||||||
|
pub fn premultiply(self) -> Self {
|
||||||
|
let SrgbaTuple(r, g, b, a) = self;
|
||||||
|
Self(r * a, g * a, b * a, a)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn demultiply(self) -> Self {
|
||||||
|
let SrgbaTuple(r, g, b, a) = self;
|
||||||
|
if a != 0. {
|
||||||
|
Self(r / a, g / a, b / a, a)
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn interpolate(self, other: Self, k: f64) -> Self {
|
||||||
|
let k = k as f32;
|
||||||
|
|
||||||
|
let SrgbaTuple(r0, g0, b0, a0) = self.premultiply();
|
||||||
|
let SrgbaTuple(r1, g1, b1, a1) = other.premultiply();
|
||||||
|
|
||||||
|
let r = SrgbaTuple(
|
||||||
|
r0 + k * (r1 - r0),
|
||||||
|
g0 + k * (g1 - g0),
|
||||||
|
b0 + k * (b1 - b0),
|
||||||
|
a0 + k * (a1 - a0),
|
||||||
|
);
|
||||||
|
|
||||||
|
r.demultiply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ToDynamic for SrgbaTuple {
|
impl ToDynamic for SrgbaTuple {
|
||||||
fn to_dynamic(&self) -> Value {
|
fn to_dynamic(&self) -> Value {
|
||||||
self.to_string().to_dynamic()
|
self.to_string().to_dynamic()
|
||||||
@ -237,6 +269,13 @@ impl FromDynamic for SrgbaTuple {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<SrgbaPixel> for SrgbaTuple {
|
||||||
|
fn from(pixel: SrgbaPixel) -> SrgbaTuple {
|
||||||
|
let (r, g, b, a) = pixel.as_srgba_tuple();
|
||||||
|
SrgbaTuple(r, g, b, a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl From<(f32, f32, f32, f32)> for SrgbaTuple {
|
impl From<(f32, f32, f32, f32)> for SrgbaTuple {
|
||||||
fn from((r, g, b, a): (f32, f32, f32, f32)) -> SrgbaTuple {
|
fn from((r, g, b, a): (f32, f32, f32, f32)) -> SrgbaTuple {
|
||||||
SrgbaTuple(r, g, b, a)
|
SrgbaTuple(r, g, b, a)
|
||||||
|
@ -864,8 +864,12 @@ impl ColorLine {
|
|||||||
color_stops: color_stops
|
color_stops: color_stops
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|stop| ColorStop {
|
.map(|stop| ColorStop {
|
||||||
offset: stop.offset,
|
offset: stop.offset.into(),
|
||||||
color: hb_color_to_srgba_pixel(stop.color),
|
color: if stop.is_foreground != 0 {
|
||||||
|
SrgbaPixel::rgba(0xff, 0xff, 0xff, 0xff)
|
||||||
|
} else {
|
||||||
|
hb_color_to_srgba_pixel(stop.color)
|
||||||
|
},
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
extend: hb_extend_to_cairo(extend),
|
extend: hb_extend_to_cairo(extend),
|
||||||
|
@ -1,9 +1,17 @@
|
|||||||
use cairo::{Context, Extend, LinearGradient, Matrix, Operator, RadialGradient};
|
use cairo::{Context, Extend, LinearGradient, Matrix, Mesh, MeshCorner, Operator, RadialGradient};
|
||||||
use wezterm_color_types::SrgbaPixel;
|
use wezterm_color_types::{SrgbaPixel, SrgbaTuple};
|
||||||
|
|
||||||
|
/* The gradient related routines in this file were ported from HarfBuzz, which
|
||||||
|
* were in turn ported from BlackRenderer by Black Foundry.
|
||||||
|
* Used by permission to relicense to HarfBuzz license,
|
||||||
|
* which is in turn compatible with wezterm's license.
|
||||||
|
*
|
||||||
|
* https://github.com/BlackFoundryCom/black-renderer
|
||||||
|
*/
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub struct ColorStop {
|
pub struct ColorStop {
|
||||||
pub offset: f32,
|
pub offset: f64,
|
||||||
pub color: SrgbaPixel,
|
pub color: SrgbaPixel,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -148,16 +156,346 @@ pub fn paint_radial_gradient(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
struct Point {
|
||||||
|
x: f64,
|
||||||
|
y: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Point {
|
||||||
|
fn dot(&self, other: Self) -> f64 {
|
||||||
|
(self.x * other.x) + (self.y * other.y)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn normalize(self) -> Self {
|
||||||
|
let len = self.dot(self).sqrt();
|
||||||
|
Self {
|
||||||
|
x: self.x / len,
|
||||||
|
y: self.y / len,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sum(self, other: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x + other.x,
|
||||||
|
y: self.y + other.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn difference(self, other: Self) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x - other.x,
|
||||||
|
y: self.y - other.y,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn scale(self, factor: f64) -> Self {
|
||||||
|
Self {
|
||||||
|
x: self.x * factor,
|
||||||
|
y: self.y * factor,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute a vector from the supplied angle
|
||||||
|
pub fn from_angle(angle: f64) -> Self {
|
||||||
|
let (y, x) = angle.sin_cos();
|
||||||
|
Self { x, y }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn interpolate(f0: f64, f1: f64, f: f64) -> f64 {
|
||||||
|
f0 + f * (f1 - f0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Patch {
|
||||||
|
p0: Point,
|
||||||
|
c0: Point,
|
||||||
|
c1: Point,
|
||||||
|
p1: Point,
|
||||||
|
color0: SrgbaTuple,
|
||||||
|
color1: SrgbaTuple,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Patch {
|
||||||
|
fn add_to_mesh(&self, center: Point, mesh: &Mesh) {
|
||||||
|
mesh.begin_patch();
|
||||||
|
mesh.move_to(center.x, center.y);
|
||||||
|
mesh.line_to(self.p0.x, self.p0.y);
|
||||||
|
mesh.curve_to(
|
||||||
|
self.c0.x, self.c0.y, self.c1.x, self.c1.y, self.p1.x, self.p1.y,
|
||||||
|
);
|
||||||
|
mesh.line_to(center.x, center.y);
|
||||||
|
|
||||||
|
fn set_corner_color(mesh: &Mesh, corner: MeshCorner, color: SrgbaTuple) {
|
||||||
|
let SrgbaTuple(r, g, b, a) = color;
|
||||||
|
|
||||||
|
mesh.set_corner_color_rgba(corner, r.into(), g.into(), b.into(), a.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
set_corner_color(mesh, MeshCorner::MeshCorner0, self.color0);
|
||||||
|
set_corner_color(mesh, MeshCorner::MeshCorner1, self.color0);
|
||||||
|
set_corner_color(mesh, MeshCorner::MeshCorner2, self.color1);
|
||||||
|
set_corner_color(mesh, MeshCorner::MeshCorner3, self.color1);
|
||||||
|
|
||||||
|
mesh.end_patch();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_sweep_gradient_patches(
|
||||||
|
mesh: &Mesh,
|
||||||
|
center: Point,
|
||||||
|
radius: f64,
|
||||||
|
a0: f64,
|
||||||
|
c0: SrgbaTuple,
|
||||||
|
a1: f64,
|
||||||
|
c1: SrgbaTuple,
|
||||||
|
) {
|
||||||
|
const MAX_ANGLE: f64 = std::f64::consts::PI / 8.;
|
||||||
|
let num_splits = ((a1 - a0).abs() / MAX_ANGLE).ceil() as usize;
|
||||||
|
|
||||||
|
let mut p0 = Point::from_angle(a0);
|
||||||
|
let mut color0 = c0;
|
||||||
|
|
||||||
|
for idx in 0..num_splits {
|
||||||
|
let k = (idx as f64 + 1.) / num_splits as f64;
|
||||||
|
|
||||||
|
let angle1 = interpolate(a0, a1, k);
|
||||||
|
let color1 = c0.interpolate(c1, k);
|
||||||
|
|
||||||
|
let p1 = Point::from_angle(angle1);
|
||||||
|
|
||||||
|
let a = p0.sum(p1).normalize();
|
||||||
|
let u = Point { x: -a.y, y: a.x };
|
||||||
|
|
||||||
|
fn compute_control(a: Point, u: Point, p: Point, center: Point, radius: f64) -> Point {
|
||||||
|
let c = a.sum(u.scale(p.difference(a).dot(p) / u.dot(p)));
|
||||||
|
c.difference(p)
|
||||||
|
.scale(0.33333)
|
||||||
|
.sum(c)
|
||||||
|
.scale(radius)
|
||||||
|
.sum(center)
|
||||||
|
}
|
||||||
|
|
||||||
|
let patch = Patch {
|
||||||
|
color0,
|
||||||
|
color1,
|
||||||
|
p0: center.sum(p0.scale(radius)),
|
||||||
|
p1: center.sum(p1.scale(radius)),
|
||||||
|
c0: compute_control(a, u, p0, center, radius),
|
||||||
|
c1: compute_control(a, u, p1, center, radius),
|
||||||
|
};
|
||||||
|
|
||||||
|
patch.add_to_mesh(center, mesh);
|
||||||
|
|
||||||
|
p0 = p1;
|
||||||
|
color0 = color1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const PI_TIMES_2: f64 = std::f64::consts::PI * 2.;
|
||||||
|
|
||||||
|
fn apply_sweep_gradient_patches(
|
||||||
|
mesh: &Mesh,
|
||||||
|
mut color_line: ColorLine,
|
||||||
|
center: Point,
|
||||||
|
radius: f64,
|
||||||
|
mut start_angle: f64,
|
||||||
|
mut end_angle: f64,
|
||||||
|
) {
|
||||||
|
if start_angle == end_angle {
|
||||||
|
if color_line.extend == Extend::Pad {
|
||||||
|
if start_angle > 0. {
|
||||||
|
let c = color_line.color_stops[0].color.into();
|
||||||
|
add_sweep_gradient_patches(mesh, center, radius, 0., c, start_angle, c);
|
||||||
|
}
|
||||||
|
if end_angle < PI_TIMES_2 {
|
||||||
|
let c = color_line.color_stops.last().unwrap().color.into();
|
||||||
|
add_sweep_gradient_patches(mesh, center, radius, end_angle, c, PI_TIMES_2, c);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if end_angle < start_angle {
|
||||||
|
std::mem::swap(&mut start_angle, &mut end_angle);
|
||||||
|
color_line.color_stops.reverse();
|
||||||
|
for stop in &mut color_line.color_stops {
|
||||||
|
stop.offset = 1.0 - stop.offset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let angles: Vec<f64> = color_line
|
||||||
|
.color_stops
|
||||||
|
.iter()
|
||||||
|
.map(|stop| start_angle + stop.offset * (end_angle - start_angle))
|
||||||
|
.collect();
|
||||||
|
let colors: Vec<SrgbaTuple> = color_line
|
||||||
|
.color_stops
|
||||||
|
.iter()
|
||||||
|
.map(|stop| stop.color.into())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let n_stops = angles.len();
|
||||||
|
|
||||||
|
if color_line.extend == Extend::Pad {
|
||||||
|
let mut color0 = colors[0];
|
||||||
|
let mut pos = 0;
|
||||||
|
while pos < n_stops {
|
||||||
|
if angles[pos] >= 0. {
|
||||||
|
if pos > 0 {
|
||||||
|
let k = (0. - angles[pos - 1]) / (angles[pos] - angles[pos - 1]);
|
||||||
|
|
||||||
|
color0 = colors[pos - 1].interpolate(colors[pos], k);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
if pos == n_stops {
|
||||||
|
/* everything is below 0 */
|
||||||
|
color0 = colors[n_stops - 1];
|
||||||
|
add_sweep_gradient_patches(mesh, center, radius, 0., color0, PI_TIMES_2, color0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
add_sweep_gradient_patches(mesh, center, radius, 0., color0, angles[pos], colors[pos]);
|
||||||
|
|
||||||
|
pos += 1;
|
||||||
|
while pos < n_stops {
|
||||||
|
if angles[pos] <= PI_TIMES_2 {
|
||||||
|
add_sweep_gradient_patches(
|
||||||
|
mesh,
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
angles[pos - 1],
|
||||||
|
colors[pos - 1],
|
||||||
|
angles[pos],
|
||||||
|
colors[pos],
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let k = (PI_TIMES_2 - angles[pos - 1]) / (angles[pos] - angles[pos - 1]);
|
||||||
|
let color1 = colors[pos - 1].interpolate(colors[pos], k);
|
||||||
|
add_sweep_gradient_patches(
|
||||||
|
mesh,
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
angles[pos - 1],
|
||||||
|
colors[pos - 1],
|
||||||
|
PI_TIMES_2,
|
||||||
|
color1,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
pos += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if pos == n_stops {
|
||||||
|
/* everything is below 2*M_PI */
|
||||||
|
color0 = colors[n_stops - 1];
|
||||||
|
add_sweep_gradient_patches(
|
||||||
|
mesh,
|
||||||
|
center,
|
||||||
|
radius,
|
||||||
|
angles[n_stops - 1],
|
||||||
|
color0,
|
||||||
|
PI_TIMES_2,
|
||||||
|
color0,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let span = angles[n_stops - 1] - angles[0];
|
||||||
|
let mut k = 0isize;
|
||||||
|
if angles[0] >= 0. {
|
||||||
|
let mut ss = angles[0];
|
||||||
|
while ss > 0. {
|
||||||
|
if span > 0. {
|
||||||
|
ss -= span;
|
||||||
|
k -= 1;
|
||||||
|
} else {
|
||||||
|
ss += span;
|
||||||
|
k += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if angles[0] < 0. {
|
||||||
|
let mut ee = angles[n_stops - 1];
|
||||||
|
while ee < 0. {
|
||||||
|
if span > 0. {
|
||||||
|
ee += span;
|
||||||
|
k += 1;
|
||||||
|
} else {
|
||||||
|
ee -= span;
|
||||||
|
k -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
angles[0] + (k as f64) * span <= 0. && 0. < angles[n_stops - 1] + (k as f64) * span
|
||||||
|
);
|
||||||
|
let span = span.abs();
|
||||||
|
|
||||||
|
for l in k..k.min(1000) {
|
||||||
|
for i in 1..n_stops {
|
||||||
|
let (a0, a1, c0, c1);
|
||||||
|
|
||||||
|
if l % 2 != 0 && color_line.extend == Extend::Reflect {
|
||||||
|
a0 = angles[0] + angles[n_stops - 1] - angles[n_stops - 1 - (i - 1)]
|
||||||
|
+ (l as f64) * span;
|
||||||
|
a1 = angles[0] + angles[n_stops - 1] - angles[n_stops - 1 - i]
|
||||||
|
+ (l as f64) * span;
|
||||||
|
c0 = colors[n_stops - 1 - (i - 1)];
|
||||||
|
c1 = colors[n_stops - 1 - i];
|
||||||
|
} else {
|
||||||
|
a0 = angles[i - 1] + (l as f64) * span;
|
||||||
|
a1 = angles[i] + (l as f64) * span;
|
||||||
|
c0 = colors[i - 1];
|
||||||
|
c1 = colors[i];
|
||||||
|
}
|
||||||
|
|
||||||
|
if a1 < 0. {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if a0 < 0. {
|
||||||
|
let f = (0. - a0) / (a1 - a0);
|
||||||
|
let color = c0.interpolate(c1, f);
|
||||||
|
add_sweep_gradient_patches(mesh, center, radius, 0., color, a1, c1);
|
||||||
|
} else if a1 >= PI_TIMES_2 {
|
||||||
|
let f = (PI_TIMES_2 - a0) / (a1 - a0);
|
||||||
|
let color = c0.interpolate(c1, f);
|
||||||
|
add_sweep_gradient_patches(mesh, center, radius, a0, c0, PI_TIMES_2, color);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
add_sweep_gradient_patches(mesh, center, radius, a0, c0, a1, c1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub fn paint_sweep_gradient(
|
pub fn paint_sweep_gradient(
|
||||||
context: &Context,
|
context: &Context,
|
||||||
x0: f64,
|
x0: f64,
|
||||||
y0: f64,
|
y0: f64,
|
||||||
start_angle: f64,
|
start_angle: f64,
|
||||||
end_angle: f64,
|
end_angle: f64,
|
||||||
mut color_line: ColorLine,
|
color_line: ColorLine,
|
||||||
) -> anyhow::Result<()> {
|
) -> anyhow::Result<()> {
|
||||||
let (min_stop, max_stop) = normalize_color_line(&mut color_line);
|
let (x1, y1, x2, y2) = context.clip_extents()?;
|
||||||
anyhow::bail!("NOT IMPL: SweepGradient");
|
|
||||||
|
let max_x = ((x1 - x0) * (x1 - x0)).max((x2 - x0) * (x2 - x0));
|
||||||
|
let max_y = ((y1 - y0) * (y1 - y0)).max((y2 - y0) * (y2 - y0));
|
||||||
|
let radius = (max_x + max_y).sqrt();
|
||||||
|
|
||||||
|
let mesh = Mesh::new();
|
||||||
|
let center = Point { x: x0, y: y0 };
|
||||||
|
apply_sweep_gradient_patches(&mesh, color_line, center, radius, start_angle, end_angle);
|
||||||
|
context.set_source(mesh)?;
|
||||||
|
context.paint()?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn normalize_color_line(color_line: &mut ColorLine) -> (f64, f64) {
|
fn normalize_color_line(color_line: &mut ColorLine) -> (f64, f64) {
|
||||||
@ -179,12 +517,6 @@ fn normalize_color_line(color_line: &mut ColorLine) -> (f64, f64) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// NOTE: hb-cairo-utils will call back out to some other state
|
|
||||||
// to fill in the color when is_foreground is true, defaulting
|
|
||||||
// to black with alpha varying by the alpha channel of the
|
|
||||||
// color value in the stop. Do we need to do something like
|
|
||||||
// that here?
|
|
||||||
|
|
||||||
(smallest as f64, largest as f64)
|
(smallest as f64, largest as f64)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user