git.lucas.co / cce-ui
GPU-accelerated UI toolkit (Vulkan)
git clone https://git.lucas.co/cce-ui.git

src/layout/registry.rs (25.3K)

  1 //! The style registry: the config flattened into one process-wide map of style keys, the
  2 //! per-thread test overlay over it, and the font-string helpers its readers share. Every
  3 //! getter in `layout` and `color` reads through it.
  4 
  5 use super::*;
  6 use std::collections::HashMap;
  7 
  8 /// Per-thread overrides for runtime style writes, under `cfg(test)` only.
  9 ///
 10 /// Every `graph_*`, corner-radius and control-height slot in this file is
 11 /// backed by the one process-wide [`STYLE_REGISTRY`], so a test that pins any
 12 /// of them pins it for every test running beside it. That is the same defect
 13 /// as the font flake fixed in 1dc0ab1, and it was live:
 14 /// `test_graph_style_configuration` sets ~20 style values and restores none,
 15 /// while `dual_geometry_views_stay_consistent` bakes quads with
 16 /// `graph_node_corner_radius` and then re-reads that getter to compare — so a
 17 /// write landing between the two makes them disagree. Widening the write
 18 /// window to 300ms reproduced it on demand.
 19 ///
 20 /// Fixing it at the registry rather than per accessor covers every slot it
 21 /// holds in one place, including ones no test pins yet.
 22 /// The family fontconfig matches for `monospace` (`fc-match`), asked once.
 23 pub fn get_system_monospace_font() -> &'static str {
 24     static MONOSPACE_FONT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
 25     MONOSPACE_FONT.get_or_init(|| {
 26         if let Ok(output) = std::process::Command::new("fc-match")
 27             .args(["-f", "%{family}", "monospace"])
 28             .output()
 29         {
 30             let name = String::from_utf8_lossy(&output.stdout);
 31             let parsed = name.split(',').next().unwrap_or("monospace").trim();
 32             if !parsed.is_empty() {
 33                 return parsed.to_string();
 34             }
 35         }
 36         "monospace".to_string()
 37     })
 38 }
 39 
 40 /// The family fontconfig matches for `sans-serif`, asked once.
 41 pub fn get_system_sans_serif_font() -> &'static str {
 42     static SANS_SERIF_FONT: std::sync::OnceLock<String> = std::sync::OnceLock::new();
 43     SANS_SERIF_FONT.get_or_init(|| {
 44         if let Ok(output) = std::process::Command::new("fc-match")
 45             .args(["-f", "%{family}", "sans-serif"])
 46             .output()
 47         {
 48             let name = String::from_utf8_lossy(&output.stdout);
 49             let parsed = name.split(',').next().unwrap_or("sans-serif").trim();
 50             if !parsed.is_empty() {
 51                 return parsed.to_string();
 52             }
 53         }
 54         "sans-serif".to_string()
 55     })
 56 }
 57 
 58 #[cfg(test)]
 59 mod test_overlay {
 60     use std::cell::RefCell;
 61     use std::collections::HashMap;
 62     thread_local! {
 63         static FLOATS: RefCell<HashMap<String, f32>> = RefCell::new(HashMap::new());
 64         static LENS: RefCell<HashMap<String, crate::units::Len>> = RefCell::new(HashMap::new());
 65         static STRINGS: RefCell<HashMap<String, String>> = RefCell::new(HashMap::new());
 66     }
 67     pub fn set_float(k: &str, v: f32) {
 68         LENS.with(|m| m.borrow_mut().remove(k));
 69         FLOATS.with(|m| m.borrow_mut().insert(k.to_string(), v));
 70     }
 71     pub fn set_len(k: &str, v: crate::units::Len) {
 72         FLOATS.with(|m| m.borrow_mut().remove(k));
 73         LENS.with(|m| m.borrow_mut().insert(k.to_string(), v));
 74     }
 75     pub fn set_string(k: &str, v: String) {
 76         STRINGS.with(|m| m.borrow_mut().insert(k.to_string(), v));
 77     }
 78     pub fn get_float(k: &str) -> Option<f32> {
 79         if let Some(l) = LENS.with(|m| m.borrow().get(k).copied()) {
 80             return Some(l.to_px());
 81         }
 82         FLOATS.with(|m| m.borrow().get(k).copied())
 83     }
 84     pub fn get_len(k: &str) -> Option<crate::units::Len> {
 85         if let Some(l) = LENS.with(|m| m.borrow().get(k).copied()) {
 86             return Some(l);
 87         }
 88         FLOATS.with(|m| m.borrow().get(k).copied()).map(crate::units::Len::px)
 89     }
 90     pub fn get_string(k: &str) -> Option<String> {
 91         STRINGS.with(|m| m.borrow().get(k).cloned())
 92     }
 93 }
 94 
 95 #[derive(Debug, Clone, Default)]
 96 pub struct StyleRegistry {
 97     pub floats: HashMap<String, f32>,
 98     pub strings: HashMap<String, String>,
 99     /// Slots whose config value carried a unit (`width=(mm)2.0`). Read
100     /// through `get_float` like any other number, resolved against the
101     /// process metric (`crate::units::metric`) at EVERY read, so a metric
102     /// that arrives after config load — outputs come in after the first
103     /// style read — or changes with the display is honoured live.
104     pub lens: HashMap<String, crate::units::Len>,
105 }
106 
107 impl StyleRegistry {
108     pub fn new() -> Self {
109         Self {
110             floats: HashMap::new(),
111             strings: HashMap::new(),
112             lens: HashMap::new(),
113         }
114     }
115 
116     pub fn get_float(&self, key: &str) -> Option<f32> {
117         #[cfg(test)]
118         if let Some(v) = test_overlay::get_float(key) {
119             return Some(v);
120         }
121         if let Some(len) = self.lens.get(key) {
122             return Some(len.to_px());
123         }
124         self.floats.get(key).copied()
125     }
126 
127     /// The slot as a length with its unit: the configured `Len` when one was
128     /// given, else the plain number as logical px. For editors that show the
129     /// unit the user chose rather than the resolved pixel count.
130     pub fn get_len(&self, key: &str) -> Option<crate::units::Len> {
131         #[cfg(test)]
132         if let Some(v) = test_overlay::get_len(key) {
133             return Some(v);
134         }
135         if let Some(len) = self.lens.get(key) {
136             return Some(*len);
137         }
138         self.floats.get(key).map(|v| crate::units::Len::px(*v))
139     }
140 
141     pub fn get_string(&self, key: &str) -> Option<String> {
142         #[cfg(test)]
143         if let Some(v) = test_overlay::get_string(key) {
144             return Some(v);
145         }
146         self.strings.get(key).cloned()
147     }
148 
149     /// A plain number wins over any earlier unit value for the slot — a
150     /// runtime `set_float` is the newest opinion.
151     pub fn set_float(&mut self, key: &str, val: f32) {
152         #[cfg(test)]
153         {
154             test_overlay::set_float(key, val);
155         }
156         #[cfg(not(test))]
157         self.load_float(key, val);
158     }
159 
160     pub fn set_len(&mut self, key: &str, len: crate::units::Len) {
161         #[cfg(test)]
162         {
163             test_overlay::set_len(key, len);
164         }
165         #[cfg(not(test))]
166         self.load_len(key, len);
167     }
168 
169     pub fn set_string(&mut self, key: &str, val: String) {
170         #[cfg(test)]
171         {
172             test_overlay::set_string(key, val);
173         }
174         #[cfg(not(test))]
175         self.load_string(key, val);
176     }
177 
178     /// The CONFIG-LOAD writes, as opposed to the runtime `set_*` ones above.
179     /// Kept apart because under `cfg(test)` a runtime set goes to a per-thread
180     /// overlay while the loaded config must stay the shared base every test
181     /// reads — routing config through `set_*` would put one thread's config
182     /// in its overlay and leave every other thread with an empty registry.
183     pub fn load_float(&mut self, key: &str, val: f32) {
184         self.lens.remove(key);
185         self.floats.insert(key.to_string(), val);
186     }
187 
188     pub fn load_len(&mut self, key: &str, len: crate::units::Len) {
189         self.floats.remove(key);
190         self.lens.insert(key.to_string(), len);
191     }
192 
193     pub fn load_string(&mut self, key: &str, val: String) {
194         self.strings.insert(key.to_string(), val);
195     }
196 }
197 
198 /// The registry is a field of the one style snapshot (`crate::style`); this is its handle,
199 /// with the `RwLock` API it had.
200 pub static STYLE_REGISTRY: crate::style::StyleCell<StyleRegistry> =
201     crate::style::StyleCell::new(|s| &s.registry, |s| &mut s.registry);
202 
203 pub fn get_style_registry() -> &'static crate::style::StyleCell<StyleRegistry> {
204     &STYLE_REGISTRY
205 }
206 
207 pub fn lazy_init_style_registry() {
208     use std::sync::Once;
209     static INIT: Once = Once::new();
210     INIT.call_once(|| {
211         reload_config();
212     });
213 }
214 
215 pub(super) fn flatten_json_to_flat_props(val: &serde_json::Value, prefix: &str, flat_props: &mut String) {
216     match val {
217         serde_json::Value::Object(map) => {
218             for (k, v) in map {
219                 let next_prefix = if prefix.is_empty() {
220                     k.clone()
221                 } else {
222                     format!("{}.{}", prefix, k)
223                 };
224                 flatten_json_to_flat_props(v, &next_prefix, flat_props);
225             }
226         }
227         _ => {
228             // A retired `window_manager` spelling of a relief key emits NO
229             // line: the fall-through below strips a path's first segment, so
230             // `window_manager.bevel_depth` would otherwise land on the live
231             // `bevel_depth` registry key by accident, alias or not. The
232             // `style.surface.*` retirements need no guard — stripped, they
233             // become `surface.relief.depth` and the like, which nothing reads.
234             if matches!(prefix, "window_manager.bevel_depth" | "window_manager.bevel_width" | "window_manager.bevel_shader") {
235                 return;
236             }
237             let flat_key = match prefix {
238                 "style.list.font" | "style.data.list.font" => "list_font",
239                 "style.list.font_color" | "style.data.list.font_color" => "list_font_color",
240                 "style.control.breadcrumb.font" => "breadcrumb_font",
241 
242                 // The control rung's default radius: every control-scale
243                 // `corner_radius` below falls back to it when its own key is unset.
244                 "style.control.corner_radius" => "control_corner_radius",
245                 "style.control.button.padding" => "button_padding",
246                 "style.control.button.height" => "button_height",
247                 "style.control.button.corner_radius" => "button_corner_radius",
248                 "style.control.button.font" => "button_font",
249                 "style.list.corner_radius" | "style.data.list.corner_radius" => "list_corner_radius",
250                 "style.control.textbox.corner_radius" | "style.textbox.corner_radius" | "style.data.textbox.corner_radius" => "textbox_corner_radius",
251                 "style.control.dropdown.color" => "dropdown_color",
252                 "style.control.font_selector.font" => "font_selector_font",
253                 "style.container.section.font" | "style.section.font" => "section_label_font",
254                 "style.container.section.padding" | "style.section.padding" => "section_padding",
255                 "style.control.button_strip.font" => "button_strip_font",
256                 "style.control.button_strip.spacing" => "button_strip_spacing",
257                 "style.control.dropdown.height" => "dropdown_height",
258                 "style.control.dropdown.corner_radius" => "dropdown_corner_radius",
259                 "style.control.font_selector.height" => "font_selector_height",
260                 "style.control.font_selector.corner_radius" => "font_selector_corner_radius",
261                 "style.control.label.font" => "control_label_font",
262                 "style.control.label.font_detached" => "control_label_font_detached",
263                 "style.control.label.margin" => "control_label_margin",
264                 "style.control.slider.height" => "slider_height",
265                 "style.control.slider.corner_radius" => "slider_corner_radius",
266                 "style.control.slider.band_thickness" => "slider_band_thickness",
267                 "style.control.slider.bulge_width" => "slider_bulge_width",
268                 "style.control.slider.bulge_height" => "slider_bulge_height",
269                 "style.control.progressbar.height" => "progressbar_height",
270                 "style.control.rangeslider.height" => "rangeslider_height",
271                 "style.control.scrollbar.width" => "scrollbar_width",
272                 "style.control.scrollbar.inset" => "scrollbar_inset",
273                 "style.control.spinbox.height" => "spinbox_height",
274                 "style.control.spinbox.button_padding" => "spinbox_button_padding",
275                 "style.control.spinbox.corner_radius" => "spinbox_corner_radius",
276                 "style.control.textbox.height" | "style.textbox.height" | "style.data.textbox.height" => "textbox_height",
277                 "style.control.textbox.placeholder_text_color" | "style.textbox.placeholder_text_color" | "style.data.textbox.placeholder_text_color" => "textbox_placeholder_text_color",
278                 "style.control.textbox.background_color" | "style.textbox.background_color" | "style.data.textbox.background_color" => "textbox_background_color",
279                 "style.control.textbox.background_edit_color" | "style.textbox.background_edit_color" | "style.data.textbox.background_edit_color" => "textbox_background_edit_color",
280                 "style.control.textbox.multiline.line_wrap" | "style.textbox.multiline.line_wrap" | "style.data.textbox.multiline.line_wrap" => "textbox_line_wrap",
281                 "style.control.textbox.multiline.border_width" | "style.textbox.multiline.border_width" | "style.data.textbox.multiline.border_width" => "textbox_multiline_border_width",
282                 "style.control.toggle.height" => "toggle_height",
283                 "style.control.toggle.border_width" => "toggle_border_width",
284                 "style.control.toggle.disabled_color" => "toggle_disabled_color",
285                 "style.control.toggle.border_color" => "toggle_border_color",
286                 "style.control.toggle.corner_radius" => "toggle_corner_radius",
287                 "window_manager.light_source_position" => "light_source_position",
288                 // The DE's relief material, home style.surface.relief: these
289                 // shade every bevel/boss/recess in the toolkit. `light` is
290                 // the spelling (since 2026-09-28): it is the light strength,
291                 // not a length. Two older spellings are RETIRED — not read,
292                 // reported by path (`color::retired_surface_keys`), removed
293                 // by cce-relief's Save: `relief.depth`, what every config
294                 // said until that day, and `window_manager.bevel_depth` /
295                 // `bevel_width`, the block these keys were born in before
296                 // they had a home of their own (the compositor never read
297                 // them; the spelling survived as a compat alias until the
298                 // evening of 2026-09-28).
299                 "style.surface.relief.light" => "bevel_depth",
300                 "style.surface.relief.width" => "bevel_width",
301                 // The two SHAPES of the relief, each a node under it
302                 // (2026-09-28): `wall` is a carve's wall — a recess, boss,
303                 // ridge or trough cut into a surface — and `edge` is the
304                 // plate's perimeter roll. Each carries `height` (a length:
305                 // the carve's drop, the roll's rise; unset = follow the
306                 // width) and `profile` (the ramp spec of its curve, written
307                 // by cce-relief, installed by reload_config). The registry
308                 // keys keep their old names. The pre-rename flat spellings
309                 // (`height` / `profile` were the wall's, `edge_height` /
310                 // `edge_profile` the edge's) were aliases for the rest of
311                 // that day and are RETIRED: not read, reported by path
312                 // (`color::retired_surface_keys`), removed by cce-relief's
313                 // Save, which also reads them once as a seed. cce-relief's
314                 // slider knobs are NOT a style key any more
315                 // (`relief.wall.knobs` / `edge.knobs`, before that
316                 // `profile_knobs` / `edge_knobs`): editor state, kept in
317                 // that app's own state.kdl, and read off a config only by
318                 // the editor itself, as a one-time seed.
319                 "style.surface.relief.wall.height" => "bevel_height",
320                 "style.surface.relief.edge.height" => "roll_height",
321                 "style.surface.relief.wall.profile" => "bevel_profile_spec",
322                 "style.surface.relief.edge.profile" => "roll_profile_spec",
323                 "style.container.section.depth" => "section_depth",
324                 "style.surface.param.backdrop_compression" => "param_compression",
325                 "style.surface.param.label_layout" => "param_label_layout",
326                 // The relief's shader toggle lives with the relief
327                 // (`relief shader=(bool)false` is the legacy banded look);
328                 // `window_manager.bevel_shader`, its old home, is retired
329                 // and reported like the block's other bevel keys.
330                 "style.surface.relief.shader" => "bevel_shader",
331                 "window_manager.control_relief" => "control_relief",
332                 "window_manager.corner_shape" => "corner_shape",
333                 "style.control.ramp.height" => "ramp_height",
334                 "style.layout.column.gap" => "column_gap",
335                 "style.control.control_panel.padding" => "control_panel_padding",
336                 "style.control.control_panel.gap" => "control_panel_gap",
337                 "style.status.normal_color" => "status_normal_color",
338                 "style.status.background_color" => "status_background_color",
339                 "style.status.background_blur" => "status_background_blur",
340                 "style.highlight.primary" => "primary_highlight_color",
341                 "style.window.page_opacity" => "page_opacity",
342                 "style.window.page_margin" => "page_margin",
343                 "style.window.plate_padding" => "plate_padding",
344                 "style.window.transition_duration" => "transition_duration",
345                 "style.overlay.behavior" => "overlay_behavior",
346                 "style.overlay.width" => "overlay_width",
347                 "style.overlay.position" => "overlay_position",
348                 "style.overlay.border_gap" => "overlay_border_gap",
349                 "style.editor.last_page" | "style.data.editor.last_page" => "last_page",
350                 "style.data.tree.corner_radius" => "tree_corner_radius",
351                 "style.data.tree.opacity" => "tree_opacity",
352                 "style.data.tree.blur" => "tree_blur",
353                 "style.data.tree.font" => "tree_font",
354                 "style.surface.desktop.gap_color" => "desktop_gap_color",
355                 "style.surface.desktop.cell_color" => "desktop_cell_color",
356                 "style.surface.desktop.gap_width" => "desktop_gap_width",
357                 "style.surface.desktop.cell_fade_inset" => "desktop_cell_fade_inset",
358                 "style.surface.desktop.mode" => "desktop_mode",
359                 "style.surface.desktop.solid_color" => "desktop_solid_color",
360                 "style.surface.desktop.grid_cell_size" => "desktop_grid_scale",
361                 "style.surface.desktop.grid_cell_width" => "grid_cell_width",
362                 "style.surface.desktop.grid_cell_height" => "grid_cell_height",
363                 "style.surface.plate.padding" => "plate_padding",
364                 "style.surface.plate.gap" => "plate_gap",
365                 "style.control.gap" => "control_gap",
366                 "style.control.list_gap" => "list_gap",
367                 // The context menu's own radius (`menu_corner_radius`): a
368                 // popover's corner is control-scale, not pane-scale.
369                 "style.surface.menu.corner_radius" => "menu_corner_radius",
370                 // `style.surface.plate.root.*` is the one spelling of the
371                 // root-plate style (RFC Phase 7a). The slot names keep the
372                 // historical `root_plate_` prefix; the legacy `root plate.*`
373                 // config read-alias was removed 2026-09-06.
374                 "style.surface.plate.root.padding" => "root_plate_padding",
375                 "style.surface.plate.root.gap" => "root_plate_gap",
376                 "style.surface.plate.root.color" => "root_plate_color",
377                 "style.surface.plate.root.blur" => "root_plate_blur",
378                 "style.surface.plate.root.corner_radius" => "root_plate_corner_radius",
379                 "style.surface.plate.root.menubar.color" => "root_plate_menubar_color",
380                 "style.surface.plate.root.menubar.text_color" => "root_plate_menubar_text_color",
381                 "style.surface.plate.root.menubar.blur" => "root_plate_menubar_blur",
382                 "style.surface.plate.root.menubar.font" => "menubar_font",
383                 "style.surface.statusbar.color" => "root_plate_statusbar_color",
384                 "style.surface.statusbar.text_color" => "root_plate_statusbar_text_color",
385                 "style.surface.statusbar.blur" => "root_plate_statusbar_blur",
386                 "style.surface.statusbar.font" => "statusbar_font",
387                 "style.surface.page.opacity" => "page_opacity",
388                 "style.surface.page.margin" => "page_margin",
389                 "style.surface.graph.grid_color" => "graph_grid_color",
390                 "style.surface.graph.opacity" => "graph_opacity",
391                 "style.surface.graph.node.opacity" => "graph_node_opacity",
392                 "style.surface.graph.spacing_x" => "graph_spacing_x",
393                 "style.surface.graph.spacing_y" => "graph_spacing_y",
394                 "style.surface.graph.line_width" => "graph_line_width",
395                 "style.surface.graph.node.width" => "graph_node_width",
396                 "style.surface.graph.node.height" => "graph_node_height",
397                 "style.surface.graph.grid_snap" => "graph_grid_snap",
398                 "style.surface.graph.blur" => "graph_blur",
399                 "style.surface.graph.font" => "graph_font",
400                 "style.surface.graph.node.color" => "graph_node_color",
401                 "style.surface.graph.node.font" => "graph_node_font",
402                 "style.surface.graph.node.delete" => "graph_node_delete",
403                 "style.surface.graph.node.selected_color" => "graph_node_selected_color",
404                 "style.surface.graph.node.drag_color" => "graph_node_drag_color",
405                 "style.surface.graph.node.corner_radius" => "graph_node_corner_radius",
406                 "style.surface.graph.node.wire_color" => "graph_wire_color",
407                 "style.surface.graph.node.wire_highlight_color" => "graph_wire_highlight_color",
408                 "style.surface.graph.node.wire_size" => "graph_wire_size",
409                 "style.surface.graph.node.wire_style" => "graph_wire_style",
410                 "style.surface.graph.node.wire_activation_radius" => "graph_wire_activation_radius",
411                 "style.surface.graph.node.connector_color" => "graph_connector_color",
412                 "style.surface.graph.node.connector_highlight_color" => "graph_connector_highlight_color",
413                 "style.surface.graph.node.connector_size" => "graph_connector_size",
414                 "style.surface.graph.node.connector_activation_radius" => "graph_connector_activation_radius",
415                 "style.surface.plate.color" => "plate_color",
416                 "style.surface.plate.border_color" => "plate_border_color",
417                 "style.surface.plate.border_thickness" => "plate_border_thickness",
418                 "input.touchpad.natural_scroll" => "touchpad_natural_scroll",
419                 
420                 // Anything else keeps its whole path. A key the toolkit reads is mapped above
421                 // (or is a flat top-level key, which has no path to strip); the rest belong
422                 // to other programs. Until 2026-10-08 an unmapped path was cut down — the
423                 // `layout.` and `transparency.` blocks (the compositor's own) to their bare
424                 // keys, anything else past its first segment — so the compositor's
425                 // `layout { grid_gap 18 }`, its window-tiling gap, arrived as the toolkit's
426                 // `grid_gap`, and any block's key could stand in for a toolkit one.
427                 other => other,
428             };
429             
430             if let Some(s) = val.as_str() {
431                 flat_props.push_str(&format!("{} = \"{}\"\n", flat_key, s));
432             } else if let Some(b) = val.as_bool() {
433                 flat_props.push_str(&format!("{} = {}\n", flat_key, b));
434             } else if let Some(n) = val.as_f64() {
435                 flat_props.push_str(&format!("{} = {}\n", flat_key, n));
436             } else if let Some(n) = val.as_i64() {
437                 flat_props.push_str(&format!("{} = {}\n", flat_key, n));
438             }
439         }
440     }
441 }
442 
443 // There is no alias-precedence pass any more. For the rest of 2026-09-28
444 // `prefer_relief_spellings` ran here, dropping a relief key's legacy
445 // spelling whenever its current one was present (`height` under
446 // `wall.height`, `depth` under `light`), because two spellings of one
447 // registry key would otherwise be decided by the order the JSON handed
448 // the lines out. Every legacy spelling of the relief is retired now — not
449 // read at all, reported by `color::retired_surface_keys` — so there is
450 // nothing left to prefer, and a config is what it says.
451 
452 pub(super) fn read_config() -> Option<String> {
453     let path = crate::config::get_config_path();
454     if let Ok(content) = std::fs::read_to_string(&path) {
455         let val = crate::config::parse_kdl_to_json(&content);
456         let mut flat_props = String::new();
457         flatten_json_to_flat_props(&val, "", &mut flat_props);
458         return Some(flat_props);
459     }
460     None
461 }
462 
463 pub fn parse_font_string(s: &str) -> (String, Option<f32>) {
464     let (family, size) = split_font_string(s);
465     (family.to_string(), size)
466 }
467 
468 /// [`parse_font_string`] borrowing the family from `s` instead of copying it.
469 pub fn split_font_string(s: &str) -> (&str, Option<f32>) {
470     let s = s.trim();
471     if let Some(last_space_idx) = s.rfind(' ') {
472         let (family, size_str) = s.split_at(last_space_idx);
473         let size_str = size_str.trim();
474         if let Ok(size) = size_str.parse::<f32>() {
475             return (family.trim(), Some(size));
476         }
477     }
478     (s, None)
479 }