M2.1: avoid discarded path copies during deletion (#3380)

This commit is contained in:
ruchirguitar
2026-11-08 19:35:48 +05:30
parent 2dc6991cf3
commit 3984874728
6 changed files with 64 additions and 38 deletions
+13 -7
View File
@@ -764,6 +764,10 @@ fn cut(s: &mut Session, p: &Value) -> Result<Value> {
/// Remove the geometry copied by a direct-selection Cut, including selected guides in one edit.
/// Select every surviving piece while retaining intentional compound-path structure.
/// `roots` contains the copied targets: whole-object roots are removed in full, while partial paths
/// lose only the copied geometry. An exhausted partial path is removed, along with its compound
/// parent if now empty; layers remain. Returns JSON `null` on success, replacing direct-selection
/// references with surviving object IDs.
fn cut_direct_selection(s: &mut Session, roots: &[NodeId]) -> Result<Value> {
let st = s.doc()?;
let sel = st.selection.clone();
@@ -780,20 +784,22 @@ fn cut_direct_selection(s: &mut Session, roots: &[NodeId]) -> Result<Value> {
d.remove(*id)?;
continue;
}
let Some(path) = node.path_data() else {
if node.path_data().is_none() {
d.remove(*id)?;
continue;
};
}
let original = super::path::deletion_source(d, *id)?;
let seg_refs = sel.segments.get(id);
let anchor_refs = sel.anchors.get(id);
let mut subpaths = Vec::new();
for (si, sp) in path.subpaths.iter().enumerate() {
let cuts = super::clipboard::subpath_selected_segments(sp, si, seg_refs, anchor_refs);
let owned = std::mem::take(&mut super::path::path_mut(d, *id)?.subpaths);
for (si, sp) in owned.into_iter().enumerate() {
let cuts = super::clipboard::subpath_selected_segments(&sp, si, seg_refs, anchor_refs);
if cuts.is_empty() {
subpaths.push(sp.clone());
subpaths.push(sp);
} else {
subpaths.extend(super::path::without_segments(sp.clone(), &cuts));
subpaths.extend(super::path::without_segments(sp, &cuts));
}
}
subpaths.retain(|sp| sp.anchors.len() >= 2);
@@ -806,7 +812,7 @@ fn cut_direct_selection(s: &mut Session, roots: &[NodeId]) -> Result<Value> {
d.remove(p)?;
}
} else {
survivors.extend(super::path::replace_deleted_pieces(d, &mut pieces, *id, subpaths)?);
survivors.extend(super::path::replace_deleted_pieces(d, &mut pieces, original, subpaths)?);
}
}
pieces.finish(d)?;
+43 -15
View File
@@ -1,6 +1,7 @@
//! Path editing commands (used by the Pen and Direct Selection tools and Object → Path).
use std::collections::BTreeSet;
use std::sync::Arc;
use serde_json::{Value, json};
use vectorcraft_doc::{Document, Node, NodeId, NodeKind, Scaling};
@@ -1191,6 +1192,9 @@ fn convert_anchors(s: &mut Session, p: &Value) -> Result<Value> {
/// Delete direct-selected anchors or segments and whole selected objects in one edit.
/// Leave surviving pieces selected, splitting ordinary contours into independent sibling objects.
/// `p.guides: true` also removes the selected ruler guides in that edit; otherwise guides remain.
/// Layers are retained, and an object removed with a selected ancestor is not removed twice.
/// Returns JSON `null` on success; selection is replaced by the surviving object IDs.
pub(crate) fn delete_anchors(s: &mut Session, p: &Value) -> Result<Value> {
// `guides: true` (Cut, #3226): the selected ruler guides go in the same undo step.
let with_guides = switch_or(p, "guides", false, "path.deleteAnchors")?;
@@ -1222,13 +1226,14 @@ pub(crate) fn delete_anchors(s: &mut Session, p: &Value) -> Result<Value> {
if d.node(*id).is_none() {
continue;
}
let original = deletion_source(d, *id)?;
let subpaths: Vec<SubPath> = {
let path = d.node(*id).and_then(|n| n.path_data()).ok_or(EngineError::NoNode(*id))?;
let path = path_mut(d, *id)?;
let subpaths = std::mem::take(&mut path.subpaths);
if let Some(refs) = segments.get(id) {
// A segment pick cuts its connection, keeping every neighboring cubic.
path.subpaths
.iter()
.cloned()
subpaths
.into_iter()
.enumerate()
.flat_map(|(si, sp)| {
let cuts = refs.iter().filter_map(|&(s, seg)| (s == si).then_some(seg)).collect();
@@ -1239,9 +1244,8 @@ pub(crate) fn delete_anchors(s: &mut Session, p: &Value) -> Result<Value> {
} else {
// Remove every selected anchor against the original indexes, keeping surviving
// stretches separate rather than joining across deleted anchors.
path.subpaths
.iter()
.cloned()
subpaths
.into_iter()
.enumerate()
.flat_map(|(si, sp)| {
let gone = set.iter().filter_map(|&(s, ai)| (s == si).then_some(ai)).collect();
@@ -1255,7 +1259,7 @@ pub(crate) fn delete_anchors(s: &mut Session, p: &Value) -> Result<Value> {
emptied.put(d, *id, vec![])?;
} else {
// Every remaining piece is selected whole, so the next Delete removes them.
survivors.extend(replace_deleted_pieces(d, &mut emptied, *id, subpaths)?);
survivors.extend(replace_deleted_pieces(d, &mut emptied, original, subpaths)?);
}
}
emptied.finish(d)?;
@@ -1265,15 +1269,39 @@ pub(crate) fn delete_anchors(s: &mut Session, p: &Value) -> Result<Value> {
ok()
}
/// Deletion splits an ordinary contour into sibling objects. Existing multi-subpath paths and
/// explicit compound members keep their compound semantics, even when a member has one contour.
/// Separate objects retain the gradient placement of the original path.
pub(super) fn replace_deleted_pieces(d: &mut Document, beside: &mut Beside, id: NodeId, subpaths: Vec<SubPath>) -> Result<Vec<NodeId>> {
let single = d.node(id).and_then(|n| n.path_data()).is_some_and(|p| p.subpaths.len() == 1);
/// Retain path `id` from `d` without copying its geometry before deletion takes its subpaths.
/// The shared snapshot supplies the original path classification and, only if needed, gradient
/// bounds after the working node's geometry has moved. Returns `NoNode` for a missing/non-path ID.
pub(super) fn deletion_source(d: &Document, id: NodeId) -> Result<Arc<Node>> {
let (parent, index, _) = d.position(id).ok_or(EngineError::NoNode(id))?;
// Verify the cached position still names this path before retaining the shared node.
d.children(parent)
.and_then(|children| children.get(index))
.filter(|n| n.id == id && n.path_data().is_some())
.cloned()
.ok_or(EngineError::NoNode(id))
}
/// Replace the deleted path in `d` with the nonempty surviving runs in `subpaths`.
/// `original` must be retained by [`deletion_source`] before taking the working node's geometry.
/// An ordinary single-contour source becomes siblings; multi-subpath paths and compound members
/// retain their node. Only actual splits pin unplaced gradients to `original`'s bounds; already
/// placed gradients and connected remainders retain their placement behavior.
///
/// Returns the surviving IDs in `subpaths` order (the first keeps the source ID). The original
/// node's geometry is updated immediately. Replacements are handed to `beside`, which may defer
/// insertion; the caller must finish that [`Beside`] before using new IDs in `d`.
/// Missing/invalid paths return an error.
pub(super) fn replace_deleted_pieces(d: &mut Document, beside: &mut Beside, original: Arc<Node>, subpaths: Vec<SubPath>) -> Result<Vec<NodeId>> {
let id = original.id;
let single = original.path_data().is_some_and(|p| p.subpaths.len() == 1);
if single && compound_or_self(d, id) == id {
// Pin only actual splits: each new object's smaller bounds would otherwise refit its gradients.
if subpaths.len() > 1 {
d.node_mut(id).ok_or(EngineError::NoNode(id))?.pin_gradients();
if subpaths.len() > 1
&& original.appearance.has_unplaced_gradient()
&& let Some(bounds) = original.geometric_bounds()
{
d.node_mut(id).ok_or(EngineError::NoNode(id))?.appearance.pin_gradients(bounds);
}
super::draw2::replace_with_pieces(d, beside, id, subpaths.into_iter().map(PathData::single).collect())
} else {
+2 -4
View File
@@ -1,7 +1,6 @@
# Gaps
> **Last updated:** 2026-11-08 · **Change:** #3380 deletion review follow-up: split survivors keep their original bounds-fitted gradient placement; documentation evidence, geometry assertions and function docs clarified. Existing multi-subpath paths and compound members stay intact. Segment endpoint highlighting remains open. Scores and estimates unchanged.
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (Indian-language hyphenation review and license allowlist, #4947; opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (#3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Indian-language hyphenation review and license allowlist, #4947; opening a slow file shows a progress window, part of #5930; Hindi catalog and contributor loanword preferences; agent wording pass complete; wider native proofreading welcome; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); scores and estimates unchanged; a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window; scores and estimates unchanged; large images in CMYK documents are shrunk before they are separated into ink planes (#4155); scores and estimates unchanged; `.ai` paths with a Calligraphic brush open brushed, with the 6D Art Pen's channels, and Windows reads the pen's tilt and barrel rotation, #372; scores and estimates unchanged; Compound Shapes add-on, beyond Illustrator; scores and estimates unchanged; Contour add-on, beyond Illustrator; scores and estimates unchanged; a slow first frame is waited for instead of restarting without the graphics adapter, and the menu bar no longer waits for the installed fonts' scan; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
## Feature submission evidence
@@ -38,8 +37,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
| Date | Change | Summary |
|---|---|---|
| 2026-11-08 | minor | #3380, deletion only: pin bounds-fitted gradients before creating separate survivors; placed gradients and connected remainders retain their behavior. Regression coverage checks linear/radial fill and stroke vectors, rendered survivors and Undo/Redo. Scores and estimates unchanged. |
| 2026-11-08 | minor | #3380 review follow-up: clarify deletion evidence and survivor selection, consolidate milestone notes, and retain the open endpoint-highlighting work. Behavior, scores and estimates unchanged. |
| 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. |
| 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. |
| 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. |
| 2026-11-05 | minor | G15: long open strokes with an undashed width profile are outlined near the view while keeping profile positions on the full path; a 200,000-segment outline took 59.95 ms before and 8.44 ms after in local median runs. Closed and dashed profile strokes remain whole; estimate unchanged. |
+2 -4
View File
@@ -1,7 +1,6 @@
# Roadmap: milestones and what's next
> **Last updated:** 2026-11-08 · **Change:** #3380 deletion review follow-up: split survivors keep their original bounds-fitted gradient placement; documentation evidence, geometry assertions and function docs clarified. Existing multi-subpath paths and compound members stay intact. Segment endpoint highlighting remains open. Scores and estimates unchanged.
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (Indian-language hyphenation review and license allowlist, #4947; Hindi catalog and contributor loanword preferences; full wording review in progress; Effect dialogs: a colour swatch beside a hex colour opens the Color Picker, #5365; shape tools move the selection from its centre point and turn it from the zone outside a box corner, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (#3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Indian-language hyphenation review and license allowlist, #4947; Hindi catalog and contributor loanword preferences; full wording review in progress; Effect dialogs: a colour swatch beside a hex colour opens the Color Picker, #5365; shape tools move the selection from its centre point and turn it from the zone outside a box corner, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x)
## Feature submission evidence
@@ -37,8 +36,7 @@ M7 type: Touch Type moves a right-to-left character along its line and up from i
| Date | Change | Summary |
|---|---|---|
| 2026-11-08 | minor | #3380, deletion only: pin bounds-fitted gradients before creating separate survivors; placed gradients and connected remainders retain their behavior. Regression coverage checks linear/radial fill and stroke vectors, rendered survivors and Undo/Redo. Scores and estimates unchanged. |
| 2026-11-08 | minor | #3380 review follow-up: clarify deletion evidence and survivor selection, consolidate milestone notes, and retain the open endpoint-highlighting work. Behavior, scores and estimates unchanged. |
| 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. |
| 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. |
| 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. |
| 2026-11-05 | minor | Effect dialogs: a colour swatch beside a hex colour field follows typed values, and a double-click opens the Color Picker over the dialog, whose OK fills the field (#5365); scores and estimates unchanged. |
+2 -4
View File
@@ -1,7 +1,6 @@
# Parity with Adobe Illustrator
> **Last updated:** 2026-11-08 · **Change:** #3380 deletion review follow-up: split survivors keep their original bounds-fitted gradient placement; documentation evidence, geometry assertions and function docs clarified. Existing multi-subpath paths and compound members stay intact. Segment endpoint highlighting remains open. Scores and estimates unchanged.
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-07 · **Change:** minor (the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (#3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; the canvas keeps selection outlines and anchors between frames; Indian-language hyphenation review and license allowlist, #4947; Stroke gradients: an arrowhead aligned to the tip is painted to the corners of its base, #5964; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Hindi catalog and contributor loanword preferences; full wording review in progress; Properties in Edit Artboards: a Transform section with the reference point above the Artboards section, #5219; the Control bar's Stroke: and Opacity: links scrub their fields, #4088; an image zoomed in on is separated, colorized or proofed only where it shows (#4155); a document's first frame renders on the render worker, so opening a heavy file no longer freezes the window, #4155; Control bar Paragraph section with type selected, #5372; translating unchanged type reuses its glyph and character-stroke outlines; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; public release notes through 30.7, August 2026)
## Feature submission evidence
@@ -32,8 +31,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
| Date | Change | Summary |
|---|---|---|
| 2026-11-08 | minor | #3380, deletion only: pin bounds-fitted gradients before creating separate survivors; placed gradients and connected remainders retain their behavior. Regression coverage checks linear/radial fill and stroke vectors, rendered survivors and Undo/Redo. Scores and estimates unchanged. |
| 2026-11-08 | minor | #3380 review follow-up: clarify deletion evidence and survivor selection, consolidate milestone notes, and retain the open endpoint-highlighting work. Behavior, scores and estimates unchanged. |
| 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. |
| 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. |
| 2026-11-07 | minor | The canvas keeps selection outlines, anchors and handles between frames, so an idle frame with a large selection redraws them as one mesh; scores and estimates unchanged |
| 2026-11-07 | minor | Indian-language hyphenation (#4947): eleven MIT-licensed pattern sets, with grapheme and shaping-cluster protection in both composers and existing paragraph limits. Assamese and Sanskrit are selectable; Sanskrit automatic patterns remain pending an allowlisted source. Scores and estimates unchanged. |
+2 -4
View File
@@ -1,7 +1,6 @@
# UI and interaction parity
> **Last updated:** 2026-11-08 · **Change:** #3380 deletion review follow-up: split survivors keep their original bounds-fitted gradient placement; documentation evidence, geometry assertions and function docs clarified. Existing multi-subpath paths and compound members stay intact. Segment endpoint highlighting remains open. Scores and estimates unchanged.
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-06 · **Change:** minor (Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
> **Last reviewed:** 2026-10-10 · **Last updated:** 2026-11-08 · **Change:** minor (#3380 deletion-only review follow-up: independent survivors keep gradient placement, owned subpaths avoid discarded copies, and contracts/status/evidence are clarified; endpoint highlighting remains open; scores and estimates unchanged; Drop Shadow and glow dialogs: spinner arrows, #5367, #5368; Stroke Weight Up and Down shortcuts, #5797; Layers: Rename (`ui.rename`) opens the name field of the selected layer, group or object, #5324; Properties in Edit Artboards: Transform section above the Artboards section, #5219; scrubby labels: the Control bar's Stroke: and Opacity: links and the Properties panel's Stroke link scrub their fields, #4088; Properties: Quick Actions has Repeat Options for one selected repeat, #5181; Direct Selection: Shift-click picks several blend spine points, which drag together, #5285; Direct Selection: Cmd/Ctrl held lends the Selection tool and shows its bounding box, #3901; Join tool trims two crossing open paths and joins them at the crossing, #5844; Control bar: Paragraph: link and Align left/center/right while type is selected, #5372; shape tools: a press on the selection's centre point moves it and one in the rotate zone outside a box corner turns it, #5477; scores and estimates unchanged) · **Target:** Adobe Illustrator 2026 (30.x; Units › East Asian Type acts, with Q and Ha units; scores unchanged)
## Feature submission evidence
@@ -40,8 +39,7 @@ Artboard Options forwards the active Artboard tool's Scale Artwork with Artboard
| Date | Change | Summary |
|---|---|---|
| 2026-11-08 | minor | #3380, deletion only: pin bounds-fitted gradients before creating separate survivors; placed gradients and connected remainders retain their behavior. Regression coverage checks linear/radial fill and stroke vectors, rendered survivors and Undo/Redo. Scores and estimates unchanged. |
| 2026-11-08 | minor | #3380 review follow-up: clarify deletion evidence and survivor selection, consolidate milestone notes, and retain the open endpoint-highlighting work. Behavior, scores and estimates unchanged. |
| 2026-11-08 | minor | #3380, deletion only: preserve gradient placement and exact surviving geometry; consume owned subpaths with a shared original-node snapshot; consolidate status/evidence and document command/helper contracts. Compound behavior and the existing M2 status entry are retained. Endpoint highlighting remains open; scores and estimates unchanged. |
| 2026-11-07 | minor | #3380 (deletion only): independent surviving path objects; transformed appearance, stacking, selection, cubic handles and Undo/Redo covered by `delete_path_pieces`; compound semantics retained. Endpoint highlighting remains open; scores and estimates unchanged. |
| 2026-10-30 | minor | Home screen: welcome, New / Open and Community are centred and the large Discord button uses the theme accent, part of #4684; scores and estimates unchanged (estimated) |
| 2026-11-06 | minor | Snap to Grid: a straight segment dragged with Direct Selection lands its anchors on the grid, not the pressed point (#3835); scores and estimates unchanged |