#![allow(dead_code)]
use neon::prelude::*;
use skia_safe::{
BlendMode, Canvas as SkCanvas, ClipOp, Color, Color4f,
ColorFilter as SkColorFilter, ColorSpace, Contains, FourByteTag, IRect,
Image, ImageFilter as SkImageFilter, MaskFilter as SkMaskFilter, Paint,
PaintStyle, Path, PathBuilder, PathFillType, PathOp, Picture,
PictureRecorder, Point, Rect, Shader as SkShader, Size,
canvas::{SaveLayerRec, SrcRectConstraint::Strict},
dash_path_effect,
font_style::{FontStyle, Width},
image_filters, images,
matrix::{Matrix, TypeMask},
path_1d_path_effect,
path_utils::fill_path_with_paint,
textlayout::{ParagraphStyle, StrutStyle, TextStyle},
};
use std::cell::RefCell;
pub mod api;
pub mod page;
use crate::{
font_library::FontLibrary,
gpu::RenderingEngine,
gradient::{BoxedCanvasGradient, CanvasGradient},
node::{
filter::{Filter, SamplingFilter, SamplingQuality},
image::ImageData,
shader::BoxedShader,
},
pattern::{BoxedCanvasPattern, CanvasPattern},
texture::{BoxedCanvasTexture, CanvasTexture},
typography::{Baseline, DecorationStyle, FontSpec, Spacing, Typesetter},
utils::*,
};
use page::{ExportOptions, Page, PageRecorder};
const BLACK: Color = Color::BLACK;
const TRANSPARENT: Color = Color::TRANSPARENT;
pub type BoxedContext2D = JsBox<RefCell<Context2D>>;
impl Finalize for Context2D {}
pub struct Context2D {
pub bounds: Rect,
pub canvas_color_space: ColorSpace,
recorder: RefCell<PageRecorder>,
state: State,
stack: Vec<State>,
layers: Vec<bool>,
path: PathBuilder,
}
#[derive(Clone)]
pub struct State {
clip: Option<Path>,
matrix: Matrix,
paint: Paint,
fill_style: Dye,
stroke_style: Dye,
shadow_blur: f32,
shadow_color: Color,
shadow_offset: Point,
stroke_width: f32,
line_dash_offset: f32,
line_dash_list: Vec<f32>,
line_dash_marker: Option<Path>,
line_dash_fit: path_1d_path_effect::Style,
global_alpha: f32,
global_composite_operation: BlendMode,
sampling_filter: SamplingFilter,
filter: Filter,
skia_color_filter: Option<SkColorFilter>,
skia_image_filter: Option<SkImageFilter>,
skia_mask_filter: Option<SkMaskFilter>,
dither: bool,
font: String,
font_variant: String,
font_width: Width,
font_hinting: bool,
char_style: TextStyle,
graf_style: ParagraphStyle,
text_baseline: Baseline,
letter_spacing: Spacing,
word_spacing: Spacing,
text_decoration: DecorationStyle,
text_wrap: bool,
line_height: Option<f32>,
pub variations: Vec<(FourByteTag, f32)>,
pub font_variation_settings: String,
}
impl Default for State {
fn default() -> Self {
let mut paint = Paint::default();
paint
.set_stroke_miter(10.0)
.set_color4f(Color4f::from(BLACK), &ColorSpace::new_srgb())
.set_anti_alias(true)
.set_stroke_width(1.0)
.set_style(PaintStyle::Fill);
let graf_style = ParagraphStyle::new();
let mut char_style = TextStyle::new();
char_style.set_font_size(10.0);
State {
clip: None,
matrix: Matrix::new_identity(),
paint,
stroke_style: Dye::Color(
Color4f::from(BLACK),
Some(ColorSpace::new_srgb()),
),
fill_style: Dye::Color(
Color4f::from(BLACK),
Some(ColorSpace::new_srgb()),
),
stroke_width: 1.0,
line_dash_offset: 0.0,
line_dash_list: vec![],
line_dash_marker: None,
line_dash_fit: path_1d_path_effect::Style::Rotate,
global_alpha: 1.0,
global_composite_operation: BlendMode::SrcOver,
sampling_filter: SamplingFilter {
smoothing: true,
quality: SamplingQuality::Low,
},
filter: Filter::default(),
skia_color_filter: None,
skia_image_filter: None,
skia_mask_filter: None,
dither: false,
shadow_blur: 0.0,
shadow_color: TRANSPARENT,
shadow_offset: (0.0, 0.0).into(),
font: "10px sans-serif".to_string(),
font_variant: "normal".to_string(),
font_width: Width::NORMAL,
font_hinting: false,
char_style,
graf_style,
text_baseline: Baseline::Alphabetic,
letter_spacing: Spacing::default(),
word_spacing: Spacing::default(),
text_decoration: DecorationStyle::default(),
text_wrap: false,
line_height: None,
variations: vec![],
font_variation_settings: "normal".to_string(),
}
}
}
impl State {
pub fn typography(
&self,
) -> (TextStyle, ParagraphStyle, DecorationStyle, Baseline, bool) {
let mut char_style = self.char_style.clone(); char_style
.set_word_spacing(self.word_spacing.in_px(char_style.font_size()));
char_style.set_letter_spacing(
self.letter_spacing.in_px(char_style.font_size()),
);
char_style
.set_baseline_shift(self.text_baseline.get_offset(&char_style));
let mut graf_style = self.graf_style.clone(); let font_families = char_style.font_families();
if self.text_wrap {
let mut strut_style = StrutStyle::new();
strut_style
.set_font_families(&font_families.iter().collect::<Vec<_>>())
.set_font_style(char_style.font_style())
.set_font_size(char_style.font_size())
.set_force_strut_height(true)
.set_strut_enabled(true);
if let Some(height) = self.line_height {
strut_style
.set_leading((height - 1.0).max(0.0))
.set_height(height.min(1.0))
.set_height_override(true);
}
graf_style.set_strut_style(strut_style);
} else {
graf_style.set_max_lines(Some(1));
}
if !self.font_hinting {
graf_style.turn_hinting_off();
}
(
char_style,
graf_style,
self.text_decoration.clone(),
self.text_baseline,
self.text_wrap,
)
}
fn dye(&self, style: PaintStyle) -> &Dye {
if style == PaintStyle::Stroke {
&self.stroke_style
} else {
&self.fill_style
}
}
fn texture(&self, style: PaintStyle) -> Option<&CanvasTexture> {
match self.dye(style) {
Dye::Texture(texture) => Some(texture),
_ => None,
}
}
}
impl Context2D {
pub fn new(canvas_color_space: ColorSpace) -> Self {
let bounds = Rect::from_wh(300.0, 150.0);
Context2D {
bounds,
canvas_color_space,
recorder: RefCell::new(PageRecorder::new(bounds)),
path: PathBuilder::new(),
stack: vec![],
layers: vec![],
state: State::default(),
}
}
pub fn in_local_coordinates(&mut self, x: f32, y: f32) -> Point {
match self.state.matrix.invert() {
Some(inverse) => inverse.map_point((x, y)),
None => (x, y).into(),
}
}
pub fn with_recorder<'a, F>(&'a self, f: F)
where
F: FnOnce(std::cell::RefMut<'a, PageRecorder>),
{
f(self.recorder.borrow_mut());
}
pub fn with_canvas<F>(&self, f: F)
where
F: FnOnce(&SkCanvas),
{
self.with_recorder(|mut recorder| {
recorder.append(f);
});
}
pub fn with_matrix<F>(&mut self, f: F)
where
F: FnOnce(&mut Matrix) -> &Matrix,
{
f(&mut self.state.matrix);
self.with_recorder(|mut recorder| {
recorder.set_matrix(self.state.matrix);
});
}
pub fn render_to_canvas<F>(&self, paint: &Paint, f: F)
where
F: Fn(&SkCanvas, &Paint),
{
let render_shadow = |canvas: &SkCanvas, paint: &Paint| {
if let Some(shadow_paint) = self.paint_for_shadow(paint) {
canvas.save();
canvas.set_matrix(
&Matrix::translate(self.state.shadow_offset).into(),
);
canvas.concat(&self.state.matrix);
f(canvas, &shadow_paint);
canvas.restore();
}
};
match self.state.global_composite_operation {
BlendMode::SrcIn
| BlendMode::SrcOut
| BlendMode::DstIn
| BlendMode::DstOut
| BlendMode::DstATop
| BlendMode::Src => {
let mut layer_paint = paint.clone();
layer_paint.set_blend_mode(BlendMode::SrcOver);
let mut layer_recorder = PictureRecorder::new();
layer_recorder.begin_recording(self.bounds, true);
if let Some(layer) = layer_recorder.recording_canvas() {
render_shadow(layer, &layer_paint);
layer.set_matrix(&self.state.matrix.into());
f(layer, &layer_paint);
}
if let Some(pict) =
layer_recorder.finish_recording_as_picture(None)
{
self.with_canvas(|canvas| {
canvas.save();
canvas.set_matrix(&Matrix::new_identity().into());
let mut blend_paint = Paint::default();
blend_paint.set_anti_alias(true);
blend_paint.set_blend_mode(
self.state.global_composite_operation,
);
canvas.draw_picture(&pict, None, Some(&blend_paint));
canvas.restore();
});
}
}
_ => {
self.with_canvas(|canvas| {
render_shadow(canvas, paint);
f(canvas, paint);
});
}
};
}
pub fn map_points(&self, coords: &[f32]) -> Vec<Point> {
coords
.chunks_exact(2)
.map(|pair| {
self.state.matrix.map_point(Point::new(pair[0], pair[1]))
})
.collect()
}
pub fn reset_size(&mut self, dims: impl Into<Size>) {
self.bounds = Rect::from_size(dims);
self.path = PathBuilder::default();
self.stack = vec![];
self.state = State::default();
self.with_recorder(|mut recorder| {
recorder.set_bounds(self.bounds);
});
}
pub fn resize(&mut self, dims: impl Into<Size>) {
self.bounds = Rect::from_size(dims);
self.with_recorder(|mut recorder| {
recorder.update_bounds(self.bounds);
});
}
pub fn push(&mut self) {
let new_state = self.state.clone();
self.stack.push(new_state);
self.layers.push(false);
}
pub fn pop(&mut self) {
if let Some(old_state) = self.stack.pop() {
if self.layers.pop().unwrap_or(false) {
self.with_canvas(|canvas| {
canvas.restore();
});
}
self.state = old_state;
self.with_recorder(|mut recorder| {
recorder.set_matrix(self.state.matrix);
recorder.set_clip(&self.state.clip);
});
}
}
pub fn save_layer(
&mut self,
paint: Option<Paint>,
bounds: Option<Rect>,
backdrop: Option<SkImageFilter>,
) {
self.with_canvas(|canvas| {
let mut rec = SaveLayerRec::default();
if let Some(p) = paint.as_ref() {
rec = rec.paint(p);
}
if let Some(b) = bounds.as_ref() {
rec = rec.bounds(b);
}
if let Some(bd) = backdrop.as_ref() {
rec = rec.backdrop(bd);
}
canvas.save_layer(&rec);
});
let new_state = self.state.clone();
self.stack.push(new_state);
self.layers.push(true);
}
pub fn scoot(&mut self, point: Point) {
if self.path.snapshot().is_empty() {
self.path.move_to(point);
}
}
pub fn draw_path(
&mut self,
path: Option<Path>,
style: PaintStyle,
rule: Option<PathFillType>,
) {
let mut path = path.unwrap_or_else(|| {
let inverse = self.state.matrix.invert().unwrap_or_default();
self.path.snapshot().make_transform(&inverse)
});
path.set_fill_type(rule.unwrap_or(PathFillType::Winding));
if matches!(style, PaintStyle::Fill | PaintStyle::StrokeAndFill)
&& matches!(
&self.state.global_composite_operation,
BlendMode::SrcOver | BlendMode::Src | BlendMode::Clear
)
&& self.state.fill_style.is_opaque()
&& self.state.global_alpha == 1.0
&& self.state.clip.is_none()
&& path.conservatively_contains_rect(self.bounds)
{
self.with_recorder(|mut recorder| {
recorder.set_bounds(self.bounds);
recorder.set_matrix(self.state.matrix);
recorder.set_clip(&self.state.clip);
});
}
let paint = self.paint_for_drawing(style);
self.render_to_canvas(&paint, |canvas, paint| {
if let Some(tile) = self.state.texture(style) {
let mut tile_paint = paint.clone();
tile.mix_into(&mut tile_paint, self.state.global_alpha);
let mut stencil_builder = PathBuilder::new();
fill_path_with_paint(
&path,
paint,
&mut stencil_builder,
None,
None,
);
let stencil = stencil_builder.detach();
let expanded_bounds =
stencil.bounds().with_outset(tile.spacing() * 1.5);
let enclosing_frame = Path::rect(expanded_bounds, None);
if tile.use_clip() {
canvas.save();
canvas.clip_path(
&stencil,
Some(ClipOp::Intersect),
Some(true),
);
canvas.draw_path(&enclosing_frame, &tile_paint);
canvas.restore();
} else {
let mut textured_builder = PathBuilder::new();
fill_path_with_paint(
&enclosing_frame,
&tile_paint,
&mut textured_builder,
None,
None,
);
let textured_frame = textured_builder.detach();
let mut fill_paint = paint.clone();
fill_paint.set_style(PaintStyle::Fill);
if let Some(fill_path) =
stencil.op(&textured_frame, PathOp::Intersect)
{
canvas.draw_path(&fill_path, &fill_paint);
}
}
} else {
canvas.draw_path(&path, paint);
}
});
}
pub fn clip_path(&mut self, path: Option<Path>, rule: PathFillType) {
let mut clip = match path {
Some(path) => path.make_transform(&self.state.matrix),
None => self.path.snapshot(),
};
clip.set_fill_type(rule);
self.state.clip = self
.state
.clip
.as_ref()
.unwrap_or(&Path::rect(self.bounds, None))
.op(&clip, PathOp::Intersect)
.and_then(|path| {
match path.conservatively_contains_rect(self.bounds) {
true => None,
false => Some(path),
}
});
self.with_recorder(|mut recorder| {
recorder.set_clip(&self.state.clip);
});
}
pub fn hit_test_path(
&mut self,
path: &mut Path,
point: impl Into<Point>,
rule: Option<PathFillType>,
style: PaintStyle,
) -> bool {
let point = point.into();
let point = self.in_local_coordinates(point.x, point.y);
let rule = rule.unwrap_or(PathFillType::Winding);
let prev_rule = path.fill_type();
path.set_fill_type(rule);
let is_in = match style {
PaintStyle::Stroke => {
let paint = self.paint_for_drawing(PaintStyle::Stroke);
let precision = 0.3; let scale = Matrix::scale((precision, precision));
let mut traced_builder = PathBuilder::new();
if fill_path_with_paint(
path,
&paint,
&mut traced_builder,
None,
Some(scale),
) {
traced_builder.detach().contains(point)
} else {
path.contains(point)
}
}
_ => path.contains(point),
};
path.set_fill_type(prev_rule);
is_in
}
pub fn clear_rect(&mut self, rect: &Rect) {
match self.state.matrix.map_rect(rect).0.contains(self.bounds) {
true => self.with_recorder(|mut recorder| {
recorder.set_bounds(self.bounds);
recorder.set_matrix(self.state.matrix);
recorder.set_clip(&self.state.clip);
}),
false => self.with_canvas(|canvas| {
let mut paint = Paint::default();
paint
.set_anti_alias(true)
.set_style(PaintStyle::Fill)
.set_blend_mode(BlendMode::Clear);
canvas.draw_rect(rect, &paint);
}),
}
}
pub fn draw_picture(
&mut self,
picture: &Picture,
src_rect: &Rect,
dst_rect: &Rect,
) {
let paint = self.paint_for_image();
let mag = Point::new(
dst_rect.width() / src_rect.width(),
dst_rect.height() / src_rect.height(),
);
let mut matrix = Matrix::new_identity();
matrix.pre_scale((mag.x, mag.y), None).pre_translate((
dst_rect.x() / mag.x - src_rect.x(),
dst_rect.y() / mag.y - src_rect.y(),
));
self.render_to_canvas(&paint, |canvas, paint| {
let paint = match (
paint.as_blend_mode(),
paint.alpha(),
paint.image_filter(),
) {
(Some(BlendMode::SrcOver), 255, None) => None,
_ => Some(paint),
};
canvas.save();
canvas.clip_rect(dst_rect, ClipOp::Intersect, true);
canvas.draw_picture(picture, Some(&matrix), paint);
canvas.restore();
});
}
pub fn draw_image(
&mut self,
image: &Image,
src_rect: &Rect,
dst_rect: &Rect,
) {
let paint = self.paint_for_image();
self.render_to_canvas(&paint, |canvas, paint| {
let sampling = self.state.sampling_filter.sampling();
canvas.draw_image_rect_with_sampling_options(
image,
Some((src_rect, Strict)),
dst_rect,
sampling,
paint,
);
});
}
pub fn get_page(&self) -> Page {
self.recorder.borrow_mut().get_page()
}
pub fn get_page_for_export(
&self,
opts: &ExportOptions,
engine: &RenderingEngine,
) -> Page {
self.recorder.borrow_mut().get_page_for_export(opts, engine)
}
pub fn get_image(&self) -> Option<Image> {
self.recorder.borrow_mut().get_image()
}
pub fn get_picture(&mut self) -> Option<Picture> {
self.recorder.borrow_mut().get_page().get_picture(None)
}
pub fn get_pixels(
&mut self,
crop: IRect,
opts: ExportOptions,
engine: RenderingEngine,
) -> Result<Vec<u8>, String> {
self.recorder.borrow_mut().get_pixels(crop, opts, engine)
}
pub fn blit_pixels(
&mut self,
image_data: ImageData,
src_rect: &Rect,
dst_rect: &Rect,
) {
let info = image_data.image_info();
if let Some(bitmap) = images::raster_from_data(
&info,
image_data.buffer,
info.min_row_bytes(),
) {
self.push(); self.with_canvas(|canvas| {
let paint = Paint::default();
let mut eraser = Paint::default();
canvas.restore_to_count(1); eraser.set_blend_mode(BlendMode::Clear);
canvas.draw_image_rect(
&bitmap,
Some((src_rect, Strict)),
dst_rect,
&eraser,
);
canvas.draw_image_rect(
&bitmap,
Some((src_rect, Strict)),
dst_rect,
&paint,
);
});
self.pop(); }
}
pub fn set_font(&mut self, spec: FontSpec) {
if let Some(new_style) = FontLibrary::with_shared(|lib| {
lib.update_style(&self.state.char_style, &spec)
}) {
self.state.font = spec.canonical;
self.state.font_variant = spec.variant.to_string();
self.state.font_width = spec.width;
self.state.char_style = new_style;
self.state.line_height = spec.line_height;
}
}
pub fn set_font_variant(
&mut self,
variant: &str,
features: &[(String, i32)],
) {
self.state.char_style.reset_font_features();
for (feat, val) in features {
self.state.char_style.add_font_feature(feat, *val);
}
self.state.font_variant = variant.to_string();
}
pub fn set_font_width(&mut self, width: Width) {
let style = self.state.char_style.font_style();
let font_style = FontStyle::new(style.weight(), width, style.slant());
self.state.char_style.set_font_style(font_style);
self.state.font_width = width;
}
pub fn draw_text(
&mut self,
text: &str,
x: f32,
y: f32,
width: Option<f32>,
style: PaintStyle,
) {
let paint = self.paint_for_drawing(style);
let mut typesetter = Typesetter::new(&self.state, text, width);
let origin = Point::new(x, y);
if self.state.texture(style).is_some() {
self.draw_path(Some(typesetter.path(origin)), style, None);
} else {
self.render_to_canvas(&paint, |canvas, paint| {
let (paragraph, offset) = typesetter.layout(paint);
paragraph.paint(canvas, origin + offset);
});
}
}
pub fn measure_text(
&mut self,
text: &str,
width: Option<f32>,
) -> serde_json::Value {
Typesetter::new(&self.state, text, width).metrics()
}
pub fn outline_text(&self, text: &str, width: Option<f32>) -> Path {
Typesetter::new(&self.state, text, width).path((0.0, 0.0))
}
pub fn paint_for_drawing(&mut self, style: PaintStyle) -> Paint {
let mut paint = self.state.paint.clone();
if let Some(cf) = &self.state.skia_color_filter {
paint.set_color_filter(cf.clone());
}
self.state
.filter
.mix_into(&mut paint, self.state.matrix, false);
if let Some(skia_imgf) = &self.state.skia_image_filter {
let final_image_filter = match paint.image_filter() {
Some(css_imgf) => {
image_filters::compose(skia_imgf.clone(), css_imgf)
}
None => Some(skia_imgf.clone()),
};
paint.set_image_filter(final_image_filter);
}
if let Some(mf) = &self.state.skia_mask_filter {
paint.set_mask_filter(mf.clone());
}
paint.set_dither(self.state.dither);
self.state.dye(style).mix_into(
&mut paint,
self.state.global_alpha,
self.state.sampling_filter,
);
paint.set_style(style);
if style == PaintStyle::Stroke && !self.state.line_dash_list.is_empty()
{
let effect = match &self.state.line_dash_marker {
Some(path) => {
let marker = match path.is_last_contour_closed() {
true => path.clone(),
false => {
let mut traced_builder = PathBuilder::new();
fill_path_with_paint(
path,
&paint,
&mut traced_builder,
None,
None,
);
traced_builder.detach()
}
};
path_1d_path_effect::new(
&marker,
self.state.line_dash_list[0],
self.state.line_dash_offset,
self.state.line_dash_fit,
)
}
None => dash_path_effect::new(
&self.state.line_dash_list,
self.state.line_dash_offset,
),
};
paint.set_path_effect(effect);
}
paint
}
pub fn paint_for_image(&mut self) -> Paint {
let mut paint = self.state.paint.clone();
if let Some(cf) = &self.state.skia_color_filter {
paint.set_color_filter(cf.clone());
}
self.state
.filter
.mix_into(&mut paint, self.state.matrix, true)
.set_alpha_f(self.state.global_alpha);
if let Some(skia_imgf) = &self.state.skia_image_filter {
let final_image_filter = match paint.image_filter() {
Some(css_imgf) => {
image_filters::compose(skia_imgf.clone(), css_imgf)
}
None => Some(skia_imgf.clone()),
};
paint.set_image_filter(final_image_filter);
}
if let Some(mf) = &self.state.skia_mask_filter {
paint.set_mask_filter(mf.clone());
}
paint.set_dither(self.state.dither);
paint
}
pub fn paint_for_shadow(&self, base_paint: &Paint) -> Option<Paint> {
let State {
shadow_color,
mut shadow_blur,
shadow_offset,
..
} = self.state;
if shadow_color.a() == 0
|| (shadow_blur == 0.0 && shadow_offset.is_zero())
{
return None;
}
shadow_blur *= 0.5;
let mut sigma = Point::new(shadow_blur, shadow_blur);
if self.state.matrix.get_type().contains(TypeMask::SCALE)
&& !almost_zero(shadow_blur)
{
if let Some(scale) = self.state.matrix.decompose_scale(None) {
if almost_zero(scale.width) {
sigma.x = 0.0;
} else {
sigma.x /= scale.width;
}
if almost_zero(scale.height) {
sigma.y = 0.0;
} else {
sigma.y /= scale.height;
}
}
}
let mut paint = base_paint.clone();
paint.set_image_filter(image_filters::drop_shadow_only(
(0.0, 0.0),
(sigma.x, sigma.y),
shadow_color,
ColorSpace::new_srgb(),
None,
None,
));
Some(paint)
}
}
#[derive(Clone)]
pub enum Dye {
Color(Color4f, Option<ColorSpace>),
Gradient(CanvasGradient),
Pattern(CanvasPattern),
Texture(CanvasTexture),
Shader(SkShader),
}
impl Dye {
pub fn new<'a>(
cx: &mut FunctionContext<'a>,
value: Handle<'a, JsValue>,
canvas_color_space: &ColorSpace,
) -> Option<Self> {
if let Ok(gradient) = value.downcast::<BoxedCanvasGradient, _>(cx) {
Some(Dye::Gradient(gradient.borrow().clone()))
} else if let Ok(pattern) = value.downcast::<BoxedCanvasPattern, _>(cx)
{
Some(Dye::Pattern(pattern.borrow().clone()))
} else if let Ok(texture) = value.downcast::<BoxedCanvasTexture, _>(cx)
{
Some(Dye::Texture(texture.borrow().clone()))
} else if let Ok(shader) = value.downcast::<BoxedShader, _>(cx) {
Some(Dye::Shader(shader.borrow().inner.clone()))
} else {
color4f_in(cx, value).map(|(c, cs)| {
let cs = cs.unwrap_or_else(|| canvas_color_space.clone());
Dye::Color(c, Some(cs))
})
}
}
pub fn value<'a>(
&self,
cx: &mut FunctionContext<'a>,
) -> JsResult<'a, JsValue> {
match self {
Dye::Color(color, _) => {
color4f_to_css(cx, color)
}
_ => Ok(cx.null().upcast()),
}
}
pub fn is_opaque(&self) -> bool {
match self {
Dye::Color(color, _) => color.is_opaque(),
Dye::Gradient(gradient) => gradient.is_opaque(),
Dye::Pattern(pattern) => pattern.is_opaque(),
Dye::Texture(_) => false,
Dye::Shader(_) => false,
}
}
pub fn mix_into(
&self,
paint: &mut Paint,
alpha: f32,
sampling_filter: SamplingFilter,
) {
match self {
Dye::Color(color, cs) => {
let mut color = *color;
color.a *= alpha;
paint.set_color4f(color, cs.as_ref());
}
Dye::Gradient(gradient) => {
paint.set_shader(gradient.shader()).set_alpha_f(alpha);
}
Dye::Pattern(pattern) => {
paint
.set_shader(pattern.shader(sampling_filter))
.set_alpha_f(alpha);
}
Dye::Texture(texture) => {
let (color, cs) = texture.to_color4f(alpha);
paint.set_color4f(color, cs.as_ref());
}
Dye::Shader(shader) => {
paint.set_shader(Some(shader.clone())).set_alpha_f(alpha);
}
};
}
}