Skip to content

Commit 2adac3b

Browse files
committed
Add stroke align support on fill overlays
1 parent 95e6a92 commit 2adac3b

2 files changed

Lines changed: 89 additions & 32 deletions

File tree

Cargo.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,8 @@ web-sys = { version = "=0.3.77", features = [
165165
"HtmlCanvasElement",
166166
"CanvasRenderingContext2d",
167167
"CanvasPattern",
168+
"DomMatrix",
169+
"SvgMatrix",
168170
"OffscreenCanvas",
169171
"OffscreenCanvasRenderingContext2d",
170172
"TextMetrics",

editor/src/messages/portfolio/document/overlays/utility_types_web.rs

Lines changed: 87 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,16 @@ use graphene_std::Color;
1414
use graphene_std::math::quad::Quad;
1515
use graphene_std::raster::curve;
1616
use graphene_std::subpath::Subpath;
17+
use graphene_std::transform::Transform;
1718
use graphene_std::vector::click_target::ClickTargetType;
1819
use graphene_std::vector::misc::{dvec2_to_point, point_to_dvec2};
1920
use graphene_std::vector::stroke::DashLengthsInput;
20-
use graphene_std::vector::style::{PaintOrder, Stroke};
21+
use graphene_std::vector::style::{PaintOrder, Stroke, StrokeAlign};
2122
use graphene_std::vector::{PointId, SegmentId, Vector};
2223
use kurbo::{self, Affine, CubicBez, ParamCurve, PathSeg};
2324
use std::collections::HashMap;
2425
use wasm_bindgen::{JsCast, JsValue};
25-
use web_sys::{OffscreenCanvas, OffscreenCanvasRenderingContext2d};
26+
use web_sys::{DomMatrix, OffscreenCanvas, OffscreenCanvasRenderingContext2d, SvgMatrix};
2627

2728
pub type OverlayProvider = fn(OverlayContext) -> Message;
2829

@@ -997,7 +998,7 @@ impl OverlayContext {
997998
}
998999

9991000
/// Default canvas pattern used for filling stroke or fill of a path.
1000-
fn fill_canvas_pattern(&self, color: &Color) -> web_sys::CanvasPattern {
1001+
fn fill_canvas_pattern(&self, color: &Color, transform: DAffine2) -> web_sys::CanvasPattern {
10011002
const PATTERN_WIDTH: usize = 4;
10021003
const PATTERN_HEIGHT: usize = 4;
10031004

@@ -1009,6 +1010,8 @@ impl OverlayContext {
10091010
.expect("Failed to get canvas context")
10101011
.dyn_into()
10111012
.expect("Context should be a canvas 2d context");
1013+
// let [a, b, c, d, e, f] = DAffine2::from_scale(zoom).inverse().to_cols_array();
1014+
// pattern_context.set_transform(a, b, c, d, e, f);
10121015

10131016
// 4x4 pixels, 4 components (RGBA) per pixel
10141017
let mut data = [0_u8; 4 * PATTERN_WIDTH * PATTERN_HEIGHT];
@@ -1026,7 +1029,16 @@ impl OverlayContext {
10261029

10271030
let image_data = web_sys::ImageData::new_with_u8_clamped_array_and_sh(wasm_bindgen::Clamped(&data), PATTERN_WIDTH as u32, PATTERN_HEIGHT as u32).unwrap();
10281031
pattern_context.put_image_data(&image_data, 0., 0.).unwrap();
1029-
return self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap();
1032+
1033+
let pattern = self.render_context.create_pattern_with_offscreen_canvas(&pattern_canvas, "repeat").unwrap().unwrap();
1034+
// let ctx_matrix = (|| {
1035+
// let dom_matrix = self.render_context.get_transform().unwrap();
1036+
// dom_matrix.
1037+
// SvgMatrix::from(dom_matrix.to_json())
1038+
// })();
1039+
// pattern.set_transform(&ctx_matrix);
1040+
1041+
return pattern;
10301042
}
10311043

10321044
/// Fills the area inside the path (with an optional pattern). Assumes `color` is in gamma space.
@@ -1059,31 +1071,51 @@ impl OverlayContext {
10591071

10601072
self.draw_path_from_subpaths(subpaths, applied_stroke_transform);
10611073

1062-
match stroke.paint_order {
1063-
PaintOrder::StrokeAbove => {
1064-
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color));
1065-
self.render_context.fill();
1066-
1067-
// Make the stroke transparent and erase the fill area overlapping the stroke.
1068-
self.render_context.set_stroke_style_str(&"#000000");
1069-
self.render_context.set_line_width(stroke.weight());
1070-
self.render_context.set_global_composite_operation("destination-out").expect("Failed to set global composite operation");
1071-
self.render_context.stroke();
1074+
let do_fill = || {
1075+
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color, element_transform));
1076+
self.render_context.fill();
1077+
};
1078+
let do_stroke = |stroke_weight: f64| {
1079+
self.render_context.set_line_width(stroke_weight);
1080+
self.render_context.set_stroke_style_str(&"#000000");
1081+
self.render_context.stroke();
1082+
};
1083+
let composite_mode = |composite_operation: &str| {
1084+
self.render_context
1085+
.set_global_composite_operation(composite_operation)
1086+
.expect("Failed to set global composite operation");
1087+
};
1088+
match (stroke.align, stroke.paint_order) {
1089+
(StrokeAlign::Inside, PaintOrder::StrokeAbove) => {
1090+
do_fill();
1091+
composite_mode("destination-out");
1092+
do_stroke(stroke.weight() * 2.);
1093+
}
1094+
(StrokeAlign::Inside, PaintOrder::StrokeBelow) => do_fill(),
1095+
(StrokeAlign::Center, PaintOrder::StrokeAbove) => {
1096+
do_fill();
1097+
composite_mode("destination-out");
1098+
do_stroke(stroke.weight());
10721099
}
1073-
PaintOrder::StrokeBelow => {
1074-
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color));
1075-
self.render_context.fill();
1100+
(StrokeAlign::Center, PaintOrder::StrokeBelow) => {
1101+
do_fill();
1102+
}
1103+
// Paint order does not affect this
1104+
(StrokeAlign::Outside, _) => {
1105+
do_fill();
10761106
}
10771107
}
10781108
} else {
1079-
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color));
1109+
self.render_context.set_fill_style_canvas_pattern(&self.fill_canvas_pattern(color, DAffine2::IDENTITY));
10801110
self.render_context.fill();
10811111
}
10821112

