mirror of
https://github.com/typst/typst
synced 2025-05-14 04:56:26 +08:00
Gradient Part 5c: Fix gradient rotation on text & math (#2606)
This commit is contained in:
parent
ccbe901cb7
commit
d7fea7077e
@ -277,7 +277,7 @@ pub(super) trait PaintEncode {
|
|||||||
fn set_as_fill(&self, ctx: &mut PageContext, on_text: bool, transforms: Transforms);
|
fn set_as_fill(&self, ctx: &mut PageContext, on_text: bool, transforms: Transforms);
|
||||||
|
|
||||||
/// Set the paint as the stroke color.
|
/// Set the paint as the stroke color.
|
||||||
fn set_as_stroke(&self, ctx: &mut PageContext, on_text: bool, transforms: Transforms);
|
fn set_as_stroke(&self, ctx: &mut PageContext, transforms: Transforms);
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PaintEncode for Paint {
|
impl PaintEncode for Paint {
|
||||||
@ -288,15 +288,10 @@ impl PaintEncode for Paint {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_as_stroke(
|
fn set_as_stroke(&self, ctx: &mut PageContext, transforms: Transforms) {
|
||||||
&self,
|
|
||||||
ctx: &mut PageContext,
|
|
||||||
on_text: bool,
|
|
||||||
transforms: Transforms,
|
|
||||||
) {
|
|
||||||
match self {
|
match self {
|
||||||
Self::Solid(c) => c.set_as_stroke(ctx, on_text, transforms),
|
Self::Solid(c) => c.set_as_stroke(ctx, transforms),
|
||||||
Self::Gradient(gradient) => gradient.set_as_stroke(ctx, on_text, transforms),
|
Self::Gradient(gradient) => gradient.set_as_stroke(ctx, transforms),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -355,7 +350,7 @@ impl PaintEncode for Color {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_as_stroke(&self, ctx: &mut PageContext, _: bool, _: Transforms) {
|
fn set_as_stroke(&self, ctx: &mut PageContext, _: Transforms) {
|
||||||
match self {
|
match self {
|
||||||
Color::Luma(_) => {
|
Color::Luma(_) => {
|
||||||
ctx.parent.colors.d65_gray(&mut ctx.parent.alloc);
|
ctx.parent.colors.d65_gray(&mut ctx.parent.alloc);
|
||||||
|
@ -26,21 +26,21 @@ pub struct PdfGradient {
|
|||||||
pub aspect_ratio: Ratio,
|
pub aspect_ratio: Ratio,
|
||||||
/// The gradient.
|
/// The gradient.
|
||||||
pub gradient: Gradient,
|
pub gradient: Gradient,
|
||||||
/// Whether the gradient is applied to text.
|
/// The corrected angle of the gradient.
|
||||||
pub on_text: bool,
|
pub angle: Angle,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Writes the actual gradients (shading patterns) to the PDF.
|
/// Writes the actual gradients (shading patterns) to the PDF.
|
||||||
/// This is performed once after writing all pages.
|
/// This is performed once after writing all pages.
|
||||||
pub(crate) fn write_gradients(ctx: &mut PdfContext) {
|
pub(crate) fn write_gradients(ctx: &mut PdfContext) {
|
||||||
for PdfGradient { transform, aspect_ratio, gradient, on_text } in
|
for PdfGradient { transform, aspect_ratio, gradient, angle } in
|
||||||
ctx.gradient_map.items().cloned().collect::<Vec<_>>()
|
ctx.gradient_map.items().cloned().collect::<Vec<_>>()
|
||||||
{
|
{
|
||||||
let shading = ctx.alloc.bump();
|
let shading = ctx.alloc.bump();
|
||||||
ctx.gradient_refs.push(shading);
|
ctx.gradient_refs.push(shading);
|
||||||
|
|
||||||
let mut shading_pattern = match &gradient {
|
let mut shading_pattern = match &gradient {
|
||||||
Gradient::Linear(linear) => {
|
Gradient::Linear(_) => {
|
||||||
let shading_function = shading_function(ctx, &gradient);
|
let shading_function = shading_function(ctx, &gradient);
|
||||||
let mut shading_pattern = ctx.pdf.shading_pattern(shading);
|
let mut shading_pattern = ctx.pdf.shading_pattern(shading);
|
||||||
let mut shading = shading_pattern.function_shading();
|
let mut shading = shading_pattern.function_shading();
|
||||||
@ -49,14 +49,24 @@ pub(crate) fn write_gradients(ctx: &mut PdfContext) {
|
|||||||
ctx.colors
|
ctx.colors
|
||||||
.write(gradient.space(), shading.color_space(), &mut ctx.alloc);
|
.write(gradient.space(), shading.color_space(), &mut ctx.alloc);
|
||||||
|
|
||||||
let angle = Gradient::correct_aspect_ratio(linear.angle, aspect_ratio);
|
|
||||||
let (sin, cos) = (angle.sin(), angle.cos());
|
let (sin, cos) = (angle.sin(), angle.cos());
|
||||||
let length = sin.abs() + cos.abs();
|
let (x1, y1, x2, y2): (f64, f64, f64, f64) = match angle.quadrant() {
|
||||||
|
Quadrant::First => (0.0, 0.0, cos, sin),
|
||||||
|
Quadrant::Second => (1.0, 0.0, cos + 1.0, sin),
|
||||||
|
Quadrant::Third => (1.0, 1.0, cos + 1.0, sin + 1.0),
|
||||||
|
Quadrant::Fourth => (0.0, 1.0, cos, sin + 1.0),
|
||||||
|
};
|
||||||
|
|
||||||
|
let clamp = |i: f64| if i < 1e-4 { 0.0 } else { i.clamp(0.0, 1.0) };
|
||||||
|
let x1 = clamp(x1);
|
||||||
|
let y1 = clamp(y1);
|
||||||
|
let x2 = clamp(x2);
|
||||||
|
let y2 = clamp(y2);
|
||||||
|
|
||||||
shading
|
shading
|
||||||
.anti_alias(gradient.anti_alias())
|
.anti_alias(gradient.anti_alias())
|
||||||
.function(shading_function)
|
.function(shading_function)
|
||||||
.coords([0.0, 0.0, length as f32, 0.0])
|
.coords([x1 as f32, y1 as f32, x2 as f32, y2 as f32])
|
||||||
.extend([true; 2]);
|
.extend([true; 2]);
|
||||||
|
|
||||||
shading.finish();
|
shading.finish();
|
||||||
@ -90,7 +100,7 @@ pub(crate) fn write_gradients(ctx: &mut PdfContext) {
|
|||||||
shading_pattern
|
shading_pattern
|
||||||
}
|
}
|
||||||
Gradient::Conic(conic) => {
|
Gradient::Conic(conic) => {
|
||||||
let vertices = compute_vertex_stream(conic, aspect_ratio, on_text);
|
let vertices = compute_vertex_stream(conic, aspect_ratio);
|
||||||
|
|
||||||
let stream_shading_id = ctx.alloc.bump();
|
let stream_shading_id = ctx.alloc.bump();
|
||||||
let mut stream_shading =
|
let mut stream_shading =
|
||||||
@ -265,15 +275,10 @@ impl PaintEncode for Gradient {
|
|||||||
ctx.content.set_fill_pattern(None, name);
|
ctx.content.set_fill_pattern(None, name);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn set_as_stroke(
|
fn set_as_stroke(&self, ctx: &mut PageContext, transforms: Transforms) {
|
||||||
&self,
|
|
||||||
ctx: &mut PageContext,
|
|
||||||
on_text: bool,
|
|
||||||
transforms: Transforms,
|
|
||||||
) {
|
|
||||||
ctx.reset_stroke_color_space();
|
ctx.reset_stroke_color_space();
|
||||||
|
|
||||||
let id = register_gradient(ctx, self, on_text, transforms);
|
let id = register_gradient(ctx, self, false, transforms);
|
||||||
let name = Name(id.as_bytes());
|
let name = Name(id.as_bytes());
|
||||||
|
|
||||||
ctx.content.set_stroke_color_space(ColorSpaceOperand::Pattern);
|
ctx.content.set_stroke_color_space(ColorSpaceOperand::Pattern);
|
||||||
@ -296,33 +301,20 @@ fn register_gradient(
|
|||||||
if transforms.size.y.is_zero() {
|
if transforms.size.y.is_zero() {
|
||||||
transforms.size.y = Abs::pt(1.0);
|
transforms.size.y = Abs::pt(1.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
let size = match gradient.unwrap_relative(on_text) {
|
let size = match gradient.unwrap_relative(on_text) {
|
||||||
Relative::Self_ => transforms.size,
|
Relative::Self_ => transforms.size,
|
||||||
Relative::Parent => transforms.container_size,
|
Relative::Parent => transforms.container_size,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Correction for y-axis flipping on text.
|
|
||||||
let angle = gradient.angle().unwrap_or_else(Angle::zero);
|
|
||||||
let angle = if on_text { Angle::rad(TAU as f64) - angle } else { angle };
|
|
||||||
|
|
||||||
let (offset_x, offset_y) = match gradient {
|
let (offset_x, offset_y) = match gradient {
|
||||||
Gradient::Conic(conic) => (
|
Gradient::Conic(conic) => (
|
||||||
-size.x * (1.0 - conic.center.x.get() / 2.0) / 2.0,
|
-size.x * (1.0 - conic.center.x.get() / 2.0) / 2.0,
|
||||||
-size.y * (1.0 - conic.center.y.get() / 2.0) / 2.0,
|
-size.y * (1.0 - conic.center.y.get() / 2.0) / 2.0,
|
||||||
),
|
),
|
||||||
_ => match angle.quadrant() {
|
_ => (Abs::zero(), Abs::zero()),
|
||||||
Quadrant::First => (Abs::zero(), Abs::zero()),
|
|
||||||
Quadrant::Second => (size.x, Abs::zero()),
|
|
||||||
Quadrant::Third => (size.x, size.y),
|
|
||||||
Quadrant::Fourth => (Abs::zero(), size.y),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let rotation = match gradient {
|
let rotation = gradient.angle().unwrap_or_else(Angle::zero);
|
||||||
Gradient::Conic(_) => Angle::zero(),
|
|
||||||
_ => angle,
|
|
||||||
};
|
|
||||||
|
|
||||||
let transform = match gradient.unwrap_relative(on_text) {
|
let transform = match gradient.unwrap_relative(on_text) {
|
||||||
Relative::Self_ => transforms.transform,
|
Relative::Self_ => transforms.transform,
|
||||||
@ -344,13 +336,9 @@ fn register_gradient(
|
|||||||
.pre_concat(Transform::scale(
|
.pre_concat(Transform::scale(
|
||||||
Ratio::new(size.x.to_pt() * scale_offset),
|
Ratio::new(size.x.to_pt() * scale_offset),
|
||||||
Ratio::new(size.y.to_pt() * scale_offset),
|
Ratio::new(size.y.to_pt() * scale_offset),
|
||||||
))
|
)),
|
||||||
.pre_concat(Transform::rotate(Gradient::correct_aspect_ratio(
|
|
||||||
rotation,
|
|
||||||
size.aspect_ratio(),
|
|
||||||
))),
|
|
||||||
gradient: gradient.clone(),
|
gradient: gradient.clone(),
|
||||||
on_text,
|
angle: Gradient::correct_aspect_ratio(rotation, size.aspect_ratio()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let index = ctx.parent.gradient_map.insert(pdf_gradient);
|
let index = ctx.parent.gradient_map.insert(pdf_gradient);
|
||||||
@ -383,16 +371,9 @@ fn write_patch(
|
|||||||
c0: [u16; 3],
|
c0: [u16; 3],
|
||||||
c1: [u16; 3],
|
c1: [u16; 3],
|
||||||
angle: Angle,
|
angle: Angle,
|
||||||
on_text: bool,
|
|
||||||
) {
|
) {
|
||||||
let mut theta = -TAU * t + angle.to_rad() as f32 + PI;
|
let theta = -TAU * t + angle.to_rad() as f32 + PI;
|
||||||
let mut theta1 = -TAU * t1 + angle.to_rad() as f32 + PI;
|
let theta1 = -TAU * t1 + angle.to_rad() as f32 + PI;
|
||||||
|
|
||||||
// Correction for y-axis flipping on text.
|
|
||||||
if on_text {
|
|
||||||
theta = (TAU - theta).rem_euclid(TAU);
|
|
||||||
theta1 = (TAU - theta1).rem_euclid(TAU);
|
|
||||||
}
|
|
||||||
|
|
||||||
let (cp1, cp2) =
|
let (cp1, cp2) =
|
||||||
control_point(Point::new(Abs::pt(0.5), Abs::pt(0.5)), 0.5, theta, theta1);
|
control_point(Point::new(Abs::pt(0.5), Abs::pt(0.5)), 0.5, theta, theta1);
|
||||||
@ -453,11 +434,7 @@ fn control_point(c: Point, r: f32, angle_start: f32, angle_end: f32) -> (Point,
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[comemo::memoize]
|
#[comemo::memoize]
|
||||||
fn compute_vertex_stream(
|
fn compute_vertex_stream(conic: &ConicGradient, aspect_ratio: Ratio) -> Arc<Vec<u8>> {
|
||||||
conic: &ConicGradient,
|
|
||||||
aspect_ratio: Ratio,
|
|
||||||
on_text: bool,
|
|
||||||
) -> Arc<Vec<u8>> {
|
|
||||||
// Generated vertices for the Coons patches
|
// Generated vertices for the Coons patches
|
||||||
let mut vertices = Vec::new();
|
let mut vertices = Vec::new();
|
||||||
|
|
||||||
@ -534,18 +511,9 @@ fn compute_vertex_stream(
|
|||||||
conic.space.convert(c),
|
conic.space.convert(c),
|
||||||
c0,
|
c0,
|
||||||
angle,
|
angle,
|
||||||
on_text,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
write_patch(
|
write_patch(&mut vertices, t_prime, t_prime, c0, c1, angle);
|
||||||
&mut vertices,
|
|
||||||
t_prime,
|
|
||||||
t_prime,
|
|
||||||
c0,
|
|
||||||
c1,
|
|
||||||
angle,
|
|
||||||
on_text,
|
|
||||||
);
|
|
||||||
|
|
||||||
write_patch(
|
write_patch(
|
||||||
&mut vertices,
|
&mut vertices,
|
||||||
@ -554,7 +522,6 @@ fn compute_vertex_stream(
|
|||||||
c1,
|
c1,
|
||||||
conic.space.convert(c_next),
|
conic.space.convert(c_next),
|
||||||
angle,
|
angle,
|
||||||
on_text,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
t_x = t_next;
|
t_x = t_next;
|
||||||
@ -570,7 +537,6 @@ fn compute_vertex_stream(
|
|||||||
conic.space.convert(c),
|
conic.space.convert(c),
|
||||||
conic.space.convert(c_next),
|
conic.space.convert(c_next),
|
||||||
angle,
|
angle,
|
||||||
on_text,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
t_x = t_next;
|
t_x = t_next;
|
||||||
|
@ -322,7 +322,7 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Subset of the state used to calculate the transform of gradients and patterns.
|
/// Subset of the state used to calculate the transform of gradients and patterns.
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub(super) struct Transforms {
|
pub(super) struct Transforms {
|
||||||
/// The transform of the current item.
|
/// The transform of the current item.
|
||||||
pub transform: Transform,
|
pub transform: Transform,
|
||||||
@ -385,6 +385,9 @@ impl PageContext<'_, '_> {
|
|||||||
fn transform(&mut self, transform: Transform) {
|
fn transform(&mut self, transform: Transform) {
|
||||||
let Transform { sx, ky, kx, sy, tx, ty } = transform;
|
let Transform { sx, ky, kx, sy, tx, ty } = transform;
|
||||||
self.state.transform = self.state.transform.pre_concat(transform);
|
self.state.transform = self.state.transform.pre_concat(transform);
|
||||||
|
if self.state.container_transform.is_identity() {
|
||||||
|
self.state.container_transform = self.state.transform;
|
||||||
|
}
|
||||||
self.content.transform([
|
self.content.transform([
|
||||||
sx.get() as _,
|
sx.get() as _,
|
||||||
ky.get() as _,
|
ky.get() as _,
|
||||||
@ -449,7 +452,7 @@ impl PageContext<'_, '_> {
|
|||||||
miter_limit,
|
miter_limit,
|
||||||
} = stroke;
|
} = stroke;
|
||||||
|
|
||||||
paint.set_as_stroke(self, false, transforms);
|
paint.set_as_stroke(self, transforms);
|
||||||
|
|
||||||
self.content.set_line_width(thickness.to_f32());
|
self.content.set_line_width(thickness.to_f32());
|
||||||
if self.state.stroke.as_ref().map(|s| &s.line_cap) != Some(line_cap) {
|
if self.state.stroke.as_ref().map(|s| &s.line_cap) != Some(line_cap) {
|
||||||
@ -517,12 +520,10 @@ fn write_group(ctx: &mut PageContext, pos: Point, group: &GroupItem) {
|
|||||||
|
|
||||||
if group.frame.kind().is_hard() {
|
if group.frame.kind().is_hard() {
|
||||||
ctx.group_transform(
|
ctx.group_transform(
|
||||||
translation
|
ctx.state
|
||||||
.pre_concat(
|
.transform
|
||||||
ctx.state
|
.post_concat(ctx.state.container_transform.invert().unwrap())
|
||||||
.transform
|
.pre_concat(translation)
|
||||||
.post_concat(ctx.state.container_transform.invert().unwrap()),
|
|
||||||
)
|
|
||||||
.pre_concat(group.transform),
|
.pre_concat(group.transform),
|
||||||
);
|
);
|
||||||
ctx.size(group.frame.size());
|
ctx.size(group.frame.size());
|
||||||
|
Binary file not shown.
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 37 KiB |
@ -55,7 +55,6 @@ $ x_"1,2" = frac(-b +- sqrt(b^2 - 4 a c), 2 a) $
|
|||||||
|
|
||||||
---
|
---
|
||||||
// Test miscelaneous
|
// Test miscelaneous
|
||||||
|
|
||||||
#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
|
#show math.equation: set text(fill: gradient.linear(..color.map.rainbow))
|
||||||
#show math.equation: box
|
#show math.equation: box
|
||||||
|
|
||||||
@ -66,3 +65,25 @@ $ attach(
|
|||||||
Pi, t: alpha, b: beta,
|
Pi, t: alpha, b: beta,
|
||||||
tl: 1, tr: 2+3, bl: 4+5, br: 6,
|
tl: 1, tr: 2+3, bl: 4+5, br: 6,
|
||||||
) $
|
) $
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test radial gradient
|
||||||
|
#show math.equation: set text(fill: gradient.radial(..color.map.rainbow, center: (30%, 30%)))
|
||||||
|
#show math.equation: box
|
||||||
|
|
||||||
|
$ A = mat(
|
||||||
|
1, 2, 3;
|
||||||
|
4, 5, 6;
|
||||||
|
7, 8, 9
|
||||||
|
) $
|
||||||
|
|
||||||
|
---
|
||||||
|
// Test conic gradient
|
||||||
|
#show math.equation: set text(fill: gradient.conic(red, blue, angle: 45deg))
|
||||||
|
#show math.equation: box
|
||||||
|
|
||||||
|
$ A = mat(
|
||||||
|
1, 2, 3;
|
||||||
|
4, 5, 6;
|
||||||
|
7, 8, 9
|
||||||
|
) $
|
||||||
|
Loading…
x
Reference in New Issue
Block a user