From 6ac62214eb4476542ab58cc4a7cb3db91204d00c Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 23 Oct 2026 01:35:44 -0700 Subject: [PATCH 1/3] Fix #3748: in isolation mode, art outside a clip group's mask can be selected and dragged In isolation mode the mask of the isolated clip group still limited clicks and the Selection tool's marquee. Art dragged outside the mask could not be clicked or dragged with the Selection, Direct Selection or Group Selection tool, and a press there started a marquee. When the isolated object is a clip group, a layer with a clipping mask or a single object in one, those tools now hit the art the mask hides, after the mask's outline. A marquee there selects it too, where #2630 had left it out, and the context menu's Unlock list follows the same rule. Outside isolation mode, and for the tools that act on what is drawn, clipped art is still hit only inside the mask. Docs: docs/target-app-parity.md. --- crates/doc/src/hit.rs | 67 ++++++++++++++++++++++++++++++-------- crates/tools/src/direct.rs | 17 ++++++++++ crates/tools/src/select.rs | 20 ++++++++++++ docs/target-app-parity.md | 1 + 4 files changed, 92 insertions(+), 13 deletions(-) diff --git a/crates/doc/src/hit.rs b/crates/doc/src/hit.rs index 18deed56b..bc5607634 100644 --- a/crates/doc/src/hit.rs +++ b/crates/doc/src/hit.rs @@ -246,7 +246,7 @@ pub fn locked_objects_at(doc: &Document, p: Point, opt: HitOptions) -> Vec) { let Some(children) = parent.children() else { return }; - if parent.clips() && children.first().is_some_and(|clip| !clip_contains(clip, p)) { + if parent.clips() && !edits_inside(parent, opt.scope) && children.first().is_some_and(|clip| !clip_contains(clip, p)) { return; } for i in hit_order(parent, children, p) { @@ -420,6 +420,15 @@ fn clip_outline_hit(parent: &Node, clip: &Node, p: Point, opt: HitOptions, symbo clip_outline(clip, p, opt) } +/// Does isolation mode (`scope`) edit what `parent` holds: is `parent` the isolated container, or +/// the parent of the single object isolated? Then the selection tools hit the art that the +/// clipping mask of `parent` hides, and the Unlock submenu lists it (#3748). The reference +/// app's help says that the parts outside a clipping mask can still be edited +/// (). +fn edits_inside(parent: &Node, scope: Option) -> bool { + scope.is_some_and(|s| s == parent.id || parent.children().is_some_and(|c| c.iter().any(|c| c.id == s))) +} + fn hit_children( parent: &Node, p: Point, @@ -437,8 +446,10 @@ fn hit_children( chain.push(clip.id); return Some(Hit { leaf: clip.id, ancestry: chain.clone(), kind, contents_of: None, layers: 1 }); } - // Elsewhere a clip group (or a layer with a clipping mask) only hits inside it. - if !clip_contains(clip, p) { + // Elsewhere a clip group (or a layer with a clipping mask) only hits inside it, except for + // the selection tools in isolation mode within it. + let edited = opt.clip_outlines && editable_only && edits_inside(parent, opt.scope); + if !edited && !clip_contains(clip, p) { return None; } } @@ -607,21 +618,22 @@ pub fn compound_part_at(n: &Node, p: Point, tol: f64) -> Option { /// out those on a locked artboard ([`crate::LockedArtboards::holds_id`]). Symbol instances are measured /// by their art ([`Node::geometric_bounds_with`]). The art of a clip group or of a layer with a /// clipping mask counts only when the marquee meets the region its clipping path clips to, and then -/// only within that region's bounds. +/// only within that region's bounds. In isolation mode the clipping mask of the isolated container, +/// or of the parent of the single object isolated, does not limit the marquee, as it does not limit +/// the selection tools' clicks. pub fn marquee(doc: &Document, r: Rect, scope: Option, leaves: bool) -> Vec { let mut out = Vec::new(); // `tops` pairs each object with the part of `r` in which the marquee can touch it. let tops: Vec<(&std::sync::Arc, Rect)> = match scope.and_then(|s| doc.node(s)) { - Some(s) => match (s.children(), marquee_in(s, r)) { - (Some(c), Some(r)) => c.iter().enumerate().filter(|(i, _)| !shaper_hides(s, *i, scope)).map(|(_, c)| (c, r)).collect(), - (Some(_), None) => vec![], + Some(s) => match s.children() { + Some(c) => c.iter().enumerate().filter(|(i, _)| !shaper_hides(s, *i, scope)).map(|(_, c)| (c, r)).collect(), // A single object isolated: only it. - (None, _) => doc + None => doc .parent_of(s.id) .and_then(|p| doc.node(p)) - .and_then(|p| Some((p.children()?, marquee_in(p, r)?))) + .and_then(|p| p.children()) .into_iter() - .flat_map(|(c, r)| c.iter().filter(|c| c.id == s.id).map(move |c| (c, r))) + .flat_map(|c| c.iter().filter(|c| c.id == s.id).map(|c| (c, r))) .collect(), }, // The objects of the layers, looking through sublayers (hidden, locked and template ones @@ -814,7 +826,9 @@ mod tests { /// #2345: a marquee touches a clip group only where its art shows. Over the clipped-away /// half alone it selects nothing, and around a rectangle drawn beside the group's mask it /// takes that rectangle alone; reaching into the mask takes the group. The same holds for - /// Group Selection's leaves and in isolation mode. + /// Group Selection's leaves. In isolation mode (#3748) the isolated group's own mask does not + /// limit the marquee, as it does not limit clicks: with the group or the art isolated, the + /// marquee takes the hidden art. #[test] fn a_marquee_leaves_a_clip_group_by_its_clipped_away_art() { let mut d = Document::new(300.0, 200.0); @@ -829,8 +843,9 @@ mod tests { let hidden = Rect::new(150.0, -10.0, 210.0, 50.0); assert!(marquee(&d, hidden, None, false).is_empty()); assert!(marquee(&d, hidden, None, true).is_empty()); - assert!(marquee(&d, hidden, Some(g), false).is_empty()); - assert!(marquee(&d, hidden, Some(art_id), false).is_empty()); + assert_eq!(marquee(&d, hidden, Some(g), false), vec![art_id]); + assert_eq!(marquee(&d, hidden, Some(g), true), vec![art_id]); + assert_eq!(marquee(&d, hidden, Some(art_id), false), vec![art_id]); // This marquee surrounds the rectangle beside and crosses the hidden art's bottom edge. assert_eq!(marquee(&d, Rect::new(140.0, 70.0, 195.0, 140.0), None, false), vec![beside]); // This marquee crosses the shown half's top edge and goes on into the hidden half. @@ -1096,6 +1111,32 @@ mod tests { assert_eq!(objects_at(&d, Point::new(138.5, 90.0), HitOptions::default(), None), vec![art, clip]); } + /// #3748: in isolation mode the selection tools hit the art that the isolated clip group (or + /// the isolated layer with a clipping mask) hides outside its mask, and the art when it is the + /// single object isolated; the mask's outline is still hit first. Outside isolation mode, and + /// for the tools that act on what is drawn, the hidden art is not hit. The Unlock submenu + /// lists the hidden art when it is locked. + #[test] + fn isolation_reaches_the_art_a_clipping_mask_hides() { + let hidden = Point::new(170.0, 90.0); + for layer in [false, true] { + let (mut d, g, clip, art) = masked_square(layer); + let isolated = HitOptions { scope: Some(g), ..SELECTING }; + assert!(hit_test(&d, hidden, SELECTING).is_none(), "layer {layer}: not isolated"); + let h = hit_test(&d, hidden, isolated); + assert_eq!(h.map(|h| (h.leaf, h.top_object(Some(g)))), Some((art, art)), "layer {layer}"); + assert_eq!(objects_at(&d, hidden, SELECTING, Some(g)), vec![art], "layer {layer}"); + assert_eq!(hit_test(&d, Point::new(140.0, 90.0), isolated).map(|h| h.leaf), Some(clip), "layer {layer}: the outline first"); + let one = HitOptions { scope: Some(art), ..SELECTING }; + assert_eq!(hit_test(&d, hidden, one).map(|h| h.leaf), Some(art), "layer {layer}: the art isolated"); + assert!(hit_test(&d, hidden, HitOptions { scope: Some(g), ..HitOptions::default() }).is_none(), "layer {layer}: drawn art only"); + assert!(hit_test_visible(&d, hidden, isolated).is_none(), "layer {layer}: sampling"); + d.node_mut(art).unwrap().locked = true; + assert_eq!(locked_objects_at(&d, hidden, HitOptions { scope: Some(g), ..HitOptions::default() }), vec![art], "layer {layer}"); + assert!(locked_objects_at(&d, hidden, HitOptions::default()).is_empty(), "layer {layer}: not isolated"); + } + } + /// Art in a sublayer is an object of its own: a click or a marquee takes it, not the /// sublayer; hidden, locked and template sublayers block both. #[test] diff --git a/crates/tools/src/direct.rs b/crates/tools/src/direct.rs index 71fc26e28..9f7570904 100644 --- a/crates/tools/src/direct.rs +++ b/crates/tools/src/direct.rs @@ -1108,6 +1108,23 @@ mod tests { assert!(names(&a, art) && !names(&a, clip), "{a:?}"); } + /// #3748: in isolation mode Direct Selection and Group Selection take the art that the + /// isolated clip group hides outside its mask; outside isolation mode a press there does not. + #[test] + fn isolation_lets_direct_selection_take_the_art_a_clip_group_hides() { + let (d, g, _, art) = doc_with_masked_square(); + let (s, p) = (Selection::default(), paint()); + let names = + |actions: &[Action]| actions.iter().any(|a| matches!(a, Action::Exec(_, v) if v["id"] == json!(art.0) || v["ids"] == json!([art.0]))); + for group in [false, true] { + let isolated = ToolContext { isolation: Some(g), ..cx(&d, &s, &p) }; + let a = DirectSelectionTool::new(group).pointer(&isolated, &PointerEvent::new(PointerKind::Down, 530.0, 450.0)); + assert!(names(&a), "group {group}: {a:?}"); + let a = DirectSelectionTool::new(group).pointer(&cx(&d, &s, &p), &PointerEvent::new(PointerKind::Down, 530.0, 450.0)); + assert!(!names(&a), "group {group}, not isolated: {a:?}"); + } + } + /// A blend of two 20 pt squares centred on (110, 310) and (210, 310), selected. fn blend_doc() -> (vectorcraft_doc::Document, NodeId, NodeId) { let (mut d, _) = doc_with_rect(); diff --git a/crates/tools/src/select.rs b/crates/tools/src/select.rs index 51ffd51da..c60629be0 100644 --- a/crates/tools/src/select.rs +++ b/crates/tools/src/select.rs @@ -1012,6 +1012,26 @@ mod tests { assert!(!a.iter().any(|a| matches!(a, Action::Exec(c, _) if c == "select.set")), "{a:?}"); } + /// #3748: in isolation mode a press on the art that the isolated clip group hides outside its + /// mask selects that art, and a drag from there moves it. Outside isolation mode the same + /// press starts a marquee. + #[test] + fn isolation_drags_the_art_a_clip_group_hides() { + let (d, g, _, art) = doc_with_masked_square(); + let (none, p) = (Selection::default(), paint()); + let mut picked = Selection::default(); + picked.add(art); + let mut t = SelectionTool::default(); + let a = t.pointer(&ToolContext { isolation: Some(g), ..cx(&d, &none, &p) }, &ev(PointerKind::Down, 530.0, 450.0)); + assert_eq!(a, vec![Action::Exec("select.set".into(), json!({ "ids": [art.0] }))]); + let a = t.pointer(&ToolContext { isolation: Some(g), ..cx(&d, &picked, &p) }, &ev(PointerKind::Drag, 430.0, 450.0)); + let Some(Action::Preview(c, v)) = a.get(1) else { panic!("{a:?}") }; + assert_eq!((c.as_str(), v["matrix"][4].as_f64(), v["matrix"][5].as_f64()), ("object.transform", Some(-100.0), Some(0.0))); + let mut t = SelectionTool::default(); + assert!(t.pointer(&cx(&d, &none, &p), &ev(PointerKind::Down, 530.0, 450.0)).is_empty()); + assert!(matches!(t.state, State::Marquee { .. }), "not isolated"); + } + /// #955: grabbed by an anchor and dropped near another object's anchor, the anchor lands /// exactly on it with Smart Guides, even where lining the bounds up would leave it beside it. #[test] diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index ac75ebff1..8b05058cd 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -601,6 +601,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur With the Selection or Group Selection tool, pressing E while dragging a marquee makes the marquee select only the objects that lie entirely inside it. Pressing E again switches the marquee back to its default selection. The Lasso picks anchor points only on art a click can pick, and the Add Anchor Point, Delete Anchor Point, Anchor Point and Scissors tools pick anchor points and segments only on the selected paths and on art a click can pick: not on a Shaper Group's original shapes outside Construction mode, and in isolation mode only inside the isolated group or object. Smart Guides and Snap to Point do not snap to a Shaper Group's original shapes, and the reference point of the transform tools snaps to one only when it is selected, or under the pointer in Construction mode. A marquee selects a clip group, or an object on a layer with a clipping mask, only when it reaches the area inside the clipping path, and it measures the art only within that path's bounds. Within the bounds of a mask that is not a rectangle, it can still select art that the mask hides. Direct Selection's marquee still selects the hidden anchor points inside it (#2345). + When a clip group, a layer with a clipping mask or a single object in one is isolated, the Selection, Direct Selection and Group Selection tools also select the art that the mask hides, by a click or a marquee, and a drag from that art moves it (#3748). Outside isolation mode a click selects clipped art only inside the mask. A marquee selects a Shaper Group only when it reaches the art the group shows, not the original shapes kept under it, and Direct Selection's clicks and marquee do not select those shapes' anchor points. In Construction mode a marquee selects the original shapes, and Direct Selection selects their anchor points. A marquee selects an Intertwine group only when it reaches one of the group's members, as for a plain group, and it selects a repeat only when it reaches one of the copies the repeat draws. Direct and Group Selection's marquees select the anchor points of an Intertwine group's members, not the group, and Group Selection's E marquee selects the members inside it. A key object needs another selected object outside it. A Shift-click or any other change that leaves one object selected clears the key, and `select.key` sets none on one object or on a group selected with only its own members. With one object selected, the Align panel, the Control bar and the Properties panel align it to the artboard whatever Align To is set to. From 2e09b9bc4732f4d8b40cf234b0b4d02472c8938b Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Fri, 23 Oct 2026 18:57:53 -0700 Subject: [PATCH 2/3] Docs: the Unlock submenu lists locked art a mask hides in isolation mode --- docs/target-app-parity.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/target-app-parity.md b/docs/target-app-parity.md index 8b05058cd..530d55420 100644 --- a/docs/target-app-parity.md +++ b/docs/target-app-parity.md @@ -601,7 +601,7 @@ Moved from the ROADMAP's "Shipped so far" on 2026-10-10. Update it when a featur With the Selection or Group Selection tool, pressing E while dragging a marquee makes the marquee select only the objects that lie entirely inside it. Pressing E again switches the marquee back to its default selection. The Lasso picks anchor points only on art a click can pick, and the Add Anchor Point, Delete Anchor Point, Anchor Point and Scissors tools pick anchor points and segments only on the selected paths and on art a click can pick: not on a Shaper Group's original shapes outside Construction mode, and in isolation mode only inside the isolated group or object. Smart Guides and Snap to Point do not snap to a Shaper Group's original shapes, and the reference point of the transform tools snaps to one only when it is selected, or under the pointer in Construction mode. A marquee selects a clip group, or an object on a layer with a clipping mask, only when it reaches the area inside the clipping path, and it measures the art only within that path's bounds. Within the bounds of a mask that is not a rectangle, it can still select art that the mask hides. Direct Selection's marquee still selects the hidden anchor points inside it (#2345). - When a clip group, a layer with a clipping mask or a single object in one is isolated, the Selection, Direct Selection and Group Selection tools also select the art that the mask hides, by a click or a marquee, and a drag from that art moves it (#3748). Outside isolation mode a click selects clipped art only inside the mask. + When a clip group, a layer with a clipping mask or a single object in one is isolated, the Selection, Direct Selection and Group Selection tools also select the art that the mask hides, by a click or a marquee, and a drag from that art moves it (#3748). In isolation mode the canvas Unlock submenu also lists that art when it is locked. Outside isolation mode a click selects clipped art only inside the mask. A marquee selects a Shaper Group only when it reaches the art the group shows, not the original shapes kept under it, and Direct Selection's clicks and marquee do not select those shapes' anchor points. In Construction mode a marquee selects the original shapes, and Direct Selection selects their anchor points. A marquee selects an Intertwine group only when it reaches one of the group's members, as for a plain group, and it selects a repeat only when it reaches one of the copies the repeat draws. Direct and Group Selection's marquees select the anchor points of an Intertwine group's members, not the group, and Group Selection's E marquee selects the members inside it. A key object needs another selected object outside it. A Shift-click or any other change that leaves one object selected clears the key, and `select.key` sets none on one object or on a group selected with only its own members. With one object selected, the Align panel, the Control bar and the Properties panel align it to the artboard whatever Align To is set to. From e725c4920cdd6ddefa2eaca0588e08ee04e44abc Mon Sep 17 00:00:00 2001 From: dotCooCoo Date: Sat, 31 Oct 2026 22:26:08 -0700 Subject: [PATCH 3/3] Tests: nested clip groups in isolation mode and Direct Selection drags - isolation_reaches_hidden_art_only_for_the_isolated_clip_group: with the outer clip group isolated, clicks and marquees take the art its own mask hides, while an inner clip group's mask still clips that group's art; isolating the inner group or its art reaches the inner hidden art. - isolation_lets_direct_selection_take_the_art_a_clip_group_hides now continues the press with a drag on the same tool and checks the Move step and its preview, for Direct Selection and Group Selection. --- crates/doc/src/hit/tests.rs | 50 ++++++++++++++++++++++++++++++++ crates/tools/src/direct/tests.rs | 13 +++++++-- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/crates/doc/src/hit/tests.rs b/crates/doc/src/hit/tests.rs index 2e2cd6944..8cc3dd71f 100644 --- a/crates/doc/src/hit/tests.rs +++ b/crates/doc/src/hit/tests.rs @@ -533,6 +533,56 @@ fn isolation_reaches_the_art_a_clipping_mask_hides() { } } +/// #3748, nested clip groups: with the outer clip group isolated, a click or a marquee takes +/// the art its own mask hides, while the mask of a clip group inside it still limits that +/// group's art. With the inner clip group isolated, or its art, the inner mask does not. +#[test] +fn isolation_reaches_hidden_art_only_for_the_isolated_clip_group() { + let mut d = Document::new(300.0, 200.0); + let l = d.layers[0].id; + let (outer, outer_clip, outer_art, inner, inner_clip, inner_art) = + (d.alloc_id(), d.alloc_id(), d.alloc_id(), d.alloc_id(), d.alloc_id(), d.alloc_id()); + let mask = |id, r| { + let mut path = Node::path(id, shapes::rectangle(r), Appearance::basic(Paint::None, Paint::None, 0.0)); + if let NodeKind::Path { clipping, .. } = &mut path.kind { + *clipping = true; + } + Arc::new(path) + }; + let art = |id, r| Arc::new(Node::path(id, shapes::rectangle(r), Appearance::default_art())); + let inner_group = Node::new( + inner, + NodeKind::Group { + children: vec![mask(inner_clip, Rect::new(60.0, 60.0, 120.0, 120.0)), art(inner_art, Rect::new(40.0, 40.0, 160.0, 160.0))], + clip: true, + }, + ); + let children = + vec![mask(outer_clip, Rect::new(20.0, 20.0, 180.0, 180.0)), art(outer_art, Rect::new(190.0, 20.0, 260.0, 180.0)), Arc::new(inner_group)]; + d.insert(Some(l), 0, Node::new(outer, NodeKind::Group { children, clip: true })).unwrap(); + // `outer_hidden` is outside the outer mask, on the outer group's own art; `inner_hidden` is + // inside the outer mask and outside the inner one, on the inner group's art. + let (outer_hidden, inner_hidden, shown) = (Point::new(220.0, 100.0), Point::new(150.0, 100.0), Point::new(90.0, 90.0)); + let leaf = |p, scope| hit_test(&d, p, HitOptions { scope, ..SELECTING }).map(|h| h.leaf); + assert_eq!((leaf(outer_hidden, None), leaf(inner_hidden, None)), (None, None), "not isolated"); + assert_eq!(leaf(outer_hidden, Some(outer)), Some(outer_art), "outer isolated: its own hidden art"); + assert_eq!(leaf(inner_hidden, Some(outer)), None, "outer isolated: the inner mask still clips"); + let h = hit_test(&d, shown, HitOptions { scope: Some(outer), ..SELECTING }); + assert_eq!(h.map(|h| (h.leaf, h.top_object(Some(outer)))), Some((inner_art, inner)), "outer isolated: shown art"); + assert_eq!(leaf(inner_hidden, Some(inner)), Some(inner_art), "inner isolated"); + assert_eq!(leaf(outer_hidden, Some(inner)), None, "inner isolated: the outer group's art"); + assert_eq!(leaf(inner_hidden, Some(inner_art)), Some(inner_art), "the inner art isolated"); + // These marquees cross the right edge of the outer art (outside the outer mask) and of the + // inner art (inside the outer mask, outside the inner one). + let (outer_edge, inner_edge) = (Rect::new(255.0, 95.0, 265.0, 105.0), Rect::new(155.0, 95.0, 165.0, 105.0)); + assert_eq!(marquee(&d, outer_edge, Some(outer), false), vec![outer_art]); + assert!(marquee(&d, inner_edge, Some(outer), false).is_empty()); + assert!(marquee(&d, inner_edge, Some(outer), true).is_empty()); + assert_eq!(marquee(&d, inner_edge, Some(inner), false), vec![inner_art]); + assert!(marquee(&d, inner_edge, None, false).is_empty()); + assert!(marquee(&d, outer_edge, None, false).is_empty()); +} + /// Art in a sublayer is an object of its own: a click or a marquee takes it, not the /// sublayer; hidden, locked and template sublayers block both. #[test] diff --git a/crates/tools/src/direct/tests.rs b/crates/tools/src/direct/tests.rs index d61a8e538..718983d96 100644 --- a/crates/tools/src/direct/tests.rs +++ b/crates/tools/src/direct/tests.rs @@ -129,16 +129,25 @@ fn a_press_on_a_clipping_path_takes_its_segment_over_the_art() { } /// #3748: in isolation mode Direct Selection and Group Selection take the art that the -/// isolated clip group hides outside its mask; outside isolation mode a press there does not. +/// isolated clip group hides outside its mask, and a drag from there moves it; outside +/// isolation mode a press there does not take it. #[test] fn isolation_lets_direct_selection_take_the_art_a_clip_group_hides() { let (d, g, _, art) = doc_with_masked_square(); let (s, p) = (Selection::default(), paint()); + let mut picked = Selection::default(); + picked.add(art); let names = |actions: &[Action]| actions.iter().any(|a| matches!(a, Action::Exec(_, v) if v["id"] == json!(art.0) || v["ids"] == json!([art.0]))); for group in [false, true] { let isolated = ToolContext { isolation: Some(g), ..cx(&d, &s, &p) }; - let a = DirectSelectionTool::new(group).pointer(&isolated, &PointerEvent::new(PointerKind::Down, 530.0, 450.0)); + let mut t = DirectSelectionTool::new(group); + let a = t.pointer(&isolated, &PointerEvent::new(PointerKind::Down, 530.0, 450.0)); assert!(names(&a), "group {group}: {a:?}"); + // The press selected the art: the drag that follows moves it. + let isolated = ToolContext { isolation: Some(g), ..cx(&d, &picked, &p) }; + let a = t.pointer(&isolated, &PointerEvent::new(PointerKind::Drag, 510.0, 470.0)); + assert!(a.iter().any(|a| matches!(a, Action::Begin(n) if n == "Move")), "group {group}: {a:?}"); + assert!(a.iter().any(|a| matches!(a, Action::Preview(..))), "group {group}: {a:?}"); let a = DirectSelectionTool::new(group).pointer(&cx(&d, &s, &p), &PointerEvent::new(PointerKind::Down, 530.0, 450.0)); assert!(!names(&a), "group {group}, not isolated: {a:?}"); }