10831113
self.end_dpi_aware_transform();
10841114
self.render_context.restore();
10851115
}
10861116

1117+
// WARN: Don't use source-in, destination-atop, destination-in, copy
1118+
// on the main canvas as it will erase the existing overlays
10871119
pub fn stroke_overlay(&mut self, subpaths: impl Iterator<Item = impl Borrow<Subpath<PointId>>>, layer_to_viewport: DAffine2, color: &Color, stroke: Option<Stroke>) {
10881120
// Render for elements with stroke
10891121
//----StrokeAlign
@@ -1104,21 +1136,44 @@ impl OverlayContext {
11041136

11051137
self.draw_path_from_subpaths(subpaths, applied_stroke_transform);
11061138

1107-
self.render_context.set_stroke_style_canvas_pattern(&self.fill_canvas_pattern(color));
1108-
self.render_context.set_line_width(stroke.weight);
1109-
self.render_context.set_line_cap(stroke.cap.html_canvas_name().as_str());
1110-
self.render_context.set_line_join(stroke.join.html_canvas_name().as_str());
1111-
self.render_context.set_miter_limit(stroke.join_miter_limit);
1112-
match stroke.paint_order {
1113-
PaintOrder::StrokeAbove => {
1114-
self.render_context.stroke();
1139+
let do_stroke = |stroke_weight: f64| {
1140+
self.render_context.set_stroke_style_canvas_pattern(&self.fill_canvas_pattern(color, element_transform));
1141+
self.render_context.set_line_width(stroke_weight);
1142+
self.render_context.set_line_cap(stroke.cap.html_canvas_name().as_str());
1143+
self.render_context.set_line_join(stroke.join.html_canvas_name().as_str());
1144+
self.render_context.set_miter_limit(stroke.join_miter_limit);
1145+
self.render_context.stroke();
1146+
};
1147+
let do_fill = || {
1148+
self.render_context.set_fill_style_str(&"#000000");
1149+
self.render_context.fill();
1150+
};
1151+
let composite_mode = |composite_operation: &str| {
1152+
self.render_context
1153+
.set_global_composite_operation(composite_operation)
1154+
.expect("Failed to set global composite operation");
1155+
};
1156+
match (stroke.align, stroke.paint_order) {
1157+
(StrokeAlign::Inside, PaintOrder::StrokeAbove) => {
1158+
// TODO: Use something aside from destination-in
1159+
do_stroke(stroke.weight() * 2.);
1160+
composite_mode("destination-in");
1161+
do_fill();
11151162
}
1116-
PaintOrder::StrokeBelow => {
1117-
self.render_context.stroke();
1118-
1119-
self.render_context.set_fill_style_str(&"#000000");
1120-
self.render_context.set_global_composite_operation("destination-out").expect("Failed to set global composite operation");
1121-
self.render_context.fill();
1163+
(StrokeAlign::Inside, PaintOrder::StrokeBelow) => {}
1164+
(StrokeAlign::Center, PaintOrder::StrokeAbove) => {
1165+
do_stroke(stroke.weight());
1166+
}
1167+
(StrokeAlign::Center, PaintOrder::StrokeBelow) => {
1168+
do_stroke(stroke.weight());
1169+
composite_mode("destination-out");
1170+
do_fill();
1171+
}
1172+
// Paint order does not affect this
1173+
(StrokeAlign::Outside, _) => {
1174+
do_stroke(stroke.weight() * 2.);
1175+
composite_mode("destination-out");
1176+
do_fill();
11221177
}
11231178
}
11241179
}

0 commit comments

Comments
 (0)