From 41d8617f1ac7224203d4b90e2c61e6660f8f29c3 Mon Sep 17 00:00:00 2001 From: Universe Date: Tue, 3 Mar 2026 19:28:03 +0900 Subject: [PATCH 01/15] feat: implement attributed text model for rich text editing - Introduced the `AttributedText` module to support a run-based rich text data model, allowing for mixed-style text editing. - Enhanced the `TextEditor` to manage rich text snapshots for undo/redo functionality, capturing both text and style states. - Updated the Skia layout engine to handle per-run styles and variable font features, improving rendering capabilities for rich text. - Added rich text shortcuts and documentation for the new features, enhancing user experience and editor functionality. This update aims to provide a robust foundation for rich text editing in the grida-text-edit project. --- .../examples/wd_text_editor.rs | 342 +++- crates/grida-text-edit/src/attributed_text.rs | 1724 +++++++++++++++++ crates/grida-text-edit/src/lib.rs | 1 + crates/grida-text-edit/src/skia_layout.rs | 207 +- docs/wg/feat-text-editing/attributed-text.md | 749 +++++++ 5 files changed, 2948 insertions(+), 75 deletions(-) create mode 100644 crates/grida-text-edit/src/attributed_text.rs create mode 100644 docs/wg/feat-text-editing/attributed-text.md diff --git a/crates/grida-text-edit/examples/wd_text_editor.rs b/crates/grida-text-edit/examples/wd_text_editor.rs index 027c134b2f..1d0dc22251 100644 --- a/crates/grida-text-edit/examples/wd_text_editor.rs +++ b/crates/grida-text-edit/examples/wd_text_editor.rs @@ -1,6 +1,14 @@ -//! Minimal plain-text editor built directly on winit + Skia. +//! Rich-text editor prototype built directly on winit + Skia. //! -//! Uses the grida-text-edit crate for editing logic and Skia paragraph layout. +//! Uses the grida-text-edit crate for editing logic, `AttributedText` for +//! per-run styling, and Skia paragraph layout for rendering. +//! +//! Rich text shortcuts: +//! Cmd/Ctrl+B toggle bold +//! Cmd/Ctrl+I toggle italic +//! Cmd/Ctrl+U toggle underline +//! Cmd/Ctrl+Shift+> increase font size (+1 pt) +//! Cmd/Ctrl+Shift+< decrease font size (-1 pt) #![allow(clippy::single_match)] @@ -59,6 +67,16 @@ // Snapshot-based history with merge: consecutive typing, backspace, or // delete are grouped; paste, newline, and IME commit are discrete steps. // +// Rich text (per-run styling via AttributedText) +// [x] Cmd/Ctrl+B toggle bold (variable font wght axis) +// [x] Cmd/Ctrl+I toggle italic (real italic typeface) +// [x] Cmd/Ctrl+U toggle underline +// [x] Cmd/Ctrl+Shift+> increase font size (+1 pt, min 1) +// [x] Cmd/Ctrl+Shift+< decrease font size (-1 pt, min 1) +// [x] Caret style override (toggle with no selection sets typing style) +// [x] Per-run layout via Skia ParagraphBuilder (pushStyle/addText per run) +// [x] Variable font axis interpolation (wght, opsz via FontArguments) +// // Not yet implemented // [ ] Scroll (vertical) // [ ] Visual-order bidi cursor movement @@ -100,6 +118,10 @@ use grida_text_edit::{ apply_command, utf8_to_utf16_offset, EditHistory, EditKind, EditingCommand, SkiaLayoutEngine, TextEditorState, TextLayoutEngine, + attributed_text::{ + AttributedText, TextStyle as AttrTextStyle, + TextDecorationLine, + }, }; // --------------------------------------------------------------------------- @@ -312,6 +334,13 @@ struct TextEditor { /// Skia-backed layout engine (shared with apply_command calls). layout: SkiaLayoutEngine, + /// Attributed text model (runs of styled text). + pub content: AttributedText, + + /// Explicit caret style override (set by Cmd+B/I/U with no selection). + /// Cleared on cursor movement. + caret_style_override: Option, + // UI-only state (not part of editing logic) cursor_visible: bool, last_blink: Instant, @@ -328,13 +357,15 @@ struct TextEditor { } impl TextEditor { - fn new(config: TextEditorConfig) -> Self { + fn new(config: TextEditorConfig, default_style: AttrTextStyle) -> Self { Self { state: TextEditorState::with_cursor(String::new(), 0), layout: SkiaLayoutEngine::new( (WINDOW_W as f32) - PADDING * 2.0, (WINDOW_H as f32) - PADDING * 2.0, ), + content: AttributedText::empty(default_style), + caret_style_override: None, cursor_visible: true, last_blink: Instant::now(), mouse_down: false, @@ -353,19 +384,103 @@ impl TextEditor { if let Some(kind) = cmd.edit_kind() { self.history.push(&self.state, kind); } + let old_cursor = self.state.cursor; + let old_text = self.state.text.clone(); self.state = apply_command(&self.state, cmd, &mut self.layout); + self.sync_content_after_edit(&old_text, old_cursor); self.reset_blink(); } fn apply_with_kind(&mut self, cmd: EditingCommand, kind: EditKind) { self.history.push(&self.state, kind); + let old_cursor = self.state.cursor; + let old_text = self.state.text.clone(); self.state = apply_command(&self.state, cmd, &mut self.layout); + self.sync_content_after_edit(&old_text, old_cursor); self.reset_blink(); } + /// After apply_command changes `self.state.text`, diff and update + /// `self.content` (the AttributedText) to keep runs in sync. + fn sync_content_after_edit(&mut self, old_text: &str, old_cursor: usize) { + let new_text = &self.state.text; + if old_text == new_text { + // Pure cursor movement — clear caret style override. + if self.state.cursor != old_cursor { + self.caret_style_override = None; + } + return; + } + + // Determine the effective style for inserted text. + let insert_style = self.caret_style_override.clone() + .unwrap_or_else(|| self.content.caret_style_at(old_cursor as u32).clone()); + + // Reconstruct content from the new text, preserving existing runs + // where possible. The simplest correct approach: rebuild from scratch + // with the new text and re-apply styles from the old content at + // corresponding positions. But for typical edits (insert/delete at + // cursor), we can do better with a diff. + // + // For now, use a practical approach: + // 1. The old content covers old_text. + // 2. Compute the common prefix and suffix to find the edit span. + let old_len = old_text.len(); + let new_len = new_text.len(); + + // Find common prefix length (byte level, but snap to char boundaries). + let mut prefix = 0; + for (a, b) in old_text.bytes().zip(new_text.bytes()) { + if a != b { break; } + prefix += 1; + } + // Snap to char boundary + while prefix > 0 && !old_text.is_char_boundary(prefix) { + prefix -= 1; + } + while prefix > 0 && prefix < new_len && !new_text.is_char_boundary(prefix) { + prefix -= 1; + } + + // Find common suffix length. + let mut suffix = 0; + let max_suffix = old_len.min(new_len) - prefix; + for i in 0..max_suffix { + let oi = old_len - 1 - i; + let ni = new_len - 1 - i; + if old_text.as_bytes()[oi] != new_text.as_bytes()[ni] { break; } + suffix += 1; + } + while suffix > 0 && !old_text.is_char_boundary(old_len - suffix) { + suffix -= 1; + } + while suffix > 0 && !new_text.is_char_boundary(new_len - suffix) { + suffix -= 1; + } + + let old_end = old_len - suffix; + let new_end = new_len - suffix; + + if old_end > prefix { + // Text was deleted in [prefix, old_end). + self.content.delete(prefix, old_end); + } + if new_end > prefix { + // Text was inserted at prefix. + let inserted = &new_text[prefix..new_end]; + self.content.insert_with_style(prefix, inserted, insert_style); + } + + // After any text edit, clear the override. + self.caret_style_override = None; + } + fn undo(&mut self) -> bool { if let Some(prev) = self.history.undo(&self.state) { + let old_text = self.state.text.clone(); + let old_cursor = self.state.cursor; self.state = prev; + self.sync_content_after_edit(&old_text, old_cursor); self.reset_blink(); true } else { @@ -375,7 +490,10 @@ impl TextEditor { fn redo(&mut self) -> bool { if let Some(next) = self.history.redo(&self.state) { + let old_text = self.state.text.clone(); + let old_cursor = self.state.cursor; self.state = next; + self.sync_content_after_edit(&old_text, old_cursor); self.reset_blink(); true } else { @@ -383,6 +501,87 @@ impl TextEditor { } } + // ----------------------------------------------------------------------- + // Rich text: toggle bold / italic / underline + // ----------------------------------------------------------------------- + + fn toggle_bold(&mut self) { + if let Some((lo, hi)) = self.selection_range() { + // Check if the first character in the selection is already bold. + let is_bold = self.content.style_at(lo as u32).font_weight >= 700; + let new_weight = if is_bold { 400 } else { 700 }; + self.content.apply_style(lo, hi, |s| { s.font_weight = new_weight; }); + self.layout.invalidate(); + } else { + // No selection — toggle the caret style override. + let current = self.caret_style_override.clone() + .unwrap_or_else(|| self.content.caret_style_at(self.state.cursor as u32).clone()); + let mut new_style = current; + new_style.font_weight = if new_style.font_weight >= 700 { 400 } else { 700 }; + self.caret_style_override = Some(new_style); + } + } + + fn toggle_italic(&mut self) { + if let Some((lo, hi)) = self.selection_range() { + let is_italic = self.content.style_at(lo as u32).font_style_italic; + self.content.apply_style(lo, hi, |s| { s.font_style_italic = !is_italic; }); + self.layout.invalidate(); + } else { + let current = self.caret_style_override.clone() + .unwrap_or_else(|| self.content.caret_style_at(self.state.cursor as u32).clone()); + let mut new_style = current; + new_style.font_style_italic = !new_style.font_style_italic; + self.caret_style_override = Some(new_style); + } + } + + fn toggle_underline(&mut self) { + if let Some((lo, hi)) = self.selection_range() { + let is_underline = self.content.style_at(lo as u32).text_decoration_line + == TextDecorationLine::Underline; + let new_deco = if is_underline { TextDecorationLine::None } else { TextDecorationLine::Underline }; + self.content.apply_style(lo, hi, |s| { s.text_decoration_line = new_deco; }); + self.layout.invalidate(); + } else { + let current = self.caret_style_override.clone() + .unwrap_or_else(|| self.content.caret_style_at(self.state.cursor as u32).clone()); + let mut new_style = current; + new_style.text_decoration_line = if new_style.text_decoration_line == TextDecorationLine::Underline { + TextDecorationLine::None + } else { + TextDecorationLine::Underline + }; + self.caret_style_override = Some(new_style); + } + } + + /// Increase font size by `delta` (clamped to >= 1.0). + fn increase_font_size(&mut self, delta: f32) { + self.adjust_font_size(delta); + } + + /// Decrease font size by `delta` (clamped to >= 1.0). + fn decrease_font_size(&mut self, delta: f32) { + self.adjust_font_size(-delta); + } + + fn adjust_font_size(&mut self, delta: f32) { + const MIN_FONT_SIZE: f32 = 1.0; + if let Some((lo, hi)) = self.selection_range() { + self.content.apply_style(lo, hi, |s| { + s.font_size = (s.font_size + delta).max(MIN_FONT_SIZE); + }); + self.layout.invalidate(); + } else { + let current = self.caret_style_override.clone() + .unwrap_or_else(|| self.content.caret_style_at(self.state.cursor as u32).clone()); + let mut new_style = current; + new_style.font_size = (new_style.font_size + delta).max(MIN_FONT_SIZE); + self.caret_style_override = Some(new_style); + } + } + // ----------------------------------------------------------------------- // Convenience wrappers (called from event handler) // ----------------------------------------------------------------------- @@ -602,18 +801,19 @@ impl TextEditor { let preedit_start_u16 = utf8_to_utf16_offset(&display_text, pre.len()); let preedit_end_u16 = utf8_to_utf16_offset(&display_text, preedit_end_utf8); + let font_families: Vec<&str> = self.layout.config.font_families.iter().map(|s| s.as_str()).collect(); let ts_normal = { let mut ts = TextStyle::new(); ts.set_font_size(FONT_SIZE); ts.set_color(Color::BLACK); - ts.set_font_families(&["Menlo", "Courier New", "monospace"]); + ts.set_font_families(&font_families); ts }; let ts_preedit = { let mut ts = TextStyle::new(); ts.set_font_size(FONT_SIZE); ts.set_color(Color::BLACK); - ts.set_font_families(&["Menlo", "Courier New", "monospace"]); + ts.set_font_families(&font_families); ts.set_decoration_type(TextDecoration::UNDERLINE); ts }; @@ -695,8 +895,8 @@ impl TextEditor { &cp, ); } else { - // ---- normal mode ---- - self.layout.ensure_layout(&self.state.text); + // ---- normal mode (rich text) ---- + self.layout.ensure_layout_attributed(&self.content); // Selection if let Some((lo, hi)) = self.selection_range() { @@ -944,78 +1144,50 @@ impl ApplicationHandler for TextEditorApp { stencil_bits, }; - let mut editor = TextEditor::new(TextEditorConfig { - empty_line_policy: self.config.empty_line_policy, - }); + let default_style = AttrTextStyle { + font_family: String::from("Inter"), + font_size: FONT_SIZE, + ..AttrTextStyle::default() + }; + + let mut editor = TextEditor::new( + TextEditorConfig { empty_line_policy: self.config.empty_line_policy }, + default_style.clone(), + ); + + // Load Inter variable fonts (upright + italic). + // Skia will match the correct weight/slant from the variable font axes. + let inter_upright = include_bytes!( + "../../../fixtures/fonts/Inter/Inter-VariableFont_opsz,wght.ttf" + ); + let inter_italic = include_bytes!( + "../../../fixtures/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf" + ); + editor.layout.add_font_family("Inter", &[inter_upright, inter_italic]); + editor.layout.config.font_families = vec!["Inter".into()]; + editor.set_layout_width(w as f32); editor.set_layout_height(h as f32); - editor.state.text = concat!( + + let demo_text = concat!( "Hello, World!\n", - "Type here to edit text.\n", - "\n", - "=== Controls ===\n", - "← → ↑ ↓ move cursor Shift+arrow extends selection\n", - "Cmd+← / → line start/end Cmd+↑ / ↓ document start/end\n", - "Option+← / → word jump Ctrl+← / → word jump (Win/Linux)\n", - "Home / End line start/end PageUp / PageDown move by ~visible lines\n", - "Double-click select word Mouse drag select range\n", - "Cmd+A select all Cmd+C / X / V clipboard\n", - "Cmd+Z undo Cmd+Shift+Z redo\n", - "Option+Backspace delete word backward Option+Delete delete word forward\n", - "Cmd+Backspace delete to line start Cmd+Delete delete to line end\n", - "\n", - "=== Writing Systems / Shaping / Selection Tests ===\n", - "\n", - "[Latin + punctuation]\n", - "The quick brown fox jumps over 13 lazy dogs. (quotes) \u{201C}like this\u{201D} and \u{2018}this\u{2019}.\n", - "Hyphens: state-of-the-art \u{2014} em dash \u{2014} en dash \u{2013} minus \u{2212} ellipsis \u{2026}\n", - "\n", - "[Accents / combining marks]\n", - "precomposed: caf\u{00E9}, na\u{00EF}ve, co\u{00F6}perate\n", - "combining: cafe\u{0301} (e + U+0301) a\u{0308} (a + U+0308)\n", - "edge: Z\u{0351}\u{0327}\u{0301} (stacked combining marks)\n", - "\n", - "[Hangul]\n", - "Korean: \u{C548}\u{B155}\u{D558}\u{C138}\u{C694} (precomposed syllables)\n", - "Jamo: \u{3147}\u{314F}\u{3134}\u{3134}\u{3155}\u{3147}\u{314E}\u{314F}\u{3145}\u{3154}\u{3147}\u{3155} (decomposed jamo sequence)\n", - "Mix: ABC\u{AC00}\u{B098}\u{B2E4}123 (Latin + Hangul + digits)\n", - "\n", - "[Japanese]\n", - "\u{65E5}\u{672C}\u{8A9E}: \u{3053}\u{3093}\u{306B}\u{3061}\u{306F}\u{4E16}\u{754C} / \u{30AB}\u{30BF}\u{30AB}\u{30CA}: \u{30C6}\u{30B9}\u{30C8} / \u{3072}\u{3089}\u{304C}\u{306A}: \u{3066}\u{3059}\u{3068}\n", + "Type here to edit text. Use Cmd+B for bold, Cmd+I for italic, Cmd+U for underline.\n", "\n", - "[Chinese]\n", - "\u{4E2D}\u{6587}: \u{4F60}\u{597D}\u{FF0C}\u{4E16}\u{754C}\u{3002}\u{7E41}\u{9AD4}\u{5B57}\u{FF1A}\u{7E41}\u{9AD4}\u{4E2D}\u{6587}\u{3002}\n", + "Select text and toggle styles, or toggle with no selection to set the typing style.\n", "\n", - "[Arabic (RTL) + mixing]\n", - "\u{0627}\u{0644}\u{0639}\u{0631}\u{0628}\u{064A}\u{0629}: \u{0645}\u{0631}\u{062D}\u{0628}\u{0627} \u{0628}\u{0627}\u{0644}\u{0639}\u{0627}\u{0644}\u{0645}\n", - "mix RTL/LTR: ABC \u{0627}\u{0644}\u{0639}\u{0631}\u{0628}\u{064A}\u{0629} 123 DEF\n", - "numbers: \u{0661}\u{0662}\u{0663}\u{0664}\u{0665} vs 12345\n", - "\n", - "[Hebrew (RTL) + mixing]\n", - "\u{05E2}\u{05D1}\u{05E8}\u{05D9}\u{05EA}: \u{05E9}\u{05DC}\u{05D5}\u{05DD} \u{05E2}\u{05D5}\u{05DC}\u{05DD}\n", - "mix RTL/LTR: ABC \u{05E9}\u{05DC}\u{05D5}\u{05DD} 123 DEF\n", - "\n", - "[Devanagari (conjuncts / reordering)]\n", - "\u{0939}\u{093F}\u{0928}\u{094D}\u{0926}\u{0940}: \u{0928}\u{092E}\u{0938}\u{094D}\u{0924}\u{0947} \u{0926}\u{0941}\u{0928}\u{093F}\u{092F}\u{093E} / conjunct-ish: \u{0915}\u{094D}\u{0937}, \u{0924}\u{094D}\u{0930}, \u{091C}\u{094D}\u{091E}\n", - "\n", - "[Thai (no spaces between words)]\n", - "\u{0E44}\u{0E17}\u{0E22}: \u{0E2A}\u{0E27}\u{0E31}\u{0E2A}\u{0E14}\u{0E35}\u{0E42}\u{0E25}\u{0E01} (word boundaries can be tricky)\n", - "\n", - "[Emoji / ZWJ sequences / skin tones]\n", - "emoji: \u{1F600} \u{1F601} \u{1F602} \u{1F605} \u{1F607}\n", - "ZWJ family: \u{1F468}\u{200D}\u{1F469}\u{200D}\u{1F467}\u{200D}\u{1F466} couple: \u{1F469}\u{200D}\u{2764}\u{FE0F}\u{200D}\u{1F469}\n", - "professions: \u{1F9D1}\u{200D}\u{1F4BB} \u{1F469}\u{200D}\u{1F52C} \u{1F468}\u{200D}\u{1F373}\n", - "skin tones: \u{1F44D} \u{1F44D}\u{1F3FB} \u{1F44D}\u{1F3FD} \u{1F44D}\u{1F3FF}\n", - "flags: \u{1F1F0}\u{1F1F7} \u{1F1FA}\u{1F1F8} \u{1F1EF}\u{1F1F5} \u{1F1EB}\u{1F1F7}\n", - "\n", - "[Ligature hint (font-dependent)]\n", - "fi fl ffi ffl (ligatures may appear depending on font)\n", - "\n", - "[Whitespace / tabs]\n", - "spaces: A B C D\n", - "tabs: A\tB\tC\tD\n", - ) - .to_string(); + "The quick brown fox jumps over 13 lazy dogs.\n", + "fi fl ffi ffl (ligatures with Inter)\n", + ); + + editor.content = AttributedText::new(demo_text, default_style.clone()); + editor.state.text = demo_text.to_string(); + + // Pre-style some demo text to show rich text capabilities. + // "Hello" bold + editor.content.apply_style(0, 5, |s| { s.font_weight = 700; }); + // "World" italic + editor.content.apply_style(7, 12, |s| { s.font_style_italic = true; }); + editor.state.cursor = editor.state.text.len(); window.set_ime_allowed(true); @@ -1176,6 +1348,28 @@ impl ApplicationHandler for TextEditorApp { _ if cmd => { match ke.physical_key { + PhysicalKey::Code(KeyCode::KeyB) => { + inner.editor.toggle_bold(); + inner.window.request_redraw(); + } + PhysicalKey::Code(KeyCode::KeyI) => { + inner.editor.toggle_italic(); + inner.window.request_redraw(); + } + PhysicalKey::Code(KeyCode::KeyU) => { + inner.editor.toggle_underline(); + inner.window.request_redraw(); + } + // Cmd+Shift+> (Period) — increase font size + PhysicalKey::Code(KeyCode::Period) if shift => { + inner.editor.increase_font_size(1.0); + inner.window.request_redraw(); + } + // Cmd+Shift+< (Comma) — decrease font size + PhysicalKey::Code(KeyCode::Comma) if shift => { + inner.editor.decrease_font_size(1.0); + inner.window.request_redraw(); + } PhysicalKey::Code(KeyCode::KeyA) => { inner.editor.select_all(); inner.window.request_redraw(); diff --git a/crates/grida-text-edit/src/attributed_text.rs b/crates/grida-text-edit/src/attributed_text.rs new file mode 100644 index 0000000000..377e813b36 --- /dev/null +++ b/crates/grida-text-edit/src/attributed_text.rs @@ -0,0 +1,1724 @@ +//! Attributed text: a run-based rich text data model. +//! +//! This module implements the data model specified in +//! `docs/wg/feat-text-editing/attributed-text.md`. +//! +//! # Overview +//! +//! [`AttributedText`] pairs a UTF-8 backing string with an ordered sequence of +//! [`StyledRun`]s that fully partition the string. Each run carries a +//! [`TextStyle`] — the complete set of per-character visual attributes. +//! +//! Paragraph-level attributes (alignment, direction, etc.) live in +//! [`ParagraphStyle`] and are uniform across the entire text block. +//! +//! # Invariants +//! +//! The run list satisfies seven invariants at all times (enforced by +//! `debug_assert!` after every mutation): +//! +//! 1. **Non-empty run list** — `runs.len() >= 1`. +//! 2. **Coverage** — first run starts at 0, last run ends at `text.len()`. +//! 3. **Contiguity** — `runs[i].end == runs[i+1].start`. +//! 4. **Non-degenerate** — `start < end` (except the single degenerate +//! empty-text run). +//! 5. **Maximality** — no two adjacent runs have equal styles. +//! 6. **Boundary alignment** — all offsets are valid UTF-8 char boundaries. +//! 7. **Monotonicity** — `runs[i].start < runs[i+1].start` (implied by 3+4). + +use serde::{Deserialize, Serialize}; + +// --------------------------------------------------------------------------- +// Supporting types (self-contained, aligned with cg::types vocabulary) +// --------------------------------------------------------------------------- + +/// Text transform (CSS `text-transform`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum TextTransform { + None, + Uppercase, + Lowercase, + Capitalize, +} + +impl Default for TextTransform { + fn default() -> Self { + Self::None + } +} + +/// Text decoration line (CSS `text-decoration-line`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum TextDecorationLine { + None, + Underline, + Overline, + LineThrough, +} + +impl Default for TextDecorationLine { + fn default() -> Self { + Self::None + } +} + +/// Text decoration style (CSS `text-decoration-style`). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum TextDecorationStyle { + Solid, + Double, + Dotted, + Dashed, + Wavy, +} + +impl Default for TextDecorationStyle { + fn default() -> Self { + Self::Solid + } +} + +/// Font optical sizing mode. +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub enum FontOpticalSizing { + /// Automatically set `opsz` to the font size. + Auto, + /// Disable optical sizing. + None, + /// Use a fixed optical size value. + Fixed(f32), +} + +impl Default for FontOpticalSizing { + fn default() -> Self { + Self::Auto + } +} + +/// A dimension that can be `Normal` (unset), a fixed px value, or a factor. +/// +/// Used for `line_height`, `letter_spacing`, `word_spacing`. +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub enum TextDimension { + /// Normal / auto (no override). + Normal, + /// Fixed value in layout-local points (px). + Fixed(f32), + /// Multiplier factor (1.0 = 100%). + Factor(f32), +} + +impl Default for TextDimension { + fn default() -> Self { + Self::Normal + } +} + +/// An OpenType font feature toggle. +/// +/// Tag is a 4-byte ASCII string (e.g. `"kern"`, `"liga"`, `"ss01"`). +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct FontFeature { + pub tag: String, + pub value: bool, +} + +/// A font variation axis value. +/// +/// Axis is a 4-byte ASCII tag (e.g. `"wght"`, `"wdth"`, `"slnt"`). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct FontVariation { + pub axis: String, + pub value: f32, +} + +/// RGBA color (f32 components, 0.0..1.0). +#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] +pub struct RGBA { + pub r: f32, + pub g: f32, + pub b: f32, + pub a: f32, +} + +impl RGBA { + pub const BLACK: Self = Self { r: 0.0, g: 0.0, b: 0.0, a: 1.0 }; + pub const WHITE: Self = Self { r: 1.0, g: 1.0, b: 1.0, a: 1.0 }; + pub const TRANSPARENT: Self = Self { r: 0.0, g: 0.0, b: 0.0, a: 0.0 }; +} + +/// Text fill (per-run color/paint). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub enum TextFill { + /// Solid color fill. + Solid(RGBA), +} + +impl Default for TextFill { + fn default() -> Self { + Self::Solid(RGBA::BLACK) + } +} + +/// Horizontal text alignment (paragraph-level). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum TextAlign { + Left, + Right, + Center, + Justify, +} + +impl Default for TextAlign { + fn default() -> Self { + Self::Left + } +} + +/// Vertical text alignment (paragraph-level). +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum TextAlignVertical { + Top, + Center, + Bottom, +} + +impl Default for TextAlignVertical { + fn default() -> Self { + Self::Top + } +} + +/// Paragraph direction. +#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub enum ParagraphDirection { + Ltr, + Rtl, + Auto, +} + +impl Default for ParagraphDirection { + fn default() -> Self { + Self::Ltr + } +} + +/// Hyperlink target on a text run. +#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)] +pub struct Hyperlink { + pub url: String, + pub open_in_new_tab: bool, +} + +// --------------------------------------------------------------------------- +// TextStyle — the per-run attribute set +// --------------------------------------------------------------------------- + +/// The complete set of per-run text attributes. +/// +/// Field layout is aligned with `TextStyleRec` in `crates/grida-canvas/src/cg/types.rs` +/// and `TextStyleRec` in `format/grida.fbs`, extended with a `fill` field. +/// +/// Two `TextStyle`s are equal iff all fields are structurally equal. This +/// determines whether adjacent runs can be merged (maximality invariant). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct TextStyle { + // --- Font identification --- + /// Primary font family name (e.g. `"Inter"`, `"Roboto"`). + pub font_family: String, + /// Font size in layout-local points. Default: 14.0. + pub font_size: f32, + /// Font weight, CSS-compatible 1..1000. Default: 400. + pub font_weight: u32, + /// Font width (CSS `font-stretch` percentage). Default: 100.0 (normal). + pub font_width: f32, + /// Italic flag. Default: false. + pub font_style_italic: bool, + /// OpenType `kern` feature. Default: true. + pub font_kerning: bool, + /// Optical sizing mode. Default: Auto. + pub font_optical_sizing: FontOpticalSizing, + + // --- OpenType extensions --- + /// Active OpenType feature toggles. + pub font_features: Vec, + /// Variable font axis values. + pub font_variations: Vec, + + // --- Spacing --- + /// Letter spacing. Default: Normal. + pub letter_spacing: TextDimension, + /// Word spacing. Default: Normal. + pub word_spacing: TextDimension, + /// Line height. Default: Normal. + pub line_height: TextDimension, + + // --- Decoration --- + /// Decoration line type. Default: None. + pub text_decoration_line: TextDecorationLine, + /// Decoration stroke style. Default: Solid. + pub text_decoration_style: TextDecorationStyle, + /// Decoration color override. `None` = inherit from fill. + pub text_decoration_color: Option, + /// Skip-ink for decorations. Default: true. + pub text_decoration_skip_ink: bool, + /// Decoration thickness (percentage). Default: 1.0. + pub text_decoration_thickness: f32, + + // --- Transform --- + /// Text transform. Default: None. + pub text_transform: TextTransform, + + // --- Fill --- + /// Text color/fill. Default: solid black. + pub fill: TextFill, + + // --- Link --- + /// Hyperlink target. Default: None (no link). + pub hyperlink: Option, +} + +impl Default for TextStyle { + fn default() -> Self { + Self { + font_family: String::from("sans-serif"), + font_size: 14.0, + font_weight: 400, + font_width: 100.0, + font_style_italic: false, + font_kerning: true, + font_optical_sizing: FontOpticalSizing::Auto, + font_features: Vec::new(), + font_variations: Vec::new(), + letter_spacing: TextDimension::Normal, + word_spacing: TextDimension::Normal, + line_height: TextDimension::Normal, + text_decoration_line: TextDecorationLine::None, + text_decoration_style: TextDecorationStyle::Solid, + text_decoration_color: None, + text_decoration_skip_ink: true, + text_decoration_thickness: 1.0, + text_transform: TextTransform::None, + fill: TextFill::default(), + hyperlink: None, + } + } +} + +// --------------------------------------------------------------------------- +// ParagraphStyle — uniform across the text block +// --------------------------------------------------------------------------- + +/// Paragraph-level attributes (not per-run). +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct ParagraphStyle { + pub text_align: TextAlign, + pub text_align_vertical: TextAlignVertical, + pub paragraph_direction: ParagraphDirection, + /// Maximum number of visible lines. `None` = unlimited. + pub max_lines: Option, + /// Ellipsis string (e.g. `"..."`). `None` = no truncation indicator. + pub ellipsis: Option, + /// First-line text indent in layout-local points. + pub text_indent: f32, + /// Extra spacing after each hard line break (`\n`), in layout-local points. + pub paragraph_spacing: f32, +} + +impl Default for ParagraphStyle { + fn default() -> Self { + Self { + text_align: TextAlign::Left, + text_align_vertical: TextAlignVertical::Top, + paragraph_direction: ParagraphDirection::Ltr, + max_lines: None, + ellipsis: None, + text_indent: 0.0, + paragraph_spacing: 0.0, + } + } +} + +// --------------------------------------------------------------------------- +// StyledRun +// --------------------------------------------------------------------------- + +/// A contiguous range of text sharing a single style. +/// +/// Offsets are UTF-8 byte offsets into the parent [`AttributedText::text`]. +/// `start` is inclusive, `end` is exclusive. +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct StyledRun { + /// Start byte offset (UTF-8, inclusive). + pub start: u32, + /// End byte offset (UTF-8, exclusive). + pub end: u32, + /// The resolved style for this run. + pub style: TextStyle, +} + +impl StyledRun { + /// Byte length of this run. + #[inline] + pub fn len(&self) -> u32 { + self.end - self.start + } + + /// Whether this is a zero-length (degenerate) run. Only valid when the + /// backing text is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.start == self.end + } +} + +// --------------------------------------------------------------------------- +// Invariant error +// --------------------------------------------------------------------------- + +/// Describes a violated invariant (used by [`AttributedText::check_invariants`]). +#[derive(Debug, Clone)] +pub enum InvariantError { + EmptyRuns, + CoverageStart { expected: u32, actual: u32 }, + CoverageEnd { expected: u32, actual: u32 }, + Contiguity { index: usize, prev_end: u32, next_start: u32 }, + EmptyRun { index: usize, start: u32, end: u32 }, + NotMaximal { index: usize }, + BadBoundary { index: usize, field: &'static str, offset: u32 }, + Monotonicity { index: usize }, +} + +impl std::fmt::Display for InvariantError { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::EmptyRuns => write!(f, "runs vec is empty"), + Self::CoverageStart { expected, actual } => { + write!(f, "first run starts at {actual}, expected {expected}") + } + Self::CoverageEnd { expected, actual } => { + write!(f, "last run ends at {actual}, expected {expected}") + } + Self::Contiguity { index, prev_end, next_start } => { + write!(f, "gap/overlap at index {index}: prev.end={prev_end}, next.start={next_start}") + } + Self::EmptyRun { index, start, end } => { + write!(f, "empty run at index {index}: start={start}, end={end}") + } + Self::NotMaximal { index } => { + write!(f, "adjacent runs {index} and {} have equal styles", index + 1) + } + Self::BadBoundary { index, field, offset } => { + write!(f, "run {index} {field}={offset} is not a valid char boundary") + } + Self::Monotonicity { index } => { + write!(f, "run {index} start >= run {} start", index + 1) + } + } + } +} + +impl std::error::Error for InvariantError {} + +// --------------------------------------------------------------------------- +// AttributedText +// --------------------------------------------------------------------------- + +/// A string with per-run styling and paragraph-level attributes. +/// +/// This is the core data structure for semi-rich text editing. It replaces +/// the plain `String` + uniform `TextStyleRec` currently used by +/// `TextSpanNodeRec`. +/// +/// # Examples +/// +/// ``` +/// use grida_text_edit::attributed_text::{AttributedText, TextStyle}; +/// +/// // Create with default style +/// let mut at = AttributedText::new("Hello, world!", TextStyle::default()); +/// assert_eq!(at.runs().len(), 1); +/// +/// // Make "world" bold (byte range 7..12) +/// at.apply_style(7, 12, |s| { s.font_weight = 700; }); +/// assert_eq!(at.runs().len(), 3); // "Hello, " | "world" | "!" +/// ``` +#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)] +pub struct AttributedText { + /// The backing UTF-8 string. Newlines are normalized to `\n`. + text: String, + /// The base style. Runs that match this can use delta encoding in serialization. + default_style: TextStyle, + /// Paragraph-level attributes. + paragraph_style: ParagraphStyle, + /// Ordered, non-overlapping, gap-free, maximal runs. + runs: Vec, +} + +impl AttributedText { + // ----------------------------------------------------------------------- + // Construction + // ----------------------------------------------------------------------- + + /// Create an attributed text with a single run covering the entire string. + pub fn new(text: impl Into, style: TextStyle) -> Self { + let text = crate::normalize_newlines(&text.into()); + let len = text.len() as u32; + let runs = vec![StyledRun { start: 0, end: len, style: style.clone() }]; + let this = Self { + text, + default_style: style, + paragraph_style: ParagraphStyle::default(), + runs, + }; + debug_assert!(this.check_invariants().is_ok(), "{:?}", this.check_invariants()); + this + } + + /// Create an empty attributed text preserving the given caret style. + pub fn empty(style: TextStyle) -> Self { + let runs = vec![StyledRun { start: 0, end: 0, style: style.clone() }]; + Self { + text: String::new(), + default_style: style, + paragraph_style: ParagraphStyle::default(), + runs, + } + } + + /// Create from explicit components. Panics if invariants are violated. + /// + /// This is intended for deserialization / tests. Prefer [`new`](Self::new) + /// for normal construction. + pub fn from_parts( + text: String, + default_style: TextStyle, + paragraph_style: ParagraphStyle, + runs: Vec, + ) -> Self { + let this = Self { text, default_style, paragraph_style, runs }; + if let Err(e) = this.check_invariants() { + panic!("AttributedText::from_parts: invariant violated: {e}"); + } + this + } + + // ----------------------------------------------------------------------- + // Accessors + // ----------------------------------------------------------------------- + + /// The backing text string. + #[inline] + pub fn text(&self) -> &str { + &self.text + } + + /// Byte length of the text. + #[inline] + pub fn len(&self) -> usize { + self.text.len() + } + + /// Whether the text is empty. + #[inline] + pub fn is_empty(&self) -> bool { + self.text.is_empty() + } + + /// The default (base) style. + #[inline] + pub fn default_style(&self) -> &TextStyle { + &self.default_style + } + + /// Mutable access to the default style. After modification, call + /// [`coalesce`](Self::coalesce) if needed. + #[inline] + pub fn default_style_mut(&mut self) -> &mut TextStyle { + &mut self.default_style + } + + /// The paragraph-level style. + #[inline] + pub fn paragraph_style(&self) -> &ParagraphStyle { + &self.paragraph_style + } + + /// Mutable access to paragraph style. + #[inline] + pub fn paragraph_style_mut(&mut self) -> &mut ParagraphStyle { + &mut self.paragraph_style + } + + /// The run list (read-only). + #[inline] + pub fn runs(&self) -> &[StyledRun] { + &self.runs + } + + /// Number of style runs. + #[inline] + pub fn run_count(&self) -> usize { + self.runs.len() + } + + // ----------------------------------------------------------------------- + // Querying + // ----------------------------------------------------------------------- + + /// Find the run index containing `offset` (binary search, O(log k)). + /// + /// For an offset at a run boundary, returns the run that **starts** at + /// that offset (the "downstream" run). At the end of text, returns the + /// last run. + pub fn run_index_at(&self, offset: u32) -> usize { + if self.runs.is_empty() { + return 0; + } + // Special case: past the end -> last run + if offset >= self.text.len() as u32 { + return self.runs.len() - 1; + } + // Binary search: find the first run whose end > offset + match self.runs.binary_search_by(|r| { + if r.end <= offset { + std::cmp::Ordering::Less + } else if r.start > offset { + std::cmp::Ordering::Greater + } else { + std::cmp::Ordering::Equal + } + }) { + Ok(i) => i, + Err(i) => i.min(self.runs.len() - 1), + } + } + + /// Style at a given byte offset. O(log k). + pub fn style_at(&self, offset: u32) -> &TextStyle { + &self.runs[self.run_index_at(offset)].style + } + + /// Returns the **caret style** for a cursor at `offset`. + /// + /// - At position 0: style of the first run. + /// - At end of text: style of the last run. + /// - At a run boundary: style of the run that **ends** at `offset` + /// (the "upstream" run), matching design-tool convention. + /// - Inside a run: style of that run. + pub fn caret_style_at(&self, offset: u32) -> &TextStyle { + if offset == 0 || self.runs.is_empty() { + return &self.runs[0].style; + } + if offset >= self.text.len() as u32 { + return &self.runs[self.runs.len() - 1].style; + } + // Check if offset is exactly at a run boundary (some run's start). + // If so, return the previous run's style (upstream). + let idx = self.run_index_at(offset); + if self.runs[idx].start == offset && idx > 0 { + &self.runs[idx - 1].style + } else { + &self.runs[idx].style + } + } + + /// Returns a slice of runs overlapping the byte range `[lo, hi)`. + pub fn runs_in_range(&self, lo: u32, hi: u32) -> &[StyledRun] { + if lo >= hi || self.runs.is_empty() { + return &[]; + } + let first = self.run_index_at(lo); + let last = self.run_index_at(hi.saturating_sub(1).max(lo)); + &self.runs[first..=last] + } + + // ----------------------------------------------------------------------- + // Mutations + // ----------------------------------------------------------------------- + + /// Insert `s` at byte offset `pos`. The inserted text inherits the style + /// of the run containing `pos` (or the caret style for empty text). + /// + /// # Panics + /// + /// Panics in debug mode if `pos` is not a valid char boundary. + pub fn insert(&mut self, pos: usize, s: &str) { + if s.is_empty() { + return; + } + debug_assert!( + pos <= self.text.len() && (pos == self.text.len() || self.text.is_char_boundary(pos)), + "insert pos {pos} is not a valid boundary in text of len {}", + self.text.len() + ); + + let normalized = crate::normalize_newlines(s); + let n = normalized.len() as u32; + let pos32 = pos as u32; + + // Find the run containing `pos`. + let idx = self.run_index_at(pos32); + + // Insert into the text buffer. + self.text.insert_str(pos, &normalized); + + // Adjust run offsets. + // The run containing `pos` gets its `end` extended by `n`. + // All subsequent runs shift by `+n`. + for (i, run) in self.runs.iter_mut().enumerate() { + if i == idx { + // This run contains the insertion point. + // If pos is at the very start of this run AND it's not the first + // run, the previous run's end also equals pos, so we extend + // this run (not the previous one). + run.end += n; + } else if i > idx { + run.start += n; + run.end += n; + } + } + + debug_assert!(self.check_invariants().is_ok(), "{:?}", self.check_invariants()); + } + + /// Insert `s` at byte offset `pos` with a specific style. + /// + /// If the given style differs from the surrounding run, new runs are + /// created. Adjacent equal-style runs are merged. + pub fn insert_with_style(&mut self, pos: usize, s: &str, style: TextStyle) { + if s.is_empty() { + return; + } + debug_assert!( + pos <= self.text.len() && (pos == self.text.len() || self.text.is_char_boundary(pos)), + "insert_with_style pos {pos} is not a valid boundary in text of len {}", + self.text.len() + ); + + let normalized = crate::normalize_newlines(s); + let n = normalized.len() as u32; + let pos32 = pos as u32; + + // Split at insertion point if needed (before any mutation). + self.split_run_at(pos32); + + // Find the insertion index: the first run whose start >= pos32. + // After split_run_at, pos32 is guaranteed to be at a run boundary + // (either an existing boundary or a freshly created one), OR at the + // end of text (past all runs). + let idx = self.runs.iter() + .position(|r| r.start >= pos32) + .unwrap_or(self.runs.len()); + + // Insert text into the buffer. + self.text.insert_str(pos, &normalized); + + // Shift all runs from `idx` onward by `+n` (these are the runs + // that come AFTER the insertion point). + for run in &mut self.runs[idx..] { + run.start += n; + run.end += n; + } + + // Insert the new run at the insertion point. + self.runs.insert(idx, StyledRun { + start: pos32, + end: pos32 + n, + style, + }); + + self.coalesce(); + debug_assert!(self.check_invariants().is_ok(), "{:?}", self.check_invariants()); + } + + /// Delete the byte range `[lo, hi)` from the text and update runs. + /// + /// # Panics + /// + /// Panics in debug mode if `lo` or `hi` are not valid char boundaries. + pub fn delete(&mut self, lo: usize, hi: usize) { + if lo >= hi || lo >= self.text.len() { + return; + } + let hi = hi.min(self.text.len()); + debug_assert!( + self.text.is_char_boundary(lo) && self.text.is_char_boundary(hi), + "delete range [{lo}, {hi}) contains invalid char boundaries" + ); + + let lo32 = lo as u32; + let hi32 = hi as u32; + let span = hi32 - lo32; + + // Remove text. + self.text.drain(lo..hi); + let new_len = self.text.len() as u32; + + // Adjust runs. + let mut i = 0; + while i < self.runs.len() { + let run = &mut self.runs[i]; + + if run.end <= lo32 { + // Run is entirely before the deleted range — no change. + i += 1; + } else if run.start >= hi32 { + // Run is entirely after the deleted range — shift back. + run.start -= span; + run.end -= span; + i += 1; + } else if run.start >= lo32 && run.end <= hi32 { + // Run is entirely within the deleted range — remove it. + self.runs.remove(i); + // Don't increment i. + } else if run.start < lo32 && run.end > hi32 { + // Deleted range is entirely within this run — shrink it. + run.end -= span; + i += 1; + } else if run.start < lo32 { + // Run overlaps the start of the deleted range. + run.end = lo32; + i += 1; + } else { + // Run overlaps the end of the deleted range. + run.start = lo32; + run.end -= span; + i += 1; + } + } + + // If all runs were removed (deleted entire text), add degenerate run. + if self.runs.is_empty() { + self.runs.push(StyledRun { + start: 0, + end: 0, + style: self.default_style.clone(), + }); + } + + // Clamp to ensure no run exceeds text length. + for run in &mut self.runs { + run.start = run.start.min(new_len); + run.end = run.end.min(new_len); + } + + self.coalesce(); + debug_assert!(self.check_invariants().is_ok(), "{:?}", self.check_invariants()); + } + + /// Apply a style mutation to the byte range `[lo, hi)`. + /// + /// Splits runs at boundaries, applies `f` to each affected run, then + /// merges adjacent equal-style runs. + pub fn apply_style(&mut self, lo: usize, hi: usize, f: impl Fn(&mut TextStyle)) { + if lo >= hi || self.text.is_empty() { + return; + } + let lo = lo.min(self.text.len()); + let hi = hi.min(self.text.len()); + if lo >= hi { + return; + } + let lo32 = lo as u32; + let hi32 = hi as u32; + + // Split at boundaries. + self.split_run_at(lo32); + self.split_run_at(hi32); + + // Apply mutation to all runs within [lo32, hi32). + for run in &mut self.runs { + if run.start >= lo32 && run.end <= hi32 && run.start < run.end { + f(&mut run.style); + } + } + + self.coalesce(); + debug_assert!(self.check_invariants().is_ok(), "{:?}", self.check_invariants()); + } + + /// Set the style for the byte range `[lo, hi)`, replacing any existing + /// styles in that range. + pub fn set_style(&mut self, lo: usize, hi: usize, style: TextStyle) { + self.apply_style(lo, hi, |s| *s = style.clone()); + } + + /// Replace the text in `[lo, hi)` with `s`, inheriting the style of the + /// run at `lo`. This is the primitive for `replace_range` edits. + pub fn replace(&mut self, lo: usize, hi: usize, s: &str) { + let style_at_lo = self.style_at(lo as u32).clone(); + self.delete(lo, hi); + if s.is_empty() { + return; + } + // After deletion, the position `lo` may be clamped. + let pos = lo.min(self.text.len()); + self.insert(pos, s); + // The inserted text inherited the run style at `pos`, which should + // match `style_at_lo` after the deletion + coalesce. If the delete + // removed the run that held that style, we need to force it. + // Since insert inherits the current run at `pos`, and coalesce ran, + // this is generally correct. We do a defensive set_style. + let end = pos + crate::normalize_newlines(s).len(); + if *self.style_at(pos as u32) != style_at_lo { + self.set_style(pos, end, style_at_lo); + } + } + + // ----------------------------------------------------------------------- + // Internal helpers + // ----------------------------------------------------------------------- + + /// Split the run at `offset` into two runs with the same style. + /// No-op if `offset` is already at a run boundary or out of range. + fn split_run_at(&mut self, offset: u32) { + if offset == 0 || offset >= self.text.len() as u32 { + return; + } + // Find the run containing offset. + for i in 0..self.runs.len() { + let run = &self.runs[i]; + if run.start < offset && offset < run.end { + let second = StyledRun { + start: offset, + end: run.end, + style: run.style.clone(), + }; + self.runs[i].end = offset; + self.runs.insert(i + 1, second); + return; + } + } + } + + /// Merge adjacent runs with equal styles, and remove zero-length runs + /// (except the degenerate empty-text run). + pub fn coalesce(&mut self) { + if self.runs.is_empty() { + return; + } + + // Remove zero-length runs (unless it's the only run and text is empty). + if self.text.is_empty() { + // Keep exactly one degenerate run. + self.runs.truncate(1); + self.runs[0].start = 0; + self.runs[0].end = 0; + return; + } + + // Remove zero-length runs. + self.runs.retain(|r| r.start < r.end); + + // If that removed everything (shouldn't happen), restore. + if self.runs.is_empty() { + self.runs.push(StyledRun { + start: 0, + end: self.text.len() as u32, + style: self.default_style.clone(), + }); + return; + } + + // Merge adjacent equal-style runs. + let mut i = 0; + while i + 1 < self.runs.len() { + if self.runs[i].style == self.runs[i + 1].style { + self.runs[i].end = self.runs[i + 1].end; + self.runs.remove(i + 1); + } else { + i += 1; + } + } + } + + // ----------------------------------------------------------------------- + // Invariant checking + // ----------------------------------------------------------------------- + + /// Validate all six invariants. Returns `Ok(())` if valid, or the first + /// violation found. + pub fn check_invariants(&self) -> Result<(), InvariantError> { + if self.runs.is_empty() { + return Err(InvariantError::EmptyRuns); + } + + let text_len = self.text.len() as u32; + + // 1. Coverage: start + if self.runs[0].start != 0 { + return Err(InvariantError::CoverageStart { + expected: 0, + actual: self.runs[0].start, + }); + } + + // 1. Coverage: end + if self.runs.last().unwrap().end != text_len { + return Err(InvariantError::CoverageEnd { + expected: text_len, + actual: self.runs.last().unwrap().end, + }); + } + + for i in 0..self.runs.len() { + let run = &self.runs[i]; + + // 3. Non-empty (except the empty-text degenerate case) + if run.start > run.end { + return Err(InvariantError::EmptyRun { + index: i, + start: run.start, + end: run.end, + }); + } + if run.start == run.end && !self.text.is_empty() { + return Err(InvariantError::EmptyRun { + index: i, + start: run.start, + end: run.end, + }); + } + + // 5. Boundary alignment + let s = run.start as usize; + let e = run.end as usize; + if s <= self.text.len() && !self.text.is_char_boundary(s) { + return Err(InvariantError::BadBoundary { + index: i, + field: "start", + offset: run.start, + }); + } + if e <= self.text.len() && !self.text.is_char_boundary(e) { + return Err(InvariantError::BadBoundary { + index: i, + field: "end", + offset: run.end, + }); + } + + // 2. Contiguity + 6. Monotonicity + 4. Maximality (with next run) + if i + 1 < self.runs.len() { + let next = &self.runs[i + 1]; + + // 2. Contiguity + if run.end != next.start { + return Err(InvariantError::Contiguity { + index: i + 1, + prev_end: run.end, + next_start: next.start, + }); + } + + // 6. Monotonicity + if run.start >= next.start { + return Err(InvariantError::Monotonicity { index: i }); + } + + // 4. Maximality + if run.style == next.style { + return Err(InvariantError::NotMaximal { index: i }); + } + } + } + + Ok(()) + } +} + +// --------------------------------------------------------------------------- +// Conversion: plain text <-> attributed text +// --------------------------------------------------------------------------- + +impl From<&str> for AttributedText { + /// Create an attributed text from a plain string with default style. + fn from(s: &str) -> Self { + Self::new(s, TextStyle::default()) + } +} + +impl From for AttributedText { + fn from(s: String) -> Self { + Self::new(s, TextStyle::default()) + } +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + fn default_style() -> TextStyle { + TextStyle::default() + } + + fn bold_style() -> TextStyle { + TextStyle { font_weight: 700, ..default_style() } + } + + fn italic_style() -> TextStyle { + TextStyle { font_style_italic: true, ..default_style() } + } + + fn bold_italic_style() -> TextStyle { + TextStyle { font_weight: 700, font_style_italic: true, ..default_style() } + } + + fn red_style() -> TextStyle { + TextStyle { + fill: TextFill::Solid(RGBA { r: 1.0, g: 0.0, b: 0.0, a: 1.0 }), + ..default_style() + } + } + + // ----------------------------------------------------------------------- + // Construction + // ----------------------------------------------------------------------- + + #[test] + fn new_single_run() { + let at = AttributedText::new("Hello", default_style()); + assert_eq!(at.runs().len(), 1); + assert_eq!(at.runs()[0].start, 0); + assert_eq!(at.runs()[0].end, 5); + assert_eq!(at.text(), "Hello"); + } + + #[test] + fn empty_text_degenerate_run() { + let at = AttributedText::empty(bold_style()); + assert_eq!(at.runs().len(), 1); + assert_eq!(at.runs()[0].start, 0); + assert_eq!(at.runs()[0].end, 0); + assert_eq!(at.runs()[0].style.font_weight, 700); + } + + #[test] + fn normalizes_crlf() { + let at = AttributedText::new("a\r\nb\rc", default_style()); + assert_eq!(at.text(), "a\nb\nc"); + } + + // ----------------------------------------------------------------------- + // Querying + // ----------------------------------------------------------------------- + + #[test] + fn style_at_single_run() { + let at = AttributedText::new("Hello", bold_style()); + assert_eq!(at.style_at(0).font_weight, 700); + assert_eq!(at.style_at(4).font_weight, 700); + } + + #[test] + fn style_at_multiple_runs() { + let mut at = AttributedText::new("Hello World", default_style()); + at.apply_style(0, 5, |s| { s.font_weight = 700; }); + // "Hello" is bold, " World" is normal + assert_eq!(at.style_at(0).font_weight, 700); + assert_eq!(at.style_at(4).font_weight, 700); + assert_eq!(at.style_at(5).font_weight, 400); + assert_eq!(at.style_at(10).font_weight, 400); + } + + #[test] + fn caret_style_at_boundary() { + let mut at = AttributedText::new("HelloWorld", default_style()); + at.apply_style(0, 5, |s| { s.font_weight = 700; }); + // At position 5 (boundary between bold "Hello" and normal "World"), + // caret style should be bold (upstream run). + assert_eq!(at.caret_style_at(5).font_weight, 700); + // At position 0, caret style is the first run. + assert_eq!(at.caret_style_at(0).font_weight, 700); + // At end of text, caret style is the last run. + assert_eq!(at.caret_style_at(10).font_weight, 400); + } + + #[test] + fn runs_in_range_basic() { + let mut at = AttributedText::new("AABBCC", default_style()); + at.apply_style(0, 2, |s| { s.font_weight = 700; }); + at.apply_style(4, 6, |s| { s.font_style_italic = true; }); + // 3 runs: [0,2) bold, [2,4) normal, [4,6) italic + assert_eq!(at.runs().len(), 3); + + let r = at.runs_in_range(1, 5); + assert_eq!(r.len(), 3); // all three overlap [1, 5) + + let r = at.runs_in_range(0, 2); + assert_eq!(r.len(), 1); // only the bold run + + let r = at.runs_in_range(2, 4); + assert_eq!(r.len(), 1); // only the normal run + } + + // ----------------------------------------------------------------------- + // Insert + // ----------------------------------------------------------------------- + + #[test] + fn insert_at_start() { + let mut at = AttributedText::new("World", bold_style()); + at.insert(0, "Hello "); + assert_eq!(at.text(), "Hello World"); + assert_eq!(at.runs().len(), 1); + assert_eq!(at.runs()[0].end, 11); + } + + #[test] + fn insert_at_end() { + let mut at = AttributedText::new("Hello", default_style()); + at.insert(5, " World"); + assert_eq!(at.text(), "Hello World"); + assert_eq!(at.runs().len(), 1); + } + + #[test] + fn insert_in_middle() { + let mut at = AttributedText::new("HellWorld", default_style()); + at.insert(4, "o "); + assert_eq!(at.text(), "Hello World"); + } + + #[test] + fn insert_preserves_subsequent_runs() { + let mut at = AttributedText::new("AB", default_style()); + at.apply_style(1, 2, |s| { s.font_weight = 700; }); + // Runs: [0,1) normal "A", [1,2) bold "B" + assert_eq!(at.runs().len(), 2); + + at.insert(0, "X"); + // Now "XAB": [0,2) normal "XA", [2,3) bold "B" + assert_eq!(at.text(), "XAB"); + assert_eq!(at.runs().len(), 2); + assert_eq!(at.runs()[0].start, 0); + assert_eq!(at.runs()[0].end, 2); + assert_eq!(at.runs()[1].start, 2); + assert_eq!(at.runs()[1].end, 3); + assert_eq!(at.runs()[1].style.font_weight, 700); + } + + #[test] + fn insert_into_empty_text() { + let mut at = AttributedText::empty(bold_style()); + at.insert(0, "Hello"); + assert_eq!(at.text(), "Hello"); + assert_eq!(at.runs().len(), 1); + assert_eq!(at.runs()[0].style.font_weight, 700); + } + + #[test] + fn insert_with_style_different_from_context() { + let mut at = AttributedText::new("AB", default_style()); + at.insert_with_style(1, "X", bold_style()); + // "AXB": [0,1) normal, [1,2) bold, [2,3) normal + assert_eq!(at.text(), "AXB"); + assert_eq!(at.runs().len(), 3); + assert_eq!(at.runs()[1].style.font_weight, 700); + } + + #[test] + fn insert_with_style_matching_context_merges() { + let mut at = AttributedText::new("AB", default_style()); + at.insert_with_style(1, "X", default_style()); + // "AXB": single run since style matches + assert_eq!(at.text(), "AXB"); + assert_eq!(at.runs().len(), 1); + } + + // ----------------------------------------------------------------------- + // Delete + // ----------------------------------------------------------------------- + + #[test] + fn delete_within_single_run() { + let mut at = AttributedText::new("Hello World", default_style()); + at.delete(5, 11); + assert_eq!(at.text(), "Hello"); + assert_eq!(at.runs().len(), 1); + } + + #[test] + fn delete_spanning_runs() { + let mut at = AttributedText::new("AABBCC", default_style()); + at.apply_style(0, 2, |s| { s.font_weight = 700; }); + at.apply_style(4, 6, |s| { s.font_style_italic = true; }); + // Runs: [0,2) bold, [2,4) normal, [4,6) italic + assert_eq!(at.runs().len(), 3); + + at.delete(1, 5); + // "AC": [0,1) bold, [1,2) italic + assert_eq!(at.text(), "AC"); + assert_eq!(at.runs().len(), 2); + assert_eq!(at.runs()[0].style.font_weight, 700); + assert!(at.runs()[1].style.font_style_italic); + } + + #[test] + fn delete_entire_text() { + let mut at = AttributedText::new("Hello", bold_style()); + at.delete(0, 5); + assert!(at.is_empty()); + assert_eq!(at.runs().len(), 1); + assert_eq!(at.runs()[0].start, 0); + assert_eq!(at.runs()[0].end, 0); + } + + #[test] + fn delete_merges_adjacent_equal_style_runs() { + let mut at = AttributedText::new("ABCDE", default_style()); + at.apply_style(2, 3, |s| { s.font_weight = 700; }); + // Runs: [0,2) normal, [2,3) bold, [3,5) normal + assert_eq!(at.runs().len(), 3); + + at.delete(2, 3); + // "ABDE": the two normal runs merge. + assert_eq!(at.text(), "ABDE"); + assert_eq!(at.runs().len(), 1); + } + + #[test] + fn delete_entire_run_in_middle() { + let mut at = AttributedText::new("AABBCC", default_style()); + at.apply_style(0, 2, |s| { s.font_weight = 700; }); + at.apply_style(4, 6, |s| { s.font_style_italic = true; }); + // Delete the middle "normal" run entirely + at.delete(2, 4); + // "AACC": [0,2) bold, [2,4) italic + assert_eq!(at.text(), "AACC"); + assert_eq!(at.runs().len(), 2); + } + + // ----------------------------------------------------------------------- + // Apply style + // ----------------------------------------------------------------------- + + #[test] + fn apply_style_entire_text() { + let mut at = AttributedText::new("Hello", default_style()); + at.apply_style(0, 5, |s| { s.font_weight = 700; }); + assert_eq!(at.runs().len(), 1); + assert_eq!(at.runs()[0].style.font_weight, 700); + } + + #[test] + fn apply_style_middle_range() { + let mut at = AttributedText::new("Hello World", default_style()); + at.apply_style(6, 11, |s| { s.font_weight = 700; }); + // "Hello " normal, "World" bold + assert_eq!(at.runs().len(), 2); + assert_eq!(at.runs()[0].end, 6); + assert_eq!(at.runs()[1].start, 6); + assert_eq!(at.runs()[1].style.font_weight, 700); + } + + #[test] + fn apply_style_creates_three_runs() { + let mut at = AttributedText::new("Hello World!", default_style()); + at.apply_style(6, 11, |s| { s.font_weight = 700; }); + // "Hello " normal, "World" bold, "!" normal + assert_eq!(at.runs().len(), 3); + } + + #[test] + fn apply_style_merges_when_same() { + let mut at = AttributedText::new("Hello World!", default_style()); + at.apply_style(6, 11, |s| { s.font_weight = 700; }); + assert_eq!(at.runs().len(), 3); + + // Now make the whole thing bold — should merge back to 1 run. + at.apply_style(0, 12, |s| { s.font_weight = 700; }); + assert_eq!(at.runs().len(), 1); + assert_eq!(at.runs()[0].style.font_weight, 700); + } + + #[test] + fn apply_style_overlapping_ranges() { + let mut at = AttributedText::new("ABCDEFGH", default_style()); + at.apply_style(0, 4, |s| { s.font_weight = 700; }); + at.apply_style(2, 6, |s| { s.font_style_italic = true; }); + // Expected: [0,2) bold, [2,4) bold+italic, [4,6) italic, [6,8) normal + assert_eq!(at.runs().len(), 4); + assert_eq!(at.runs()[0].style.font_weight, 700); + assert!(!at.runs()[0].style.font_style_italic); + assert_eq!(at.runs()[1].style.font_weight, 700); + assert!(at.runs()[1].style.font_style_italic); + assert_eq!(at.runs()[2].style.font_weight, 400); + assert!(at.runs()[2].style.font_style_italic); + assert_eq!(at.runs()[3].style.font_weight, 400); + assert!(!at.runs()[3].style.font_style_italic); + } + + #[test] + fn apply_style_exact_run_boundaries() { + let mut at = AttributedText::new("AB", default_style()); + at.apply_style(0, 1, |s| { s.font_weight = 700; }); + assert_eq!(at.runs().len(), 2); + + // Apply to the exact same range — no extra splits. + at.apply_style(0, 1, |s| { s.font_style_italic = true; }); + assert_eq!(at.runs().len(), 2); + assert_eq!(at.runs()[0].style.font_weight, 700); + assert!(at.runs()[0].style.font_style_italic); + } + + #[test] + fn apply_style_zero_width_range_noop() { + let mut at = AttributedText::new("Hello", default_style()); + at.apply_style(2, 2, |s| { s.font_weight = 700; }); + assert_eq!(at.runs().len(), 1); + assert_eq!(at.runs()[0].style.font_weight, 400); + } + + // ----------------------------------------------------------------------- + // Set style + // ----------------------------------------------------------------------- + + #[test] + fn set_style_replaces() { + let mut at = AttributedText::new("Hello", default_style()); + at.set_style(0, 5, bold_style()); + assert_eq!(at.runs().len(), 1); + assert_eq!(at.runs()[0].style.font_weight, 700); + } + + // ----------------------------------------------------------------------- + // Replace + // ----------------------------------------------------------------------- + + #[test] + fn replace_preserves_style() { + let mut at = AttributedText::new("AAABBB", default_style()); + at.apply_style(0, 3, |s| { s.font_weight = 700; }); + // Replace "AAA" with "XX" + at.replace(0, 3, "XX"); + assert_eq!(at.text(), "XXBBB"); + assert_eq!(at.runs()[0].style.font_weight, 700); + assert_eq!(at.runs()[0].end, 2); + } + + #[test] + fn replace_with_empty_string() { + let mut at = AttributedText::new("Hello World", default_style()); + at.replace(5, 11, ""); + assert_eq!(at.text(), "Hello"); + } + + // ----------------------------------------------------------------------- + // Multibyte / Unicode + // ----------------------------------------------------------------------- + + #[test] + fn insert_multibyte() { + let mut at = AttributedText::new("AB", default_style()); + at.insert(1, "\u{1F600}"); // Grinning face emoji (4 bytes) + assert_eq!(at.text(), "A\u{1F600}B"); + assert_eq!(at.runs().len(), 1); + assert_eq!(at.runs()[0].end, 6); // 1 + 4 + 1 + } + + #[test] + fn apply_style_multibyte_boundaries() { + // "A\u{1F600}B" = bytes [0..1) [1..5) [5..6) + let mut at = AttributedText::new("A\u{1F600}B", default_style()); + at.apply_style(1, 5, |s| { s.font_weight = 700; }); + assert_eq!(at.runs().len(), 3); + assert_eq!(at.runs()[1].start, 1); + assert_eq!(at.runs()[1].end, 5); + assert_eq!(at.runs()[1].style.font_weight, 700); + } + + #[test] + fn delete_multibyte() { + let mut at = AttributedText::new("A\u{1F600}B", default_style()); + at.delete(1, 5); // delete the emoji + assert_eq!(at.text(), "AB"); + assert_eq!(at.runs().len(), 1); + } + + // ----------------------------------------------------------------------- + // Invariant checking + // ----------------------------------------------------------------------- + + #[test] + fn check_invariants_valid() { + let at = AttributedText::new("Hello", default_style()); + assert!(at.check_invariants().is_ok()); + } + + #[test] + fn check_invariants_empty_text() { + let at = AttributedText::empty(default_style()); + assert!(at.check_invariants().is_ok()); + } + + #[test] + fn from_parts_rejects_bad_coverage() { + let result = std::panic::catch_unwind(|| { + AttributedText::from_parts( + "Hello".into(), + default_style(), + ParagraphStyle::default(), + vec![StyledRun { start: 1, end: 5, style: default_style() }], + ); + }); + assert!(result.is_err()); + } + + #[test] + fn from_parts_rejects_gap() { + let result = std::panic::catch_unwind(|| { + AttributedText::from_parts( + "Hello".into(), + default_style(), + ParagraphStyle::default(), + vec![ + StyledRun { start: 0, end: 2, style: default_style() }, + StyledRun { start: 3, end: 5, style: bold_style() }, + ], + ); + }); + assert!(result.is_err()); + } + + #[test] + fn from_parts_rejects_not_maximal() { + let result = std::panic::catch_unwind(|| { + AttributedText::from_parts( + "Hello".into(), + default_style(), + ParagraphStyle::default(), + vec![ + StyledRun { start: 0, end: 3, style: default_style() }, + StyledRun { start: 3, end: 5, style: default_style() }, + ], + ); + }); + assert!(result.is_err()); + } + + // ----------------------------------------------------------------------- + // Coalesce + // ----------------------------------------------------------------------- + + #[test] + fn coalesce_merges_adjacent_equal() { + let mut at = AttributedText::new("Hello", default_style()); + // Manually insert a duplicate run to test coalesce. + at.runs.clear(); + at.runs.push(StyledRun { start: 0, end: 3, style: default_style() }); + at.runs.push(StyledRun { start: 3, end: 5, style: default_style() }); + at.coalesce(); + assert_eq!(at.runs().len(), 1); + assert_eq!(at.runs()[0].start, 0); + assert_eq!(at.runs()[0].end, 5); + } + + // ----------------------------------------------------------------------- + // Complex scenarios + // ----------------------------------------------------------------------- + + #[test] + fn bold_then_italic_overlapping() { + // The classic span-resolution test from the spec. + let mut at = AttributedText::new("ABCDEFGHIJ", default_style()); + // Bold [0, 5) + at.apply_style(0, 5, |s| { s.font_weight = 700; }); + // Italic [3, 8) + at.apply_style(3, 8, |s| { s.font_style_italic = true; }); + + assert_eq!(at.runs().len(), 4); + // [0,3) bold only + assert_eq!(at.runs()[0].start, 0); + assert_eq!(at.runs()[0].end, 3); + assert_eq!(at.runs()[0].style.font_weight, 700); + assert!(!at.runs()[0].style.font_style_italic); + // [3,5) bold + italic + assert_eq!(at.runs()[1].start, 3); + assert_eq!(at.runs()[1].end, 5); + assert_eq!(at.runs()[1].style.font_weight, 700); + assert!(at.runs()[1].style.font_style_italic); + // [5,8) italic only + assert_eq!(at.runs()[2].start, 5); + assert_eq!(at.runs()[2].end, 8); + assert_eq!(at.runs()[2].style.font_weight, 400); + assert!(at.runs()[2].style.font_style_italic); + // [8,10) normal + assert_eq!(at.runs()[3].start, 8); + assert_eq!(at.runs()[3].end, 10); + assert_eq!(at.runs()[3].style.font_weight, 400); + assert!(!at.runs()[3].style.font_style_italic); + } + + #[test] + fn insert_then_style_then_delete() { + let mut at = AttributedText::new("AC", default_style()); + // Insert "B" between A and C. + at.insert(1, "B"); + assert_eq!(at.text(), "ABC"); + // Make "B" bold. + at.apply_style(1, 2, |s| { s.font_weight = 700; }); + assert_eq!(at.runs().len(), 3); + // Delete "A". + at.delete(0, 1); + assert_eq!(at.text(), "BC"); + assert_eq!(at.runs().len(), 2); + assert_eq!(at.runs()[0].style.font_weight, 700); + assert_eq!(at.runs()[0].start, 0); + assert_eq!(at.runs()[0].end, 1); + } + + #[test] + fn many_sequential_inserts() { + let mut at = AttributedText::empty(default_style()); + for ch in "Hello, World!".chars() { + let pos = at.len(); + at.insert(pos, &ch.to_string()); + } + assert_eq!(at.text(), "Hello, World!"); + assert_eq!(at.runs().len(), 1); + } + + #[test] + fn style_entire_then_unstyle_middle() { + let mut at = AttributedText::new("ABCDE", default_style()); + // Make everything bold. + at.apply_style(0, 5, |s| { s.font_weight = 700; }); + assert_eq!(at.runs().len(), 1); + // Unbold the middle. + at.apply_style(2, 3, |s| { s.font_weight = 400; }); + assert_eq!(at.runs().len(), 3); + assert_eq!(at.runs()[0].style.font_weight, 700); + assert_eq!(at.runs()[1].style.font_weight, 400); + assert_eq!(at.runs()[2].style.font_weight, 700); + } + + #[test] + fn from_plain_str() { + let at: AttributedText = "Hello".into(); + assert_eq!(at.text(), "Hello"); + assert_eq!(at.runs().len(), 1); + } + + // ----------------------------------------------------------------------- + // Multi-attribute styling (uses italic, bold_italic, red helpers) + // ----------------------------------------------------------------------- + + #[test] + fn set_style_italic_then_bold() { + let mut at = AttributedText::new("ABCD", default_style()); + at.set_style(0, 2, italic_style()); + at.set_style(2, 4, bold_style()); + assert_eq!(at.runs().len(), 2); + assert!(at.runs()[0].style.font_style_italic); + assert_eq!(at.runs()[0].style.font_weight, 400); + assert!(!at.runs()[1].style.font_style_italic); + assert_eq!(at.runs()[1].style.font_weight, 700); + } + + #[test] + fn apply_bold_italic_overlap() { + let mut at = AttributedText::new("ABCDEF", italic_style()); + // Apply bold to the second half. + at.apply_style(3, 6, |s| { s.font_weight = 700; }); + assert_eq!(at.runs().len(), 2); + // First run: italic only. + assert!(at.runs()[0].style.font_style_italic); + assert_eq!(at.runs()[0].style.font_weight, 400); + // Second run: bold+italic. + assert!(at.runs()[1].style.font_style_italic); + assert_eq!(at.runs()[1].style.font_weight, 700); + // Verify it matches bold_italic_style fields. + assert_eq!(at.runs()[1].style, bold_italic_style()); + } + + #[test] + fn set_style_red_fill() { + let mut at = AttributedText::new("Hello", default_style()); + at.set_style(0, 5, red_style()); + assert_eq!(at.runs().len(), 1); + match &at.runs()[0].style.fill { + TextFill::Solid(c) => { + assert_eq!(c.r, 1.0); + assert_eq!(c.g, 0.0); + assert_eq!(c.b, 0.0); + assert_eq!(c.a, 1.0); + } + } + } + + #[test] + fn delete_preserves_italic_run() { + let mut at = AttributedText::new("ABCDEF", default_style()); + at.set_style(2, 4, italic_style()); + // Runs: [0,2) default, [2,4) italic, [4,6) default + at.delete(0, 2); + // "CDEF": [0,2) italic, [2,4) default + assert_eq!(at.text(), "CDEF"); + assert_eq!(at.runs().len(), 2); + assert!(at.runs()[0].style.font_style_italic); + } + + // ----------------------------------------------------------------------- + // Regression: insert_with_style at end of text + // ----------------------------------------------------------------------- + + #[test] + fn insert_with_style_at_end_of_text() { + // Reproduces the panic: typing at the end of a multi-run text. + let mut at = AttributedText::new("Hello, World!\n", default_style()); + at.apply_style(0, 5, |s| { s.font_weight = 700; }); + at.apply_style(7, 12, |s| { s.font_style_italic = true; }); + // Runs: [0,5) bold, [5,7) normal, [7,12) italic, [12,14) normal + + // Insert at the very end — this used to panic with a contiguity error. + at.insert_with_style(14, "X", default_style()); + assert_eq!(at.text(), "Hello, World!\nX"); + assert!(at.check_invariants().is_ok()); + } + + #[test] + fn insert_with_style_at_end_different_style() { + let mut at = AttributedText::new("AB", default_style()); + at.apply_style(0, 1, |s| { s.font_weight = 700; }); + // Runs: [0,1) bold, [1,2) normal + + // Insert bold text at end. + at.insert_with_style(2, "C", bold_style()); + assert_eq!(at.text(), "ABC"); + assert!(at.check_invariants().is_ok()); + // Should be: [0,1) bold, [1,2) normal, [2,3) bold + assert_eq!(at.runs().len(), 3); + } + + #[test] + fn insert_with_style_at_end_matching_style_merges() { + let mut at = AttributedText::new("AB", default_style()); + // Insert default-styled text at end — should merge with last run. + at.insert_with_style(2, "C", default_style()); + assert_eq!(at.text(), "ABC"); + assert!(at.check_invariants().is_ok()); + assert_eq!(at.runs().len(), 1); + } + + #[test] + fn sequential_insert_with_style_at_end() { + // Simulate typing character by character at the end. + let mut at = AttributedText::new("Hello", default_style()); + at.apply_style(0, 5, |s| { s.font_weight = 700; }); + + for ch in ", World!".chars() { + let pos = at.len(); + at.insert_with_style(pos, &ch.to_string(), default_style()); + assert!(at.check_invariants().is_ok(), "failed after inserting '{ch}'"); + } + assert_eq!(at.text(), "Hello, World!"); + // "Hello" bold + ", World!" normal + assert_eq!(at.runs().len(), 2); + } +} diff --git a/crates/grida-text-edit/src/lib.rs b/crates/grida-text-edit/src/lib.rs index 1548a62ef6..799d98f524 100644 --- a/crates/grida-text-edit/src/lib.rs +++ b/crates/grida-text-edit/src/lib.rs @@ -1,3 +1,4 @@ +pub mod attributed_text; pub mod history; pub mod layout; pub mod simple_layout; diff --git a/crates/grida-text-edit/src/skia_layout.rs b/crates/grida-text-edit/src/skia_layout.rs index 2d2d4a5a4c..c58c32db1f 100644 --- a/crates/grida-text-edit/src/skia_layout.rs +++ b/crates/grida-text-edit/src/skia_layout.rs @@ -2,7 +2,7 @@ use skia_safe::{ self as skia_safe, textlayout::{ FontCollection, Paragraph, ParagraphBuilder, ParagraphStyle, - RectHeightStyle, RectWidthStyle, TextStyle, TypefaceFontProvider, + RectHeightStyle, RectWidthStyle, TextDecoration, TextStyle, TypefaceFontProvider, }, Color, FontMgr, Point, }; @@ -152,6 +152,29 @@ impl SkiaLayoutEngine { let weight = skia_safe::font_style::Weight::from(self.config.font_weight as i32); let font_style = skia_safe::FontStyle::new(weight, skia_safe::font_style::Width::NORMAL, slant); ts.set_font_style(font_style); + + // Variable font axis interpolation — push wght and opsz so that + // variable fonts actually render at the requested weight. + { + use skia_safe::font_arguments::variation_position::Coordinate; + let coords = [ + Coordinate { + axis: skia_safe::FourByteTag::from(('w', 'g', 'h', 't')), + value: self.config.font_weight as f32, + }, + Coordinate { + axis: skia_safe::FourByteTag::from(('o', 'p', 's', 'z')), + value: self.config.font_size, + }, + ]; + let variation_position = skia_safe::font_arguments::VariationPosition { + coordinates: &coords, + }; + let font_args = skia_safe::FontArguments::new() + .set_variation_design_position(variation_position); + ts.set_font_arguments(&font_args); + } + if let Some(ls) = self.config.letter_spacing { ts.set_letter_spacing(ls); } @@ -201,6 +224,188 @@ impl SkiaLayoutEngine { self.paragraph = None; } + /// Register multiple fonts at once under the same family. Each byte slice + /// is a separate TTF/OTF file (e.g. regular, italic, bold, bold-italic). + /// Skia will match the correct typeface by weight/slant when building + /// paragraphs. + pub fn add_font_family(&mut self, family: &str, font_data: &[&[u8]]) { + let loader = FontMgr::new(); + let mut provider = TypefaceFontProvider::new(); + for bytes in font_data { + if let Some(tf) = loader.new_from_data(bytes, None) { + provider.register_typeface(tf, Some(family)); + } + } + self.font_collection.set_asset_font_manager(Some(provider.into())); + self.paragraph = None; + } + + /// Build and cache a paragraph from an [`AttributedText`], pushing + /// per-run styles to `ParagraphBuilder`. This is the rich-text layout + /// path — each run gets its own font weight, slant, decoration, color, + /// etc. + pub fn ensure_layout_attributed( + &mut self, + at: &crate::attributed_text::AttributedText, + ) { + if self.paragraph.is_some() && self.cached_text == at.text() { + return; + } + self.rebuild_attributed(at); + } + + fn rebuild_attributed( + &mut self, + at: &crate::attributed_text::AttributedText, + ) { + use crate::attributed_text as at_mod; + + let mut para_style = ParagraphStyle::new(); + para_style.set_apply_rounding_hack(false); + para_style.set_text_align(self.config.text_align.to_skia()); + + let mut builder = ParagraphBuilder::new(¶_style, &self.font_collection); + + let text = at.text(); + let families: Vec<&str> = self.config.font_families.iter().map(|s| s.as_str()).collect(); + + for run in at.runs() { + let style = &run.style; + let mut ts = TextStyle::new(); + + // Font size + ts.set_font_size(style.font_size); + + // Font families + ts.set_font_families(&families); + + // Font style (for typeface matching in FontCollection) + let slant = if style.font_style_italic { + skia_safe::font_style::Slant::Italic + } else { + skia_safe::font_style::Slant::Upright + }; + let weight = skia_safe::font_style::Weight::from(style.font_weight as i32); + let width = skia_safe::font_style::Width::from(style.font_width as i32); + ts.set_font_style(skia_safe::FontStyle::new(weight, width, slant)); + + // Variable font axes (FontArguments) — this is what actually + // triggers weight/width/opsz interpolation on variable fonts. + // Without this, set_font_style only selects among registered + // typefaces but does NOT interpolate variable font axes. + { + use skia_safe::font_arguments::variation_position::Coordinate; + + let mut coords: Vec = Vec::new(); + + // User-specified custom font variations (e.g. CASL, MONO, slnt) + for v in &style.font_variations { + let bytes = v.axis.as_bytes(); + let tag = skia_safe::FourByteTag::from(( + *bytes.first().unwrap_or(&b' ') as char, + *bytes.get(1).unwrap_or(&b' ') as char, + *bytes.get(2).unwrap_or(&b' ') as char, + *bytes.get(3).unwrap_or(&b' ') as char, + )); + coords.push(Coordinate { axis: tag, value: v.value }); + } + + // wght — always push from font_weight + coords.push(Coordinate { + axis: skia_safe::FourByteTag::from(('w', 'g', 'h', 't')), + value: style.font_weight as f32, + }); + + // wdth — from font_width if not default (100.0) + if (style.font_width - 100.0).abs() > f32::EPSILON { + coords.push(Coordinate { + axis: skia_safe::FourByteTag::from(('w', 'd', 't', 'h')), + value: style.font_width, + }); + } + + // opsz — auto = font_size + match style.font_optical_sizing { + at_mod::FontOpticalSizing::Auto => { + coords.push(Coordinate { + axis: skia_safe::FourByteTag::from(('o', 'p', 's', 'z')), + value: style.font_size, + }); + } + at_mod::FontOpticalSizing::Fixed(v) => { + coords.push(Coordinate { + axis: skia_safe::FourByteTag::from(('o', 'p', 's', 'z')), + value: v, + }); + } + at_mod::FontOpticalSizing::None => {} + } + + let variation_position = skia_safe::font_arguments::VariationPosition { + coordinates: &coords, + }; + let font_args = skia_safe::FontArguments::new() + .set_variation_design_position(variation_position); + ts.set_font_arguments(&font_args); + } + + // Color / fill + match &style.fill { + at_mod::TextFill::Solid(rgba) => { + ts.set_color(Color::from_argb( + (rgba.a * 255.0) as u8, + (rgba.r * 255.0) as u8, + (rgba.g * 255.0) as u8, + (rgba.b * 255.0) as u8, + )); + } + } + + // Letter spacing + match style.letter_spacing { + at_mod::TextDimension::Fixed(v) => { ts.set_letter_spacing(v); } + _ => {} + } + + // Kerning + ts.add_font_feature("kern", if style.font_kerning { 1 } else { 0 }); + + // User font features + for feat in &style.font_features { + ts.add_font_feature(feat.tag.clone(), if feat.value { 1 } else { 0 }); + } + + // Decoration + let mut deco = TextDecoration::NO_DECORATION; + match style.text_decoration_line { + at_mod::TextDecorationLine::Underline => { + deco = TextDecoration::UNDERLINE; + } + at_mod::TextDecorationLine::LineThrough => { + deco = TextDecoration::LINE_THROUGH; + } + at_mod::TextDecorationLine::Overline => { + deco = TextDecoration::OVERLINE; + } + at_mod::TextDecorationLine::None => {} + } + ts.set_decoration_type(deco); + + builder.push_style(&ts); + + let start = run.start as usize; + let end = run.end as usize; + if start < end && end <= text.len() { + builder.add_text(&text[start..end]); + } + } + + let mut para = builder.build(); + para.layout(self.layout_width); + self.paragraph = Some(para); + self.cached_text = text.to_owned(); + } + /// Invalidate the cached paragraph so the next layout call rebuilds. /// Call this after modifying `font_collection` externally. pub fn invalidate(&mut self) { diff --git a/docs/wg/feat-text-editing/attributed-text.md b/docs/wg/feat-text-editing/attributed-text.md new file mode 100644 index 0000000000..beaceda20e --- /dev/null +++ b/docs/wg/feat-text-editing/attributed-text.md @@ -0,0 +1,749 @@ +--- +id: attributed-text +title: "Attributed Text: Data Model Specification" +--- + +## Motivation + +The text editing manifesto (`feat-text-editing/index.md`) established geometry queries, cursor semantics, and interaction contracts for a **plain text** editor. That editor operates on a single `String` with uniform styling. Real design tools require **mixed-style text**: a single text block where different character ranges carry different font families, weights, sizes, colors, decorations, and OpenType features. + +This document specifies the **in-memory data model** for attributed text. It is the bridge between the plain-text editing engine and the styled paragraph layout engine. The model must be: + +- **Editing-friendly**: insert, delete, split, merge operations must be O(n) in runs affected, not O(n) in characters. +- **Layout-friendly**: trivially convertible to a sequence of styled text pushes for Skia `ParagraphBuilder` (or equivalent). +- **Serialization-friendly**: representable in FlatBuffers without loss, with a natural binary layout. +- **Compact**: no per-character storage; style data is shared across runs via identity or structural equality. + +## Scope + +This model is designed to serve two deployment targets from a single Rust implementation: + +1. **Native desktop editor** — compiled natively, rendering via Skia (OpenGL/Vulkan). Full text editing with system IME integration. +2. **WASM-web rendered editor** — compiled to WebAssembly, rendering via Skia-in-WASM (CanvasKit). Used as the text editing backend for the browser-based design tool, similar to how Figma renders text into a WebGL canvas rather than using DOM contenteditable. + +In both cases, the model covers a **single text node** (single paragraph block). The host application (native window / browser JS) is responsible for creating, positioning, and managing text nodes within the document. + +### WASM/JS boundary + +When compiled to WASM, the attributed text model crosses the host boundary via: + +- **Serialization**: JSON (for development/debugging) or FlatBuffers (for production). The JS host sends editing commands and style mutations; the WASM module returns the updated model. +- **Font registration**: The JS host fetches font data (ArrayBuffer) and passes it to the WASM module's font manager. Font resolution happens entirely inside WASM. +- **Rendering**: The WASM module builds a Skia `Paragraph` from the run list, renders to a Skia surface backed by a WebGL canvas, and paints caret/selection overlays. The JS host handles DOM events (keyboard, mouse, IME) and translates them to editing commands. + +This architecture means the model must be efficiently serializable, but does not need to be directly manipulable from JS. All mutations go through the engine API. + +## Non-goals + +- **Block-level structure** (headings, lists, tables). This model covers a single text node. Document-level structure is a higher layer. (Figma's `TextLineData.lineType` with `ORDERED_LIST`/`BLOCKQUOTE`/`HEADER` is acknowledged but out of scope for the initial model.) +- **Embedded objects** (images, inline widgets). The scope is text-only attributed strings. +- **Collaborative editing protocols** (OT/CRDT). Can be layered on top; the model must not prevent it. +- **Undo/redo internals**. The history system operates on snapshots; attributed text is a richer snapshot. + +## Terminology + +| Term | Definition | +| ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| **attributed text** | A string paired with an ordered sequence of style runs that fully cover the string. Analogous to `NSAttributedString` (Cocoa) or `SpannableString` (Android). | +| **run** | A maximal contiguous byte range `[start, end)` over which every style attribute is identical. Runs partition the string with no gaps and no overlaps. | +| **span** | An attribute applied over an arbitrary `[start, end)` range. Spans may overlap. Spans are the _authoring_ primitive; runs are the _resolved_ primitive. | +| **style set** | The complete collection of text-level attributes at a given position. Two positions with the same style set belong to the same run. | +| **paragraph style** | Attributes that apply uniformly to the entire text block (alignment, direction, indentation). These are _not_ per-run. | +| **caret style** | The style that will be applied to the next character typed at the current cursor position. Determined by the upstream run, overridable by explicit user action. | + +## Prior art and comparative analysis + +Before presenting the design, we document the four major attributed text architectures in the industry. Each represents a fundamentally different trade-off surface. Understanding them is essential for making an informed choice. + +### A1: Apple `NSAttributedString` / `CFAttributedString` — Run-based (RLE) + +Apple's model stores a flat `CFString` paired with a **run-length encoded array of attribute dictionaries**. Each run is `(length, NSDictionary*)`. Runs are non-overlapping, contiguous, and cover the entire string. Adjacent runs with identical dictionaries are coalesced. + +Key properties: + +- **No inheritance**. Each run stores a complete attribute dictionary. There is no parent/child or cascading relationship. +- **Paragraph semantics by convention**: `NSParagraphStyle` on the first character of a paragraph determines alignment, indentation, etc. for the whole paragraph. This is enforced by the "attribute fixing" post-edit normalization pass in `NSTextStorage`. +- **O(log n) lookup** via binary search over run boundaries. +- **Editing**: insert inherits from the adjacent character. Delete removes/shortens runs and coalesces. Set-attributes splits at boundaries. +- **Trade-off**: Efficient for text with few style changes. No tree to maintain. But no inheritance or cascading, and run splits create complex merge conflicts in collaborative editing. + +### A2: Android `SpannableStringBuilder` — Span-based (interval set) + +Android stores spans as independent `(start, end, flags, object)` tuples over a gap buffer. Spans **can overlap freely**. Each span object represents one formatting attribute (bold, color, size, etc.). + +Key properties: + +- **Span flags** (`SPAN_INCLUSIVE_EXCLUSIVE`, `SPAN_EXCLUSIVE_INCLUSIVE`, etc.) control whether insertions at a span boundary extend the span or not. This is the only system that gives programmatic control over this behavior. +- **No automatic coalescing**. Orphaned/empty spans can accumulate. +- **O(n) query** to find all spans at a given position (linear scan), though newer implementations use interval trees. +- **Trade-off**: Maximum flexibility (overlapping spans, per-span insertion policy). But worst query performance, most complex state management, and poor collaborative editing compatibility. + +### A3: Flutter `TextSpan` — Tree-based (immutable) + +Flutter uses a recursive tree of `TextSpan` nodes. Each node can have `text`, `style`, and `children`. The tree is **immutable** — any edit requires rebuilding. + +Key properties: + +- **Tree-based inheritance**: `pushStyle` merges child style with parent's resolved style. Only non-null fields override. This gives CSS-like cascading. +- **Flattens to runs for layout**: `TextSpan.build(ParagraphBuilder)` performs DFS, calling `pushStyle/addText/pop` — producing the exact run-based representation that Skia consumes. +- **Not designed for editing**: editable text uses a separate `TextEditingValue` (plain text + selection). The styled tree is rebuilt each frame. +- **Trade-off**: Most ergonomic API for developers. Natural inheritance. But terrible for in-place editing — entire tree rebuild per mutation. + +### A4: Figma `TextData` (kiwi schema) — Per-character ID array + override table + +Figma's model is unique. It uses three layers: + +``` +TextData { + characters: string // flat plain text + characterStyleIDs: uint[] // per-character style ID + styleOverrideTable: NodeChange[] // sparse override table + lines: TextLineData[] // per-line metadata + ... +} +``` + +**Layer 1 — Base style**: The `NodeChange` message (Figma's universal node object) carries the base text style (`fontSize`, `fontName`, `textCase`, `textDecoration`, `lineHeight`, `letterSpacing`, etc.). This is "style ID 0." + +**Layer 2 — Per-character ID array**: `characterStyleIDs[i]` gives the override ID for character `i`. A value of `0` means "use base style." The array can be shorter than the character count — trailing characters beyond the array length implicitly use the base style (trailing zeros are omitted as a wire-format optimization). + +**Layer 3 — Override table**: `styleOverrideTable[id]` maps each non-zero style ID to a `NodeChange` — a sparse diff. Only the fields that differ from the base style are set. Unset fields inherit from base. + +Additionally, Figma cleanly separates **authoring data** (`TextData`: characters, style IDs, override table, line metadata) from **layout cache** (`DerivedTextData`: glyphs, baselines, decorations, layout size). Only authoring data flows through the multiplayer sync layer; derived data is recomputed locally. + +Figma also stores per-line metadata via `TextLineData`: + +``` +TextLineData { + lineType: LineType // PLAIN | ORDERED_LIST | UNORDERED_LIST | BLOCKQUOTE | HEADER + styleId: int + indentationLevel: int + sourceDirectionality: SourceDirectionality // AUTO | LTR | RTL + directionality: Directionality // LTR | RTL (resolved) + directionalityIntent: DirectionalityIntent // IMPLICIT | EXPLICIT + ... +} +``` + +Key properties: + +- **O(1) style lookup** per character (direct array index + table lookup). +- **Excellent CRDT/multiplayer compatibility**: per-character ID arrays splice trivially; the immutable override table avoids run-boundary merge conflicts entirely. +- **Memory overhead**: one `uint` per character even for uniformly-styled text (mitigated by trailing-zero truncation). +- **Two-level inheritance only**: base style + one override layer. No deep cascade. +- **Trade-off**: The per-character array is the cleanest model for real-time collaboration, at the cost of memory for uniformly-styled text. The reuse of `NodeChange` as a sparse diff eliminates a separate "text style override" type. + +### Comparative matrix + +| Dimension | Apple (runs) | Android (spans) | Flutter (tree) | Figma (per-char IDs) | +| -------------------- | -------------------------- | --------------------------------- | ---------------------- | -------------------------- | +| **Topology** | Flat run array | Flat interval set | Immutable tree | Flat char array + table | +| **Overlap** | No | Yes | No (hierarchy) | No | +| **Style lookup** | O(log k) | O(n) or O(log n + k) | O(depth) | O(1) | +| **Insert** | Inherits from adjacent | Controlled by flags | Rebuild tree | Inherits base (ID 0) | +| **Coalescing** | Automatic | None | N/A | N/A (no runs) | +| **Inheritance** | None | None (last span wins) | Tree merge | Two-level: base + override | +| **Multiplayer** | Poor (run splits conflict) | Poor (index adjustment conflicts) | Poor (tree diff) | Excellent | +| **Memory (uniform)** | 1 run | 0 spans | 1 node | n integers | +| **Layout mapping** | Direct (run = push+text) | Resolve then push | DFS produces push+text | Resolve per-char then RLE | + +### Why we choose run-based + +For a design tool that is **not** building real-time multiplayer as a first-class constraint (CRDT/OT is explicitly a non-goal per the manifesto), the run-based model provides the best balance: + +1. **Direct layout mapping**: each run is one `pushStyle + addText` call. No resolution step, no tree flattening, no per-character table lookup. This matches exactly what Skia `ParagraphBuilder` consumes. + +2. **Minimal memory**: typical design text has 1-10 style changes. Run count is proportional to style transitions, not character count. A 1000-character paragraph with 3 style changes costs 3 runs (~384 bytes), not 1000 integers (~4000 bytes). + +3. **Automatic normalization**: the coalescing invariant means the model is always in canonical form. No garbage spans, no redundant entries, no denormalized state. + +4. **Editing simplicity**: the operations (split, shift, coalesce) are well-understood, debuggable, and fully covered by invariant assertions. + +5. **Alignment with NSAttributedString**: the most battle-tested attributed text model in the industry. Apple's design has been stable for 25+ years. + +The run-based model can later support a collaborative layer by converting to/from a per-character ID representation at the sync boundary, similar to how the existing editor converts between UTF-8 offsets (internal) and UTF-16 offsets (Skia interop). The run representation is the canonical in-memory form; the per-character form is a serialization/sync format. + +## Design decisions + +### D1: Run-based, not span-based, not per-character + +We store **resolved runs**, not overlapping spans and not per-character IDs. + +See the comparative analysis above for the full rationale. The key insight is that runs are the **output format** that every layout engine consumes. Choosing runs as the storage format eliminates all resolution/flattening steps. + +**Trade-off**: Runs cannot natively represent "bold from 0..10, italic from 5..15" as two independent layers. Instead, the system produces three runs: `[0,5) bold`, `[5,10) bold+italic`, `[10,15) italic`. This is the correct resolved form and is exactly what every layout engine requires. + +### D2: Runs reference the text buffer, they do not own substrings + +Each run stores `(start: u32, end: u32)` as UTF-8 byte offsets into a shared backing string. Runs never duplicate text data. + +Using `u32` (not `usize`) for offsets is deliberate: + +- 4 GiB of text per node is far beyond any realistic design tool text block. +- `u32` halves offset storage cost on 64-bit platforms. +- `u32` matches FlatBuffers' natural integer width and Skia's internal `int32_t` text indices. + +### D3: Style identity via structural equality, not interning + +Two runs with identical attribute values are **mergeable**. After any edit, adjacent runs with equal style sets are coalesced. This maintains the invariant that runs are **maximal** (no two adjacent runs have the same style). + +Style sets are compared field-by-field. No interning, no pointer identity, no style IDs at the data model level. The cost of field-by-field comparison is bounded by the fixed number of attributes (~20 fields) and is negligible compared to layout. + +Figma's approach (style ID table) is an interning strategy optimized for their multiplayer use case. We deliberately avoid this because: + +- It adds an indirection layer (ID -> style) that complicates every query. +- It requires a separate "override resolution" step before layout. +- The benefit (deduplication) only matters with per-character storage, which we don't use. + +> **Note on future optimization**: If profiling shows that style comparison is hot, an interning layer (`Arc` or `StyleId -> TextStyle` table) can be added on top without changing the data contract. + +### D4: Paragraph-level vs. run-level attribute partition + +Attributes are partitioned into two tiers: + +- **Paragraph-level**: `text_align`, `text_align_vertical`, `max_lines`, `ellipsis`, `text_indent`, `paragraph_direction`. These apply uniformly. Storing them per-run would be wasteful and semantically wrong (what does it mean for characters 5..10 to have `text_align: center` but 10..15 to have `text_align: left`?). + +- **Run-level**: everything else (font family, size, weight, width, italic, kerning, optical sizing, OpenType features, variable font axes, letter spacing, word spacing, line height, text decoration, text transform, text color/fill). These can vary per character range. + +This mirrors Apple's `NSParagraphStyle` (paragraph-level) vs. other `NSAttributedString` attributes (run-level), and Figma's `textAlignHorizontal`/`textAlignVertical` (node-level) vs. `fontSize`/`fontName`/etc. (per-character overridable). + +### D5: Default style and two-level inheritance + +Every attributed text has a **default style set** (the "base" style), following Figma's two-level pattern: + +- **Base style** (always present) — the default for all characters. +- **Run override** (per run) — only runs that differ from the base carry their own style. + +In the **in-memory** model, runs store fully resolved style sets (no inheritance chain to walk at query time). Resolution is eager, not lazy. + +In the **serialization** model (FlatBuffers), runs can use delta encoding: optional fields that, when absent, inherit from the base style. The codec handles resolution at the boundary. + +This is equivalent to Figma's `styleOverrideTable` pattern, but with runs instead of per-character IDs. It gives: + +- **Compact serialization**: most text blocks have 1-3 distinct styles. +- **O(1) query**: no inheritance chain to walk. +- **Simple editing**: "reset to default" = remove the run's override; coalesce will merge it with adjacent default runs. + +### D6: Caret style semantics + +When the cursor sits at a run boundary, the question "what style should the next typed character have?" is ambiguous. This is a problem that every system solves differently: + +- **Apple**: inherits from the character _before_ the cursor (upstream). +- **Android**: controlled by span flags (`INCLUSIVE`/`EXCLUSIVE`). +- **Figma**: defaults to base style (ID 0). +- **Design tools** (Sketch, Figma UI, Adobe): generally continue the upstream style. + +We adopt the **upstream rule**: caret style = style of the run that _ends_ at the cursor position, unless the cursor is at position 0 (use the first run's style). + +The caret style can be **overridden** by explicit user action (e.g., clicking "Bold" with no selection). This override is transient editor state, not part of `AttributedText`. It is cleared on cursor movement. + +## Data model + +### The core structure + +``` +AttributedText = (text, default_style, paragraph_style, runs) +``` + +Where: + +- `text: String` — the backing UTF-8 string, newlines normalized to `\n` +- `default_style: TextStyle` — the base style for the paragraph +- `paragraph_style: ParagraphStyle` — paragraph-level attributes +- `runs: Vec` — ordered, non-overlapping, gap-free, maximal + +### StyledRun + +```rust +struct StyledRun { + /// Start byte offset (UTF-8, inclusive). Must be on a char boundary. + start: u32, + /// End byte offset (UTF-8, exclusive). Must be on a char boundary. + end: u32, + /// The resolved style for this run. + style: TextStyle, +} +``` + +**Invariants** (must hold at all times): + +Let `n = text.len() as u32` and `k = runs.len()`. + +1. **Non-empty run list**: `k >= 1`. +2. **Coverage**: `runs[0].start == 0` and `runs[k-1].end == n`. +3. **Contiguity**: for all `0 <= i < k-1`: `runs[i].end == runs[i+1].start`. +4. **Non-degenerate runs**: for all `0 <= i < k`: `runs[i].start < runs[i].end`. Exception: when `n == 0`, exactly one degenerate run `{start: 0, end: 0}` is permitted (the caret style run). +5. **Maximality**: for all `0 <= i < k-1`: `runs[i].style != runs[i+1].style`. +6. **Boundary alignment**: for all `0 <= i < k`: `runs[i].start` and `runs[i].end` are valid UTF-8 char boundaries in `text`. +7. **Monotonicity** (implied by 3 + 4): `runs[i].start < runs[i+1].start`. + +Every public mutation must uphold all seven invariants. Implementations should assert them after every edit in debug builds. + +### TextStyle (run-level attributes) + +```rust +struct TextStyle { + // --- Font identification --- + font_family: String, // Primary family name + font_size: f32, // In layout-local points. Default: 14.0 + font_weight: u32, // 1..1000, CSS-compatible. Default: 400 + font_width: f32, // CSS font-stretch %. Default: 100.0 + font_style_italic: bool, // Default: false + font_kerning: bool, // OpenType 'kern'. Default: true + font_optical_sizing: FontOpticalSizing, // Default: Auto + + // --- OpenType extensions --- + font_features: Vec, // e.g. [("liga", true), ("ss01", true)] + font_variations: Vec, // e.g. [("wght", 700.0), ("wdth", 75.0)] + + // --- Spacing --- + letter_spacing: TextDimension, // Default: Normal + word_spacing: TextDimension, // Default: Normal + line_height: TextDimension, // Default: Normal (see note below) + + // --- Decoration --- + text_decoration_line: TextDecorationLine, // Default: None + text_decoration_style: TextDecorationStyle, // Default: Solid + text_decoration_color: Option, // None = inherit from fill + text_decoration_skip_ink: bool, // Default: true + text_decoration_thickness: f32, // Default: 1.0 (percentage) + + // --- Transform --- + text_transform: TextTransform, // Default: None + + // --- Fill (text color) --- + fill: TextFill, // Default: solid black + + // --- Link --- + hyperlink: Option, // Default: None +} +``` + +Where `Hyperlink` is: + +```rust +struct Hyperlink { + url: String, + open_in_new_tab: bool, +} +``` + +**Note on `line_height`**: `line_height` is classified as run-level because both Figma and CSS allow it to vary per character range. When all runs share the same `line_height`, the layout engine should use a paragraph-level strut for consistent behavior. When runs differ, per-run heights apply, and the tallest run on each visual line determines that line's height. + +**Note on variable fonts**: `font_weight`, `font_width`, and `font_optical_sizing` are high-level semantic attributes. When the underlying typeface is a variable font, the layout engine maps these to the corresponding variation axes (`wght`, `wdth`, `opsz`). User-specified `font_variations` are applied first; the semantic attributes override matching axes. This two-level approach matches CSS (high-level properties win over `font-variation-settings`). + +This field set is aligned with Figma's per-character overridable properties (`fontSize`, `fontName`, `textCase`, `textDecoration`, `lineHeight`, `letterSpacing`, `fontVariations`, `toggledOnOTFeatures`, `toggledOffOTFeatures`, `hyperlink`, `textDecorationFillPaints`, `textDecorationSkipInk`, `textDecorationThickness`, `textDecorationStyle`) and with the Grida FlatBuffers `TextStyleRec`, extended with `fill` for per-run text color. + +### TextFill + +```rust +enum TextFill { + /// Solid color fill. + Solid(RGBA), + // Future: Gradient, Pattern (matches Figma's Paint[] on text overrides) +} +``` + +### ParagraphStyle + +```rust +struct ParagraphStyle { + text_align: TextAlign, // Default: Left + text_align_vertical: TextAlignVertical, // Default: Top + paragraph_direction: ParagraphDirection, // Default: Ltr + max_lines: Option, // None = unlimited + ellipsis: Option, // None = no truncation indicator + text_indent: f32, // Default: 0.0 + paragraph_spacing: f32, // Default: 0.0 (extra space after \n) +} +``` + +These properties are paragraph-level in all studied systems: + +- Apple: `NSParagraphStyle` on first character of paragraph +- Figma: `textAlignHorizontal`, `textAlignVertical`, `maxLines`, `textTruncation`, `paragraphSpacing` on `NodeChange` (node-level, not per-character) +- Skia: `ParagraphStyle` passed to `ParagraphBuilder::new` (not per-run) + +> **Note**: `TextAutoResize` (NONE / HEIGHT / WIDTH_AND_HEIGHT) is intentionally excluded. It controls how the text **node's bounding box** responds to content — a node-level layout concern, not a text content property. It belongs on the node record (`TextSpanNodeRec`) alongside `width`, `height`, and `transform`, not inside `AttributedText`. + +### Complete model + +```rust +struct AttributedText { + text: String, + default_style: TextStyle, + paragraph_style: ParagraphStyle, + runs: Vec, +} +``` + +### Empty text invariant + +When `text.is_empty()`: + +- `runs` contains exactly one run: `StyledRun { start: 0, end: 0, style: }`. +- This degenerate run preserves the typing style. It is the only case where `start == end` is valid. +- On first character insertion, this run's `end` advances to the inserted text's length. + +This mirrors how Figma handles empty text nodes: the base style on the `NodeChange` persists even when `characters` is empty, preserving the font/size/color the user last selected. + +## Offset model + +All offsets in this model are **UTF-8 byte offsets**, consistent with Rust's native string indexing and the editing engine's cursor model. Conversion to UTF-16 (for Skia interop) happens at the layout boundary. + +The choice of `u32` (not `usize`) for run offsets is deliberate: + +- 4 GiB of text per node is far beyond any realistic design tool text block. +- `u32` halves offset storage cost on 64-bit platforms. +- `u32` matches FlatBuffers' natural integer width. + +Figma uses logical character indices (UTF-16 code unit indices) for its `characterStyleIDs` array. Our run offsets are byte offsets instead, avoiding the impedance mismatch that Figma's model creates with UTF-8 strings. The conversion cost is paid once at the layout boundary rather than on every query. + +## Editing operations + +### Insert text at cursor + +Given insertion at byte offset `pos` with string `s` (length `n` bytes): + +1. Determine the **effective style** for the insertion: + - If a caret style override is active (set by the user toggling a style with no selection), use it. + - Otherwise, use the style of the run containing `pos`. At a run boundary, this is the **downstream** run (the run starting at `pos`). +2. Find the run `r` containing `pos`. +3. Shift `end` of run `r` by `+n`. +4. Shift `start` and `end` of all subsequent runs by `+n`. +5. Update `text` by inserting `s` at `pos`. + +No run splitting occurs. No merging is needed (inserted text has the same style as its context). + +**Boundary semantics**: The data model provides two primitives with different boundary behavior: + +- **`insert(pos, s)`** — extends the downstream run. Used for programmatic insertion, paste, and undo restore. +- **`insert_with_style(pos, s, style)`** — inserts with an explicit style, splitting/merging as needed. Used for interactive typing, where the editor resolves the effective style from the caret style or override. + +The typical flow for typing at a bold→italic boundary is: + +1. User sees caret style = bold (upstream, via `caret_style_at`). +2. User types a character. +3. The editor calls `insert_with_style(pos, ch, effective_caret_style)`. +4. The character is inserted with the bold style. + +This separation keeps the data model free of UI state (no caret override stored in `AttributedText`), while giving the editor full control over boundary behavior. + +**Complexity**: O(k) where k = number of runs after the insertion point. + +### Delete range `[lo, hi)` + +1. Find runs overlapping `[lo, hi)`. +2. For the first overlapping run: clamp its `end` to `lo` (if `start < lo`). +3. For the last overlapping run: clamp its `start` to `lo` (shift by `-(hi - lo)`), adjust `end`. +4. Remove all fully-covered runs between first and last. +5. Shift all subsequent runs by `-(hi - lo)`. +6. Update `text` by draining `[lo, hi)`. +7. Merge adjacent runs if they now have equal styles. + +**Complexity**: O(k) where k = total runs. + +### Apply style to range `[lo, hi)` + +Given a style mutation `f: TextStyle -> TextStyle`: + +1. **Split at boundaries**: if `lo` falls inside a run, split it into `[start, lo)` and `[lo, end)` with the same style. Same for `hi`. +2. **Apply**: for each run fully within `[lo, hi)`, apply `f` to its style. +3. **Merge**: coalesce adjacent runs with equal styles (at most 2 merge points: at `lo` and at `hi`). + +**Complexity**: O(m + log k) where m = affected runs. + +### Split at offset + +``` +split_at(runs, offset) -> () +``` + +If `offset` falls exactly on a run boundary, no-op. Otherwise, find the run containing `offset` and replace it with two runs: `[start, offset)` and `[offset, end)`, both with the same style. + +### Merge adjacent equal runs (coalesce) + +``` +coalesce(runs) -> () +``` + +Linear scan: if `runs[i].style == runs[i+1].style`, merge into `[runs[i].start, runs[i+1].end)` and remove `runs[i+1]`. Called after any operation that might produce adjacent equal-style runs. + +This is Apple's "automatic coalescing" behavior. Android's span model notably lacks this — spans accumulate without cleanup. + +## Querying + +### Style at offset + +``` +style_at(offset: u32) -> &TextStyle +``` + +Binary search on runs (O(log k)). For an offset exactly at a run boundary, returns the style of the run that **starts** at that offset (the "downstream" style). + +**Exception**: when `offset == text.len()`, returns the style of the last run. + +### Caret style at offset + +``` +caret_style_at(offset: u32) -> &TextStyle +``` + +At a run boundary, returns the **upstream** run's style (the run that ends at `offset`). At position 0, returns the first run's style. At end of text, returns the last run's style. Inside a run, returns that run's style. + +### Runs in range + +``` +runs_in_range(lo: u32, hi: u32) -> &[StyledRun] +``` + +Binary search to find the first run overlapping `lo`, then linear scan to `hi`. Returns a slice (borrowing, not cloning). + +### Iteration for layout + +```rust +for run in attributed_text.runs() { + builder.push_style(&run.style.to_skia_text_style()); + builder.add_text(&text[run.start as usize..run.end as usize]); +} +``` + +This is the exact sequence Skia `ParagraphBuilder` expects. No intermediate representation needed. This is also how Flutter's `TextSpan.build()` flattens its tree — but we start in the flat form, so no flattening is required. + +## IME composition interaction + +When the user enters an IME composition (e.g., CJK input, dead keys), the composition text is typically displayed as a "preedit" overlay before being committed. The interaction with style runs follows these rules: + +1. **Preedit text inherits the caret style**. The composition range uses the same style that a normal character insertion would use — the effective caret style (upstream run, or explicit override). + +2. **Composition is in-band**. Following the convention established in the text editing manifesto, composition text is part of `text` (not a separate overlay buffer). The composition range is tracked by the editor state, not by the attributed text model. Style runs treat preedit characters identically to committed characters. + +3. **On commit**, the preedit text becomes permanent. No run adjustment is needed (the text was already inserted with the correct style). + +4. **On cancel**, the preedit text is deleted via `delete()`. Runs adjust normally. + +5. **Composition underline** is a rendering concern, not a run-level attribute. The host draws the composition underline based on the composition range, independent of `text_decoration_line`. + +6. **Composition must not split grapheme clusters**. An IME commit may replace a composition range that spans multiple runs. The replacement uses `replace()`, which inherits the style at the start of the replaced range. + +## FlatBuffers serialization + +The model maps naturally to FlatBuffers: + +```fbs +enum ParagraphDirection : ubyte { + Ltr = 0, + Rtl = 1, + Auto = 2 +} + +table HyperlinkRec { + url: string (id: 0); + open_in_new_tab: bool = false (id: 1); +} + +table AttributedTextRun { + /// Byte offset (UTF-8) where this run starts. + start: uint32 (id: 0); + /// Byte offset (UTF-8) where this run ends (exclusive). + end: uint32 (id: 1); + /// Style for this run. If null, same as parent's default_style. + style: TextStyleRec (id: 2); +} + +table ParagraphStyleRec { + text_align: TextAlign = Left (id: 0); + text_align_vertical: TextAlignVertical = Top (id: 1); + paragraph_direction: ParagraphDirection = Ltr (id: 2); + max_lines: uint32 (id: 3); + ellipsis: string (id: 4); + text_indent: float = 0.0 (id: 5); + paragraph_spacing: float = 0.0 (id: 6); +} + +table AttributedText { + /// The backing text string. Newlines normalized to LF. + text: string (required, id: 0); + /// Default style (base for delta encoding). + default_style: TextStyleRec (required, id: 1); + /// Paragraph-level style. + paragraph_style: ParagraphStyleRec (id: 2); + /// Ordered runs. If empty, the entire text uses default_style. + runs: [AttributedTextRun] (id: 3); +} +``` + +**Delta encoding**: when a run's `style` is `null`, it inherits `default_style`. This is the FlatBuffers equivalent of Figma's `characterStyleIDs[i] == 0` meaning "use base." For single-style text (the common case), `runs` can be empty — the entire text uses `default_style`. + +**Comparison with Figma's wire format**: Figma stores `characterStyleIDs: uint[]` (one integer per character) + `styleOverrideTable: NodeChange[]` (one message per distinct override). Our format stores `runs: [AttributedTextRun]` (one message per style transition). For typical design text (3 style changes in 100 characters), our format is more compact: 3 runs vs 100 integers. Figma's format is more compact only when every character has a unique style (unlikely in practice). + +## Interaction with the editing engine + +The attributed text model is designed to **layer on top of** a plain-text editing engine, not replace it. The plain-text engine handles cursor movement, selection, word/line boundaries, IME composition, and undo/redo. The attributed text model handles styling. + +The integration follows a two-layer architecture: + +``` +RichTextEditorState + content: AttributedText // text + runs + cursor: usize // caret position (UTF-8 byte offset) + anchor: Option // selection anchor + caret_style_override: Option // transient, cleared on move +``` + +The editing flow: + +1. The plain-text engine processes a command (insert, delete, move, select) and produces the new text + cursor state. +2. The rich text layer diffs the old and new text to determine what was inserted or deleted, then applies the corresponding `insert_with_style` / `delete` on the attributed text model. +3. For insertion, the effective style is the caret style override (if set) or the caret style at the old cursor position. +4. After text mutations, the caret style override is cleared. + +This layered design means the editing engine remains pure, testable, and decoupled from styling — matching Flutter's separation of `TextEditingValue` (plain text editing) from `TextSpan` (styled rendering). + +### Style commands + +Style commands (bold, italic, underline, font size, etc.) are orthogonal to text editing commands. They operate directly on `AttributedText`: + +- **With selection**: `apply_style(lo, hi, mutation)` — splits runs at boundaries, applies the mutation, coalesces. +- **Without selection**: sets the `caret_style_override` — the next typed character will use this style. The override is cleared on any cursor movement. + +This separation means style commands never pass through the plain-text engine. They are a direct manipulation of the content model. + +## Complexity analysis + +| Operation | Time | Notes | +| --------------------- | ------------ | -------------------------------------- | +| Insert at cursor | O(k) | k = runs after cursor; shift offsets | +| Delete range | O(k) | k = total runs; shift + possible merge | +| Apply style to range | O(m + log k) | m = affected runs, log k for lookup | +| Style at offset | O(log k) | Binary search | +| Caret style at offset | O(log k) | Binary search + boundary check | +| Runs in range | O(log k + m) | m = matching runs | +| Layout iteration | O(k) | Linear scan, k = total runs | +| Coalesce | O(k) | Linear scan after mutation | + +Where k = total number of runs. For typical design tool text (< 100 runs), all operations are effectively O(1). + +Contrast with Figma's O(1) per-character lookup (direct array index). Our O(log k) is marginally slower in theory but k is small enough that it doesn't matter in practice, and we save the O(n) memory cost of the per-character array. + +## Memory layout + +For a text block with `k` runs: + +``` +AttributedText: + text: 24 bytes (String: ptr + len + cap) + default_style: ~120 bytes (fixed fields + vecs) + paragraph_style: ~48 bytes + runs: 24 + k * sizeof(StyledRun) bytes + +StyledRun: + start: 4 bytes (u32) + end: 4 bytes (u32) + style: ~120 bytes (TextStyle) + ─────────────── + total: ~128 bytes per run +``` + +For a typical text block with 5 runs: ~24 + 120 + 48 + 24 + 640 = ~856 bytes. This is compact enough for thousands of text nodes in a design document. + +**Comparison with Figma**: For a 500-character paragraph with 5 style changes, Figma stores 500 _ 4 = 2000 bytes for `characterStyleIDs` alone, plus the override table. Our model stores 5 _ 128 = 640 bytes for runs, plus the default style. The run-based model uses ~3x less memory for this typical case. + +**Future optimization**: Style interning (`Arc`) can reduce per-run cost to ~16 bytes when many runs share the same style. This is an implementation optimization, not a model change. + +## Invariant enforcement + +The model exposes mutation only through methods that maintain invariants. Direct field access is restricted to reads. The key methods form a closed algebra: + +``` +fn insert(&mut self, pos: usize, s: &str) +fn delete(&mut self, lo: usize, hi: usize) +fn apply_style(&mut self, lo: usize, hi: usize, f: impl Fn(&mut TextStyle)) +fn set_style(&mut self, lo: usize, hi: usize, style: TextStyle) +fn coalesce(&mut self) // internal, called after every mutation +``` + +Every public method must leave the runs in a state satisfying all 7 invariants. `coalesce` is idempotent and can be called defensively. + +### Debug assertions + +In debug builds, every public mutation should conclude with a full invariant check (non-empty run list, coverage, contiguity, non-degenerate runs, maximality, boundary alignment, and monotonicity). This makes invariant violations fail loudly at the point of mutation, not at a later layout or serialization step. + +## Relationship to existing Grida types + +| This model | Existing type | Relationship | +| ---------------- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------ | +| `TextStyle` | `TextStyleRec` (Grida canvas) | Structurally equivalent + `fill` field. Lossless conversion. | +| `TextStyle` | `TextStyleRec` (FlatBuffers schema) | Maps to the same schema + `fill` extension. | +| `ParagraphStyle` | Node-level text fields (text_align, etc.) | These paragraph-level fields are currently scattered on the node record. `ParagraphStyle` groups them. | +| `AttributedText` | Node text + uniform style | Replaces the plain string + single style with a structured model. | +| `StyledRun` | Figma per-character style IDs + override table | Our run represents a resolved range; Figma's model is per-character with deferred resolution. | + +## Testing strategy + +1. **Invariant tests**: After every operation (insert, delete, apply_style), assert all 7 run invariants. +2. **Round-trip tests**: Serialization -> deserialization preserves all data. +3. **Editing integration tests**: Run the full editing command suite through the rich text editor state, verifying that runs shift correctly for every command. +4. **Edge cases**: + - Insert at run boundary (no split needed). + - Insert at end of text with a different style (must not corrupt offsets). + - Delete spanning multiple runs (merge survivors). + - Apply style to exact run boundaries (no split needed). + - Apply style creating identical adjacent runs (must merge). + - Empty text: caret style preservation. + - Single-character runs (emoji grapheme clusters). + - Style application to zero-width range (no-op). + - Multibyte characters: style boundaries must align to char boundaries. +5. **Property tests**: For random sequences of (insert, delete, apply_style), invariants always hold. +6. **Import tests**: Round-trip from per-character ID representations (e.g. Figma `TextData`) to run-based attributed text and verify structural equivalence. + +## Phased roadmap + +### Phase 0: Core data model + +The attributed text struct with invariant-enforcing mutation methods, the complete `TextStyle` field set, and comprehensive tests for all operations and invariants. + +### Phase 1: Layout integration + +The layout engine accepts attributed text and pushes per-run styles to the paragraph builder. Variable font axis interpolation (`wght`, `wdth`, `opsz`) is performed via font arguments on each text style, so that a single registered variable font typeface renders at any requested weight, width, or optical size. Font features and decorations are applied per run. + +### Phase 2: Editor integration + +The rich text editor state wraps attributed text with cursor, selection, and caret style override. The plain-text editing engine is reused without modification; a diff-based synchronization layer keeps the run model in sync with text mutations. Style commands (bold, italic, underline, font size) operate directly on the content model: + +- **Bold**: toggles `font_weight` 400 / 700. +- **Italic**: toggles `font_style_italic` — the layout engine selects the real italic typeface when available. +- **Underline**: toggles `text_decoration_line`. +- **Font size**: increments/decrements `font_size` with a configurable step (clamped to a minimum). Mixed sizes within a paragraph produce correct per-line metrics. + +### Phase 3: Serialization + +FlatBuffers schema evolution with the types defined in this document. Rust and TypeScript codecs for round-trip serialization. Migration path from uniform-style text nodes (single-run attributed text). Figma import path via run-length encoding of per-character style ID arrays. + +## References + +### Platform references + +- Apple `NSAttributedString`: https://developer.apple.com/documentation/foundation/nsattributedstring +- Apple `NSTextStorage` (attribute fixing): https://developer.apple.com/documentation/uikit/nstextstorage +- Android `SpannableStringBuilder`: https://developer.android.com/reference/android/text/SpannableStringBuilder +- Android span flags: https://developer.android.com/reference/android/text/Spanned +- Flutter `TextSpan`: https://api.flutter.dev/flutter/painting/TextSpan-class.html +- Flutter `ParagraphBuilder`: https://api.flutter.dev/flutter/dart-ui/ParagraphBuilder-class.html + +### Layout engine references + +- Skia `ParagraphBuilder::pushStyle/addText`: https://api.skia.org/classskia_1_1textlayout_1_1ParagraphBuilder.html +- Skia `TextStyle`: https://api.skia.org/classskia_1_1textlayout_1_1TextStyle.html + +### Format and schema references + +- FlatBuffers schema evolution: https://flatbuffers.dev/flatbuffers_guide_writing_schema.html +- Unicode Text Segmentation (UAX#29): https://www.unicode.org/reports/tr29/ + +### Internal references + +- Text Editing Manifesto: `docs/wg/feat-text-editing/index.md` +- Paragraph Roadmap: `docs/wg/feat-paragraph/index.md` +- FlatBuffers Schema: `format/grida.fbs` +- Figma kiwi schema reference: `.ref/figma/fig.kiwi` From 70e26fa5f0c1196bc2628366d2a1a072e04f4ff3 Mon Sep 17 00:00:00 2001 From: Universe Date: Tue, 3 Mar 2026 19:43:07 +0900 Subject: [PATCH 02/15] refactor: enhance rich text editing capabilities and history management - Updated the `TextEditor` to support rich text editing with snapshots that capture both text and style states for undo/redo functionality. - Introduced `GenericEditHistory` to manage a more flexible history system, allowing for style-only changes and improved state management. - Enhanced clipboard operations to preserve formatting during copy/paste actions, improving user experience. - Removed the deprecated `AttributedText` module, streamlining the codebase for better maintainability. This update aims to provide a more robust and user-friendly rich text editing experience in the grida-text-edit project. --- .../examples/wd_text_editor.rs | 77 +++++++++++-------- crates/grida-text-edit/src/attributed_text.rs | 43 +++++++++++ crates/grida-text-edit/src/history.rs | 45 +++++++---- crates/grida-text-edit/src/lib.rs | 2 +- 4 files changed, 121 insertions(+), 46 deletions(-) diff --git a/crates/grida-text-edit/examples/wd_text_editor.rs b/crates/grida-text-edit/examples/wd_text_editor.rs index 1d0dc22251..39d69abbe9 100644 --- a/crates/grida-text-edit/examples/wd_text_editor.rs +++ b/crates/grida-text-edit/examples/wd_text_editor.rs @@ -66,6 +66,8 @@ // [x] Undo / redo (Cmd/Ctrl+Z / Cmd/Ctrl+Shift+Z) // Snapshot-based history with merge: consecutive typing, backspace, or // delete are grouped; paste, newline, and IME commit are discrete steps. +// Snapshots capture both text and style runs, so undo/redo restores +// formatting changes (bold, italic, underline, font size) as well. // // Rich text (per-run styling via AttributedText) // [x] Cmd/Ctrl+B toggle bold (variable font wght axis) @@ -116,7 +118,7 @@ use winit::{ use grida_text_edit::{ apply_command, utf8_to_utf16_offset, - EditHistory, EditKind, EditingCommand, + EditKind, EditingCommand, GenericEditHistory, SkiaLayoutEngine, TextEditorState, TextLayoutEngine, attributed_text::{ AttributedText, TextStyle as AttrTextStyle, @@ -328,6 +330,13 @@ fn skia_line_index_for_u16_offset( // TextEditor – thin shell: pure editing state + Skia layout + UI state // --------------------------------------------------------------------------- +/// Snapshot capturing both editor state and attributed text for undo/redo. +#[derive(Clone)] +struct RichTextSnapshot { + state: TextEditorState, + content: AttributedText, +} + struct TextEditor { /// Pure editing state: text, cursor, anchor. pub state: TextEditorState, @@ -352,8 +361,8 @@ struct TextEditor { empty_line_policy: EmptyLineSelectionPolicy, - /// Undo / redo history (snapshot-based). - history: EditHistory, + /// Undo / redo history capturing both text and style state. + history: GenericEditHistory, } impl TextEditor { @@ -372,17 +381,33 @@ impl TextEditor { drag_anchor_utf8: None, preedit: None, empty_line_policy: config.empty_line_policy, - history: EditHistory::new(), + history: GenericEditHistory::new(), + } + } + + /// Capture the current state + content as a snapshot for history. + fn snapshot(&self) -> RichTextSnapshot { + RichTextSnapshot { + state: self.state.clone(), + content: self.content.clone(), } } + /// Restore from a snapshot. + fn restore(&mut self, snap: RichTextSnapshot) { + self.state = snap.state; + self.content = snap.content; + self.caret_style_override = None; + self.layout.invalidate(); + } + // ----------------------------------------------------------------------- - // Core: apply an editing command + // Core: apply an editing command (text mutation) // ----------------------------------------------------------------------- fn apply(&mut self, cmd: EditingCommand) { if let Some(kind) = cmd.edit_kind() { - self.history.push(&self.state, kind); + self.history.push(&self.snapshot(), kind); } let old_cursor = self.state.cursor; let old_text = self.state.text.clone(); @@ -392,7 +417,7 @@ impl TextEditor { } fn apply_with_kind(&mut self, cmd: EditingCommand, kind: EditKind) { - self.history.push(&self.state, kind); + self.history.push(&self.snapshot(), kind); let old_cursor = self.state.cursor; let old_text = self.state.text.clone(); self.state = apply_command(&self.state, cmd, &mut self.layout); @@ -416,25 +441,15 @@ impl TextEditor { let insert_style = self.caret_style_override.clone() .unwrap_or_else(|| self.content.caret_style_at(old_cursor as u32).clone()); - // Reconstruct content from the new text, preserving existing runs - // where possible. The simplest correct approach: rebuild from scratch - // with the new text and re-apply styles from the old content at - // corresponding positions. But for typical edits (insert/delete at - // cursor), we can do better with a diff. - // - // For now, use a practical approach: - // 1. The old content covers old_text. - // 2. Compute the common prefix and suffix to find the edit span. let old_len = old_text.len(); let new_len = new_text.len(); - // Find common prefix length (byte level, but snap to char boundaries). + // Find common prefix length (byte level, snap to char boundaries). let mut prefix = 0; for (a, b) in old_text.bytes().zip(new_text.bytes()) { if a != b { break; } prefix += 1; } - // Snap to char boundary while prefix > 0 && !old_text.is_char_boundary(prefix) { prefix -= 1; } @@ -462,11 +477,9 @@ impl TextEditor { let new_end = new_len - suffix; if old_end > prefix { - // Text was deleted in [prefix, old_end). self.content.delete(prefix, old_end); } if new_end > prefix { - // Text was inserted at prefix. let inserted = &new_text[prefix..new_end]; self.content.insert_with_style(prefix, inserted, insert_style); } @@ -475,12 +488,13 @@ impl TextEditor { self.caret_style_override = None; } + // ----------------------------------------------------------------------- + // Undo / redo — restores full snapshot (text + styles) + // ----------------------------------------------------------------------- + fn undo(&mut self) -> bool { - if let Some(prev) = self.history.undo(&self.state) { - let old_text = self.state.text.clone(); - let old_cursor = self.state.cursor; - self.state = prev; - self.sync_content_after_edit(&old_text, old_cursor); + if let Some(prev) = self.history.undo(&self.snapshot()) { + self.restore(prev); self.reset_blink(); true } else { @@ -489,11 +503,8 @@ impl TextEditor { } fn redo(&mut self) -> bool { - if let Some(next) = self.history.redo(&self.state) { - let old_text = self.state.text.clone(); - let old_cursor = self.state.cursor; - self.state = next; - self.sync_content_after_edit(&old_text, old_cursor); + if let Some(next) = self.history.redo(&self.snapshot()) { + self.restore(next); self.reset_blink(); true } else { @@ -507,13 +518,12 @@ impl TextEditor { fn toggle_bold(&mut self) { if let Some((lo, hi)) = self.selection_range() { - // Check if the first character in the selection is already bold. + self.history.push(&self.snapshot(), EditKind::Style); let is_bold = self.content.style_at(lo as u32).font_weight >= 700; let new_weight = if is_bold { 400 } else { 700 }; self.content.apply_style(lo, hi, |s| { s.font_weight = new_weight; }); self.layout.invalidate(); } else { - // No selection — toggle the caret style override. let current = self.caret_style_override.clone() .unwrap_or_else(|| self.content.caret_style_at(self.state.cursor as u32).clone()); let mut new_style = current; @@ -524,6 +534,7 @@ impl TextEditor { fn toggle_italic(&mut self) { if let Some((lo, hi)) = self.selection_range() { + self.history.push(&self.snapshot(), EditKind::Style); let is_italic = self.content.style_at(lo as u32).font_style_italic; self.content.apply_style(lo, hi, |s| { s.font_style_italic = !is_italic; }); self.layout.invalidate(); @@ -538,6 +549,7 @@ impl TextEditor { fn toggle_underline(&mut self) { if let Some((lo, hi)) = self.selection_range() { + self.history.push(&self.snapshot(), EditKind::Style); let is_underline = self.content.style_at(lo as u32).text_decoration_line == TextDecorationLine::Underline; let new_deco = if is_underline { TextDecorationLine::None } else { TextDecorationLine::Underline }; @@ -569,6 +581,7 @@ impl TextEditor { fn adjust_font_size(&mut self, delta: f32) { const MIN_FONT_SIZE: f32 = 1.0; if let Some((lo, hi)) = self.selection_range() { + self.history.push(&self.snapshot(), EditKind::Style); self.content.apply_style(lo, hi, |s| { s.font_size = (s.font_size + delta).max(MIN_FONT_SIZE); }); diff --git a/crates/grida-text-edit/src/attributed_text.rs b/crates/grida-text-edit/src/attributed_text.rs index 377e813b36..e8a3b5e911 100644 --- a/crates/grida-text-edit/src/attributed_text.rs +++ b/crates/grida-text-edit/src/attributed_text.rs @@ -1721,4 +1721,47 @@ mod tests { // "Hello" bold + ", World!" normal assert_eq!(at.runs().len(), 2); } + + // ----------------------------------------------------------------------- + // Clone / snapshot round-trip (verifies history can snapshot and restore) + // ----------------------------------------------------------------------- + + #[test] + fn clone_preserves_full_state() { + let mut at = AttributedText::new("Hello World", default_style()); + at.apply_style(0, 5, |s| { s.font_weight = 700; }); + at.apply_style(6, 11, |s| { s.font_style_italic = true; }); + + // Clone (simulates history snapshot). + let snapshot = at.clone(); + + // Mutate the original. + at.apply_style(0, 11, |s| { s.font_weight = 400; s.font_style_italic = false; }); + assert_eq!(at.runs().len(), 1); // everything uniform now + + // Snapshot is untouched: [0,5) bold, [5,6) default, [6,11) italic. + assert_eq!(snapshot.runs().len(), 3); + assert_eq!(snapshot.runs()[0].style.font_weight, 700); + assert!(snapshot.runs()[2].style.font_style_italic); + } + + #[test] + fn clone_restore_round_trip() { + let mut at = AttributedText::new("ABCDE", default_style()); + at.apply_style(2, 4, |s| { s.font_weight = 700; }); + + // Snapshot before style change. + let before = at.clone(); + + // Apply a style change. + at.apply_style(0, 5, |s| { s.font_style_italic = true; }); + assert!(at.runs()[0].style.font_style_italic); + + // "Undo" by restoring the snapshot. + at = before; + assert_eq!(at.runs().len(), 3); + assert!(!at.runs()[0].style.font_style_italic); + assert_eq!(at.runs()[1].style.font_weight, 700); + assert!(at.check_invariants().is_ok()); + } } diff --git a/crates/grida-text-edit/src/history.rs b/crates/grida-text-edit/src/history.rs index 59e8851fd9..c36cc4a462 100644 --- a/crates/grida-text-edit/src/history.rs +++ b/crates/grida-text-edit/src/history.rs @@ -17,6 +17,9 @@ pub enum EditKind { Paste, ImeCommit, Newline, + /// A style-only change (bold, italic, font size, etc.). + /// Never merges with text-editing kinds. + Style, } impl EditKind { @@ -26,27 +29,36 @@ impl EditKind { } // --------------------------------------------------------------------------- -// HistoryEntry +// HistoryEntry // --------------------------------------------------------------------------- -struct HistoryEntry { - state: TextEditorState, +struct HistoryEntry { + state: S, kind: EditKind, timestamp: Instant, } // --------------------------------------------------------------------------- -// EditHistory +// GenericEditHistory – snapshot-based undo/redo, generic over state type // --------------------------------------------------------------------------- -pub struct EditHistory { - undo_stack: Vec, - redo_stack: Vec, +/// A snapshot-based undo/redo stack, generic over the state type `S`. +/// +/// The history stores snapshots of the state **before** each edit. +/// Consecutive edits of the same mergeable kind within the merge timeout +/// are grouped into a single undo step. +/// +/// `S` must implement `Clone` so snapshots can be captured. For plain-text +/// editing, `S = TextEditorState`. For rich-text editing, `S` should include +/// both the editor state and the attributed text content. +pub struct GenericEditHistory { + undo_stack: Vec>, + redo_stack: Vec>, max_entries: usize, merge_timeout: Duration, } -impl EditHistory { +impl GenericEditHistory { pub fn new() -> Self { Self { undo_stack: Vec::new(), @@ -77,7 +89,7 @@ impl EditHistory { /// state before the entire merged run). /// /// Any pending redo stack is cleared on push. - pub fn push(&mut self, state_before: &TextEditorState, kind: EditKind) { + pub fn push(&mut self, state_before: &S, kind: EditKind) { if kind.is_mergeable() { if let Some(top) = self.undo_stack.last_mut() { if top.kind == kind && top.timestamp.elapsed() < self.merge_timeout { @@ -102,7 +114,7 @@ impl EditHistory { } /// Undo: saves `current` onto the redo stack and returns the previous state. - pub fn undo(&mut self, current: &TextEditorState) -> Option { + pub fn undo(&mut self, current: &S) -> Option { let entry = self.undo_stack.pop()?; self.redo_stack.push(HistoryEntry { state: current.clone(), @@ -113,7 +125,7 @@ impl EditHistory { } /// Redo: saves `current` onto the undo stack and returns the next state. - pub fn redo(&mut self, current: &TextEditorState) -> Option { + pub fn redo(&mut self, current: &S) -> Option { let entry = self.redo_stack.pop()?; self.undo_stack.push(HistoryEntry { state: current.clone(), @@ -124,18 +136,25 @@ impl EditHistory { } } -impl Default for EditHistory { +impl Default for GenericEditHistory { fn default() -> Self { Self::new() } } +// --------------------------------------------------------------------------- +// EditHistory – the plain-text specialization (backward compatible) +// --------------------------------------------------------------------------- + +/// Plain-text edit history. Type alias preserving backward compatibility. +pub type EditHistory = GenericEditHistory; + // --------------------------------------------------------------------------- // Test-only helpers // --------------------------------------------------------------------------- #[cfg(test)] -impl EditHistory { +impl GenericEditHistory { /// Create a history with a custom merge timeout (useful for testing). pub fn with_merge_timeout(timeout: Duration) -> Self { Self { diff --git a/crates/grida-text-edit/src/lib.rs b/crates/grida-text-edit/src/lib.rs index 799d98f524..afa479e519 100644 --- a/crates/grida-text-edit/src/lib.rs +++ b/crates/grida-text-edit/src/lib.rs @@ -8,7 +8,7 @@ pub mod skia_layout; #[cfg(test)] mod tests; -pub use history::{EditHistory, EditKind}; +pub use history::{EditHistory, EditKind, GenericEditHistory}; pub use layout::{line_index_for_offset, CaretRect, LineMetrics, SelectionRect, TextLayoutEngine}; pub use simple_layout::SimpleLayoutEngine; #[cfg(feature = "skia")] From 8f70ff688972de5d7b8cdbfbd70e002454871dc2 Mon Sep 17 00:00:00 2001 From: Universe Date: Tue, 3 Mar 2026 20:29:10 +0900 Subject: [PATCH 03/15] feat: add HTML serialization and deserialization for attributed text - Introduced `html.rs` to handle serialization of styled text to HTML and deserialization from HTML back to styled text. - Implemented support for inline styles and basic HTML tags such as ``, ``, ``, and ``, enhancing rich text editing capabilities. - Updated the `AttributedText` module to facilitate rich text operations with HTML, improving clipboard functionality and user experience. This update aims to provide a seamless integration of HTML with the rich text editing features in the grida-text-edit project. --- .../examples/wd_text_editor.rs | 99 ++- .../src/attributed_text/html.rs | 651 ++++++++++++++++++ .../mod.rs} | 2 + 3 files changed, 744 insertions(+), 8 deletions(-) create mode 100644 crates/grida-text-edit/src/attributed_text/html.rs rename crates/grida-text-edit/src/{attributed_text.rs => attributed_text/mod.rs} (99%) diff --git a/crates/grida-text-edit/examples/wd_text_editor.rs b/crates/grida-text-edit/examples/wd_text_editor.rs index 39d69abbe9..799729b0cc 100644 --- a/crates/grida-text-edit/examples/wd_text_editor.rs +++ b/crates/grida-text-edit/examples/wd_text_editor.rs @@ -7,6 +7,7 @@ //! Cmd/Ctrl+B toggle bold //! Cmd/Ctrl+I toggle italic //! Cmd/Ctrl+U toggle underline +//! Cmd/Ctrl+Shift+X toggle strikethrough //! Cmd/Ctrl+Shift+> increase font size (+1 pt) //! Cmd/Ctrl+Shift+< decrease font size (-1 pt) @@ -48,9 +49,12 @@ // [x] Cmd+A select all // // Clipboard -// [x] Cmd/Ctrl+C copy selection -// [x] Cmd/Ctrl+X cut selection -// [x] Cmd/Ctrl+V paste +// [x] Cmd/Ctrl+C copy selection (HTML + plain text fallback) +// [x] Cmd/Ctrl+X cut selection (HTML + plain text fallback) +// [x] Cmd/Ctrl+V paste (HTML with formatting, or plain text fallback) +// Copy/paste preserves bold, italic, underline, font size, color. +// Cross-app: paste from Chrome/Word/Figma imports formatting; +// copy from here pastes with formatting into other apps. // // Rendering // [x] Multiline text with wrapping @@ -73,6 +77,7 @@ // [x] Cmd/Ctrl+B toggle bold (variable font wght axis) // [x] Cmd/Ctrl+I toggle italic (real italic typeface) // [x] Cmd/Ctrl+U toggle underline +// [x] Cmd/Ctrl+Shift+X toggle strikethrough // [x] Cmd/Ctrl+Shift+> increase font size (+1 pt, min 1) // [x] Cmd/Ctrl+Shift+< decrease font size (-1 pt, min 1) // [x] Caret style override (toggle with no selection sets typing style) @@ -123,6 +128,7 @@ use grida_text_edit::{ attributed_text::{ AttributedText, TextStyle as AttrTextStyle, TextDecorationLine, + html::{runs_to_html, html_to_attributed_text}, }, }; @@ -568,6 +574,27 @@ impl TextEditor { } } + fn toggle_strikethrough(&mut self) { + if let Some((lo, hi)) = self.selection_range() { + self.history.push(&self.snapshot(), EditKind::Style); + let is_strike = self.content.style_at(lo as u32).text_decoration_line + == TextDecorationLine::LineThrough; + let new_deco = if is_strike { TextDecorationLine::None } else { TextDecorationLine::LineThrough }; + self.content.apply_style(lo, hi, |s| { s.text_decoration_line = new_deco; }); + self.layout.invalidate(); + } else { + let current = self.caret_style_override.clone() + .unwrap_or_else(|| self.content.caret_style_at(self.state.cursor as u32).clone()); + let mut new_style = current; + new_style.text_decoration_line = if new_style.text_decoration_line == TextDecorationLine::LineThrough { + TextDecorationLine::None + } else { + TextDecorationLine::LineThrough + }; + self.caret_style_override = Some(new_style); + } + } + /// Increase font size by `delta` (clamped to >= 1.0). fn increase_font_size(&mut self, delta: f32) { self.adjust_font_size(delta); @@ -595,6 +622,46 @@ impl TextEditor { } } + // ----------------------------------------------------------------------- + // Rich paste: insert an AttributedText (from HTML clipboard) + // ----------------------------------------------------------------------- + + fn paste_attributed(&mut self, pasted: &AttributedText) { + if pasted.is_empty() { + return; + } + self.history.push(&self.snapshot(), EditKind::Paste); + + // Delete selection if any. + if let Some((lo, hi)) = self.selection_range() { + self.content.delete(lo, hi); + self.state.text = self.content.text().to_owned(); + self.state.cursor = lo; + self.state.anchor = None; + } + + let pos = self.state.cursor; + + // Insert each run from the pasted content with its own style. + for run in pasted.runs() { + let start = run.start as usize; + let end = run.end as usize; + if start >= end || end > pasted.text().len() { + continue; + } + let slice = &pasted.text()[start..end]; + let insert_at = pos + start; // offset within the growing text + self.content.insert_with_style(insert_at, slice, run.style.clone()); + } + + self.state.text = self.content.text().to_owned(); + self.state.cursor = pos + pasted.text().len(); + self.state.anchor = None; + self.caret_style_override = None; + self.layout.invalidate(); + self.reset_blink(); + } + // ----------------------------------------------------------------------- // Convenience wrappers (called from event handler) // ----------------------------------------------------------------------- @@ -1397,13 +1464,23 @@ impl ApplicationHandler for TextEditorApp { } } PhysicalKey::Code(KeyCode::KeyC) => { - if let Some(sel) = inner.editor.selected_text() { - let _ = self.clipboard.set_text(sel.to_string()); + if let Some((lo, hi)) = inner.editor.selection_range() { + let plain = inner.editor.selected_text().unwrap_or("").to_string(); + let html = runs_to_html(&inner.editor.content, lo, hi); + let _ = self.clipboard.set_html(&html, Some(&plain)); } } + PhysicalKey::Code(KeyCode::KeyX) if shift => { + // Cmd+Shift+X — toggle strikethrough + inner.editor.toggle_strikethrough(); + inner.window.request_redraw(); + } PhysicalKey::Code(KeyCode::KeyX) => { - if let Some(sel) = inner.editor.selected_text() { - let _ = self.clipboard.set_text(sel.to_string()); + // Cmd+X — cut + if let Some((lo, hi)) = inner.editor.selection_range() { + let plain = inner.editor.selected_text().unwrap_or("").to_string(); + let html = runs_to_html(&inner.editor.content, lo, hi); + let _ = self.clipboard.set_html(&html, Some(&plain)); } if inner.editor.has_selection() { inner.editor.apply(EditingCommand::Delete); @@ -1411,7 +1488,13 @@ impl ApplicationHandler for TextEditorApp { } } PhysicalKey::Code(KeyCode::KeyV) => { - if let Ok(text) = self.clipboard.get_text() { + // Try HTML first (preserves formatting), fall back to plain text. + if let Ok(html) = self.clipboard.get().html() { + let base = inner.editor.content.default_style().clone(); + let pasted = html_to_attributed_text(&html, base); + inner.editor.paste_attributed(&pasted); + inner.window.request_redraw(); + } else if let Ok(text) = self.clipboard.get_text() { inner.editor.insert_text(&text); inner.window.request_redraw(); } diff --git a/crates/grida-text-edit/src/attributed_text/html.rs b/crates/grida-text-edit/src/attributed_text/html.rs new file mode 100644 index 0000000000..ad0e856bab --- /dev/null +++ b/crates/grida-text-edit/src/attributed_text/html.rs @@ -0,0 +1,651 @@ +//! HTML serialization and deserialization for [`AttributedText`]. +//! +//! **Serialize** (copy): converts a range of styled runs into inline-styled +//! HTML suitable for the system clipboard. +//! +//! **Deserialize** (paste): parses a subset of HTML (inline `` with +//! `style`, plus ``, ``, ``, ``) back into styled runs. +//! +//! The HTML subset is intentionally minimal — it covers bold, italic, +//! underline, strikethrough, font size, font weight, font style, color, +//! and font family. Attributes that HTML cannot express (OpenType features, +//! variable font axes, optical sizing) are silently dropped on serialize +//! and left at defaults on deserialize. + +use super::{ + AttributedText, StyledRun, TextDecorationLine, TextFill, TextStyle, RGBA, +}; + +// --------------------------------------------------------------------------- +// Serialize (AttributedText → HTML) +// --------------------------------------------------------------------------- + +/// Serialize the runs in `[lo, hi)` of an `AttributedText` to inline-styled +/// HTML. Returns the HTML string. +/// +/// Each run becomes a `text`. Attributes that match +/// the `default_style` are omitted to reduce noise. +pub fn runs_to_html(at: &AttributedText, lo: usize, hi: usize) -> String { + let text = at.text(); + let lo = lo.min(text.len()); + let hi = hi.min(text.len()); + if lo >= hi { + return String::new(); + } + + let default = at.default_style(); + let mut html = String::with_capacity((hi - lo) * 2); + + for run in at.runs() { + let rs = run.start as usize; + let re = run.end as usize; + + // Clamp to selection. + let start = rs.max(lo); + let end = re.min(hi); + if start >= end { + continue; + } + + let slice = &text[start..end]; + let style = &run.style; + + let css = style_to_css(style, default); + if css.is_empty() { + // No style differences — emit raw text (HTML-escaped). + html_escape_into(&mut html, slice); + } else { + html.push_str(""); + html_escape_into(&mut html, slice); + html.push_str(""); + } + } + + html +} + +fn style_to_css(style: &TextStyle, default: &TextStyle) -> String { + let mut parts: Vec = Vec::new(); + + if style.font_weight != default.font_weight { + parts.push(format!("font-weight:{}", style.font_weight)); + } + if style.font_style_italic != default.font_style_italic && style.font_style_italic { + parts.push("font-style:italic".into()); + } + if (style.font_size - default.font_size).abs() > f32::EPSILON { + parts.push(format!("font-size:{}px", style.font_size)); + } + if style.font_family != default.font_family { + parts.push(format!("font-family:\"{}\"", style.font_family)); + } + if style.text_decoration_line != default.text_decoration_line { + match style.text_decoration_line { + TextDecorationLine::Underline => parts.push("text-decoration:underline".into()), + TextDecorationLine::LineThrough => parts.push("text-decoration:line-through".into()), + TextDecorationLine::Overline => parts.push("text-decoration:overline".into()), + TextDecorationLine::None => parts.push("text-decoration:none".into()), + } + } + if style.fill != default.fill { + let TextFill::Solid(c) = &style.fill; + parts.push(format!( + "color:rgba({},{},{},{})", + (c.r * 255.0).round() as u8, + (c.g * 255.0).round() as u8, + (c.b * 255.0).round() as u8, + c.a, + )); + } + + parts.join(";") +} + +fn html_escape_into(out: &mut String, s: &str) { + for ch in s.chars() { + match ch { + '&' => out.push_str("&"), + '<' => out.push_str("<"), + '>' => out.push_str(">"), + '"' => out.push_str("""), + '\n' => out.push_str("
"), + _ => out.push(ch), + } + } +} + +// --------------------------------------------------------------------------- +// Deserialize (HTML → AttributedText) +// --------------------------------------------------------------------------- + +/// Parse inline-styled HTML into an `AttributedText` with the given +/// `base_style` as the default. +/// +/// Supports: +/// - `` with CSS properties: `font-weight`, `font-style`, +/// `font-size`, `font-family`, `text-decoration`, `color` +/// - ``, `` → bold +/// - ``, `` → italic +/// - `` → underline +/// - ``, ``, `` → line-through +/// - `
` → newline +/// - Nested tags (style inherits from parent) +/// +/// Unknown tags are ignored (their text content is still captured). +/// HTML entities `&`, `<`, `>`, `"`, `&#NNN;`, `&#xHH;` are +/// decoded. +pub fn html_to_attributed_text(html: &str, base_style: TextStyle) -> AttributedText { + let mut parser = HtmlParser::new(html, base_style.clone()); + parser.parse(); + + if parser.text.is_empty() { + return AttributedText::empty(base_style); + } + + // Build runs from the collected segments. + let mut runs: Vec = Vec::new(); + for seg in &parser.segments { + if seg.start >= seg.end { + continue; + } + if let Some(last) = runs.last_mut() { + if last.style == seg.style && last.end == seg.start { + last.end = seg.end; + continue; + } + } + runs.push(StyledRun { + start: seg.start, + end: seg.end, + style: seg.style.clone(), + }); + } + + if runs.is_empty() { + runs.push(StyledRun { + start: 0, + end: parser.text.len() as u32, + style: base_style.clone(), + }); + } + + AttributedText::from_parts(parser.text, base_style, Default::default(), runs) +} + +// --------------------------------------------------------------------------- +// Minimal HTML parser (no dependencies) +// --------------------------------------------------------------------------- + +struct Segment { + start: u32, + end: u32, + style: TextStyle, +} + +struct HtmlParser { + input: Vec, + pos: usize, + text: String, + segments: Vec, + style_stack: Vec, +} + +impl HtmlParser { + fn new(html: &str, base_style: TextStyle) -> Self { + Self { + input: html.chars().collect(), + pos: 0, + text: String::new(), + segments: Vec::new(), + style_stack: vec![base_style], + } + } + + fn current_style(&self) -> &TextStyle { + self.style_stack.last().unwrap() + } + + fn parse(&mut self) { + while self.pos < self.input.len() { + if self.input[self.pos] == '<' { + self.parse_tag(); + } else if self.input[self.pos] == '&' { + let ch = self.parse_entity(); + self.push_char(ch); + } else { + let ch = self.input[self.pos]; + self.push_char(ch); + self.pos += 1; + } + } + } + + fn push_char(&mut self, ch: char) { + let byte_start = self.text.len() as u32; + self.text.push(ch); + let byte_end = self.text.len() as u32; + let style = self.current_style().clone(); + self.segments.push(Segment { + start: byte_start, + end: byte_end, + style, + }); + } + + fn parse_tag(&mut self) { + debug_assert_eq!(self.input[self.pos], '<'); + self.pos += 1; // skip '<' + + // Closing tag? + let is_closing = self.pos < self.input.len() && self.input[self.pos] == '/'; + if is_closing { + self.pos += 1; + } + + // Tag name + let tag_name = self.read_ident().to_lowercase(); + + // Self-closing or void tags + if tag_name == "br" { + self.skip_to_tag_end(); + self.push_char('\n'); + return; + } + + if is_closing { + self.skip_to_tag_end(); + if self.style_stack.len() > 1 { + self.style_stack.pop(); + } + return; + } + + // Parse attributes (we only care about "style") + let mut style_attr = String::new(); + loop { + self.skip_whitespace(); + if self.pos >= self.input.len() || self.input[self.pos] == '>' || self.input[self.pos] == '/' { + break; + } + let attr_name = self.read_ident().to_lowercase(); + self.skip_whitespace(); + if self.pos < self.input.len() && self.input[self.pos] == '=' { + self.pos += 1; // skip '=' + self.skip_whitespace(); + let val = self.read_attr_value(); + if attr_name == "style" { + style_attr = val; + } + } + } + + self.skip_to_tag_end(); + + // Build new style by inheriting from parent. + let mut new_style = self.current_style().clone(); + + // Apply semantic tags. + match tag_name.as_str() { + "b" | "strong" => new_style.font_weight = 700, + "i" | "em" => new_style.font_style_italic = true, + "u" => new_style.text_decoration_line = TextDecorationLine::Underline, + "s" | "strike" | "del" => { + new_style.text_decoration_line = TextDecorationLine::LineThrough + } + _ => {} + } + + // Apply inline CSS. + if !style_attr.is_empty() { + apply_css_to_style(&mut new_style, &style_attr); + } + + self.style_stack.push(new_style); + } + + fn read_ident(&mut self) -> String { + let mut s = String::new(); + while self.pos < self.input.len() { + let ch = self.input[self.pos]; + if ch.is_alphanumeric() || ch == '-' || ch == '_' { + s.push(ch); + self.pos += 1; + } else { + break; + } + } + s + } + + fn read_attr_value(&mut self) -> String { + if self.pos >= self.input.len() { + return String::new(); + } + let quote = self.input[self.pos]; + if quote == '"' || quote == '\'' { + self.pos += 1; + let mut val = String::new(); + while self.pos < self.input.len() && self.input[self.pos] != quote { + val.push(self.input[self.pos]); + self.pos += 1; + } + if self.pos < self.input.len() { + self.pos += 1; // skip closing quote + } + val + } else { + // Unquoted value + self.read_ident() + } + } + + fn skip_whitespace(&mut self) { + while self.pos < self.input.len() && self.input[self.pos].is_whitespace() { + self.pos += 1; + } + } + + fn skip_to_tag_end(&mut self) { + while self.pos < self.input.len() && self.input[self.pos] != '>' { + self.pos += 1; + } + if self.pos < self.input.len() { + self.pos += 1; // skip '>' + } + } + + fn parse_entity(&mut self) -> char { + debug_assert_eq!(self.input[self.pos], '&'); + self.pos += 1; + + let mut entity = String::new(); + while self.pos < self.input.len() && self.input[self.pos] != ';' { + entity.push(self.input[self.pos]); + self.pos += 1; + } + if self.pos < self.input.len() { + self.pos += 1; // skip ';' + } + + match entity.as_str() { + "amp" => '&', + "lt" => '<', + "gt" => '>', + "quot" => '"', + "apos" => '\'', + "nbsp" => '\u{00A0}', + s if s.starts_with('#') => { + let num_str = &s[1..]; + let code = if num_str.starts_with('x') || num_str.starts_with('X') { + u32::from_str_radix(&num_str[1..], 16).unwrap_or('?' as u32) + } else { + num_str.parse::().unwrap_or('?' as u32) + }; + char::from_u32(code).unwrap_or('?') + } + _ => '?', // Unknown entity + } + } +} + +// --------------------------------------------------------------------------- +// CSS property parsing +// --------------------------------------------------------------------------- + +fn apply_css_to_style(style: &mut TextStyle, css: &str) { + for decl in css.split(';') { + let decl = decl.trim(); + if decl.is_empty() { + continue; + } + let mut parts = decl.splitn(2, ':'); + let prop = parts.next().unwrap_or("").trim().to_lowercase(); + let val = parts.next().unwrap_or("").trim(); + + match prop.as_str() { + "font-weight" => { + if val == "bold" { + style.font_weight = 700; + } else if val == "normal" { + style.font_weight = 400; + } else if let Ok(w) = val.parse::() { + style.font_weight = w.clamp(1, 1000); + } + } + "font-style" => { + style.font_style_italic = val == "italic" || val == "oblique"; + } + "font-size" => { + // Accept "14px", "14pt", or bare "14" + let num_str = val + .trim_end_matches("px") + .trim_end_matches("pt") + .trim_end_matches("em") + .trim(); + if let Ok(size) = num_str.parse::() { + style.font_size = size.max(1.0); + } + } + "font-family" => { + // Take the first family name, strip quotes. + let family = val + .split(',') + .next() + .unwrap_or("") + .trim() + .trim_matches('"') + .trim_matches('\''); + if !family.is_empty() { + style.font_family = family.to_string(); + } + } + "text-decoration" | "text-decoration-line" => { + if val.contains("underline") { + style.text_decoration_line = TextDecorationLine::Underline; + } else if val.contains("line-through") { + style.text_decoration_line = TextDecorationLine::LineThrough; + } else if val.contains("overline") { + style.text_decoration_line = TextDecorationLine::Overline; + } else if val.contains("none") { + style.text_decoration_line = TextDecorationLine::None; + } + } + "color" => { + if let Some(rgba) = parse_css_color(val) { + style.fill = TextFill::Solid(rgba); + } + } + _ => {} // Ignore unknown properties. + } + } +} + +/// Parse a subset of CSS color values: `rgb(r,g,b)`, `rgba(r,g,b,a)`, `#rrggbb`, `#rgb`. +fn parse_css_color(val: &str) -> Option { + let val = val.trim(); + + if val.starts_with("rgba(") && val.ends_with(')') { + let inner = &val[5..val.len() - 1]; + let parts: Vec<&str> = inner.split(',').collect(); + if parts.len() == 4 { + let r = parts[0].trim().parse::().ok()? / 255.0; + let g = parts[1].trim().parse::().ok()? / 255.0; + let b = parts[2].trim().parse::().ok()? / 255.0; + let a = parts[3].trim().parse::().ok()?; + return Some(RGBA { r, g, b, a }); + } + } + + if val.starts_with("rgb(") && val.ends_with(')') { + let inner = &val[4..val.len() - 1]; + let parts: Vec<&str> = inner.split(',').collect(); + if parts.len() == 3 { + let r = parts[0].trim().parse::().ok()? / 255.0; + let g = parts[1].trim().parse::().ok()? / 255.0; + let b = parts[2].trim().parse::().ok()? / 255.0; + return Some(RGBA { r, g, b, a: 1.0 }); + } + } + + if val.starts_with('#') { + let hex = &val[1..]; + if hex.len() == 6 { + let r = u8::from_str_radix(&hex[0..2], 16).ok()? as f32 / 255.0; + let g = u8::from_str_radix(&hex[2..4], 16).ok()? as f32 / 255.0; + let b = u8::from_str_radix(&hex[4..6], 16).ok()? as f32 / 255.0; + return Some(RGBA { r, g, b, a: 1.0 }); + } else if hex.len() == 3 { + let r = u8::from_str_radix(&hex[0..1].repeat(2), 16).ok()? as f32 / 255.0; + let g = u8::from_str_radix(&hex[1..2].repeat(2), 16).ok()? as f32 / 255.0; + let b = u8::from_str_radix(&hex[2..3].repeat(2), 16).ok()? as f32 / 255.0; + return Some(RGBA { r, g, b, a: 1.0 }); + } + } + + None +} + +// --------------------------------------------------------------------------- +// Tests +// --------------------------------------------------------------------------- + +#[cfg(test)] +mod tests { + use super::*; + + fn base() -> TextStyle { + TextStyle::default() + } + + // -- Serialize -- + + #[test] + fn serialize_plain_text() { + let at = AttributedText::new("Hello", base()); + let html = runs_to_html(&at, 0, 5); + assert_eq!(html, "Hello"); + } + + #[test] + fn serialize_bold_run() { + let mut at = AttributedText::new("Hello World", base()); + at.apply_style(0, 5, |s| s.font_weight = 700); + let html = runs_to_html(&at, 0, 11); + assert!(html.contains("font-weight:700")); + assert!(html.contains("Hello")); + assert!(html.contains(" World")); + } + + #[test] + fn serialize_escapes_html() { + let at = AttributedText::new("&test", base()); + let html = runs_to_html(&at, 0, at.len()); + assert!(html.contains("<b>")); + assert!(html.contains("&test")); + } + + #[test] + fn serialize_partial_range() { + let mut at = AttributedText::new("AABBCC", base()); + at.apply_style(2, 4, |s| s.font_weight = 700); + let html = runs_to_html(&at, 1, 5); + // Should include partial runs: "A" normal, "BB" bold, "C" normal + assert!(html.contains("font-weight:700")); + } + + #[test] + fn serialize_newlines_as_br() { + let at = AttributedText::new("A\nB", base()); + let html = runs_to_html(&at, 0, at.len()); + assert!(html.contains("
")); + } + + // -- Deserialize -- + + #[test] + fn deserialize_plain_text() { + let at = html_to_attributed_text("Hello", base()); + assert_eq!(at.text(), "Hello"); + assert_eq!(at.runs().len(), 1); + } + + #[test] + fn deserialize_bold_tag() { + let at = html_to_attributed_text("ABC", base()); + assert_eq!(at.text(), "ABC"); + assert_eq!(at.runs().len(), 3); + assert_eq!(at.runs()[0].style.font_weight, 400); + assert_eq!(at.runs()[1].style.font_weight, 700); + assert_eq!(at.runs()[2].style.font_weight, 400); + } + + #[test] + fn deserialize_italic_tag() { + let at = html_to_attributed_text("italic", base()); + assert_eq!(at.text(), "italic"); + assert!(at.runs()[0].style.font_style_italic); + } + + #[test] + fn deserialize_underline_tag() { + let at = html_to_attributed_text("under", base()); + assert_eq!(at.runs()[0].style.text_decoration_line, TextDecorationLine::Underline); + } + + #[test] + fn deserialize_span_with_style() { + let at = html_to_attributed_text( + r#"red bold"#, + base(), + ); + assert_eq!(at.text(), "red bold"); + assert_eq!(at.runs()[0].style.font_weight, 700); + assert_eq!(at.runs()[0].style.font_size, 24.0); + if let TextFill::Solid(c) = &at.runs()[0].style.fill { + assert!((c.r - 1.0).abs() < 0.01); + assert!(c.g.abs() < 0.01); + } + } + + #[test] + fn deserialize_nested_tags() { + let at = html_to_attributed_text("bold italic", base()); + assert_eq!(at.text(), "bold italic"); + assert_eq!(at.runs()[0].style.font_weight, 700); + assert!(at.runs()[0].style.font_style_italic); + } + + #[test] + fn deserialize_br_tag() { + let at = html_to_attributed_text("A
B", base()); + assert_eq!(at.text(), "A\nB"); + } + + #[test] + fn deserialize_entities() { + let at = html_to_attributed_text("<&>", base()); + assert_eq!(at.text(), "<&>"); + } + + #[test] + fn round_trip() { + let mut original = AttributedText::new("Hello World!", base()); + original.apply_style(0, 5, |s| s.font_weight = 700); + original.apply_style(6, 11, |s| s.font_style_italic = true); + + let html = runs_to_html(&original, 0, original.len()); + let restored = html_to_attributed_text(&html, base()); + + assert_eq!(restored.text(), "Hello World!"); + // Bold "Hello" + assert_eq!(restored.style_at(0).font_weight, 700); + // Normal " " + assert_eq!(restored.style_at(5).font_weight, 400); + // Italic "World" + assert!(restored.style_at(6).font_style_italic); + // Normal "!" + assert!(!restored.style_at(11).font_style_italic); + } +} diff --git a/crates/grida-text-edit/src/attributed_text.rs b/crates/grida-text-edit/src/attributed_text/mod.rs similarity index 99% rename from crates/grida-text-edit/src/attributed_text.rs rename to crates/grida-text-edit/src/attributed_text/mod.rs index e8a3b5e911..4032814e8c 100644 --- a/crates/grida-text-edit/src/attributed_text.rs +++ b/crates/grida-text-edit/src/attributed_text/mod.rs @@ -26,6 +26,8 @@ //! 6. **Boundary alignment** — all offsets are valid UTF-8 char boundaries. //! 7. **Monotonicity** — `runs[i].start < runs[i+1].start` (implied by 3+4). +pub mod html; + use serde::{Deserialize, Serialize}; // --------------------------------------------------------------------------- From b4e4911a514b5cff1df3a00196196d80096030ec Mon Sep 17 00:00:00 2001 From: Universe Date: Tue, 3 Mar 2026 20:34:04 +0900 Subject: [PATCH 04/15] feat: add Inconsolata and Lora variable fonts with documentation - Introduced Inconsolata and Lora as variable fonts, including their respective TTF files for both regular and italic styles. - Added comprehensive README and OFL license files for both fonts, detailing usage instructions and licensing terms. - Updated the fonts table in the README.md to include new entries for Inconsolata and Lora, enhancing the available font options for users. This update aims to expand the font offerings in the project, providing more flexibility and options for typography in web applications. --- .../Inconsolata-VariableFont_wdth,wght.ttf | Bin 0 -> 343368 bytes fixtures/fonts/Inconsolata/OFL.txt | 93 ++++++++++++ fixtures/fonts/Inconsolata/README.txt | 135 ++++++++++++++++++ .../Lora/Lora-Italic-VariableFont_wght.ttf | Bin 0 -> 219120 bytes .../fonts/Lora/Lora-VariableFont_wght.ttf | Bin 0 -> 210884 bytes fixtures/fonts/Lora/OFL.txt | 93 ++++++++++++ fixtures/fonts/Lora/README.txt | 71 +++++++++ fixtures/fonts/README.md | 18 ++- 8 files changed, 404 insertions(+), 6 deletions(-) create mode 100644 fixtures/fonts/Inconsolata/Inconsolata-VariableFont_wdth,wght.ttf create mode 100644 fixtures/fonts/Inconsolata/OFL.txt create mode 100644 fixtures/fonts/Inconsolata/README.txt create mode 100644 fixtures/fonts/Lora/Lora-Italic-VariableFont_wght.ttf create mode 100644 fixtures/fonts/Lora/Lora-VariableFont_wght.ttf create mode 100644 fixtures/fonts/Lora/OFL.txt create mode 100644 fixtures/fonts/Lora/README.txt diff --git a/fixtures/fonts/Inconsolata/Inconsolata-VariableFont_wdth,wght.ttf b/fixtures/fonts/Inconsolata/Inconsolata-VariableFont_wdth,wght.ttf new file mode 100644 index 0000000000000000000000000000000000000000..2739432d52b7592edaa377917c8d1a50e6a8e808 GIT binary patch literal 343368 zcmeFa2V9iL_BTE=&+f7;RYXKVc3}Zg5SK+MR=T}|0V_)tgFq;@#Du76n0V8BF}){i zZi=Zl$xZJ!$xSt}q+An?y&x$2e$PD57SQ<1@AH0s@BjV$KQDS_&YU@OX68(N=9%X# zBBwed$fsG{!^<2<7vNbBnJo?EIWDZss;a+->114fB9#oTw1lP z+EFzq_Zs3y{aD5_5R9)H-a+_AgyWa3XkG0e{>5>|hC7j;vA(g=k({#s7vS;8^YRsr z)lGhDL%#%nEBGx9juq9947A_OSm{TMg?BVHwzS?C`S<4;TT#kb;_Xe%)lJD=&10Y& z$QLg&&bZ6PLf|vyP4);I&RUqB3DjUDyB!?ghiqU9C=|cYmB*NVyUTg{7yZ*j@%=+; zq9`oygC!3=w=`tjF&3cgA&&0`8s!$dbX8$+Ce@fb4v5O@;IVNSP|7Oa%WJ z#87A=;tM5*n&)_#VkEOe!Wc*<-l=>O+K@xZ0`&=J{ouwkv8r}iE1HO}s%l-vfv`B1 ziZn$i(Gs?ttz_%jX7rVZ*z@d7_6hrr{VJs}J{{7L4*wXR!6vl3@tLTN8=u9_)98ZF zW>p^g9M<9ls>|Fw%4NP_)WhbcbeFj?z02HSpKM-X zf7o1acbU&mbD5XhT;{rwF7vXHUFO=^DXE^~FN%UlJ#GIgT4VuZ`=NO76VQ>x5M zldH^2lDo`{lU?RT@D~p6GA|hJGM5d1*jzfyWj-&_Zl0gC&Rmk@G8ZSh%<~ev%yWmj z%yWiznP(?vnP(-q%rg^;%`@WHnWx9Q%+ugcwLWZ~Vs)7(Tf5AYti|S{IG4H5Y%v$a zy3G0Jhs}9rmpK=Hj-|^y(c&^su>Ww-lSl#kkC4hg6xz3~`yWhjf{5XZq8_I%h5_BkVX~boYO2oI7Tr? zJ>{q(Tn9i}Nyp49M-L>}$D+|O8_PV2j@eYo$IQU7Tub?c2LdPg zga-kq5leWmCZF&SO+MkFntZ}BXG!^lhiURL+wlGXniU8S2atTi2LMPu;SrjA!Ut;d z35N=0`3WDS$tQfUCLc2xk3yK_6CMqmtR|oEIHV)} zqfNLKK=KKX2atTi696Qi@I*~M;X^g~gePh82_L4(Cww^4k^H81hOVc|Cmi`m`Gk)E zPVxy))#MXCQj<@(O_NV}nkJucw6TWsQiSF0#Nx09}PIF;Tf8IqGxLI3D45x z6P~TfCwz=1A1eYM3n2M~j{}f=!p8$hKH(EI`Gil@PUe8LL>B%kmiO+Mk1H2H*2*5ngDMUzkXQ~=4Lwm^T9D-duwUYYV8uyBC-$CWn? z|L^brY2bff1A5*6zKs8WOVH`@=UYYhAUwnOWy0#``SZ{{6haJgWT#*q#-kVjI}hL# z;-|y(+j|0)JuU-g6S*VRVh~BsLEgU{mg5d2;@M#;W{*p-`+g3OO1s%f9*%899xvl7 zcssv=KZtG3hx|u=Na#hJ7$xe&h2niBQW>s{Q)Vj5l(ov$%0G09Zh&rx&Zf)P)#z61 zI&^pHp4Yvn+o9{y_tPioC+g?vYxJx2oAh_-o%(<3ztSHz1RE>{yP+Lhtz&+EeuMmy z{U-R$_N(-}-tR-dAN>v*6=S?H!#LTvz}R5C)OeTiY2%y5FOB~(cKHYSoBh-L>-;bD zf6D(2lhKr5syAJ1dcpL8=?BxnfJp%h0yYI~4R|YHUqDY_XrMK4bl~K`%K~o=d@S&% zz+*vvL4$&9LDPaNg4P6G8}vZX3qfB5?GNU`{eu&OvxBDvF9~i6-Vl6K@FT%52Y(X$ zdx#+3qhEQy)_zy?yS?9&VFSX3hUJFM z3u_2#54%3>!LS#?J_!4z|HS^2`aAkx*8i6NkM@71|EK+Thlhs8g^vm^3SSVuBD_8P z`tS$Cw}pQY{zLeI0b)S-fT07%445%s`GAWCTsz>t0k01DHbNH>5s?%zHeyCZYs5_v z4@bNh@lnK&5r+mU10x0w8#s1g)xebluN-*$z$XWOA2~L1MSm66v)J`(wl$gd*z zM7jnI9+Wm{@}Q-Ing?Ar=+;4x4SHqJCxdnlIy_iEICgOQ;8zEK8kHE;67^!#hfzO7 z9f2r%W^2rQG2h4RH#2j8 zbAmb3JjJ}wywZG~`F;zx^tY5->MRe&PL5p=TOWII?6tA?#XcLmKaR(R#|@1e6E{6> zdE7;Do8z{`{XOoTxS!&VS&i1g))eam>ul?C>qXYh);FwQ#OvcD z{~0cZZyf$b^04I9$qog($srX_l;yD!$u~I%o;gumxrO`OC;oTYzndZKN&FR&1-a zt+8#g-DPvy-n4ybJD8@VMWiLA<)_u9-I?}O+Usdwr2S&|x6iUW>@D`o?YG(=x4&xt z)V|ApBt1GkH9aSNZhCe4{iFOx%@|cOYSXAMM;#nJX7uf&-_D54xFqAg%&^Sn%-1td zW{IqTtnjQMS&3OAv$C>sv!-Uv%UYOKnN^?FmUVH~!GZtvYyX+E$f}E zPqMzs`Z4R*to>QXvoo{jW#5v0V9dZV6=Pl-^W)guu{Vuaes`r zj!zjsYW%qImyO>t{_XLHCk&ske8TM$-kTUTamB$h-JN?Rw>wYC3&@Mgi_cq@_f+1o{2}>;`L+2S`CIaz%YQxp z`}|)E{0dSFs?YfMY@x1jap5yXV~ZY`WSX>O(gl;=n(RNh-{jKCH%xwK$|X}iof`1E(C@19|tF>=OvGb(1>KI5qw zAI;31xpC&pvkbG6XHA>6a@GyA_RO9%`}H~J&-urk_vU;y=jhzXx#Q-R&)qop(YYVb zJv49VyqWVZp7-p$--<28ImJthFDbsK`03);i~m);yZA_nu4F(-Vo7Gnq>|E-x{|de zn@a91d9viSl21!^l^mY0%nzSGbpH7HbLLmiUpfEU`S+i9RcT|{_%cV?ma=EcJ}x`3 zAbP=s1?3CYEO>Ciy9<6>C>D-cSh?`ph0cW^FZ_LxvM6#<#-eG9>K1h@dSKCui@sX4 zcd>Etu*EYLFJFAc;=31bTm1gw-HT5w30xArBxOm#lDZ{#EO~s%M@#-#(z7&T>F}jF zON*CQFTHH(-Ai9s`pwc~<)P&x%cquCm0wzZZ}}VLyB#`5qGN)i)UnQSlj8};tB$W7 z`zm-vzlzw3?25{Y>niT8c(dZCilddLN^50i<+RF0l^0juTKP=n=au`b{Hv0x3aXY? zT~c*V)jz7XSDmaLTy3wORb5%#UVUTr)79@)@2Wml6I>HlQ&3Z0b5YF`HJ{WRtPQOl zR-0G5q_(a0>e^RpcP-N|YhQM%Zc*KX%h~cV%jYa_U4F;%`q+1m1T%kGvVt@_rW*0k2*)|%Fptyi@Et@W1Hds-iEeY*9d*5BGf z+alX$w3W17)^=^%^KIX^9c??k(y(&)O8d%^l}lFEu3WvcedQA?-&y(H$^)xHR}EiP zxa#~>_pEw&)eEcsv)Z&eZ1v#PajS=~wy&PG`n=UkSJ$p?T-~wyxz(?&{$ll>HKsL5 zYiw&O)||g)%bF+FY+LisHJ_~cZq2W24zKB1Yg!wzHg0X|T09TQwc5nr6P-*H6iqMY zH`hB_8(1)}V#eGBg%&;`KeyPzBlG7LTiD@gxeF}pqZzZNTi8Q$XU(v%b{t1r*n;A@ z(=2Qh&JuQ=Em8=VA@b&DdboWblk92oSce`}IlI~~H{h*<3MKgP=vAL>&z0!hQ z*z+wdwlwC1YiEzaO=l0Zv{kgQyIb0tTG*{?tD76y^|D?#4M6SaDUhQFkc;Pixbf1( znE>!OxPnD9oGu`Q^9;CZFH-9qC@DBc0UakOaOqr^GddUN=vjCmMX!ovIN3lrLb?`7 z73hOTV6;cYIS*oSY5|bdxO!iRqB1WWvqgk~0B*%-A13g{2M7dOXP?ko%t>K9*QrV*1i+t`^9#9@s9#S4w z9zouZDUT~pC{HR+DNid-WvlXx@^|G`20Vh7IOp_|*S){(|}OY&y^5Q*bKA@hrLld0eGj#R8QZlv`Mk za+`7|3sYWHUV+Z2v^sVrW(ym1OMR0{hBZ@`PefbHeSxJ&p0`<)-kO`{qi0s4c!6?^bDkq7n9Q zJ{7YS#u-u`g2W@p=@>uGPoQMOFgFK@LQy2nr#bq+-9tFuB+&ea(Th273a_Lw%)59u zKgCb;9@Gt!1?nG%nSZFjih(%;D*#&KLX4+%%EcHb?HDQRG19I=d*7(sq};6Bf;PWh zxkI@Vt^ShoGI|%ytkjZp-b($3e4PoWy*wBJKe=iKv5|PwdNEJqlljH`Z~S_cBq^y%nv$Wk#rVgB z#0-oX5)+H3^W>P3F_|%AW2VH+i&+>`7t(mK?dY~5u2B;K0Po^WNt zjS2T8yqoZ0!l#L26U!4T6KjSJ7#f-EmwZ!dnCmq5QuG(ZEEtJn*cLttJwVmR7XCEO zo8Cn)`W?M!xUluo$BW`M@s9WgL&yMq^n*UEN}|-qXz0Tj6BrX76CGoTiH{i`lNyr| zGbSc4W_rwmm?bgwF{@+RV>ZWJFZHp-;sY@FI z4oud&^`X2W^3hiwm3o2Wva)x2K5&P;zN^2hu;*W{@Sggf&0Z?7Rd|Qqz-n0ybKsrj zBG<`Yv40x)DQGwC3E6JE>rwx9o!CA9C+lwGuJ?D{fPV*fmF}wE^$cUXUfe~!ob9@k z+})FQ7wt~l`RuOJ-FS<`*v|EUik%n0E#5gB?&6(ucaGeNUE+?zJD2R72};(EFL&Iw z1MgUVzXrBKfB* zP!=gmlyap?slmD9V~SIyYq1DpZF)_4Px;K3uElmC9j(kVUJWX9s9t1?0`$eWy(y~5?if5(dQFZMb6ntj7| zi;*G;BW?He*9Ypsz!FurS*~nvge?E}M@O(a&kLMG45U=1D@G4%z zFXbylBfkXC5|8l5_!Imceg%IIPd=wGd;gBT!f6(RchPZpzih>OWi#GIC*s}lP`oo9 z$KzQF-v4Ie{p&$Idc+ttd-o|koz291 z-swDpmGB%^$BWtdd_HU7Wvm5vxmx*Bwvs#8Eqpz@m2Y6z^L6Yxei6H$Z)Okj>)2EL z4)!#^lRd(3Vvq4#n3Lbdw($qqi~J$>8Gn_1#9w4z@qe-}`0MQ7{7tqUce#G$U$B0x z6VKIM*aIKJzWoSRwWC<=adV9w#%^;jcAbAPKeivwY`90l_OUE(VPkk4%fWke8}0?z zaR(qB@BT;Ooqrnc29)svb{^gvU&fo*6}*{U#A~r?+>MHu070 zYQBnH!&kEo-o{?%PqH`p)9fw2mA%9N&fewEviJCN>}~!G`;mXhe&8Rl9sDD`f zh-qS`n2PHwqp{-{B{IZ#F+pUDv0|JUqm(HNg%x{{MPj}UxL({Kt`nQYHR5mL7Cf!rByLt5;v!s!xfDC7Wn#T(6Rk=$=IRQ?Bz{%~ibG1M z_+9BI_9{W*KT5FJqxg$&#OLBCJoSHtS$?+?fqhM~IH(LzMk>R^er2#SUKuXFP{xRF z6`MG!48j|yB=IjLTYRmgiX)1jIIcvCe~V8Qvp6YsDdFOTGDI1n3{@sEo)C|V$Hb%J5%I8iNIWTC6fcRF#e?E?@v69kc6#*w9}xO~078%l zfekS19FT#y7=W)&_}KqJm;~7~0oHSX9dUjD^4%~8xGzlX6ZVbs#b<*rO@ra^-H-?T zzk+b^xb^2Q$GQ9{@c-Wc{wRmL4Tk{F`Y&J<;{PgSf|u%OJ_npjNA9>0NPjkP$N!l> z2%I7;QK-)!c+UueB=A#G-tWNcYMwQ-4_bAaDN}VyR4HzI~x@E(1!y5 zzX9kb2lWL4nwwVjfo3gC{oWl%{UQ6m0o0yFfbp77cwfi??Y{!*i)Vwsk}ec!`@%@j zBK{(rZOl?xrfWd$O+2I*y1hLHKz)Sz5|u9>K>dgGO#Q&EKhp1&fEK_SK!fJ5g})xq zriK5%;-mV7X`qKs&((iAMdB@+KY62FwMx%R~C7Ho8uOE8u?)xB&18zVt}ZZF^D;E$82m=fNP&tADG`^{Bb}dZx2m;WYkq)4E;)@56yv+cjCjort+Y*GSo^Be& zQyy+0I>i&vytx=ax}m(L0Kx$l0Oe141_B}hw1!3lD4z1C^h*I_0aPv;FXU4=05DMV zNjA~q0EGXUe=a@#)wnL4)IN_qh#&Y_+s2pP4?IHSqdE~#8xV}dI^k4-<{vjszI#4$ z(|qH7amh>L65!@_`*bj`c~?H@(y~@sJE^le6%jjpok#Tt3O9cu!d<4`2Oj z@opYpS+ZQjPeA2z<39Cw)7sDp|MEwM+nV9G#3GG0OSB}0{uw<&1261D6b2(Fy;Lg;GckPfUf{Q z;C!zM@FC!Kz&C)40hNGdfL6eRfNH>nfZG5|0WE-sP@1dZzYHK5TLE-ld<%fm5&s$h z&9zN{4*|CWz5!eexCn4Lpc+8)F!@y0M*-cG4)6}(D!`Kf%3~3r6i^Si2T+bOYz9B& z@o&J(0IEObH&gTPfluXc15mkqp$_5m0TfR%Nxlb=4&rhCM(^9`uM5E}oCqd@N^iNN zajJ=112UY%a)9SSJM@ON5^goCgL^*yah!GD2KNs9<2dhh!hJ@5QMLzXgPbe)I)w8O zoZWFgj1Px90;h1C7xQAc^KoXxaoP#V+<{Xojx*28;a-JvBF?Yn*TcOB=RTZ&&A-N5 z+&N+%-1!2jaAr~lcd0;HoSIa?trpdIx7VcL4gk)u@b(Yyk8T3KMR^Rk6KB|5c}{s9 zwWgXg`4&AAekj89Ci5}WUs2x;BF&>LL~@fh3`&Qx33qR=QU0b}i!ck?MK?Qw{tb8+ z@FD>HCh$$b7695a5N#P)0KmKYKx7iI955MxFEj%D@mhK(;1z%apH7~@YpY{`hw3SM;~tuhXP+a3ZZ+~P(+17Iptj8ko#HZhz>GQ6|In=pA}0X5Oy z!Rncx>s{By`1UXx9NDfDu7x;@W~_%DbGcmC;B*i5=>froTQBgsL&H5dllU4Q<)U7T zKi7p^Pd**FyDsKBcrwPdfqD?@K1lS9I2(pc0x2J;AKpT_Zo#Rpo^>9FeEfAG%|`9* z6s6rr-+L0Ba3nRj-e(oS{Xq$qUvQ%4os9K&JLE7?jmAK3}ZG0U6 z=$lFc>L+v~fo^yCQS4i>KeswJ?tMGeBuN& zW9|wUhTrzNM)R#*F~YFp8&^JmDEOzYfrbOF5&VOJABq9N-CDl%y)KoO-s491?C<$5 zeElyxu4hlrmq5USH}8_uTaF8Pz`kx{c(|*P{d(f~aeOmNZ?BD7Y2CM`{P@T&(i(5L6EA%#jKKCVUxiwDOkN9x>sXu=I-FONu z*LfV5kIu$*BFN>!OuRQVUr;jTN_<4^a z0!U8JaUonL{;sQspK`6{elCTZi0(R$Qe47LxP&pnmBb(Q>pt0o_yE7-Cr`SP`MrV4 z(Ic)ge0`AUGP*MP_2K^ecDvH~%>$0_-Rm01uZ&do?7>O3w+3im)S!;1?&yk*b*<#7 zKOGGo;#$s&_nlJ0DSZ5Bx5>4J2cPQJ>0ELA7vUn&~=|W<(kenMs@Gsi4qKs zIr9B(>34p=8@-0!d8>NY`spdrbNaO4c-_`{>eMNnKfDtsjES9{2fy3HEYPJvYfGt9 z9{TY$e(830=%@7}-p)?!->8Izu@eV2D*gJg!^bWaVHS4i_`>==5i*4BKe}>b={qv?12AO`V%MkU{m|i{~Tv0 z0ycIZJI3}TUGM*mKY79MXxY?jos=4Q9$)w^W&_NQzeGj3F5>=sA|ugHuHJ|4*~SNw zw}yw1w~p(N1q8UtdHivci6)Zc#!h0P5SqQyXml0xr6>LTTzPz5w_Z=e$h(*;E5boF4w)RMQ38$=IU-#V7+i*~E((#?%%l8{A9khjyNsRtY0@)xcDjb}clB&^CL(fmI%9Na=g&VIV>>%{>@X%CJh*2M+)i>aRKAlg;ho=o$FT80E2~o=O>DpJ zy2fm#juaEa*}e_o;jHVA_Wu3Z=|48W?b_dtG1PVV&fs7ka`cKoKk94fYQLVi#@`+as5*R!5e>vcNTb!r3LW2Y|H>Ck@dio&{1Z-Cn^-G83GmMA?NFtASdTn2YX z&vim@=4!_W{w~+0a8J3`!|jnSmR8{1(mmct@jc~hoJH}l%-vIX{r^WXuzZ`Ct z*nkm8u90<$jlf;Xr5FPk3UIrn+ojuprJ-B8ow|*{_vt#|cIofLO3`W9*ww{OlFLI4 zmv z|DAnjzG2rc_H67!J9e=5?00_hiQFpFW7~G=OGsymn*Cj)A4}L&b=q$oIDxT*?Hs_D}sARbasBR3(l@x_Rh}j z+wt-jdqCtw^9uSJate+{COv_M&dx5Qlpln|T#AO&{inh^JNI=-(PoG_0I5Ihjfbq? zrD#a~Wc%pO&JVwps|D@gwDq)8XEd2G7LFe$2cx3P&wxzb*~{6o2baS_P5i_$T^K*{ z$0;j6v3>6de&X|8nlR7yY435Y$cE#`ySj}3mNuj^2KGw<7OK6PKoj;NZj1L=W!J^>p60nnLHhoH_QM*z^!p+f*yNSEMYo%{C#psRiR08rQ7y?~Qu=p6v1{qO?- zdfUDo@Owh%_um748`1g27l2(Ox_8|L(`6m)Iz(1li2RQaNTi)+%)WxH$Vj&4zkmiDcAwK z={ok-1`Nv$P|}8Oc4A{UDU5XiT!4qJN8Fv=kVtz*sNL0%@1(VcCVraW<#K>o95zJo zFafD3(e9WStWQ{t{^;NTK)-&6LP8D)2OkLvI^pk+^#>~oRuilwSR3@+-I)IUPM*Yk zk7?a>{CL2zV}VDH1|2yPeE4w4p+lhu5BB^1`+f%wgzewo|BpW~uMgO}H{z?WB7XlJ zGk4^kJ%j%9pQvAciP^o|yz2||u3eUoKEiZu{o#lB?b{RHeRtT`Ul0H4E6m)s&pu0g z`DObjpQOL`+Ss?>p7z{xc&t%-nP6S*oVBx_OWCRQ?8FA>f^~0Tr#C<$&qRb_>veCd+Kixw^@D?M+1N%6cnvuDkmF@4(9DU&A^C7DA_ z{waKGfGOXaUu_zZ!nT?MfCh|6;Z8%o(@zqevyv^aj8PgpZEk5{K~!vPS(G)_nd8(Y z6cRY9Ix5`}We|Z>NDC3u=2@rBSx{;z>?oJ1Kq>AOQsc8oqKwlpr^qiZbrvOqE@Ra2 zBpC*y8l2oGdWt*RYH_mJ9UWC$nUVl8IZ<1=MD+Qa%21OstFt268fz`BhN!JZ78qMx zo)0Y0jd9B)v^w3OGBO$Ij;2~PQc-ZnPcx?07{ot8?A#c4>e zR?IH#h;{OEYm^qATMCVFM^s0wHP%vAw$1fMG;v#FAxp5ltyX^JoUJ+h%6SV)k*U*i zWpU{u~;a8D@QDyt z4_ZP)=9u(3#vK2gK->>TTM+3PP+r2I@#pOCf!J3@ZG}wK0OW1FeXD;?6iN$8D(NNO z4o;%AduS**vH1u>mTJXIyfr9UQ2O^k98g9{7aV!?HzK8QtC*Q=^>*tyrD(vyt$b#3 zIlA^V1eJtB3%a#4XI?4smPet_$Ci~9j7Xv0Whu2*M_J3ZhQsJq;nvX5{AnHe=;-Kc zvV(1P7!u2qJ4&6@<*1`sL&u=oDhX36twrTvv0~WZ{}hla=Ud9173Il5Eulpnh_+NZ zh?_-h6-vTZu1mn}tqIVZA<${ER_8ebta+Z8iEN@8W1tv6Yo3!ws9GIW31%Jel`iQlggd?gh|f!N(btJISG9agsC7{N-NNpA+V^t!`UcR zc#e*FNXuvdMLGSj0@OIFt+ANgK`WDbmCc9Tr=o|HI$2alhqc4WQN)BIFhhKz(~vlY zJp6A;wmPcOh?JkjQ7zLJp}0~zRKBP}Yit=~E7L;+m1afq*2R7aUJ8f_ z&c!u{DD~k4Mob+Crp8QbI|poD7D2Y zu$0M3Lrp)0l16D1Eg2|s-lbI&>xx>00JXeorUsJl43z)<6P(5bG_+HPGN{phR2{cW ziKEbJ8Wah|RVx4-S&r0OVvl5hnz1O}-#?y94l%gU0gRAC0yn_|I?ikZ7|j@)f5#AvX_`e7b} z1~8#nob!^gh{(EKAyXuxi_;WBy^hTt-@lUXO@cCQs5&^ftvvQr@F!K}|< z-*2>Lk>_vC+A6poW_!%R*3h6pO!OU<Z<-NmEmCsXj_a zBMT$Sxk_zP3@hk`7(DT-FcXrtR81NwuEP^aij;{-+ZdYCU72izyfagE82?&k=nAqm zo&K^QG#e!vsbGJe6<*6w15Kq2;0d)FQ>iL?C>mqBqodM+9oFJt8li!S{Xh>xow8B2 zY^`q4%|)p9Y|0&FK!s(nxRk0NfKDJgPXHo9p~pAWt{s5LP*nU4OlVrUx4B%TK#f~f z9rO!%vY%_wa$t}!O1CPx3|J9CIk<`lw2>5`jncqAv%V%?ZBzf=kyei|TIj7Fi4@w( z1F;#_Md^`aq9qjS8Y7!75j8=$W6W0Wm#8tIM~0%BV>&tltm=|trRo1gJasU+xnyOS zoqH3U>(KNa9mb%u#~Aza2gw99_|eKi9-pLW!@-pA49KVb3mVYrPxDYJ+IQXC)ZNf4 znFb!zyk_)VQbymnC;}sC=|14-_5K=0OSc#AeEhGNDYK z=dW9n&lQcuDZd!=)i$=wx;mD0v5hUUuEBO8-|Dnj7GYAvQooJOh%W2sz>?ZwrRS>o z^q>QmBJur$Et>WRw3qSl4~fRJnlB*`0#LVYuE(S4!O5HNrJj6OAzvy%hdbkKtoE$g zQkUZk$(8?QRkty$##Xf;xEYJ;9Aja5oT@Rg& z=u7>N`;NUC`H-H|d;YOAbv|Ypf z+2o)bH9UZA4wCOEkZ&NnKCo4z2XRY4v4#h;Q2`be$Aed3K)8nE{YZfP&Rpk@vmLuh zD>H0BCYhWXkB+67Hfp$@4L2>&a05#Z~_RO4qFJ%lA1@g|;hi?_};<5~^x$C8Yi-gp@B;TpX^ zi!svI*~A~t;{1Ns@Bz%?_ko5-uz0_RG<+b7_Uq8_NS5eVrQw5Ej9-z44`w#M2n~;7 ziH3t39?gau-qG+OEXi=UhR3iJL$ijPS-fGUhFe&w!QB?Iu&&Uh(c@US{#OmRG868x zsO69M_8FeQ0`+d4B(hNb>l)8cX4G%f@FW(dch_qePojPhm;= zI1L}c5_P9EJe3XCeXijnS)9(@A8af|=k5<_EM8Zx@z{}ejE1MPXkCnkk75?Z-KL{i zjIvXsXRtVYF|XD!lMPlL(C{o4ue57;Hj7r=Z83(~lu;UeEK5}6x8Z2tacsEwS)-3< zNwBS`%ACMb#I+hek>N``4bNezVuFU}GFYt8@I2fx+N0t5xHGMdS$yX+9A9p!G7E9% zsY%0&@O@c98a@TLU_R3DsXpyE&8NMlsPWIlYlHO~ zJ`3O3Ez|JX80CvJd=9?+a+hH)&d1&QnFs#C8c#7Wrr{+%eQdr@A3M*dY^6SZtjwp6 zE$}JxLZ3diNcOQ3+!ShM&CJ0H@%>^G{03Hq+dfq?{O2*gob$lH0{4UZN|=dTPtAyN zAP+ZBK4@wV4Y+qj`J721Icm;l#(B$ouDHG>v9LUp!NDqVf5|K1OfGNEc_?Q+N_Q^q zzry3L_g~?iA@xy*yIm{JneSZi)q;y^)O=3P>1dT&^uX40a(Qd*<@eIg)RI@1=i>6l zx$EwwoK2>x?{o3=)#bT(eRX**zTUb#7w2EojaNc%e&6xp+Z&0$cbr#(uikp|{CUjT zbksX-U(NK6JyRdOjPj4^!a~`imca8s?_rK*~+>{)#rdHT80PHRO$Tm)-5Vb9IM(OYW8AP4A_-Q=U1G`u5Lrae1Y>b3YgNU*U15 z|0}${HFc*uTV2lHN@wHzYr65)+RJ}7Y2Ma5TdY@#yRK*BI#-;#&gbIs)zR7H_twqX zIL=*1Z!NrhUfS8@d9`wGK5wkMeqPGi<@Q}a&c@YOXXlpbtG#n`_SWOMdH<@Wyi&X= zyt|RJ)y}KibMtv6yLEeR{=df6SGVVu>8smwbN1Hlxq1Jpro0;PrZ^9Kr7G-{ma+4& zzjE)JXz$dD6?zOTWsJm*wGun7I)qz+rvmD*3rm&z+*-uXg1xBeu%xjJ^cLK^ERwt3 zR$%jh)gaX>xo4{eMdhf6Uy1zOyI~9Vb?W}O89Y=P;+-ddQ^o@MO_JAFGS$E)Naaubek zR}x`$$A$012k;2|cFIup8;|6J_+Z#ZNn+JJnh(LRpS;J-+`?md9Jlg#SeZ%SiTKiK zC{LmO!@VR^* z#;hIIVTy5oyaZnkormv+%J>3U^;yUl!HUlpd2U{-J z!IsO@ydGBe8*oaK4l6OE@I7rKY@1%dn|TXw=zNoPQlhryhwn3uc7$%?mSrHBAbU~<7pc@i4rZwZB3I;*9YeMdXL6$5K;mf$Y#cVML){ESBp~-^pU>3!qVa016taz133qYm#u5AJA04fR>7K z`nC-gfvWIzTaBoNwVyh%9QGOOVfm*)G{O?u1!S{Uw893^O0f!d!PdaG*oEvBSVlU= zj>AgOI&m>91+~Lg(0Z|f^~2@jA+SCCIMcEFaliIH_D^;N3lbZd0VkCc#bx4hd@m6w z?U>Q8)P;&G*fZ?!WDinwV1;>$z0ID*uU9=SKf+|Mo(53rkIqvs*m z?0G~y%7S6P=^$P=S=irLEUXycg!9xJ*uz+P9>DB-y?Bh>2`fKO;JeP-*n{Fpb}R1W zz9XIzPYb8mDxML47tf04VB7cwu}!o8^9rp0yao%8uZuUto3K9gmUtW14c~<|qW58K z=tEc=`WRM;J{6ydf5A@D7vf9UI{Hd{4Xa7titk{JXgjPH{RsO-JH;-sTl^$`hUKAO z*^A;oVh`*O{Vw*x4$&WCKP(U(6oor3+M9%;ddD*_gb zbg*P(Q2Z35;;)!s&nQp{Qi5UAC=|99!<7C?IBYXTCDN2cky`>n%tXPy- zB~G!zI%R^A2>VM($}s#sNHQ!fjevE=k%|qLo$RovG)fr_%axf*mXfWEfn}y~u<$ei z)|GOUTqRG*R|=FurAV2iOjf2SQy+in`AWUALTP}du_omLrI}fAYxzppkK4pL z*wt(UERMC}`_VmYz0$_sgf+5Nut2s3-&&q&`Ew(zk6o@@p=pmFtx2 zeXN3#H8a&N=v~U)uw%C6&+O^q*ABO1S8)y7fp36!!5Z5SuweHc`;qPJV`=wUeDC?Z z@`AF>ZE4hPdsle_Hnab!yrsOYyraAetB3C^A1EKf%HhY#Cx+aL=IWKzer*kPX@$9k zs-Ksk`8LhBYyK$J&vpCxT3F-H&CGERroNAiuJ%xw36VO?~xhQ+`!rtD~~Ax}nujQ0agGc+HKD zRyALnRvufSp-@)KP$-$y*fcFRPpf6VR_c7Mbop8>^JVQ&%2B35Pj;$^JgtIx?h0o3 z6>1fcrA^aBrj7C|R4b;XlUdu+^0Ew*WQhVMc`HRMwlurlG^tN+cAIW;g`-&&Vb2Yi z+&fKLp?#ELil_NdV6Aj^cLilB(+cw?9r3w-Q?$}fQA=l-(ppzvRUI(3w`5ydx~zyT zEn7{RmY->uE?dPg-J?-(<>{uQ%xVGMNz~lZ3e&aRN7)TC)uc0hl8zd!rW&PnrBNA5 zVMDD(%QDRJR4c8pKx>}@t$GDoI~8b23)HgO)z-J!?ONlcyW3T3_xwWrtYwYO4QeI@ zTJZ|q(lo9DO?id3pjpe>&?lSQR@6J%WF?`(LcdwQ4L({+JzC~v!w?FX)l%}i^!GBi;c znurXIo~;%nEvraZjQ(XP_9=$SYqO_m>dDXwpHXNi@w5q|wd@PseuloptJ^{?qav*U zg>GGI!U_w6O3u_R22Y8vZZmWJO4M$uFQM7NP$E04+I*Q=hV!I40?zYR-~YU-y6WcY zmbw&fxGU z18rWU3dzgV{4~u^*L7^*yt4lb=gwjx8dte2tMCpJxs&C{xxuN6CAD_*`< z&3sus6mztx+LMo_0j-94?iyzLRr@w|nkF)Bv|qJ%-b6LDj*};gV6%^waxjl(8){_< z18Tjslgdl8r<-c~&9Mr@=fr+EC|dPgFoFt-Z*%13#Fe)+1k* zA2X_24!bSSuTHB+oi?M^c{-`ho}L-7ymwxhGcyCvuWhcbZm4%ORMk})>Se1L>OBev zOTMliRZz1`m#VR)saaxL)rv4G-O!*WZSYAtO7`QlLeKP?sZ@KW)oeqfrHA`#eqS1ymzSFDL3hA1Pin0xBrFhfYK4UdIU1>|L zBVl%1rW9sRvybwpAE`+tN5$y3Y*d~sxx`@WBXt4aO_w<#EEg#FQj_rI0D&({1Ya&y z@UvCP^vgR&>_}53(z>cqBBs`57_t)euW4*+*08#j8edD@YL%}Ad+7!ht**llAFGTF zb(%mm5Bh~3nTJFu4??s&2vze?G0H>oQ67>{%|oJU9+FY!L71jHsTZ3qPo1f3c~UP3 zYkJAl^pdIRB~#N&rlyxnO)r_*fwC>KY^kL5VA&9)8=oNMDuXI(rpoF`M@f9rd4nP^?qPl$G;RlB+b5)kS(Lt4s_4pWQY2d%Y}tWs6|Hcz zs_M|@DmzUq)%DdYpi@;yW}dNnMN{h#LjU+gd^#m;}`Y zPHI0?hSfPhZ4O6sbK|PECRJ8;rb&jIX(VW*EPqL=YFs7v;V4+PN~y1|ar0!Tl#0gI zWh&OzRHbnjs=7~8cbN7xbt;6f7CTQZwmmOf?bdl(TjkkC2URu5(Xy-pEBg7tRSjs{ zW;D1A_OEJ?BV6TZk+guShGmZW8i|KhHPm1r`bLLqNi;EP8px5O)0fe|rmenSmDtwg zkr>q0cJMifIB7-^}w4mDWM(1Y+VunGF zX>49wvARkPkTMVmk})!8giHweaG^_hlc-hHl4w=XlB}$5CRxEqO{kX09n^9P(PG_s zp@FpAS{iDRS`8Dk8V;55l3OL9|1PU`tXxClr9P0zO`~LP+5nA@Xecyk4xAVgGPM-w zCPw%Yy}WJ-YW>~J$V;M>zZ9kBFKLvQn}*aL8YEDW)%+z@=Iz1JA?`DRA zpa{wxdV@qYOgXDzC=BsZzDnpXQv#=zM{AT`G_BoCDqjO+25YFs?G92Vl%p0a^FTOI zhBaO_pm9dX7z%jnhXmo0OasBENWF<#nSvyzJ0q2)wYkZ+D!qxl>eANasf(&ZpP*L4 zeli2C&8w*HYVEu|Tdj~LH&n7yK&!5o&`Th3xJx~lV$R;fA}PvSMXej45Pc%NeNqbZ zrl5SJYTaqPde(A0Gs4@!q%dy^SuL%f$|#Lf?MrGGg}8n)g`>F=#Z@~*n3v+Nw1){r z@5AI(ZGV}rPtWwFQHeY`R@FIbagf^50?Htj3lvqpHc|G8aTi=IfI551HV#&kyQe~T zP%CMO7VEA{pbWeFw+v{U5uSjzNl=DxN$%Ufdl9vo1xcbiBQ;&${w-PDL@k|K7umm6 zO>0f%Q#)^umjXQPpw`aY2x{##xuKGm0`C6pA!t=rvm*|7sRwInmMS1FZ*Ps1QJx5& z{@pvmn?e=lNkRF@5-@PMbwcPz%Mro|7ICnl+sL>^<{_29G zhKWZFhst=FUL{~9mjyPvHT_Rg@sPchtuh|p*<8?WBNeaUWK+NjX-rsgk; z0(C*Eq*DHFnwzt?{@k2Ib#o$rnOe)=O-BA+vYQA6K@pUXy5Oi`nTK4VpfJR%k|d!Y zwL~u+yD5}NRce^Vf{aaSlfzeS}9az_}(i70PG+|e#tE_LU&?BejRzFO4coeIW z=`z(LAbX~IrVU?xcF0r@B@xattf*_in<+y}b)`F1nwBapEyKUMr4^@uc*d0SM%mRz z?DVWW{j$ckmTF1GDY5j^?dlUux_X>uPuCu}((P)l>FVLVJw0C@R2LN$6-4+vhWb2= zhN)!V=hiSS;KPL{iI$e7>NBo2r{kbnbwxB9s}6y>a!+%d87nimfB`_A9$(< zprEVA3U>8O7-3mj@VWWrGb_UK7{QjVJtL>vGX3STP-Wwa3V(S5L>OrXJZhfl>d_9u zs*H5)Q8e9_>n~5Jd{T@GTGrTjzN4aXC0fQKM?K0*SC9N`>FOI4_<4aiY^tcPZ(QYN zwvSRXSDV46H4l8P;Oe_HTe^Dc312o7{0us@po*o^k2q_AOc@CFR8b9j>gmgx=Nqv5 z2J(Fa8NPvB83^`h!6z5nYj2ROQgu5qXg_vxmlI3aj*IM!G!FHoY>Ir@y?pg-Vl+QLV)O4_})juC5tbt^G8 z*s){ySt~`@B2aQ2OKPY3bt?(XG-5vP`ShbOiu{Q!BIPlOJ#7md_z5rr$oNfRq)7+Q z05({rbSVp%$+iV46J$!ESkms4Tmxl%w_+N3DkVqe5oN1MnJ-Helr}jT-%axC#dqK( zX(MFnIAu69B@V{j8CYIIe31CXW*kw2G{=;I%#^Ybzxo#;<(?Ef5MF|OQ?>AEgl8gb zk>P!c-F7!48A5EdJ&>}TN+8 zi>Tk9#kRP+FVl!viGLh7$?%KjxOtU<`;&3_GL*iM?Q|_jy&c6MtRQvE(5s=R5ZA2K z=ZAd*+P=gLklzazVCCrpO1{%q3mu65?pF%0nj;Wgg7Xy|6=$G7k+cu~1 zOO~Yh$h(u5Gd=oSAb&o2CS%DHl2hTvc(7nMHXNp%wOG)G?{=5g<;=!)< zV9=Gu2bz`^y3=UTpoT5-U_~Cx=EkUeT3U;nHXMCQO*^d1gZ=Ktyfqs3otrl70}u9+ z2Xp=}%Dx0nuHv}+^?S2(&+OdyeecemGkf2XcBPe&kN_dX8Sao^0$#9XZW#-32*&sb z#`pqkupvG$#(?n=LL3MCBiJ7yjvW*1V8H&cF$8l8GyDCk`n@-^LL%RXemnH*S6y9Q zU0qdOeLO|!xg9)FdLTtTkRtqEVWw8h6umvwd@EB&ns3*eDeUC!Dc~>AgS)cz6!L1| zA@V5o2TIkRMC$R{_wajnWl|V(}Vz)s~rh*-U}EvP2a;)l=YU2NrHo^+6-ApJ^tpRl(;fT7};m%oMn- z=b=SUV6RsROx4>N6pmt%+y!S)ECSY<$at>>@)K&FjJ?Ssl(0Ggqfa-p(D%+IiccA3i;tP9hs@O7X6jZm^-&|$uC1MF^Q8mx6-J5T zikUjqNKtrf&Lw?ZF~OMZ33T4;By7gGESeF-CP1G2&&afDx~!Aenj!(y7Ct zL;<57zZmfyc@J{ly=(GrH}VRcYIDPAN8yHcs;o<*o(Bn&sq)Q6irS&)>2C{H)gCXL z2T45DNEOaBQ@e~*alMf$Y&BB-S0VK-DqEN_@(O)sDphdj|CIl|qU4`4QU%I;jPo8c z^740^sV|wS8x1VQ2_u!i&P-iqq?iJxK7WCcR~|4@`O}S5dnD&~f}TaYf|Yy7H&c7f)Olv=WFysXQEG>gms(JiRBtMTUyGt7!Gq3J@<}5v`LLP#o|(GG zOxQp1urX_)X)8C>m6ByU{MGxw!fSFPiCH@k>r4&XQet%|I zm-wUc?~#`gzbAgDnQG@D6~CF^Qr^3aR2*X;C9cBndl?_4&g9fCq_%Qu#!NNL6uqtS z+sv9gNIRbCE&5ndVlNwSV^78&j@=)-HFjg{3Ny80rcN+ZTg?>Z&2Wi6GcRJMtVSyO zqM7;+GxZ}g^`Mbr3K(DXE+a2`laV?oeMIviOGNh>Wuq6EsngBWPBXP=rlyQkM{Nh; zO9$qXQ6d^LQx+o?!5E4_gWwk$Bm%j_FSLlBf+XrGdV3weeczhAvp8?}n!Lj~uZ?da zQj5d@VUHXl6>b@C!|3gZJ^W;6i7;W}vf=OZ+k3)a2!A}h55b4OGE*m+sl(0GoS7Oh zQ&cv?W$i{@=p{4tROpe=cS2uQl+dTm6y;sZd6$@Z?=n-n%+y8`%Z!<7n5mkP3Y*l2 zGDcp=Yovm&o2jSp(X7Xalfm!c?`}?g+Du`T>3Psqdg?ecwFRjKE-{2uj#Ew}6#$QA zUf@YH1udn+p+o?D#_v~+ya41tFY!S$wPL1D1?-Vr0^?jyK?mun_FGD&2#!ZLYlimx z93Ng$0(N@a&hx+If6D)uqWB*&JWHhS#vddUzo5zwdhq)`{GG+A-AJ+g`RC}F{_~H> z|I1&0hX1cAJJm-yo)=S;y-=Sr;)e5vr=(7>(7j%-sP9pn!31J4Uc5(a5#|00e-yzw zmt$W+MaKC49J$~wPEX0Gokf2i*599~zb`3)y^P5$@8Rb+@IHh;O?kt7lcG0b7N;Mb z9u-lw217oByRV$F258#G@Bdc(Tydrfct0TDD4%g6)oH&UHTCiHTk0iT z%Bj~sqUWB5(}+$gNzXV)_c?|NDTFnsY+$H=*K?gp7w2+|#R}6|!>LOfL%oHV;hEqT zBD_dZTM?0E_(6Ivu2UyCx5N~Nl~K<1sj@Xr#uAjc#EK$Y6XuAnro2V1!5Pe;GDM}g z$7R3lr#H5rAkV^Y9@l9N;M+C?$x&m(S0zOO=4nug(>soo5}o)M2A;zbxA5o`1(a^& z1{Di_WsM1iQ>an)Byj`Z#WUX%|M6ux`K&`B29IZ)Ql(ts(L!9-q6~Ac#IPTI<{wu4S1gA=O?HiBPZjkWeNWV+V8{(zaYLQL^L0-7=0n% zKcPPd^)Yp+-ckpb4=ZlQ`YiQJ;tRks!&nr(UqXyEtW+4v=q-zqE|f^CWD z?V{duhWDd~WSsh08vh|`!4}5N5pU~0yk|)RhptmjW9ih438R= zZ~ajj@LxfgR<|pqAl*9oZ#d8I^gf7|==w*e0V^O>CstTcr{yKkU}GA7r*6_QQ;%mP z<-!X}s5K-J`*23oi8IM8g?Ix_hW25MvM#~51>eKvyO{?OiB)((*_02rJfi#wIQdTH zxs75}9}C@z1@*=C9`~XylJ&SQAq7xLa@Wo8<2ru{ibss_>hA+wU&-Ky7J*bcaXMOR zus1craU9lBSc1&4;!^UcJ1Vg%k86`4Z6|2;sBenHxqKS$XMBW&ox<o7u<9M>Xy$j~${ z#S2KAE-NIl)H)Yyo~SPE`!yA-W|S+f)qbpXNvI^(t!r{v!-p73f2KYS+@)A`j_g;z zhEwN+g-3^})qLE}gg$0yOhC&3igkrKCR|KQ)ZmHojMD4A2Hed6mT19RB51)4v9jHY z?0I}ciJoyE#61jUQAodq6`~v5(fixQP*H_Ap$P|RIqkiGlAN}z9%uWNLT@b8BL9=P zdxLoh$P<cI_ujG*(?Ndg#91{aUx{Ss&dTyI*<6^@Izf7Hgs? z7k*gjGRmo+bUhTlA24U}o3GLuldKUVE9AM3OB;3b$WqS2>3t`>-IBsOPy8I#(TRIH z;&OE0=8hlIJs?zabv^SKtFyp~l_bI}mPscH74Ul7Fmhi9hJev_^)!|(d`DDL0?qI< zelImkTh3{;1eI}yGW2mk*W$WXqWwG0EHR+?Yxp`dwNQFif2;Qd z_B{YgLeGuC(w^+La0^esZA%0%CBy;=Jb|AjzNl{LTuO$^%TYF`IP^ZDQVD%*#uO=? zxJwwh;2wHI>TR+a32D0_bI0LU94URE-Q9xTkyy_n8T9l*Uf2(1ZFL#$C{;QO+awLmr>xXQmw#eoc9lpQUF-J}Aim_S&F@ z3B#Y$cc%DxTtxM8@fvof5UihNF)I5L@&YNrUGr!^Fn|YIjwQCFk3Q0wB(Z*7N{o4m zpS4U>m#z0JQeSu&DxtTT^e0JJ$#~ZL*3WfCb-8A5(YXsY#j=;=TBV&Vj*mkQ**g~& z&4b(Lf54;Z8KvaJ-P^biOJWX7DlsnQ47|(0!L1@Y#eD*cWxz|X?Z0XFjAJAEjCCXG z#BLc!4q8!4^{35!I8~;}qM~NUAHbiMjwvX$tXXqdmIVLyy8F;-i_$|S@tkz0>~v50 z>)IzU)227N=cqZ{s{Krc>BZeUBjA(l^3OjE=`c{=2SN-1=(+qBFTZEJ_Ei{=U86v_g|WkhGD8nC{(lU zx2RcKm)~&BA8 zpVgA!iWjsV#`PMJoTAR)N-MvCyyN1>wf`GB|fT=VHel zZkM9mF8w)*o2KZwpg-S&+n?xpKt5A%TX&+T~t2>aRZiO%S*DQAu4> zq5qESihz>+VgfNIYHt?ri6$rWBdlvaLk|U+QcYZ}F2k4qJuplEb3gi9wuB@jXi|!i zXTjb!Xk8<|H_ep1P8tgHbIGUKHg2Ka94Gbb$-FJ5G)c#7P;?J!GXF525ZO`1xUzwj=3TJf)9}gkeE| znPj^p%Zn{Kd_o_`FK7^Er!`ib*y+UJO`#tJd<~p9Bv2Te%UdUI{+h+L! z+GgSB=ZPQMQ-EKR&)WHrChDm{{XMiR?l}x9>-qT?G>Wu`@w|hc#nt9$9^v;dXpmG3 z>C6|150>`=<`hM?BP{(n)Ann6J6|yQu)sLju2~etBim_esglC;KyZCg_m(XZ9(*Pl z@UZ0IJ0CJvF}SCD{CX`VUHb>Xy|dI?L7$I!pgxIqyQ!pbLRZZ(4-|gB)9e>H4-Jr2 z6E6Z1w}jy<8;H;d>L>b3=)P9K>?`THXc*l!74omV2)#i*j79qE(0Z4{CepL?q&3jh zQW15&wXU!2Aj2CiSyiM^w3F*V36{Uf>vD*Ygz_=1##w6Iw$hR$XMNO*$*vA zr9H|rVt|x_1gL*SDUtvc^B=Zoa+H!?$V;k84lLhcyqeCfjG+tR3D40RNsD+HI!3nS z3Dh%!eZqL7k1lH)8}~p&*D+7%IPryL)Ts$K@dz{Cu#(~D+fB)>@iTe;Bis)X-m7`U z4S0sX!tcH6GvX>(01Y!3L!R05LyYWP!Tg~Xbv=u>piVuvi{hS0q31u#c=ZveeMDTx z&vn8}UWSl%hv~wz19xPSJTP`V_wvlkBBh_LJMj{;D3p&XG7=>1!VuX{#@g2VWrpH8 z58FW54<*huYWxItWM9v;MDfiPs+(tCsQVU$W?uMq5o6sCNq9^lZW_CtVR`t?E80=I z9wwh7toP%q+6{UxX@{UP!LYy4zOCodY$huC%RMb=qn}qHOC`TJ0ep2fla-|F7U*L8&PV$X|~; z^JZ{w>}iNI(k;}-W7Xzt9=Fbq0TZ_)FmrF);BbMHRot-xG>O{DA)yaWA$je;I$D#vgD9;GoeUPQ~Am z_@nph6@h4itdr`+pD5rjp#Q1h{Y&_J3eQL4|9FoGCXXWYGD%KtFUW^pl-69+<={>I zIVe4L+`|hCt5T#d!0>PK^A40}9}EF8dcOcnEH}8Xm!5|pS5j{po^@FFBii_@jL+~U zG(-Pk)Q4A;$OLbP4Vuf_Auu$0>ZL>$UedU6wh7s9=Gnw*Z}+}JO`C^oXaaDz#5cV zgySMk>$TQ;7Rl zAHRz#s*jiye|t?$Htp8cmoRTI>iUbf*F`+j?mM8=mFVTyxKth#HV;wi)&ol2v-;n9 zsSHY;d5BVNp57+;MLZ36@Jmf-@0Xa->hG??qJdLEmk^)a#r$X3PEP3PB;#8Co zABt1An00&)@Ki?tb$s~kbufqYwt}L+$8aU+IG|5D1oUkt?gw?;5kTK@2r01{>@!We zu2i}~7i2pCmX3Gy6=bK^gt~h5bqPg1IXGB>~M)Mb$spbwIg} zQIs}oxC1rlW11OF+6Vevyr7gniyFyBGhIpv5B86^GlckiKaQHtP*P6wc(} zApVC_ip^j?Tnsnm-)PY`-t-r{zb{gM_mPj>@$402ix;@m^XC3#;Wu-4-0^F%oiq&B z#9AnUI-xhf3s4h+axO7XpzGjkh#$hDVbxFRQ_>4wS3aLZ5~Ls$M1+70Xube6AINBA z)NsgZwPmz$$g38sI6*u%I#Q_=i=#3*pcacmUmrWX84LGh67{S^*(!%ehUn zXP!E~km}Bjl+RJmjnpRc>4{#KBUc|zRM!o}XQLza@??HG(Vq&JuuD@YJ*%&Zf5R-K zN69bvD&=xM-{V;EdwssBOC!p_AIT(er>iiaj*iS}%|;|@vsK3`RS;G1g4&Eql#NHN z)}lx^cWy^2SP_7%en-R55kTU07=3E>ykiUeSx8nlmI$!o(pD66SUoQNZ*A zCf$aardFCTA966vWDnegH84h9kVwGjHA0uD>$yr{mXO-*K>BWzbrXHbHePU8OPO`w z5;WVej$t6LW2>v;5lBckX2`z#5)cfZAs9o$QHVq&5`|QFH7N~MmWHv9jBObSg_9mF zw{XVh4GYW1_lerk8xIred7*MDwehIo<&_=v@u~B69C^VUjfxCvc?`9L;JKr>;jHA; zT(PKRyVMn~PKqH!kj6sP4|!v0q^b{)L>eR8hll@WaB-;Mb_CqT4X4b_%`6<#Up;bR z(_j3B5Cc=f9xC)id}G@hn=jbjJ2idY;X5vv``ykY<%I zUzBp^1I(b&Qi+GSAz^NpFx`O3n=q@>;#=%*0ET4qcO;vHjbyXN=ma+QNj)rO^FaD; zlX!`~tce$5QDj>#>;5jLK!h;}sB?GW6CFCNjuCzEEu+PtLV7)f7q(oDF)$`A2Y5Y_ z4zlI4j_>JI2Lb=px7Pt){x6MexrF}vE=f@Ww48KM=x=Fc%O&)KI_@ZNKYR#ivgHI# zw)|zb6q|~nA zktro)?hyP;8@LRc>M_@u96pNiK7#PPuz~xm#D?eCKlTFFJYkIku!Cg8!M` zh3px!{gP96NS@Pp;*KFLw`uhiL;}uLa`(lSO4MS&s)FzKmSKPZ}$7Mtw*VJ5w`jf^}X;XpuOw8z0Ts0wAC?Z zhpi5<{^}~AT@|*~hOvg*Q1HU1AY(nJj*mjUhsj`vx12L2vLg%YlI2a){$aaC*j(MY zax~fK&G}>f^EGcan{mJU?!FTSorhm{(h=|8((6kObW+n z*Z-t&(*NH2uIOp~JIOB`W$_ze*AQ#VEp$gQMR2(+;kbRpW9g{kQn;)`!$HVr`Jh$V z#IIV1byg={8Xv#(?KPsW8mmW8d%&43+*coFt0^LW;@>|R{Nq-$_Lor7`^Wg~@WmR? zcb%AYeV1ZPawZJ^9f4Xth8D8@wOcH3KZHZKzkc>MF%IB+6lw7Jo8coazCes#{Qhb2 z#`JW{N*)AbU|O~_#h+&nLb$s+=JgVWY_Ej*;w#+WKEMna{e2*w+awI(G5yPVqE;iT zL3qe&{K~Kz0BaB`wi*%_Sq)j=%`fX#15m7>JD|v70SY!0P^?pbg-B_1p<62jgyPu+ zTVs}zGA^-`^h)fv06Xb*fSG#z2yh>KHDL~JZ4PYnt-mobnK2OwL)J#N`AWs2TN~83 zel7RN+DNFYF?*BN22h*ULK*#lF}3&v?I*+edBl7 zS8%VO9(@nvfou&!`_o)21qRtVl_;<{)aFfb{S17KaMGIgkhctce7l>lw7qme^=p$KVv}RKa z+HM~{YzuUSuvpz5uPyQWKOH9~###vT(lGv`C?Vn}T7PVP)U`R1d`5upz{#dSf00&4VJH8hyeSE>n?X`e2>p|&#=q;=x-D`7#9g+aEZgRrclM)kst-sN+y)i>ta{cOGh<`%K zB<2U0QM;95AQ()i>?>)YPzLLDn#5r8O0!nv=C75_~A{??IQmODRDEKFLQE}OO6MScGw`rbdYb0qc&kfYgsZ@oAR z-g7%x3Ql9*fBxGn1&9jjaYmw;+j;@^8%ddy&o_*5)B>~#e~o=~!!jtq_5$JXSH8KMdj0q#N~BvsdX zSt6paWrjo;w6~#DIyMct!DcE1Y{G{2Uk}~ViA&cloiR>Wto5r$if^bNR_y4NVI{lZ z>#I~US?fyFff*JK;IM_Hs0%_b9P0=%|7~zq&_^2g(VR+SvgyZ+GgyFcrt@O`k&c_2 z>1J~^3>zUobf=ejUv9QcM+ceYUw zmrS>|jcgu>s*h*FkxVum#{3f0p0TD)a<(Yk%%9K55hG}CgZLgKErMB&M+7Y%YyiRF z)f>ZO7k7q@j?*k1eP;8+dmInjFnioQI(me_TE^{mkLB-0-yi+60jy~WizC>2b4=?} z7iND9ULyvBpGV*ox=BCaQ(Jb6txHu`Y{raK*5PjkgRqHyTe0fz8NcZ>qwB9uUZ$Sg z`mt!db-noMo3FR}(WV~s6L6u=&jD84ox>4E5kxr6h()7DvgNeD!V8<@a;w7SwXa~6 zLOL*rhtU8OrspEyxGW0mdW^&I*7F-sXG1*7V)k9}Tq}ZF86V64xm>G^#S0u@Xvf(l z40(GJ=62{OXvzTelR4{eKw?3c38PDF67iG{iIteh7D`OFa0F0v1CwbB$rI_sr1K%n zm`qQkvAT(OZKbdp;f-3&C5V5ahrtwoQ;18p z*vgB8(Y{d8X0!W!*L+prVIdDbU+|dxCF~ka` zYQz!?(0rKe?B8_y$jIrNmQNcQIc<4pL#ec3XmGJqT6Ass@R_H5c-yuQpLXVlx6IC* zf7JH#7Z%Rne$@G-SGY#*?e}%`wwkkdL@Xo>S$zpZYa@v9lmLU9!K6=fARg&aGM-ci zjK10+@ffQORUrBsd^i#RV^{;@IpXIAW$g$B>Za9(J_ZI!#N%J`_~wYRq=O<9vJPXl zf$I3XgVX_jbA&=d8>peUs7v=X zCB@`xO033e13}Fm0HwE!d`*tA?^|t&5%M)H6BQwLBF5e>WpI&N_yLI>)HHvCZv+$d10YK_r;bF4;QrJ|!2-wGHZ!xem!deYczS z-TCXE(^?8)x_|WxhTUq**atggRqI__A5l)2B#fk78#)x_5=v5jASU7*QBE*6&7fQx zejDW)jcSQWj%tkrh&zwGIY~G9*0P7c@H4rdM9m8v(8C6lO*7Yb2yDX|PVuZ(^3z=3 zA*i#~LQ&iXPzuF+unL4W(3%X#do&un-3Hov_bt!ut>x#}8uxvqlK1ox^i zhDwELfVuKDPC2CNll=mUSi^k0Q=Uo)g&iQj)W5Rt^c5;%-vRx5x6bW~1{j^!pW-n# z0pRcKVtvVFI(dE90i|w+ynKpBv_{2#wx$@*I;Aw}V+DPC&+4n(xBFHR(>CjSQx<$T zaUb=;0d%asfi=JP!ON~I{R^>h%)a9Ds4MPb(I3LeQ*9-lN=akmpX9}RtYaC5ht`VN z@I>Kow-?o{R@k6%zp-)@iF~;{+Z(NJ9trmZesjl%Km5gK{WV{rW^-4vK`c?l2W^$@ z?>q7@uUqzq(!BJR4FzU<^WyZvczpVdtw&xk3pyz-NdAlm@fvtEUM%p%$ekgFQ!Z+- zx5-8j6TPAz&YH2PHvRC%T|JS(<>r-_4ooBx6a8?ihPDrv=7tjRMvh1h=dz7#M;#&9 z+3Z4hIGA+=@eg}}^m;ni5&ccJx3;ihc#FlbzIsOsdDDlJUw8#7uI*gB`s(^<4E65C zCKn+zkK@>(9YaHh4}A6)=|)D@orX8_b430!@Tw7W>hWqT*;toj1m!xUJZG#C%DO4lebpo^0v} z_5Q?2J((UHja8>A!OEtOsGh=D)wcY>(@(r~bGE$g!ktU!>}WV6g<#j>!|}=E*Y7-g zCf2jGduaQGizG1<=!u`8C%O^6!o1As^Lf2W96ZvOTxD+=L=mIdT7% z<9oA{)$0$x=E4g%%z!l)s^|9Y7%OfZakiclLF$(2;cSz9c&?Af<7Yd@qnZFG&1i;% zA5ZN-XuI28%&kFxM!qJ1jeJ^mJi< zJY2O}h1C@sE61y;ZhNRWS{axvcGpkX?5tJi9TuD49ZC4p)rdb8jV8L|)9Z@^OVwZ@ zK4Q1neeS5=o5%pWXnm+e^9l`tO#|ENbR z1Ov!hoH!GUHLN9o-m^=XC8P$GWk?nz-rFg>daqanu(@kU!aQK&1q}N77DOpN%b1q| z{KT$y83Xe)^?-@_L%(IrI~F?Dm=A2#ZL)TvF=p7|5lCVuo@?a{ZrcL&w;9crm}m|p z+i{E2p?%RHHnqQN*0w2HU{N5Z}LjA9)Y1%2YnyII4xk)&H+rKXz_&x&p;! zK0Dhpd11Pd_2xz@t+IG@YR6D`c*o#Ja`oMd&E+?K`MS94esy?${;eNFzqQM_Fk5#!uzGBiROZoW>?V<+4Oy7hj~Kz< zi$#e;Bqt{!-;9UXz`w!rO}vL3x-hdd{fI19eL)}kSi*ehcMOB0Y3&uUETp9qMxQa` z3XV>_m4zg9LP>pujr9a&>Z2?)O(&E-CxwaY)HDsuH|eBP46{vd%&`(QRwY=)j2cmc z5G|zPiRaC4o_@w6q6gDk4x8D~IDXEx=WKB!1rS;v+j`t_+rz$AvMD3`B_pkog7~2fR{QO7B&&NoD)M6(;`$4(HBw?<3fiS5(Ybiev z)2&#mB21l>OBkY@Fk$w?>+68(eH-OG`;qnC@ib$q0*2X1t&=<;%P4s+XFuRMfFh(Q zb)G{Kd3T%Slwm)a3#R>)xD0v0JQkedRSM#jx&9!bz9wr_+OwaVLCsC%TWe|>W_f%U zBj;i!71cF8f88j}V?-ealVs?0iJf-E$@a)yaoipRS^vb{MZ4l89L*dgoF4BX**<_b zG;;t9tlj}L2Xz?05ANdez-7pj$Nqh+K|BdvXC+;S4dU%BKje!=H3}|j&p*ET=1(mB zZ;N_CD=(gW>q98#N8#UMw7cQWbz3!;X0a%CxN}Z3IEi(1U}!Q1LQ>SU_nf|N!|9PT zCZ|tJo{ep2t>eX>*45&RZ+%33qV*Tlzl@dT5&{Y7{rqdJ**u+I+kr5*Nef3X<`|YR zG&Uuko3a0cc4`n%du$$v=~nCyMGtup6P2S5?W7p&QLLb12E1r7SS znL(`?9mm>Cv1;qN^NWXl;Qbpv`}OVDT!px91Hl zDGr3uwH(2i9Fj1^A&JM(UIf(Ukc1*{LPF^gRY&LQVhVZ!Z8Bl5!9F%<9E$OF*pLHh z(ls1mI)HA{6I5F}O2R97riPQ4XxvIDbKDZ?=D3wmG-4#>`iSwMJ~?908x7R*WAp|l zA`7^?-ily1;_GDX$zapp(D1m(!&Hkr=B7<7u{6XK`r`eq|0UvnuhrH%ZCI?%54RMH z)j3DwlrVDdJSBTa$}dHr%~nbn@-!vP?NWZ%#=990@m<2)0{MkxzAcgs>P?ty;5vZs zVU84=k!;evpF|t~f2{IT#~nZG=+Z_vERN3hA31Sws|^0p8s~R$qG4RAE`)}iD{i~p zTErdn;*_EB#OD6gZ*1Z(}#P?y?w4t@*uS_n7f%f2a*^h)b${F#&-rcWEv8?+6C8v~4sCO(T;-pr3sEyP zw1m0Ngn9q#+!Hf^F)azhX#FP4HvvO4jZHH`oW4t#uyQ^yjPdeaRmTWD4UYhI6JFXa zx6Fve9FLxkSoDE)-1|S&fu7OkO^WW*p$`W|=N*J1f-acQ_rEOZKMc^HIykiY6%+b$ z>7f$W`Z~GxtX45$zO`RsoSzWleY=DS+sP3fqsbA%OB`8b#E0U@v{}ak9d&%@Aa%SV z9@a`G^tV?fMH@j8IPx}%h$9mw^mlarZ2Jwu_t#fB3A5#zWM{}s}0IlWpW|4w76Js%*^Bq z1!Z$874w8_DP2Y&>2Hx=OR<(quKF_Dx~^x-XUNPKH9s?xADnDz>5YU?E`lyL%G| z`-H<;|LnlF0X5iDOl3XKboEZAvb1B0c|x))FnNR?x|DlP+lGS*QU_vgI|IJ`5cf5u z5oOFT1T4eDY1fLkRtws&7QJG1QZTh`F)^#JRBh&-71||Y>=QS`jkL%JwJ)5hMvpH( zK${j^nNls-yVOYL#urPDSbr%T$PXmTlQ}Qi;qvDqGwLU4YWQ3r6LF=dcTdlrFq>Hj z^ehhdZ|Dp6A2C{87{FmvS{uN+gTC%rC(B;R<&>Dc3(KD#vPi~GJlLXlToM@X*ko|1 z<%jf|x9H1tyx6!f4|6eArm zH{PM5pQ32&oGlb)cMeY+o9ef9+1!yvK0VSCi}g+yqP-PgZ{esRS83sxk>)WArT$u< z4JTaOAz!S2p;}!xl=Q>{!=)1XooV5I?~?rv-eFYtV2^{j4^zTmRY6FYy%~lX0L+jv zswWVOK$}dM4@oUEQ3L227^5zXX~2-Ap+>Tj>$pl`mXPCF)(xAW*L``(qz_-I>V&!8 zgt<6t!W_dekllX7qy7Q1TTn(xb~B@+1=mW#xzeqyc>DU$1Jc*i`yeV$vT)!D$18Y; zW-t_q77-7{`oOpmsp7*e*eLc~al?47FtJ$7HLEV#2O%`urY?){)*2aCejw#ZO!Q?2 zv%bt!uQM}q!tDI9lgUsqhHV#+sk8hJo42&4#|l`Y4BCY2h{2~G}3rfKP{w3`ZeQN zj!^1{Lyo4FD~bM^&)1vU8#t>wUkn}*t%u*`KRVT$_9mNK8}Y$%#Fr>mg0bOhEK+H> zi}SmhjophCS2R#r%9Msuo=Dj4^JIeQzIblaj$;OAPFX$8uu8CD4Uexfet)EFm?f^u(^tt!KbSY^5|HQF1G_l;%BqwZPsj%uB^SX5`W8gu7r!EjG5T8g>7 z@p9x3fn+G>Pdfu{pFieIOq{e~!|`LWYDsjP9pwr~+>v5%u6M`e)b^fGZLZ$jJP=XO z%0xn=W09~sp7c7qLx@_-D9o$kWfis_C)&w=eFVF1p2PT3#~|!YLw+g30@>7|j6FH( z4B`nLGpBw;-#Vf6IR7|9>AR6s__D9U0zaT5x#kTW|Wa^<=DG ziaWD2$4*WiH=9e09yww6+xyn_gj`X7HJa-dsa&%h^c5+3!L;%C4@g}D&j-Bd123uu zFC+}SVj*GPuQJRKV9dQc5{C4Vguzxp!b5woc?$$_ei-_Qy=#IYUBjof2pjFLSY~u8 zmIdYcF2i>K@5rOo@m|g|GN!NIN#Bd)(dsbYLfwS1*+(928`i)`KE93-R9w?T8jtYO zev=7MQQxID*vFUgcHVEo?bG1V>UBKOQOAc4Qpc;}G4g12=x<|=NEA(hqVo<4%^s}| z{T-bnH9Qxevx`~UU zkmO$GBHOyx#XrHnjbKKOHCB7IIyk6gyRpsEFc{>2)eJreo- z#4$sWYBHVeKYVEZq>-^x*VX5X`PtrVE#$8itZu8-?XpFyDPN{(7qz9HRDZ_mA54|T zbF*h}Tzc2cOz-x|ksbYkOem1@i46$FIYOyWyc#xnN&dUe?+>sas?zxXOQU_`@Spw> z?IRrrzb*i`z3OzW#1yvThF=#n+)cI~W8?V&SUoGe^f@(H)DO8lEfw6>KB?fu5-Yf9 zz2vKfhB_3S_}4>dxLCHk^>kaq9lyF=`~&Vguqo@55N_cS$~-jP&vB1uhISBUoGT)g zQsdT@kR5lflMhNW3+$%EhE`)Y1bjZ&Zd(81^C39lP`vfD)9PD?>xacFyq4CjxU!s zZ5rP`-;?imIFnJ^__(tB_<7sP@wOFQ0LTC{vL?>yA6PV=x85UTY2V+tr12qF0 z=q9Tnk48xk|9{$LGmhX+G+ME~HneR#9jit&frv9y9xV(^rhJWNacUszOUE$->Wa^e zj;=3y!vkBl4lh@$%fpRL)#|23x<8TVPxJ4ssq}o^k)QfPy=Ku1GrMPX?Sg zsvSvrt=@1Zlo*O6UH)!|FXZs&dgICAYE*2hZ|>_`uGg3Q`Zm{Rk^{+9e=^yhLgGvL zbW)qvoEVABMfEC3V?SH0n>hYv`LlU_F`pz>!jLtRFngaOy=FNbFlO{f!jK)1Fqb3H z1Q^0&#se8ftD7(%eDzg=IU6u$%v0ha9V+o${A&}>c0-3s7}8i0W?#FmF6)ZI6J81m&}uNBM?x}u`g_j3@9ZgY z^i=EJQzRb}|F=y1H^|tE(~LlyFy!?}8uaL*7cfT{tvL;Ze1v@o33EAXM$jpQyw?DD zws2c$7B69p2owReV=Iy(>In(;F;GMi7=q#;3EOLi(&i)-tQz7}Y}H=Hw}unS(#zK9 z@hv|ZV~!h%hb+B>(c@c$$Bf=e7#cScW?!4X=4!&}IE7@&3U~rpfy7*Qik!jbP(skrkpC2Cl!vbwzGXl5{R=O2f*4}CS48-va0cDuaE$pJq8 zuyjmKr{tF`Ws;K1aU@lzM87=)r*g1X_)BOH+(@_pDd6);))k9_6H7EC8udDFiyy}^ zAY-*3hc}u{ThR8;Gn*;M^9&9wb=wjT{_4bMCcYxoM(di#>A2E2TQBfwB|4jeI>5E9 z%n^Ox!kW`cIwj(Nfs(>Pz#YKZI3mVkT?vPDTCn>Q#58HoMF&0MccMr9kS2S#x5*KF ze(g52*a3geY3Wk1V296rQ(NV!vG(ZSe)ao*bk&qvo_gyR9{t-v{cWtND20{G=UGpY zg^DLd@PpF$D#!qZ`(0)JL%B1 zDlQ8#XREXy7t(q+=!j!q?x0d$2xYR$NdDO=h^+2ropik?Sb<_JUIYU4010k2Ee7dZLkkc8TFYC=xdpgeg zY{%@nvsL=u7CND2oVUOj+#ktVzIa4Dvl(uxH&gj=}y)XtskS$E9i4j2-({WT8#*W zXyiNkT;K*vMSKnIh>=&J1p=}O9@zq2PmzVxk+QP8Zv^_ru6H)xQFLvk{2J~8N5h?7v|ioy862K7tGD*v=Qu-4-xlv?8#g+d4~f> zbAL8A_GfeQw>DEO&eW>2#p0}e_UxsNXV20M^6c4}fgK~5LH5({0LcW`$}&M-lCgrz zXFW_GT*8o#C1IqG#XIOtA3(y8pDtmfkHvV*HBExyHBB8xU(?j8IgS9)nF)rK*1rnb z_!qXPz^E9jyoA>{an!d9i&ezuCwQ9=dr(XVM}Q<9`kE%!fe6ss>HyC<0wkgJHBD^* z6d?k1P-v`a>h$Ytnpz#uhyWc7x`VUQqm*ZYn6>OHO1_b8k`%}_O$pUr)0C|e5(>PK z9_2o0z>m|K9-lWF($5>oQU(vHeeH7Ob;*5NALsZAoj014lI>4y2(40Fmkj#WnFE#7> zHeh7?FT;*wiUN|Ta4@Yy2FS;jP}0X{Aae~_wx4`#2_t=MhB4QWrIb)K4KR=r!0^1| z72~uJj80AVHkUDan`Gp`QkP)8SU+QQh*H(Ij41Hr%eXlj+T9{C}P*y09q*#_l?VW}8o{^;FCAKj=52h~x&E!_;pYohs^(8|rQ9Ny%H;lPrO2 zdr~{mxhNS3F3xJ)N+w;oXHB1pzJc-(3zhKOmqDm~&Wdop)GIqGw> zHf%5GEJo`?R;+jZ0jr~BoQOY68C-}ihr^X@R@snL@XtCO=(eqBR_+G!iHytnI!^P` zL0lQwq%~CC)`W{#XvXd>+}a}-!7BRU25DpEao6!C$2bsWyPz-=j|`{cBYnxfO|{IR zKh((f&g7m}qkXyTkiVFiec2iCVy7b(k}O>|Prw=NOU3F}e92czC#n%QCLp2k=!wg_ z&l-s0zn&vz=QeFk27~ova<0qeb*L_9tgjZRHZvB7&uzR+C6XGgq=qYD;f`b~UsTUKuGkk&Pwk$;3T4)xkNo-<{@PM=d|R)o zh6WBB&5cy!9$JxP>fi+@6}o9CfHPiU*Gkg6Vk?)|1d*ga(-}wVq|jVN)k$p5@bdow zWN!JvfpkXn`QrZ@t-avn1LEJ5D7tZpY*$4g*hwY-(U30Y& zmqNgOUwb$zqly)8eKb>DYNm7Jn|orleB814&x!tG5TV6Hs!l+4o*AL?Be##!)xc4LYgb?fnFI+(!UkruFVM26? zI~Zh9Bo0Aq_@e+KO)%(Z}l z&sa;6#rrSA;;m2%J8HWsC}T|&=xIl!_P|T(#HH_HEHEy4dmp$JM|AzS;z!B=%`XZA z-Q+)8BN2rD^~HNcEqM)moO-b{9)gu%^rXDJYit~sTk%bl*GBp)QQ__#P3IbkjK!+i z+^(1}I#7vj4rBs0T&dD!(X_a4AoF!kcBFQBwl6~b#H}7KyX0rf6U+NmK-ui-cpzkQ zm#m=V+|@bBT{PG9GWWpS?riP#xWxvXOLJ@WTL^Khx-J_UJZYRKgz?GPtkzh}ZCgQr z!w`=_tmGiHfxW5Pp`@fJybl~wP`*4lDbfd*6>xkKtI796KBpDzQ}&N11})Ql8QY3y zT35e3*TuBGlh?Ewk8KBr0>&WfSka(W0=_O1I{MAZ(kw@&x?JJufpRnDPWI(S4)3iT zhFjA5%Kjd@YR46wO4g>r6C;yL*`A#XPG2sz2#NN)!^uEC(j5(joZYq2La~{41d@S< zz02P13ItQ>?sU34P#(%x)>qLh+zyt)k4c$_4T8+i;7UnT<{3t-q2~YtzXLmh$+ z1mi}sjp_of=7?e!q)|rNMv0k5ip0EEVqOO*bEHVjMH4e%7_(45#+WzE2*r()Xhsv_SJo>#q%hNPY>Xs6U}oari*25 zgvp%ePGXdHV!8$}klXc)$viK{P#nvYd;}Cc7Q)B-S{dP*GH7o5yx0`9w7(Gy;rk{rsYgKC6HMOt0)rk;k3@^zr3iL= z1Ot_+TBg*UPK4b4Of6O!O)mCt9~nEk5iG{j{W*XCk#us*?G9I(ndDG0H0cN=ijhRU z5EwjaqIHb;;!)>};qX{$q{rhZ3{K=mk7<%!+=$cA-%*#9gj`q2D0X4ro3yS36LPpD z9yt+Xl{D0N5O+*rEz%aXL4oORNl~L~eCmA@8HegV%!%6rRflHNyroROnTiyatIvqd zt@~`5`IFa+jSgY6dHwGW+_v?oY$7xnkBOf^Ff?UWr$GY^1>z+X57tNB!o8R7*~zpi z@ynnnG>7dSDVi0VkN+QNdSY_&*M}wxe8YQ`_O#9E|5Dv(SjlOgx9P2BFHfpvdY$0=H$~`V$zOk`yb+tZI z@&zk1J%4IlMq9?BF;_4Xc7eZKH|sZ?b~EP(qKMs_Y-GvqOBjlONEm&MsR|f#jY-0g zK9MlGJ}Dx?W3DkBSeLQJL_lqeE}(z@E<%gp{~J76f_EU$v`UP zu6L7in*B&z`YO;tT1RtT38Sw9^`dp=Do_nQ^a~YdL158{OMm^}TpL{_-f<>QLQ!pZ zNnZXJ*$TCkfG(g1#UE)(7fPOvgAp5T;!*}2k$cw(^IHz@JH_L7Xd9f-q;GNO4k3Qj z`iNL;efQXv&5d~Xwol%;Rn%HPqp{7k@c93GDYNn%Kp61>bNovf8ru>^AKPK$PIWEYGX9m=VZ%op_VL_TL&0GAo`K^A22L7meQ(3Kx~KJlV~NX53v>COB$uJBpc(RzIFri~hPW(YF4rUswojT| zmN3L+33HJu<7s&SwYe;z$Rm?b7q`sX%vE%TVf{%kHtiba13aG8e5&J)T#1P^n#6R; zA?YSPBw?<^COx#RhPIjdQ{o}bAYt~k+h%G82}7e&!hGNj##1&&C1j%kS|386n&<7D zeBO>GHM|l?F^#vKw@bG6i_>tshsx*e&KnlT;=JAM9p~*H;-2}j>=`NlbWYCf8Lm|& zPhG<7-A|N>5MU+^%_Cum=Mv`QQaceIljn4j8`PUH(q}mEydCNO52K9*@_G}_w>bm2 zKNH)U59thiH!)C7c*S~OvJ{H;=7Z}s+j@MP!kI4C-q{%-KXj||8GQHZ0^GeYuJkR0 z#-aNMV>tL{vnc}uijuF%+n@)VgXB@m(=3Dkmju(79S<3NgzN+grL`284@Wf?eXeEg^Z`8$%Isnw?Q z`RQ71CVvLLM_{aNs#aPD1dmBl=pGw4G<#ve7w~vo-Q7Oi`-r`t!hut~;H&&H1~1u; zSB;aSqoNs&@>$a2?zlKzo*osEp=P3WuK0B8YO7+iCP^cfJT)6llj!X2 z=CR{8H4;8t8G{I_-(e9Jdowh%wLZFIATzRYd+%?VryrddKW^L7hH|bi>Gmcg-fnwW zSFJjhs_z)@*|ufN_Byyc3v!etaQE*((*$vLh(x>Qp75;XY6e}#UfDGfB^l+gjT&=M z-dWl?>!e-v*5Zr9$K?hP$FREmec9SbB0jKC8=B3#J<)t9R4oU33rEzN%hhmSzBrbt zA2sVL6~`%t;qDIRCQGG8+GUvt75ZbeVCx9^dyDDn#(~^qugg`Rs}CR5fFur~5kEs; z&=)*$?>jEDhne#@92Td0g}bDV{T`F@)Y{N6Bo7jZfN>G`g8Ri>>m`w%-m#l1Qj2DTaG zZPa}f^aU&4m^>vrL6^rnL&K$5AKG6WNQUcWUtd_vv{s$|e9_;t*i4P=o^Uk=daXFH zghM6a%0!_!UI}>3^_KM2wRfg0L`CD z&#o)BUKOXL#v1+MlfQNU3G1uN*MIb5hmppd0Z+b#o1XH}|4ZaE)$Q>Si!$r$S}B+( z-{`IaLrvh2P+MEjm0*0Yle`HOfjloBBat8lSoBTFn7!_wK6)TKQAyQf<;~+$%SCUZ z6h-vTKX~+HbtD-q4re0R0G=8f4P>L9NMAnR8%CtX*}rw5w0_**-9M43FZI?o^hElq zdAGaV-%Ji~>z_{#XQK6T#^Fepd!m^}7MJg*a>YQbClRkjqgYrWE-iAOpATJvz2Dhd zt>KXb%o150L8;+z>zfXuK-f#`EVYwaWC;b9X{^@}&={maE?)K)wwaJn{G1eRJ z&H4hlo@jBn7U&B0juxuxhm$Fs(VE^h*gR>yGo2bn4|`k=`@rERHxL#IVxLN8Fdx7& z(57%MkZdgV_H7x@EbiHM*%yw0pV!vrE`@H*LmRA$;x2`{R#NUY=MpQD^Th}hQYQT&Up`D_KGV2 zgGFx6xez&*GKJU_!#M`cR`CpS+7w&Gv;2H8djB2to(`-;?{D9cUaNSHu^7}^1%hBG zVbE&DH$>^#pwuc5e896or&aucpI7kZ#w*lIR4a29R4T8EoO&Ui!B@bakLOGIJ#iJ~ z0smfp290?BE4=?G!xKm4`wyt^LwVxos&bL?qPR@G6wky>&d^j02rM_G zvf$LExW35Y3PUjrnJ9 z)O{GkEr4#-eVF29gPWVlKrR$2cRO+Ui@h3I?k`Q|QbY5lE6IIX71{c_(MB?sa=UR) zxxLGpEmo7m#q7lRR34rS+<0O17<^IK=-yB$7%Vm1>BgQC5%Y9&o@^i4=<&6l3-L~O zTv(yYt2Ril;5JA$)h`tnB6-q;9#`B~sijBDxt^HAmkflf{z7y*J-SfHMvCF=RQ-i$ zqy`1(c6Hfm*;LM>)t$kF*B|wGeEDc_tezco+Tzt@ZUo=bHYAEYl`KU=tZugh;Sgeb zmooqS7AU&y9&_^gfFZL^0eiDV^&)h74D183T8{=!hEXPWK-)yD3QLD`42SVj!I2&?$!rt^)k)%l>>99VkG@?bsVcKVbFL_sJB0Q z&ufXHzTm4={YI5YL0J#!ev`gljArVrnI#(1{%hs&0dmOhUkU7CJZO15&?4Ml@mE*xw_srzRE`<#V(#9vC{ zcGPeo6v-uVH{npeG?_0<7w4C@ZG6!etfI4=4pr-0?ipE^4VIJ9yx)OuPz0lyu~K;| zf5Ntv^91PJikpwWi9Hya;{JxF3cgbjl0|nMUpy!#hk6t09Z9?mtUe%~QNIcuSAv&3 zz7WSZ9BuYO!G^5?wpz7nf!5PBSOg0$wCAr}lIlYe|72u<0|TQxn+uM%y+B=SpaV&* zh0Cb;#0A&x@?Te`s$p-;=SxK$1^-kr4JW@eoVvPo1Cb>z`BNxcZ#~+YD;1ly3U1AJ zID)=pDjTjPA5nmDHMW})j21%039CGRK zR9T)5p_|!DEeezN;`k^Q){4Wy!`?VX4o;@s+DX9txT+FukYJ=VG{ z8~OVu)a@UComv;vXf|aCmIv9CC5uhM)@=G-q7ErAN;K-oCa)%`4HGD8J39@lTv#Gv z?3Wwl#*s0u8RTUo4>|n-hdt=`Oh`Kap3mv<`5X@a!(Uek&bGiSkz?P2|J?GZL0Ll- z7I^8P#z&!43MVpB)>II$0FvQ%4IjZc8&)w5bmavd3@kf^fr*KR z*6>lVicXDGh1AIiZ%YLgB8$Jo^h1yf%z{Q>#$D`KGxJ5*4m;6FVy28zQjVk>$_rD; zc2>9}HK9gXEpCK-cfTrbpFy@H?khyq?|sjvrdg|DrdumC;3w0Lejhv-j|@B+~(o-Fhs zJbhUR$eeYREvz11hN1xZZ6&AOSf8B^ZsKybzT-=z6FyhGI+X1U;s}(^)DUQ$+1x&_ z&Y!4C_@|^*cD|Eo$)>8BQ^BeVM<4|A*I>vd8^YNcP5I91RJtxv{Y;6$g31jzYg7#? zTbXBTCIcdEVG_F}_i;Uo&!JNfEo0G+E^egBKTW7XczY|t3ZtVbjA`Yr5fZxs3W@%< z87<>T4Dz0;RuI{g4rE4%Of(Xa)ssMEePtPuoIT1t7*7{rJef8_xHOI#M;~cpX5^?9 zMl*YfAI#U_EQM(^=3JBq_6IefaM9E7*dNRVL%9H6gV~pY*+3|Z$S=V_Hb~N-nw`g= zlR{W$sTgod-k>fz6%Pc zo>zBM`{X&BH_z&AySesm!uss+1$-{MoVjSFp6$T-r^eRE3!p97j)1=8XlWN7TZ7@* zWVI(5%=M&Gz3U2134bQoo(*kmM$Q^+d{%-$qx~0C; zs$Att^@zZ%IfBw zJvA}uZX7utm5$SV8xgJ{&-A>2J70>w?&e)J(vK>K7Lo2zdjjUg7-FK89!D!J6s_bM zX(ehTRGls%r+M(dZ>7h_wbHr6x9~fpHz8$hkD5*15=Dxy5B@K4(Pxlfe#m)Kr>vgu zZ92HKva)M_>$2SXyN*2a2N!1hB6F53pEPOtF$Yx;m8*d7CKaF6R%UOKMF=k;ZcwBC zxWU^xzpJvc^WY}${MA#Yp0}*0;-F&)&5}8hzU+lRIP%E5)&pDn@DKPc($C@l3L*lQ z)oQWes&a)!s9Kt{lD}37kVd<^pulp}>tVB{4E`VrqN%D8vjjZi-`B$XI+t3-oWu$!14454jdL%ab$$b z_3M>4p2-iMle?w(0KMJZo4?|$>g$6)QQlCqYk}o(fyK00PyuzP6wVt(>1w$&p3BpU z(lo{4|EVZLMUF9ZF~6CgMwXp4?J?3c(+;HkyYMuHr(~tT>)ZG|_}5Ak{B(o7Yo4UX zb)L8bQ<84&nblmMNYppYrt$i1ek)%uaLxE3;hHgxaJ_)1{dnrcT!iD~eB<+I4eDTR zBZH3X3s$QP=^2Dgd8;ecQJeALjCdr}J`4P-V*&XdX*t@!0t?1wko53W=u9ST2E|aO z(c>1nG1H#Ov~#~6GJ!w9?=CLlUw(CwbYfuUyYJ2{emY6GJ5dhceAChK6ieN`E zsHz@7xBv$4s@(+NuU@aynXzs!Y(}yhH2~4%A|T`%NGG@Ro{q0B*01|AuWR@+uU~gT zM0z0lBFHw zZ)Wc5LuQ|LK%H;uq^QnlvO7}2^weW}yN{ib^}rZ!fr!N|Jj0J;2MgUHU56QwrK*mx zx`;!P9Hi}p`$q`LG{Uu)PQ(10Xq2C~9n;+Y9|WYFEj=-``I~}L(wuFCdq(9(Bcoj1 zY+uFom0mF;ryI-QO} zRl(N~3OD!{C+ma3`ebE&C|Liabf>Q&7;5l!rYAX^4hJk?e?vIj;KK)}!|4o18zRyA zDE=e55%IQX_z#FDc{#k({g6;o2C{XNCkN71Ro%_~j`{)5bQ7-(Is=t~3j=ZhVQvfs zyS<%p8|#0b_7LEI|54A=u0?#JKeGGD{tNhUu7f_(^rX<<8t}m$H2UfI2abm{$T@r@ z3(@FrR~);!T)X1v{aaOkd$3j~yR-G-w(05ojD`Vj;EDDIPpzKo&Aw2Dvuk!${=l|G zL&#rW)fLbCeEG&+*_#UW$PS$+?3CZ|)OJ>7daDhHRLv!`F)RBLfey)`x43O)`InxW z&YH~37GwW8hRRy_Br2>&JRpaXK5r5>mX1w^xcaGdA$XI)j;U@$VQ_gTP3rD$05@Sm z4l)_FC)1%(Y&gP=a#ZaC_BW{gPV7;^Suw;3^^Xb@s;xLE6>X&r{!94e;xE=j+v_3~ z^&Qbjb)~r-5=}C6>SS(B@tr^TD?uI{q>qrR@foUGYvZwVs+0sO%8`CHNtaOw-EplY1XoXf>h0bUB5 zAdZD79g)_#5NL)dqlju)L4eH?gi{6mj|_7o9Mx&rA)OG;yQ^ir$>Z|IymH_iTzj9N zQkR+(NMetjWcN}i)}YRC-eAa3-TRQoJ-{tCbG)~X*LE(Met1nqWQtyI^E!;7$$bkt zi@RnYk%bn7XAvIxl{m~l1)nIs)u!PyXon%~2)j<~FofhyLoe(UdZAIQ+SS(TA{+}O zRZQ&U61stA8=8KA?8qu{AA&~F3Su{gs3KO#C@}5&owfr`>*AGdeSP8D-r7og7MqPK zTXMn1>V&6$ZgU`?bN3I{Em73N+^deB|Lp^+-A+W6u9tn`kjt6wTG)B`*}Z1B?S|gZ z5=oK~4n+9*8~P9F${5QJc(K7tU*WZx%sQPlV$-dPT4_j#Z9tu!*c452f#wdaeoQs| z!3pjve`>gR(4mKqzp#8Jw#l_RzxLMh-0&y-W!!FHkWLVuT7+@LK6lbwM)(ez1_HG8 zjKT4Q6-0)0F5N1>-)SP^S{t$Psk0H=16xBhVxiKB!VJ`S#*fR3A&dNOIyXcHPCCpF zE#3$C3U_*^`dTyo?(B@?y7`LU)xCcQ6tBX4y=Dt8k2VLh-TCgrCqb4fy8wR;8%GA0 zFq4Ha35U&2jIF6l)Na+;|XVrJs2LjdAb=cEm&)f61qx~)Yh}uh$d+|aX zUjtJjfr??LRoMwBh!#ko@)Z%`Qi55edcDdPOli}J za+Wa7G315oDedn#u(_`?-h&8+zlx{rlD^Htaa}goIqdcdugjAUo_VL~2i%b8@W*iC zQ*SYcJg$J%9ProF1@qx#lQ)jTVn(akZS}juF*rphH5C7q$a*~4N|&^vdRo4IR!#9& zwrE$wIfgEaWHdV6LBn^da@hUZ=yG#s)7zn7JI;uD>{xWgHawTp=%}g;R=NY>yok7E z(Aljvm(?1I`f^SD{BTnuTwC!h=^?m$_^05w5!NY29qFNHe86NLgaFCs-MUpS^lRD- zhCUq$?QV&z5fdDm3a684qxvq>MdM&LI`yb~EbaWS2fK~GH6(eD zI*Nzh8&8L~ukWnBzS}1vLxz3D_zR#SB+dkY1|Rk|#|O>fuq~on6%aIlN0Hq-#grCZ zPwN})LA4i(3nl65HL~d2E~yvSdS1G=bgSegxbLEPBEL*N~b-I9rjgWk8V{>cQ;p*ET|CMTOriJ7R~hlCEMOw z)~id#Ul1xv87syPp=FgNWw8@ra-_P^Z!}G5)5*93G&4C`=dP)*s1E9M>(0Qo1edL% zt*Ig4O}itJwuqy?qs<@aGIjdu`Wn5>m7VT{voOCt94Yu>@j}*~@O4I;ec?tdQ(E0H zH{1Mvhuh&DC0|@Di&>IWZ?s@Gsl%eHUbo7jBk2miBq?4xPvX8kRMkQTO^A+ z;MJ{it6i+KaSbi13VT$%K+dMyyCrPuP~L)Rw{m;ZN8kN!{)QX!;?*&2+uUn69Pr?4 zot;leEzvSMF*_sX`Jdg7TWkU;SH>3%W$u*74vD|<2Wp@v#=d-m_{&D zaM2XEKqd<80Tk)qy?Jy0efKSUviH9GdY`14skl(HQNG7|47)VVp*cI0UCSTjUjRE+ zWVy~_)JZ0Tq_{xQ_~Zw{wgk+p$RWeezwrFJ3od9}b3xO(3$NX4~hV|>~Hf|() zW+v!-NTS#-PFM}l)UE&@wBQaea_>S{MO>OB&6Tad$z`A6;C$GF8#HjqgU1t%x!n~J za%bL*YaJ@0xYnWgMA++#&?mAB>e*KC=_!X&6%5@LEY2LjdA20dWPpxA6M2BhnOjDVT_Pd$T=7Uj(cK=zxa z2WDQlX;Xi{(kgCvJ^F?BCx-n==&&5_AGX6n-5~Njkh#ZqTvA|4=lz)9cH1`UzuPYB z@5gAE$$p0DL4T04*n_qRV`4RX7<`+_EqHo1#$j~8h8-(>701ux?D(3=MIf9!9-Wpkx| z2@OSSF5aBC6+VfpTW$?p+_#qlU7WN?D!ym%fslmSM+<*t?iHxmLzs^$W z@mLBvUCu%SiTnW&IEr(C7G4$i#;7e(iW4NDFDbl$n9Qhq%dxCOU-_QO0z4AoNEbH$ zClUpOfeVMaGU-BP@grY8)cRXyjM`A8t%*h+7x>DKdd@k9Q$slW8 zbvDtJ%~g6T{l0{UZof&mos}V6OW;YMR(42*4=`VovByEIL}A3G9Grz{u(hH$pgIs@ z@cxZ>!N`f>z%4$?@_~r62v2=3!k-R*Tz@cK$#O8_(ow{^dG+ZYic^i{&z|~lxV~pu zl;0UG9-G21_49gCsUCXoO?5;X@THrQcH?0K&3R`JyZ9DqAJ#@~j|N@ji3K$<9bg)_ z3u_rSrz)$!LBGmKx<~aIU&`%>dm>qXdTO&TVYeq7*rS-4y4PUrGZ@1ib<&vw3-U&N zuig;sY#X|w_uwq`Anee=9gaVQ2E+`T(bPJXipP^lo7F>;33OWeqpcrYZ1sju2gko! z8?f>>vJy>GYt^%Z+otDK`!mfITfP%(&iFf*&z`xgGu2yt#@2?Oc>VzC*q$>FZ#ryN zyKDMXZ(;Tkt*2Z)Yv$FbOzGLi?$JR_ULUMs3 zv30`T39?8EdNJeldgW%BXCEjY^w73zu9t2dy0N}NT0ZncDK)ek_3Id(!heCDZ)L8n zCagALS(VWR#Kci|l6+E_nx)+x{8H?soB5CJa=p%E(0%oR^y!coVY%T$Aqh93zd&l3 zWTVk6=`7reZOyQOpgu7D6|@A~y(Ey61+NUXntS?b_@b`~{_-~ge&&6xh~2xqv)fd> zu6UIZks1*31H0Nk1lLWV3LOs@Z-UuMIG3Od_R?LMzdz;33y%3s*Ly~uE)<8PLxv8c zoFT+C`UqzWTOeT^E%gpZwRKfEX~ktS)LdknK|`Q!ZJ69r*c)X`iMw`{sg|}&5j!nu zBSbDL8T=s~=Fm8AnD)ZCIAHeJ9WjHU!dn~hR|RBizA+rCPh0qp-Ln=gnG+Z;eo|9N zI5Jb~xw&{>u+kZ_+ghU)Q1g+L+nESPJ3A-E(1w+?_eQ!AHr^yi9h4kQQGy>WJAp7B)E? z-F9pD>2nHatyt7vXq#^*}P>r%bjx+3rk7{c*3!2}fND9t0!qEchpUZL3Q)p2`kkOJR>! zESxqCeRRnj{i>e2y0$8~;%S4NO*?)lM5Q>6Da3`~LIF!Me8O6ei2nZ>mqzmY_|D>; z(WWT=#~Py*jd82bZui^hHGo@I%YLx=Z1}$hFmOn4c@XjQzn^bf{+oTE!xOSm! z+Hk#E1VKFFk#MD>?Q4JGOTP9e(w^09DJ&wrSOspyi7b0-OLbjiy}<&(8yAXG+lo_K zY?)Zil-tw)s-4rBNOaDr#d|V|_jGS#B+}T6%{JoQ^_}ljuUxs5e$i6Ud%mKfFO}}0 zBv4F!4HcE=Y z2zXq;^BVte;HkhlR0w9Ghv{ic5-u%K^cH!*Fh{`s@r9&|AD~k4IG?3bMiSM*a{SLY zH>1!aW8KV*oj6#9(CG0Nc&^|zW?-W=@p+N%23PSV!xMaaQ}LmPZN=TLbb*v8_!9$v z7AyRb%x1mOWI_lr+O|R4NWdKWBY&71bCxv7dVT8hmIne))}A3PE>5{R`O%&wii17~|spYsMWPkz1l;1h=8OT-{Pk8dwNL>%HN+rxZ5Fa^P(w=@oc6b_0E z4D*pu9OC{Y8jBXT`CG-u6*BlNjWnv6l`ZG5V-JYJA)PFlO}J97j6)eC4;tly9$S)M zZz?`aEaJ6>C%)Un=Ra)YDUCxG;Q5fhk9kA5>KOL3(T|&-f1$ptyrO;*At%Yb@Yrp~Wx?)eFO_tm? z{5di06$V;qEd?#y>ZXm_u#?<5)WVO6YU>rtcUJuF8~AB;@4j1CygtuQ z5B=c}A>g;Lso=~%1^)U0zuu{nEmo`9?r?xF7MI&CsZ(D#)2K6vz9a^V7jtZI2Z!hj zG}Xq8*l+m*q5ngESH+7jR`AP9C2wmQ4u)!koQDT+|2VCRodasKEO z+s3EZHaf-r@hSF?O2IqFra-&lrYEhgXu-25NOAV46k8`qv9*)}1&-y07d>^qMhNFf zJ2nN@(cFA%-zZwy{;~Z7Sc)qJ)4?-Q&gc|cIa<5m*NHfyHe6Zj>(!nvdNPeGh1#KM zBRvsyHX@a}a|)u1Mjz5qN@*LFI4bQZj7g;Cp9uztWMfJ$!vp$>C8aV%ph9M8gri18rCK`Ds`XaOu3_%trTks_zc4nG zolag@0kqFL&2sQ_G-4)0yBzx2RCursMAU&p*x~RZ9Ro}MTkI6J0_R)LX6LaB5liS| z?DM`FQ7Ug{x3TZBAF>}aMr*vnr;B&o%<=!(&+e8pTFz`KzKEAIT8a(-3*Rnn5&us3 z-+P0%O)8$tKb3d?|GO=vd56tAd|q)YUJjdAbp0=UJ9nP=cf$YP8~nlAi2omqIya0C zT1%M-M#f;8%)bc%wjtbeX+LI=7_m50{lD}+x%}Vi#?=icl|PtFPu4ZQ)UdPRrN*lN z@V&V5^m(gC{ua@{iJqY8MDzWhzmQ}dnWLLF3;P#_-k90=*ctb@xi#6lBY1M~-TE-S zSNHsm#YSE&CDxI$*?TPZvGe(HrHz}(qZVP-j$0qU5LOY0X@p+H95J{5?YH0NU*Lr( z97#iZM~`?Jln@4{k`@8^MEcHq?zuWb84) zd_8CU$1~3)pLxc0<&{_Q0ovDz(MQ0n6X>2dBAQl8g1D5 zfC(~m9|a_%{UWf?lTd5e+2H0d$!6Td9jm$HrlKU>03@c>Lxa-b>Y;tozSVG5NQ46bU-RlL8ulrJo{FEbq(=lMj3U$0kUZ2O(9F*fM-`FX$EqiE9hA% zuUtK360`_9*lJNj(!91uxuVjkLOE0dYcl?M(hWnWN;j-ty?QTE4xUgBVVB_=Ao8l% zj2!DyIOdFduu;0k6KWm>38{PWry#otxCr*HmQ2Kzp?wNR*lPZuuoP%*)`EFIf)J?) zMOy-5|09vfoB zs7sK_yi{!H8r4%2l!Ba6j}Z*g8e8o&Bdij=Qsor20M@bXR!gZMnvjlkN7%Z8U!o5Q zj;VNwbo50+i?Jtq(x^7&W<54}TI_a>eWIU>AxF#;{T%yt1RJSep-B~(SkTFZ^M1G8uW$-b$9y6FmvX{mPO^#8! zqMDVuw#ccD4mGFPgHJ;Pt_b1|4O5Ym7nMTyjV=>oVi`t+V~b&=NR_L|xo($q!!C_d zjmlNXAyza+r!BR?x?TJ!k#q>Na-}}2z-jnowi@9`pa4P2m?`BT_9B;Kb&tqL^$~g5 zI(#R(s1$XiYjs)2pCW>&7AQqYsg}a^Xi04#jc&koJ!m?Sj|x%pmU^?;At6(g8I42` zHqx_*gMvy0!{BNSh1TXjnuve2#+!6+FMbNC_$gIq@7|$p8ecm2kH&Hk=_xe^Dy98u z=O_-k9>Wepal(v*j-ZW>jlq#cd3Nn3{!QB}P5Wx6D1&XM?`j#N_=hq`6cks=ZA4Bv z|6GqDs7#4{O8rH*N1mR&yOc`EZA05e>LH|~;2yS@l+mYfPoWUIP8Awe44_g$AvA-K z$TSo3VfVu*)u7=ZE|QwjIC-wBWl9=G>1iF%Ff?+2!ytq+X)MuaXtiDCE%s{*{+b}y zpjf!dCnYE1s*ETVrMIDMqMkTLd%eN6mB=}?5AwGkr=+%m!{cNpTu@@()qno^=Wp4I zf26UL=j!McT~F&56ZIgB1u+7Z4n$P2Euyb!bgCK^+7#7S ze&^KR$NtVYmVOucNe0OV1RSE3TXN=+N~J$&K;r(Ey*;aYu+z~6&i_T_JlQI~$8Ffo zS<+rOdjtx!R?ud{uA>s-Pqc{ADmuuHO=y*Eq74aC<$xW&Pnv^aMlUFNreq)O+}fP zP3EI33M&gM)QSMZy5e@!;=5`sa1s?uEYuN%^dBLZdKgIE&I`rgVl8P>@mc;y`b=$& z=wBqMwH888BkKV;f$GRBU(&SJ*NJ7yY}wdq6}>z<4`NYNb(ylWb(jk zX_1l=LcnQ#T;MJFOcXB=yv|1zwW-s2J%DlCFx}QsfQQj<`{lSs6)`lX9tf;BZpvDQH*vc*#&x zMzknLl%iifPP*Ee>v3)~8_^%BRYlk})vSV?(KJ&}8I3*V+6_I6q#|USYL1LZMH*X* z#!+M&swlFmBO3KH(;Q?(C5RD;+9_j_YPNkLR)rt;=fkAD7@-!W!_6ef2OnuY%M=sHZq6N3~kkZ}; zSv;D~QF&-$Gp^l*wihaIB&U6%8mc+bB0?kY+NJ4aVo$l|Uj_+gdBk9J(jWz;bTtYJ zZi&-PqTAzb({uCYwH`IMvvPGyZhBjMb!F$=qgso7=tYWFSMpnF<%9Bb996&}6m$jb z!SM4ACH=g4lovCvrxa9#R*d7lyaR3I!VJh_MgvpO2DA{}6)qUmYEEPfwV6YY1tQTtu76co8B1e&7hp%LkiY&`5K*oe{E zvsY;Pz0mYZFH!X~Rwi{ioZKkakU~e6cb&0HUvwkUm2{}>CiJ8cosX#Hb{hivGB^aV zy#43h&%1WxMUl*!qNjK2Vf627mAyJLM)OD2^;Cb-^?RY~wfg+$x?ZIp7`8)ycYL$1 zS7@D3*JJbw2~egTNr!HSSAaSj`UB=(LU)f^nGw>>jad{)wSDSDP3;ko1?ngk7HF9V zgKRgF_V8NJtV{GuwS4MriUq3l63{?tMM}{tRVgDZ2pU2n!VXfQmk>r!3xw+E0R?6C z@rwFrYiFai{Y0}o8WR&}r!n(9!8M`3N2ZA*x=r+4WlAp%Dmq_CYn3+$rzz^7YBaSp zp+SjGR7K2YOSF=Pfo{^gOjHhOL23n66KeBWC>PBz(RACyT2vWfipIwn6Vo|Wky1M< zw$%iBRO60PL+&W4QshEkAWc9Te~c#8a;fF1HHF9#YM5r8O75?-PvBzItedA)+oojK zlxW3_71oI&Uj=71YcwtFQNZR zJVPy+a^mzHwoQo9gsw!|mfq+OK0-LQ#IPDEv(&!e*~BA9$tzk}tM{mJqn15^d{A=I zyrEn=sIo`ctYuw_Y!q0Ll&8$&AxfgjfvS778Aq91p&Tef6)TE#6a7H=3d)RIQ9@d9 zf?82+Lz;c4W0Ph{BIh`XB<3HgK+^iP7!{_?!U$GIujZdF^CXR$Q>eXKNSM;}Pt2N> zoQfnZpJSDHHLjd8ubNVZm~GK07g9{rU}RQC_zAl@Ei3m4mCeeClS1~Wl1FYEkvqyP z7j-U4b!~2@NNaU+29FQZJnA$K;CU?@O2S{M`r<`d!XGd62f8L&&_wQYl2xlkEvN;B zJ6;`%iXIWZ9#J2%B}ic-qvGpP%cgwBGJ%YIGzw@|qfjd{!c)EvdCS@pF%bvsE{ooZ zYyUjueG{C)f1oK%Ab^ghW{SgBjAqT>OJhrnFXgSYJ_6n;UN94m{f;P@acF1UMCdRs zsqgQPL!!QyN=eqiaTuF0!!&kH_UJX>LkC zR&y(RwJ-|3N)LgZq)e74n5lckzFVBbbm%>tJ87CAcIYWDiU1QzSV2YIO_>gh{zhok zc{q6|MQ0bTMx_S|>a93yVKL#nv>t!L$)jDcJsP{HlE8{?pTfxYjw4U#+qZHY-2+O4-T!SAnXY--|= z{qj{{0@^X=h)HMEJQbh5zE$SW3y6id}p5X*v%qC)^By zlV}ICB=bHJ3)`O<9)4t17(%Y4)(vM0SK=Dz{pq#_ed%ybD-NOYvl6Y9;80^WgzJ3i zt+K5$)lq3l7o>v|ld75y?Ct32o>^Z%v%90C_rRvANr^~nCeu=ZGM%_~;;(2snv0?< ziaispUca1{+xNy_c_sd~bY$`Uc=1i1q!SS4;k}4QayDArxz&t)`8sm;2$v}W*U30z z5bTrrS;q#ZEnHN8OYh=jU!h()JAT5JP3u~J(soMYX=`sIt%QGw^l%Q&9eA*p!fXK| zgU~h9%P9h$F^A&@xb&T0d`fQB#WNQ*FKd~WTyW??(mA;eZ#*=m=^w#jF_t>{ihJqY zhI&pKCs48F8k!nW&G={1nPkUf25i9@Hf3guqo#DmG?*3PI)l@v4_xuRMU6+?wEmNW zznxwf=svj3)_Lr`cOB6=|Hls>br#P)5?=AlJ+la-Zg@y?N!Oy*hz7ldISf98w^G)D z3ZN2L^+AMqDg->iwGmRMEqfw=P`T#=aJ3MK=V!hzYmHFxm23lBMGZZax96bo247Wz&;xSF_LmfhRwuN$;ymWN43c74D9C8!gd*7!e1t^njnY)+gF39epPK zjDWnVQEy(=fM)7P)lfNVe7!2qs9qv)m~t>qMA=8zDj+Nqb&p_%f*gsvnc|Xi;RxiO zI620@U+{JGCF^JAJ;}ysy2_tziB|Wda9>(zCE1p^d2g~c9C6Gj?g`cAJs0{a12#vf zGVh7{EY-M^#A}b*j)>&^)zhmJU2S!)+PSGH?!eA=r$Vju8SCxNNHAoV^-{erm-Q|V z;8g7cwp2Fn^QSAEyx$y2rd`Q;+|BOF)}f~l3~$3p)?48Xc5TI&vEcCL$k|Kv1Sw6p z((Zp`rr>&pQOyfHy~$zt;zeJf zsJ1GDgMq+Dt&1z$aFveLX${3(pUTqCcB$los!HYS2*DfRfjye zo|<4PX!9lmiA1EKFKKg`Y`6-g79q(Dws0On%sXmpdL@I^EoygH)yLiO z3ctZODU+R?^XH>2l0mkIy*7Ue(QZN^mo*$sczoHIIZ#L?C*^}YV)TTZ?r^|scFJ~K zjo^v~JSIK72Z-auypS^<#5L=J1r^%L;(oJkdeakp$L8)VG=OS8WyCQ((bHfiVeBB@ zj&ihFIc!Yhhb{>(4K{f4$C_ANUp*xk%D03&rd!%{lGSDMTOE~APq-FW^#zS}{wXyr zhfmF9rySAJjA(}8y58B@{H#J{Wyh@Q>RBE1es0ao>O^~WptZ(}xrM{3*INQr;doQb z>T|>rRo}|bII6Avs2O>@x7W@YXuyT+U2|*e4(duJyXMy8JxNh?v^qN=tLoSQ&d4I} zgJgFG^~OSqYF-~It9cIlA~Aar8qbI**oxVZ_I^`sXZKBMIJ&Ma6pIc#)RwNwWSTp^ zse0?rKaW=hvf*%@>HIqa_=nR!|3Qr;=D|j~cct^7p?s`%z>n2q>|?NcU9!i+oOC=z z##THE6b*&m30(y5sBn;qxgu2$VKQ+SW4axuPyEXc`}3l+)3Xjt*7UdvKXlPYd4)#WIoK0$G2-F5SOTNg&352&2r2-*1=?w(uLvt)?7>#fGJRD69R3!e zzOw;}vKR2BeZCZ5h0x!z<|@Au1iTQ)_z@C3SQWr^JCGE=fl*Q=Z53Wt5FuiZA_Cn% z>H;$*X$$GO%aLL^Qe@>rXCTg~eCRWzOJ5s4nMb87kk9fc2b~ZvbgsMA{o2%<<@8s7 zrW%c;!94IW1k<2|#4%+uLcaIcrcRxTfG{T(Z!1ora1_AY49rgfvz6{0#dMW6kBDs* z{V?f>t5lBgnF>GRYxmqU_3pcGma2N5e!6Gq4JtP{ypKO1{RFW+x>#bs?aoIEx=Ov? zimUBbF)JPT7D`Py^O&J?&aGG@qrQjOh(;WzFDPaX9S6k_q@ciFHBy4#-zV`9j_+Fd zG|7|csn2$1Jnlr?NsyY-m~OA~)GnTF@yDk-VgZB?uIr4oxnebu+yKRp9LP^KAf#B2 z-fS_v*)c2Q%Xeoxm(GhFSy>;m^VgHL0Z(=M_kr#?Wqj~LH+!8jIVPCZ=m0@R+>I?b zPW#!2BlNB?Ml6nwFb7j@KNuuf1SY-M!+pf2;+~!tF=m_4kh^fw8uk#*aJ3-9gwN?p zduuBybh6Ek6P;YQ$_nd{6eZLoEY>$f{7)3+MQ|yfh-+4Gd$EN9vz#(R6|){K{FAC4 zoBB@=Of@#07kKg~hf%@zvMOyeZZI2)Ep45 zk5@KUAnYb?BB*gxczqR4XNAvO;XpfwahCs0{$~~j^`5N=xEMEkNFRwkt;!L1MRr2Y zAfN}$PDqV=+r^yJ=>O_~6Wj7r=I1A$)Kk2}H!U|XCm#2;&7SR=+ECl?GfrF4-@mvg z+I{MQxB8OpjqXGt!2@TzQ*j@ycd(}6?fe<+)+8T?fe(_?w*_HFP%iiZkEXw=k+(NZ zxn%L;OE5~b9m`R0$mIz1aX885pWRnr*tw<^e&ELYXu`-a{SfewB1|zsayH1;uCz<#osIL z<0b?r(y`R=tKz&St^c}Ngf$NYaO;XS5^A-3q zMt8z)d2E(f&T`VX)}T1c`R3zSU!B*ERqA#;ZQhW-?Y8`Z8*Zl~khkaWh`;hWw2=(E zZ!T^qrN~1@%xIIQiqrC_v-TZ7P+1bndI4n-|6n)|_&q+q+pBX~%yy^MVZ$wQdKub+ zy5&fTIb1N6G`5zNxsh0T(I##x?mO)_Wkpb_g4JcuP-+0BzEfVR!)A5b5v|7H^}9Vj z2g2a#%m_<^L8~6?3>vZB4mTpx8EDi|iQvSKzUU&fVAE-*756<@R)S>Ny_;_dp;-ce0Wot+D7 zYY{)0uDo~wOhh5jv+4`Jsv;0i#IlEo9)&v#+S7_if|cRNw!JpGyhF_YaTG%_JdmIr z2Qqw$utIvplE=ATP1qx1L=cFAnlM9R#E^g(QNkWlhBLE;aBaA)4KJ8DbS%Ji&V_JI zq^&Jdqdjm29c_-V1OM9`L8th%e8GYTogs%K>oK^EGJ=ZY zCToY&`v&vBi!3`a442Api;pb2Epp`%H*G)4e(EagkwqFOBQt&Qb&K-79FdSGqlMuP0jJ;psRJ37110lQ5C+WHDciC_&lHcTZyF`neV<^{Ow~&X<_oQzxLjP2yYU*e)#9|A&3t{ z5#EynPKz1w-6+WW0K$bCX>Vi%Q7*~F>mqelP$GtL1QxlTKVCdgl7_yLc3i21EGrh~ z@FjDAa{x8mCY7Sbt1-i1Gb7T@sHpLTN3YL-bg3Iee~Gy9ck{JFw;^&ocT7T5ym#OI zm7wxQPa*MRp)v3i5BdS9#Sn>l9-e&51>%`?*XpgV9+!oR=Q;P+8i z(2jrv6}Thao8i9>ITA682{&M>FOUZs4=-C|yNCUMEH9KnjsK zBBz?CGMowq0ErU z)Nmb@^fIDORkC<_E%mU6dFAZc+uy+Ne@48K2$$$O^&39HxZJ3AM zikS|=1K|7bq-xx8fjcSW8^rhT@-NXh?#1^T#rIe7eX00<6Z-_|F94q(2A>V`&4PzZ z`7pm$dH|yhK5AS8j15jS2%(uptQ0aql&CG2=PwUko?pR-Q&(J(Ivunw!_4a@>0u=& zd5`sQd+J3_g#RRg;3bnKox29HWRO>S`1I3}7dg+6BK#WZDp>Ydi$#bb(;%+F5RL~$ zj-c0}yTNlvHgN}E(iWKA*f2ZT=B~wkt`X^~P+e0~T_~M&R1yd7f|yz&J%$>=jSB21 zN_~jycpsf_92ur5fm+O4fR{LB3s-e5+*T7|*aP(H{+iL0Y09xrpF!8}xdTRNgUY%gfM~S$WN` zXs)~FM5^z1Bp1J0x=3&nfn2P*K~x&1t9F$P*0!LKg6dM&kK2N?8yaT^+FW&UU!p>C zm6%DM?7O8bZ1Y=#Oy_=k>s-sO;DP2G{^L>p@N$%3W4Qb_9+%qU zK3^q0iuskFK#3+fzUKD1qCXxB_$w==>NuiJB1|In4^6sIsU@pS&m>B7wnUboAAXrttdI#U#+n|+CqcJ()nwDZ1cq^*!1 z<{M={#_xSX%l%q26|1~4BXUCVEf!@z_kX?#Cb`X0YT z_Tu~IuPpffJ?RvFm|TJHkBE|w1?Bh3F??VDCBjzntDvnuk)Frw93zSiLVqeARpFwd z`v~|g7Oz|1Wy?r?&5r6^TYnV2N2@={1RL&x_261p<-BKEc;a%2@B>HzABO(zzF3Uj zrH98#1km~txYz?O8evOV81?uk!M8Qw3youp_mOcdY_k*Z&EG2B&^zNuq|`V(;fQ}G zcuQWoq2|aLG~zYh(}>5u5;5WjOGCb-pS3YRC9%Gt`18d2MrlL!nt>AmC~Ypdei?CH zX)g#;oqlwCA@CT5QsXBp4JL<0gHa2uly2l#AbPezUi;N~phs&V8i}??112#LgO~P<0q1(2bEOouZDyd^m_v7(wjkh|e3LGEbCt@Q37jaF#~lI@I7y zwEOkKNgj6OV*!_K>N;+8p*IV_~b+D}65@%oM*rkg6s# z1!W6sORkr)q?JIy`OwMhCwRVQ)boBdy+PjKzEDklot!E?-71Fauq#?#5Kwrkp2WbKNZivD^H^5W(IA! z1(aTi=NOk#g}fG}-X&=NA$yQ5SDs_M{-E^yPW*bA`ux}Oou%g+*hcnFDg9lg=Uaw9 zXZz6#)E_~hGa)}k-<$(@LjMr3n~sL?0|2nAIYJdk1At9{PvI_@1%N@17%WVODU|L4 zzzeW`LjbI|>wgB*0``*ui$wx3}bYKhi4&Uz7rS=dH}}*z7N>TSP1llvw$^#T_73xB7=aJ8LOxRTmk@n(I&t~ zz&nh^CIPMj;CUQ$$5Ds)M~qdXo|Rh|OT+;vD*>9*ptTCL`s!;6d{dfO`R7G1f8{fO@q( z%vd{PtONDv*v?qzLdLpWjP*RhSg($;e$dg6^7^+i2466nigKo+oaxsvHscn+`;5)} zl(AW$Vb(=}-!nEF`DY)?*nz76z&Xdn*xV(62LbyTJ9r8Jd^mUyWAnhDdB8ajIOl_( z^H&32WNbkb05mPw#n>SY0DL|KpBJKj3sJv?DCf`w;4Hwij2-3!tOV?2>~OTvk-uha zQ3$XM@C;*%;{g1AH1HpLJY&b-&)7*Qds!O*xR)XQ$sWK7fGvzIHvkqeb~Ws_zPnfHUZWFUSMo63pf{mx~+8p zP|tNC#@2(T^`L3}<PN06-l!EMjb92LO3DKFip}p!H%rzxYnTM~q!^0N^ITTZ~-_ z8ZSc~FGC$KLphhn8M^}S*PxthBLL8RE$Ve$9&iERMaEzgvg}KqSS%5nLpD}hL z@ZN}e-1s?TH{tnBcz#onv71r9o9_kS`7Lt*TNu05!q^=b0iI&)&IZQr>SOF4)c2l^ zfHxTXNgd!a0Qhik7h@0V7<=R}#vVTbfc#J3`xA2+dlKoNe3h|nHGnmM7a99G@cn!c z@IGVPk$3wQfZdG!0{DLcp8Nu7c4Psk0-j^+mr(%l{&ELnPXz$08T<9=0DS)~`19NV zV>|I|*L#fp5#{{xEWopjy$Jrihh67vIl-V6AM@uoh&RRH|fJOywa<83wo=xX~eU=QQ%z|p=2 zfckX!04Tcy+N|IJ91Q@jNd~||0DbHw?*yuo<)UdDS-UN7p{2fTfF z-uE)&{qHb7r2&BNQ(k9$Dn3ue=c&6GpN4t)G<=?hbkl1XpV0%j2Jjx^Gphh+Fh2Wz z#t-_C@p-^KZztmmQ079Ebtw391aL3;objVU<8ccZKk*61PXb-1e8u=m;Qr1c#?M5a zvs{3=0Px@}ly!Clfcl+%Cje!wM%k-R2iy-pS?7Spb4~?34ETugbL#-h0Vwlaq(2X| zop(Io`+(hyuZaRs)|y)Yz<)k?c|Pd55VQ}XjP+|6zc|nMC0&ePek|ixqpWL?_C_7! zx9nj2R`7SzO2+S4#`q6Y0Hpch9e_6&zZ+%Ujk4~(8}Jv#e^O-pUeI{|Rg6E_$N0mK zF#gDP#F0JTCxu;e95d-%DL5Ogly$fTn;0g(QfJd=(MG3mGnla9yx(g2fAz!e%N0!~6bzXelq z8R~N~?mt-$yr&@lX(lF}-UK)c0D4Z}&2R+)0Cit605}#f2>3o=2jDG+OOOBwzyRQQ z0Qj-;9sqc>67~4D4NwP|3ji;^eFI=C05pCZWv&8!tGWP-0I2h-9|1tqIWw4a;SMHk zNCA#z(ni#CBk)~(E|V@nT`sL*(q%fpJ50J9&#t(PNmnjo(pAWJ6?k*aG$vhp0F$m; zz@+QZ9@p<>h`hn18;=Iy^G*1CGt%Ay`fhojNwm}R^KHPl3H7->3Mew^4$$$v zif09+;uvW?nc>nBhQa7U=sAQbkF@v`pFRh z(0?!ZdS4yjCMMm#ib)Un07(1bCMG=uJP&7?v{?c?!=y(pVbY@~FloyaCOwAyThT6$ zU&Ev)9%Rx_QN~YE&!4@>q$lxw8))4IoIf7`;Q4ms+m7FUF%R$+lXkqtq+f1j(o-z} z(ERkBOnN2`c#cWGLjGS{0CzCyHzvSenDp#MCjAy^pK}3#?{~nxa}|@G?*IVb3$HTi zzre%agPuPuWYRA1X4mIT!WfobJPEL$NiU&ZFXOjYngDMw>DBKu>9sZhetZ2YCjDtS zlionvyzv>6b|cO1y-a#@8o?_CwsLNkmO#16wCcT#c6q)q5IZXQa zBPRXhRKQLqeFpw~hPL_aLni$bb^08?Lq|(rfaZPR-&fyfvgE?Hvw2K5zr$oF_BVJJ zGTFD2$-xBRCIFs?1^}-zIs6EdV_TRU{|l3o`eYS99A{ zo}y#&RN%v$L7w^gx`VNy%NCB1s?g4zr<3!@3K>wd@|CW44RhT1OQ)7y$*o-oxTb1Dw9|E0I2s03>jww;XKUBvDkfHi zeTlS}LT1=NgcBSbYYMd6MUQ7XfW!>G&-PUCJ-)SkPhdTd$JerBF15J+@wrW!y#Mu_ z@t5m$Pe1+f$3I-l|M|}i1H*IqFP<0}K1{mky?s-M&l|pc*fE?^%4Ba|I*s3m4Zzp( zbJ)Llvui5*oE33^e}|w(_r(`xbL3LXuw>ac{Lek6eLl;2`IArf?77V1`~2lv%X`dX zVTtwf-o5Xm zs0->N!-oey4r(f)rh5Egk32wP8}M%ss|v&-eE2&fF3~e)UQfSm5d0!6dSKBws$tOs z3*n;leEo)X0?WE`6oYJdJ$}T?2t9N=+*nLzV1j#vZQMA>`SAL+tZw~!!nSceVcUqu z%zP1k8|L`!#r35RBUmv`O10FZmPD}uSgHPm^$WfpNcOK4SO=MPtw21;z9h7J&G?a< zhfA;2OQ_aFYdP-->nK`(1$w?%zm^*X3pcLipE8YN+yF%QhP#o(OeCux%5nc&KKxT; zoR|+*{PsQ}RX&XH;TyU8SLFOU?pEM8!CmFU2-a_EeKhcdt&d(Y2>z6{zJ{s1HFB&4f+jjPThi=zrd@8Rilt($NybVCrstNhelp`U5Ptb z-%$tOS7WL>e&l=;WhLH?8G{<*M%vSu7*Clop<~7bjb@IT``PD%+@jI;w^6jw?|;M1 zRLa{3R*k-|Yg_5-+qOt;OE2ZEpz)5z^H|=YQw{R{8huuUJ`88VvzJ~Vz?7GXFizC2 z`v{{^!>EkokMX?*m0?%h8Bp~zorkHhtC`i04&4jO1u94yMf_z`Q_JA04&rtkoJ8Le#jT>qtoeaZBnf6lyLd_FYv$tT6)C!f&! zP?3G|*8cVDjKv}}{;(viHDEOabV%!MR;|Lo&aGeU|JyqszVo-@pI-fc*n1B!x323> z{NC5lJEmYdIqXH^kRqurk&-B?%d#xXac@~Cw%4)jSa#M<26!_RWhs{9vbJM8l9P>` z*v>z(oy6UdEUWiIu?)!>PUXx181(vpx4-j%H^dBw95TE6eY@ZHe~$z(0N%xYx14*< z@BGfW)oj>iRzy)2v5dnd}+6bd2xT(w*-r^XH+pU&V#6??zTAyjnzhK;L(^4y#pgo|&@ zsxGfQr`~5NWi$B-+G4PIXlQ66RY;c=MZsPyDM4_CK!)#CH^t+U#T^WVLT#PhT^(($ zt*t?qMcOImj=%i!%SRH@&Yg1l=qsh8YtKFRoGP3=`Q^9h|5d+EzffOioEPbP^|$HQ>s$3+y~8*+;IDgx zFTeT4J^Cm08})6*d5!)t@vCpX`St7Jkh@*K!Z`Qnf1rO(c<4<-9;hB+iClZnqH$^}(*AUB#~bGndFQP&3p_G4>w zi;#frmk@PjQ#U!CS}~hWCX;h99B~|pOi>#ZWDkuGCm$VUlCQnJz145yqbM4C&)a34 z$=FY zpx?NlXt%Ji)ii^W0-zw7XNy>%OHH|7v=kv@ev zbT8)6#h62TF^BHP9C|zEP}m|O>JGUdgh*R1*V*0O)6?ArNm+=VIB{ZhI?L?7KvS+T z_@!ol!0&Op11oyES0i#OF*0=Y=+WWvnfun~a%pthTq>_|(dzay%t(-59ln*@F1h&P zi!VUN20b$~)9luAs)(_uM^3`gRGsgbg^i`0m+w|@Df1!S{eyM(g{t^AN)`$M}CCs(wfBfKH zeCeb5ZTe1qi{7rU*YDKt=_jchwv=;4W(-oA9cz@G+_^ih5*1aqGKSo+k>H{blR z8`-05j)(YG1kn9}|C~P$KJ+_yM?WbNVT+9YORHtAI?$X#D@9L6(O*Smc`9i#1^{@A z_dN*ednT2}%t#i(ZqbTS?Lv~vT&`TSThZ_!ks}SQO;#)lsGF5a1r@H8v-j;2OX*}T z+-A2oH3dBWfY&C@ofw;$nVFi%$ie1d2d+l!7gsYGMU7f>+X zbNS+Z>+N=MoorTA!)6ik86ScS0)D5cXQoDRM2naQ-J3Seq$In#9_;H9Rxk;puTaoJ zl}e#dD#L(wI31FfiPPjLiCPSt4;AI2K#N6@1iey7#j{!bU_PIoJ24W4L?{-3A3K|y zjdF=y$|EcFN(p0F)ARXkHk~PAq8AbwW^>56YtgD?vDDVb^-{Jptt^dp!D-f1-M}`i z?D_S{)V)kCX>lor;*~e`!+@zDQdwu!wn^JuTviSW3x|PHg9)sSWf^)LJ(zk?qg7^> zrC`>YmUzyv$~4mwx7I9i=v(_>a2VzoJ)Y88m%C588Ehg^uiJwAH1sS|FU`6w#zGda z=?w+>Pu=9Hm3<5D;$ldpw?HbvtAcn83;xUO65QWTv>Fcv!?r3qy_m}t%GIzP_`5^@V-J9+>+$Cn zoHt;>efW^kJ}O11DVW%L6?H?Im}BLND2giIvE%LRX{PaO_^0{({JZ>DkQsFT6aN^D zj9;S!O&AY1VLZG8y8FkWyWfQIa1+MEO&AY1VLbGhOy=hNezo`T;pyqa zhYLAPcDY+R+S=NBdc39XQflOlH{LiNFLZZ1o!w}j@$pb7(k|H%T3^alIQG62Fml#8 zE3wyp_q*SPLgV9VYYX%%=rH(Chw)@+^rCnmdEl#iF?hD?8=>5F87D@S@Ym0N`x^bv zpxRxiUn_k5*#XS90S}6hST)^;jn?}cgZeP#}dI^KCP|j-_I#k8tf$dMwAV_Z`TSe$@s1ClH&FE6? zGxTMzBo&L2luyiMs=TVzi;5PSyP$Uk?fGa_*P|#(lFH>sJE<^CsTT!hFJIORfNC*_ zp{XKt9AUXZ3nCV(l?o2xUZN-+s zweLcLU8k?sU)6Wv|5{z?7Zpt^g-?ClcO63JZsHH(|6@GUFJ|PjGkmJdU4Xw3#<9X^fqh|&3xGrRT4nKSwpLqwAke1@J*4O%Yw9lN*an1Cp0y1!)CRG| zwQZckOZ1mzL8)D*w=8LNtk_s8>1}2k0Nq48&>htDHlq!o9Fa#uD9ty5quvPpyll1t zK(h?K#BH(jY={5kSTJ$I#l2P^wSwNg#Hr?J$<(*DetK1q8M&_AzQ8q1k%wyJM&tJWO`Fd=HCS|IZqrJ)5f z-_nD2h0G`Pk9q-1FKUwle3&!Ky`WYueJa!-eWmtPVQzfaU0;7`KAqE82+&v%%+gyH z*P4E3rZyI0jbq`Q%63uNwHZ9vkC4wL##y3PcG@gkLA~u*2^*yYwUZsKV($?cmS~7b zL)mlLd9KheRBL_|2#fC8TCtD7R+0+}B&G4qM4H>J#;bG=uWIdoKEEW?eyP^E^h>qg zUE-I@^S@M`|0U{+M%~4nJ0`;053c}}j7E^RA6@|TCLZWGADKbUqf>3Z#9Sf0g!~}2 zR=5*&yA#qF(U`^9Wt_wg~fRiKu zs=3|C& zWUA6}VY!?v*gJ4|tCsc^JzZTrJ?mBms>hD4UVUYGNL`UmXEnjw-qF(1a_Hde2cx2+ zXA6#tw(r<}@uea#%;B*3^wTXfYFB67y1>5V#$j3q+x3sj zU;D`uPdxE2x9Wcgi**k)q^tES@qYv0vN!b0^`GH8f1)e>QXvnFGUAhgf#vd*WGb7< z0(8MNjqKu1_MG&o>#x85L)WvvX5-w)@4&f+z0B?t-u>=(-^#wurg?y0!5`rN$bSSB z`Cs|J@t?xP{Y!p1ck;XMz80qM{Rm>egH`*Dd0_*7t0=&H08QIvG9UwPhr_Du6LUF? z|VD;Po+E_xjL<`AOo3OY<9q5UKed#Rw>~vK*@wF za)7^;uvn?s?CM$_@DhQi1(Z1U=CSGN>7f%aT3~z9+a-&xDCP3g)vL`ir7F4>>Af|L zSJBToptg(v>ROqKk}s)i1MqUzKIxxB$S4+#w~Sv$5Fh%#q_Up|kVEi4obQ zu5h^=QZ+G~f&~<83b@1yE5pD^$4-up9Xm27xLmOC1A&U4%5=asa#W~HPY|)6&H@sC zPYjQYj-D7fgfcyzp`i}>0F`M$MwNUFx5BuqC1|k&q86$JV0f8S#o}_!g++^HI#9Ey z13d~0K-L7HjRi3F1CYQ0NZ5N${zB^n z=;PPwYxJk|-T1Hc3vxvavum&A|G^Nwi~pH^A*~eQ0;*k@4_bnjei8QMd|7$-lzXaQ zv{fMP>ff@*3(11S-DPMdF-~hYg8MFX{)3Zyb0ZID;Tr{`%E;{ODtkLOy_9 zeh~ofUHZN9zTXTD{q(^-`aPJ^>-0a;+wi&4FXz$>wrj*GT3k)RKrqx6fb0reHO#(* zsFfK_@5kl4+S}VM`LB1de}a?8#xLNX0ebyce2OjQvFZvs(a( z-+dc)1fReJ?*#Jj8U7vq4>)}|pTPMIn)=3M|B@?_m`lu_oK0ZJ=So$o34kRs+{q1e zUsx%?1Wy+#5xd1307L^j8y+eIvJtQi|D7NrH>%B+%LRiSgAa6JVwbfFs$Vibx52}e zO~V6;g8{9GFllg$C~813QqgEm6m1CXZ)dbLDaolFNY4AqeTvs>}xW8}`SPtqxH{Y4jttnYCDpG?@{m#!Gr4jGBI) zF_kBia)A#F8VsrL%xkW+=TaU~O3qxKOy#-Hr#wT;J!Ab0Eq9)0axV2F`SZg2ETv}sqi1!5~p5!@zp*0B|vC;;Ig|E z7>yEvuY?IeXcQWSn7KQ4L(n`8yD%u?maz+&j>sU#QpA zmY++^Sz5c!WO*sm_ zQw`5^)kSSjbG6aGL>2{ExMWe#qhU1g&zln!=3ULgAE?_C3t6(J6-q2b(>7_8a)#II z3$?-`r%FQel0X`y2MC&_r6!>}OWNM|nw2y8bXV(tQG;6N#Zn_O+F75UXF^7UO~L3) zR6f?rdXJw%CgaiAx(#dP9nZ1n;65{vQQ^>6U%Li#t{+N*B2`2tRvK{8J=d`@b_;OP zA{+-9$&+MB&na9qT8aj(K^aRTjI4QFG-`_m^FcZ5V2%a^6x00V)O5d;a5CrI{CBN6 z+h}%7)+8pCmu@rDliAn@1}Hj@m7g#rCCOd`1fmDi^Jw3Yb~|nFP^IgsO2)tn`~^s1)lxo5NQ0k zL2D%}YaS62>&4Z1ndChu{pJ*kMKxT0)EE%fJbyTPs*W^VfLhg`=10x)B^toh{J3eD zAygEws`}G>iH$wHlmxvLH^m<}Tk!+68rECH(CVh>uAj$)IcAoxqmwip2+tabJG1bv zGqpP6$taPy1Xp5w-^viak6Y-2RNjKwcO}+~DWaSZHlv7Tl`etiMD56XC&c88;Yq<`9G>DMaV0fGfsDF^p`83|{@gUF`Slo7UU< zF1h%UORw7X-nW2h^m~|K?ff!2*^k(d__H_(p1A{fObDvj@B2yQsdl*J-Ay4suomcW z$t3-cFg~cZ_?m*==C&Y6>KqnMG#aEF)7b59Ky}YfPmaeEC0Mgs0bGeNY}5PIW~~Z* zMX%-=lJeSYbOCrnr3#u4wha_VGEG23bvP(s=Kc=J9tu@K=hGOBrdEJnJ;5N^Qe^=< zY^r_*4K+M`Vt9OFd}?ay*wGoUH*E2G(WroA)Q%DZ)xfTRUSXB&$T$=Tbaj#DVioa? z+SLX-0n{gO1*0=oYZ#TUUp+s<8po`uOE*fA8H`5$x;ehKo8zm_=xL?_>oZq<${4#5 z<7*36>8%)Ft1!OSVtj4F_}Yl^)igRj9t@6;j*g9ukGHpYZCr~!9$l;Qrk+9CTscA5 zR79u{4Q0)?ZTbARty{4*%(kP)XA4Z<@Ydc@mA~}TfQ(kuw(~IbI_}~F1}oQs4luAj zLHhZD*ntDrL#f^Y44_+oRByp+g(uTtCaJ6txtjgNe%*B!U36XTxB&(^8R$ReKg0Pd zdzGKyzcNM!4_3Y`1mTEoY7r3<1^bW=QuQr%|0;J{<+c<1*^zyMM5K-8>FHc<8q;NF zdV1B0&W?^QyJ+uKxjjAzETJ6VhwXdtwJE44a+EalXw>a@V{?<^&rR4=UThlxF<6qC z^)RqJVOGa?Undj}Xb0|%A3r)(vO7keoxy8QARH9i4T4UTkd4<>}YTA6M^D}!3ZqA5f)tjd7rM~6tH8v(W(HogU}qw5g4 z&0dQoleYR19Modhvc&4siKh!xhYoJ_w049=A%lUmif3}UY&Mgbj+JE50;Y${NieS{ z*`(Yo7{UC&bAfH>4U9c&K9M(y0-cDES{&7i5L^=$TU=C>26E{!9EZo!f>cO=x|>YK zqjLq#*3@iI9y_L4qA3r0gT`k;P=Fg(WY^|Rn>MfN?!vM0q6=M9VewbLsiJ2S^|4*B z2U=U5rD7iR;zF({D8b&7dRaetBSRz_*%fMS4S8YA+U;%Y*V#_Myd19r z<)Kv&dCh>r0-eRE3}95cdMtQv9-U?O;NV6Am)a|G{G7jLi1^;FIm+)U;tBd(; z>cq(Lk&%gLwp_MAgcpkdt*2*Vshrb!+&C;2)en|w5UU4JwiE$^x&sTa($W$HRj3(U zE}b7gKCoKjR@b(RwrtwCp#z&R9RiVF(*T(J0oDnlqpMc~%3X8#u`W9xs2E}5K5&=b$(z_TdxS;UU$U>@ z{2II^53}bu=!<+CxLJQkCse95|7HFBHUA#}6yI*S_VvoI1xv&sQT0COf;2B z#A1mgwI(s*!pih?G@eYQiM@p;7+7rqatX?xAcEB<2f_93vA}WQa?v2IR*7X5i&0@S zGqET{{v`YmlC1FL10Pd@w1ORv26(y>HQ3awhT3e-V@X&J{J zCAGz%y zjh4_o*soHkRFR00P;qR`qau&0)!y3P>&9X|V6g?z zM|!|YQxEu+sRxib@iHj^^g#3VQjGb_>Z=Ipm@v5UUX8DM8OD5v;0vJ%k%F(wBb6p6 zCsTUk4E8lUTfNxv4=lfvCr_3v4o|=Uh%l);tm9+wK^y~{HCeC{x6P5kRSes@S_;2)~8j!f)g6<{vT65AzQg=Z6p)@)__|>HIjq z+j8+uwPP2&1&{NKjTZAItCeJyG&}*rlk;LCz7MX`!4959LSi^oz$4Ucn6yqQS6e15 zBz%0OYQc_e18f%X^IW1usuuS_g&yp1*#r>o;I|1jIY@o5dmCkzPp%}fIfPY0HUj!V zi`{AfQxIbvy5ej{7zVENc?it62(~GbbyIk>sn%wjwXM4|Fr!vM-K4$$ybpTptp3^n_#dI@fxKx>!ZQoFQJIAzr=H`X*7YejRc z&Evr|)+8Eh^k7;@%ayf`${IDxy1a4R9-^}7VX3mtXjRX115Lz=B0#BX3$5x8VIF)K z^Wbxk{SRW@dOzmDhcFL50NIZW6Rgka92tRthyN4IUwbMIc{G>I>9W6vBsKi1xtt~e zNy1=;jfaVXHAt-Tp`pkctYgQI9vc}sHgp)riFlcHbRd|F1Xo*|gJtQdu`E z=F04-KiIZn-R2AX;L+N&!YvF9`FsM?3Izo8Ia+aTvxCi%laAQRwS%Tu!%M*2*yOuwKmFDJ_oKi6+F#utevAI+`X}^v>vtG--dDjXr}M3N?l=Mwp$!dY~U;te>YpPVN)O+kc@O-xCkttj|56ZrlT1PWz>LN7rCt69=2p< zx0R$KML`;IkZPaBGCYhS>Gf7BASOTY)N?N$7#@#h3$hp4Z{3N+#Kib)u9D2aou5n2 zK~I^(P0Y+h*1)|Njl-2mYGX87vO1Z|g{Bci8P2p~HV(gEB3smHK%yN=B^r=huiO=l zrSeKsvy*AXDtwVh8w#hsFVxQ9owrDgNXJ#&MS<>O4Mb(WR7@wZNaH|sA}WvHaKS@Z zi-Ca;6%N2t(Ui+c1_RI5P01iMN(O@hM-LAC#!o*sOE=!oARb|Xd-B6VX&=gJKe(H%PmydLT&I6G*#<3SK@Z@!t! zz4`0k{`Nl}yACGK`}Mo^$Mh9YyhONNdBjo%`4(PG=0WZ%j3g{HxR55J^@AS_e(+E4 z#c>_qh1jgOuzUG$q56VIVhvl7DMyye1vhIZ<)l6a#3J2s@5B@cQfN0fbl{sjNz+T;4JNY#bkKQN@l88$tt4 zSuRwRTyAb|47?b8U{xtNPHw;j8fU{9*S2}Y`0Q&NGB&d=w5!QeC&o-C+O-SqiY!me z9fZjQtr*_^sF&=rFnhS>B4sxYx@WGK9wvhi$5X3T&EUhkA~4Z3EzG_@2=5HHV5AD{ zmEC}Cdi6g3@&uZ`gNe8_B533uMRzeSr8pz@Z@W!$rlNJLw|B2YT z^U<$NJgL!(bUL2YkZ{02losC6|1JjZWf*s4TaYBCSU8H%y$s{-GK@RJw(!Dcz>zHG z%d(@0NH92S?Oty@-rPKm&Lw(B*ek&plBz?8hmM2oG9Jy|sA6<>#JY{6#n_iAy2}6?xE*EaV zuw9FDC*p9wi7?xDft7TH@XEw%y8uQhVO}T>Vkq-2b{Bt?KZf%^>gQkcxAC>y%cj^H zY=WJH#heDfxq;t+5Vh~~N2Qx?LbUDG{Ly~Gt;1a7`<#MAk+BfdtjJcWY)I`uKp-6m zfX(LA zhd^Z<&oXD{>eZ_|oh&;(q;`v?3@JV(5pm{$fX^X60o!1DiiKLCcD9Dt)O1?j#Ui=n`@70{4AMM` z07J%ZL%FSB7`M7udJ1aCR9X##yu->`qzP0%Sz?6(!i_XUxmodO;RUt3!lB2&Syj>U zSOW4|g_Kn6c2`NyLJ-WR)oza9?lPscFdj9YLSGg$C8)2ZOcA|{G87A}5Q|blWN98; zwTg{$6j)G37E4|>jT8uk@#ga=16V1v+OQL84l-%@fe_b?Y%!;>{pG!ZU~VkE)^H3% zot5iP^K*wvT)f^a^u+@QTD;;=A=O%0c$zPZG>pY%(b{F{F_kdGanC?XKFr_kEfpfjg&UU6!O}%YlsYnX8RPMT3 zk%)evlJT@)94%hW7M2KHZt;3Xg=n?1@HAhRDL)!lv_-NBstqD?>Sp4;a6K>TDbb|q z9DoQZJI1S1?RhR`7mczlHM!T2FtjfAG|PP1^y*aEjkD7N9hTyA7q$U*@#*I$%)VMt z>nmynE2b@0RindF;85bNrdC7a@lEa2pO)zjxi&jNMNt7OfT3XYl0pJ??wA!05Tc9s5 zqg0EfNu_N$&AQj(6_Ish8ST2a1sj1at6}%{woS#C)v^OEYgzpN22HzB1}vOQ=O;(a zd4Cpt2UHKzNtVbnS)cewZd`uT5Guy21@eky-`UC*QMO8}GAU9((HAIEyfR-fy;@qL zESA&z_K}o;%L0PKRK;^f8V^GZ(uauCsXf)Sle-{E?tmo0mH_O(;p4j?N$!G-x&zw% z9Vlrd0oSnSkS++@M|n#na@ogXrIOF*5le#G=kv1?Qk>wJLhLxVS|U6b3tQH#Ax!wl zk+9z`vZ0}aN7r7sa^=caY@ixFHl%K}$`s$4E6GkhIX&(1tXbptuUX^qz#mfdh*KjY zv&_@Hc01@zTiVqxBB4&$^2#efpr(-w(?6AGmtMLil%9O*si&Sl7-#-Xm#WR1CYDzZ;vEkEl&O2x59h$&m!IT$3blSxbD$ED~7-E{cr996XUZ=-vz5HEZWc-Z1jyz0m zoZ~n*;H311^gN2w#a&p`e$Rf&UP6dD{Z+=Re`DX3KXx_XiCeB_5A{>`hh-qAu&4J~ zG8woL%1-A1lK53~K6s82v(qUE9G|+;Zb7;mB+5ZfI~eUii6DN6zW@~6CLu1XT9#zw zLW9k0g+mosS%ljI;d;(aFc|zf41;PR?V}cwv1xTYQB^l$X(SgnkY5Xu1!@|_XJj&( z=5qP5^CM-2Loy!ZNSv98gCcK~TGWXSiDYST7oelPES-k83bY%i+ht+AlvX#EtD+Uq zfr7k=E$!`a%_AJI03UsOdz+)Gbgx>~X62Rm*wLfM;GF#;Pfmy7$*oi*3HhY_K7|2h zNQz#74m~}7Ha;}2Zj2{OloX05XY$D+D0GEFJWeIT)`wpWsZJ@bGJ&Li)%+YvETBYV zB%ocw{*a9&5^~e=ljP*XZj_a5qK1NP!#voAc|g408!!)u*Sih#U>oKEd8pv!XF2lB z#gYXEfC)%Q4Q`rJ)gY2PI^Dz|U|1Fk7I(`kV4G@dEG8&^dYstDcQ+`R zbne3IKgE(q;zJGEonjdYjUYq-c_1;O%ZnCL`DK^g0JrS>0en7Vod3@MioYFOQ-cn) zm#^dR1!=PugZUl2Ue9kcM9_VsN zave!(tIH)-lE5+NvbqESn%fOWtc{<Gnkx?EMjT~S-%Av2h{Bd?#p zXW*DnQr09Wu&N45FGCVQ)s6;1#JNBzAZtBMy3=m-^jp!>ccG_AcOrJpZuInS^mMn~ ze(V^4yJN${^DFf*UVFNb)C=hfhSh!WBcq~8tp9Gnh}7xX3KQL+d#l}Wd)2*?VjYnb z;iSx+JdA{2LkaP6HPV5IOHC|b%-BTKpNIp%&6_QiJjp4Ar8NEY*A&DI7 zWDOXfgx6}fEMs3tY-Jt@Do+m0k&Dk@n#{y<0W_4Rp`Ve+s9eN|!Db#JkaN3& z*J{{K?AKHhLd$S2QiY~g^~0@ zjHHiXB>fkRq_ABf4V(_Q5^)rha0yILO%qzJ`fPs01_awWd`zrna1(h!73q8MNmL)~ zDCdhTJ{67529ZFnf~jF^#X7NhbE`HpbXe^>fqq|fd8CmiAZ`~T);{MO;0J`{Ne*d==qZ0Xm7IC~KM^W-XrY*6+2c4?l4bDJ-6Q#2BX-2gqHl$jHEk1t(%r z95$AYXRBc==r?5y;z~8_G=OZ`-yUSsGi7xTLdf3FS3rH4!}%lmX2{ixKKdT^IL7l@ zaK`?f4;kmL`2fEd87Uon6@2r5jEsoCz$oek%j|pncgFdBa3wclHGY$QNxBoXoIkpn zJ!34sg9^vi9m!ZWBIIJpeJvs+Ab@mG6WGkil)*ZI$P$oy&|U>xohFxOsvL2mlLLWZ zzy+6q-5&8_MMmpt$W5F>-d1H0QPfY9y@qRi;s;#dPA}61JhanlVEDL><5G5-2ksSGxe? zBie(0N46PhrD0Mj$i%4Sk%+NW346-$8x7p7+tlFMGl_Q_wg9dq$^%*9(V7q?+9 z_F*n=$6VZwxfsIYY0)zY-(*N5=V8{vLi2zr_&T`@~G5Pz@sw3N7VG0b(rW zp+o}R3n2?aw3l|22AzcT2CTz$OQ26dkA#9-Rh66uPZ<{T5EgU8|Kn+25xnSH_$isQ zJ=E$E5u&#bN$uo_Qd`TB5`-w3YRKc!3JDU-bFtYLcz-9E9|J1rpygkYz^Ke{A?&nR zpo=Avg2hFPdB|qd3KV8@^60^2@hCL_B^X8ej|whAcv4~}RS1KTOl^Z7lxaafWfe6d zjHXr8==J}9n?cZsBAIu$(DH?LdS>52lo@e$c$&9Q8{5mo5F6Xeh<-dHX1XZIFB*T74hJS)&_&I*#^N^R2l>ecwM13W8n^WLvFy$iHra?Nr2xH+W&McRVbyv&xem+bE<07f!AJr z{m|jVl;7oKwxUZ83dd3Qbs|T>+V$(#_V)H5b8uI)ja7@8WGtFMJ`UspAH1rN8yyqD zu}9XIa%}R@i=_9xa&T;VW`;Nzr988`0}~VASd2%jjzAYlr8QkaD^j03q3qan_yw-2 zR*U)d>(;DUxBfx|d#!D?E7dGWgfod)yi_9H8hGTP!^fc0z7F~H+AtD^xLbQroff38 zL84*=_B#gt?)AwxUVH7p3(x%Gt2ZK(?8m_(y+ZFZ&Yjpq;2rv(TK0Ya1*pYOe*0$q z3s~)M#%F6lFS;D(B``~_!xf*j4}Rqze);5+Pd@gq4~O@Hws9w78&=hK8|Xt=;(#IQ z!tF)m|3x@b0n1}Cn<=mgoVHlM1OXWnBAt@ePPtal8+b=?fMSV1iQI`OKO~3Wg=FZz zvcBV@JvUsx=jPi#b_-%4II_h20UYDCO$Hv}{roM+Mw@0okv;<5_S3hsr-2Ks;vYte z!xWM-7-$g4NJ52eC9qn^ zB8IIff`YQIQ?F`-d%&K@e_V-lQZnpyFjO4arD3qEijarcY%H0GgZ9~R!FpKKYcAO6 zOM@u^2S0urG58b?Ktz~oauUHnlSf{9c}j8nJy?X?wyGKioz5FT(kKVzq5`=YoZC#k zD!T%~phr>L(Fm~5WZ6bk8l;_pHg3n7lP~7e<$YHPTEGYLEL`+OjfsvRGNCnlu>=r+ z>US#OT2u=gHeIlJ&FW4sf^AA*q84D~VY>t9Gle)d8dR?m1TArV7+TNBI829%)gd{N ztia=z5kE()SgmRcwuJ(I7$jIx?BG^rXD2arVp*hQW>ta8u-bzG!~jqdB{|U6>Va-z z_XIq6hs;~W#-M35HKTG(9w8e`AsPfXf%MPlb~=E{t7{S6WTQzdiAc4tfmH$i4@?Q< zphj{JGL0~r@mV$8j5P{@28cKUKMsH~vJMk}^K5x>$miN9X9f(7}H0qla zj#HHWdAUxtbU_xoFs;q98nXlBsg|>-B@`E`Wf*MFegQdO41f%OP-Vf;pt?4puI7t# zt*enE0OZVCi=H%!rK+N?XU!2{*0r&BD1Yv$OviP`XRR#CXKQep&b3~Yqvs4|QF0$6 zL(;jHMK^M)tXk96?~!hw(k0LLM`PbEr3ny+8{ud5_OjO6%SgdVO=hMpIHidx=wCDY zgIS(}wU+X%P{#?#wc9V0=Z7GKy@7nA$N{5gtCX~t8;^9q@57bYixTTKV_(9Uc^}sA zyD>*TiZVZnGQG$$jv2@#+11hkFwIepc)Ge;9jr7r2@?qu*dj)pM7BfF5FU#aSXpg? zc-#p6I5Iq&7x5kYWYrb|LfzG3zuy5Thc-Pu6(&-gPT{vY0*k>~DYj+s6;0 zkmsIxX`(ELHe7VkMZoZ;rs7D>XSI3-hXcB{GZ0iefxeVCY(hN9g&Vr z>Z`B5^7`@FaA0aWRl#?8e(t!(Q%Q5DODe+|@F`jS?mztPcOJX}i!3E$yB&+`M&rCp z|A27cfB)~-0QtFrFhSSgmw)g(7!D`zZGRdf4kb`TqN_hmC$kec9avYyHFh z#zgW_!Y*1sXfq6#6U&7Tz!{a0gaH6#Qbe)ZSP|C1{eQsQK@IsG`#x~(XOMo;iWtMq zSU$gD1bF`pQ61Y@1{U<2{6U=W=bczYkFdY7d>qK)&Ye5&zMXv;czBNY0ZRG-PK71- z4*qQzEp+}A>+{w9we{3#5abm?tEgs*1WOnP?124BHFFY8B;N0(*j*hf97Vz6i&l5U zgRkU4F{J~S0qJtWb`c=KaX?E$4?|*C$S6j#0}3Tk6g!w;011(U@_q-B41hfE^(%0^ z3>f9d<2kKtYic2#PmUnfUyFxT^4QlidY^*Dda{ry3?Po3%FP>k3)Wx+fMMh)g(@~7 za=L8t{SFD+r+}Dmr5ptVn~|l>IysX@#x*ao5Lb0Lf(YU5_DXts8o)MqaKRQo!p5pq zuTU%@YQLzdomMNarn3lCEtWK{NC=DYxvhwA=G^LQX$l8IEp8FFEITcovW(o0nM~EH zwx=@LL_A#)Dh|I3_lm5Qc6%bOSpoKxA_zWn`J3=da1z+7WTPRZAg$thY%1g+4MxGv z1K3;0=|px{GQ9x9Bn`RtFskc++w8f5h|A2s9hPK5 zM1Dd1O{%C^6|-7Dc)B1&QH>k(%J-ngXULv}Itw%4NKvh0?)gkbRP!;LOm+7bK)}Zj z;6VMaHR}Hcz=7I5aA*+b$9v2hVRys!&nUPVW+nWjcDYP zB(gByrMqj+%5;CuYAvxmGWF3t@)et$%=|icy2aaA-cq{3x!R{|(AI=pU4ixOYK$yINwYOr z-%RAHzZv#v5Nz2Q1cS`NE`XZ>QYlj%xX(&tg&499Og+$AEny+r+DCzgTer4VN8vFt zK)kAo#ZiPrBlT4dAr(j)_zG~-0atOWKY%d{ZnJUxOJpw$w#~*#u{oWHAp9e))|4WglLbP} zBH+Fe-GzugY?tvstH2;20j#5KW@od42WTBw&zg*4M4?iS*h$)9_0MK<*dwl7fpAe< z1qBij(};eZ7X;)cfd>;gVdx_4`cl7$a88?X5scLaxwl-;pAXh_HW^BDVKF_5-di|7 z&Nfpq1{W7#u)u0*qD*b_{|vfB^=f2lG|pSZ>ny={>qjDuLE~ZJ9hwrr)FvBC`rkck zjq++VFQ~l@%+5uY%sI0OPY-)06dvuEkP7w#5eWa*!8#2WQ@ zKH*ob3D44wOGztY!E)rRuUSQ}87euAH56uSB|;D8cxfE}OZAB{{$VK8%9^W{H7`lc zfUTwbc&WM>k`yUYYvmnmtQ#38L~AtacFujGQJK1kU8E@#7;jC8%-Qx>-It0q(kIQJFa0B{v?P`nJ{}P zqR3JBGJ{l>v`_Kmj(GgdymEz?~3Rr)nT`0@JfYz?!E5R5z^{Dp7J4Gg9qT*`lhO*v2U z{|2|Rm3^h3reuWuI?NtZSASB^{Om0;Ow?c+nQ#z*5Zhd;|ekV?6XOgJ&6yv#plRE9~mJ>Ttw`WxeWqa-rJ9nQHdBeV4UE|LqdBqOafM)EnAW$5ekWr0;mQmrP?&O0j! zt%0YI(S}%_hJ}!%ZF_y>^kC$y#>i>MNN>kTrwI0TjC2PurDj%@gPggrkXMIV}dZQxp9Gd)0QTJ(zqweH1b!2uQ> zVBJ@1Cl0zHkFd@GhrQ%*m&TGj5v#h@ zeg%Aw6z7gGYpIPJ$^u3+z8me(fp%y@J5Uy|CbUBn+QAB<+Nc(aPEFwl5h*?wW?ci; zv9Z_&ei$1csw&$PX8M5mzyql_uLWCjGw2`p)c3qa>fA8%1F0RLIK~q9UwSQUg2(w7 zm>70rqNuENpEZ|DAxp5E_%qJMe6oB!bgzm!bwRkfcW=Fy- zHh``3Y;JKjg(+dYiE#B;-TQCGotm2BQgh;bv~tpLNP0IS8;$ep-1KU*l>c=t*t8NF zOKP;GA?^(SK#fFf%qU}4olVJBI|`X>g~Zg)`NT+Ju=>t@LwR89hs#TCDcOXL_vs&uc32bT)HsCpdR3=XXTbj~Aw*kgg zDuo-qvULytBD)ehs(g&U%(n83Y|k*Tc99yw{Pn0F4a&hN;&$mBU{doR?1%;*#|ITF zgBx%`wOmcE)MPq6=dvF5vb6wc6AM1JjoDLnY{+c0<`KUJPhkGs$CEL4r6t!h)0{Zx zHE5wfW5IexhKAwMFz3zk7}mUFq+v0mskijKEoChkKBRIA$908#AxtXR=QKLzz01P98O zb@=CB+6&(4ZsXi$2?GxraM1(!0w5gn zTT>#1<17duh$7EVgNtyLk*);ok{8^ddQjRYX&JZ($hZj?6$RQ{7*6T4!$rn;yay}D zr?Hkigtg?W=&yUwU-w|my$5UVJy>)56(pr<_1k$^wu1K>w1-z`XHNpiJ2{)FN=`32 zGt}yK*o0sZ>_SF7(Vm_)n^ybP@C6`=<`M!an>Kbi2X@XNsm#peEbnlaPaZjXGB0_o-WBjtt?O`!nZr;1@|VASdaPWr zDTgPE*yM$n_DB|cU^J?3L9Ca8U4D?Fprd;wQdxiznJ!pbdNytaxqZ`$CO@CWZ%>XA zH`@a)>iV!W9L44~s^#myMz_57q9243hjV2OT2{}Wr}Z-0fKcZccrB0FP5LJ-_kHs>&%F7}cLs07mAB7R>aM}HH|p=vKaGTAwDLi=S$xR% zhaiZs*Pxb*GTcCv{|Le~f}V?rR*%mbfip;aU>6)$`$YhuZ@&de%w~BT=pOIkUq$}n zA7B)Ih;QLNpojiM`V`1@e|!seA$pb_11ID*Y%}%)eCh9zQ*$Ltu~*rbg+IOXPj7*P zEYDZ-Pw@Z2A2ZIMAf@f!BYojIHe-zDfe=zk5Z@N*?DKG^lt5#V!HfqtUW{P-pJMUr zJFEE`zZ5$%S4ydLQLkD-#&A$(GH|Ymew|Ae@)<3Yf}f1jBu}T~(^0s}t)Nw76CWJm zUP>1xf?VMxco%T5CD75{>O!+1r7ck{B{=!Q;%qck8ra$80lEqDGl=@MPY@7->5&td zIin}V9*<=62V3k&NIp7(y%h>+5GXiDj6=u9jTm{9H;l5F;3rTwG%*<70!R|Llf-P! z)k459JQ9=?4ZK18qMH}X0~a{yo&$b;JSDfTS=DOOl1wZToxdp9{hh1Vty|Y&=X#Na zl(NSM!n7O;wKse0>Uz%M+5szuyis|$WbupG7+OH)N6ZvVmAFi)Km0AFWJJ{SlLy0C z?vSKc-Pvph|DrH{bR0Ym@D;0Ca`^BV=F7;NBbuYBIndO)def@P)QnBbw6*#O%Z5W% zbOz}sOBSn4yR>EEv&jtc5;H||pJFx@FzrD2M@uL3rKBnpPvRF{c4XRGgb_Qj#+w?O z1#3KMT(wLN{cKDf#4;~1tknu=9mW>+HDjm-Q6Thl6{2h)Gqy7Kg0<~zr6MrLC{;8| zt%Gkha0a_v8kxqa)b9V|r4kQ-cpijX(7I>CBk=(0IU{%;OpbnGy2fn@0}AaI4%DiP zd=a1m^mDJE8t-$P4`1b--T2eomDAMt|6geh(jL#HuCm#4v#f3$&yNS>h;QsU6YX4F z>b!p2U(YLm*tN3{H7eB&Z#|wm&_U-t+61%C*w1Rw7+#{zCjX~#T+HKHVbmFOp@^Q= zFd(L3(2#tujgA_Qar)?RV%yKgu`+d~9@Z9AE_QEkrwV6FQdRkp!)=;T%PMr6+n3rv*x4@!tVYaJjzc2Q9 zkQ>6VDW{F`FxU_OzR{?LjcRNSSNG3!steJm#j~SP24j~LBZ=0Axbx4LKE*XJIwI$VKaB?)4x5B-oijM?t6Z=!e&}D zuP~gcwyh2KP143)Uxf%2vZJ?1+RzTj#6|<;XUH6Ip^TvgjwP)`iMp(*M|newEFg!9 zeHFf3P*DxeTeotza_+Jcqj%w0i=9?`>~Bc5@2c1S5v-Pvp!WZP+8@OGkD&GsV-0;6 zwSNSo^QsCZ@b!Eo z0s$dDfw+}uF4o-K>O-dRn0giMZ>v@EDHx7H_}bfh@OP;cKlse+Go-Utt1fpnKQjut z!HLn~G8oT1uxZCeKRAvv9=nX3d$Ox-C6X$yyI`9i8RoH@WinT;u34i* zv$;3`j`r1S+FBF@HI2n(wdEJD#-IXEKmW-7{a^p#FMfYyG&S+;&wlvzfp@_n@EL4q z{5Hh1U4+0LhjD%ZJfknb3VAOw5ncw1cP;pJExJu_1`mEW@-Ke?QEz|l9{9#T{r3A` z-oO8choRVxy|@>bP?%gdh%2tPPG5jph7Jc6L$-vC68@i@}WJ;FcFKfu?+ z0Lri^dyahv@es?%IlvP@8y8C0nYEY(5m1nYQg&`` zwupSa`M5J4A3laHReg#!JCm)~54kdJHUS$Gd2JHvt0>rLxddZG0Xfj;ZnkPQ|Kjd+7Knmj_w?Kvu1m5c}%G#nQl4#b^Q47Q5^Ou;Jq6vex3S7GVm4-tY9-kcR}x z8)6yJAL?vx^5x2iZnR+|lWHaFL=I*#I)aSq6Y-eeA8@dIBBovhxKYABH(b6 z+~&|>3*`%mJTfrTf&3Q;AVlZQ9DQRvN)woi;U*y-*X>{|ikZ1sJek&!P0?oK>Q%)Q z@(H-A@sud~gJMNiT70NpK9$MwdiBsDM4oiQn$J~4SXmWJluA}ss@0Q8s}tdcIqa{j z3AvL=BP0MZnvzgVXX6R9JCZgd-ZELJA`6*OyCh=JvKkTkz$=aC@XQcjL4OBav1rxS z?Jyueas9JSTpgOJ-475pj4?a%jc_72ExGb;Ea zklTI5i8l@oCtR*$v>EELV?)Qrk`**2WMu|1jwSsl{GUwif7LA9@`edVfE7euk^=N2T1 z?Ax-Tw>{L>1|vDt-r3fqmtrH>glu@Wh+R++GPJqZqtg~B5X<2Wn4}nY%Er}geoBjq z!y;hRtzEmS!^LwmlOT*`EN)Lr&)T(WK>f-;{^nWiCh_d=fB*C|&ph+He}D9=`}aaf z-Uh+B+Bh!)Z1YL34(vtFjrT#IUS*sVA9=5R-?x8(iyr^+HwKKQ_e;oFEkqQ%-;Kz9 zuunBDjT9VC?6@sS$TE`8<{&Bt zJ=oC!5vw4fF{P{_fqh>|bbD;YOs2MPtj#X#Ii#Ye9~LnQ3)uukqhoTUQyPXlqOc0+ zxzy;f{UQRpaclS@&>T)NkTN2Bu=iFn4xie-zM?Z=Wj2ooJTpunT?4Q&H#;508j?YX zX&#(Z%zLI|jmKtDdNvlL%;-Fs3l1VOvOI+VcnWz(NDD4T1WQF1A<*z6QhHAE2kq60 z+LxHdsyZx+!xIw-pcxq%os7m&U4VhJv&jE}?bFcn7DR-nC$W{$iIbTsHaJ0x%3(WN zfKV%B*sq2W(T3$a1DVXT*&v}LLw6z9XfXO zB&ev0s3oWJXh#wSawX47@wvH~$!MN_2L$B#-nhrv2xg? z!icfP2vR#csGVsH8xQ!ys3SdLI7kxZn>}nivqCb>~ z2EEK1j3&$!v$YJf_Hs7-pG`7*Gkc63hI)aW-Ebbj>!Sel={#Cn_-Q;N`Rt0Nlh0x~ zXCeZ_vCCjy4Yi)0h}O7sk++_nkCtiawBl3-{f%j95kxNAwS52jWfE!W1dB4nHe)j) zGo2Rp@>(iUEgG94a{%YdXRHm|u$(#eRFR2@s%dw^F&@C{hP_(#2 zR@=~mthNl(u#8MONX=A07Ju?t-Hf&8F3g8}u=d=G`EV1~o||AZ+=cn@Hq3{0IjCC@ z=?D=m>DcnVsi~!rYRAF>%`fEppX9xF zU?f+S_FYw-b4zOFEKSa~$JoXgd&b5#P8hsI1GY)KFPN}PSkkiUuGWkL24m6!W(kI6 z$*{0XG#D^=VZq=uo~RjVlym1+r}}=UN-b$-G&A<{egC{)*GzY-)w*@-);;H*d(Ly7 z<5JSc5A55w@4(Tay&E#A8IuKgCY7VQMJhc%2gFu&2AX?t<@Pq&vlN<0)rZOF+JMnQ zFiLYt;lCKPIDLZ!Pyb-v>4*bM7EiMZpb{@uVHN0j?aE{MOmQTdoMtTs@b=-*i( zs)e+E2$uO?Fw8+Ep?u5!wkxl^@@>~B--XGkl?CLj>H{n*KPS88Dz(uP2dCt-g0YS_ zF@pKTbW%(c6=J8N?0O)!(bWD8UbmF5!;fzFKtGU6XBNOHXB;Ga0PwCMvBYWQGg6*h z-)p8rL>s7?$S%jubcX9n@sbvTFnR8Lt6;_p`y*z5p}la%+FU+tKRoWj9RpoNu%RhThnPlR%ctmiFn6Ut>uS-GYm42@JYBX59X5wY9X4}cNi4+-5&T4NC=ET)>UiE7N$#n6Ms+`JA7yuSjB-S zrx`&ZXn`rg9-{=z38ymxE$z)dMRU38+9S%;w0I}Tq>NCw)zUogL6XUx#`mPak*F%~ zL6YB&B!kW5KfGEO+gJ(71TqwV$A?#$Ay^yS{fJ;iIdEV&;|YiBn%cX%x|)0? z?%u!w{SqUGGc%V?H65i(K*!HxTM5R4w61>W!C6OyDv33QR~pyH!{>MT}i5*;fRr1z+1O& z>hu?9k397lQux%d`J$_N9a7%+$)6wMZXWo_r}tiIypP@V)yDJrf2;9U_m6)ZyX$k` z`O&BEh9VAx#W`3?FUR3{9z{i^3U~_?a=VPbHJQ0yg{WH;f|s#Pe}nN}Z|v@`{2aEh zUw&f|zT-?!&MA?WAh_*7jT=IOyMt6;03k4g*2?AJ;fV&|1jT%SW>85?&gJyoszSY$ zJ@#v{{H}SMqAPz?k`#rzMEzSN_D$5X=u#fQaXpIc{#^N22-6Pp`!>>pzX{viE7W)4 zc6`vR^z=ut6aU2L3UdEC&=^ur`bR)$->tqtttHLzkn(B!&14wgyi56%a%9ok5)Ko$ zmlQX67=4um5l&6V-f?l0N&1eH-|ezR-8QS_3T#<6cq+U)?n(hkkPmAs&W_K_WTKnO z8E6?(H8otU9b#XP*x`_mB*w-Wco#TV}mL1UyK3Y4RvUL zTVZ~lDlp;-P0qprL5i>uYe@|sOG27epcW-RSHSg!C?x61&!kJy2)@3B1l*h^NuOvh zT$V-s68I7DlG;4sfWuqgv!SOY;&z?0edC(7pf)_|)Ho3Wc`V2n5=p494FdWrrlx>` zjL&5COBe!XtSa_pF`JkzmTVpfx-_RdlS$1V16fqD*YOjLisB#N{_L8=zy z{9as3hO4%*uC=Y9&gl<0{IxCXwrxLWUC3q>+uOqYC5$M5=L3>W?PJu-X-eS<;b}t7 zkD_kv8J4Hcub#?dlu3Ot2a{5~^ z5^C*kM`0E;)69a+sYngmq7zFzEcz(K=y`N0r=sDiF{_ar-EWx-+f>Eh)Krg0^x(l` z6g}(`^;~Pm4|nL`!94lnn%ip>7yy45e={MbOH=qwWe7&brU#$ix6fplg+Ne8d|WeCd%*N{;q!LMbyFn4=YJiq!b% zWY#b?ZR+sbjCnHB$EHDawXEH=DHb1 z1*4?FJbKx^U5J)?9(z2CcSu-;c4MYoXzk{F_~jLkPf$g62Sx}RgCZFp#Uiwkes9d} z4pK^U93D9^08u8Q1Wo!CJBaj$m9IH(znCGv0_CXWWjCZ@eWXcsFGWUR2 z^HLw(tzViIskhq|ycI6&9bDHmEP!9(B%)wtC7Yd1BL@Q7DrA_(^K;3pM_laLg5t1w zrAoI<=CIBVI0_#%+ER?5dHsC!Ol>Wm0B2oWTRn(>>>ikb3Ump(ODGrQ%KbKXIjg28 zsqd>fP#R!HE_KRQnfQ`NtH)IVNVLSk18v>d*x`eO$~mS4Wt72%v8$I#HBbr zK9w&N%fh`!1Df7w_8a3tE?{<;5f#-J@YR z8&rUV313r9(5uBSEzUz`OIRUCQAlLavYe!_6#HMyj6$}UM(q*r$R(j_q_5f8*>ogG z_v@TC;#bliM~v!VeGMAhdA9!IFenn1GOLaYhvru3`*5ro3SV^*W`Z_}BB7%E_y91M7} z{KFrlrT2BIR1DemZR)m+s)I1YDU`$Vt?&m?MA=-D!m?xzanY z6QXs*LZTF+E<{A+t@Y2`-D$g&LFI1sx{WX3*-5bFoiJ1WkM{h_-f{!QgL4Fnb+EQS zlfv}MF08$K)mK6`GpT$_eaDsD$32aPm>McxO6(VqgANLLQNzojY=#5{?LxdE9Hfpa z52q8Bmc|;fFEBz_xj`E&sE)eM&PF}z%%>J$RJD`K?*T{6AC~ha-D^C%pdlEnCG!25=zK0?YHrM3QD%P4Vn$u}+&iSX+y! z;goXD@U>!WmhfN_Nuf1|YUrC`6qIg46vq}==nB&5iHX@X=vl}FGf-Pji#L?rahcs8 zCZi%0Hjr+2mh*Gt)52X-62hXQs=+%99af=2zNC-sw;o{7`En@(j-sx~A8Uc!EjfPZ zcv)@T*lu`S>I4<*eI9l6;Lwa-W3^0B744Co86BN0M;pP<_NwWM_|vFLBG=8klGRPqpXQe0 zW}t`Eh{l(!XVH((*psG;C+)ODYN=ckrfHDEyqe~%deT;ofjO#Y7=sf>Rr)HJsj5#= zbomKBMGK*MT#||fp_#P0%Sf;bm*K6#GnWRVUGkGnc&7Dcd8}9S+y3u+{!HeBtPaZq zyp;R1G-tt>^+EDUT?QtaeMWtI`0Lc zA=;#h&=CE!5}^UM&uXp;(pbF97%+=tcd}eczfOo9oZ#VJa`m??s*x&`GiyOP1BzYF zz{@kgGbZcC88lv%dSki8>Qz_mqEoN>w6{erd`WAF{a2N7nb(4gtfE-=>=E?q6iS>l zr>df(JgzC;i-^c<7DS|ctzPQKnJ-Ii%e|Jit9_KVtM?j3X`#9E*4;r}EY^x#*)ZR6 z>*kYn<*qKvVs+ChiIDX4gga%LAAJ0zl9pRg&;MCwlA2JIf1}g9%Ey;d35k~%S}zMQ zr1H~$DTdkacPUn;RzGL>7e0SGWAZL~{Vs0jySTm!d_F3%)HHSXG~*s^YN+w!k}W|^ z-ms3ewvBBeZx(Y5Tp{~8AS8iMadd#kSV6x4HZ}?Zv*E%4SQ1+ykN?Es7#f|=Gd#JHN!^gPRLtM zm2JTw_)3Eq8Cx7rQNJKDKRrG%IWd(5T_wBj+qxb*d*H8`r_;cpup`t-?uj+8bYWyLEJ$#`s zj|(J%<>Yci17+D=6~!ltb*E7yd7+^1Qf8DN`fk~_ZQCWU{)d-RxZ<5R0fWszZGNB0 zC-pn1ezz$zDn+ zTyn|VE>nKdCzCnqVh>gI!?b{gU?G~TCnBg z2%!XXx)kfdgi7&#a(Wi`GiIjGOGOk6c-(gcO)%KphTdK%WD_&MyGlhbfIA)zC*-m6 z!2PJj>{zM}O{&(L_GdOZm&$V&*)L;N`a8Q@IqM9k6PJ`y$=M{BtK#j$NjH+wmd>{T zDevqoW$iD(qXuk<%G4Of7?_g!CFi4#3k-k(I)y!7p=};!{DB>5DU(jl>lgT%g!a0w zHqwT>Yva0Rb(R?NOu?%F0In<)-97=^iiffc1#gL5w4k3mSyQK907-FOV?$$&8%S?9 zg~N0jaFi#A@6TCG7I7ClT|tM3)HNzc2{_5&58|OM8JrWYY^O8e&J!oAZ)t1xNm5*i z=t!P%O;yx#h`xkuOXsu&+lYMBJLH^^rpD%oyHXe(oy7$)KRuV#{8XiCsmaesCg|AY z)Fc6CiQiEd8{nod*$(MR}O(@ri5QtI#7iqyPR9J@S6O za>|a=%^rgZmM>FfnouMjYhQOyZ)e1-z=SQ^jh@4Yho%ckxM#yU-Oa`#lNZZf@2C{A zS%xERuR{sc2Ytz8CJ$Hh%rIcC>7s}222A>>CIbRp`D`IpD>Y7pkkcvnUn*9GNEP$~+|A0)b0J&QuTuu$ z9sNJ}*Z#*mzQ+4zaLA7+QODIAH*Va#`6@*Jhva2mNnMbmeEwP6J8rw^y4^}t`K9^; zZTIdsT&a8xv{DKsa5XyOJEqj%-N_EldKm02)5sVbBb@g|6B|QO3w^T?T(du-dPY)2 zP-s)B-7+zkt)M7M`Al{y5b(K-;`HF)IGaLuz$FaSknB;NVwOL&x$W#V4RHzxh`1cn za)~6(mMSKWLNmZb*-Ze3%iGWv%w~>1^@O+^2)PliQ)*iqDKkQlnayyyA{)ZwEM)Eo zKZ=4VHoHnYzzQplRV3B15igQ-lcj}vN)7~^pi)Dv-xjT5;`4l`8blZRLSAELJRKu`Q5R@h+h#LH z_a9doyT+MRHj^`2A*R=B7X29FlBeicOG|?-+PF}tpchqY72?N`wGh;I9v;@^OfA`U zRo8a0M#S4w-F3y|%9yTD2gBm0h^rCP5Q)o!6ce@5AX44wxwIzL zoi1Y?zm#?%=(K@3@x`I{2g}kDv5{4cSV7x4*`QN$V{VREL_FNPW^GSLdl!1A#YK{8 zO-+Btfv2CwZ#`vbp4z5xZO9J;yxkLk8l}6Yrn~!q-ge+XFo>`r(;n~8(D9+cqlfu} z!&8-D@W6q7=lxGSasU1IfA~c>WrSS6$Ee5oZ5E0wS7MXSEe9*7l7S(RM?Y0{!K6emqqUQkQ|Ezj9v%DXA-hNibzr$-C zP{x(7XzzXRdv}5Lf4=R#@4d|&JT;YyVUEOocqNMIxeQ(gaR#SS{Yo}UMVV|d9!NmK zJD*4s77{%X)x6%U?xc<$Zf09NEIhoj8gc(k?7G(Pgfmw<7Y}$*^~8uPN=o9eQM{vG z-3hea`b!DTfJZ_A?GWe_XYKFk?d)Wr>+MAGr&x}Sgnc&ulgID8#gjzr;b;0v*QQ$A3QuTG`)~JJ{42vAhB`kAV~T1JoM}7 zTvnj$c{pwH)KG+kUs*QsZ{rc#UXdr=_r@x z$RHUSNh-dMjhnV?>Ly%XV--8gCni{PO5~!G4a}07FZyfhSn=v={2eZ!0OUaUn+UIL z>W=uGdAx%;hpmpKHJyZ6_)-FqLQ>BX=~ps|sS%i+r;2uu*qtHqzmY$#2?_FyFa*qk zh6d=1H3b?YB}-qTEtP>Z>Zzv=jHDE^^egDqr=N1=U{r2w3hS3bLq8{0W-8Ov*+Q>s z!)_%dlYcTFto44vnd(uY&d%d;IiV^gOA`$&kAi))-ltpI(1tiYbu^!`iO=HL|%mtH^GwGt!1-``_4mAi> zcS}8aLns>J4xq+xWOqnC1t2h_g8OU*;mjV*+X5XMitV+5z-%(5Kj(Y~Rw^Sfy7C{; zGfx)p7){8B4E*Agd_(>T1$6R}hYlS3#hotzQFy&^5yx)hHc#vmzdQ8U!$14|Pd<4C z%mc5%qTOPww}1Pw;o-+#Xi^beflR)_*aJ<$O&ovc-1ERgPd)X}1D6wYKZnd9XI${P zTGTG*P|0>djaFijQqf@t{zq+qLeW8?0jC29C`N5UdC+zHd6+mGZ@hgM@yDqB2Eazw zU8md$sy3;1s{g2d&pcwl3Kivu@JRQh$fLG5-E`AU*DL+X0X3vvuHJ$3 z)T(Ip4eDQQAf)~c`)lwQzvd;xxDHcf@@93fdEAT^IE;q4k*~>dqw3?!KeE4L_wLu2-)m9WaIQAX$2|ZpxY`~pfRSn1OEa(Zq$KNo?~ls_~6l@skCH%70NF5 zO^~5a4iAsd&;(e<^c?Kjg%p-=2Fo^?ZA?!MgAJWXq%#H@L|e!Jx@Lq+hz}*DaN@cF z;uIC1M#PKiZc53{2TlNq4v_PC;HeHmTH=)zU(9c(FBm8<$BU4suEvGG-N^ z6uy@O`cIXVl-`LADOp&R?DT;@A3-9fr&3B?m>HHIJMh!*JvwHq2IX07}T*-X8sFmgtoG+6ubwH>q zK%=teBdX}Ll{3?96=!oP&6dv2Pv!MX@J=f#3M&kPLM<-vAzRBtq3DzXCP6ytAj43t zNX{Ow{VS{E~Lo|!5 zAB4%XW-yKYp_)wRk|ZtqybiG4fGS+76MAK>BDC~`A&WLY@kIaz20ReV4R9g?pyEDc zOD=!FMfkzN791TJ3gd@Dml2+DQ8hKdl0osH;R$NNspUnSw)ogi_T-4obE5xh$uYD9 z1I04nvFr_6=~b)RO)K{<5?2#@$?~dEa>4R{%eDrp2TrI}9)S{W|>VYS8nFc(#+&^h5XSK-7Qj68=UD?M| zuJ@GgEnn=ZBX_!%gkSa43tcV|VzqP$S1H!!F02NrIw#iVC5+ppSPhqPH#-8y1_q8E zWwjifGQ*2$ti{f7IXg8n#D@W0_puc@cwEB76)hk2=DjW86dJ()wJenz%T=hMCI;2Zw9+aDN`^IK8s?X(&dR)3|;Mfr^5&3yMc<{jLlWy@-eja)Vg<)1`VvxUmu zjsm(}9)X&$2$I7grU*Je6qZj5`W|;!Og?QP?~Z!iiddLL;hpY&TMg0kXp>>2a~?^g zf-^A^Y3*utq?0)mat)(M%+M-iP^rit%R&Smr@6JczaxU($6}I7WLfv-868GUEu-L} zPfDdCx(6E-NiK{`=3-fRWPLE8AA;iNV6i?a0O2wmx z7XN&hP_AJljvbDApFT2%s-K^oJbrkEtX8GL+a>-|YmalHG`R3Zs?W28OYD|i#)}r^ zRm4FO&8(N@S$uXKVr8{6m)*<4b)pVAd%uLuSKYm`U)Qhb7j(MPuRPw6Q{+@Uz~WUu zIeKrR0Z>PlUP54{y(KijnYs3*J@F?mk6j-`t6ZU4ZI9ia*}oGAoq?}PH+eC8S=Z%M z>bh)j?wD}w4+eKGRb9~j zaPK3#ag1_7wvrtt&uZZX<^Np4$h;jaXyYXnfXcz>!E$qDzT zV%;BJ%=`0g&woGF0KCeKOwBu-hw$<(h~jgSbN-i9}TE2-*4x%0>fzJo_GY!G+;t?9bxb z#2}y)6i-k*(NgHkZch$RgS^zHhiCis02Q96sV<`_WRd`%wG+7+osGu<`4d1A%82>t ztat#k6LSm{YvS?2fq@Y+w{sC<=@~AZjP`k?JYU z`uf4yhhwuy;tTelLp+;;HD*nB$Ky|w_9cDpn(p_U2AJ$#S^MHJdm93tps!;asZ>dmRUkv}?y6U3VX6>MFpu7zR>E`X zLWaHI{Cw24X!$;}M;$cc^+|X)^3>(TL{98`dbHpV9$lFFBd+S%Xn66PPg)o>BV)u* zuWwGa6-~}~%lD6(STtizw5TX|6FCZZyw%>e)7;DT+{-OzzL(JuLP@Bt{0#3W_mEra z{lDja#!(A)`&n;hT=+!or`*Xi+0(UME9~he)1L0dyIHkrM+QcE+w42{c~{ue2kzS^ z_Vl(>?CBR>fgAOu1opow_Hx$isCw)~E@ z^qE^eOw)j19#G|@=(*x7J zjsCexGu~|9&LcHDm62RePuQM4p*@>z;46pjxQZb1H6*9KoXoWgNlSD3WWS_oay=YbZ|1(efi)`5 zefwwDsGC`%-oVU!9eV8z=(TnVI+&}7ID2?-CDt=<2)(xD=12eX*k2~5vUv^ex8(8T zN#`VwJb>ofKUwP5iwJi}l zWNyPGM6Fx5rnANCotyJ|ckIx#u!h4!4Y!fU+@^0QC1=QA)6%_Rt5l;~U#E$^M64Q9 zYv;}*M`~(z?o@;ys&Vbwwe?&P5>9&8Xhcy>npg&A@&B&rWc z+d)t~U#OT9&(FNWP48E_I^{ge56*1=<(YA}M#=W-N-}B!0y-0~G{|4OP33L&V zYxU*oKePPaXC5Eorw*;|?afyzF-V8{)qhs+0q~xVwc%F+4&=ur?DM8b*l@v2C{sCW z_m-6Ho^WFel!hkuOCFpM*?{!4 zQY45EI9*n4A6|7uKmbrqWOTFXdbmC!ky|!e(=*rxJSZJL}MrK|`^jB|693RoGSBN@rNNXZ#A zNH)O91A*Y7VxLA^NOX3%6}YAN5>7rr?wjOaM8Hqnu{^`<6Og)9jg4nUlr2w|HL@OS zWIa;50jYf+Qrm;JU60iEply4Q+8VbTw4R5YYWFx+hy2(3Tmu6gt%*!;Z`3h7Ssxl0 zh}TR{4-8C93=B+9_x3h4^!7G3>V2V|JD=L$P`huR*So2EY;xz$xEJ!k#fQy);NA!C zrAo_t5CU@*?Q_Ec3}gi0dN5$6HZ6*pO?lCEOydM;_%lF# z{5;u_Ex>WWCWq#TMS(J5>gGKDhHkQ=^}f1bu&bjUv$3(O(|ipO+={w$!-v3$&Knfy zD2Z}qb6APjM??n_TgGQ^Dn^HlmCZhL@S$)1%L^&Hb0HZ$%|xr8LoUxtsr>vt`#oRy z$rA^0tL(u)E~!L7Df#oLqo?P2&)aqDt>j#Ph4j|}+{k6s0dn$1>YLzKctm|leO!Hl z!^*V&Dr?vq)t9jOG$|r+S;_Ge<@3sYj(@o6`Okm8Ioo5}=tMT=%gXg(5X~NXY9Q)9 zc5G7Qc=njQ<=yP)v19Sga`z|Cmz=PsYXSSey97H}y(l+Zl>oH)lTFgNj zx8J2ki67f_sZ>hf+2m^6yV)-YNt@ph4gwX)Xs-0+EXsqgHweCi2XAW~8Ezre1u<K)-i52J-Q`7LI3DF|N4zuyicKg7UXgQmK5`^gXfH>Ou11c|MUKux zj?P7n&P9%zg2BPT!vhoK0&~ZDdV1P}mHFex1_uZAK5ssMa9HRUOGZBG$-5f~VMW}< ze7x!S@hw|6wFNwZR+joB)TvG7DxoewBm7Y?xUnmOH~^!HdR(~)7K@pJzbQ`GO6I;S zzVZT=!)AN>7ytRWtMRM67TCr%j;lBbTxrqI{z5#7gsq}pFQIXeMZ)r# zaD(zR#T~B4e6-5YTHuO%p%QQ^`;~j`w_J7ARn~mAWs`X(oJ6d+DY703GfcdxSOn7= zAu3)CSCt%rT48zMc7?sRSTH-ERiGR((NHi2#!=;Azy~V!rlue}jI17W zha!*!00^u=@oE=57c7ds@g@wi%5}*iCQr@PuJvGP)e=%8h#I*GB!%@%EPYtpT&P@+Kb(%RNI|=c{ zt=vhB%T8j+i5gm3+{6vnr!>i&;c4~V`)ci-uHu{3-qPk|Rjc{5JDqgZSJ<@FKl)W| z?6hs2e642RPJ6A@=2pMf(^~YIYTqbWYRMaf@FH&~MzwB5-fl(S-ltgW)@!*JujNj> z7I}Lu@@4~l;)|{aSyORDx_C7C$tRs0IS%@zu5M_ES{+j$vxRsy2@j**3`nK;$3H&$ zI3yx4+|}LP-5k_#=+z*fgEJWka#Fw9v=Q{JUgz8-uuP+@WX92I$4ACzlW8T~ z!CEyowqe7%uGae6`W9iBS<~4N(3`>N2hKYe1AoVkHG#6fJT(MdY-l2F^G9nC7l-50$Sf-&201{8agka$I$jhWdW>yNk!i)LYawDpYpLua%E_UVrt~S3mc; zS6_Y8KV1jeOh&CI%i!W#Gyeg$s<{Ef|(E3V6!hQ2R$ggch&^T}CT8ZQbJni_YWh8zP`QP}Jb^|6 zxh$3v{CNxbbU`Ui!FI%BDrHOO$YlVF<#qyd2~DL+VzX4hN~6=b8d|Suz-%WS!)eG` zUGdijsc$0)GE_al?*ud2B>tG6o|>~-FR*x`MZIn3KY4W81aRh(!%yoWvRA~~RdS>I zwl=$L4t;egT}^|S*>Fp-Xa7nKQZ?V7od&5?CB7PW7HJo4d7=i{g~VTh#J?Pgzm*ks zHxhq25`Q@ozZ;2<+QpgfjoHPl%0`2sb_m9ryh?JIMRpjvsCLQt6)>F%AXC_^r%N7l zt#y_XLx=QEqXIMROa`h&FQ$uEF_K50eDcYoNh8qBlH29iJ81v}oS8M`AOX0veS2@% zQJw%-GEsJfd$;TE_ydpA#^VpfZ-8d>{6+Sl^LZafHm_S`4!X{Gn>+s5pF>~v^Us(U zAGIfDmD*6OK2)pBCaesfni|Zjni?fBF9|(40EaK{?d^T}OOVPOT#EOp7G=hL>V^Eg znQgKqpZ`oIPfq!@{mm4Wdh=zz%fsI5q=8TMq7N zKRGsuo3(-POt~gXr znEMd+sxWEK%p?}z{k20=?)Ap%z1~8$WM`Xhl=5JZ{VLFWw(1?~fU0Je6r$i-+R0@^EsSW(;}>EWlD|ZH=WJvrY0w0u&Zkb>Eo&zB6|Y*qq?kD(E~OsN84*5io9qwbCg%pKPNhC~2~asz2*8?2YmKs6)>-d?r#$mHAUnb{X-E7w(thsQv}wxMk|sNK zRXB{E#swC6$uo)_5Ny;>br2DY&DF znO=*ozmDni26X-Hd@h{o*I`Lri`{;W?9^vM4T-ZE?>1Mi6gaB|-`g>By!NbCd~d$i zC8Vimw&c}^_wQmWz2kpr(c8ktg70i*z51#vE+^&iU;j$$zE5K-VjAdYF!6hR3-dTr z&tm4=GO8N{)0u7k-g2NccUF79Ph%T>R-?Z+AMlgcaTdeh>i`>gHsil1=YYTBEE@pv z7sD8E=1sto1+h}M{eQ6vt=8tV)xpn118H*zy$ucIT(Wdht6MrXe6eh4ph}GjCFIgr zoJ|91Vy~Ta4$BHYNdw_2Sw05_+p{@`YzpTjOKT6jGYJ)Mz(QvLyB{-{kaXETUB;uPtu>a|yNYaYE){kn`f*s&DD&>gsCoDYHlR&(Et7GDbs`^_p8CIg0Eub8Udb&0}{|77~=q za=F&6?ZLS@JF}2m@Oy2NOGpN`kiZE88npxh^_wob$am5D+VvyDQ>2o0ZX{QZ;?!(< zO_FIJ*&~rg>N|vBlAND+bpk`_>UC4?V#9{DU2Q$>Eun=Bxg1bvjEehoH0x>G=-Chs z=Ld<^4d(Ri+a%Z^JA3IJ{}g*!udIi)8Rm6kFU4Bi zC2B!eiY13V3c;ft3ceVD{HR(4SxPL74ZV`h;GlEZqF2I&zDwoKKxFb?%4c2wbj@1g z5a+(_c8HLFNS5~kIR;m%Z-9FA4_1*k2vAaZGp{AR^Dg!G>Opk`5{13+Fn?S59r>S= z<}s>_D1!id4x7hu+L2?Q@-s>de#-Ho8vruC=!K-5rqn|mKTNj|bG1%o(qY=;|PX1T=5=_87^K%}0BL}W{3G|y)Wm5NVtl_AdsrciMO zgKj-aW)ZBRkYwT$(KXmJ_LBJNaJq`o_1J(m7Y)GUu$tYCMTjp*d`qz5 zK_&_rIc&OuV&K7rbar;|gG8S3y+EF7sIaTdWo)&~?mDiFJJE=@jtVJ0HI2hY%Vf(@ z@C_lz6m=cv@cFP>kjrE~B73b-fWa4!^8HE#<#A@3x9?j=m>h?0&d z*GocT&|c6t)@ZPV7gJd?5fvVhT&bwJ@Z;j{9XdWTTX5IaxP7j4Dp!hiu!#Z>Vv@=V zsWqe@PdsrvIq0P{MPDV4;~vHI6j(i z`AUi_5U3;cO?h{3Zg|i$T`1JH5g2c)(=V-Yl?nwytwqyAxd0Nw?3zXwX6MGo(^|NO zLVwhp8Yjp-GCVrPBp4^LFgG z4>IO2LwCLu-T4Z1=PS{jn*xEu5G&6vz`i5yx(vAIWBU&uR@{+R?rn{+kQhC7`0%08 zwC2?Ng8BT!$P5#n;&kAq%dtj(_c;o&m#GCM)ZX3M-V}B!b4T_)xli9TJ+)=adFQMn z-<=W=b&ahZ9X%Vi?ATE$&J5t6Jv5muhu55Q&W?>8HFg>fg)ZFG#1NB8E42Y)jk&P_ zRN4COhJZ0UJap{%tgWu0y?5OP-FfWT!w>)R8y~rbf|YNDe`PD$MUEW+qyFCbkmJrT z|KvBnF)OdVmP9`x{=Cp60e!#s&aeFB;fH_o;P=05QNG92h+B730Z_zvj}OlAxwKN> zAi4N4JEXE1YIL1*&gGZibOU*SDe6XE4zB6P>XYX2UjXwigTixExkJ19^2@I!Iek5P z=>A3j?mhgng@xijCExt=TXwT%{YF_Z)kjS8YLyuCU07Aiv9R6kRUosifY1`4(?U5$ zdcD)FZ!!$ls-{{=s^wwB<_nY)Lrpl($(ThNP~+o12=X?8GD$%$P%lad^@swINTRC|Ru08EMCB z9uB+mN$S%0pN{N9&@Gm0Jw3&B)%Q-F(0eQ5HOfig($xFL?O};qNotIogzg}eMb$a5H8+T zM5$&qS<33V1MBVtc~YH0*|dp31x^sCvcAJb%8W!9SCJ|h?@kdb(k1F-8RK!HaA^ov zG_sm>74#k1#U$ZXL@cwKjA=_JIZ@Cw1T7j_P0|Qbi8n#`!Gw`o_`xs?SbblKOas?RG2hq0F#8MYFjt%peWe-^*s%aLTY>n)Y`~=#o?;y zSg&6JE@wAu%WcMoy!U+W=TBqP?f?0w^%qm7krY9Uw`M5rdah+y=Ngx?F#6@qzVa~QDs&FU}Q$jZ!&iY`IQ(L`c-Dk@JkdS zzChL35Pib_H}K8e^m651@I3_zjJ!hKtA33lBi~ei!=XVweLH;oR-o|{0N^%3J}{zu z!T!eGaE0w+8+FVSEqqUfQjGF(1kK^xhdDZvilJ|Wl)aRdY;v(17pMZP`=F;78_(tz zgc?L|EvIKF`~<9`m`3ZvUFUN8YACYTSnE~wJ_ji>7=F5zOE1J2xkm8gu`nhjOOmVu zc8`-nc}9G*&lkxar5vpAyko6p=fDg5d=U5-Z9-S>wdZGNtVnqhMhV-7CZ`d_$^@pY zlCGA6GGqtJ|-r3dO+}ws$SMRks`&%E4G=>Avbi)N7&gLL-$rm%pwBaWn z|ES&?4A#1!@?iB2c>eqMzyJNCe|e1L!GZBS+eF>*%};#lv!A~EM&|V!*%e+zwZcxS z???rU?L^CkXXr(2_ugcD(DC6qqmZLj^dcfafQe|QP| z_=7;o&xaQMi`ZD-Be(0jJbzRDI-a%{L+RB>5uN+(?|%2YZ&PFsc6UH_-&13fQz%8+ z`&(6EoD!n2dXKY2;VX6-DqL(*a6#4Y_TxKZyk(nhX!bp=m3g-BqlX4_AiQDX!(i|& zPLwi5=YAT=$U+xKq%uA#dn zTFY@3I(X#nXF2%m#=3aVqApPgha|Y?!ru>P~a4OdoJs z$ys3=Ds73MxBaB7-7~d|N8W5VTWxpSD(x1mc0DJxTV+09EppSM_xGu@RloFNqY}F^ zkz+^stum^ggt|LZKgo(S`&q8`vvZYxj#>T8K8t>uvDdk3KQ~*A_9-c=pTp8m!mpOk z?X}43E!?NKa-U+1^DW5hEy(LF$m=cKF>NMOj%}bS8{kRJ!@ez(mik(mypRR59t}*4 zNd@H0LZOsM%+2W0O@;Z9qZAZFzbM9$LkEu>9gr;EBcmx4*3rNGnfIaq28V_bt!=eA z$|6&0EwxZ7PfsY+R5kh6V2nb2pTBeG`j+N~8Xq)sJ}>1a30!Ont$)EyV@ct$Qf;LB zo`1oG7s=z?E=Z|fa@BKp?%a9qw#}P1tX(sg@0ThF;w7@4tXE3I@e$)LKVD%LS$G$I zp7;Cj`|enJ)sA873E>g?`ra5ySCoOGk(6SFQ)Z7aUXb{rug zpVQm9ef!q6o$aB}QuA#cA({u%P30?6a9J^I_QXD#Z;=wcB?IrBQdubXZ=1zbW`X9r znk`rZ@fB-$W+87meAv}c@qRv)2PK!Moq4!!gCE*30qphVi%P^tWJ)XayWG7P3yjr&T^}8-?EC- zr!1)_o&E%EdM54ECfaEUbK6su`bF(@M5G?kx1@eIQZFdCm$6E}6sf-eso#avUxCzL zj?{O;>6Sj@O5Glqo(`~>oo==6Os6|K&bVTCj zaJ9F07q#oo`CqQ&w!F)gKV>=Z#6)nOaY?t~e$Y-{*UfQ0QY`WG3w$SqM-yjU z<3Tmo`T(=){miNlFst6jth$&e zg~vKr48@>D)KIfJT2N(G`5JO9sr z{NtaW+P9xy9h=UP5J-(|>flNF;XF`Ut+T7My`{AUi`JH(#v?iAh_^k6KkFNnV_4{Ubm4!4JN6=he6oZ^VsgvAu62i}HPa=#pr(rJNYqw{JfTxI=igDCds)X1h*B z)lwza%>K|~qo@}e?;<&M#ay}wv2M11H%Oj8D0jJU-u0?izv>mYzWzhk5_~X#WB;T2 zW%KwbG_{*mMR~~nF4!&Kdn3-llDa{?m5kpnv1)xv{UrbYg;nexDoO6dPyZjud8neW2`%DG!nUTn zySvsEuJg;{28OHeBC$(Q{*XqBo03{xRDJ}+Pksx3%5sM@7m8Bi98x#Z1xsSUY0Hx; z=~CktSJ>+)aE7H4bLm9UMozNk34n5C_T{t5=|RkzqZvx9Cz9jiQ)%)Dt1p`K<75pA}e zp7sq?$l^gmbEDH4ct_+mI`f9uA?Zmb9Lv06cj`Wd$S7oT#2pt5ol?43ly-ats@>y? zwPNhq{1|pt$!EVOBIyaC0P8X*wseZboh3J4@;q0v0Itlay^3b10 zvqMAK)a0>!fBDN__K&2C!Cp8g@#`g^!G^ld6T~yaHeFR6dM~b;;+E|^w)ECe7edNX zkRuCMj-#9&+5h_x0h zTg~IStWEEs!*jVpSqiS9_{9X*CC3&m9#^R&s`>(Pr|J%pc#(Uvhk!@7_v4{p)PY{$d^vs4q5GFkSq=J}RAxvsxaE z8S0yRY?t_XS-T_P7QS&xhM@8Y^Tu^ewJhQsXzSo$(cKJHj8wl6m4K3fcPCRZiy3+#Z{z| zyi2W^r${8FFbe6D3+4E8Ri`%yE3d=Vu#Qx-t!tZt8#Z`-!MY|XloN6}@O;k9uqD*; z#dMzSUx|_jHrcECTzW5ff`zduHuzKHb9^f6Fv~?DzfiIH!?LyYl*XfxBoQ_90l}y= zK-O>3p+7fQO2TkeaR=+A3`u);i?^c#y9S>Y^$-1ibSw>UW;~wenadjfQn?RysIMHb z*-KdlVy2j)a%Z$ypp>Ef`uJp$MVt^gC!U(;yd<5^B`LBBi!-FyAjBmua4uS4H(KDO zXn~v20z1$G7oY`pqXl-O#Rj?)a7100IC7YJ z!a4;0EiHOqyGu=tj+uN^Teoc%TB`NbE;5<7XFA%Z^oC-Qo0irf5aV$)q13I%1l_hN z3dM3Tc<2zK`Sx)C7ryzOZ-4Ecd+zzjM?U)T`#$-pk6Oumug1M`F7z!I;lY2qv7d4V zmk27F^~Fw^=wvp=MpTW~Oy{AXl?i}jN2<@@Cw>hgH3PontIC&fVtfzlP>$cjrtt_h zejXy%{J$^}b7bhf0Jq1Npe5f+1i4vxJ{2S1sXpIax@_FI%H2+=%!FqExD1e)R7#IV zBn2-Uf^3>VD-skAIo!G2-TgtPt#rsJ8Yw9T)6Xp}L1Wig-_gj|;K0HQCCCBQMvZHR z@6P^OsgA^#6ly$`p~-g8XcY4hlA*r#G=X7R_A6;D6$TAT|D(7>E|@-17yVy~GwFkMy-jM$wk?dLxA>!wmL{ZhM>@2s$+ zm+Z}zw(DxX@eSgAYPOo`QxBL50FM)_v;Dm67w;25`sF^It-UqU9*Hbs*_)*srBh4o z9hUY2)%L1&=T`dKS7x86NS)KlDFG*fieUg~ShW*!IB4}$zJm11OM$H|FGU3utNHSb z$DmXC3=6!~=Y{GRaNRNngVJZx^`7|{tdsVF)%F%ya+k*7ptR?!ws+;Cmbs}*0V^eC z^jykSE3`~cAVj>0hme;fD(-MgS8s1GdN`Zy>0y)HyK!Ti9<2vvhAolL%nTnTa!knw z6k2_~?(ni#47uVxiMi=X3ekVmgMsYfuSWTWm5Xo&3?XUSh{`ns~^7P=J{`Bkb ze(J7kp&`4(*l4W574m$JD~+3tcN%vx!rlVB;!0vVB~{losLNx!l!MA=yf>b+<=k`6 zdm)e8UJCuwG|u+jSaX&iK0|bPj*{Td(GPOHaA7R zO`Eo~vkQlUDR^Ixi4X$bz!FY;&BP&PGFW5|M=}WoMRI&-QjeO&ign69$x4qsb%!H| zTX{AYx4RlTntZVq@l%wj=2jM)N>E2L1k>D}a8om34SQx{Xu1r7ch008&CF-OFlD9; zxSR6!HBBBZ-q%gs6Gsz7IfP8?-auU|x6L$@>mqR8)CS#}P8iMR#B)GH4qE{z9@s+~ zVquL^`ij6K1FZLi8k*~bR0^ZZ*VN={>Sz#4O=D{=WzRh@VAM-XI3$9!)ZrtEfuHag zEVhJU3NDG3PLYh2JM`+#R_4B$nq%#3`)7-SN{It44y!Y<)_XMC8j>jw&ywT;IxGt-L;y&zfc3lKd%4@{xMZW~L^|lh^`P zZ${ORFUb5;wee!F@L(@DB#kYPl4wuyX-I!o)5e_7a*C{=;#^Dyl&+fm+tvQ8)Wp^P zth_ceQ$VX;+fsI@T$?26n@`f#%8{@3OZ*$BjJ(MJO*__LRmn@tIBGQ!&}V6DUY4(U zW!_M=pG$4`%Z&?3a^cdNkysu)`DMatl9#1BakeWrnR&d`j!rMPw@eeVYFn$_kpm3X z^4hS-N4dl!xLyX5r{$|XTi^WB9%E+>;^4}@rKK@>dRBMEkilEJ&8HAX%Me0&v-ooM zewuv0^g~v?<$eyW>?g~mG}oylmwLMLs$~>q{#57U3Q93i7u)G@k#OP3cn_gz4}_c*VXt6tfX%3mx`!=<{~ zGPI<3TTfNZRm~Mz+165){~nkK2;!P~Jlf(A!m1Tra;l#1HkH)-Jo)^uex(>#O8Gs3wd_p+Az>ls_qvqZC*k#JNy6JDO zL0%H->!FPPS4@;YV61#rjj2t2(({;&-GOmeprk|;JABWxxae(mJ;rYH!`{}y!Mv-f zal;nBJ4&oKX6x!I<#aVuiU)kbLzRrbuB8LS)ND?V*}whm(WDLXx%7BE07R(k&&S48 zH#etbvwoRc{G~M}TNsmVjLC(J$)$`*4`Z^4G1cV>9KD#Ub^rdB+)UZGS2+;B zO!nifRXwaV*9Rg|0&+lRh>_HRuQlds z18%8E2ZGXY*^9QgeEEMup&uP6xR<`X5z6rs)}N|`u9W+#9>xNzS_C#lW|Bx0*Z_Gt zH7~llmY>|>(t5kXiaKqxqTH8kspVC-8a)uHt@czbJWCt*FLhHpxtpdrRjpuB)k;-M zs@jt1lIk^^x_F7_q-rj%xa{Rbb8{S9nB}d^@-AliT6)*IDEY0l*~%>UMOq@mYV-8? zbf-#Yjw7ExlFtL_?2Y@59Xr~i9qsAuKC1TUeNN``1>^!nl}F?D@lTBXHZDM;bzq9x zj7zonZ_W8iwbZzhjVnEuDNm?tUwtL?1a~q)8`VFnpXB|k&CA!7`M45}`=(RVRD6y2 zBMSzODN5HU8B`bAjbGJF|IXMMnTlL!{I~Thr}?W?+}BXsn5+cGrzQdhdP?E*Jbw~Z zYT`b>7HFxAB#WsL11IG2uUZ#b=yo#}S27mQXE0vQSV+yRE4W8jG8R`d7Stt;wvmr9 zKR=hC60qTG=R`_rIj>_2j$e7+GVDp{lowsxQHb{53tZfW!S_>Ir$my5xF5sP;9Cv+t`iVR5VJVHd*vu60 zbXM-XO!=7i?=ReN^)**tdCj%2{LqccC-HS2SA&3(|A|=kzq3vJlllnYsn7Up zO;e@M;6jJ z-fRu+N*8H-v;yOCAP8466|vzf3oo@oiFKU4_EUF=RjHR<&yqGl3yHmoLuS#et6wSm zrj_Wo3X8a^EDKc)wNn01X!HMJ?@XZNs;a$zr<&*L>Uqpe1|oz(1QZfNf(i(ts3?lh z;VGaf3Mwde_3cg=6a^HO;i>o(MMY6WAA*P|4hVukAQMS)vzr*=G;`{oe~3ibf$Xdt&-JilX{Qeu3zf7oC@D#o9UOl(E0L z)?)oyGYi-H_jJ`u&uQDBFLVF%&1+~G`(mBH5Z7C{&Vy|!>F}aHQXkX0q4#Cm(3|%J z#l2A`__knA9c-H|obTW{vY9aU$G32d4s0X#96^C0Ln&<8_BsFF9Q|F7kodhF1F6mME*~OQG*9^X(xrObSxp6sTt73_#6EWvdcOkZYyF%%!{R@&A?%Cf? zH@qegh|IdXcLv9gKKiJm6}M~t?=)p>6Qqs1L8d-{IXPz0_t&jBX)trpTh?t-uTYLc z6nfR)DC^V=`4*)B&-47JQ}LR}Ccu3pK!FM&F%)bImz8);BKANm;jA#3gvbxRELVpo zKSY6r%WZ`fH?SDU_Yb;i*ZrRSM*G(u;3Vi|^=~CtyeN^h7fA)9nG(rutxN8FylDTn zEu$Q)OB(wmshT^~ z(zVDA5ax1#cDuV5tytdvPegbLFVd4~bcE<|o3Ai4{`?={{**nU{MeuV^w?vMJ^9?= zKNsO`YCI6(1+pcW9(!x{e(Q(7d3N9Qjvaq_@RrShtk1Si!n8jd6v5l953BvR$QIz) zAKax3M`F7tEBE_8eOx=-&FwT#K4SBm>Bjt+GDNEXO(4$X_`H4G01nNBrSJ;-wGWY# zbSqEx+I#ii4ebIkPLbZ5C1wletX^N8qpsB9Or=3+q(N%DY-qQ!qp=>^%}HptHXqu} z%t`|%Pe}TC{}Z9z#XbVNAg>})JFNyATNkYarGZ5e3@u)^3>Zj9WAL90?Y4GzHDMn} z^QZ=pZ%bA!B_U5$VRLJ9E48?-oju+ETxhp0!ym^A{1{f?&B&Y}Ixl3cUW^2P1S{}E ztcO=>9t6&>S8Gn17lo+M&r4PIOqNW(G>>YGQmdkbgBU_k`KMc3JG z)lwBBtp_C5?!pY529z?H%;l{B1y9is?USW*dNJS*w&`8>IZSI#nFbJL}m7bfDVK7)w{3fF&tx(a2mr2Hg*Qh#gT|(oHelqK zs(CHiv7B1E#jW9hmPGOs6ArnSa7$<0Ju+9elyI07`(Z0=$raObo@D~Bn;SUz!7B*3 z{`ljM|7;V6{zs7JbCBsvtdBYV^WJ;!z3U4bDK9#m<9zEAt}QqI;Ag+N@5@_W3m)cd zkTk~=5jfwv7CPM>mb2aA2!fjf;RmsXQx~`#WHP`qTmiq{2V+JVY$i>a*DFKHceM|G z@PlVyw(g~%yMs)}g^2!@)X!~F7g2#Qqui-odF7QCD7VoSV;|RlJYbkl^M+3W%IsjX z+69>DW+kqCMR`Uk!#Dqa%9rH0isLf%bhVSdmhV%)$nic~VJ@d82}GPMWm;_K?8f2L zX^R0$eKk0Fata_QYo=PO#D%v&9{hMzO>+KZ>s6>})383Mz%O{f(LnV?wVKcIhc)PL zD>Rz+`REk^M0l&8RS6x${8{4CQ`Y07c+TMrHbeuCet?>NP`kV02F34X^6v6UAvfm( z{|M`d(?Q90H7g=U6L11+ZjVpPXLFe(wd+Ox!h*-s6K!tVO43HK!2><8{mMgdA1J+i zy6pDbw?RvE-mSxeZqWCm!e^2HSml*`NyJH4?uO3xsI!taKscoH0F#=fZY|~p%%C(F z6@SRn=Vm=n$^z1}Kfcrecxvk$$WNJODu8LwN``1=dN!pCA8DweobA(UbAvP-pr#Ta zF&#fvh2SPN=Jw6xYfd4psOCq;8P?(9sypEK`9m$;ZPnR1qgtRPcb@~)w(1IO?Is=N zaA~rT(y6SSy(-pF=eFGsB}b)C&lW3SL9EPZ+)Y`D3bd4gtZRVRp#c_nwcd{g*o+2P z$SZC}1H=s&J%X-2qsSl^<56!T+J>RdB|xDia`swZB1^qm$MiDUmb!1>RIcixw6O_Z z+swp1dtEQFsBuoEX#AtNIi(xM_AzphV-S$Ep#xhz_8MLSE>|(AiO1(4JW{JIThV@m$A_`}>g(r1^)P&i?jyRrVPKew5Xs-g)D1Z z;1}bx5)acrrU{#gHGt)GY8&z0{Oee$FIs7rVc626i>ZWVjnaXqRX+g#D(P^25(N+euKUZce{f4@4x zY&d0&@`x)_eJYJcl%@g4@(z|#rC+~)@3w@g9@k;I){*q4cxk zoLTcV`xAs~$M(&|65YMP#uZAq2`(s(@qIG!{rX|yj+m*AW=z);tY^4eCi)DzMKw3~ zsZTs%I+F8mTlv~1-Zu20>3WVylq&tW!^`_<7Nq5PV|}DgKS-B+fQddGT_PNPr!&&0 zGtx_}fSOWlQY@?nTkcVh;qE0%mUM@eWo58@w)pOVNa1bOgYPswB>Qo(=3-Qo61ESfk%B_>B9+m z*tn32#tYx5JPcsXuiodC?t*yzgVuVCklS*RSQ7FU{>#oD|zPN1RG&7Gaw$i4v1K410@Y(U?o7JzhWj zOL=9Ud7J7L3Kq+hx)8Tw=Msth& z@f`Gnp}^Qg2dq3LC7yZ7jUc%Z)UVg!B zrP6}0Fk0If{(h&fkEL8GVz`Ev_$V(hC(VJG8D3~?V>$POv25uCaLEg3US6oLxbwK~ z^SJIS6-DV~#hu4>zZKnf9@l*y*S*^7_QyI|UyIrTN+mOspoz}Zls%31w9XFicxD?5 zOdeiy<<94K4o=JfbcZoM5~Ns#CFACH>yO#QKHgA&46Gi7lWMoLxtK_2io_r3YZi%) zPNqP03rTwC+LKS)gs=%M+^D&7?GbAhH@dat?x&%ve}0$_ybv?^U*K*<--O-fJJX!tQpsvRA2h zZ#wY(wYE(S>I$i);znwop(ILX;FNGEHT(0cG=O72v>yYjDTGt;Ga=7nOh9|5<^y~V zm39_1_HtT=a@btCcW~c0owc|fCZ2{I zbfJnuWMNx8lsKi+S+Zvs^Xb&U>abU{7p|+R#cx$G-{H(w!Vw2;z@(5*jV{}8)`2t6 zv1unpUYN+zJGNRWk~$^RS3fhnYpKK-Arl@LLG^g(M_4(RvvR)5%J~*!{Sj8qrL6eN zSvi-pa(a)gdcD{@R1~MnN}z)^)DadwETfU9ol&P!PS4DG=^1GAI*vd0)HVn&1Uql8 z+xh(SJNN9Bw8*|WF!hS=9Hoc~h-pCTuY3-gsqsiNBzby;DAYof2aYSuY}+<5mY7aV z&*d{?+n<4wY#1co-d(!^n-7glOaXFE{rN9r=~4*&7HjBkic#GWs1?lPg!f9KhU;jJ zedLkLB93ZuVt9xaSk}zw(2i#@l%IU!iJY`FVE==L0+J#qgcu9cfA!6eKKqv^|MK`B zAAR8K*KC5X?2YrJFmJRj)<6G^Z(J+u>|DFaCBR<6y!FwqP4Xw?V5gIP zbUuuuL6OP0S||>z$1VM}>;0#G{PK@qdc{@eDK{v;Ri>d)d=G_)a(oCx-!2Fj?^Q+pavDAW{am_Y%TJa$KW+nCg&^a0X$c`96QOfDG*S zvJb&l!a~%_@u)a66#Q0cWI&HImP4V?Pt-^$SNdcFrpbM>Pt5iYiSd%ESHXe&`^?pH~=`f9+$@#U|aSA9X8yu`$j|3f`ptj zqXU2tnpuy>QUAO@{NayJJocw&X#}>IxKDQ{9V{BVy5(tCSgDLX{p9}7qp50Hwq!mp zeza(gXo$1mS*S1!_`(e#Mji`Y_s00^=4iw#pDCZ{hOkAqKk+zslr`T4=UY=mz{og$ zv2diZvAHqAZGty#Xl(CnTeYG!;FM-5#cHy|x=ZFYw|N}9Zu#k_o_==s;3zx&_$(wn zzCb8nw4Bkl=0<4ZS~`}j@WQ;0O~Txx^k_X^CW>qx=Pvg2L>r`hfSSYBAcLs~pdjgq zmhb^Hp5{i{ReJV+YK8K`DxXS?e*II0H6@vW#8DN`Ub@>c$Z!YE&So%{(IWPzRut7? z5E2A4DSa60d902er_#}*D;?;BPA0IU$HkX}FM}`5Lmv=ZLD-*G3}lHtAbLF zl?f#I4*bq_n6biN@((U`<+AEXPMKhD-+#^yqqEg$J+j3$H>6I@DYG4Rd|a#x>5g>; zL#&JQ(2*CQBi|)D(t}AzFc~JvnRvIS$*yQ?3wwNyLelHa*6)&5FgHU0qbIwlxJDW;=S_ShJjrqj%G~kt!Pt}JEf>^UUL=uDVi8? za7DJ(T1}jeLB}gl%aWMqVe7DB&B2z>cBUy%hgO7G!EN-LE8I?bvzQjBKfDR)O$$yC zZ4rr($me&w1~cwJubdeNGLfKlBHl$iyHi(ZAfFxl>$7s^-8){`lfWSn`$d%C3KG+} zC1Q%hG|3``)f8f4H)n2dC+@)m#$7q{;bqnDDpz3tVppdXl?34dOH^3BIn_!ehW3up zGm>tRgpFs@WwRaM+D%}8DoL3*0jH9pc_bFq)DUZFjYE|}-60R^S?01h2V}>w_js>`KXgWXFa> zakx;ZQ{wc(Pm5Ea&U+YFv6gySOaH-I`aWw(xD&2oE%maNfaWQ^%Wc2d556szi&D(j z*ckQEFJteHU3>QI9Z6$8$}%djH#sA%#2!1b)yi|Ddto`4DfGveiocwiX7CgA7WTND zo-hjCmD>5}L%)6+#W^Jls}_$*TU3}@Y-di(jbf$j*uKMjbuBlAH#dv1($=l9La^d2 zhu7b@X!Y9FtClo}8k(8T)<#dQTAUunuo<2y)%0-Zij{}2>G4(hdtsqMr!o??7xoMe z?tK24r=EQB$!){MwpT1|WM(nDgF$Dlws!5|hqZ^)*?lPb!&e; zzrM=)41fGq{`gq?*oZ;$aqBAg*4yrR51tiMo7Lg&s_{Ue?SieV0{kQQCEreR#f~5vv;}e z*Wuh&Xl}*QhOPkb0elHQCe1B#B|FUMcJzfL9*Ax(T2v!@30H54FBO9!NVNg}1BIUL zUF|G#7`-Fy%V-};sDbXW4lN#Ioxp|-H!{9m`JiYt5K`4Klwr^2=O7FOr; z)Sn)!ZeeXX;{>(6(ERpoWS`p3I(ZM){2Sh|3F5pT(|rA{#K_*1y7ii`u z7&Bq>zkxBkkukebH1kQeW)8J4Ta39)fwLm1uH5XXC@?yn=a5Ht3mJ^=7EJo&2&T$t zwpjBC6?uzKDNYR;LqiiOuKXl)bb;KbM5KyoOlD%6Sy;x3P>rE;YRdNQL& zgL)T^lp~K^(H!#D(vuV8b5(DsY1xrfvsN;*1+aWoMm0?>HEr0e*NoT|KxVFZgaWm=}Azl$OP4FXi4^(D(SS5BmO{3uNWc~~ z339QndGY?mPbKJ3#F-JpO}HXO<*^T144km@xy=5PaMpClfFu3@Hbj_mv8ZTd817-8 z#Eyk?Rbm+)T0OAm^JlIw@ur|ek|V)LEaDYV{z>&J@a0Q;q)F?-)s#k_-S+s7j9i#Y zA*b374SSW7~V6?T}hM zAKJZlbc$zL@{)F^NH}*~)eNsB@eU0cf+#WknS?>}j!e-!2~!VZ*&Mw76yrhkjH1>d z7{U=Xgrk)N9n1$4$rSr8e1e^kHJ`1n`hAkc4eQuvoF4TAdFP|Q{^!^|*=g8zsmhP&1!Ga070)6;p^x=P^ z4?m7Ryc~TfOej}~KI|kihG~Jq#&Y*Ibr6nfqbsNu0{whoNmq6fnTc1IHH& zgU?P+LA;a(LNNK#^s7tP~q zwL<0CV~>&JsH2YRRHr8AMxQ4}N2~cFG%hVI5l8Lm?L*_^;}g@FQpN5j>MiX_GO?!r z`tLlt?bi?f>es*guY10J!)7c@AzfHt@xRDx-gNiFl*m5xgPR66V{E>iU!DluV_A`_w>970E{y*__z2fM&CAU6cf96w9{*~zI9txU5O`Y9~$t-3@_TpOX8BN7on%1m2^2p|e&!lbi zd^%F=k00ywdIEfiAyKL1)Z}bFz+T$um&+CAmvUkP#}ed%pGaN(Q$2)}tbyZQ#Xw7Q zM2g4%pz1P@hwH3)bYx^- zVp>{jRJ@IFLo?$rhXq}%YGTC6nbrpGon*g4uUR1@%Q+W?*sIOsUGBz3ZSBo1jTEpL z4NX46@4g17q6J8^1wnK+buL-8yqd<`^~TOdH6uxbm(2%vbrP7xytnfu_zV1#NZI`V z#Qm<9nuAYR?{!GN_^~@)hp|)77T0sd^-S>@PCH9{|7}>toWs9D%g^wUA3$$59=h4FFZqecz@=Yyz&05vE+>Zq?0ke za=r2=HRDOqdxa1yFr9dWIVNH3v(v@7GRa~lC{Mn;sCe=1$@~aiKbK~&Y5W*`Gm77{ zb>=<2YWgN7GW4eogXzgm)DymurUS|23OtJkC69aIF@iW`334R)XXc#zb5+QDJcbf% zVI9Og${az5(cx(mrH8-7J0-^S(o+*4a=a*)+`EdM3`v5LJy&^Y9{6u^%BvgMoeHEq z3yP=lkh$f~4&r7^g-koy2z$<%ep>?g7PRKdM%c;na+U~emlm?lhMjcon3LhgJEj_r z2=jq#vz=_Vy^T{?Gs4XJ7IxPQ*j-Oy%}D=oVP@SZyKC2>*2y84MsL@lmP7X;mw@}f z2ZcBk3PKF-e+B&zM?PK-{m|QWC?S5SaKSwF61nX%d(c>Ky;fNc`V9UMv~^6XWr(N!P#j*Z=$1|G$3) z|Jn%t-(UZ2^$>a$P4p6dB+Ayd1O9c5`hR2o|I?UnW(PfmUHAfaVd-Ce8g}1l*nOMX zgN2dbr@3`fw!}V&e#9GuP65oNPeN)Qr6R%NqN&a1J+L`Ta3@D^AmTihGBHu8XkNlf zM2yoD<|67G=*n2FB(Wie!q4GHOVX7!lQIg!8E==AJzap@Fo*IMAnv?XA{)G{nZ9D! zk(3}Z1=;0zgFQ`f?h(B5L&HtJcy`v*OI~jw-cG|T;hazI1h8t@2rCxYbFF2z?P(*hb~i=bKW9az`Zshm2G(Nf_n`k zmHTn8(Wv3^Lh1=p)rY0o@5GIoOUGiouxmTR-fwK(`qTd!SWnf(Wp-+nrd>64%L4TE zRH))_0me`!5t`9lCAH6qwOlM11}BiHimM;HHVZPHpB?1Fy#2zO?xVhy7kokT=lwtd z!VMY?=YhTw1ewp;oZjP9&SE`%m@B`GD?gJfe{GM0*9@KmkecV86C+uJH2{ZR+;}25 z%`S&@s%(yWR0v)GC6`Lck=-wpT+Nf?i8RqT7im1Vn;1pD0B&%6Tv?vXW~Nymb0yL` zwA%`Je6&9;R|O;ia&;|YM6*~=(p=h3!%>Hl0ui%s%BGG>IRweTXFEcYw5K_3SNDMS z@b`L8wcZ1k0eKZ32z*=6M+2Io5~NFWj+dJ`GY& zhHOwDwijC~*+1?LOa*l-LQ@b9mMC2T#sHZJ>!JP_z@z21UsBFyd$xRYX{Rgw%8&W= zM)iYaI9iazy;PH3&8xloXRf^Jir146`>VQ?l_^!2?;>QlUA;o-S2Jy}w?cbBN&+JO zOeR|ZY-MQ_djKAb2g)SY5jH8h;*!i5#U=z@cz8l#2`R~=Y%Vptl=(1t+yaSs!GaUj zqh`zN_WFqg)*l1SO@OrIk{2$Ur}hms*#RIQ31E_;g(8(}&f@5^Pda-W9dyMB2Z7mz z0jo*iiKs)RvYc2rpGJE|je-*uSZ)ifc~pMnizY%VdXKVP%Zn+rL<~G&rGE4 zjBY_FmPz{YmQpdFOU?)vt_@7BTL$lAr+6=teJzrGJ3GaDk?enGP5wKQ{az${ovL{~ zd??DONZ)fLS;&V;FMx1~xOJsi@KIeztzCW^5@yDQ_^WBInHU?To$wSvi*%WM1!!|( zaGG0MTll<1=~~EDKqfhz@@;HA7wT8va?5p_P$}<&KwORwT35LGZ}{HN?!Wtnt*5j3)XTfx zO^Q(Z8g90aH(MW7x7LH|OCfHQqTd-i$SR2~LrR}CFbe`5a*~3j%UYb&iO6Q9I&dj@ zyQff8{mLDrJno?D_MMRZkbGkpdk4&F*o-W6obBK=z-o<;jK-o=3kU+XjsKm`>7X#18AyY>t&*dTq#X{26esc`~&4AY}$xf-H z($Nw8jbyBdGFAoHZ&&h5)RC;wz`7c}vWJF-M~OpAKX0kevvCp?*dNAVGk_c#p@GiCNkI!lup1Np?_Ln7R8anr34-AAb+ONfs#yx~wVB*ff$ z9q{D=BS(%>V)?X=2?wa4bW#SKSM%I2qNf;qj9Qx*odC@xEg#dQso6l~HF7~K{z1uB|5CKm^qw9#KBr)GuV_j0zkhCK==a|2i6(=?w0 z;jN&$r&k>3*(J0 zPo|K9;C{(LK+L^`NhYLy$W@A1Z6&4UiPJzdJ%P>jF49WxvVO)QaZ+?D6t>kdAfvBQ zKB~S3`{+jXOyv&s)zp_j^KO(Bn!_J-)=VX1ft$~|RD<$QMGqhJJ9J>$kshqFF1(Z# zst5mFOlxpd9n(?h+tqYM$sCM(ZO~&8J<=0)WvA41R>?-{Bk~rs*IUqD(!+QQXL}3U z>s++gThLx_MtiLy&*)@v!XM8WNgSiJJOIKA83$EVsNkGUdm|N)mKk5JDwLy;_DhhQ zOF*122}J~soNqWhGRX{5+A8{Lm0rr`=;%W}G32aOAa|e2(0R-q3>V8jUuRcqBRhdK zoey|)Gu~X&$k8-5HMgOtT76b_irsyZu0?VGzI}Gt3*aRx+XsB+SAPEMpWJiLJwLwt z+t+UfCwiInPFfns@lG(J{ny?4{U1Moy$n#z_}&k`b^R8Bh`tL{{7Lq44vD^bxI%Ne zG6$ApIw3lV>JG7fpb+rraWIrp+#szz=|3}YA+i3a0G@oB;j16lk>C4IPy-dUfgSq8 z_OTfbxtuZvWBU)4Z(j2`=xEe)+6T$=B8Fh)|pQSEE(i;P-s2pXYXuwv$AMwb9ggw=2W;Ru!p>}e1CWC~E zazgi+@ntALMcO}!_Cx2rijR=2DnR&r%Sk>zRjd@;9UZ2#_ZyEq^2m=q ze};|pITs+%n@O3=aRHn9g=oFYt*cx&e&?t6{^;wUGtUC+`KEaf^iBM(m!^V-v9h@y3-}T3v!DI!M_F`ZEV;umeJ{odm9L9fRR2!qe1#fCS8i8+#I}DE=JF$$ z1^Rv?`IA817IR7+b9yuHm*X6gbPO$ZEsx;r-k^Lzxf^;Dy}m4axC9Y1g<5J*y!4vK zy|1$H1G!8NJeM25Hd`O84csscg+cKI z1kWoiep#Rh2|G5abhr(`1<{*H^361yjYa{hvxdpw$0M3pP4c=;L+@j;@o};xvt(H8 zr0ZCFR-v329tSNgU`R2pKv;!5^tK3x@QXoM;}?ri;zR=l!R5u~3CN~RgG#r*Aqq*g zXbgkT!+por=Bl70Yi4LP1kVSy&tQhDrz%10o z=W(bjs(GwN6}+v3DB|IMkDrMxbmi2Wv7Pjyz&E7FGAvI;OJ z0IibwqhMY%V6}kmXF3GzhXMvBCH3us+U!IdtVSCgjwE!T4LZ>VooEALB=u9v*WT%J z$%SX>H0{yQ;L$<>6*%B$xsftYbKMm7h>twCZ*VZKvs+sI>NjrQz)E?)^>+24hfV>M zB0YO9()#P*lU@V-5{)}VSq+>H5@d`*gj3YF;{06=WbNZ@9LtsbJOrc{^NJgbL$6U9 z@FQ%DA^BuT@F1!6)C@QDB{W?#2K)jCixrFfMC8Kk^;QirMZMvutJ*K!&`JnepD`n6Xj}3fzeMN=}SVr3>LE{5jEaGgzK6 z8V4m(rT8+|4}msjF^}5dziM?F@Gh;VDB#sL2mv zt-KyQrW|MEHJL;h&ZpE*j(@X0?fTr!--E*DyEkpNuH-lGnODS8?AP~&JHG$@@87ni z?pQ@|CGGW!O^;YJl4{>Fm}1!J>x&T&15cgpgZ34I^bvdxJzB8-O-cR!O2}$HfoFRc zREs}B4}Q@;9tCo6oUL6i0%o~cJyv}k!H7@U$9d`^44)Q2@)z5O7(OYCq3_R!0`F4x zauqRLvJ32EgW5>x%Lol@ZnckJDpPeGtK;+Ofe9}l%g1>CJ_K!H2ZZYr;xyh6!6+M z9-c*q1Ri15K%Izb!(TBf(wR^qB5Gf&f$fc<#V}zOx)?XqB7Pb0C$skisiJi^Hbo%S zQ^$en8uW=$0M@FDa%!yUI!||>2qrF z<(S)o$-<_h8WaJe1`}9(Mb(MM(4P=!1v`awYdyki5N+mg+m($|zV1Cs??cPF$rdlAbb;hNTPK;z2&T8Hf(77mw7fE7d zw0;~>bQHEpd?j%kcKZsguAFWkfhJS~S45;5d(~AdG7uJ%Aq9wijxzdLMK6g~{s3C} zLUhdlx<=wKA3!Th9OgoF%?Hsn>r~4vMWZ757Uk2JVqq6Sc%ra#v@r4#cw)2xa(UQX z>?lzcQ+c(fPU34M5|ga|Ip8>c3E~op%HkA(DTQPU5n;5buFlgfQ0-q=DbGxxbAzERk9G+!hV^{L=gCq@#<*=%N3jH|LHhP|U=uH%lYL>uMc;bF|?P(YK(0>p7x zlMa=dDGIHTaM|b`GkDIX|GKaJ+{ozY z)a1k*R-ZEzh7OA!pVQNc*}Mg|6)HeTo+N5V3{dot%IB=5y3QxGNn+vV<}y_Y!}x^$ zl(SdTb7fpN3?169=(Y zG0b(8zXsB&H0|Q#p^3r7^*h}zmK=GOT*fbfTWND3sH2$!ta&?0_n zB3%KaYG5f6e+@M>;b=5UxE6*3XBFfvs@QCkZIFa-vFnPpq;=xeZo|aNMTVf)7~G4s8t3yvGgIS8y04E5*|(Jd3)oj+@7X3tKH6I;sgkJv)V zF&a2-7}e~Sc=7h8$yAE_#q`zB$}Zp`poXfY;Ebh-vO&|JW(h-ZYlY-AV0*=uS9~7q z<9Iuam{bNR=n8x}%4>KV)-G0&h?*_bhZ#m7#LDL65)Cc82t5!c_0WyTPN8VwMXO0a z+mg>Gao6!y!FH!-pwHxNVi<`^V5HNjf>~covezEV+BlZAvAIW4PU}&5?&8bAm%$g= zy!o}!&;WO$Y=q_VG?fzvuZ6JF3CCbMjWE-i-j@BAdD$NwEtO`tD;`ax^A48vaH$lq ztR|W;(&OMcTehPS@f8sG`j`EKVT=1_Vz4F!OKYqUUl&^^Ai*0%U05)t(Y+imrV@8n zrisjXmA`u$TXr{7L*(G=K6QexS5WhP<)=1?iXlacD8>yEzE&~r5CWpI(lRe~5`fR5 zRGDH5YF;E|VdmK0fe7vDE{3is46e3R(T++gx^z98rA6S2q$rHUvK{c3xWI&B<*(+Y zCLC5(eqF5^Mluw7k(7i{#FRM0r9h`!mAyJ4gNSl2P{*D~*4M<<6G94gaOlhcwDnwiO0i9wU!rDG}1l!Uf? zHeVJSErAtQHGaW@EXAFfu8b{ACsp&)(^>MvsHln^2%7^Row9TPDMFgL()dKSTw|+r zg~f8!st2TQbbOo)rpR4#Sgw+saPyNnDoX&WMXau1bned?)xYxN^WSaW|nIgc!S> zGE{gD`;ZlHMu<#OR1Dkuv=f_- z5;vrk_+>ds57&yc)+NthQGAVM%}h-Tn-WU}`d}i042SOV`y~7xib~c2u|no0I2#v^*SeaC zJ9BEz)-xyRl}N+~Gy^eoxT4IHM3y4n3cjyJSkYrNHwBnq(cwcj zrapqEL`>Pe5n=JPP%}KMm2;8+he=6{*0_+vkO=Q{?^j%0M=*ClbN&B|-#ZDNdptV# z9CYplh@tR|pTv#52Az96gVF1Jc@$dwGT9WReQ`^UgD_kRO$b_lH5hJO`f1}^O7 z@MXOnFOMxd1gxyr`SOsmL!rqUUlvJr2oPCs?BxMuhr*8i>nniejx7FfRVBQY&pxLI z1Q|Bx0PEnbtb;eR4#Xcjhjq|k*E$(hDUr$^0+~rL#$^?-Eee)1a)<@e-X;}Bc6eGq zO+2fItnL5Xh{&-ku!OQz(3^GaW?sM!5Ox)@4Qk~++t%x&X3Iux0@`ft>>$@95d=Bk z{OJxn*R*TCboF|oIUjKdqVwk%0t2i0 zSOV5FtWxR_OVWkQgS}(b`VX--eYQU#J2-%iLoHOB4P1*$Km*wJ%fUZMW)UOp+${0Zn2H9)yzFefzSZo}Q4a7Fu<}39CXiTwi)a=-QXQdEI(a zx>UtZkL2k;!e5BUZX&@nNAi?3JV&P2LDiFRyHfJchp4?q5{!t;p$1!9J*vDa>jjGw zF8Z27uv0aq*DL!KT3VvgCeu<%LOiy2B3aVSgRYEO6{o#|xg$@R2Rq6uo$uD5hmJ-M zZDcH0qXkx@1=et{YeWlpJC*~&ZeK=!s`h{iPGq)1j3=7bFckBt@qN!d`sky(XP|e1 zm$qec@`V>(*t-w7T*YYZ>FMe2YBw7y-QD3psG)7~s#l(H!U?OpT7pi?4BUPH4{o{T zmT&%05-+!2zk$MX4!!UCZ`y&=-bg-AKzB+ATSrDluxVhHJck19r-7T*m);!g|5Nrc ziyiJ%J#t{q%qhF-v8re|1lSSUJt+H{n`vZEsKRW>jqhXO%#M;fnxN7abIt;53$12D zYjaaH8i5A1)fW!B^?p|_Js}Qzt(5hXb8vvsU|ghWVg|H19_8vBg#xv%qNJ#**u1)%kD1gye7Xr;p;E~&2EMb7!7 zH{i?XsN$Wke}kQ8Ma>azLgb7pfmY}88P71xZg zu0tWlXiTNZX*ZMNC+a88iqs11f6i}+DU`)KA~ahi(yP|J3u__zx{)=tk@-5C`4S%R zQ<$%VYh0s7QxA?zWA}2HX~M2Dyb*J8FH_b{)PNsIad+)sUpt?$7g#D^mPyPYwizYR zWp+1DOzhshd(SYdW45Zd2(-HW;Au6x53IYRj<*+HqgOe2dQD)(_05t!bN-){{{N>xe`=cM-b@MgrL8yGv(f1XZaapFHvw#=N>XgMfE^8BhSbORrMSL8D=pYMI zjs%NyV*Z#WX$f zE-qfK+x{;5P)tqF5=cyvfGWU4&=hot*z`6ytoqV!vX?gabVR7VT#$a}wi62NK8R#6 zLHi|&UaY-c`xm+`QJ|u|j)SGk?0#WvhR?Q<7=$s#S>j_u2Me0neLw*u<~2a6bg;mg z-7_}s80K+YI;SIt;HD-I7Cg?eUop@-LNr6Ohd9jv0jx)o4ns$rhRm%)=3bAEI3Afh zo;~aI9=sa%ELpw>9zNN-7QTG4hb??9@zB$>K--(PwzkMWdJb=vH`=;n$&%Knx9{&h z?GO0veNXm2`Et&bN1kL4Je580Rlo(0u-YxBU-rUtBC5 ziei!ja4^Szs<(6eQ2i-KrZ+ObW!5)N`+F2FbCD=+dH?s`u)URXA~w|e_egzPJ@~u) zrLC?G`p$MMEov6N%hnsIZ@mY9r{F>~u)UC8{C{&1g8Y;~zh3fxvtCLJ)5wP{`^Eng zJGHeYLM>|D{!T+#*w0D_fA7_OD9U*KyCKi(`^8#hVI8t?6tb`mSvZnCUG|G3*)P_z zU)b#z7M6~o8G-1cMa}L491=5RG4}LoT@fShazwin>)GE-H;oPNwOb7Ibhauf_V<($ zVd{fsQ_FNggo+LulublB4};Wyd1q9$8$rlL^DCx6({H@>X6*0Ft;_VAzIknY16Z~= zI>Z+^>)-p>+FiX;NSZ3+S z1G7}uaP}T_>Pbn0+hPUy4-Q~&9~lqZTw{GQZ0VdH}Qpf z{fV^TD^<0zyE}v)u~nx0A)DgsnWrLiBo0w(VU93oGh2*uZaSwl<08w1<>$ zqWg3ps#sYuGDXBrEh`rD#(hOWf?`mL2JZC>9HeE@vSrH_LFc>GEfHr$)bYBpv5}z{ zq>1$Ik#q&xx}}R3b#>>9x(oFT+5(2i_KuE@F%73>i_l3u~Mvtu(926sXw>w=7xH)m5v7I`L3WSk)P-nTr?IrA&n3z=IVm58HjAwD6kK zNM^pBwge~J$LqmWeZp&g?SVf${^ZloJiYh9&7dD74=Tqo03$^j4^TPE zBHQR#<$}NnFpV4kM{#-ZUy3=~AvyyQDyU(gPya?9=o|JChb6RCRg^!vFQN6oDH}F! zc+1<#jy#3D9=E^s!0i-eoe3duR1Qv2H!C@rzzgU`6SNPvZCJzrT}B%lNm5!uoiZh4 z%!!$+sr0~p6kgD{+v&4AboiY8J>6si>w|Id49k*{dFABY`GP15sxAzhTytuQ{FE~U zW2f7iHH<#O|8bFM(b1=~jA*G{Jorc#6pHsJxu5fQNEu8?4Rxz|L5*TEIed!&dK z+Gmj9Pz0w_xRZuQ6Rtd{8^v&LblP!s=-vrBo9mu=e)r%Q9NV;J!z!T|2IAVUQ4cV@gJhaHt4}4<#_QsyHL(5|;mJg@s8E2x?-Pk!+zWP7hD@xZ zco6V=Ejzwv=gytBPrKD6#bN5okAr3+h;r>>Q`iste^HHMBLGt~jP)@8Ugf^}Yd`q??;rT?7yD(M zN(II%q3S-0uNBtooCAM&;f256euKP|GO^px>)m#RE*6XciQy&!y^lFuED)Y?s}1cQ zJQxXR4MAo4MdLyU9Y1;&nW^ncipAEaehdEJAJB0A$LigD-2%Y7N10XbcU<(E*SzM< zSG*DE_#dgGl%vG!5V_k&hUb&YKKqzdX6uVGm(R?A%Mj==A*#mCR9 z!ckDPLBJo?d{u^l;xYbGn_{s^-GZRMt&(|EOC!)Vw*1ivT4Rju#TbL_Z{OGq5e;a3 zK|q%DaEsSFF_9|6Qs>vpsfh{mC`x~L;5;d*oU5r(_-%th%?LIGAd-{^ct^K8A|OZZ zh&TpVYOZjGMlGon%_@b9aZEmNWZw(JQU^J{ci#xrlw{|2@0KM*HybK@cxEbsW{{r1 ztv&P+V0Ct2*+~3MdIC4~7<|zaSe7Qj>kM{x`?G^-XNMo&GhYXYX_CGOxl zU8shgjydqB)j2tvZB{!89K>{`RWCrMx4+QEzZ*Nci~muA~0FXX5;Jt zb?cykJMHF9ySdZDxYNblX(M;Km^-4;Ro`|ThU3~h@A=M`uU#)q5?oE89PlTgChqzr^5>_qq{O|> zatlt?C$_PBRxv2aXO($9T&FzHcb@#jY`&`csFn8&dW4p@sW3SRK$)aA>28!OsnxhH z^%h=_tyt=RW>YNZb5sR-!a4?AD(4Qz1~85sf(EDd9jvEh`Fo}6~6X3$agO3t^Bi`8rDqf4bopO`gqs1fM(Caym6Z2v>r@ZJ7^D6HXVRi+^lo>3RkQECy5 z8#1gkAibq6afjegX0mb0Oc9-0)pXMN7^+!6ZYSmNfk>>8#=>?1eV?;dK;;l#4g-H_ zpr}_Kl&kqeQErOspbP{*$TpGaGv|%NBO-jEAX55NhnM%1QL~w9oX#(Os)zgLcG=i% z7HjDm$4m9qd_1dJ;&$&~oZp4qzKYfSqPU$PqgZ*iHBtXxi^LKMVtK(JjRsjFsZ=Zx z@%a>gE|>@Eqh$RVuaXUPPfj#7?;D&;jq)`z+}t`fv1my&*u5+gUbLh&F%~nst2=kr zYP)ttB0F|q@u)VKD70gTwEMK=MYP^!)`fw)?)}q4nbZS6nV$ObcfN4TnHc2KUw5Mw zwHhgSINRy~Y4|2g^EzH13xSfSm=9Z|14=2s#na@zqq4 z+^Dv8uBR{557ZT_&Qb1Ben-^(L26+xQ9lEEa2MSf--E^dNA|rBg;Omg8U(s2BNSJ?nK4v zLeUa;OQoFh<*q^wolXXWxfEX+=wkYNRoKjdVxX-ZG$BMZ<9={?u1bXh?=m%&W_K{0 zPP+Z!AYFfgPET^iA4<>7q_T6;CU-6n$(Y@GB%*0yqQB87gebJ_#1?hH{DCfI$BstL z?zt&%D3^v`A{7pn^Pz@p(&3p&DX!T$OK->|e4Il_2aqea-2>;qZU9`!IsCwEWoJAT zE?3XBct?mzOam*^4hFNp6zlBnz}Yt?J@PIM^}V+9;(jFOZ~z%#gKa9s$AoXvBJ8y zH8;kY=|Fn?A5T6X>acA)>+9cUFhkYovE2U?Ge ztU^X4R<)2NjMvF!vH1@x7~Kz z7dD^`-(VeMFNmvkVt_`UPCM(gxdC4!rre^Pd+xcLC<_m(@1|+-t4K=Rrd@W~Wv2kO z9H%z?6ZW!+8?y-|Z{mCjuWbM3zwX?BDp|)LB&Sverk+t&&mgMJ zEgPv3=B|HrHlKK5=Z+nNbF_>r%?<6~F`CIkpYI}_*4)~&qN_y^xDhSq@tCV4kyvZH zPnji?h#48SveaZzf3a)Vu9P=TF%X4;n!oMvlTJPL)QH(c`)oq12(Cm@Bv_9lgVR93 z*zwaJjo$sE|JY!CimmEH?txoxx#N+0Z@rzRdM+pLzEgXIyaA`4F552v%NuPVJ!X{Hq+B)Mf}Fo+9)AG?vJtfMb7W|NN9qN0U)s zslJ!Whp*W3vc)A(SL{P$s}3@`c&*`(Qj-4o%BszfRAvkl0+dF0@{G4alS8MomPuzp zkpipKd5tkHm*O4@m{ddv-)oC_;wDx(6b6j6fLJ*pV&QbQAJ)eA#hr2%?08N%C|$T< zJCam$a&~0upc6`Caz0phZPD??U468hpa!zf5Z5Zzzq+xZu?^xNSOsFSn!8!b*4mpI zRJAoGVK_BES0YIWwM?{`g}QonHZeAmg{QK#XAyUv@>??1OhAh_4c!nwK1nW18m zJ`qMJ>Mj*hRQk=kP3IYH-&s78X>aI}gqMm>l;(b+8;Zq|-5(<#-b!`zHS1%ppH(RQE}h-owDMPf_2+aX!bT z^v$|oeVpUJ=#+Jt{rY><_p7JON6zBdr;&i;TAeV(>{gecfDr!5HN7htObP4JIT9Q; zh5n_ITNDa&EDupJl-kKtgTZEsX+lqeiOtRwQQt!MG+spf%VJTN`|T4pwnqtzNe2Ys zH;DGwCp6^T!+ZHQx_zHKuW>>V=R%)B08fBiF6N9gO1UZ@SgW6L^^|21l#n`>nmg=~ zFmGjM%(AAZMo6d)O%}97LMu!R4GoVbrbQqTowuWMRM5aJgBFv+>jE6qrIbC0To(C8sFdbr2ni~TVgjT22C=+l=h*}c0T zxEQCDFV%NLM@=X5~I<^7dW6oW0P>0J*kb-l%+8{ibq{dIza(H5HWF0tDX4 z$;4CZS7&D8>b7nC=mXQXaBj!cP(oMzu*cLtt9dHO&xCYruS4)`a=l^fgD|Tz+~ZPW zJ^QbB{)6j(+KVSK)2r=XA}BYx?8K8(&AN5WW0vymQ|JZxZL3?Zw_?PVMW>K)SxkdB zE;PvQTz`LeYxj~~e9QU2S3bUho_`-#w)AQR-@-X#U5#a3&G;8CH92Q2Tnl zT(4KIm+=iVzFw}^%k_F0Ul%*3tVuPLR1-1#SSbk3{_#RQX&- zdExpq0j^)!y`H?8ed)^P^+eVWTy>Sdsb4jJGk?|2B3EsnCmU3Y`TDrIkV_Yq#=^Ks zDU=JjbYW@8xNX%5R>aJ$`dmlH96q3oP#XR}g9?cAfT992g+PFW%;_XOcXd@) z*F3+!wX35j?e}@EbN>0R>#LngRqd`}?`J>Hde*bnz3$un$`*5NR441dy%mx`t;)Nh zJhJcr>4&_+@yh*=tkM2^?fl>S^ogtVk1ZuVjH^`dEv~E@51MUU1b}&{-d^#IwPq}Q z1;DA^J^*7-ZkRap@s%$%?R{5&=qe>b9o{X%24fPD-3p(^ci=iQh-R)*&xO(G z9c*uBdn?=3RNmeM3E7*~^nw}HhEGM=tD9L#&Hlqnr7v;x*a)uum?$+Jp9U3#2Rh$A zqYw4y5`1qEH+Y2T5{`%ILTkv+ za;&+vy{l)weMTQYDHhPgkQOUc=rRk>x6kP7Y-#Q2?(XPX)GcBuZLM7$?Va=Ot6S|8 zDg}-#(*{3qmD7WFwwH$EsDUn#hsp^X)VG za_#Lx_1V#aJL@Q?$oP)W%(u_z3ji=hS!gB^8##;~{eh$71=oE0jL^OJeEq(k{`99m zzLzM+4}bZmmtK15iHF`qk?mSUx6YpvqQ8jObqTHu7zbBVXfE5kYp*lMK|fqsULTBZ z1k4*5n+*&B;m`WVeFK}I%li?%daL%i&wXw)GQ9n!7aFzwuTOmF`s=U%&=tx6z4~Sd zC9Z&fMz+_nU1n~VsOK>bPBFJL)yw-$c7P>`$%&~cvj0;frb9k4ks$s1>V0$rX=CEN z4!V|JjMH5wQuFOI`k>mJK78c(v7ti;_wCxXYZ#!HZN7a*-{jGxWD~&n12?fq4XUU5 z>V57i-x+M_S=`-=5?*TH0$7LTD_-o!Gl3^_k^X)VGcBRu(V{uc$4&Dp6VhR&`=i6tz2}Xq*h%3Df z)^*w5!t3{%+gnk@+&Cwzc~q2Qi?Sq)4ML?UPvdy9#`d#x6+!w%@HN$V>@f?l=- zS>!f**(a&~zuRZ@?R;_1c#fc#(6bUlnHe75|J;20jJ~EWpUi3keghRJkj1q2>U}AV zU$10ilZn_&wwQ$rPRl@4r_Z;Kcamvi>JEokHU{Ca<7UMr6|NS-29W!hqaj~3`Z_$G z=FTSG7ck}1{DDw&`+WNt0(RJLnO9_H(X2iW*@BDu>U~Dvg4Wi~g$p~odlt50%G%qy zJJDO5XJ365I8n`VHnV_Kv#i7$66EWNICW?qi|;T(^IG@NJeE9Zgyyx{n=rrEfqDCs z*541c`Db|f0du?AK5!?l%$NJkUOAs-9(WV!Xg|@#DX=!O2|Q-h+>Qdb*`+*VZo8C; zx{VFaXHP@(*v?sr8Nb?g8lhL)L=ROd^V+|VfU^i_39yTrYl&MI3PxytyB2)44J+~9 zd2HC6y*hEVPUC8AnD1)M>!*fREn2i>#mZGD^>s9R@E<~euofGkdHv7O3z~oyYw$LPYYCnM@LjoN3+)3vF^{cJ3N=v%ctNeIX3b!p#_9M0GQwPADXJ zNtaee_KZ@SVUHh88dRYd(!N?!9=tVPBL-`4?P#Cezd!EK9fo(JA#BP-{7ZtpN}mf+R4$=mij_6^BYB=RJDNX25)aPp*_I5iA%C9A2s|5UQPMTCgAt zpCKRkhhzPs8I3s60^M^hAkiMt6#r`rsBO#J0gLDSUBN<9S|C~Tr3)4wygV4*>d5d| z!Ru|xSMtFnOK5?L=2+k=+I34~P22`ieD39|+P1+Gv3``oM{Jm!CDJ zpEV|G!+!hB*sNjmAdToz(}rz=%iX@D*{~f;+uP%n0NLXAtH1vBCg$~jt9_&PF7wog zHY!!)0(BL9uwMjLcs5c0O>>u3QrtF(Da$%#aE3^o80OE+zBtQEIbZOvA!jO)<|X=u zhOM%HzqV@03SP=*|Nh8Z|Jjk3f}LTQ^*Hjh>d92BhRu8A`2!|F@3BB7-fmAD>h~Vl zQ2VI*!|!Z_@$Jp#flD%5+j0RUi~peplpiaPs8_12K}~`za#34|0H>itw36xO47b=y zfi>o@^hh00tFLSl-8m(M)^L>fxqeoa1W_Bn}eQ0%EVIc_H{E-@Swu-PP1 zpecicDlvvZt?Q^FQG!A3sr8Ed{Gh6rj6t<^jQ`{fYSM2En&0T?wsEa=#oMib!K=rm zBxZTy8j{-7_*}#djs>-Ae-bk4m~kiZR?V&4wGo_^EeaA&tc?A9Wm>hXpom#Br=_HU zNJ!f2iMq)sNS!TYZw{w%r<*_s0bVF*Nn7ypU~liD&Zb~|;`rf%#+~z?r%-NyntSDW z+QOXYs>5^V`IXn#&vTHQn#gj7#n{{#_R?m(oS|Ux*3)LaeAfw-R1e?k&vo=JUA%DN z!jQd;H$R&*{Cd&T)!Px2Xt%q)-KQD;rb7q!@7ud~Z!E8qFKr4N{^=Y*@sR{XJ8nG% z`y(~A5RzYPEu^M%99^!QDLHV%(3Zfn zn@tA{PkHjeZUJkKgJK>JO@`S8vpA)NbtGppQpft&3_; zJzKU+mdlfdi-<=&?uX}`RHkceSUqK(hNg+tZrIZ4!0aqsz-nI~e>te0wob2r$oCqs z?6z2~>ra!%Crg_WxZk83W-W*n=`T1?5!=dM+@z- zoc37F7;2$CT4;}E+M|W`&~werxjX&p%#0eCo({Oj#@v>nA&a4hW@bWn`W;+@>Bh!s zxiiXQ2BPlL7WLaD)dSUi)w8N+*Cs2=YG1B=P97^ko%Ei4%iq6KyG(ndeusXWa+38y zd9)sM3Ck7mSK0IonC?6MtS_ph@Fb0A-VL3yE>DIpP?ml?u zESAEZzRbu-MuRAbtcR>f2lPVH>b=uHGf}blrTC|zK}o7A{Cm_B(iRGLLiS>T46|2b zb^pKl7r|zULYd_0Kg)T2p7T0~tNS{x?$un~cgfXtpo7)&rhBw4o9*oCk{Wl9 zqUK|SFpf=R6jvbBn9UE~)f5Z`q4x*M(Y1JSlQKhzz`=t@h9|e4RI4ps3?7#AMX{Viba^Ery$SVY-|IQ}h&5t>6`}KaT+~Ftg+jc25gm13()|zmxPUXIe z*wLAg5WcT=v*XU6@1(lnnTKw>h&O!@M9q4$G;od zso6=fhJ}G#$P{>1oK1|(3|?URzddS?+JQ%WyY=$5Yu8@-nKvNEkb{fxD)nLYB{P-z zthz&eLjAM)eK3SgAU__m+;r)sm)>v*q*w{6eb$oo{Gs}5(7E@M{_ik*)u7$VA`{l4 z>Od2PGKttYMEmiA#jX^~R-8Mp$Fr4XzSVjgYs&O2am`dRAMpuqlgA6Nl>+60%fTfP zCQnwZLO%$10_KpTuaq)d2@b?!Tl8WonT@njf`FtzFx24G6Gx9rn06#>4L5~5_=-Wh z)Mga`_75}xua=Cf*XOUqrjpH#=9WO%bunt^UDS5DR@;ix(LDo3AWa9ND1 zOTi3ylGZ>qH#tOiIy?%`ywx9IX3HA4SWKabNE4CLcIH&#rL6%t3Xkst{J!tVv8^jl zjm1vQq^3n9X^g0}wIPs7&m_|D(AvDMi*jto!didt|HL>0@Hoh(HlhWf>?oX0Z!-L;Q81|Qf-%fIyN+b(5o{#It5 zHahw`qN=MH6B1dyuJ$SWEkAhp$tR!u&9^URA@X59_Ilj%Qz(gD%_lxu9}YHA2oE-U zqivp0NJc}85ElDG?oxbgnn+G^IyRmfynsT49r)`Pfn6C6Cu?9 z#EoZWGq<0s53!;-UG-6u{A0_9FSy`>4_`uPDUZ*;mgwtu)t@tVz6MaBxjq=+Niq}!T@b-YHM!{ zqgCcog;$|!3$=B&2fc}WQwtYd*vV?b5KgDH`GNtT7rI}b3w@y*^6Ol#n3+s6ml`XZ zNj@}SdIh2o3i^}`;$l?$z!g_RGmtR7v!eK**j18QUxQ&dOx+7RMAgv_yPJ|cC{SUN zzXBK|O>*CAwov}S3S17mr&(9s(|Ao23p2l;7D_Z-1op z>qs$+89}NCHP?!QhvQ^6!vHWx0YcIgxHS}qpv3}s8wr6!7L0X7wn~(UC zj|m-G1|~QlPQ<6}F2G!6m5^pjOAt=__=vHtX=HeknRM*fNK?>HZ9pnM+1`O89B658 zNBRn-HTI8e+nW=~R7tZ_7&tkWzyc@6 zy1N@~O3vh5$|;VfZo{|Z<=_38jKrhAdwIu?zk4U@J)a_*@phc9RTO4!HfcxCV#>Lc ziRDc8Gxb=EJHLBBp`e=>i?21ex7BWP7DTB8t6>f0380JzlB zRtfq=VQOU5M_EW1u1hw5{6p7VbIpx6Z@%PxB=>#+LwpGdpL0pz{g67%0reL39&_7H zuHz@{d$YPx4H2|Fj-&Q1f|zaOLT)m*%hfgt%=S~sDaf=akNeFDs9$ZfgKj8|Zqr6H zrKqMm+qU{m_^OHDnJq z=Cuc+0-KD=MJiR2QK8$4@Lw%z0=z&s1D;-|jl`ztj(C?EvO`L7HePM#2oN=BZm&vr z)crxFVj~nFT)24_3wiuAt*G0h$TZouNi_|>qZYUN$xIUQlJrc701{D~`sllr(RUA{ z@8^uZ9gM!)7=7CreP3eq-6M0A%#q=+u18)|bMU+gAbwaTPKo!|Z}AxECUiJzVHxM+cyQ1wD&6*5utX&_ONnArz=emJ1MxhWhOSJMzx==sJq?Xb zwhG$ANCDuzG?I^M0EXD8PvbOB)*2mvK^e#GjW(l6kcNeP&%*Xb;yryMzzl0r-WjzK z7Ei=9HL|!iInE+tX#bv<8a#FjHFLR~zNEL+spR9ML+vl^KAtMzK^x76@#UZH*#F$K z&pz{q@84k<-}u9G&pr3>R}JHOY7d0>UA9jNI}%H&OUO$0uzEU!?QFzr-vA2!Bj(?( zqHv+t+)ihoy!&Qc%S||+vYku%{${6PeD&v#Jo3mx_n0Fc8jQU7&J|$!2qqL2pXb(~ z-6z;fubTq~5{Sc>&Pvy+McGltfpgfS6ku**NtFXAeW&e>XJ7LA*Ij!!1 zeCo2xF8jcTm7mE=;MJXTTN~@HRy@#8P}BWeDkJZwvik?>x5$HDPRvUF_D0;$9p?4} zpf0kf6}zzo&s8=9-Njb(C!SSq*WUmB_irRs`dr;x6@@!Smfw#d5Vuv^)@EXpsVf$c z^lU<^_Y@KsRmJPF7*t7v+s?y?q3Xsm@R=0T!{#(2k+pJOlHekMFc&eQ-YV3{^nplS zq(jp@0e(uf_B6i_qIj0vG{qFSik;gch5IRm*3xRcNh*^!|3iMYCdbINgM9GB6^jkwBKL zQa+VcYDKr-i*QaQV^H0eQ++-)m!3|hiqTcF3e%Vcb0$I{nIu9vm8HWYoseX3;+s#+ zAdV9&xf$6$B^I&Gzz8xvl`)zsz;RPEhmMR*Pff+9L&!QK2wbg3Plkjc5Qb#JMJSO< z%vS6iL=i}0;Lca#XbdV2k3XDj3^_EIyVJ(GqoYG{$RbL)nat#IezD9!A&@&7=ED2w z0$>f&undJLa~J%9jBpE%zq8%RIQMip9n}9j!8h4w&7(qNl(2~3O)WY+{nHbp$-f;H zJzgD~I5q+u0E=0hkQm5Op`GP+=*2t{&T1x+oKB?Xtl+1xf}h0-eg!LdA6D>VSi#R= z1#iX*eikcuR%K?U0-3W^z|;0_UlWT|MxQLM@_Ej@qhsNsCNfvB-*i!=>nKZ|M2Z>+ zA`XTgk9&xFWA4que`+lb7)%fb#@W--+Tw{c4h@Z@l*Ue6n5LjLKX#bPr?}T^oK;x3 zu;4;nk!c30I$KGcnaYtTrHCiNhmvgo*S|!|xr!Q-4>*;ATs1A(; zr03%!nGmgT?{eIp=%P}Uw7jhtk0r?^E??dZ;Yyan9gk;=whmsS{b(qUZDiTI*7MjM zU;EW_l&|dk#nmyl zYQEwKVt!EcJqrw=6H7o{s#PszGFG(nbfA9qX&U_^L`tg2NR7uavlg_BQFc$Kr>D~O zURq{AMm%sCVgp)(8&1s*qF@mtu?0A$pXi%t2r3(4io5ndA|gLh72Phc1~mp<0$G0Gc&qQZtSd)0|!P%54?Qv`0?YzW60u4 zu#PDsY04nyOUOeNrjLyth)G>fX6)si&%OklX~z=>cE0@bP6Krj7(+|cIa4pi%R%R@p0_NJ=l$JVmEHWZrp_3_&9ds zChW#(3@sq$O) zg29=Yyal&;b~cR}W7e~;UA1iSBI@nhe53}$VSfb^5DZqwk8nG90IddNeR?LHO-y7- z5>1Rx=3(DR!g>{GbwKcyr^hreJ>`tWq19{FtX{o1wEomZ)^rNstrWbiFBDQEd-qb2 zm_2^z5YD=TnNTmpAA9WaCwJ`JxpVgjwK#_k9iOgwy?gi0wt{+GbNab{wLl5xyK4XL z|It&=%uGDXZO6>SlaJp2g-eN!y@Tl3+FBRn3#)lN72N&naBn`O-}?RE{9z-nH(37s z=RZIC=tg47S80Fz-8U~{Z6lg{=Tbz_OH6G=?UH_3)-V<3!I_)nK0N3)J!*4>J6rsA zRQ=cu2wh|7$pdR0IyqGj=V1(-$^)L8KCtk-_q^vl@4bHEc^~~>*JIz0kt@HL<;ySC z=ZTvAjzIE*)P}vEg3o1;V*CML=nK5Qnb^%1IIr2TDHmd4?jl;(PK@m;oSFyJKVXJ_ zs(#Qk4iW!cMH!OI6qdQ&)`3okwPH@U#6ZoG8^Z8Xd86Zh5LVlAT1iAQ3uT&^j>m8$ zdSzAZE)?)k9e`j4f|r1Z)4C{qae1Xq9cD1QKCqrhtmq2Xa=63X5Q$`fA+reJLyG6D zR&zI+3Cx1B5R#Eqv*{IspR@$R?s7AggiY=W^uw5;qRkm>@Hr`pgs68mZEc3r1Za9o zG0*y<2BS&P#9giQG7#mJF+uB>g!I^?)AdGACgu`g} zxP44yLBs|*Sv%N>Ma29VPku)vM4?xp!EO(S8&HDCCP(%iB|XP)`t6khRra(--chRB z#qbX*%j0)lzOq~ zwt?zgyfhqUt+k+u?{_z!yWyPEPd)Y2)t#>4;dUB8rl(qZ=gyb*?RgO-?XH(5^!E1Q zVTaE-5ZZZQDtGwMfxXZD<@Z1O);I3jyY~yXZGSU3ws+PpVo}ptYha`=6nF|_cLjUS zCq{84)FA&3b}Ax^9uoX_-}^gr-j7&|(5aTHgY=}RkZyP#Uc5e2q!!hY9P40Jctc#* zUAidPy=2+i*MI2qo5=q>2Egi&`i3`LtsG^Bj;kJZv-(k#-Jem1NF6<`K1t%}aVS6k z4=a-!S%<7t-Da-+4p3V{RCR#T(e37-H6Xh%6+SExWFwuvQe|R(|7_ago{fWTn@LtA&Q2uFMm0$_7emYv zDm4fw)6?Ul$45uUN5^N<9Dnlo(2)`0ZRs%Vl)9 z`F_=dZ-d^N*s@$2i9D+{KMP}~)>~uKlJF`7Za;^Kd$`Q#2l_>FrUi*HZ)z*3T%cNVLXTT z7R5SnK6FwU61FWQu69_jU9x2H+3&j?-nVhUW)dOzw)zNhfZsE<{*33JMQ;+?1m)LC z#PSx1!f(1%*{wD+FyF6!$7JVyJz3tJZ1<`cngjE0*GzmmA^hkR;747UZ;#tczSL^n zax(OZnG(5M!D?#gzcNoc$;jmL2->qg#}lXs=eoe9K|1JMCK-sx>=L1Z!K;!vv=&MrD|0!pGW;_G6)lz{&72PX>0`mRd?rvfbEgh&^g&W+KNLOKYCfz}06myD_ zjSbUj#>$@No>ixv*4{3MKsWGuEqNe1Sy8Vo`4+F)wE6VK0j4&0sL`uTkBfJo%6Ywn zd_7)cm8Nxfds^CvexdzUE1}j!npmN(+fqp(LC$IrKfl?F(8J=ztRw#M2QrA`qN8hr zH4R=jl$Zs`Kq{G}9j1&QFh}HBjHa^~O=mNjF6vg{m$!qavZ9P*VZWwyPEMv$ENyDX zhKIx9VWy$u%r4Ch#13MAiuD;zXDnvk8@mVXcr1|F%c6=HrU8z7;X-g_39#a(=QLlrkc+6?=+lQmn$pxojV7x9_8|>LID(|=M4lJ1SUgIw=)RLJfNmfY29rr zJomse$DeuTLL%!cYm4DP`g+aFbDN^pJOHY!8vpH$ylHYk75 z-geR3EUa zuQsYtQ*Cq}IWjR}1?v3BG*KO*RTQsGKmFv3quJb!pS?&MldXktdFp7@SO4q*^Rw#9 zwSl@dJz*3x^-M1pOCih-+y>GP8=?mEe>uCRjV5}o`1pS z&FeZPXTACSE3SFlW}_b$TCpQK6ZH}6s;*zZW^oIt>*jXfAS!zs0k>Ry;?eQcQx87) zQ#fS4a}nOkJ4nBr$_DZbSKeI+RACS%@VVZ?SfcCE?`-O_ zWgE>Rk!8zXE008yzCcp{XBeJKh(r94z5~^z$wH+RqdLiHJ-QXz<)DPssmAg$*6j9% zwyt)=4(LR81>sNkNp>5tEvy-R$7SQ38yoRSxGlZ-b(_|&Z*O1J*UKkRj)$-@5atuL z3b6$wV(F9hPvrB@J@?GhJ9g|FmrMTGqYvHtgNF^J6tR@R-zWG493Eq1)7c89YGPz| zkn(gXQICY;@#Dvj5S2>irw{Dh`TTQFKl*z<7>ih9yGBNK)jycam90)osWh~2YHrTm zz;)ihb>4`Tc(bDN+|J#~-NGF!0lY1y)-rTH0$&X_Xg3K)%8i#^(Io+GH#|HMPeeQk z;*i`b2_k!1L{G%WhmB6t@zuoTVX>QF3h>A}gBxZ#kvAOp#fkOn*PXOH;#uDAU~X`A zE(efmkLb%+u3ayLW9B6z{J|qc7F<2$jthyr8_bMn;KrUoA9=t1YY#v3B)zTS&1Op6Dfar+P>2_x_xKVhkk^phQoGkHOZsz1z|2&W+^YZ~#ARtu!`gxzD z{(00Q`866=NN+_mMtn4hzBmjA%aFiRPd)qMo``49q@-4+#`l!a0gUK-UfTJT(V0pi zZz%+6!o5%-g6<$_D>n?ReeUxwzW9Rq`4@Ln$otZ+zkdG37w6VeJ@g&H>7GR2>BB~} z(|3C4JH6d?HbcV~oD2rVaIth`&JKc3EHpJmly4bgZ6nGhgNASFw8^7QkRQXX{Y-cF zl%cb#3rCcj1idrW7uyd1_D1)sCH1J5zn~AlDp7pR&MEjzxr& z6ZIZ}rEWlD(f79V2opSj+w#_pSUH7nJ`hoQBal8aSHg$vXW@!VhJ>;pD{oaAIi%cp zM+a+qVNvxVfnw-#St5$pAYWICD6<@q{0N65hZJ!f)ID&;EX(@!V~;5A(#ay5Nc2*D<% zWfWwytoK)!iiZy#I6OQ`V0vUSTd{h4{-7Iy6}ok`9BOO|`F#>D_u53xlI27x0?1e7 zz*=y@XAH*?#kX+z^5t!|=($H&?%*)8wsO~xm8c3HoHfbf!J;LS8cRE<`T;(rpsCL% z=~407tStSBk7QEWMu8iY@K8W6Ih~xAP)=H{cR=$aZLy`9*yh-54GJU#xRWEJB~hyn zyKQj!>N*L_2<9bnx+*1d1T0Xl0*xI@vVNn1-0(~~9sCOZR*vN-Qyc6(nu>;_QsR3V zYsR^+kx53Jd(%acCoC=H!79B(ZuCiZNF8JS6@RC~UCf$%NZ<48Zw=u5>jU5X#p4@l zpLPE4Z+_$Jx7>2;Ew^sFrgkG3=C>1ayRP;d!uwYfzJELSHxj5ji&a1eLf#K~XUq7OJ1OUBDc^?dvmyvk1usCTq6|TELo35b_|D$R@ zi;-WL+kNVGvH_b|g~)a?0EdqNulo}U*^j*g)ZJsac#p7pxsUY0jZ}WFH@AP|LVlkJ z!)|l?hWZiZdP0Ohn^R%^8N?ay+q~Iadv1|Og~VtY?$5QpgwKnb)<5QjTcgP@|R{{n2ThTTxJtCyz1D zPFy>i9jGS)=r-`Tw6n0)brPH` zvHmI|p^Dsx>ck8Z`t~!K*@zy1+F@2`TB`vb5oO3aVbE{Yr@IYveF0P9>XUuCq`yVrxeDnPU1fv%*vPo z@`D%>(g{nN`2}N(F3HprCxwy502y4LQr(JB(CC>QeuW@N^f z3`FJ^LL1Jypqg$i=V9KiQBOw0l_M6>0Vx?Rt(ua-ktx|BlWwhA+$1>R8f8+BoJs1B zBUGze01FI=F+j@4<^m>*%&P+uaN!mAyX;sPD<5lWK`MX}L+H2A!U%b|*!%)P7>t+1 zuLu?!kc9DOZA-K22(;q&$wkxs?QJfxPqUIFKB+>G1|{MYd2KeifDAzeZr%Vv7+E%h zSfwaO1GCQb6AgkIE|+puqE{mJDp4QZIM=Jpir&CXkwk#Etiiw!E19U=k`x7u)qC+bZlYx_W~LN|luMZ@FXB2G`00o%bU56_ z3dC3dXkxMHWL{y`QoO>Nh-}uF;qP!z6~+yBUL32C5titM4+IT_NeExsea*ehO5pX6 zjI=fx9kG$7cF@|Ik2YvuUB|k^2!MJ6Di|={C0|*zPN9A5))x`XaKl zS7X~{+sLy|)NEUVE;!){#XNIoX3Y~z2 zsbo`(b`OQSU~=KbEfi`Ms6l6ns;NN%=X2CiOwQ6Pi}~V!&~6TL-xfwUni5ueu!334 zCktf>H5yA1XiqxYhNt9FZy3kQDlXPZj5&ugIy$P@M8~SFK^Zkn1PWivuhwrySXz=P zlvqE#3nu`T6!PPS-r!ATj1HGKlcHi+r8GW~D(DWnh1Dl0tFYG^gXYua8c?#ZY0AwA z>lSi8Vrp@Qct5ELdNSWNk??zrj-5}4tE5%IHD-oi9^o`JWVV<@GHK8$E(&=$8;Ssm z#ckkG6hQsdC6Sb&uve-+=6dpGdh%v^^1msH^7d|3Igg&anV!6vp1e_fKKW89^!exA z*dC6@!(D|!*FWpN-KA0}gsKk62Hh^S;*EZrPKk*gdzqBM%hUR4hJ9kef{C9`O#IyV zC;b-u^Nt<4oM^v%kk$Of0HH2pz1?nZ3&|F~vGyVD@y8#(2oL40Bo5b@+t~uLS0A5S zj|-VILAdaV4r}&Us%50hx=^kYhl$}}I!Wg2WzJ!`7J5tQB6WpwzIrNffEUz10-e7W zqLR10?IPgx?`M|(1Kam`wod5Ci-Qj7oMzvIHyn02{CnKDTEzX&x};q+0A%HD{F6S( zRhXQVtMJeJqMaF~&aU-O`XJvycVU7-!=Wr-xH!~zVvw9HJirntV+IT6M5W2$X!>V; zk1N^LC07#717eXgGe=$+lKu~vm992JG?Pv&&1|&}%-iYAc;E%}xlP@=@=mPhby!d7 zbLY?XxpSq@Rh`X!5Ueg~#6e(+VMr-h?vs`>vEcr5H^MAX)6Cngm`4RV8e$-xKro^O z0*fP-#ZAd%lPTyY9xj)3&h5ob(zLL3@>jq5)t@HqF;M)-ay+urp0#I*(3Vm&$6i(v zDTw>h5zDZWttAmNInxW>2(DNQwk2VQYo!J#@F5vyKdQKZKvi33P@D^WII*>&M#l)AJf! z?PQc88wn8j6YQ!h3OFtl@&xo0wmLy*2~9R`&`3_ubr;lhQjZlxe-y->&+aZSTDd3Zmm*#kp&oTI}Q&zFE-%})!rYl@7%+n=wf{8noa={aQ$17O`5XRm3s6%c|f;-O>`Y0ckYcNyp8mM>|vQ;Rz>0eh9l4myM=a{2{H zz#h)pJuRJ`6BEfABpWYL<_Jrd<6A&2W`1jb@}AHM(o`+xHD{ri9S2QZ@d z4PHpcy&81ZsYEa@VR83)+a2G1@WBTk0DN_A?ONyezVpp*e)D?|{^I+Cm$Lr51gfZ0 zS>-LNg-PI^P1H+-yZ)EuYu|rpk7;VR$R!w1J+7!7thVVR4$7{mhu)PVU&&GAX0o?w z8~ZLh9~l3A=Jp-st1vU%2&i~_9Y_I@)Qs8<&|)i5lY3Z@y+-Y&0{UsdGqSPd10{M6 zqvdzXpXRpTDL++q;Knxtszi&2X~7hXwg5hMbV2 z-R1RCc4V_`6&7h3Xi;G3;umkjXJD96=}6NE{-|U)kaLk)zM8#52OOf!Pq>OW%=rR{zA`m!KrfS-qqN}+jM5FOlRnk||2$gImmnPoCEq*-B>?aUe zOpb9*r6FX&Wr07|kz{zFp;Sm`l5R(S?AR!SA~t>KP*O%mETgdUr6oO7u8;?8NW9N2o}!i zj4Ep*$|-~j2LRumM~HAj*`_Yua4`d*K$!UL_`o~hnZJr$>{sfRo-}8e5%OZn(7=E@zNb_MEF4t)sW|qPJ&%WHFc<@>Zv*;^r3Bb5A{$G?d5X&sE)HN7lGJ#InlJCy|?s>I*Q7h7zC4>zvY! zhBWi|sm$Z2<2#Dlq%b3`rWIFUOj( zK2cl2qmcTqfJA9qeAgy`C2NIh^H#+Zef5VfM&R!X5F$T3@ksq-R_(oT6W{nNVdT6; zt<6STAZ#mu#b*#F`hD$(cf4M4=DPU7U`FCQIk$s9&?lv3V0B} zj=WPh%>M^L_ilmZ%o$1+3P~Ottx1&%Wy3F}dw?pq)gK3x%9a*_>mI2RlOVf1 z0%ML12C}LzVECVZ{z%^5crvng zCpX%2ho6Vk!E`@m3dOU&`#&Cf=zEALeV#I8kx{a_TEkiqj6Q7cqNU2)%y%;CUQ69U zQqoSS-R42STM?@A5(iDY$=elO<}J2?ZJ99Y)tz(NVt|GjuIq zU;wSIR#&4SqXayLx);*pO|{qb3l4Nn)#g2yZv<9yr)fhGB36z`0))hNzec&1p-*e7 z%%bB;i=m^kbr?e8)xoN8=g3>l`9fI5)?n$@aWtYn){3R;>$bvRh5B)~22=(A*JT~B zp*;#nxOxjAY-=zQm^HR&6cQl z3!@jJyTY-sR_iP=?e#^@Z&LOW371{xZQLl2X3Kno5Q`;BNt^+NHq}=8 zEw^#4a7=EMKE=&UB3QJ3ht$fGqaugsHml|(G7Fn@YeCi=V8%hQnDJ5E5qZkp_=YJB zY@TlYq`hgDC5Qn+G?2kZuv5TCQD0f81uSKpahaw6d$@rn#$^-NEX*});+lzKU=!Eu zZ;FBSIRoAX^NBU+4aQS(YY?4z*r`tJ9m|b%dONN0Hg{XRrrkPv>n4y;3%Nta3lonX zxd4&ClYka_31)fKw54cMY{kf5KackXNZU%jhe+#R_j*&_S?{dXQdT(GzwYIYnN-GF ztY)f31PSJjtK=-HDb-r2Wol@}nJsgIYx@6{5!{$FzOqnOq^v$q+QYDncJhJ z8`>MvNb-#&4Yvc$le}f((4-{~JI##ZAKN$PSK`fm&4#-)UFi*5SDdxX8t#S7>s(vp z(Oq}%`cwZV5Zxz(2d`^Wx#5!}1m|Li;Z1L(%0f}kfAa=#oo~KqGe+&VWK|cb^}36G zxH*umBE=J{g$=iAcNFpkhpp^x^c(J$=3uZX>I04N87&~9f3nLgA;LPz|$1{qfeO0^Rt`e75pu^FS60hr2 zE~VG4px15eR+J6hV4di7OXzi{bX(Z3dEC&l@G zwPo7YFMd&EQ$AArh_>afyJWifjJ&`s!lzfvP$On7kot#i%_qNQhM>eU5e~iZb?SpC zaOadVkoXcaN1p$wj;3P9qNaV83O=!tEwg%Z)UvpF@*(r5vN>DkQ+7IK_|B-AJc4>5*?{Vi?fz-K(2sw)_@=V-Koj3seUWmS30RC z^9NZ_`O`i(30u^MRXEo&HLc!qe5o*rfu8fHYE_mVP_w9t*6-FO;45E`X{-0=6G{dx zT(K6en4A9Gz!f{8yujqdkLmuTKdHxq@!;I+vZL(Ko2SC#RKUo=D3hCjMox04wv`@% z8Lsa?X_NY-a*~WSiz820vS(1Kle=;2+*k3<(1zKHSQPW8v?4XR1oKY3<|PePw${wC z-$4)SpojHxyn=NI%lPAXJLq8@%ta*8>z5HWlXTi6$8DE|gB<%S{-!Ui4#_##`C!q6 zp7cOXO(xHWOC-s7ngLp6>&N;KJuNE$(itS!JEiA*&~iPTTQ&gxq= zT#ccor7Oa%vRX3mp8KFC$`=uBHtxVzT}rT{s<_<)uI$)U>{u*@8U=;I>GUJ%GzCqm zl+(E5@6K`2Kb)h?b>=zpdxgSFFdvbucTMFE?p7ur=M-Iriz>B1-~wvDhzz(+UA^&! z*PL+!rwF3SZhG6C1}QDebb4g)E`5 zLJRif#NRY+>*Ut+Yo7%k^4Z!1|J$~`;*S@-(tlD`ilKCvJ`{2$3ehqGZMc5Plh=`*J@ zF8T?R<5g2z?e#lN5(ff~p%9RPfmTVxaPuNn%;d~7JQZC41;<*=?_Ji_MZd)yon3YP~F96PRJ`0D8@;o@2Z|EPp^0N27 z9_i!zLDuZW4U@{acfjWV6JRjU5Ny3${fKF7bdZSUXs}T92Yf7mD3PeuT$W@CR@DCH zauKXRKAnn9#&ZR#=_zDWYh(-tS_xJFzzq};2-gT=S|of<97V}P57=UJP?*dDvl)SL zkSA2$S4Jj=hGU%mT#(kHW{DKe-72cj34X8@h66~K8~lOQ%R53rO0(Pz0n}e97B^ba zq2LQWiW5MG_ykginoCq*qB5`?DlBwWXLxyU*rEG8HfKwqR7jMq{H;`?TAegnE?tn6 ztV>GhO3p@~R-9YCdYPlW%+aC+6J$(zy3s#nj&{-qyxpu`#Wm3u+9|c163>knI}!nH zvb$7w0XC7re31;M&6s-)MCvN_n}P(6*56*Hzk_}=KkBd`_Z+b{z%9khvz=OBb`ug} zs+?O%HP9!8ZMU61*-4*lpilnYY%}B0s@X=}P*_2GHX63JLx&o>CyItG<^gp!{iLD& z{@Lhx_>gDUUav+Kb8!um3S_fc;{vD|3?gM73LoF8RLl<0557@Do{b5n3bAdn3Toa^ z^I3zEGDrT{lxC>u;fOYzhI6;p8ZJ&&0n3IV*2;v+hOIbO3Uw6N;{t|@?!jEEeulI7 zG-t7jR{b7laS>-Bb@#8KRc~Zuoe9gAZh?-05)W`eAXQ2r0)*v-nz@j#T9_Fb zOupuFeISrEjRYY^6JXerRmRuPy|Ow+v+7rwE9g!8o_wd9iwo?P>L^ z>ZicCg|Li6%6A-}f5%D|5-Z>F`Af;E9K-1k!|ii3S?;IRV;JZC5FJEpAKJWh>C(*~ zdJ_sZcVi2Vn|a8GNU?lUy-q!gk2tuW#q&>qmiwu?)9eNapA$9`nK4L3Oy`#F%iK1% z$Mle?z?C^Ynu=CnhFSWx%4y6#ARFe$9T2j6izQ(-S}^$kmWG18n5+dCTP*j4i1Y zSk`@*eAhlZy~QMeZ^YDaIcluc#JlyJ8%GX6kEEX|^b zs9F6@VM(b%R^_v6wb?|nq)ld^8fxWAv^uPo!t{XyQ z8{cg9zia8acCS~>omjWN2Mckd%vxA1snJ=_*b+n&F(3>pLY*L*b=6&QfbF%lcNa#6 zM%;FAbVEbah3-&c+Ut?`HOXR>c%trEs_!Xf_wCv@;%Z;I%2QoZdT~{|Yt-C{@%%;->NkS*SVy{1wskze(Pez;S1&T*z4)sy z)oqc6aQEFf2$fpYMjVkSqZ$=ZnxXc2H&eJhL05lP>9<_GY}vAl-%76cS%|5Y(pMjt zV-S6S=S$hwLB8b64i8) zvjNobRMn>gTaZOQ4W^=Y!i-U?65FZTfw&=f!D68*xJI?iL`#LXMW3--<6jCof}(`f z5`>y8Zjx8V3x;mZW-NDy@_CfXWfjgO1)B-aOL;?&Rhq0{3hM4KOhsWg;kxt`8I`GY zm9@*Twdd{-*}SQ&;uN;#c7dH`r$$E%eQ#wsA9KOu3{A72y_EeI3h88)kL9iYh>flz zPH429{)D$8P#LO26mw9J=p%3n0C3JOpMzbN%8k|7<*VsCC((CaL*H3P-;v6V1s0n} zK#8t^(7tqpfd`ON0#V2W%Pl*AE^_6-K`hsNJpSm{SSmHeOn{M1uVJwKd>#bBhj^jieYm@u;F2|Zy2V1a@mR9W@{`lT?HA#^%T3L4R&c(>1x#<4gyu1E-G0NlVnw^HmK{6z!p?|e_wGFhj?ciQ392BUn?Cf??%fgl z&gTxzR&DJoV0H?fdHLm+pSOZ6+}^!U?ix<>wN;bT14_c4Ro&g0%%R`@;0HhW!?59e z;DM)V%hzna^4-@(9H*Re=6Rbpp1sH!wL5!Wv+2TfPdO!GzwX^{Sbvi8Z!XNPnM?)4U6ohxu9vo+YOZBYKKzk9`sOV4BC{k^i! zy7)8Kp0S|e^~}=eslynrZ-Nba9_ado)UdPO#|1wX6dIUV2iC|%*c8qSn}kl8G9wVt zD9bdLR}cd5FaQ}7TrIPf=||ETYNeu+K(Npr7INr6kYwLxPLmWOW^++BM@~X#+T4of zGP>2dX)+N*hbH7ou2|L~^gGaEnI|*7ceh;0wk{w$jc$Vvxg5++e6@+;EZ1%^n;@bU zCGKZ)+6>&-eIWQ=94$o_G&QYV(e9N4vw+6n2Xn_>20ZKtuiCWfypw|lR5E6Qlc5%I z^!9f69X6LzirUKxVdA#l-blR=)uoq>WtQn-02Je6$x3DEQn1o2Hvrq&q`2L#l?p>c zZgwi?dDgsM&y4mUR+Hk>dzClW6{rd7{2 zoPq9YwL7qW@80z+)^3sj6i&?D8)_?b!#FtSY9r-Js$l2NvT+{G`yyL+_a-H!-bY}z zq+X)PVzgiJMXWxhAr@;e9B%o6EW>KRX_vg6-3-Inpe+mpoPJ7&jDC=&)~u5D`O=1S z7;Lk%qqn9WWWOb1MK%%~hv7`h@%zJ7EoL|qpa;DPiA&Ad{g!SU(H@=XPdx$@!0rdT zQ8DQzsN7AJry0kfE885D%Vc}@MWp&b>46!W$*8Th_4()5t|i!Bf#W%%KKtx|q&&m~ zDkGaoQ}p7rK1Wc5Wk?B-9Z8L=yzfr%ZybeVW72pkqln+?SHf!1kf=l!y#P0xc z4*7DRbF+Jg5KZsbj%rIHww|6P%0cva=c1H2J@&(g$H$$0os0TyYw;YOP|mPS zPv1H{y%BckHuZ%)8)~0a_q}AkGL6Ha3_l?bACN5XoDHA}`p#1(l|Bae)oI1;a5iw&kaht|e9RSeNBT=B2N1X8^<^hw%h=Bj+ z8=$)Z@Q9aMD#a*GU>4@nOd^rL9*bAbV%+KMI~AsZMT__|*^h{gWr_%W&sq8qZ5HDC z%c`=2-Y+rC5N#Hs_lM~Hju5Hul&wota4oH@ns;n*97$Cw3lk6P0e0=*@9}(Mi}jAj zip3{MW-h2Ts@v-Qon%Br>D;nG8P-1U@oWYnHU)@lNSUcSU#KB$SIc;KmD&72zYvlu z(_0+A6ywVhBJqrNu#bY6EslU&(~fB^nLN9sgs)|bBL#n`SIv3_sj5;8K2k?Te1LQL z0O#^X`toNvm$z{)S931sb1om?T-M4Ah{3SqxgVRNxJ2~JFe|gfAvjv!_by!S3mA+p0om{q`QJyuXE`UN!HWY2xt|J-0o!RQQ>SHV-N3do0s<*QseLJD1 zv(?q4oc1bzRDR(2%$2Wy-NhGd-eelLZIJ1Bgt-$YPzSyh&nO}!NMGKDGp(ruD@{L( z|5*1!0I64mh=hs5rj{Ul$e;^_tHrzqX9$4-W+KdqaSN+RaeW4yRYAz*Gl^^#xsudu zrVM#oDO0G_2D$Q-w6NmC4VX%B<)s_28r0wi^mv_!fLVa4c_IHPq$bB;=NcOsGqyPZ z$B80izC>-kEF0l~ap=^jSp0fL)+p9s$Wa?uD;4=tCx}OMG*MO@EejVeWC@E9Go>Kp z7hxJJmntj<#zqd{@azUbYTRq#=WO5vZJ`AnLC4@)Ac>~@qZCEX)ZD>#!he(nL7|T} zPD@Qk&cH!!l_kTHg7=85qG6ky4xjUeHI<07r`09#xj^6QNatBg1gk{TlvsePuC~RE z!mw_I6c^d6$vM0KamI@9n0gQ$uqfvtBrH% zUI=!yUAI|;v_k;~S%OwEV6K!JER|w%?5NSdz7UrS6i=om5AWIiBEI6#WVVoujg2Mq z_U5+MmZp%`rlpVV-@EtFXi9N}dzJ%8Uee~~H(~g23ulkjzi`#kmY_01L+{>IsYcuL zWPB)SRaUH6;J3RfQ%4!OV;SA`LxNz)QVdxeHeFT>k=EKuX{xhohh~wZU3Wbo9Dr({H zWZ-1enHm5iVqjWovTT{o&SDKjA_cz-B4R ztJ2Glj~<;!CP$78L$#Ji8iJn7k!4Ea30KDVA03aUB_gFm92ISM5O}A$lV;0J4;>gU zNz_aVO^I|>=Em`jjN|Q$;~z7Qg~#+p#_^4e<86%N?TllLY%-}>Oj{f6K!+}wMe$FT zlm7q9-kX3)R+Wd}=ia)tS9SH?JzG!D1_Nk7HbqBf1w@E3I_eiq)EJi}YNE!t{n6CY zGlL=^n?@&zYd#Z2B@%;zMuBkwWD|ycn35lr>eBw&$c!H6~#{>~P>=VYXfOqDY?5Dyzg zx#&d2P7v>yI&h^3oJ=e0^X8dMwMdv1v!O@#XV#D9RxK_8wDIw&JXS2|M*4DLiW^Pl z%Ua_%OVv_Vo>UTdAoafx=kny)5}ceWH$)ApnQg0a<+C<13_2WS_T=*A%Q`|z7M+|b z!ZVjM|CHrW>~uHwVIh}=RkA44A&Hjc-fy)pUvc}d_u}H%`|I1kchk0Q+itx1_8F$^M{T(As9n*J{Qs1xN7aH$X zpAUTSmH1~~`N2OX1^GLac%~)1p9MiaPnpBJsOT>52U)51M)f)WXWopUf6E4y)GXo# zol9@w7AjQ8A`4yX!IT#i4+kY#IBuDl8?f&sR4wHl{j3vP^<9ahubOFMgsf;KEki&YVA?0zOv&~Y4?X$hll#ZYzIeaI z8O!6egLQWD+JjA`j^a?&5-U{C>g-M?IahIWhp^ew-I&v%h%Emi1xZDRWOXHK7`c4r z&i(*)kdM%U!g?`qV+5I(?`NjI#dHi)ZCH<;Dn=SS9W`3q2ik?;?P~1c$bu(kUd4WH zVq*IDFqs=w8$w1N1js0D={xPTrLjPDg2|f`qSnkmaeYU&%(Pg2F#BvC%N%NHNIrkszp?IojQ_vrC_ms=}8`?Vyz-o$BH&7x9E3%J>wj(!FU9E zQQ2>ke}w{K?rY)82PolE97c6FQp>OqCEmjymG#55Ntt3zHOg>Xr#^nc z@sESQV){DzFO)47dQWo%vk^ZO6c`a}2s9AD{GSN-EHKfnD4 zIL>}>`!9a=5aFkXfB6nHm^UyJ^OL+kK^F2G_JT$9*atc+blT;l2Dl zuUBcN5NazcP`e9b1P>-D*h2boXWX}4F6~_K z(u}I5*5g>J!f3^M2!>$2%yp)Q984V6$jsLYb}h$4TdN=A6!~j@p)N?nNf4lP9~eB#>b%aS zZO2|pT!j}6kU&Seixb!?QxSj~GaMj}Ow{Iu9%e=tS7$Pd^ljw{l!)MEk&1{MLz>u` zR|(XRY8Xbr04ijR($9LrS%8)yDo}Hu<7$Q`v)&iYPsoBOk}S;Xp<%=!VhSqvi+BX! z+({pg;?%`WG5@$vPC&n?;79`m6bdDd#$(~ai~))dJDJ*}!8?ahOOAEfN zfCvu_AD|`jo&p!*ro~^UtCt=BI37R0j|w^#-XJRNfJMHvawbZnW-6E2XQQl@yK|4u07e=rA895uzMsN zlbz#{RV#UXT?9Ik557<2N?e2bhIHzSKmOg|;O~C?#miae`SqBVYjg(B>-qH&n6VV1 zyC6Jp(*w^u^P`(~{A!2HdtzN#f?NaxQuryDq9E&52U&A6MeVLa4F_uo_hHRUZDcv@ zdjp@jggEIXpLq*&EsHu#db5r#+zWS;FliB$@y}5xZ`SYY*zV`D-?1p&yC`4wuFY`J z--(~n_~=J3(cc2kByYa8vUm>etw~&otwODxt@suvM@LzdEK7PUB~`_Q3@TEDa-*U% zc6Rz%_IEUMV~AQLAfQC~q%06sZ@_&*DOf!zF)414waRK4@d#4_0lzm2A#)Q_j0m-L zFbk;`v62~Ec#UKliw(74uP~9A@VUVh8XO4Bq2U64Qa_hZ<_2oik&KWE=m{%KYYu^6 zEbIi$Fu9+cx`AXnfqQJC_81Z;D^}4&wwwy+mF)=lbfU!wJ24HoH||$>Hw-*;P8UU@ z;S1$yaUzysKK>S|&?m{tPQ@H_P711!i^L_eb_4~3@z}XIHWx#5e;0LCMk*{xH#JqT zeN}_?0YV0#B0=3u_Kv77v}uEs32iDF{upj3ite^IGT+EbQUNry091kRo)1eiLls=F zq#R>Pl79GqY=uUdrxD=l5LZ*RhVz}P16)lAfU9LjVqXTeUEhOj?D}0OwZbGD_kU9I zO1+rMa_+WC8iebw_WxZr~7ec6n4_0^9b*tP|C%W4*7WTl*>G$LsXhBHQZa9|@DF5|{uz40Rc zTj2AzNOBKrBu$zzlPsn_J3c@yrpl-aw^hj6P8l;Z^4Z2Jgoi8P{4B)}#6LbsKGRb( z!2zuQ=_Osc11$W`B1ZX)%5Mvo%X@zL?9#KbQ8@Fg+GxrgG|qIr=w5$Mp}!JC>ZQMP zVn{1q-r8zU=X#d=GGV(XcrY8=#n_hr%GQ@+lYYCNOPy2SWbh;|0|>mJmW_hA_>*TWL- zoII+k&+NMUv0^4Xe1J@Q#!MZNy*S9sc*HN^|g7{8~wZ=Z(1 z<;Int+sOR%PUEVpH`sq@6f^{gXT~z-6hck4UP4KQC)B;hlg5)+A&*jmq1EZXWlg9E z6HY1>p0E#l4?oi0Vg|lIK$Ox5nQ;7#%gzV{Rxa8~dn%Pym5ZnX_Y7NGmWuh=wT11; z$#c|br`p}xD((CoZ!{{lK_l(%9t zzG*^U%u6-ST7C%w{yqkNkMW3_fusMc#`*AQ@bp)&TkTWbG<&$hlB+?I=niD8Vw?&v zDYL6POgGz>yrgZqxTuX{v*F^?mU^aII5N)Q7tcQ<=U?2v{L)N#;~68d)>W%6Y>P1X zG8JC#{pO2v3#ko{N&vS=0;Hak4x>t!-1g*D4w8ix%_x3+S3y+Jpr|^SmD2* z^RatEdonoE&h!2H*T25ccV0$ay+vpoN?&b%%G!3|@vHT~r;NUQ#>n@PtT-+N4w>JK z^*(OA@vkWM2r_4 z?Y;M~)OSl{cz>1A9xir=D--#94p4V>gGvT3+<0DOYW?f(W58d!_K~N9D^B^dr!$*D z^Lq%>yx*EG9e8B#279d@ZXeSpebDQ)Zzi+Z__5l|Y8A6)SM*(*$Rr@2+#T=MZsl9O z?$xIUGvS_P%u_3t11y^5D~7wfU=^J#Z62=dnK%+`nRxWj1mit^q)>fESx*<~&u#nn z9WF<9-^;ZdBW)M1TGbjG!7Uy-ZFHi&Wu)38ary&yKUwZ87_nwR4Q$F{-(E7f8)qBRzxS+U(<*6}P?K?;;|1{Nk z(Q?e>PxXlVkj>0`j#V;Sw8i%&QoGg~Jw2O@Q;c5qMW^>jadMv`Y65}{23N!)xN?}z zFVj(x4wO%2F2K!4o-LJjANU@9AQit$dd8tlYMe&CP8*lc6Q(5DDof{S+p;kNn%N1&jz&-TPE*9qPkY(vL8(1 znJ&%T^4eG? z*Zq_V1IIv8-J6R2CVbB1ig&GD`}XCN=Y+wxQ|&8)J08FHbDz6+4^uj>;P<&C(|y`L zSHm_zYQ*5PvwAG5+LL!htRJe!~dx{)UFFno9Yn zaz{ENiIkOybRGe~Wdzn=;O zp8D_6&aKPJx~Q{MWQec*5}IDAUKQmgtMS$!%v+yf3XZ%utRG)7~L>~`E` zRGv386T>L01Ez0qHbrq2aR^`wsui0N+BX^RWX8pYKM++KEr^6;9*-q79#r{Il?RAR z(P-x@(=NA;V$k3VU_p^J#)wg+eUu>XOp{^H>hM{3h4}xrVS9~I>Mg1YxzWtkq}7JE zkv&G^m=tcg#a zOj*_9g1{a`v0z)Cx=0=2sgxt2V2(7Fu9i30QD6D`>u=m(w;0!44>U6V&NI}Z2UH8C zwP}?=#5px`Im@-o7)McOo~8wddCwTX=GS2(yUow^U08j}>$L!9!pD>v9*wVL^jB*6 z*(NZ_QBNctLW!%4Mp#77Im-0okTThp9D`yf)A}=?w>tNzj?Mw^e)u{1W*?Yk63}Nu z)LbVmiyU27!}G94W^5h@YoPE)t%=)*C(s4DK%C2fk{Ru*rb8sTa}Lg@O<>FlaOMv-YyxZEDLA9eO2L_G zh!E>Iw#jEn3G@}lan|`9oH6oIo&Xw+7;CUjX`SG7DunTE$2gh7=H!EI3p2%xCG4XV z`pkUZ%qweqz|VyYj=!&S=|MF}hN(=sn}ai_0gab0fHO@HsEv|W2m}Z*D_@$q#0~(+ zClLS$Fmgvg8cjly)gdp_ycFhNW`fg4Q3DGIgOBn%khUH;q|WF}k%8-AjeGJR!uJ;u z5|G@hMMev6@|W3eL21Zt3o^e2vx_Hs;yO)vqWc2UsF0{v^KBmFwic zKFVdJebm^7sQjZ6^Sa0e#zx_T&4IP zk(LB$Nl@WZqvgqsc7R?aHaU%U_zRSYTIgz_Gph0z% z=fZ5_*UxzzSK)IWNBl2z{KR^}1RjTLm`Rq4(#;%5Zf1Im2J65)8a%e8$a}-=XPKye z9}zP}Qh=ep-yh`T=VS3P-?UY@(*O8t>l=R`w_WcqzqohDLYJ?9kvH{sEOdk469mLK zeKGHjg)UzIqHf6@I~KZuFaPYnk|X@bRB3qNN1Jix{yEbx690>P#i6X*Uq|DYjCP5zjU)=xN1>2MT`HjCPT%r=BS@X$TzE6I;W1)+Ge#7sn zwF{B%$?RK@_Wy5dh4rsrh$)=Rz6H6;&u`G)^11~2(*GYMkksaRG4GCrE?)nlZpj@x z7P`Uji4G}W>b=buvh7LBIT!zaB)iXk{N{m;tZ4r-^Re$!3*oJ-3CKj=|CODaou_Nj zWBl=@WbYQq-To>W`2Wguu*-~ZQZVj+Z~to-Y|rmc^b~@{P$4L9`9AsWj)gA%y)pBJ zX!d0GElBjgx7Jbxt7cZ^?XzFVcE>^&|K3__d(6aNHeSfK$4=&4jOb~$eg3ImVe1&` z3T&S%uzjw;_PL_p{QWS<g;OB%hOgCDq1{QBVI`oGZ7g zo78*M5%m*(y+gJs`4a!1{%vG`yzW)TV%<#V|4Ftu`AXqfWLG)NiwZ<+vEzik?|8A# z)+pg%>KyFwQ0F*z*`90t-G)C1;gPkZgfECcZ&>a*O-=R=fb7Z3MpYS#s*w?&O+?(!!1 z^K$s}@_xTs1AksAl~m~TP0FbUU%vdy%T5ZxpD zRq_m(vDL1s<5RiNR5tHRBvd{djdbgV&;zD*MicFcsHM|cr4XV-YDhM$MZ%rpoafJH zB}1e8pV>dEx>kq6?9wZTD5iGKjVYMsCcDGVig(s{S}oH4TN#Ub^=`&OVi@zQcW2C_ zs(WBS{%11g+3Eqd`JWL7C9vCIcNp2nH_D>htSIg5GM+XTyD^36=?uiXsTh!vw4O6OQ%hG*O{A*CyeXV$7oZS8*m$m?s8qP8X5?< zs@6&BuCG(OGFNXmm#G8t^^980@ln+|;9%_%uyzSpdkSqk6-X@sYnOnvOTgMC{j7-U zxIQJ4rUv;-1&PA4(Kpz1Xpo<(FBR1LIoecR(R_UEw=c5aQG3=tQ?2EHAhdP0TD?`> z{EQT)_xP%D#Vr@J_{0ayEygG8Tlk+1rLR%fq}69WLOt1f6R11DUn-c5$woHTbclR+ z{!&3*6I=PJO^15aFn_6F&#-Lt3^yGj*Pg!=br@xX|4G4~=E~h&>I5oxJD`#HG=HS` z>TdZRsQ3kH#G--2X8Rp#tss>J44%0;MX$F=ukBl?c9ym1@kc&GCbWCG(^)@#<*&Zi z=_k|6rNQ;RP8a?3kiYs~r+0n<%)Fj;s}rnA?)J>-L;c2X&z%0M$1h@BzwL}MePd)- zZ%wHyE;4oJWJu{819?sQn9j*?W*ZH5JLfdG{xr99GKf8mbKTB4fN;hw3Y|sCj%co= z9EHxJ&;=#r)m7l`V4r+4SAna}4^8t7m|DBJ_N;@V*6OWvLI_{>-?G`<;^3rpO`2|8 z1^P*0<5-rv=-hAezuD{=9`2D3n`A*qPB`;iwPaWfjV*@8BuhamoGymO7DHo;p|Qn6 zW31||Q6_Em4FXhYCg}@6MQ+2$sQJZCzgEVBwmH6Q622JZv{$j{-P z<|88fS!#$m5PHDo(3fB#@)mk~?#lQ{z(Sv0g7WYpX=$u<`4UBvrTYR0#9?5y(jZW` zY&1R~^+B)L2%%*I*C^fN#w|2;`8A1U8+$cU`5g>v?A6re*Cf`{*sGCMrrYrMf_mq& z4Yxbqja|0kcHMaV+glh}cYKq6hk-(4>PiNZ9?EbgS>ss-vwjPQbtElKW%a!d#Uw4= z@cLedbtElKW%a%K9K!Va8r?gO#@fMUk*rd!yqU&=E?Er(JX#ALtp$(PLRV{{tF`@3 zC3a0$`k$11Z@i)oX9KOkttcNsCQWqn&J)bj`#B-X$N9yVaCCaZuxPBSQ(w##a1I5+ z<8==iAVI?>>WjG&&Y{40Pj#3-oS!j_@{u9b^f>Q42k(#tj1cAOG!|;=oOhnXLy-=Q zbCiz^v!>N~=Q%jfZ5a6|9~t;2%JsAbsT#fnh|kj&nz)iDWSJ}^kQOK!eS>L}jiy7g zTMiq8eQA>ofoU@x8(UYWeZtEC<0cnTTv|5L*Q9;IzM5_)P!n1sU|{6U2%zkL1U9k_9+9wYBniKfE>Khphy|9tbCdt=5(ma z!&p0nPtzf=LxUn^4EM;V>5v0FA6NOR@|nZwy1<4L(UVXSP5KJMgN*8I3Z2qq4-5|? zRL0|Lh0~o0oA@@$MDI|q>eY&p(`Dh}tZ+ioKwXzm*_6q!qbQ*gHty1{-SO_E2hI{SuHQ-HuI9n@U3#E2 zW3TVhxT?AO+#Yn$gN_sJfgTyJDNYacu&BqIM#ZNGMP0F_F(xcWc1$1iczE}6`k)8H z+vW5@kA%0&>4P2yjbYx}7zz47T~qAqIcsBhmtmJ8O4g;+E}!5QDTnfVS4yq(304`m zuH6Jqy^#rs;8SXg8rS+kgc$O9zk5?^$R~*79xtU7 zB^GF_ZmCysJ4Q?6v@}jjc6pdSOvRNkFQy-mVh7HjvabI|r)q6N4%(Z9tWts9W7}HyC!n3cd%bGR3Vv#U3s~y_7qEKc?C}MmsuO`KtpKW`UU+@nMJs`-XcES)J2%_!cTh8F z0o7gss{qw$Y0{FloEl)IMSxYb0q)O(|Gq*vCaa*)L7&-}Hi1W#12n7FjLepf*iRT2|d zn(djxf-PzY&Mnu}_X@)BOUBLP+$Y5|`ci7pw{F)ukEjPzs?WD;-7aP9)I8(;@Qkw_ zIt!kW)jUJNGvE;(&&Z}!3~o5&UG7T>0>jT;9>H3eSr|U$I2|5SO))1*#Md0`J^`K~ zx)H5%kQ2Ua(qx(_lWqdWSr38Sv=3S&{V?VtH-g0E$<8IPUz5i=KaedjLidL-5*j>L zW{A`~E_PGOSkt+I)LXX;?51Jsyj@_)IqLH(Uku8DTw40(^&Mwep2;>$S>g@to0wA{t(K%2(0iGdH zrKJt1(i62)c!q$LVF#>Wu7H&>cTN+q)`2Hr<-rZG)&VYH^~U)mc}5?t7zC<7(&?jV z4bsX!pbET=owshb-{>?kX`Q7W(qI*!IxPjPLMh&rSy~hWtia9NB>=4gR^aCCiqWGi zU}X%vT`VrDe1KIL&&Vzn?kZ zNbfGBcNfyT%hUD@69pD+)zM!Eo%^v}! z2OZB+0Ar?Mx^quqEGb8&jny9{+(j?m4zsK86c&@h^CzCo`cC096k2dts2)8J2V3d! z32B4S2Kp>~-BYq){ZvwC4?=e5T9^0uwywsJ>Cm;yfqdy}aASH`iB2A43>iglmnh^_ z)LB~R?P7>k*3#Cgw@XxUAL=ZueJ*c0aebB^=;nI2r2X_jcS`NlqvMc~@8)MPujqsf zh=X;$&wK={%Da#*& zFw}MO!Q0_%tlQ;zD2;vJkYFmg=GH^VB<~^W!p&sQzd~})ohDutgp&@*hnv7br3!C= zO=sUwFPqN3K*@q+siPZ0 zW27x^W27x^W27x^V=`Ac;~aq&BhaE`zRP@r2(%c179-GNq#sYIP?&^_;Du-%vbAfS z(TB39HE1=I+T=W21ycIxP%lN7^;V!{RGqEfAtUQ-m8ga4V%SM(4%*si(j0b)wV^eK zhPB~n4h?IgNpsNFMw8~Ct&JwlL0cP5nuE4BUWn!(f*4NgfK99oIB(tB(3*p1ZPYaf zu{L0s&LyH2!zY~|q87tVogbnW!$h4?^X#7!kh;<$8qsJG&1-1tTIzb5dR^*Dlj;wa zCJ76#YwGpJN|R`;=6FFBP(kQV)<9~7rw8@hiMmOBkR9kP(Af?K4I0xo!-)(>2$&F; z^ZS;Ku!F1^SUb>6AgCP(Pl#y;8VH261FZuhnv1v3w|!{m9+@fh}?vsJ|ybhfe>GtQO|OU8M&hq-OgOdB-wTy}C2AKai1D`cIcl*%&0 zeZF;Ae}q7^H9~8whlYBkJ)1p-XvG30&orhm32HBP#*lXhQPnXr+yx_(CXCQGgbnB& zhYR{JRovEkb-ULvG7@u?72lfccHfZjxS;n<5k!w0df!a;c10~lY~kLYT(yNc_sYT_?wHVBRN9xg}WqRK{(PN$7H@iF*>V31z zW2N4=iw#~MMWa~0KpztxPxZc~i(cQ{@5~KN%~!Wx;__MHwVL0~TgA@dxeeZXWy0gV z-nVqh>y7&@o$`9)eoLpk-nicxC;J)x>+t61!*STLi{r3p!SS7S!rG+8Y#qm8!!C}) zhFu(oUAs6AyRPGS0(wqB&wW5tbhHFmlTdy>RQrdXuZ?=xqUrkDXfrf3bYUxERFL$@`GUKlZt!D!+eq0;%@IrHeYopCj%v>97h9Eziq37a* z*LrWQg88kFdUzn;+@cv5GS@cRck4w>SPf{T1b5IKb_cz97pB56B znqt2r4#YIkv_57?yyaz5zC7O!IvAH!`xCBwXS>0P8ZVWrlkk)6$nD2;)Q% zJovRR*3S(;(PIQ`y2GME2td&sv^g9|iUh3>FwgV6qKvM9%DGA;CYa{@U|f@M8s`Ut zniRYgd(hJzBqJ;iubHnq_%xNGI>2Ml0%SB?v`_0F?kJ{xn)*;0;1{AaKy{)tKy4Z3 z%+Aj>IiXeJwcWgHzE!Pl>~)@1eXD0xqmE*mb*H&zh!ZhB}2; z(V-8#TxspOcL+p6d=5Ek$`l8$`%@agIC?*&wT!;^Q<};6r;eqQ=Zi0hn`>86(9V4j zTkDd7liCD=&4{B*yhhsofy;=KOB_dDJ84Q%v{5aw|*Jmqxan#T!jVZ3rR`%kkp=a9v;`wYn?}YdrPslrg3Twf(sP*Aw=}5E< z+aAt^zOl2R$NJ7fk#+nNo+e9J0_^avb4+^}*nHC-9_Fjx-7)Q9V+}8cW7@+>=J(;a zkpV|tbsy|RS}gIPlKJ#^iHUSFawHt{VegE6J@CD=FPcpC&Z7m?_YcQ7!Ah(MX^-IhXaCT z5*ff<)UYh@@-ZZim#^L*;YW;0{d(=?*IqsbroNZ9(ka0*nqA+kMY;C!$xXa?ftVzY zm+zPn5-aHhunaO$yKooL2^U-p7vdYI5=1YI$&D^Sxlp|m5OXb83_}Rty9^>wy>t-@ z_jW-6s)T0Hg^06Q|ejs=K6PPn=%g zt9zwmPmE1{ukM(RJsrcNB_m?N%r?=NP`p?rOmC39M(cMa;bpMp`d;lLUzdcP!NMCp za*;27A)m8Uvy)uJjKt6aLMiTjwU@k?n4+-Y`d;lNFA-G~7F^$}z2rkWtefqv@6}#% zg1x{2)?U|DBqu_0g z?&`BjgMD2}DlDNh$=merBzNnMo8S+;cd`z{r%{sf?eF=t1YdYRsICf}qi(rHo$-l_ z)NSf84ier(D>hJgM5;(!5Wa3V3p7yNdh})@eJzoK3A$xo}-3jpLznV?r2S5WUA*R_~6#+}%$YXm?V zJgH4w`ZD`7;h&8H9)Ps_I+^AZz#2vnTRbk1#ncH!V*y|!pkm=F7AgfotXe|xbiYmR zlR>7o_PBC^5sF1a0cybKvdN%;NMOYpQ34vvPfaV8gE(jW?M4=eC~2?=4zTuBz)N19LSsjWHu~_H&6$J z;~yMX{_Z|MxB~`x)YPYdiWTSsf10W3BEb}mF_+mNHg@jRuEk&mtO<|r%%ngZ-fKXT zJL@QO4+!*Xqs2ui2XQt6pkIgomLju)HJzI`sWbp9L03XPpl(2*0&rLjnw|=M=mVgD zEdK`zfIL2bQ}T=gNLnq6ER>7@Yy&sBeMfp*|RI9M)Y47XR+P{c?PP>;1*rFgq9|&q`EJq&eMPQ@eGPo z$7fP1&NF6HDysix*&j_Av;3N6{}Prz)PGCZpGX-~EDSoueyW*C9Xx89u|LO}wX(_n z!zr~`{~aE1AP@%vaUjqG1VrA(vD9T|OcO% z2=hxC$uEH`zof1F5@@<`^!&Qd8*?LP34LA~K+-NTH;2LKn47R&G?0`&N)E0Yb0g)} zjk(1HY4lN8!wZVJ>1>^*48K9i){z)>Q?`zi;n&F4Y0B`Epn7wLpU&2i0JCFo6LBYO zfydYTt+RC`Os#1@-Vd&!{hVO74y|(hN?4{k!_Ujs(HVYTwoX064?DDxa3*ObJl44$ z+q2=zlASboZi`MJ<|dgg2+>B1Xr8KPy6EgUR0tVCah*7)Nt4jzogdO9F{r>tQ&OuN zjg=;OF!KCn$B((`**7~Fw7$WNzL^(ugH1cX-IyEfc?W~VESnvAG5}w~miZEv?0VHb zynViSgv8tiwU1h2ZuH%ax$#gh=Em~`H*rSevT?S+T$&{NB(2KX0&`t_@O;#goBDh{ zYRUFf*oJmfLe9 zZm#SW`R&N?D-&~iBvV;rxj0X5JKxf&dA*WOdA)JJUr5Bw!ElMVf#Gh%4Gi}pZZ3wy zgk21mh?|4q5^-}dTq15BhI=uq<44>)Y|&JGEz@HfJB}A|bI}A%QY>Kn=JO(ME`D6g ztd|Cq2vIddk)MrY;Ka2|RcRb|@xg2TaU*U`jUD6`&A8AUaoZ_6bsYnmBW_LOZ)CSe zo5x-n+-Ol%8r;-kX>e1EPdeh(H|Ry&2Kyx9=15^D;-r&)!MucX$(#*gux!P$ zUc{~HJMoB{j`X+z97z_?5hC}uBopWe5(&*-mbkd(VP`s8dt7o0nj=b*Tc9IKS`vGl_qfq5jam+8q*=mgaYuT;MGT755*I6N62O z-$WD$X(W1s12-M4r~^%hDx4F6m^|M3A>0Hu)b@fSZmx%$jst+Y_s{ikvx@_BJ={1H7w~Y4L-AM- zH=qJ0^}7n*;NjNz)!6HLxH*y2>*u;2Zb3IM260jmH%$c0ICJn;(+ESI(i({vsYK1v zu0Em_!xBZTomic;gHiOZlm;-4-cM;QqwoEcW-|UM2ltw3_jx030&@6_H={?-M9~DT zO=(D%R!RUj)f5?#7_Rqd0j{P`x7`9(*o>#}>!~NO8BgICQ-Hv99h#n}`Z>J0pfwBY z;dPY_B5vz&-7;raoYVEa{J_ndv?T7Po$gL7M0<04bz*|&sut#>y}2b9E8P2nPdI7G z>=s9K{l4H6PTHiUQx)oaIR)o0lYRvW>wEE4KDM;vygcnOV!DtBBqBDZ=Nn?SE zUfdT~<>daM#X68n9 zMOyX}{Gg2|b$q;m%Zc|0uRL{1k{l$&^a-IyEwJBCXicP<+N26^F&*Bip686dx~ zYM~%^ukITWYaNl3&}h@V6bX*bi6;sLWnBt#uh)GeV(n6pyI1#(h_w?<^PZ*qM#S2s zAopC|H|C`@QBbo4I+19+l#75fNcbIP#e*86N1SE^8-;nyDHRf+*)k||OA`cWwvch- z29hSLKu@9l#$E&_b2x-8!<5}2O1jfHgv%3gtM7HDN(iHOH9k-NoEUeIe2Kn6N94Hl zEBO+r_0lWdDYvu)87e~l^r&81g7g$2|N3>(8_58aiKkkWB~cfBOy>{*Q{N@iQJ74H zu&VEpPGF+cHEFL8CzkW!3=Yj^Y}{KqBSYuJAZaS$;?4$TjLKxF zgF3tPcqB#3@p|D5hBq73IZKZOJr)wuy0?dA$E6^Bots$%B{@-RErXi5gQh2$$M0&y zO%x=g?B#qo+|HFj&D=p#gO)+f+(A>r0vfR}hWWEWHI-=@ROb%O`sVX_>2xyJ99t0v z;o6E`rbNS5bow#RR)m!1*oqL59uChoq}6(6uJ>CQ*@6_YpgtqE^D(n8p?7y~u>pCI zkSKHkMKsU8Z2T+Y$JaIK8$dE2`@Jk=^|Qs!;1L-~<5>vUF~J?!R%iGxL~Q!#ra4^mT& zqj>6h+x4l#I-O9@+b(%$>C{BB$dLoyE}fc49yy&jY0iNb)5xR8Q>OFiT~C>pM=#Bt zlShxItdU2LrwlsPekAQFgWtG6@RSK>)NS(MdR+NFmvcz=Df8h)mGpYKOE6m|b2A-b zPPGSZ_|L2MAlg*}uz{$k?5l109HyTrtSC2Lfk1KAVx_jwui zG+k%ddl>9y*t_W4lwnUx8yWV(uj(20w62k1PrJlBqr~6WiS6Fm?^FH6#ryjWwu5|F zxv!tK$7IGet`C@U+7xvb*Pm8ry?rzcHAD1_Mi@b=;5A&j0V#n;y`;wRTc}4^=OI*r zOMk|haP8fbK4AVj`mMK*uHcGuv|DeVjF1$%8>R-Fw~vlt_#Pw2#j~;?H9GoKU8|Ii zh$1L*Tpw~&L{S%+smD}n2ebW5wN_i5JYc6MM%K?{r}-YI{vfrx zbR9;whE+(Io~Q}EDf5b3ocdk5V4qw5DI^?F#7-$S=K(U50*GuZtl^=%JLe2RIDw0= z8rI;^y`OW26a~=L-iE{aa($yYXXwlIjl%kJ2hKvF9`Ey_Eb}U0)hWv}@x;6YUIYj- z^<55-@G56CWLygk$MS%HqHdqTE(Yq?IQ>T5Xxzr>C@$v4E~jU>nH#&DuHb5Rak+3!U9*>8Xi#d?5mcoHV|`=;X>mL20*r3?MJgDk#r53ihaNrZ&^5dF z^>|T$4i4?PG@KDiiyOMWPtaWX()GNG7ocaM11?anN94O_q1AmHZqD=q{u`|iHM-=W zv3LoaJLMqn{FsI7zY(-+>X`O%%&pgRQZWlHM;kE=3!dm4GeIWD%m#)$@|=uuywN{g_U`SZy%HZ4%4 zMM%J_(xSQfNnwg`Bc=y>p&;pyo=@gUQ)fPz+X=~Mtj{Ntwl~cu6Xw%2KGNEz`DDU$ zUf_H(fqV0OGJ&`=pR6%k#+^^*s9a)QHX*5iym>yEz_xilnUG%dd@_M&(|j^dHj>4s zR-UA61h(Q~L^jHX@W?qrMIWR$st?&H-ICKRAiq|UDI{*^#O|mCj!L7eue??}Cz^0d zR0CZG90f-gW0~^=x+Z*uH^Ncl9=)A&2AyT##5IXl=kVDcLjk41Kx2 z5yDZxCpU5|gDy0SE;I|#@uc?Uyox~!NEhvV)n3d7nK*#bYE#OAj}z?|xTM`FQl6@( z>*}QIBEo@yq%+1E_j5X}^`FL0r=LFQV`Hb&HR+HSne}AMiPr|#t#_Tv^>)c{gU61q zQ_UUkq*G~!jydz#oH0&1#dhj7XDnsn1p`q(S8yCGcdG8x_sUR#>!Qzk z~S9^Q9CmxSQ!ogrL z9Eru7w=>K8npcEio`vY2{AU3I=hau596C5O=}+`7?M;-+h3Tp<-V*W$0%oKoH(jdr zE$wObPjSkWzqKb4iUn=kAB+Vf6l|%<=hRaa_aaroPujlBULkXsRjOH~fql(y|G<|X zKm5}#U1WdAUSYq1MFvi@-{2p(=||5#_@fQ>>mr}u`IDP(z4hj={o_A=?KM`=9oqy@07eN9=WGE+vamSTtdtuPEL}< z4j>@!t?B`_U7f?{0b{lCP2(4QRR0O9>R%Y#|Ci;!zQ+6C)t%~I-gg@37^m|-hlR?v^1h667c ztgkKwZmpD6Dpe}AOlwa0Sv|YL670^mI%N&BGMEfRJLQia=%fg~o!$lMC(cKs-8O7h zQ>>3sV1*3R4+@y6)?(3CRyfRh795t(4rQm5q5tf3z_MyJrb~VHtTS0H7l<(bgQsY` z;O>GW-eVka$w9rxJku~LZ23)ptgS6(6s9L9rwg2&NEk*bn>CDJA`vXl%#^+F9Y(2= zVk(+trp#KUmeLFJ*p*5u5mx1VzN~1a^BuQMnGM9m4%4pH?DVVUzb_PHZFCl4x2uJj zsi~=%f_P%3`Knf}*_CRgT!LCk`8&N$rv({Pjb!A-smH`xO>`LGJAFDSnnfSY_lxXCLQ;3mvA zQ?n&-!VZM}s#0s_BhA~H8(y^#Pl-lbuOPaScEBfd274X8rO^>tm zH**%t2!*2IT48#$kS}<=rN5cC1mi3o-_qWmi1_?LkGlwp&gU*Jg1QfL$hh%;AvP0p z*v$1e|K#7l{ZHwO><`*+x8G#H&VIA~cfk{4IxRKk5~^yY0?V(ldi{Lv!@5&?O4sqY zk5H8JawthtwbSO4WkByxPiThoW%XTkA2xr^2pKOmE;U{UQ@YgXI%zi4?;{p0vpskj zwMwz%@gVP8MKo0!nmSV!WLdLQud-{xa$q_&hwB7`%t9j?5)TQxDi^D8X*rdXZF+7u zZ)d)!?2Izf%E-W%61H8cmSw^l%L=RxDQ}RKZDj^>v2Oxq>zdaG&)WHU7Pa^0h!jcwHh&tl;=8O4~~BMnAn64wZ&OeG?A# zX7ru+!J*y*hk73zs(-oabkN@WuVuIvCSV)i%fSo`pGZ*IhAQWb8q;MFjKl@q?_^&z z8VDE56#38W9T`e<+$J7T})4s*F5giWfTj=v6-SBiG{o%65Jx*%|cv zSURe`1z{Yo^O;lDczj0Y7&dO!;499Sf7?GkapY(JcnPy+SK1%7-(vp}OynK*-?U`@ z`QN^C`}c16`j@}__3!=Xe}3;Z_Mh5YdH<>Xm-gTC{!2S>Rdh#GSBi`%(MI!-`&P*jrXuZ>T8WZFv2R2 za`Ibsv-*mj&Zs1Cf=7D=Nf15J{R?5d8t^@XvpI&;zAw+ z@V?curLIy^Z<*d;)~To>fu#(dEi~)1E6irbvI@p*lbu!hZ0ojd3&w!{fbcFeKhu4s z0yQnYYw0s(d=5_N0wz|ZfGkMIGQVK zE?LgaEKH@TK(#PCJ6oux&-5Y9Q1?{GSGGbdlokrBTG(ujg(!>F%k*Y=XP{6Hg+k>* zZfIyIM_+hoj(OSa3#dwVtkTjFr^oV=@5XjUL0d7k45~Z7pZTwTBOD0B!h@Ubv+dm= zW!(3>(%92Xs=TtjO`(g!n(2sNYr>{=DaGmjS-nO5&N$8Z2jgMmi1B6P3jZhG8ee_c zCpUso+3TG9pU?d-?Kj%nxHB@BN!##tQ4e9XnICSp7ujJxFSkDTVC{#Qi|vnq?pvsM zmf<^aU2l&N5vT!+))z6F&b61KH~*9Rg?hsHtuevpQuRHx&HD4T{d^TRl#5_n)W?^}*`CbOMdotBaM5X2^R=3KThKJnLMh{@|fv}NV1{wQNr z&?$Xs|Fb{(kNr_Aa7k7joGBP-85b4AMlqYwyhu!GX(^hL%FW7@dS_!IT8y2^Z+`pXUK zE5@5Ywn_cQ=rcZ`8-2a%$}n?V4UMGCV4^E?eamjmMhxSuGYn()!0ycTj{Zxn2cC%? zt`r}*0uVNVut{sM0M!7ml*}CFKfsWm`sG}GI@ch<@R=yJ0@)<>w$^ZUkgMfB?IVj9 zcO}piCPsjM%Oj6GMe2Xp|Mb&iw!LOemp_G~ke%88zyq1IS(upkLfQx`ekS=-}dW0Nz}u=_X~M`h*8 z{v}H?Nef*gpWh+&0EN5SpIo%KGm#iPu=gokE7k9P@3#NGBa;lG{P^jzxgFciqA6B5 z^o1{`%{(poq8_KS>7&d(Iu8gfp>Ip*TYRm`?SJsjpB>tJU?#X^ZOS)$SIJFJvvf;L=1~PAqP?oxhFZb+`rVuCi)1y*?dH3z3%9Co8*Rom z;M_~Mz zi(uSrW@pbS)tPqO8leJoVduFFIZXajd()8);jQaQK)88{% zA7|Xs^es)_LKwGx&^2VGx_#9wK9oda5sMwhQTmsK4$Kkl^Pae+WOXCN!+2KiUm@k!URyfhwGP{5O{zGHAQlNX~si&U0 z_N+w6Uc9(FRyZ|Xs|i8}I9`%+vb zAGW^qgCG3hpDwmP4mV!SdmZJR|4;Cm|J;pn`tWyNNAZ%s=Cn=rIXGZ0V2Sm2p~Jk~ zUds(u+L!sZee;{&++hEMo{^slWJjk|q9Y301p;X!Ti7l-{pZ?jG1N=>9C>H1uEvOE zHW{a?S@nD*8?|twE)~qtVk0?5i z8G^&=>(+ZVZru2Rt?Gv)f{0~vgYjK1ya8S1)wo_hY5cRk(G|vGwMX7Yw??|FP#UZ> z#?ZD{P_(L871Wq2*wAqc{I#<)9qsIl+9(}Z$KlRQ2v12()P`!RyK06(92Cu1chrWT z+5u#@Wv6>ZVerS}LCf+-!j?Z60r8OKmNjtI*&I`|vxjD~+3al5F>@keTo3V{HZ?vp zC6XM`dm2-~gCl zM}lpUaBOtA(9+)@7QJFW$m=|IbsRj%RVpls8uZsJFgebGsnkkg_iTw`9eej4n6Nr~ z+O5L)-pqvtjzRGbMg#WL$Rw)Ak|o`!#UXf0v}4hdzTPmV!Z42=D;frMX$}pQt#Dh< z;>B%+g9i`hxq)GqF`xtW!<67`YO%TgBU|)RAwcMFG|#Ivs#{>&K0Ik?dxifhoY@rKD*`%t~&M9zSh9* z#}Cc2-fr)TQ(+m|<}Iws{Z0a{~5UuAN5Xz0k$(2Q+QG6ZGxdYl)*axPz#YGP@@POk8Yvh^k7 zea0J%WojR~>Gl508H01TZd6J2&(^2j_O`cu_+9Et>No135yQTEzka`o0=_JjjI{r_ z@kMyjF5_dyTm9a^tmFUq4nmthZfw;e~Iz9MkQ;)ZJ<~ zdh9vIAM5vGbqMq9I`b2s_{2taqqaos-ye%88As5eA98gv-*<9UakP(HCpMmYl@Egi+OMzF5ybsuD^AZspmSEaxfs=<`P-0ZLwujUGNcGIZeChaTNO zbZ}2lJtCC+nf1Q*7V*x9qQOW!8h~F< z6>HK!tcyU%QvN*CFM}`*Onl2{PuR8@h_)r7kytc_telvP&&=j$3qX*8oh{^?{1`9( zdny|0OZyG-p`6^PW$;^aqa+f_OH4UX1^+rd3?4A2vGGzEtv0rxdj`ijZsE+o*y@Mf09+o^x-)W<= z)lI&4Y}oL(TcFKDC@*im6Qis%F?hd(IiLmY~fU z)urS0GZJ>|nP;A{Vle@`?vD0@2ix0CGm|nacbKLdVuAg_|^O@C-??C8;{QUy;&uw5AcqIguC zW5Jf*WlMVa4vLCf%=h&z?_1Itvy9n;Pd)Y2!5M!%Y*tGvOQo5S5x67lu~s#EPe1+i zRlV_WB3e2kO#a}}vB_L!y@{^0`_V@qedLkHo_PA{r}qtIS&uygJ;j0{0*`^P-~_0p z0^#M$m-Ve&wGz7Q3Lsu4jvhHWHnA#On4O*&AFGrrgf@aScGE_7@Tb#0}#s%{=nhV|({J`S|YLyPqA+`dWMXPe1Er zr><(pu>l(R<57RD+}+(u=x%akXz1X9Bcrn=lThl?r9GMWXTSNtv%k9gu3y}7>-WF* zg)1*XuD#R#V}zx=-)w)p_4@DL3y6RBn;+kD=fe*_{L?QbFT+##M*GzW$2V&4p?DDg z%>ID=>EMnVzxUIh{`C7dy%jghpK+6oh|4qe`xW+Ux!J|2@bcb_nEg27Q{o$cq~C9W z68=y0%5Qx8KYsGFpZ)Ad|9VYwqx~@({{g}?m6#nZmy0>LOcu^NGo4RCAfKnoUb@8} zPY|yPA|{33`>*l|H%UdreaS@kFv>$Z2JZoHP@#cZEwg!(azZnjavb`B;vhKwiN8i1 zHu@zV9E?p~DDC3Uk&k?tz71;Jzj4cJFMGp>FGD%|8c2Q=o&HtEU!eGZ5;+-B{}%k~ zKl-DsAO45Gd;9yiVuf)v2EiANFBl&*HX3--u`Ir({(%t5BgAn33?cjt+*{kRF|NiW zPhpSz1(wG9j0=q@_x>tY%7@gI>OT@=N{xG;WiVUiG;&mV$A5XncF<3ekJMYXdd z$~m2@UUqheG!mA9DhwIUg5KSD`nMZ^Xg@!-mrt?)IN2S_lmxn^Z5ak>t9%GkjKMr_NUTgws zrZ&rhRj#naZxsdNV4d@l4kX&~`Y}W$BNA*~wtDT_wJX}J02CXF#9A?$kRo&-H#t$x z;}(UemaqnWNC(+ts7mRU{L!ItI4^_FD_^+E$3nNk)MDDKT4Pof&01ubWd#W+ z)7N0QMOfVU;l94UMds0?(<+Epf!ENHLT(xsH$y0^AS^Cd3$%9kKC%1J-#zl|D6Ty5 z2}eV-d-qQm!s0>z3;n+(;xFaPjC^5gB2P(3A__dnDi)_QnfMXd?)b#fNp7DnRkwu- z)7VFBmp_Y_30Fx18(C6D{7CwIcD4-S1Of(#la9m2Eg2YTc)(0ipR;P$OxJ??CKBme zNTeSj(eFkgeG`ea1Bvt{*FUq~9}=gPa1Olwix&5GclCCMP250uxu zP+!;!J%EI8G5BJg-QDr-?v6N)^Wuz#$N-pbrq&ZC;$?-}+uD(*T|GTLt$}K3dKiP_ z=*TbxCHKHNTz^3yh9MztiPg7vhr7Gmqu@k-ax}AkX1WBGgN)fyq4@ZdPd!ICJ=^-Gx$dW_l89dTKhGF9?g{Rl&T?PaNLYvTxtu5!COnD3mQ>JF^~y zlgMxZTpt}ed|>a>k3GEmi6@?TYH-?)E(1N+EMMH+7UmXWLA!`7Kls_x(;hQhYK4j6 zLx&CxPY~6Ncl2b=ei?X!KMvNSH5Tbt6Z>?#3G0EoicTXN=Jmg%y5f37z*`ACeb5@X z=?6c+bowar;LXU44Z+kekHDo4|MHKBN4?5kj20vBZu<|2g?|V+bCG?j-NomkK=Q`N zhld}(5h1YydQ4g9ft6yaube9=s8*5ppu*_?M6=KX#fPRUrHVTD94hV#qi=qBduTmG z3!B$Nr5miz^{KymE1uGWs%Cu3$QYl-t@sgYhh9z;>lB!BKo!J?ude3(UuuH%n^%*N z^KK)lY~RPuZTa}+c$=qox z3PB&3uC;?$A6QQJ7+H2BjMVxvc{Id^D%D!RAD9@MoGr{`$(GTHC?S|%esk8#kq3n|&BQvGIc%DT#k`;B? zNUh?Z_nJ67l4F}lnjH-Lz#0=PtV&EgvmUe+WGv23kL^1;JUTvAMyCUP4ntu_My6-r zwnvYSVa-cV$~0 zcWJg zCao|MQpA7{LrC^3Kh!yB)_2F4SYT-Z4ZR`Nag@s zhY(?d~QMw)#!mrrhoPhJV1{0f@;zr!b!@X3$CC*KdB{0N%+`i{iZ z6fQZb5pB?xshBXeL~QE#oN*pwT#tuCB2{3rzY+*k&^L?#+_PJ#?>Sguof9fp;RA>I z`~Cj>%p}aVXpZ++h8}w8p`mL3@tNOT-bW_K`mSuYw|8)G@W|-cbfvf$q?jFk3eEla z>~1c%Q$3lDL@hsNeeG+v-a2pzah0vOw&ne1Sl1TgYw0ay1OADA|7|SuwbWN!KKAA0 z8yDnc$vQ9Va`>73{z&r1+hJ+LkKcZy_8WaU;PSF`cM~&%n_->^#$kZ^DvWdHr7uA- znak1~-nB3@+iJYiSV?NmPMo@@8E-c}tlwK%I`I?gyF^jWGv2}b96aDB!M>8Jknuy3 z#d0Bo_oPtRz7Xds%>*SItzt6(!I~AwiGowi2aeCH=CUe=GD&o6M<5IzgizCx(IZHH zf^4ZZmd}HE2zS`3?aSAMfZA&L<1?bUd`M03{9te(LFQcCA3_JQ!d998*FF~)THZM^ zfrlbYfG6QMq7%MW!Vd}G#PRvgT(+ak%$HA)v-~!H#u?MoU2zD`YG3L*qdIi&z4s2+ z&Nx0Vna4_!#_a3>_HGG@5Q2>eS2xXTpNEs2(H~G}^!wD=Xk6#QNlt~6oC+sd1t(d9 z4<#NG*)L*O212J_v+fK?)#owU9*5o z@_cpsu6>vPdj4nmo%3I1ss~G@e~HKBt zP->Zj%H7j?dGjgD`0LbFYEJD#W4gxpxp9;L@*hS>+ej?o0uh33*OQqyYc-7m3LRFR z=~7UxS8SVBAh=#c6(R_$Iid;=(JZu6C`2N~Ks)o$W{e1MX&hRdEUVmRg*(!bAtMsW z5=DM~J{~VRo!P*uRUQUjZWOFA5~?cfk4AWLQwc=YB4}4bQ%jA&(rlw_x){c-bg__b zS|WE)?t*T*j&lD)1dvN9_X{ca-IV()py26t%7vFSIU2u|IBPeNLmX6d7+6)htE=58 z&rYA=y&vKb7TrOpmXioAhW_*@)EoOV=lxn%{SE$yiB=bs;KINj)U zLLKS8^;@@XJAM7y^d;wQS{1jfc<-k3F3AYF=P7xQzeC0o7Rz*Q`KRxG_bc}dT#3ry zI_m5)sOkUk-iN*KTJL@KO?Us(-8aGBfpsSgu$kp`u>JJ*=1=YNCF#4(8*aGa4}mv- zU5zoMY(RM4>o0%Cu`)U^1^C#gS8%5O$*48IK7(s0nv2wNJ<$S2Ct}yIcRguV{+(Xdw=xG} zfTxAwL@N(CzNrtzOU*|mv}UV%$(&zPbpmw*R$N@v)|rX+i?r3&wtl@fZ~pYhc6jgc zwKjWqV=4HBS}+oR5%0BtDz_1TaI926AiG-SAiJ~Tk;_`q2n9(IfENkFi-c*(EhGi= zO>o^*!@~orpoh%^eg4=uY+nAb5=h0c{05+qY+N=$)lVO2&Uo5F&YYYBE1k~e`1Z;1 z6Zh|A%1i;!(AZ_hvLFB&t6B{NxGy5zxD`Lq*LSuesr;(g_aYKOi_*DtZoq6?P%G!+ z05o9E0(L?(!cX128%lLEt8*d>{v5Idt3ed}YSnRMCEmSlFD}Z;VJmv-FNuQR@>>x< zPw~X*><_XASp8W^fKSM0Pl{gC;dIQ@X99^iHJ2a`SM%S2g9`_HLOp?rZq+?un)@gB z@1}6pNC7DufJ2rTed^_YA^kuKN-Ykm#S`u{m3js47!xX5))U?-F(+26Ll?P9bmG>R zV!_yQZ8_kSRhfUZuGSW6wGb+999L}Bq;fY4^|Ja+0xWNK=9X4VN{9xhifDo|`jd3k!|1uN3g)6^F;2mdwcsr(IcF==jR2)sV}>90NV=%c@wD67)gqyKXk%GEb{ zms$JnyYJ3j(AvNBK4jhc+57I;3FUQ$vD}me`|J%N4(>IPg}z$!z|;w*dZcUilpOxe&n3>x}EsZOZamwMYfZ@(`d1 z8y%9o)c}(WDcQWwt2K9G7$o?Au^s5G{?p!<}0W*EvtBJ zOk+GG+QME(SZ!#2R+OxUaWmzKVJ6m4RiND14^#j(klx+K9GXa=AD#i3cmLMsgWn&{~2?d|SNa%e9dJM#1s zSfrmCL!zF^%^f{@c;p!7k@@-j%uyZA{zm7SP@{ zDuuGyV5nW1n>{o#Q&^EyTQ<7Y(6+TufR>GJU6Z!`>i7lFIG@VZXTr(32cRs}n^@PW|#xjizThoT_P42&Jh4T5-4}rrTluh=kCIvi+3i_1LT!a?N=G5>7 zP(+_53M|e6HOm@0)w>ev=+nn~F`RNz+`;xmcK`zyT3K5T`$6L+bi7bZssXcXi(-*5t;}pbJfbc)*#huK!{Q ze9oamwaMpZk-O3N&d(Jvt1KTIIW%%C8yh*oxCTT#Hmi!IMmAC{jZW2KsZ?xvYP7fK z)R)WqK^}^d)UtX;quo(-m@l&mR%a{)f~76qmWtJ4U1wynvA(scSX@#Ep6jFjr5s{7aUVV*IX(=V+skxf>zwN;aE@;=1!QKao&Aii&6|8`W2S7CA5$dz7nh8JLM zJqsAwX0{i4GL7^fv%HZbCkx4=$68jFMAlK%G>?Vj%=WF8a>5Ekg8@tiiRHZYT{$@G zirs(==YSkmjqR6Ti%|V@V+Y&Yjqh-DoAD9jChWK0WBCF{@5R;bVwP*sA?9yXrC}2t z8y`gE$`V6YZ2;a7u2jJ2v@;=iXD4tjD~N4HJSS0G3w;O#u{eYc8#B24T)js%_S@C6 z3I+hs0ye4)Hu=Lr5)FSibao`-Ju@-!l>D9!l}h#c!~_@%NK^^+S`H2M%;G{(I6<$z zFg*!_I618cjggToCMJsrGZc_>izvUcK(x^^fk>%W^x2_M<=E8J_&8vpJX)TTD3{Bk z>nef;6D%UXq6m;+&KIZo%=8jzHTGk8ps^036;f67Y4Fc=c0MOPboSAs5p;FuohOYX zTq>o&GNpi?!Nf{?L5A+gBtat~fv1xR0Y!GSk=D-6JytG<52k9|(#DIM&;>dl@ zUjuWgDhgOHxxzE%w?9mX@R<3OF?nbgDd|HPQnf+jtgCm?lpY2Spxjn0YB+_!07KQv zqk)-pbwE{Hr_Ql1W}2RFo#t&tKz!!E@j2QHp%xdyU;P!-^{r5g%itU@hjV-*)Z$hB zrfN@-hLtj-)Pzc)Yfa2A6&ZjMF)aTLwbZc7*#ylAtD+Saba^~fHQR1ML`;NDRl^zB zw{)m@Ht6*A#bauru5V2SV&ZI^h~RWmn4g&$89~HeT*RAvR;!^`qw&8AjD+;wUiMdc z?~V-J@z8VA(<384|Hdcy|9h|*oab%um-9G&PjqNxdV1oKM@FXi?3?i=1&oXYgaXq* zw2^8J)}dA(451XKSy1~x!%=<@uU-Yok~Tq8l_tSYnue&7`I(oaDSNl^B|!UMGTyBo zv#vV*oa?T}!SN}suQL9dahLJ0tp5!k+|$)3j4h|_rjWww43PXLA}BDCY6#A!E5kQ< z;|cm91Vt$BK&NRV2a4Y+CP-8+_={A{TD`g#B!`$NvougBQ{6T*oqi~Lxl{hwtkj1H zw-wBoOiULHGP@Y2EyJ0f4rpOmXcSo&H%|-|D0UQhIGA+#i3gmxw!k1%1TG^gDi|6s z5B^|eBtKtJRgK3;wWb8OCXv%Xu-VpNh<9SEK>t2O60HJLF5%@Q@$CI%N#V5;hU=4Z zt%iiIEE2RRrJOb0^+Nki7CA0pwplP~Kz=)vnM-75`!)2_HMB#>AAS%4+2(dFX>v5!}1B<`ao|O&|>&;wU2C0`poWvit== z_$CtNtW{>G&15_?tdS#gX>Al-9pVU=skBPtnUUqsrbi*mn*L@n*BBOk7#3=*BNRWa zS3UA-6e-`q`Ky9IyX;@)tuz0wyaXxRr*eadHvfK#tM$Y+>Sp5|>XXKs;j)S}peNe4|qa4&Bj;c1k=OJ7s(|gbhFjZbNdmKeN)dKSge$rg9 z7KUTQm&dvW-|x4QXd$Nx%SG9Ot=q3H271@esm+m!ps(Y>nq3=iwvLUI>;@@WsFcj0 zcseD!meT8|b#zO~w#`$rZlJ0HI6*P61T>epcKn?GX&sjWB@Cs+vz2v7Gqg~$CtcF& znVkY$F`ju$rir}3e(ct7eNA7AH1%4LE3frlZ|Sdn%O@^3p^j69VRg|?z?9!KF1q5i z#(l

Iy*Lw*Zi=P4XmHCSz}E8R5fo>IsU?ex}r>&=I}@9c!QZ?nI%IDG#X zFZccierz|^k=^Y7#{A;_enIbV7WB1hkMWox;SfwU$N=~-{uUju+kWZzD3UvG78|ItrB`_muYz7sswn+G5g+5Kq2nQ?FzR-zHs z&dyzoazA73@86AE_oYObc-DCDyLPDW8t;1VE{-oTE?p^hzjf2fY3X_}MHyA<31np~ zRkcEmVUfm@U{rzf`pvu9)O82MES;XtPsiia`9?Y42sZNNMxG+|+c<5R_Rf=5!oo{J zOyd4hsrh$EJNH{WcPz{5;F*j&uQ4A~-@X)F zn*+UN5=n;qLHJuVnKh6scVc`S;bDCGK_r)3&wy8ajZqQy##c05S2SdSF>_`KPb@;I++zazk}I{4{8*(I+0h_uhw}80GNk zhrjxn&wS<&eDU_}3`v(jJ3IZQAAf~QfK~np+q1p?m8;u0dwuinejfbl!w)@hFYo&4 z^T=$^|CHC>`@lo3v(NmAf8%F(L}K6c`^)JZH(x7=&CC7QzxcnG^B+F!(~MWxc8O3F zfFrO$5{V+j5p9KL;ve8jMyIdUa4;`%MB|{DaDm`CoSK6G*y&^tQaK69iQL{mJI@u6 z&0OHD`TybI^;h=y_n&hu^A>+aeIUQJ$5AR01$$QmTLf5}}*Ve6DcfI-g-OUT?uF!{D z*Z#Em9kw@Mc>0|2U4Qu;$Ib7Y4^ZeG{`X$|-*@mIQsPt0R2}x*4-7-^MQRnjWR@nH zZRa2W4&U$85_MltJz44YkPFOWI4=O=V?37!GJ@P{oe33?ab9qVG*SM4{IoGFDh>+W ze-BWi`k;J}8zEM71pmwQ12M64k!#uQa+kpF_`fj!@;GPQ!X<7&#^MGMtmF-T6aix2 z2)vXzr^LJ&s@CaMLeU1<`{vWE91Mnp&T&w$5`m{_2hCo{1px4zLa>g ziG+jlf$(Dft^R={weN(YoV+ZGBb#XSo2Stb&nB9Fcj&LUA>y1k1?iR0^gnu;M`NXB zBRf?&I^O^2nRFEC24Vr=$nQ4^E>HxVB2!B+MHq0UX#ID&l{iRpdwjNIc6<*?2e%mqAO&}I1ts_nN-#yqNgRNGbl}ry zpvEotHe3I#U>P`-Bk7o#)&nBM;#3AvDn|ui*>AD6G7TogYTRRBdlE1c}f_u(mdXxLC$I@SuNXyc}NZ zTSzYdp#G8d9`ywvSl{w7rESJe3~XtH=y4pziRV~(T{6~iS-j=ZGi^@FAM~Hj)>QS3KIC0zw%maSmEwes(Vv^ z|0aElYgW-=0S{H>^3W}jHXu*PEMNkRg%!T^Y+C<1>h2=yZVTz&LhIkq-;_AdBHb#S z#sBuqaCl;(xn^-o%BnD$7G#=d)uyqrBF2_qx`IEjQgxFu)YDIf2z5vt%(0yt;zi;I8XUbj21DDDhZppVnLFa%LbA#JC@;h zQ~F)=7g_DDe&xjK33l#k^wuDM8)kzDaUEH(;Mt$EI;W;OL7#k0KDyVPU0XYtcFTS{0@omDJoU}pjjwy(Y+Tof4qYXdU~?;8 zGM_Z>Fz=8v>kZZ$26kAJ**!8=n-XSYl0i~H#sFV<98OP0K22x3it&m5DsUEPi+K}Y zFzCRxw~^6xKskf1Vf+Mea`T=&cn_M5rWUS;T=kHv&E)EIawS^0ZgSN_kH9*m+?<&B zpU#O`r`_)4+`kvRrAXx1G3`cYXCslzqp{!ny_j}`^~m^_txF~*y1RWTPkK9f+|f8< z96EGaJFCw-RmJH!w&OoyQM?@jIvicICLW)Qs%M`a;vd#IuP|PS z#V8=R($!V1PFBiKJyq67pI%R2y(o}M9gJ5hZEZW^!JWW)?^IyX%5&kK)%Cf(O?mgl z^_+Q8Jr7W}p7)Czi8!@b>Q~QoQqdZ3L(24;8{OHmn?6`uE3&x`5ZWYQT9-DiCFj?A zUzd8et`+}asaxZ_*0t8PQsdUOt$LQ$3F|`R?~>Xyw05XYVQ_#MWP^>yH>CEFai#kA zihC0`*`|699tq2n%b02yuV~&wEt8~XEk~$jKSGXD&^dz~$#NooidAsKqgtd(ZLKk! ztsIhm^HZO{afkPp1ESy9yBpyCH`TJS+2_IcT1L4N2ktXtD8k2BGnO&IZj)u7saMKz zE8pBo%i6{_&*ht~!RR=9-=H4HmHRn0E&=yhM%57sL%}jCW=d#~ zEVjz|xgIcamHT*m1K&D^o6;h_`l$C%eYu8i#~$Ti6NpFg+CijNbrXv~dv0k#jE6qo zUC)mmJ$iKXm=56+DWMfWn~V7GR6(ul_66Gx5eY3K-VD|J?C9v&#H=20!4De5NQ0q< z1&pWd+E9C6Uw=Q=ywzP~x_4F2`t^ABb7wxU!a-b{2>x<&vn{tYX4|QQi>ZT6)WK%z zU^8{FnL0=o#}6PWAH}<6GOwfvD|#RiZGfwA+j};gciwsZ37p1ta;m+Z5B7qKvujh5 zi3y!tXPRxPL`0QySV4paPqn zErBvghC6!!hqQyUNJlY@Q!9F)t!B(kP0udO&w_4QUR6(D7stvh z5Mn?JwB>j=_Z@upr$77ovqz^ED5qaM{^S1`Y4*Y2jN5~8{#Cd=#9J6X+4y{n+Yd^y zpLk@{YlM1r`-7|jR-4r~l?iDu4YzE}Mg60Rl(9Caez=o>4bGtY;7&wtVuR0%jD_zt z97cesW_Mlh@c!Dk1BB|IHml!EsYSipIG-2{hm4C>x)y4FZB^s#Nye~cSQo1n7%y?0 zR14ZFx|tNmr7^$Xs;VDELM~DI9yYzR(LYhbi>F(au>o;r& zg)+T;8@6mAVvJxKd6{(u-14Vb-{bA^K8*3!*^7XJnN=#mdbM$ude}G%K6sR+Z1iFx z#{`X%OeN(1A{${gZ5kl;ji4Syk-<2bsCy152g20W@CK0h0QyES)q^JJDsjlE4Z3v+ z2TPaFmn-@ciBNfoR4-RT!OXemp1Ua(;%#^);7TgUZL!j=St*^&uPB{xh>{Ub&oV<+ zDwByK_ZguhJB^FyfrG9!p(e*w{y=a0qV=ria!4 zhT>hZGMIv5i7|ZEqVALI%u3N?cTs!Sxq7c(3%?Gh8sXgog3qi2*#XB_|1Uwivcd`Scs zz7(s~BumqQz%)A|C@rw-j7B?KUuyMQq!)5<0>^C9@5q4i!@r%hX^vmVo6=HYNfrzTvh3O$9KaW~$XplKv)?;^cI9ucSM5YP!4m*pSnhX)yGr zNq421bmqsj)3dP$t(K{cA7*!XJBXO@L1XW~OCoPn@3|Bw^8EnBB5Fy!oxY2e43oYj ze7XQHWP$sy;83WYHnlKOak{lrpRT(L3usW%PGPQ+=+({(voGWqv1>Wq&cdASaAmwS zez;aU+)Vj8N_-t9zJ(HhIVoR9iHp5l?78cx_c%u93O*dE#YMRBP(sINJqzWM<1G@? zWodCv#}^1?tSU8!KLFF*W+KFo9O_b+}?pr{4%OzH~i5@1Gj(khu`?pZM#9> zy%$@+YqYsosSJvnKylFU%KEiLIMAp;)*2?pr+-pI>O;sWqfpsSBF}$N$Ho7(R$D40 zA}}7*S-ciR4~jzBAqs%7lJTgGb7TV>5iufbM4yZU4JL{_L4-r7n~McRw0MkhBowcg z_@=4jLBcjG95jn~{-P$;@tK7hbhEZVh;M#NIvyx7X@OG;psHqCi;9jKg(Dvk_9V41zpnyZaFjR#oY4MzJyqZ^3hAp*TTr1qH~e(QT)jfMSDHNyC|#&{Kx zz5m46Pvk`0fvbeu_A1NEONHeHVpx@y^Qi1}#%8*11{_}(Z-ga|1AGBep3bz@y|N)xgoTa;30v&7~U+ANgPr_DfLhEz>xC2lA~_(2Z&SLC=A zkzhOT*iK8hl9q5yzrk^kHNa}K0ve=hr}0^UUsnR+;c4 zu!`+TLlChzRrdg{7vw+UUM539^xAeb-rI={$EfgRG-OmkxCIEB9CmT7Vna9}bPz$^ z^TZQRAHr*c>dzfLc<|uJBw?jYD>rrY=_hp0Qo(bA!kv1h{QO)F>1U=eHGc5W;qh7B zbM)xMGy*)p*0IApnVX&mR9l^x0w=F~ax;X}k|(PRK$Cp$TVK}M?4?sMrWo_@A{C0JOTI(l>@kcR#z8%_Ns2LP2 zH|qE@Y_e^~-1%iuGLRa(OmH|5h?NTFEuqc%T9MveA)Ej=f`Q6LQIA$Uf?(hBYR2a;8DX5!e??CjhOZ8{H@w>|{OJVSSckqO2~LPwj+jVZUghH(*Fniyvc zlUSK`-QLv}62P|@s7*UYSB@n+bbBotDQ@sirDu|sg*@#?y+Ozvlt_Y(;Hf< z$1)j2z$FbjWc-;!hn{)zNgY}nTFjvZKW(i>Y%)xHw-@u;Y1W$zfWb|AV;GlV?RpTj zz<%Q&jPDH?RY20g5GFUKkFilC5@pp9!>$$3p?F+Eny0WT*r4{rTnOe670~)^Yh(SV zpRVm01CpFIre>I&Q7pig5!kr#^v!F#12dCT^J>cP$1=V!Q!u$utS8178FeJOv~c~( z0rRPq&CxPzfVII|*8&Eu`cBBFP?Q&Q^}n7^Y3b9pl7j7|;3fLBaQAxTNeL3u6;%vX zMNu&2&58u07=IqQ^6-4VC<>{`)_B$IZghvlzplM!AKYN;FzDkTgFSwCd37XvWXt9#-_1C7{BxEUG#R z%!n+irp8GP*jHhl+C~4BN&)cJ$>P+w=(OO8a;OmTu%gel1-xWuN2)D>P@jp!BhFB= zxIB&WK!Tg7JY(uiUX+EUDF}fe@_?pEiFPk#TwqINf^^bQ7W$6bl%I)+_bu*jZ)ci*7;leP8|XMYxN~aSkdkD z(G!?_28!Q|>)u6+*+`2?9Gxo)R>SlNK6K#8C!c&lhiBjlip0;Wma}$wadvb{CpyP} z{xH9vJbk!O3*uvz)`_!b%TY>)Sxl6b+_3HC=ZSL1*DMYWCPNDE4A;afWc`RNc4+vImX<6mUf+%E?s_*L7>U(jX_uVO8G*>$W#x zo^jr#*IaYWyLElu`~tlth11US)3L*!+nSZ*g-cORvYdxgh|(!pk*( z0ybnlA^HCLXN3W5{rny(z0~iS(#NHji*6GjAjhJU2(bFSTr@&3MaE3r$k&a|hI)91 zchLa%-LQj!G*l#JqX9IKJIaJXCwN6k?lY%It10A782m z>5x{PK*B4>CdWDf%%2R)pE#`|_XHpK!4Dqz;@x+D@d}`1Z)5!(RJuNlvdtw}bDj~w zt@=A$D^2HZtzWkE&J+eUR{ zb{3%>F`>itW)YKxMfZ!{y7~6~Nbw&+S^I9bpk*lJd@UgJ+WRlO6zc{4JuvWj8x&)$ z=|dqynNwXXD?4sG%i{D}U1k6kPC(iy#SD)QhKYs>ug=}@_Qd!sl?Lp>9B#ZgV)#GX zk?_V;fP>Q*M77|z=axQRbVa{ZA0H(khQ=3#u%A|arc*TR!Sz9H4&V3r9o~mmwDD~W zmAj2J^HPi(XAMZKfC1Cj#v?>JJP}Qp_%U>pW;x!FK)+u{eZcY3fr_?P{k(j&wk8f7 zSX?RpR+?I~C`ahOr`QPcu};j4fx^74Eo6swZRJX7(5_jq)qm>!!Ahc*QL61Ge)GdH zrJGg~g=t=!cBL_7RhfJR+d?WtYO2$T4ZLZwRKznPoFlEHUk{#1nidZnSV>bF>XD{R zv}W~AlP2+(5gotS?t{xctCnQdL2G`}lT`=JrKymtI*1L!lod(vkFaK4>}@u`@NKMF z?{B8nnFG+us;@BC6VmK|7#|^6)JHH`9c`xDadF*X)xZlXal(|u^nep<3o2F}EE=Ur zS#_{psu;5BU^_r74MPG;~ z$8mk36Ems_yFLeJ)fvoVFVl|O$R8>MJGE1qsft$__L*y~?3cefv=jc~>XiyuP{5@d z`f}qs;DSOsmSH0P%+JxFie*(&2NqN$)G*LQ#u^^DbrgaDI))L{gQ|pTUONja-02pn zBR3uO1bpqdrKiRV!Kf(V%~V!#AiBd=9(u^bs+Bynbe`fNN8_KWrz$*ena6&1jQI(i z(mXcRwh#y9SbwZi(&o^8_li#K_tD*~v}~}%aoi!8J(Lil_b}1l+l;puZnIMCWdQQa zLyjG43lP~})8EKvVF;&CGiZh}6A%Xv5dILTN8r>2G>mSN4xT`Q*TvI zGvm(RF?}sZilAj8ii%xidPIF&%xKs#;-(38F@lK z`)|VDg`%lSa`003#b_TwY7EBMml+!(;Md-`|x^iY_PImK3#IM>5V(5!cyn!4k{B2aXgJ0Rx4HPjbD)UiUU9cT2cLq2&f< zz4FJ_pm;m@pOw+Y|Iwvn&v-*dWhbgAYHOvwB>NOVa(?XKlnE4Lej%SH7!P5Zndn8H zlSc_v3oD75!fy}sN!ql<_Wxx*pO*bPMr%sYnp*bjAgu}eb<-$~?Yg|$uVZZ2>q#p$ z*fL-@PnuS2vkmi)R3cVvKSRFRdm=|vjABvmZM|V(p_%fQuK8lVeGLWkVqLTCnasME zO?UyAjsPJLh+ZvxHfnVklI4*pv|mi|M^DT;)N-MWD4VgWlg~2UhKV<5wPB8o_OBPL zM20@Sw6tt*Swoc)tQ%=3>kxvXBWW(_Bzj7IJ}2NGiNWyhFWvJ=0a5%BZe1S%MDb3T zjX%Tr=xP>cfMCOw8q>CzcJ?|WifkvLK)%f!MFzvp#VgU9wu2oahLEv|!Dt6?`T>7= zwNLdlO$=5HBrg0@R)P~^*Br_?W+{(XG}D6cL%;y5E3WI1C|t<0JhLETvpf^2 z9h0a}<+_AomS?7?7Xz`Caj_-)^;6I1P|q)s{c52^2WpnyE^xsGv{uXiA7sCPc;sn( zKXwyYL8jwzG_4lt?`;@#t}r@~!C8oee=QR%-HHd^#Ta#DxyrAflO@37+%)rDOpc69 z=jJ4E9^?IsB*0qHl4zZ{Jsvqc30yJ%A_=hf6bZ1hA^{qwpS%0&tFON7l`p&O;_Vk* zbWz`LApufBEg^15Eg`|l>eC02)kFe(v96{iJ<5|}Hud*m_LxmYob>m{-y&4z+BSl&JsC(KD=ltjE)FvOTJ0lW6IAnwDb8gjkE47GsW2p>h8;okNS4 z5p;miwiX{C^RD^4Oeta_4OX?2G3MvZ&Mxg78#R-w;02um8ihzj9I6 zs@O}1okJxr9aM}k- zxN&1go479t%pxiJvb)r0nU?)NW<-58HqG zE%OUt(%~h|V}s*neTfcC+O$ib_$68}1+A1`2*orCN>HsdZ8<`C_tkLH0$P8^kNw`# zlFxr7hOd3K86cWMc%+6T{p6t;Z57JtFf!t6jeq#~4i8R(&sl`cJju1p{UNTPRNEG?C=w`clSO(SV+;HCO{xXlV`7)Qqn+2==a4 z!QMUu_O|7TxU4!jH@2W=N9XD~SPlfMvZ6J(&U*`P5^ctpzr7Qt?rlv8+nAZjvJUOW z`ZNI?^9JJ;=j~KKQa^5{dnF+1P_R5VugY_CXe6o;2m&kbZaAvk2*|bNQcYGJOa`k8 zeZHjyF%XcIYvo0is^qy=@6J?ZT`6xFrBGd?AycXyde9oBTGFax%oeQ_UINZoH5#pq zkLp0*`aog2G~`6*mzJi{fdg-+qgXrPbuKiX{W-0|cfJvQ7(e?`1IiDa`U+w+4iJ=1 z)sV8CbVsq++0H;^jardDQ&2joVlmlX)PecAhJ%^GF*{B9)6S-NaItaJ(@sQq!U)q; ziLO9+f|K>bzhum@HQEWpJZY#yMy=T^e7etQ1O;zy{kNUp-;`@bTX-eq@G2<3&_#(t zvZWt{G>A(PJ%p-oB>My|-As&y{xX<-I1F=phRA)eJB1V^YOtlPOsX39q}ahATIoYAr5e~I$Y#_fj;6c_4jI!$rQd!E11Gt zs-0kJ+nLnvdTrv>2|@`A^Zv|Yg&8p_9I8;z@l2A7$3#89s54%5j-kyWT542c%n3)q zP*o@ODLfbhk&w4sSjbQ2%|LO1Q9Ygjo!w#j`I5ZhQiN!ReF?E_C~lU4CAcw}ZL3yw z%q%lM(gF+C_a~wBL&GX8 zNFd6Ex^)yXP1t&WtVP2s$J|oDgpuuq6iwJNyMm0OwnlGOI~_jdhfly z6w|A{QdAU0KziT*ckaDQ)0f}-mH*{4bEnTaXU?2CGiUBXC?UiNo`UEnWn^YO_4t>M z5W-Hw9iB8Rd+vbe{7(r@zL${J+a}GOpSE}GwkSe;UnRu4Z1RHCInU_#GD4JZqJf0$ zxzUC@Coc0rU2C+ua_+p;d6ynO@+{)Th_A~n$yuLS7FkM&YbYTWJ6CVYDZ9J3Xg?wJ zd*o*n{z+cv}<{Z(05R=?^GjQPzZp)fS+9#)ta>?1Dr>&d({#&&dmZ?DRI2 z--q(?g-Ect#bP_+yuZN0lJc8xd%AcbA>mp=Yz`Nf=H_&=^r?i{jUz?z^qx+ zYll|YjcX?Y%HXNF&*?Mq{p5~grX2(46^9fqgrOHuUwn9P_J~p9jIp%XTu@lfslpxm zl$SvFC?lq!4h2S3K;9y}6>=E`n}K{KVtd#kQy-be-^7tFCk7fJ_9A;v5oVf2wA0LK z>5J!Nl1gIJ%VhlCr@>l69^(15P3&Jw1P%hR5FvKvy^uh{L^u&FD$gkfe3V!bPZCTF zWCF?q5UL!+K%^&CamZ1Qduk579JUa95os*8BNjj#X$Ux?P#+qL6?@l$PT(oJftKN| zAObajj)bIc=WX$Lni!veI3rV#<_70U65%J4rSO-Nt?;*#yW!tMo`L@yc?te2gQ`t25GuTY{v)D@b z1*`ym2?HHi8QTcIg53=N7Iqu_?QA>zqwFaBbL<@aiwv-_R@MrCfDM41qXYxyORTU= zOxEz(?qgJHP+s z&%;NLojQHyY<2Cq^B3waUaG%*rQvF0Q}eZ!*0%PJ&aUpB-oE|;6J>%zsZv{5T4}6p zZ0+nF9G#q9T;1F~JiWYqeEqaK|A4@t;E>R;G2s!BQPKJsgE2NPJ|QtFd0fiG)HDd- zNt35coi;Oj_PoV6Enl&6Rc?MkVNp4t02~wXCqhO92p!CG^P>_fQYYN_QKxCeClZeo z8tyyIoEhsUJ&AZ`Y@adotXh?omBmvN6SITGNBM6azwz1XIe5m2&!SNNgxugsiQvyv zqx?AG@wxG1?dvK&K|w+OF4sLy6DKD4%8$m^S3mrj12TAedh(>aY+pC=Ns}78-nY{R ziqwb_pO}PX{^)M-c!+g9r^pX)J#C8Ce?nSXEPrMg(9Fgq4h{}USy^kd6P4q~YeN$f zGN-03TUR!IM$Vk&6UO;?&X}=kQOWH1sEv0h$5$-LEFL>I)qB$9Ol_#qxU6zoT3W%f zWivL$7bV7~Wlxx();c+rFPZ7!0GfLyBqXHege;hzzE&MEZM<>A#0e8)m-uRZ#-#iB zOm;|_nFtc)PXXM?%a%=BAMWa=b@c_Po(TcII*no8gc;#ct^j$$r1?{my-RkursYqL zT(fes&euXe$<<|Cq+3AfOkdlG2z~xdvoewrN_~8+T>@QJPoAjL0dYN=N|=}I7&R+b z>lzmwl{CXQ-`>+bX<^C~WQOT|b-uAS<9&6B37H-~{{H@fo-~KL;&?)I zv`?tFmygCOG|bV#Yg|Zxx1&_f(bCI%lc#4`SbThFT)c5ighKH1Wey466I8xS{l<)$ z?2*3E%UxzTVzOXFpx2aVYVB8d{u` zGIJU!jQ38{>r)JKNxVnS3IhZiW+2u=_*yu7EFR)S#ZtJFJc~u)W~8FbuV<@k&O>Ni zX}DfuKzQ9qWSAw!zZV!|$BmybF*QA7(v+#wXUxo=HG9sy`3n{;Ub6J26)W=!)~wxl zE5s)6T@)Jt#GM9nxu~#2xIsvh!&s4pWI4=;wGbtT1ri}ZT8iZr#FaJ3VI}cof68GM z(m$2MYBCNIS}JcrtXa7nwj_&~w;a}xK&&BB8S7zT8|qJv%IUTw9we2@ctFP8D}{lF zHC9|PozfvVDZ|RVT22>;FPSWd0W-7+sl1ZdLf4SODzxJ+ht(J|3ps3o5P`N(N{UGy znM?9X2`L(k&p6n7^QNrt3NV#cxxkb7NYGUv|fm| zrURBTv^cz7o?cGYla0_sqVcy8eU+icdZcb7QE)|oAWE!Rfc$Kdg?8qly&~jk5i2F@ zP*w{FOF_4CvRT9;A1P7^3I2SP+9=|z1*YqOdp`QnqI@Ajy!I5dt3~_k5nqSUP|MTN zQm&ZFTZlm30=&ya*m&<+5(5ai(+-1dWWQ!;a>ypY$8pLbtKjp#HzUsB&?1fZn@VPj z;d0PKD^l*iPodFt%_Um^aXxw&qGmS21!AAm;FkgO3gE;kyAE-VXQ~L{&8Sy|*aD=k zLpwQxrL-iJq>C+cj3kW1CD@gHo_AAJ+zLgtHkTd)`S8+n?%Mm{88li$c$ za+!2cMs2Al4W{<3E>kyb=B`g$P6y6oS6n+wp3Fm}X z!KBbA+!O(dXvJ8?B*k3C3dLH*X2tD_I~DsCHOe?;s&a;Mu`*w|LHUa+QuR00)2i20 zRjMnh4mDLL_)RI#WGcy-Z!Geo_67g`0)HMU=%#i?tS;Ep}MkZ}GF`ILrGi z|6%!@0l_+iJhn6IQQVeQb5m>QAe)R+p_hG)!Zw@zq3WCTM1AmTT5% zHfg@s9M)7@XInpO-EA||<_nvjY>wL0+8S+BY^T~Tu+6b8vHi-n-7eZL*)Gd&j@^@X zuh@NH_qE+`cBkwv+O^u5>^1gY_Tlzp?Wfr z$BUj0o|`<+dp3Lad8xf7dQJ0M;Pt52^ImUxec|<^*AcI3uSTzKZ)dyDUR-)7%FKMOx+DBSz}e(*cuSL4^<*QK4QU7}sBEz{nr zy<7V??Ni!UwV!H#)}Gd0(e~(+I(ywsx*}bi6Bj3bnxsj3BB?cbaq_>$MvdJ+wtrl~xcc#v$G<xtXPzUuSt{?an$nDSVP~(%4D+CN)gXnEdSI zi&Jz{Zk@7gO3T!#Qx{BKKJ}fcz0<}|n=)NXZ$qd z=#1K#_hyr9>+BWTzt4)FHDOlPteLYG%vwHc^{frE)wAtpubbUCXZzfkxv$UdnpZOK zoB1L0pPc`~{3{C#3&t;)ykPEvuNKBE{O2N!EPi{5!;-wEo=fjs=D2L{O*S{} zTW-6&a)n~WeJfg4PF~rTGc)InRmN2*t3Jy0%?-|dE|27u=WWZoE3YrVApf`3Ggg0B zFsoo`!Rmr_1zQR}D)^@0_kvS}Qwrx7<`fnezEb#p;lB!hE<9FPTi96GQ#8HklQj`* z?p;%}rhd(}wUgI=SnO83v-ntXRmqN$vn6%weAn$*S6}K=y0`RlY5RJ``hxXk>u*`V zdxLI6*oFxkvNrsE!|AdKW!uW0E^F90Z{v=Qf0Rd+FDw7F!mYxuBBf$dMRvu`ihC;_ ztaz;A*@{;yKB@Sz;zULLCe5bVn_k~^Wbm(kQ~zd{%?X>+H)n5NvH7meZ{BQw^M;#m zx%r_jbW7}(^euC@tk_bxW&M_$x9r@qZ_8I(e%*3rOT(7lTZCI2Zb`gl#VsFhwci@F zHEC<=))`yx+4}I-Pqu!u^};sQHm_|_+tRjWZ(F!+<+kE&&9~;>y5lz2+jiWpyZ!y! z&)t6Q_Wtdf?Yiw@+jmrYRZg#5R+(2>T=~`x)sB@r-nzr{j`dLcb?0Zh0(OP( zGVU6?YtOFzyB^u~&s{I?dVAN$yT01>({A-{*WD4jlXuVEoxA(y-GAHt((cc8pV-}Y zC%JR#on?3Kyz}8Z+wL;nwd=0u?mBt5_uZLy-*xxCJ<2_=?Rjs{XM4Wgb7aq%Js0+L z-$U=QyvN}lpL>GtiM;30dp^0R?q276$KN~u-jaKF-TT_T-`#ui-mCXn+?ROYiu+3L zd+xq3?>l^7!~N>}qwk-0|El{p-2d49-|cnUJ9F>)z5Dkb-ly6Zy>HUK75jGYdwAc+ z`%XV#`@pORRz0x&fma?ly5C{{vivf)8fQ-*;8f`_t*1@Ygfk*PmcSOX{p?fr9s7Zu5fp;8;3#+r z0Yaz{FFYtbCA=hjDtxK%#OjxTt0+-aC`?KxrH?X98SP!^y~}r(?-Rb=T6?Xt z)>G@N_16Y#^;)AgS(~Aqqb-O0d|msl_CxK*+Rt=OI#->)E?5_?i}GjwDt|kFM}HT8 zPk&$kaR15vEB*6>ehjH^>hEPZzr_(TR(L(M$fwBbIHBVhJVoCC2EWil^gM0E*_S)> zl`;5)9Rvo)1tO?q41$F?;lLmUMB$|f#QL3~SfyB_C{yfF+ABSkfyyWmgYCfJDPZ8F zb=P|1@PK1r&?fx_24e;>Z~z7#gBauigG;~wC(!I~7^Hc?Lwp8az!@W+f$d^+&>J{3 zaA07m7=27m3?n-1qTGq~z?FgOf#U-Q2fikxALsP_zYbO#4AeE(UB!8H10l6<*1jSo z*Y2!ssC}*Wq1t)1Z`6j@rq*WGCgU%z)>!j<%^x+N)Zom!=EEAd8oL@bA=Ri~-Bx|6 z`e5~ogq+Da`Ki+;6;A!o*5`01=_+~)yO*~tI14zv6I=y%!BYqng25$R>*6_M1f1;& z5#X8$Qp!kQh(~TA+nH~ zb*Kd=JxcUpe%Mp&74|24n>7et>^M8bzG7dqw*-N`z&>Novv=5w>?LS>YQd7d#*P7B z4^Ul;ooNzyE){zA9O%aj$xS$4&Lg*y+cB?p;(U3Zc$DxmPM6;S&%MjO6CBvTgdFk# z^vvI}Z$3$yNHggqz0{IgQ#@vH?uCO*%%T5X_*eSY^{VLeA)502dRbEA=;={p2APDt$5avk&Ab%)askfCO9Umf@rTF*N3xhY zkfk`wUq+qD3hG93s3%!TJ*Yd$rQYOb8bel7KeCzX$u=5CDrqvg1D2v4bS&9LCy={n zD!G$RB)e$}xt~rZ_t8n@UYbRoffe8>x`gbf)5$;SGV&x{OrEAo$qO`xyhaPi>*QU! zj=T^1zb-A*g%OLPk)_fzy4`Ye5p zK0%+P&(piqPv2%S^c`lP?=mBOkHymWSseXp(N9=1{gjQRpRsZDb2grS!6wiz zSql9Zn@GQ6sq`C`PQPUt+(yE(==W?A{eexUKe8$GCpMM-%%;&_*mRaje}(nzH#U?0 z&a&wrY!>~K&BlIj4n54~(j#mhJ<8_OV{8FE&KA-WY!N-l7SmH~2|dl0(lcxsJ7g}jC9P#S^c-77&$C>5f#uOUmQOFT)$|f8pdYe$`ZY_VSJ)cbz}AwN zXfAo4t|TwgRm6dON1VtHL__|CRqbn>e|-b{`AA=QyJG=Qw7fuxuQ zks_SnmCP2A>e<3Q`aJd{hZq+JLt9kcv*c_v>=4{xVlR zDpw=I&M->6<)L;zCCao(bF}1yh z{>xm=U*=l=Wv<1rTm|sqeVg%;C^m$bb52fiIqAveep_zN#(dJ4n_IG;)QbM$)nz%k zyhUzPUq-^&{cJDW#~y%weSkg4{>J{! z9%9e157>w7Bla=-gdJh^tethRPS(Y`Sr6-FeXJk0Q)L<247ZDPj`v%|!mon$A81N4bD;wT7$E%6cT1$z<( zz1|DbVe$y`DTnM*KW z_aaO^AF+>67k?r}sR*;k1<3yhbKU~=x#NBCQMD#2j5LE*w*j$Uq*7>myj3aR01FJ@ z7p4i*g&D$3AzPRy%ommjONC`PRa_t}78VJ!g;~N}VUAEDtV1nV-V;`Qt^pXJ1H?fC zNPs4g0$X^7kSSydlZ45_6k#f~ggihb^~7(LNRTPmMck0^Vb3GZZ$b>gVoMZsBQ56+ zsoWHnM?RoFB5fG7Tr1F&f~FKy<*oA5Q%=b|l;JrX=aD%SIxeUqao;dlB9Q8e_RtG^ zlnBtQF^*UfRO}yOY&UyEPDT6Rw>weNjE4&IkGHuCb@&W4r&}THj{Z?Yx`jcSs2$Rp zM5;@r7@Z0p`VVM5#81!-=V4`?C-%he=Wt%(l#?Lh>lSa@oWe^eF*;GWt)LeS@S&x_ zPZ4l)0wXjPB>_EP;5QY_STWel+@Z7fQ7dZ2{r?C?F0XoVMy;Zj(6<8r6P^(9Qef3w zgC1?gQR5U#7IENfvS=|R?*pX@+DhJMx3NlS!hCdjIrHf9auSwg7?uhVOG^<;&MTZQ zSt#j@zBs0Q#a9XAa8n~im?)$QX>u8Vv1O?QW)|=;V6hkPckmj+aU8}&GtqJccu0X= zgDXbCNvzylf@E~#1a@2^d~_n%}aZtWLt1^Wh>jp zaDRs=p$S6|W;V>Mr|?;>BHQ6pey>Kvmd|+bA#G)wk;1rkIxNQ#vY;7vaav$Os9>9r zqk_KsChqa{vgi>dy@`fi#d$>&E5?1Ab*z-FXB(tDHORS*?t#YM0cf~Ru4b0d#e)a? zfrP-CW|oK2oadCM>Jh|GblC5uc1cg13FiM1D)_TwgzdHvuLlgrglMH$Y+ z``MH5A7D?w-^U(@zn47*e-r4*K#8rOX$$z$+&{w}uTqp(9LkN9LV5vKP+6oNy@oL? z#wgwlikqp3@#K`m2;4txmA%ob_6*iFzV?31-pAU@S2(`D9l;7$k2UUw>+ru>`Tk;c z{;O5-Kam3Rs!$1<-dfIkEgIA zOSr!Q>?L}90va3v4eD=h4J@) z=t5|zsf9bN*I|P$6}VmR!i2vOU)Dn+Z{!4L67zrVoQPiFj%f`r`2v2YSR=oBQBo0z_O?bsNzqh2I_gxi34fXO5j6AM;=9m|c}M5J4ikpB}79>bmWVUFDaKF9^!`^iL{IgJ-(IhVQIaoIQ01&Z_iaZ}ZBIV-$sy2|Df zcfk&F!R#D{$+CB#b3$&3a+1r97UXd`!sR-b3%TGiIOvlR;#_?n^7km(YJ#grIbMDN z!rSmZN&p%4-BKE6Q(q&6L--FuvZ1?iCp(tUZHtbs!7bauUF4EvQVdve!`N0n4X&+R-dPeI{S$ouF=P&R z?|`;%5F2_BGV4228*L&%!)1{$3q1ZF^nl^~okwig1leVo`UD$OANa#YoVR>lV}5bD zC(d;CcN*) zJxmT~1+k##OdYh%)FHwMm{>kw;jnT!xfCuAd2n2&@%EWJ+L#YmebCNLa40M25I3(W zd=20-y#e$-Lv%tm=m?m&Y#yZLa9IaDN6c&9H>b(BrX#c$^l?P^D=x#(2jmu)4YU#K zt_pCBH$BM;O?|WlYb|8Hi2ot*3gj`pV(MZUXzLw>zao)*EIH1i16`S#ByjrjdK@l3 zE+XH2jFTijFHIzo&4Noqd_IZA`MMvoAzq-953>U<8lq!kh@O5!v@Dh+uuzoULuSG4 zg}VuEAzVD%D!3JJJK* zC|YDX&tbrKC*gD_NyE9%cv$n&U~3r9l*9wP7=&{x4>%W^hr6QR!OF(h4sMF4o3>yvwM zhEyxch-BsfncjwRSPvLHF*ftjM*w8RHt3XG4u3+7D8Ci17%m6S0&WW2Ot`Ud*>Hhy zW8mCq5Sfqh$)c-FXTX0^Xg>1Ok#c&+{TsWSlrWas^ zCE}&?8F(kcacg-5T%_za;=Mr*hvCih@jaZFk8-9{2y=@zuM<6#PF5nE0>|qNcQ%Og zlWcybt%c+Lgv;+#`F$(iIyfyHFE z{|thzwin?b%nu#-@I^8e^M%JFDb`oIm87CvBy2Sr%!d`w@fdCi_+iX^_*#i`3}Whs zdkiOYH+Ucm2}=EdvoV1xs1he+YMc~&M=f!_rh$#!hOELoeGR9w_OJ-&!a}=`1i{)2 z>oqvh5fP%f=fet14u*SQQ@2NZN#GbHgdy^lrzxE|RQ9r7sI_ggYXduS~^SO%# z(-2s`!!X;zX*i95H7$Zhk)LTatmVIw9;zq5(HPjnjWiax#sT+unn0pOTYoa_%(a-^ zF?1a4{u5w}P62+u(~0B{nu>Fcbee(LT>y*oAvy{B`zg2?FpW;9Gq`1)JOE4G8{|#c z&9CCTB@Pz%Ij}Lu(|P1PP6X!T_Ub~+!bNm3T|$>)Tn^J^uv;I2C2%=8iu(mCal3UD z%@yw%tfmFDkQPB!;bsF}ON(gggn`Gl_LC#-ZM84rn3hi~07U_X@X%3EN? z-AcE?u6V0xW8O|HVPD(kven+7jw(3Vukz^ckmrkVcj8Za2xo?s^f0-d9-&9^mELiBf}W(O z=xKU}o~2c^8h5sr&>C7x&(ZT_DeOWGkP9EvI(iXb^VO4^=w)(|ULjw?GWA7v+ z1=f9T+xLKF-wPIgZpHV5RbMAt^@F&zpM|n8Him_>2z)0Pg>xi5i-Bd!$YS{ygScar zh?Bl#HkOUUSA!E+3Y*AMSsF`c87z}!u}N$)o5Ju7AuR9shLC0B8^YOa4x7v7vH5HP zTgVpSp4k$%lr3X7vE^(9Tgh_RDwfOgSUy|L3Rod4Vr$r1HtK{_I$LF%appI8ev4D= z+i_>RlI>u3;JnLx{y1d6y3x7U!?-K+2yV+f#&6QFCvg_|H2Wvc!k%T%4O+Wi#(g1Z ztn7962CUt0;e6~J-0yi0=Y>NyyH9a<=yUc3`;z@jyifEE`xdu=zJsOw2hmo}t>(Yt zZqD!Q58OREB%Z7tWyf&;=mbvKPO;PM3_HuJU^}eAO`mh3WxbAnJNaLqzL7^^%XB6# zIQ4bIsVu*(Bjg|W)azj(4jR`%*_xIF8^T!8R>8k2oye^fLOS_ql!a}`k~B-04GY~|(R#N4 zmb*o;+(}lvn}p@U3Sp&?Bdp>UIU!%X$y111JZprtxXlBr9`5(77d8lG!bYK7s1P>c z6!$c4{@jOq9LI&txXEz}cb`t+p4eGZE!-?@5pEH-!lHbuaGP+uDs@#^{-%6&#k!)H z%+yTjofa>@_43;wzhn7ZACsOT#Z$#N>ZQwZxn63b98ZAL*2n)KYFvfPT2)y4TYTczifmgnT==C3PPW#s0d6kcVeIpwNM5k^&}m?$-wCWD$T zcbP7CkuHOp9&433*qmHNhAJ&pohd_+DM2CUi&*Gm60>+*ACs0Ky)!dalf<4ilZJqm zvJ3{RNh5mE$7IGTCa=mV6Y~u*nVQK%b=Z`tswur6 zJ|@E`L7OS#lo_v_KZHZ3++dbWiA*z2GA@~!HuFc|Fkc>~`O+{c=a&_MsPo0aQZ1Cg zUN{7{<-)w8{IdLwMH^KM3(9gf%Os2%7c*jT$q{#@U#ql_%syrO2VjT6%Q&(he#FM0YscG_CZYR~K&XJMLk*8|z z5bRdDLr~?3qoB$cfm7uVjtO9rVN93%N|)PCmtjngv&tWAM+RR8CoN5#FL$3mVvdU7 z>kV;YIlUoC8upmXELDNnv!-AON{IpnqgBC(UNA@F6oo@`G)q%BR7X`L;$c}7#qSW| z#+L+Fx=fz*Bz2JtSCKqbi^S1qMQg(uw};Bl#by3QVDZS>984$>see1l??+lE#8E=V$V`C%&#OjS!lkzLpmsgan%iSWfPi$5^k~mAO&ClIpJ1M`UC@-%# zzp%7)t+_;uNG3EU&H7lq+=<@Yi6~Z(FM@}+w2Z{YNQ@b)m!?&0q9_27FTeHDk{BB! z5*F#^xLiI)lrBh@mdsd#+^<3I*B~QjFe4|IH^}fBWOxlSTm~6VgAAX+44=7uxqpKU zkHHM5xtuv}rh~a38D72IzFzK6FZZKQQ!K8?-J)2WW2UHHhFo4(V&mnO<7EgEB)P>TxqOmbK1s$rRi;p?L6x~~62?;+;#8@6oS86^2BnWpm79??EX3tz(&T2+ zWH8cXFeJ@QADbqF0bPeL194`;rpe$)>XSZJQk(FW2TT&ph|2_&L^t9xfh9SJxJ*z< zhtS7Lx&z)a9Fp#UxD1D+Eg&xUFKG$-*i0D@NsB;SuAeEBQ(B>sF4G}XuAeE>L0Y-> zu~{-5vgG<%a=%$}y)3z2mQ*iJo`rGp?2D5bKF-YGiE{ZwxxCDfafVDgPS4nwSaE&8 zja)gNHZpD)%y%4`x>Aw3lcg$2i6i5wsW*({qOZgvMwwJbd8mvs%0_u^#bKQrEjSX8 zG$=ReIGKJ%bGkV$mp97e zXOt&)tlV#`+;6OmT&x*6xqPe)Z>$V&tPEGI3}>tiU#uBEbNh1tu`)cdW;o5|%yBav z%>Bsl#>wr+$^FI2{lujyws66*Wl(U$$&kwnT5P=BW4zo_f{amu+*6X=Vv-C=l3YGX zE}tZ0kSbFq)u@6RU=|#4W(k@qH!uUCXlqpL0ldHNgstEkl~P27Q|&ZB$Wkm84gKF zL0s-%QcfTc<*?^II-2uV8V8ENRvo4I>4GaZpgm`SGLj6Gp{lDPa~o#!-SN zcCfrPXH0Wt$00c&B@IfUq>=Gdxjl2f9ETK=;EOYgz4eo(x~#)DjXc}SNUchvnVet%ja?R z>e7lb@m;h@%->jaGtb|MX3HIk{pdm=uNtX;s2I$tt%L3T+hsZozeZepyjz9zVXoo-a>C6JM2BO3566+)^MY z1$d?M)z%v!lBE{PSKHvLgjM-v8;c5fmW?@CY@35>(8uU4R~MH;WktT25@SeHl@zVx z3i!tS+|qSg-gl=6IYiYUGeF@{tt6f7?idsk#ul$A;?OX`C`FR2T7i(HJi zIExK>s2vf~iBn3uFDk#ebtwcnM^EdL^c5;%rh&4|ijPSn9{n+NLUtd;w zvpLVExU`@MAG4r&G-HLqHxo|W)qn+7 z3>c)fJ|;a;TvPED*TT%KtPFSFL9E^ol{P0eeOl(cDEtCzYH_(fh7g$F;NYwIua@Fb z^KU6^Vdcxfcfbp9ZeN7o(%Fhr_xs4-VY7P)=lY-E9RFw74A0_=-)30g80?Bq!qRmZ zwyO)cuigoZkP%j&4X}&63%kcb*foyBYQeu!?#GuucK8}li*J$)I9t!a?dCZ+BY#jh zB%Bs52#rDqem7^L;ts`6io=RCN}{w>Ix4;J#eJkQUO7QINjVF@nX^i{R{4nX8Rb=F zhjKs_po&n%sm7}^Rb{G!DwEnm?V$Eh`>QvppHsiC{y_bu`UmwP^=b75b)&jdZL+Yi zaIo;S2(pN_NVdqdm}RlpVwJ@c7SCI}VM#1gEVo;JZRKL+YZYP@ZB=YlVRajRQD?8! zLsmzudNcu=2u-YJoTglJt7f-mujXOR)0&qwZ)-l$e53hIb3$`o)1>LK7ObtUU95er zL#&ruKX3hp^@rB~vi{M=)+XC#kxh=x8k;hktu{Mt?z4H-<~^IwZN9Tr+1lE=+WOjt z*hbqX;1_r%*=E}=vVFz&9otWAzqb9!_K@vK+gjVpwyn0kc7ok%yO;4RJm>AM+O^va z*sJXq*xzLTmVJ|hpF@a4v_pbJio;}wdmSEhc+BBhhgTimb@b%)`yYpSn`;0~^u9sa~TzlM@o0VI>+g`Va z+@5rM!R<}At8VRX{q9P48+R9XANL^lDECD7H23N53*1+_7rL)^f6o0i_xC)kJeGRA z>T%gK$TQM2&U3uy9iI1i9`JnB^BK=qynMWtdF}Sv>-Bf9C-4hB-QLvO(%Zq?!&~Pa z<{jgmGE_Xx=p$Z z{#yT~{<;2Z{lE49+5d2WQ-EiHE+8}@C!jE(G+W_m}l5)VLfBw#ym5oHasf)mhkT* z93yUucp*{|nGyML4<^YGg| zkHkJ5dnQgBw<7MjxWjS%@hS0H@ejqHh_8vigkR}dp0F?B;e^ij8HZzg`2 z_+?UT(#=V?C*75_FWEl1Ah|U8?XfmvZyK97cJ0`*v0KJ&AA9H6edEYD^|;yN-Wxw= z{5um|CuC1}c|t=`?Ka}eKILz()vknPEMG7`{bXdL{5pDvTsV&l)9;tr|z42X=>Bdu4%KU zJv*&!`kd)+%J14*3Q^CW9y7NW*nPQHRIxprkOr7gJwp~jGH-rX6DQp zGw09TJM&z2V)l;gZ?k{SJ~Yc^)|y%G&(_SIIeY%>LvuXlXy;6s^U$2Sxi)iG&wXX? zJ99sqXFbn(p4Ys9d5_I|X5O3gKA6{rU-sF6-}d=ve#?T13vw1bx}bKUa^cj44=j9m z;TsFzU-;R=>V+2uy+w#WV^_1k#KFZyE9>A}Cti@FwT7Kbd>FHT;Z zx_J5Gt&6`|Vzb0$N#v4EOP*Zv%90P3e75AsJz6yth_~e ztMS`JyYe2&dm`_*yoS8)e3qY;Uy=W|_*b3ZzdCw#{OY?`n+lW#ngWLc&jM{ha6xRr z*n+fzDFt&178NWnc)8$`r0LH%hv8$`}*3mYa5D% zV*ldQ;^N|GiVqfFC{dSqmV}l>m&BKhFUcsGRx+n#Y00XR!jjUGO(nOM>@2ybUYtm_^KYa0x4zpgLPAJfdy9dDRNf&31e`f@hnP1Dc>&=;{(&KW zzE>L0pQ}6H>@p@QI4DrZloo1%4p`XPDE%*(OqC?CQfYF-Z>Q=iH9D=ElV@vt%($SS zz)IV|fIzK>>EwkI2VGo#{Qb-wQm^a;ks6o*QKDA*GRge%qU7*4yHWL`%b~f4l1kay4uF(mX<&M`0emn zdu;%I^m(#x{cpc?cOE@<`1F-dJ5OIz81eb-AV;pTkn_nKXU*p5eJ7i-gxU@zZ{fCdJmvXi8f3~N*p&SJ3HMF zi=B5wZ~xV%zCQePb>Bd1Pp@ULHpmd4oSc}L92u@MO&L-8rY%N>8#qj4jnps7U1Hi@Gs%lu|q?B`OjVJ0=3qrrb}nf z)?K|~Wo2clHg$JhuBwvxI2$m9^j*17U0u`Fb@FI4W%dF1MQk6Tud|!lS+f4Vp3e5R zwzkfmKBB0sbXRILCR2ZZ%Y_RUYR|Q(Y~4KE-8=#vEa(muzTKk(1E!rLM!!lideqtn zQzVXQK)WM3W~hXjW6mqge76knM#fH@p!f6k@bL0<7RlG&-QC+e(9+Wzo0giGICgAu za#W-t%De5v>GJ|{sH{|7IezSFHxqXF>9m@z{+|z3i)5-j*E}!~6@{Nm4h#qgaBex= zV5W+>Uz39wdL{a;L|YPe`G{@T3%*|eO-O_~SJ(Rg4mct$*P^#rJKM&_E6u%;<5S`j zl2(sXU8#3*Zf$MuvcOMfCnb@V`uciHi=Ms)FcW?Wy{GS5R}Tog-G3P8+6-~-)yC$S zn2-?Q&G{A*Z-^`-$M289(K6HI>+4c3{s$@5ijgwN_9CFWP6nC=$JyN5sq0eE(kK~p z^1A+y4wjL43}3R0;<3i->Kq%aWA6LJbtwai(NgKmU^^0rbjlne#bV`{1cfV!m7x$v zRO@Fl4JhnjU`j|xoD;<`qE0QXR~y=u!Qz-216gx*s@xxejN2YZBMT$hX0dzi6LR=KgJive6 z+fk`(ZvNk3w(Ea`8NXXS-d`6Urqu=n=yckMK>u^+j~qLH;k-DC2^r&(rD@59R6nL= zQexV~goGr>&vWPNPE^+tpUUl;s;Z{m4nBMV&_Ha&`B;D193L@1D{WA=uhYgx7Gu(U z-3=&G6nOqOkWheP-%wlIK>iotj2vBCpV8wzlrkPL`y?ge0?lwrf!*<+97IYHqez+N zf$kepLc-h#U=Q$sZqSvc3q0TF}z6)Ov2ho_xipgui4Jt-Uj zBy2|VX`+#;Ee8g=2TVTR`jo60*;#4hVsu_Qf9(M2>!mL4x}caCXe>!d2^qq;WD_=~e^G(hbZg0%dF>U0tpnMbMX zI(`lGp*<2$OTF8pMX7fw9zPhL4QN87{*#m9MLk4lcSTZ zops-p`m~AZX(ITxC*PB($iG`L)Y-fE`UeCB=>q(tLIOK`TG|H))H9Q*xue@}fV5w^eBr{yi}k0^ z)j8PvdfC`$h*hOEegE}mpM288&0U7W-@!ht{_LV4Wv8eQj-Q)qSm(PaQsTtomvXZrYprx_cRG ztE;R3k5IB1g_98J>}at=YwI78m^L&Uwa(q=Bmbw^ic+DwufMys`D*>8hNepwuC#Xc zbhfr$?RE$@#zY6Yu&yhAe*W(JA00e$fz(0s?Q3hg+SJjv!%MJoar(2kOR23Jso}NOa{xx~a`6$wDDCacbt!0R zRGHI*y-9Pf6-|#E%jUrnW|&W0*Gfwr#Xqp%X9m_7@9jd%PiSU4wYHM+mv-kZyz%YC zTH)<&xn8JU{)Yy5d_WafMuHLS;ww|_27h0^zK>M}E z%k@{A+Xtxf=;=$1J&qwpgWj{d;m{ZFeem&bH7<6Q=*#lrfQ6NvGaua@R!S>xo38e& z;+T8%c1~z+xFC(VSF$wTb-#b_;_fwKW^gn{QnPB1n*ZQ4qp!ECqocjOqfcRC)W+t@ zmCH@t;bSM@zb36pR9wE~>~O86sZ(Vi9+#Aqn9$#J@zNy=m7pZu{j|3mn~{Di+S}ub ze>oJSfhM=z3TH1CDy0J7zO%pBSvOq^57+5JLWB7Z#c9B#^YiuVY`b*2=6r2cRfmgD zaEP?4=6Y>nl3pJ(CdAh-anYm+qB0Sim=vQQww|?g_gF_Jra{$CiU|)2u)KEdTC2&2 z1XON!q661zsxP&40mJqvu0ZQ_+L2qLN(WO-O^cv#b;JMSv17dhomXmP3Ud9E>vvph z8PxA4k4;P(KQUo!>e$4n=*fi((-QC>oUlHQOHN9NjR`VA466gv;$q^tARf6|hE-K_ znsxV>+N)01UPHL6+bvMbr6Te2^6+p6Rom-M;5jKn!1d1wJ^cf{?JZ3$eRkg7KAw(N zw7t2xwW;p(u~SubS1#9GsHwf&+R@$B+Sb_Z7=jCsL874e;@$T@{_bcU4m&Cxxu9qj z?3`?DY^?2^vA+BJ?@%%}(AC+y!%LwN*^5-2J=YB3;m&_A*~1FLqy0*?GzyObiWK9R zxQvM@aS2n>GNxrui61*YE@BK*+uB=cTyEr>icUZ|P`i?$vsDLtAj{X>RK4sXli6__1T+U|qM{sdN~q zuEt>X_4W32b$2xPbT!B@nrEVrNZH8M!(%YTyv8TWGlGAqndt1~;o;h^@Cyv^a&}O3 zcXbX}ISZ}L&CMMqjlH9jm345C8ed0S`vnFD`Reph5%||);r?#K9|tCCmC5An?7{z6 zmbV?bR`uskzn#9^(m7z-;npuu+Sb+w?yK%37WVem_IB198%trNM$+m(S{hpprby7* zp`(%WcXSEw!4hR0pD_W!x}@>(3E`Mp?)vHeIR~4bTM%^ED- zY?W|BEo)Ycpw+B>dZ?bU{b;rJmmJkbAQeO2w1D;InNU0l1niFM_6 z+iTYvF4WZ2a%E85%v$NRR-OGdwKetEu1U0+0xSaBJC(wJ*_zWe8=GeGU$o1(g`C9Gy<5kr}gM&p;gK0T)s$qwfO4;=1k)zOzp>MfVoo#M8_OFls>~wZ==sjO; zRsl!yYOBT!p&8~2*QHqg2Pu7nWhBg00Bs}}*$({-wwy4CWXqfU!^8NmD?3n)hCa>k!~t5 z#hQXllPzCWDlMDpa5_>|=~8{Z+oWcsI;VLooI&hFaa;NE0?gx1r;!^N34oU5s>t!rqmYr3XZ**e-gdW^Z+YZ6qJ zcCKy_l-O6Ql^VG}|EhDXROJwif7=%~r?6;XKW z!A@k#^sFE|O-oZ-V^@!dn>IWubnK)_X-VV4f&;I$wzW21cJT9#8k>-;$3IA_+-{@u zb$0GJe*DU@Ga}U1RW&D1H|lNGKmkZtS5>!=ZdW^dcdZU9gSF1z$J6^{dz&Md8WMfC z0FJRu*J>}cv|g*b*3#aN1F11#$%$b>LEauNZXPbKHg>Ky1MTgQcm1t>19rCF;V}_0 zG11WxdSk>G;*NuMQTWuh)SMGx@TjT*<@+?&4iS{~2Lit_hx(^y)Lg#2*lXvi;LmvW-h z3e;#($O18tIjb8iZz4s2cns&Haa=RU=t!*}u z7EAm`%|KVhwM*w~CH|Ep8E0c{C0uz0=p%CmsOb}-M#HQ>%rWe-Q8_X9r$RTDf>IR`?^~juQoPbZK!Wmx<`eL z88ar#$C_|$TcKoDY9B>!LyOW2Ea|Va@40fdp|j80)7xhN@}{nZG*?zSX&hCJr;cCh z8R&OLWp|CErC_R5n0gzUX{DEghmVJogG-R!-?8=dsgo^?bal7FpxDyG6gzyJ94!g< z7(G^=0fE6|oCQ^PRrO`T)6d7Ir=jMoM0GAVQ((38_45xIGbS|1%MNnb0xG$!J^oXx zWp77IV?+JLx=Zy}E;KYW_NbK2-B#`aVPQcY_R7|}Gbc`-I!hRKSf+-PX9fZ?SFA|1 zyF)q9(~YC29f4G#YCHA)M{m9O+wtQkPW1PiC^>YvuHW9>!NboF+Nryps_PF4jzxeY zymvrQtJOW%E^_7M)aj-krK>JDD8So#ptI>J>?i#_ot@yBj_w}PQ(5UtX@C3W`udK( zJ`=fG12bwv7jq1X3?HL)b+Hl#DwUM*)2C5qdsRK7b@7J56lsjlqVo++L~}Y zMx3lWu*_DSsk>m+(57_J;a}(m63faRf{8*~x2DXx`?Q&ImG*Tt77C?}lk24Ac;o95Q-h95Mwzl@ni4*6qu?UH-(k!r)@exm*EYyw7{~LI1Il#l=75b~I z`~Q!CmLwMc#hS!p*RD1*M@L7WAf1zSKeo!3Yc90+Ub}qx)S=T=-+%he;i~4Q^Cy*k zwn0Hb)^he_!)b+g+M)@!nExp1|&f1rzAtNQJ$AFKZS^Z)Vo z9&nPJ_mx<6&Rv~zoar!mW+&&_oEKOGB4a^7fJiV?u;l202InOO6 z((CK>PUsU)+0Q z#134~AGqf1TsLRnwPJIB{~B66m541}4W@V>xZyIj*<{km`B)%A+Zs&6}%{bd5L57jxxGF>B9|wHKg#0rraRjWt}eI4)EniTve>ExfI?|Fy>4@cseLV@B}O0Xepfy=6TVrz5Z z-FI&;tSm1stt_v^0=(7LtG{);ez9(-OX{BbE1W-GoK?tmrdCW&mj!W@(qA5{pR7}L zRs90*x2~?P29REDMz?<*t(wtsa%H|iAX1uJu-SC_>;^$>X<=?P7>*=!g#x#Z#h6Yb zb6i!>YOPkMbGYQfe0cHpt;NjBoAC04qS^4 zT<`wmTRAl%2 z5e>@cv&m?1eRVSw3PlQBj>T%V*&MB&7KcqO5poGcScSsmGPR%{i%P`2N){av&`8x< zwIsJ0UcP?)`t6mZK&!6@R7#zpt5dP|w}0`+S8p?CbU;?wnp=$P2hW^7-7cG@OQEvC zX*6RZw3t^77W!!n^Oh^Um9EzVXHzZ@s$|%JUT1v$a|Tf!Ee>6kfmb z_O%-~ZroarZ3{&rG2+H5rBqzareZ0agLI{epJe3j?QA9t1c2XBOA?qsrlvydxG^?7NvN=K98lm(_Z!)*#p=4oohDB zu}Jnxb=Caqdit-$gV zsWcX&RwK-Bhmi#cZAG^e6-r5Ga=@+Oy8&?~6?*FjZ!g74vktXRXGbu_YA~D4lH{$| zzx(6s8^Nucw{EXRvTzRB(IM3hM;`1sI-OoG(OG>5p67p`=N%l0KgW}2NM1T#r8Rn^ z4G=MpCzDR4Qt?PQ7!Gd76Txj{o~4qO7MH{3_Ig{bT5%oOY$;Z@LZQ>iWdeOAm)MR= z#8lqE!3u;;%#*38S|A`V)q)$5SSnHFDOH?+MyWRn=l|R9{O(`;>5g?CkYqQne|KTz z6Q3BTXH5bzS0n+5ijx;hE$y9Mef>U%g3@Z`0YNUZ9p~xTk!-G|TCM4kvm=+nL)pFh z8N64Q-R|~yoK~B|?RK~5=vu{~rDeP-HrVb?fo`Rt`HzYz@ zl$>Yx(njl+$;f7+o12?!i*s{xi<|MHL`PB@ZZ|W&vC#U)W~9hdTVZfwzbPRJQz#a) z$pj)u*&J77#%bDzq2L$mawV-d>J<{1oTU1BGL1@9Em!J7xEd-QW}=vzh)Kl!dZD@H zYovL0%b$-_Xp=>Vp>J#NaSFFqLpTTKLIo)~2lO}x%x-=5)*H9Oq2SJrH7iy-Ev?6y zlRzw$*0t>?PWU9Vs#@mWrvif;h_2(vTD(hAMyt`BNbmN;Ty8^527Yfbmr7-@h30u; ziw!zru&jzTm=Ok(LC&jJ>cj)2rFac+bCk>nr0e%xUxrTd-rry5q8iVhzrV~a?_4u$*D2?UR4Qyp zp6+4%j1Ks$vRb85#Y+k)zQVY_AJXZA{h-IWtw%qIB|>9+UteEm2cp8j-g=Zutpy>+ z?vas^E}z%c+Kw=kshliH`6~y8(9-7SuU((t*Bv60+;WoX4RJY>O2v|Cu9985XYtaW zVi}^-02S7%1xz-CFGLoP*Vk&N1^Knx*REZAcWIlr+x$8rKJ{5>zwx*M&DvTxTjR?0 zR-4^K^J=;1=K4NM?Z-PlG`rG52d>LI*UkOo%~I2!AsMidtvVq=j6S%MZ9xi$Ts!1`!onT2m(Ze0^(+UtdJ9B|)XJ zo8)8h7+je=&+PB$a%(j3*k=XBe7PJ?&vua+4pCQ*QY_XesGW&pwK^RJnVgecUtWvz z^+u~vTH0!|GZ=dv2X!)=DddyUXmqPkRjTqZ)eGeqHg2&@f|ua(r8G@r(I5}m)7#VA zJKEoGq-*i@1^jHJWJS5Xv#YzS%kQRuq}Bo=UQ4PhlEijocS9DLmFDT=$35a%zCeW9 zXtR!5&C=BdQ=ye|xxDb8bh0Qc?ilUIBDUCEjdjW3D(t|cyYV+|KC6<)5fD>dtLR~Jrdb23iz>t(%m!A*DJHDMfu1of-5VO7-7GBY$0Mf z_v~}SG-p=8sh0u0m=)Km9ewSjJpo^9tG=EJugqV4{kzwjCpLBC-GsW`M+3v_aJg+X zH+zkp4NTN0o-Xl~N{tD##^!Lgx3;dWg|;Hw;pMr7h0RQ<2rMz8G0Cdh)z;?k(n|87 z`I|Qu*3n-9MXg+n*KLQ|d)cQ0YwqklXZikb|MvG0yXOeSf@+nsyb?+m3-zw9E~hNN ziSQfy?9XDR4d)3U0~!2G7T}Zg?g3SRH>KBW)iTInA}mx%$Aep0QNtZ0Bc;RPY4O== zE6eF@YR^!ajCME=R_QFAi*4S$dv|$#c_S?_+MNwslb!E(aErYwar3~nX6Jeqt(=i^ z%WwdBaxJkRT3uFJ01L4!l9!TbeYPSLYb+LvN>BmdBoT`w1qu^l3_3%b*MZNqNP23J zROg8$8uGDJDw7%QcDuTg2`JG+*7p51fF>7HU!o~+7gd->xBvy+s zQ&hv>?(FIF!M!a;OBCq@gP2oH#pCf3Pugw5kHx4F@?k(Fw{G7=<0EC6S|!Fd!84m| zZa==?-qGEAsLMs^0|5g9BQ5yEWwGNFwTVsL=@N9pk7f;XA#)Rr7K*KTtqnoFojerE5YrY*yLz~vC-4vGfHPg zik7Z6Q)y)-z~M{Tb<;e-BuGca*0@$qD?v{-p6Ya=eDDqfIb!7+Pu3)IeWbZlLY~?M*dI4M%L#F z?6|S(bQO2r`xcx7*YN|_x&zmX2d+^Sb>IDR2d)!4*Uet01t<%!$4;P0lP*&tww0`% zK$F`Z{EXcC;AiCa2S2Or{j7N!?{8%af5P@cvsF>S{`hKW=P%ehkejmK_$FE7wPZLg zclAyVTaksx=JJT9fWTJcsWpXST|{ev{>4%xJhB$M-e}T^D!I^lQh+15uoa2K@<^cs z0$kgvFCGT_@5^T#v;91!Op$Cw;mX*-ydIeJFAb(};R3`?O{%!Px3tuLKU)^_Hj9aIY}8xK~DS#MTx z62!X>YBValN~JYoYcq6`!L>LKiKqP5*4B0grVMH8&ylwN!o%)aKCLRpGqWn8 zMP5m7@1398JQ9T^iC$|o0rS^&;<1x$Hch~mu2*SAEzbq+!ss1;eD^JuBe8w6^U99Z z#P&M(!1Y0UZ+++YjP0V8!b(vZO}=(pX)Bf^78OUx4@WX}xyIzMnP~|xA6;MDMyG5p ztSrnerzJWe;xBc$6iS*FEAqr#hyzK=S7=DQ!6?z#u%P;SrhDp{%}5wFLpYC$e4JTP zAzyFla4Pw;#_aY6Hp+Ldym56~(lOpgHglWFYLLDrD28s`3KhS>&h~##=FLPe@?%3Y zryjX*=EULQZa*?JdZkK5YfLIpJVVj0_O`D6fu8=!!!x5UTCFkbbXKPcu@SS&cL?s7 z=2MuZd_AoKmm9v$M-%R4Dm{L69=x(9Jm%AxaJPIf((Bwt&+(5P!8Uqm1m~Mht>qvo%>jkODzn=3 zFrQ0hA&Kf;w$d^&w^C07GB$VP+LbE^=lHSUqvjpw%?}TA9*<#?cBY-M#h#s`V96~*k#>*mq{(=)&SxF z#FgS#i?qXF@0mG#_}J8;4zty6QFV59boF!zc`yWeySjSXo$V?T1QQDo5D}>K{&rer zaQHiifB+sE9UmVb>hS0^U0rQ11H9>gi8=E-I$dT1bLzXDT46brh^(&80xhf&$#~g9 z4C$1ud^Q!HTUgx82E?nY>i%|wB+B=Q>dwA5_S>+L{>#q0;nm17+T%8F}2h})PK?(OgE?(Fi~Fd?wQ zbS>?84U^eI(mF1Stel8M!#MLPq+?+>RRh%jez^W6`R^t62GO7j;&OUGB$eYj$|G_40ni7rO_g@D+9sI*hk>?mnYXqWKh$ z=dd%pPx6G$c5-{k4o7KeR#`9SayZZMPUPP1#yofycj|JvU7a&yM`m{KNT|N|Z8fJ- z32<6k4&d4E-+Gd1Y0DQo+CJF*Rbh!FMC~-4uyyW#@Baz+p7D81db`7JSJrYMm24SX!800J~%o$A?0urzjoenpLQ?9%wFLF3=s){{8C_ z{l2-#Z_LG}{RT$y0rtx-zxf7QIV4jkwQ7-(mQZV}!IF}8I$G^EovF=kCfO&8LT7mV z(U)F$6hDU_fA+D5E}S}d{^_Saa}-5OzCnGX`;#v|_rxpDKl{SRFLhwA9vB?#8@u@U z!8ne~a+Nq>{{+`jP6X(xgc=~5Q^{Go2&tG`x)G&1f z@FQ+rsO@OO_H7bV#bhkHEf$L^u`MLwmlF{9;K=y_1rL=}&v{e6>nKIRbz^ab~5=2>ge~jsfbnuccDv4Zi%X@84cr z3V3&Tz;2FGsn+RDHoF~wWWCW~CJ`YlWS2p!+&7=Q8uOWb!^#dy}w0NdN!JJPSfd<1iP4?kGNyjEmm5%X~F>N|@W!M?T)HQFXX?*_oZWXFQN z+H73Cir)=j3xbeBlhFgRWuKn_^4fd@o$cYs{9+^u#(iku_}C6}nNW%L9})XG-CCw) z|M30m3kxe{ip&sDdiFlQbzf_my8_!bdE} z(n_pcKRPrrIT_zxzz5=`#f?N-&#&&ct5K)DF&TY=oim4D`BN<6zFdgIRS8R_;V`$9 z+uEj-Fv+AkU|Eeh^9qQ6PM689;cU(|Ga77<^fh_}^(d*~Xbf)< z>8VpErY5Imx?JEwMWfr>k?qcop@9zUdYzr^BfZ_kmQXW;QU)PqJ6c}4a(fkXUd1c` zS_!w$XdrtWGskw>`MbM@du)6fR3@ajcV@~s!|lf`9hX&cxFJW%%~k6&Q>RW&O&vZw zJ^jp6)bYS&sxZ4}s^9@S=9v>yy-uIcB&abvD?TQ>3A<~wuuuJMye&?CW}m_mui@mM zoH%i8d>ppw1YPHX`zr^!kt>lubpFYYoj>=`Ll-W9SJUco(5s~8=i=BS=NF*Y_XHT#frw406k@!=M&S&9TooGY{Tj!lATPs9=S*}`&WD>RwR z%6blwe|1y+M*tH&mn*;!X63l;Y1BZIrTAky=etU~4l}y%DIv4XiSP0Kv zy!hB-k3M?w`HwxORq6#uw*yY5GXU;DU3j0y>_`#qHU{0bXXoCtAe!u$PorN3>zq`E zgTtEQI6MYJv2^tCg|pMshiM1$GTs)a-7+)bH#=#_2V}%5*WmP#BZmohP{H)f{M^DS zLmLA(gFNj0`{sAZ>~&jlO6j~ElhZTPhi8sXdZp>@_03Z!9=&+t#7Sev_~hvDKu?=n zC@Pl0WEBcTYNb@JH)sqekM=sfw!9n(0j<$LapKgelhm1jjvQU>)6>(F14E#M_4l_( zwsCUpG>Im|^%{tb@N1R;RhU~`zWLw3vnIIT`29X<g8XNBD=qyW(MlBjD7Iylze5taGWb$&lB6oFkcXz@&+v|ZeG)A=Ac!ITM z9(bbdMxO)V9%XrA6%~s{qOn9%=BRP_Iz$f(}Vr}Lyg~)?8D_zPQJiFaag6c#pU&P z4G#`YP0dVCO&^_kA7c zc1)PvDYZwY=HZJEoqz0wSHJN2PrmZf^Upl_!c)ik+dBJ>TzvfLPk-$wioN|i>YMzp ze&J`RX9EG5P+Sh(N3@1Dj1>y1Se~^Mw!_=$avg+btF|7EhBw!VC!?@=-Bxyo`k&D^ zGaPO@hup8(T-?}LUD+-bCMV9E8XupUnw%IvIX&ss*7#fm(Mkd}lYBFqr7|IHHOKNi zo$t(xj~x~Fd-@KwdpfBRvK5Ax5_oUlpkJ;`1e1w!A+)hkF&bRn&d!c@ zr?g(#A+=)5D(dt_u$HSWzRm&48gRf`C{qRv882ZQ07l+g1_X=cHL}tOn;EA04&5~% zk;$rn2(D7~N@G3;NZ%wA?xchNoWcaW23-P zdwcu)`?#EeA+v>+Xnb7`C$fC^IXbK=iX0vH*sZ_ECpn!ckWs;F-(yGVFG))p-l$G* zvGZIGr)LO1gA>oa`q|HY?$PI8efgfgChKW}Qw>kkz`B~pj*WD+xUJUiQ?EY%{3FMS z=Sj1kr(T88xqJ8K_3HsO;5DH#pxHD{aU0I1VxM#A4%nHI-f=AfrYS7kynXfB^|x*Y zH}WE;ObQNv662Q^&ySJ~seeL?&H$$5# zP9vMh#Q9g|LkX_8AHGjtf45VwC~hzR;BWu#@Biub@BF|2xcC92NQ6x-5?eT&%4LA< z58^*rp-@C5Ah=wfNREjNQM?CHDq(zfhMC*of9&mjY@+%;naEx|xv?>FPo>XSdE8(!ucEf4*}4<{IWX_JWjH zPp^IJtp+>xI+^bmA9>WH3&p*fQj_CYr44P3rgBl zrO$r(D_{Nc7e4jE!)I_@M8^4tjTe~*u=*kqnJh`%#NXBA`L~3(8-qSrYgp5sF4qBq=SURIodj}_u9-kOFze4&$%Y;&SN89ANN1sM?^337hc3`iRDp2#h9sR?{PMtb&;@Cyr z>66r0^Vojp=H~jw2E-potJv9mYwey|V)Vg2YglH#O4?+zyZ!E|i4QH1$0u5S{=kkG zy5pShZnGa$vX7lzOiz`{Or)tHg)%yP;wX}%t1E*f_BeEOeDvz|@BQHV^($AeUAyt) zD_2FrEkIjxBBMbjgHVfHDCUZ5mDdhLo@l0<6pH&bhlJ?YRv>3vl!+Lt17=5~r~ZQU zl*u3!(KvQFiO?p}NSTsIOce@9tXo@#2fNy>3W03Dt#}|nX1I5RtouWR?Ds_&0{0H0 zvAy~8X!$tg@Os*gAN`4~yLaPyb_bTRAexbn57Gbz1HyRKK)&XY0IqX|GJSiS+v7AE zY+WOhqX>w#X_bKQ%YpH;w6-Iq1zh{^^r=%vI@^7Ihr{LZIMiO7QVRK2iNoD<_Wb#y zeb~$b0x|ep8vbiDb;0K93Q!ISq1~sG8{BTU({6W}lo}-;q@7$r#F1NVR=39uyArqw zkmTF^PKz^g1!mwfUu2-X?B4Md`l&M*u2(*=>MxJ%?H@?`mmLYBDeQB%_`Cf_55JEa z1G$$*Q0D{qECSp&08Iqe4rlCOsn}%ssq`Jx-wB2O0b3s|?hmc;z_9eZzK*`JlgB@_ z=!Jk$yt0rT;vT}R8?s4tdi|m zmrSnAt*nTpnih{&>ukolkV3#N&BVbP*2P!Rk}03R^HArh6CW;EduV5(eE66xvqO}t zM*W9tLR-L)vRYG^EN~@1Ow5>xRZot?i5(lC>2)~U2>P+Dt*d=2va%KhK&m444~|Yw z!VQ_I)-u@|VJlSYDY%OyR?YOPK#GLCPT=lqbHKPqx&tAAWE$Y6@D}NCM)`L)?8vSZ zruW?~zAoRbBW^USMu~z7BsVyo9V~I9L)g+T&wm6JupWK7sTkr&1zc zq#obx{SVy=CgN}rv1&L!qvNNax%lvtFTC)=Gf#QEebc8;j~~7C3%~T0&piFmBNrZi z^wF0uJ^9F!BL<{cRkqgN(b3V7kzRPQL!FU(GA58I zWSs3t1ZTZrB6mp~NuK0VH zp)=24`njL`X<8^IP2Af;Id;yEvX$%(*o-i-8MY4i%4`nt8{{4IRAfCGOUCk2L@-^dalILzS|)}!%g;_mIuO;(Ya@hKYFy{1pWsxdFG zvc{p?ts>dMfT~PEKHuX}@s+G8wcm{(ra@DPc_|=b>_MGM)Uc$!PDX=~gzjzSA&et` zFjZ)#CD88)NFWOhVQF%x&$7PX{meR8$2;vIHaw5CsNuWp_sNp= zc5lQAH7>LHSRH01x4G-ZK%Bd0qHH%|<(}o_bB&&0*xecvh@y-R?T!rXenH#4a<+TK zY{LyDTOp)ur~AS6W760BgZi@pM$cBZN6(gw)sC8NK-AQ-4M-b8wurSg=1DVBtMD9D zI*NLl>SpZ93kBF6m0fv2f;U#)CG>7jI<&Tx6Dof2y({ba5?^n(R;xOr3Xmke7&{PG zEa$2vsktBj@W_#|E-Tav)b+aB*WaNt)IvlkBvRt>2uk5KO(h%K&Lfd&wL1*lL?#cK zWn&bZExnm^`^o$(`E4)(UM0KS}L; z(j4t(_PcqPSlhwxa4s}IA?s~atCQwxoEmpKl+HzCiz}!Z5)8+-keG?BZACR|P(pGX z-@pJU5EfG#Nlc>_d5Z=LcmZ)^>!GU+O=y&N*vZBk_JGfTzhWfE+^IUhF(%~MBVL-z z7lMkxD5~}Kuy$#Un@jH#V%w2n>lqms8g6$|lK}|N zQDDRRZ|JWLclrA~I&ou${s%mJ#$pi(g^7ena^uGG(#_>NH#av`wDnMTKVkue)Xi(x z=H^O8uG*mK?S10$)2C0IIyTwf4iaiM7T(ARgbIn?X6>7v9vGndLG5)r^g0wEF&T`N zNb>F?nxR-)y&FpydwZ24qQ?-R86`MC2EZ6%?D`NVV2UAlB(3ILcu$L?wLt?yjf0k{YRhSoM94tkKV*oDR5 zG{R}Pdm8QEzt}5PV>e^$(kfN27NB`mf@o^7REg&bM9CbgtYUP7%V9O@wWEVBwGrZ# zV4zCO-AEgaQilT6GKK@kb<7C#sX2YpGLu#8JFft z1yK<4%h_BiGdM6c(ceF~ys)^qG`C>4`8-xDM6zs7`{L4-YfH;Z{R0D7s-{3dxjQbq zaFJcRV9%L|wM2Y}@Ye3QFdVX{pSkq%rC<7mJ(J*pz)>#;h!YLuGA8j)gOPe1^fCt8ao}hniUflb*)Tz-YEY`k9p7+E&Z3GJOg9wQ6++6rJultfgimLXm{I&yM)>`(`ibOT+!PDn40 zO^#QKN^fspf3IoGEh-oDb;}^$;4%mKHU*~wAgV;luTh0f>}#H-rD8b?x#S|~ds4pE zi437_b1lm)<)sdrgC8puHg7j81+gp1+*nBrccU8tWYEaGkid6_xd}YT2gw>^c zag75-AW9_)K$~SUlU9QNm6kIo+xg&NM>Zxv`fQSh@~2R)6bVEksu0_tX_Ul5(SQ$B z1d1Eb-Lpv#5FcXC)7`pHY#(wOeMr{hC}?CHtl||Fv_`41 ziXpCGFzKa6c^1kx+mzJYJv=hnd*3r?0lOZ7B@~B$1HtU7_7)ni5D$qi1)j@PHmB7x5*8zydqz5N}P&tz_ z*6SpQ?)F5TTPkGN?yfDM)>x_tPPQbUudDR52&xjaj^u=`nsP;>f!aGKphJ_WQfRvvAA6&1Rp$_V(-e>Ngmx=qR4)sv!JVEd$?J zDy0j>F}G4C^?FbPXmDf%<)ua_L_=kamh+FlcN>`Ag;@m;ifh4OFi{(s0u8ufY5ZH< zYdRlVT?<7M=_-o13TtTyEtU(L3rL@>tY#|(j#6GKakVB5@QAd===CDdsh8x+DmlN7 z3L*f!w#cm-2zuz0Qmfsngqw4(_cQE&KaV~~Tfp{es8yavJ3JC5-yjqKkwXbYeZ4@b zT3QFDkD&O%(PK|PDN|U@l(+d_KltvuckkZ08_**2jsWScEv27%F)B^dsm6|VnY`W4 zJ~83$7@9dZ)#YpN@w-}wfwh~MeDorQn*m<}Z&<8R?QP&?kWlaV#KiF6#Mx&q0V?t7 z&%v5Dl*@HNs|%p;%#VHm*7QO~!8|CiuYUvj#l_VHv?HSKun92v!gp`n-bgjQH!>>^ zY2}3PQ4t9FTr6IhQm>avr=NW2{Nclg zPd#+@^g}%lKlaqK&p!LiQx9J}|LpTmTmTUs^j`co>?x&kath$78BmT6A3kETPmWTL z0H0H*ctWH=7&%xG2v-UaeS@&V<~D%Hfhea4=|~P#CyP03CbQ$*DySiuLJ{heb)exO zy3B=apx-JL)PMy?A4x=fPB{UEIdE!Xk?mYf1mr)pp;T_Ny~61FDfn18UP#2^(Re|Hfv|f##4J1H0ZC}0RHB3eiO~o-1h|Ay zYtuqFK2?&6s;NW_%PY@=EN2z>D%HwWY#Ev8Mi$VITAfFg8ncoe9YJGsn!ZR*z{#*X zd~}D6TVL zWf!=#fCF?$&V|l_N`Z1c(9{Nb7{@P?;!7)m)p0n|{gP(oVIb9N36_u*aJ+cj>pS*tLq4Ni=q&(R8@ zRFA)<&4KV^KqtxqP!w0Ig*+Ko=4@$gAMW(20~$CF+#;B?**J)btkl>n?LG9==bk@g zkk_cHTFooxW)=B(kdUbF-U(*&;->$^j$R$NXLoBRc56@op`kILynx5+ZMD!)PN-7T zWIY@?a^&dIqlbrj9k$`2zK&Lx&yIydBQt0roZXfd8%%U+^#(E4u1MG5_b&OOBUzob_H+!_f z0)kSdn!o<*N9wQCzbN`~LzQHfkuZ8qTP|!YudS_bHFS(@kXqu->bSxduN_AUM2#>h zk^Q`VXC*N!fcO!(&a4-}_FJv-{{JI++wD+zJ09LlNCw(#u+8e9kE+FC`JU%|L81{2tkXO)EtUkO%{ zN@aGt?yj!|Bqd5k3jk29L&EJI>xpgQKae&l>kv}lNhN?p$Tf5YWv+gd=x$M=@dcgK95W4r(tL&A?GnJUKD4*qwBlyoZoq#onGFYpPV&=t|`pu*B$G zn-f)V{ZNOH6k|aQ)anXNL@)Ig_!dS*Kvi`s1QaKiQ49E;<{oeVKrhe3+Y17k-Eo!DBKGwB7w-}3+vt(YGY}-J2$dz7 zM2-Sa2n{@_P>5~6QpkRxka$43y1fo}OqgC>T3ubbx%!dY0-wZN3@G`KEsAbzphR*k zSCFamP=6OmlnDH_x2vS}R64s|s^m#sBDq2f!$_k7$x{n+jnA9^yZ`>U5=fR(eL?YX%8W%^TfXZb`O$6EhO=ePCj~9V=6Yhndlc!E2CwAt{*|TR) zPmTIKGt;B}oj9=@|Gj;;#ig}PiVg929#diXV}wN;*j@MNY~%gtX|;J30i$8VCJyT9dg z?D+AizV?n@6f8o$C6``JV8Tj;#nr-?J$@^8N{K<(b>!r!Zea57Z3Fe7&!R$$p4eCH zurVE`;z#FpETHD-iVT)U%a}2R#U9(<+G|z7iBF*LT0Hh%s{&dg+pJZANVG*E6d``0 zRLCT}0*Z)lrX_?B-QtnL!jLfy*lS?e`Ugx|(2nnAU>G#=fD`W!5$t`3ofbm_j5M0v zz1ee8(>NzzMDO<2>b2?vsr>WOLSgoUOZ_wP2IfiiOafv9bqNvJcDS4-m6C$W%yy!T z&{jAslZn){+TwOO%`$;77=a81q?_U4Lbc4KQY+;e2+bPg!ZcRidZAVeDD!1W9JL z4;th5yJS7=SM4mZqG%$e-b|Z1diwi1{ID9j`usNNZdqG92M?bBm=cA)dR#`RKpSjc zWoQ4)sWWGga7ky69i=7$ml+$jsoHtKhV372*TK$hm}oZE>Y{9RGtrogY~qw}$WBrY zN5iK51JX*HO{tKecpO)zv%+I>8X#Ncv&s1&CReLMwaww}JTx|Q>==BPnW5em>(D@t zr_FDtg|-&6$pGC=6ytDocz{pwAz6Y_K!F+PsIG5-b-TG4FLSWc)w)ayaq4`vit5l< zy?lYtY&Ymz;N5lj%uv9=UQ?;dj1HqtqgM;XQl&};pTAlm;_A%2{J%&`^|Z7uR+PEv zWVu{Jd5u)6D$}(d8fRQ9EeS%|`QD>eA*!xcc3Hyb#mroJF_;kDE2eb|Q76f7HgwI7~HkPma z@M>Bn6GMrdlF&{!B4sn#H{ zgOylDJ+P6Xq2b9R=PzD-E)$*)qs;`qcTU%GVZ`R88x{1-p-nNNS> zxvA;nr$%SGeUqrI3C=vAPrt(Z;wN7End3l0zJ@RS8a2!P!Y_g@@`oVs?M7?^H1rv> zH8O$G0Z~-kWjo-ld9_-V458eh)r$_0f_+xS`ww)rwvvFPO~HleTMgQCvlhrTicx+d z9%tjChUoJA>iYUd3cR_-jzprrGao<_HoTojSVdxRwRE<79cDGZUP{G6H{Q8+eT@im z?Nf%mmy~V52)`9xDOHF4i!izX)|yf&m*mB)f5mVYgvD)CpV>eORq$6fgWK6EN2bvk z9c~v?FDZds3s$rNx*7Q#RLOHCDEV=9F7Lny&@C>hx6@SK3p++1UsOz<}t? zdm^F_8W2^oQQUL<-D^F!ngWsUOb1aWfr zhXs4O+S!00;(_knfFMpU)bnWbnz1b@ab&f$Ak=23)B(w^^ENAOgbUpW0tR#l9a-Dm zz;vhgh6yF!Lqn}tvLrz0+8ZF`F)>EyM7peIwY*-c?eWVQ%zn26vUja*wc7ObD452R zeW0P7Id^K5d6_&SUU-`WFEcp8#yH1^y1V^GcUQ;2v17-^`$xc09-SDT?6=wn`>Chy z8|NgiVDCR<%!V_2U*WxxPW9dw5sT-I#`(q$!Y~Ly?uIQ_$YtO*RjQd1>IEAOh{bfa zclGo_cN;6wx-9L;jT!YWhaZ9>J-$xBS-W~W`h1lt zSm5Uam+vX7FC()0Z-xaX3zYX~t@jM#HiNVWn$ELWxa=wRcVq^E1KQJbs2`$d9iHjO zpL^`Y*;D7wwfowK$0iRSKX&}gqfb2j%rh^(@-r{J^fN#6*m0BuKJ(D%=*ur(dimv- zUV7r8N(I|y=#B+@fTU6$?}D!sQ;!-h&N zdt#+pnwIl$_#nokfJQf}3ww5RJ{H9#Ed&^&Wlx z1M>WlfNE#cLE$+O=`W_(%ts{H&+hbZlXtnl)macqP#IYztQMg`j!>%zdzwmxpy0~N z{4Lm(H|94J9NKJUj8|yuLgE>qEMndVk(|R%KqfN~^2~P@TGUkNY^8I-EmB;kQJ~;8D+gYk%M0mc0}@=N5{bs>lwu(yDb2Em|MEZJy{s}B z1+C?*z~-{*wK~2`?|_NzbsN=?+Gw;{OkW3Hk4xFCqUh@U z{Ct&bXd)5_Yhi+yB^By;A_eWUX?1GZ19|NS4$#*eAGA~-nYA1~8R=YbWi=QfXUHvB zU{%PBK3MsTnfKJsfPeNN^XI~ z-voi`STvp7rqnvD6NEs_CUF%pa)p>mt*$Ra9UxdFD@d;-@d+1XgW(=!3+=w2 zrcDCUC|E0(IYv|9qiGj&J__^66rzPF`xpc~LP5(OP}bJ*9$njgvbN=&8BwWKxN04< z(|t5Tx6FRc#*d1~t+2URCJU;l6^b>kM8FruY-Xd*pn1S#lm0!~CX#GI81Frs_D7U` z`YkeJ@83v$TbE5N<+ilDfdjz)MRH6>5$ZaQ(9y}0Cy$Q}v^dAd#Dm>FTHWRYpyk1` zV#a-EIIHJP%^3`H4GWNsN;Y$u%&f_5EG=(@6R1A)E+AFqYViRJuYn4Fpz_+D!fUCW zIRh7P@9FqalwxCM%{NJZTkObk$>b`%od~CEIf?bPNQjRBQ;7ly%HQU(8LU=&`u6Rm zNDdKkp2;(eB>CZCj|nB+fH>f4QPlggfGzYmI|NMGc5rhm34QKry1)}7{UWXBQ`=k7 zWL+i_)QkBdDq5mMfXJ-1<2YigUb5ctACdNyqM=9%UO1O;*Gi!;{GjdA9Bd$IU*lXs zb9BTpLt2(8viYH#yEjg)Ys9IW`E1)G&3yKskoOtv_9HtFfVBbh)n(}6S?&n4MvL%g*1jI zVL6QU`0k(DxF@JXxgXUrjBt-y#VH;=_jNR1uI8eC z+jayzg}v#Ga=DO>#qOEjKt1J+HG|n=b4Y;TfHtOCMRf!eBn)b`f^$bL-@FzN@OMEI zj)`NPg3xC4UO5e`N)V7`vQ0xJ?K`k`S(wwS2}rR z3}-eGf>X0qlAvN*HfuDgbb5o$szIzuL?suNZ)YIMtER+(0L_;WgkilNg-5cSfg_>M z7ZLc#g;oKi02H&fy0V@TNE;YT*7mryQyZ7P=2DL~Y!UW44WKS-oBSs-PC5nBSW=XU z?H}mx8|u-iR6I^y(F*!to4?a*GunXkY0=4HS<)IBu8>nqfRjSG$>19q8bICA(a*ei z;p9MPU;kj29q{Q$phDg1J}VFl~neAS!%rT{ZC_z1|K?oZ1nJn;qd{#!~W!BXHOAW^7|l{ zky4WG?4?RhK%38%2}~J@bBmnYL^3N6Aeq^nhl$9(`JMmztvB9!XCs?TW=eox?lZ&V zg&e}+JJluhCcRcB=H(-Ms!PzMyo5%lRd9ixi_cRz?CoZigw`8G(e&2saOt(ZR(EoF zV{=b=39d}l&XkwX?=3H3uo|66*0pFF@iZoHc;~LkxUX#wSouOtN?+`A5i|i_L@eI= zh{|6u_8p_z;?f9ts6m|!hwskEfw2(jJbn~QXtnE*o1|qD69@^zqpcRV%jO*z8yg+& z?eA!5LnXAqsgu}9&u~Yn;lK=s;tB-fYJrQgC8ihyUl8C?Es3&?w~17V^uLss+qCD_Cv$|P8;yofa5|Q*a8S6!GPY=)R>q) zeB{`PQzuWJogShd4s`F#Fr3zJ|M-n-^8lT(7|gI2v1`P_m)#I5paqsh_Ya@wuYZBHv@ zAqX6?snsH`))R01J<>ya zCx`-H9p^Aq_H!Ao$YcRhoWm6qV6tlfbwLzUNtm)uudfG${xR@}j!sUx&0-+if^;w# zukZ~1q2ZC?;X|F27D9|VuNG_ZBdiYh8>FQ&0k1#_()m=3==`O#`Fzfc!kwMHJ?Rt# zi?_EixFq*WibuH(;OZI;Y8js+sv$v@OmuY$+x&i=5L$ogeM5U~H7}9|r18}iWh+XK zp?Jycy$qLGRH!9TS5A488DeJcGd<{3No6XX)nqhLyhJZnc9?**=c*ho ze=m+ZkE%)vfbxu<{?W1HCwzbmjUz;*QZPu-TogcuDmnLx#bj`CaXrhi+c{hDtu;7Z z%PTlULCD~7l2|fD8E16VeoyZ>Cutlp~KM80?y20#@Ve`1D$CjoQd9_%$cC-K)Qf#!KJvV#`2Qnli|g6 zfl?{u&9?t!3IzwWiO}YTu#KQy#0b~AxSJc%Lc<4|CGXD70jd-!aHODis12$@CAYRT z7fyxanL+{Bj>Y*qp*WT^{{*3^Tk$z=SKML|AD>SqHK3 z&fP2T+`7BH4RVUeX)gtA*_0H?x|NcMz(?5Dz z2`!3QC5(FM*2d;m6lGG@)^}mZWv*OpvYAb4xzc11B=f1CfI}7&ATlxU9Rc;mMop~X z09Lz;f2B2^R-X%H5i}ZWYs>uR#s|bAGb$|o?WFi1=p1_r*l!1$1?>4e5R)P8?{0Cl z08cc06jSNM%;;EO3u-v&O%8vzWbnwDbLY;Sn5L$HK_rZ5j;VtTJ1Jk^*`Zwf+yDAU z&GPlk3<{yTp;0_*zCVJ9wa89jmbfe6&q<|J7zQV;sl8A)7x(-L8Q~e+rY4ts5JTn^`+vyCZ z7YaXdfjr_QqmhjVq4v=C;r0ZW9YO=ljzR@#ZP1^jPSgkOusu+`Uwg0SJs@h2wOto? z>~I-ccA1w?5NICN!j2XbS;V>owWhQQyh%wYl-oK62l|Hw+l;b2aGxu8=7J?I>U4`y ze9Qy~NGMT)HXwzy14N$!c_E$HX1AJPTv7_4yo69QD+D7^R3=&lvwtHNOo@p|Fs-wx z3Tvy&w{LH3WPuxHf8s#Qk)J!O5uiP|^-_+PiN+cjZi;<_-MaVes&7Ub+28K^Az|`< zGaRYpfgs)o5#9p;xIYlYo58p9_UlP*I>{4uhG?~k4yuY|y&SqZUv{K2yYh2~; z$=Ok6|E^h`;tTk@k#I;VPtTEBDd3P+K)=tW@{~YkGRb7^l1xUgSy>B(mH{mbLeoTM zbbBC$XR`pwe_4UDy-px<>>6$X65z86rK8P*s%5}6I2~4NKm!bDYrCw2DW#Dd69vQlORfzIew6+%D2HODRu)(PeaA*`|}%QQL+ngh#?CNDJ1fQ2F zapWd6dv8QjvpSTslSrhg?cheDEU*%qM+d6!X{6?kK9@$ESz5Yt{dyGGTwaB36Y+sN zP!_lqz!&b=X)3278A)+XbRB#1HaR$^z-Z%fgXflvsqOn8@u?Qo`@N z5oX)Npy+Egz6`Nf4b6cfns3rwSXrh397_goy|oN@>tZ;~F(M*pwWv9yf-gp!6pB<6 z`S(JlnhhldXo|;UHU})ITA11@W{Qc8mF49`wE{vOv?LBza+o$`~D> zl)yX}l(y&Y&fU6k{nq;8#^&0>+_md>?#?Z2<@siU0e7Q5wXlRI7fWTn3?=NPYLmr` z|AWFwoWSf5hbOf=oCdf_(E3L(QOgl6JWo7mD6_o0$QdyN;Du&vVOd%0ped}#IN~B#z_i3n7CIhJf zy&;n^7*VQ^GW_|UYa3P~i3?mFtSd=cm@h>-*oaLMMQ)2lDF3t&EVA2U5mqxn*qUow zc`o?PsYG_^?i{%}R9Fa~1|c1VM97)7qv~>93zb!PlaQ6SxdFg}09rl?e<8Y+nxzWt z7=9hkoT+gHSOwMO3W(>+t4muYnX?@hve%_Yp*V?Bp%8Pb;7X*yn+5rWZw1QLYoi6l z)py^$aeFyVk>|VWHAHtH3Ic$4zL;5FUWy5Aeveg&UeFq7G7HVMav@LliH(i4SnKqA zaR!4D1bb_v(F2ocjZIXB+p;tlMRDmBgU#hC@+2auQe!ZiR0yD5ra{Q%t}IZ+xwjTq z);8AH1ESzI3XUsfO)vN|d8ZuUi>WjerB^oyjg~@@WTF?YGCDjh7UWRHm3(Y*{_Zly zRfpiTj2s#9icH@M5gxyhDE}*Prla?>i6o>XD)rfpY9U*wa`~xbT`GXAro*n{C4!;Q zQmDu^I?XC>C5Cwt!Jmj#jrU=CS0YtvjRu1TwK}N^DhyTxYKRnL>+m{+xy?nep*G@t z2?Rq6u!t3wPWZolev^V1Us+lWrvWRJz|P@_l<)*(BJS*=YN^7-+yKarLy6$+YRhZ4 z=E4P}h9aptk6T+_2CftARV)n!N*09Hmm94i8GI`JT4ODFaiNMQmhPZtyu4O}ESK3L zgu-=Wk%vS9K3Vr}cAhZbt*3J3(g?uq8mWV~-Io;yi zYr>KKRsg^~0WlVstjMeZkw%-zd|RdY_TJVNn)f7V%C9$Cy8mg+Rjv7V%vGs*rNp-c zK|bMx2QG6%l&NuVF+RV{Yk!)0Iq=JDmV^DoJpy#tzE5V(fr;Aqqyyj5mdbIs-N|j0 z1^sC;7}YAwc2N$K7--^DK%|hsJ>*ci#*;pd@ARUyu2illhgP=vcE@z5yj)V#g<=NK zt<4QU<@q`-O3NAOI#EGSp!zBf2oTQ;q0OyGos+l}%dL|r3ZgmWtGJ~*cBe3ACfHd!(yYNwk4~=UD!n-vS`dNC7F}spi`~fd9K9GS1L{0E%8b~E5xx12j>dKr zEROEhDpBbvS``{Tb=NDZ+ECrC4b%jfj0U~GA;2K!2L$wDj7Db)4#x{*c(B1$Qjm}Q zEq1(;#w3J>4<4O7W<+$5rlqD{oXXPLrwhiqRUFEU4Hq0Z+Z^?5b7M1B*>y6b$^bov zx0;&dW(6+-F{u93YJH%8uLG044Iwmy-tExp^ub6_Y#f5aje5lL4{Y~j_3gRW7rVFMDZ#>J2^Hef)mY{5aM zo|85xM3Tl}3ba}L?MAGm!^@sdXNfjPhoO^K!*Tv_r=s-YRbAcJ)7nsft*q&4Z6_)X zXJ0StsOxNOfX6zVP@S|Cw8xC0dYd)CU(1{wEsc$>osFGXOVHbcUipekb(gA8&vuJr z)Y@A*n;RONVNa~Tw(CQU7)^17XbwUUHx9eTMn{K*nFIZ*n?lS@s6HeurUyl(jPkRy z!ExP~T@+(R1}-m`oenmSzK^yAeEfWCR-mios%6EEU3SEn={ma zf+CeyI6UT}e178%S6O;v{N7Y{J3Zwyhy-JLD%tnOxWgT56@4F;I^4_N^GsUANLr(?c`*NcJz*K>!%RNsb4gQAhz z*501xr}7U*``2#PN@-|SY%sL7T`$bN#v(@#k1!bAu`Fzb>iw`P93Q=(Umw({wxax6 zWh1(<$dP6ONp9;{60gGDn=-^jK&!Ya7W8r)|_Uv&(886OuBW_3M3CT7@(7@IlJY~T)@^R319EI8Lk<~XgF!@gA+r8BEkY(&ksiU{X(#od&tn1tJtqLRmLe!CRbTZ*RV3V zsik?yB)VoYH8;DiiBdZa1)eJ}xq1MMsHR-H@-VIr({-ULjMPYuc=@Yb(c6MoM!(a~%zjOAYE#hcysA<)h0@Qd>#cRETWA&*{)7KJ?J%4aJ z_Es`9JFr|X$l}j?YJR$Kv9yv^Vwld^+1}bLo3LRTt1di!t{(NXALeOcb39pbI2ucf zF4beD_^^p%2VuCTPgY}?U!_Iet2SAT2DLNP>BMPgSktT3VAFzDCo?4^Bt)9~Q02Je z=5rAWiD7+Sb4N#$wyUkFovE;R5j!WVyK&4@OMAUyh#ZDZDw4;H8j6jS;u4Z4j>Vj( zC=BtpHe>4ujh`b>!U<*=+49$8iy>7Xwz5G3(}3q<#7#obu>de_YF}qlMe&7;70u1P zz36H9sR}Q=T{7N|18@vatD^T;^cEB;MjRkgTT$K3MSX9vX;=>;w5!*Zb7JA!KyT<8^F;~O;OQ?h{gbSL$`vZs+eIMphFhJiZugktt~G( zd*NzL2lm5o&(NS57uc2$QRnZc)w0e`oRERO4V`}&`k$gqdV|)_V)EDP)aaPR9yJn9 zWJOeB#XL4!^T#nxh*=|gCH&BwLC1ouLx!IQy~L-xr@Ir=0@0h&T!&>J6}8yz7sDF{ zlbSiY#6ANZ;)n^vDi?Fzn(Axuri)Eo(2UcgX@|oev6#kNTa=14@IAVpcf3#bdB;(Y zXLn=Q_x2u#q&Orewno)z{DX%F*@FFh5jo{mP0ogfUX>goUS;gI_~?ip3+7kqdePn6 z*k_8i*+e*eUh{d^=QFQ&u_evrYb99QUR7O-rW{5&d%LdxbhWBxXmdq%3-T-uov**D zsGBM)ua~Q21hA(ERSl|~_Rfxmwz{(GRm{_pIVHmBk9|af#KvKm+!7NNi*s(n#EXLH zkWu^T6K)@SThb7*>wSEvWyT%%%(!FnqzU84Ot?KcGRhJXf;RFXd@=lXTTc(>TDAuV z;=n+s-D)tZuvP(Q=Gvpf!!YG0qCw-24(g(R>^0%oK$R>vG;m(qSbDV)joOwpeRG2o zMS~&G=OU%pt=8IFw0&G9F_q{hA zZ>e%CcF{)->MpfhCADX!<|_DglAanysi_c3uDK7w1u#k6VI-% zF3l;%R9Unrpm=TA0vMm#Hm8gZBFx)DOHF3IiP%|)^>zEB8HGt)MoVlc#=x=FfD`R8 zKkClG^Z>j%iqQ&FuwUD?(kqwC%4^X>rt%L6G6w298@qJP*GjHc)imLhalJXnj*~&V zYZ|ar9m_##y7A6#55l`^Z&z$n4VD32Yt(c#_h5%28+IE|IlJ0w%gZkRRC=w=FW97U zzqyE*{sixvpHiLak6)>>UQJ^${o1{0%yahkz@C%GasNoIMu$>f>8QiPrfXNOwlV+k zMC|V#YR$vf|Dg{|TS)K+e~Wvh~Ho|NSi@0rYI9~4qbn0I0fz{_gdqU;M;?vSFTB%J7M+IAj`2<%RWVx=cRgE1Y z+9evbHEGeYk3PwEgTYx>$7RWte`)@1?mvD&KlHhuek!`=46;Zy=nuct(q}OH*Vkk8 zP21NX`D>tufw8f}CZ&uYHY`GispV}2Cr@7KXf1E8De$YUmetpST3&y-JN)O%3&p*a>}T(uA>Cd22KdPBI42IqSos1A~lc{M#bq zM~xbpJZ|z-%$G_HrtOnUt~FO*uXnV1PH2ju!zgs9y84>0HH}V=j7;#Ne%G%6kbO!$KHlC(j{0HG1|33K9=@dIz zbk{ecPo&Ra3=D}xzjSYF6;|70_Z+9u9F7sMDAAQ4X)&0hhTfKFGikbeuu~oOVPZX4 zXJoWvT8U`?V2KTSdx9~9V!}+gnzp{R!*CdS>(#4Qs!(;Pbq?g2(yNuNDwC-TFS&{- z6j=`wu+Sili;fOjtHw|VdeB|1x4H1)pzD>FuT)oGi--%hbat9et=QF}8yjch{=^}1 zG2IT#jKCpIk#vGmW>)5@f`YQLf`XhAnVDIhqs8zN;+=zaKinML?FjJi?Dj`Z-HxNz z9oTni@Zk8QVexU^V^gg-GY;Tr}Yw1lqX7*vmwX?l9#RXW_+<=c%SKC5eR zlufNh3-!_z;K%}$HqO5IxGC{bVWHR*+v`tvItE-D_^bEU*G8a2)`~$XyuP8hbNxe+Cw^Epadw6!Rccd^#S(-e zcp(HQrCKUjn9@~#KDP)h?@>u%e%Q8Ru*>(g#{&vLW6-(UG5D^fRCc2OA!(2QhB+%j0+Rj0$OWvuqkYb>*^Mbs%5}E!hWh66FsF9z3G|& z|Mu2Usmg6%T+>Kc55y*^(lxE^{sEPWCuE_xrdK4bT2iYOezGq;RTmq_5(WkMj}CSD zOgvrRtKd{*w+pF$k%>&FkBM_yZgW3Dv?Sh0Wx?L|Wn_e;7GdEMPN%~M0TW?E!eCoI ziKFiy1_%zCrLwdZuah_~S3;z4gf1cwyY3Djnuvvsw+%HI8|rXeV2w^z>#!+Vbpw@h zF^rk8YEdQJ60h#!n~M2;3^QlwPiBe{dRtb8VHWz7mATSOq@=pEw3o>|U5!~_L4jTF zFo>2a&+Q%k+f;UOJv~M2M3Qp7s`7fx#h+R`(21?=k_Uze`ii*x&9U7^uG|eP>vMyb!Mvs;lGj{f^9fDJ1#4I1IE9&aR zWNV$XBp35d%37R$gT$(??#5C~=D+UX!O^bxdYs}W&i_@3QEkEU+Ja#Pi;zjJw z({9FSf-8Mj!u9e0+FXkpXK0A7fPZDKMcNG$G~A`wgLrb1A;6B>xWkFrSs0pkMn_=> zO+`O>(!_}g2`RJhxhF`4-t5RQbZ?~^@Mah{v9IFNRVpB@IIjffO;%vgHY&)50u5zt zRH$2PN5O5Iv$hudNp@q&cSMLi&=!vU+EwvrWUHl`ipwP>Y1;UtA)`kRn|6O1FDdbi z_dW`n!>wjD`V2Z-I&cI>*-uyNy1GR363b1+T3XB*ti)gj_lv-)%Scf{CW+H;u<^L0 zGxLUPCB+Kf&APBKPGWI!BPUOaVVHJuwXCsEcF%Egz2kUxTmBQCIL+88h2xC?EnFOf z)2E;vBx85x#>%p*Shv{Xl>9B|_r-qP*r5jd$M}gE2iP+-ILN5(#%7X@Z3gW36&j1V z;6oEaQOD^O8>>R|6o+&5qV0)8*DxX9f{ubff1RTa_4U=Vs`?h@Z%K=hRG2P~E(dJi zkG0~_(Xr8Xi$=_|La$O+ufNT$g=O%izo}b};H+0PFBEiP;WZWAQjX^8awJ4$Yo8oo z4a50DApvH*r20W&jREjtpamyWU}UAS3G*xh!v_r+I&Ao0OmnfuqVB|w`ypYNejXJS zhH40FuoQn=c*1SBj~P2|bOKVnUqJfShGL7P|9v?Am7uyxiT*wF|C3B(4U)7@6yg8e z7L~sWPO@Ofoi~=vRPRXx>b8M(qW7Bnc^K{R%<%Z1eD*t`PyU^4ehM~9=;&0iKN`c! zyP7MVqR_%g^;&HAYO@&CQXl5cU^+O;O-!J}evdtkII^#<9m8sBObW0C>eAzT0sJt zo5S5&9|M1hSD=6vhqJZEk*w1er3YY^j-oU>mA!i%^X7TRy~p9s5UlY*)vD8DmM)e* z;mie&Tp`(Q#`KV`E=QjvV}~!%4~ZdIjYg%>qL;=~mQRBHFn=7#AnY4a{X_9B7W9f~ zvBcGGGo^p_t2z$*GYs=tpzc2V>xdedr*x@Zv{|#j1vJCLJcF z_yvazN=OV#w>LF)F+JX#0{s;X75ZeiZK|`4R&@J>7&U8|U%DzHBG@Pfla0X<{qE5R zaE;RIXHOiGkdUr#X{l>bn?eKqOxphUSeU=g96Nl%m?6V)kJjWD6lzkp)OcndqYcC? zi53&qWR1OTP@)yxs!kl`8g90yTd)zDe~jIvpl{C@XpBsX3DNd)x7!i0=Wo@aGf^8Z zI;iaF{_TC(HC?YkS97l?EvPM1QgZ21?!~f(bZrgRr73vlbGt!TjVw6C9uRIz*LL(_ zo_Ubsu^WRY1o-o|daXZZ{;1`MP+NKcre619>bG8x7rTxIw>zRwiU;E~467P*P2!T0 z2FHfj0@1f%q{+46>EUR)`vnArx#U{qs zMYcUCP$#;z035{Zbvv;!LzlyejyjyI)>vJG69bx> zJGq#VhrKYF($nn@XB6BtsG`GdLBS2x*qEeANjFR!6df9<=NkAkX{`G`EAG>_qjRA< z)NWj-tA&Rbi-vana^M`tC+LFGn0pMo5avxj_xybil}vXYfFra!TVukpejA2o+}P4er^`i z*0HDv9q;H-z)Yrkw_ioPbG;4|wUKt%${o{@Ld^kMmEG)@ZtcPm8<;QUcfGQ?S1l$? z#>HqHo_s3eUDoJqmULF`dz&LuFGk@OfCgjDE*X93s_Nua=$IL?6dX_@@sV_y|K38hk@WV*5ZG z4IPQS7fet=liSrqxN44y2vDpsqhm}Vwum9O&lou--HdjZ$!4>L3>i5rdHl#hMtlGJ z@4Gi;)a?5opFL~d)OkO9{6CgHoNn%Q_6A|&;Si(R5<4U=JZ`Z2{zY&<$rRAnqzR0> z=bn4!+=#rojCOy{@}fGDJpdw0_Wjtqw(%-D(sYs$z@C^a!_`e&JZ0 znux|SPOYKCGelf_dYDNU(8r>E%?SqFJFv4xLM@NN)rw2FaRkBIjhodgK^;q*aY_QGG69?3mFd5pL zrJ&Gs4F)BmhYay(DWz|1^~KX=^?gl^9hDuD7;EyT7xqmugvN%UJnf&p?)Wq~R9t6^ zzAZc^-Kj^Y0XoJH8Jw3_sh;`GTm|(4D@X+Y^q0xh;r6eb$n`&w*DjP6WLKN5y*b~DY;{Uf& z;}*96jS}nspE6)M!as6g$%4`cFWdMGiyJ&x)Ns=$k4BMn+ijze5Vud5IC)C?$bqFy zcqGQHFe4pp3x5;lyzB559}p0XPQO8+p%Ky1;hu7A3Z52j4aAXF%rD3mo_O2vVS_>~ zfheWzVIi>?;7y03B_&UsIupB>&lo=vops~KjlvGdm^eOu?1X-)5E2y|VrSTz&*6D{8>6gaNp)Qo`G=_bAu9dtG@&Reifd8-QlFgk5#8{8)|gJd4Ee zzL79wRMN;%!-k-LKPkZy=qXcQfJ>?4xdCs<{zj}i6k8`xpPrI3W9E#R_svg6p+0_! z*%lrV5uS(HFRe%yEiRW59dZiW7iAm8aifav)l1ghsMXJaeI_G-D-}s z;mF&h=s<0DRw24-2K4Zp= zJ7>?HHFM&4F-nJ*V|_rtNGuph9y@l-?Sm5(>;R`=ahlU9V?%UGB?#cVJR8gTz!2|td*0fwVSjv6!AU~H;vZgO;0 zUBB+M+hP)iq2(~x+gENwU%AEF-P6?A8<#ljcBD=?`iLFO79{%aG)aiHguXTmwq3a< zy3Yr6ou^@BycaJDWjJIi9goCoBl-~8d~8Q7&zwDPAsh1_FTe0Ki+}nTFFwmGjNXCq zCMV-A8NYnLr5k?ZT@P2)zk1?@XITny9C6e$zj)zSZ0Iw;dgfOw#_fuliO>rd1VvuJ z3RKYbACuj`j~~gCPRbg-osDCdhmEfUUvkifKTLyS1|}tGAh9H2D#=s4ci|VGCv8BN zqzTdpDP9Vb%-Gx6$y!(yD`kZ&n`N;5Y&YA^wqP6Tb?g<$ zE7=NMZNb$g>;X0her=K8YRuTO0@7dP9d*M?;~?#oe^z(+o0*XM$0giF%~f^ndG|~R{h~Um{rP$8%7OpgY=>NNwaQ83#4!4vi7s0&--+3WPwuAq0=I0I#kI{hiN}!70!+nMJT5CQ{{nMcwc~ zgV2yy6m#X=+PRP(R%X@5H;Ge(WqGPROMl@K#AJy=`f2AEacaP^YsYD~Vkf?p?N z4tph^nNRu__w0(<2Hr~Ae2c3a2birTyQ{or&w8chUTLvcTHuw$(_D6Ed(EbJrDU(< z^Hq}9EXoxk{I=k$7r$b}m!#+p#-i(lSwE@l7G}Qtif?fhDHENA@4;xFwD%TfJ7Bgo z8gW8TBq`c!c1vlk$3`?_&1IMFdses-jaYM=A=cayVy)kOu4f?D+-A@)*C*O14aZez z83?^{OFlDWv?>aE71b2|FG^7rxEs0!`XuPkO=c%=VRks`+o)Yp+oCo{{c~yKfcw^x zja6Q=XT8#Lue8`J371_q7I@8OdnMmLF4+F9lYVH?>=Z^B(yAYYot%wG{9{A4OiDhz8Lvb z36}qnBYPMI(z_0z{SH!KP-2<*}r~9^e&EEA&>%7t{;vVsd@JTCO zCNNv!m6qVw0}*q;6t6VKD-HEZ!YqdFvw6)7UWt38@Gh^^;FZcfQiOOKxe|WSV-}w0 zm5zI*LmsK0jeQ=o@SR>s#DUBA-f)DA*dViT5gRTEaS%?S4@bz|5^T67arFTaBjJb# zw;5u?CE-894k9jYmcnCTY70k9gd+}IQW)aEEg=rv5;VdsA@yC7up5>w?1p7{%)+1p z?)#ADZVBn_mJr^X>>|AGs|c@K5+QQ=%Im){gx`JjDLipG!zXO9S6U!`i%-~W*MCS; zyi&4PO7cokUddAljeeDKe0Fpph(hgYieN)&e@HtdK4*C)hh1My@>EP;p@w}jYm zOTMc+V78U++vJtjdnIvo4PAY4fEn3<$y4@a;G+Lgnm6#OeTIF4eS|$8reR*m?3FYg z$>#J*EgnhOwN(kbwo;FotIT`#qA+J=<=NSw9;nRa_t27O#X@ zbDL3&3E75NbA4=2;ny-D+YnoB39;jrW_TrW-vqjEgx5@j*=0A(E1AVT;)69T?vK^! zm0CQK@9oK2E=rhpJZ`*(#Yb~uzu;`O(Tm>*L)%Jrjf5* z=ZimIT+y3wh48lp;c&G@!V&5z$5v}7t0*4*$()QN><^X zs|96A@mvkvhC1}ddVdPclEodCF`oM@L%mXrSIY28-q`Skqo40>-ut|M4zsw-!kACO zoNQ*{!CjzN^3~$~;@;~@zqItFsy79L8$8bqF84_NT=Duj>?yQ@mWw-rFW#J%zBKYl z!Fir^3qJ03`BrJq>@43O)q?>=VC&cY4h}@k$?fB%dbwVw_U? z0dYt020>4|&L#Pt<_oW{^bSt-^dtG=-uLz%J>1hT;49aHU&3>q#`kgbcHS#Jc4@(- zC6Hz_b|D83m!&89;o{UwGa%h3zkT801<||vy}Yhu&V##Q_DjxQd$FVk(n!uqI!c-# z1#))DR#JicF2fZOJ2LC!?85Ald|Zvp zgzyW-UM@D3Y=iVRQx|aZ%RrDQpz z*Em~USb~08DHvuSlq`VsU!3LdE13;x7|iq~Qy`6k|F)JSL&BDj7kNpN>*|G;k|;>S zIkPM%v0!>GPN^FW5 zadkgu*0zf|xQZ=LO4!9LNHv@l>n|csrD#a(;$BEw<-^4fSWiP*D}Q}qrS&MJ-SXEL zcUixKv>noS);*BsGgd5ewoB?G>0Uf{CP}X$-Lfxkz||M|q2eoK_B21VRwX2Eg&9&F zJ~+$|U04fAl77Pv;qJBk&{?6MvOErh(yp-n$k?0D3Nz^(KV%(6(hK}hk;oUabT?dZ zlJo>zO(y9h`IGbS{?3f6-{306gcQkG;hS{z4u0rZ14#}1(D#!_x)*kJBqj1g=TVNp zu9;U~IDK9PDOlzg4xOtK5_eo!UyPo6NsX9Ybzu#p0A6*j=)8zy3?diC3A?O^JF>5A zFu-h$GAnQY1)(7Z-hOURns6^so?2`x-UP|S9p|?fFM{+Ih6AtAb8*n&`KK<#!|XHe zIJfq#0-hF;{kkFm! zGCMy-v($x< z9+jt_n{d7i(oC5boh&ScG>z{nI(&YfFyoHmz2^}cWD|Aq$K+l(V@2NyiJPVF!hOZ) zhZp*Jerqvou!D-Za7zKSi2YHDDq53W2 z7w7p5T#doi{YCJB6a=#!=XXOIDYL?b@_tCkysL1oSzH~>s|u%|gDWU&+Rh)OP)2b_ zVa)nOn2qA?g>kQoI2|XO3N?NkAuW{eFU%_v?oE-WeV@G^`pD{)S?6CkFSO=Ya`^Xq z3&qvBB1MbV!9{JVNYN2|SN?nSq}#cpD7k0?%+wM)uPxJ0dOQ&^=E}Vfa&~S*5yHwo zW9%&IGe~DpzU3EfhxA8&q-c|f9gKk;%dc588?NZ_+&y$(CeJ*#rRXzU-6c=WKYb4I ziE%8Jf2at4VK4VJ7i1M}fwYgGD_Xq@`_1 zuW$;a4-v{Ja`|uk+_|dP*2Ct*bA1Q@#yMGiKzYsBm9RZlWAQ5*OQbh`2HA+50 ziF^Le51czxAk475>)g8q9A=#;Nkkh27jzVEfOL;M z^>`Vj@EDorA3VDgW+Rcddkf)TQA-qTKbHbYgR48wV%(IShpQ56mfBz%i{`X4WT_Tyt=R>e|cu8Yo{(XFtYKzaqb49ay2TJ6xVt zpnGE@eEowmt6)>1s5Kvz!_O`gaUo3-I#`IuGkvNE?I4*K?#t_d6ot6nS+HD~aYt@n z!7fO*Gj;w^+$&=Qmle3mO)F=|+6tc)_em`8^jQ%X*EuWPP$=rZ1YUW(uHXYm)8xsy z`|{xmyM$6;RPIigCCMy*L%{+e@$S44IdgHf8vdp{jZx#i{Hns?xOxY#I=eq#JYxp$ zKD#>rX$<$++3gskW=xeTd_Il0&|o$`d-gGTYR6u#@gaf;tNx#R46<-?QmH@R5_a9MhkAI{Av5SsFW{C0j)`hG~y%looY^JhZ3N8a}0 z$^r|dA&Th+p5z-RWgnRRaMsJtXa~o7oLi$|(CO;)#gzXAb z=eg?fUm{+X3qSKpekf-p`F;yOocBupB;0qqV$ON|j7Y8LF zAV?p}2acA>B9woX51jt)h0~C}lJ}k2i&V#2VtHH6zOyLJV3vMnj4X1=KKV@U@^3Cd z%2S!nE;=;>(%)3-Qx9+73h4!;ui>mPtCTOCIe7AMNWYVoo~7{e@0dC_iG1}M;*aVD zzMLP*bCnmL^22A==JmkFHu>$`p=l!iw#(n-IP-*`5yxj-x%fk9NmpJz%o3%gxh}I{ z##YTEms6ypc{|TcfwpJhK7HOU-1i#vG$U^{r0IC>2D)!0KYTh%gp$?s!%UwiT6oW; zB1KV)?0A3UnKnqd^5OT_zOaL&eV-o8T?Oe&dD~sZd2=AWtIRsGEpHK|Jo$_F7oSE7 zz(#s@7Ei{LG;mLZnz6@Ig{xTMn>z)+WOj@nKCv)Q#Qql|bh$!j-By@>l< zE%=Z5;hed-H84AX99MDL0O=nHNm1@TNGll2acRdMgmY7_h@;u^*T0#4Mx?(LS98SE zq>21cMh{6J@k6;!k@S-M_UVvR;e%hxhff=y&mw7G?)sd~knWSWkRFuJWPiL@_~3b!>33^RE`hWN zT7K!z`ys8DFPxln>`h4bA#7`M;vhXF7iK@JTPn;{)?+Wu689~TyMI_iTCg-#q=iYD zb$V4!JYDJdVL4q{BChaZjJWe zvRB!|ES0^*USe;vzd-t&eF-i5ik-pk5P4{C9$^<*Eo#4d*1#^YCMlSeN;a%k<5HwF zM$$|{fBg?^n|ohnj`%he+#8mNQH-_SEbjapGmJvZ%T`$_0n6? zxX%F1ki@)DU-{5bR^f&yy zEFF}-mtK)Fq)cgzbX+)%*&zDZ}1$>#5&40;%DV6YN_)6&#e}Vs(bcO$#uae66 zD}1$7!PoFLQZ+B)MN$pF#;-}WDDkVLI^N1#r3T)|aa<=Gfyrq|H*Urk`CdXDfsK%v zRE2LTzJ>T^YrX6(cv{Ko7v ze&`gQBFy-qAE^vtJNTg)HDuPz4`quoQf7aa+J5Xf1)XOO&W>+56$R-Ut}dsmAHePc zk{;xT4yBUxHC&D%=}Ug-#0-)?;q1q~*~rD1U&cBA1#MQLBiD2ABP^-;J)87_B7%i(jOW7aWN!`{RjR20d614 zM#;|t;5N&C0n!)nbyha&Jhls0tFn=28TuejewV!v(t6I0?9ZMH=`Wlex|BT~5?sl8 zIC~tVZrB~3jWUJ(2(ydXA{L(GEPGsb2&8q8EZL~V*`v6cn2l16?c{8K$d5gUZSI{b zwggM}IK%8M`YvHJ;k6-H>334SS-n!7td7Tu@SUpFszs`Ca-p0dZIIX{E#gZo4HI{pTKlmC{l=Wp>1 z{B6FGzr(li9sCRaSN=8M$G_p<^6&Xkp2@TLF@Bt%Prdz1KigoN}Z6N`p`OpYpLxSFNZpA16`W-9gY>@lU` zL>51x)SoCKcWY0i!ri75B3(L8?1j56C-%eLD##m@S`e{)Vk6uY@;gd1?mP-NQiZ!G zP)+#)$IdeT?DqGobG>V@h<_nU~n|80nliIUB5KlTb2X?Ohb`Jkq zIgqsgp)7ed8TK}^C9o%WTKR}Ag8qFnY$5zCOoX44_+{k?6Q$`2#0Qj#m(NBCk;zk3EfT5?eAqmQ_l9=0Ujo z0EGtr_+gXq!7($Q-IjF{&u-4@!?SCTnUOLmFET~$JD*jKEsNK)z#|=$PKWsx76e~^ zqf}*;!S793d9W`i>~DkpD#!(RqM4Tp6H$^dc2tD+9EG;^h|oG$%m_l)v$ilD+zrio zT7+$XDekSwS|dW0u?6n7XT5;Cs65n;>b|Bu4JNh z;QJ`fOtc1m*sgSE7DL9hAB;+~=U&)t%slS98^6)F>G>$tM??+m{jP~oRys2`D~_W`0oZSb+?6qdhx5{{+u{bNStOKjru1$fqbXJbLR( zYvenaBOaZeXZ!4^SNiLK@JoOH47d@y z4IhCHA{?D4|C%#TE1d{;m(RZP-BEX(;ht_$4&wiRA8B*tebcu}CgRlJr6+W)5#jXc zK@Z$&5^n9HHXq^<<%&!1lon6C3tMorBQxJ)rnHOn`9bGOCwDwoyLGGEb$@>%AK@9E zkNduApPxKel*6LzIVx(`8$O=6?C*~27Nz4BX5F_i>*yCAHR9CIZNJ?WXX2Nr7m!~N zhwd=lA}pT!T((_QE)UErD97Dpm&f1k`y3u!>~GsAd(y>a;tB)eJp=y1_vVjCV^6)n z;z*B>zePRs&z}SLikSg*N1eFOlP|>oquz(0?dq0)Xi@Sqk1JW?r{`XX5iUQu+(5iF z_S3_zf%(YgkVnVJo%fV(kNd8xs0%ZMq%=Y&Q5U*Qp{q6h+(lX;%$r4-i+I63-653e8TzpVBxYLZ{*_AH!$Wsj- zopj%~r(aqMdo8}-N&|G5bPVy;gmzd1bj9gudx%7ZF{6=0^@GQFKEw9Ay zemY)>wq>y|4xtOhN{?HQN^q^#6E_3fPiQA1&O{i**X0-GkkH>YM$jocHA;?|nD6Pt-^0lG2WJcZ#(2#kbea zHBY`2Ca!QQS8mL|NSm!UmL*4#f8b`7pzkxFe5&*L8+E!Xk6nWu^hfmnD7$W`cbZ-P zW(g=;+FZ|JNk4QcSD}xsZof@--(xn@yy%UOPgt z{C}915<7){KaQS9Jyt>oVV$;>+3;={g3*I;7J>D&Q7neVvN$$~#j^ywHzuL~dL$do zZf9dKwlJAZWhv|qyjRX*cd@(KTy_t;m)(b6hzHn%Y$033eui;}N7!PleO}5QW6Rht z*yC(DdxHIvJ;R=3zhSG`%NUzj%~G**!CID%HPdgfH_=DC9%B`6vv;tH`giO-_CDK$ zb>$zjkJz8tHufpoj**MqY!CZ}earsF_OrjU@7MvB!H%<&7{fTlPGejn2jd!NSw1UZ zg{%lO5ilJL)5I=gopw39&MGk;UxS$yO{|%vRQs+59x z7}KRYFmf^rBf4{>yQR6(J?MFzFWo2IFFk@$l1HT_(o&44Jc03%Cov-Otn?g4Ri2k# zKsv8U6{&3kIpR>PaaSXi^q?7kYNS>WXhB-3vHT+xcZ4CeWTaODWRX%5rIm!#8Vy;b zn8YUHPok8QDeYvW-W@OzDJUTg?*i{eDoRMldqI(w5~ZdM>A4W{BBZ8-^jrciMQTcv zo-$JOmw5U!_(Lx@{$!-?%b-YItny}SaOdmzlabcxkk=uF{gK9RV-@r}NaX;e^Ct9h zi*(i@r9T4yjFi?^e=4MYA!4ow z>5eIgNO?|a&nfje(!UZWLI*gsppgZWCYVSUOr#Ae=tD2!8~VWX&lYtceCW?&8WENK=qLo@CM z=SuUKpU@2Oe(8Sj5orks&0tpO#*^SP(n=Oc+7S$2i};uEU+4rUo#3Psm|Te*%t;#p zpbdjyDspNtrFSr;w@m3RQ+msk-ZG`POzACCdPh-uMPAX+6Q#PA zQe8`_uBBAhQmSi_>aXG)U6In3ekpCGl(tezt0<*al+r3nX%(e(7^QR=rL>JwT1_df zrj%AwN~@95XQ1~v_`~EJq_i0+U4+n{$6qYcycn`bb%|14qEweC)g?-GiBdg*Qr%9e zZbzzjqNMA=UmT@9c8@~Z>nQDYl=eDG`*5Va*bl-e89|f{NEQ6CSd0R(SuOSmOFy zUUc){vK32o55N1fW%OtJ8T~CPcK?0sm6klz^We4zXL|pJJ~;G&ix2fYuyd0NJ_mGl=gEhZ&zTFio% z=69q#JN59?B~zzNRZVG{k~gVr(*8*c#~&V_a(i2H)2OK7FAiRkkP`b{Bvveh&Ip|m z!fgvIUBUB$b%9IFJ%LM1r;S?-6}mRf4%H+TM?S#8;V2buuIWSnrMiCNzggS={Qpwl zi~8X8f4kQI4D%Lt{Y!O!?tt3Am|ggPto{GBx_=z@g}GJze<#ZNe^&o5zqti)%dyxU zd^g`iW3ir**sL2yVRJAJo7Zn7_7cC$ucF`OE5^PmLYqkt??PZBoAf2hGnVlAQOpSt z^d~+~<}V1MbeEJXJ1;R#l!xgJNgPeY98I_|hB%RU2XQ8G7V$3P-Nd=Xdx-ZE?;|cCKA^Pi%wi7`7m~>$ z;?IZ=6CWWiCSnvGPhU!WjJS;W3*zI%*NAI~sl+tmT4Fl!GiKPC2QAj_NQ5T)eIn{S zBTRM^_YiX#`yvZ<5&N=B%!vlkzX75*97KQFuH9&<3ic8eLDW=&GEq&`5Ygwh>pP^d zpg++7cXmnv!Zp-jf+ix~c_9Z9gNT?Z2NMf%JY%14kmf=@DBVMxN1RW*kBGUsxcUeY zy||DcB`zT@W$gVz)K~1weCY`m{D}%}8^I?9MN3j?{c-|yO;9H4i5N?PNf6OOv=VJZ zJ26D5**Q-vgZ|P2T@#EY#t{b*&p7DvGHD{olZcavXa(cyT;>PY?jfT6 z3wb^ftzgJ#1%rPqFoI0r7Qq0KgRsQCzxi(PH2@O?uPA<5ROcoJ;Mtqp~2yroS3GwGh?F?w8c*JbHv{e zR}o((zDit8e2rXKLwub~Qb|rDt|g`u-&U^wrH{Qse3$q;;%CaW?cYM*E5ThzyM@sA z!X2g1cfmcxZOM@0kM!+L_AMKk0kE7K)gsSAzmhy63dC# ziIv1^Vhz#3jDHcctpt0CiXifwpiEQ~HAIo;PE(#cO?mD#@|^MW3CMGTc47!Ilo&<~ zCq{}ViurzE3=tX$8KoUKiimO!axxL+9OSV?&uS@!IE^@+ zcn5I?aV|4|-i16ZIFC4=cpve8;v>Yx#7Bush)Y>8Y(2(;5GTvw+63h6#Lq*Jvjv|d zJ_Ga9(zC?ph${uz98vmjFGP+Mj3uJhdh{w_<)SkA4KU7B33|@{vcun zMClJAeBewXN`J_A5mEX>M(Gct^aoM;gDCw$l>Q(}e-NcVh|(WK=?|jx2T}TiDE+~u zM3nxJml1zKMClKc*NAI~sl+rQN`G8M=?@||Y#W8#PywR!-=2fqP`u56+#tAzi1x#F zCfW~RFHsSc7*QswiD*B-9HINvC~ABVH9m+MA4C|z`-un(WP}AojSnJ3AVTz3lBn@N zO+-qvPcI@R1)mfYyWJ@#wv9j;EhrQ9L^CmnXdzmOHlm#vqWno3!TzpP{kfcdM?65x zARZ^4BxVy&5l<7(5OaumO8uWdU}uT>!~$X=u}CRdG=X8>9_}e7UQlZOIGkN1xrBI` zSV}A>UME%(tBEy&QY0~o7(+}XCJ{#wM-!8YV~Aq~kvyxB>=b8fo)&QiBF;d>8HhLo z5oaLc3`CrPh%*pz28Jkq_+*!evrqPlI0F%9AmR)}oPmfl5OD?~&OpQ&h&TfgXJDSv zuyr`%Ofa8VKrAF8&K^=B&cxMXBI4{1U5GPbQbN2;EG3o`uM;bY)x;V>#F=0eF@~5( zOd^gVjwU7(#}LN~qE|>_hEI0ly;o2s>WSF*6DC1K3(-on5$(hf;%7?RpI$J14KImL^}gSI|D>J14KImL^}gSI|B?+u6?+Vg~DyL zT8Xj5IN~5;JTZYdn3zNyK^#dOO}w2rhB%RU2XQ8G7V$3P-Nd=Xdx-ZE?;|cCK0rQv zkhqXc77>3&e311j;x|h5){Us|1^-6ePxPlkPV}dX z4@x?G^h7fe?>zWeh*%SYkBw+2h7e~Xy|Z4|A-x4OdLZTOPs2-e_SD@ z5T_BR6Yn6-5JXD7n##mThjMDoP^6WhOw<$2#2})DXeHW+c4CO~N7YRBfYR}Y2J{mM zzRg0ml%oe*@Ll5Xh`X5KV^ONsd~g~mCU}6DK|D@8Nz5jmBAzCmA?6VCl%_NrQcN(P zSU@Z!7AZfyS%?%9SBr^Au`NwVF=0|dyi6=5mJ_cND~Z*_8bPFhU=%Tim`F?_jv|gG zCKJaH#|l!Jm$h0Y@&JfD03r{7$O9nq0Ej#QA`gJb10eDM7@};!nik}N&3cgs{(yYU z{Qt0C>T<2VOrc^1z4R ziaY=!4}i!6Ao2i+JTUDukq00n4}gf#kG6>T<2SDTj5P1MZ9srRCK;!`s zd0_K)kq1EJ0T6j$^LCL3K;!`sc>qKn0Fehi{8r=v5P1MZ9srRCK;!`sc>qKn0Fehk zfXE3Tasr5)03s)RREC`JI&uO={*+U%Vswg#oB$a)0Ypv!krP1V z1Q0m^L{0#a6F}qyybB;FY%Ui$0fermYO;gsy|obr8A^Lf1j)ItX0{ zq3df5Lf1j)ItX0{q3a-YJw03KI%McNh?M!TLg+eV=sE~p2chdAbRC4QgV1#lx(-6u z*BFGZgV1#lx(-6uLFhUNT?e7-Aaossu7l8ZxWL$Jh(C;>F!uYCqUHcmbAYHhK-3%{ zY7P)J2Z)*jM9l%B<^WN1fQ?AAG3ak)n-`1sIuLy+;PYhuf*`bCQi?w8LOKb`L_N_= z3?f>HR-%n)Cx$5bncx!NR13 zc$rvAEGJ$kRuZd;HN-}iidbq=I#9PX6I+RG#7<^hy&OHog5AU(BKrAOKZO!W5dC~0 z`uSEr1y$C5uofkfc5@s`Bw>PnKG2W->Vvt^O+kO6ff+xXf}UcyjGkga6VXfzBnANWGcky0AzFzxqMaB54n;{&{eBfn0>KDkBr%FOi8z@! zm6$?AN$~Cmq9g#9lgSgrUlN}oK1ciwaTW1p;;Y2f#Mg=IlomYw4I);pLB{Gb5bJ}$ zw}@|JTp$@Gf#AEu-x1$aI^La$9qO-g{WI zFE;K*jGAGxo4ALF_h7^=-h)BB2ZMMI2Js#Y;yoC|doYOiU=Z)YAl`#PyaywObD8bE zHKLUP6SOiwv@$@nGC;I4z#<}A8IaM+0MWhx(Y^rDz5vm_0MWhx(Y^rDz5vm_0MWhx z8`;`-+Av>%y}JW*mIPagZNyHdeZLK5fM7SVhlt*uO&ZbL1ERMFL~qX~4axxa?o5;c zY*Prz0AYgO9?*~d=X+~V1_=5S4NMC;K)8l7K+r@q69b7s#9*R@Y2Rr>8PM@g1IhqF zJ28Y9N(>{06VVRZ6!d?{yYu)gs=NRHGm`}fTSy>;eN70KDg+1+2%D@C2t)`;2q7R# zK$eIWBA`aJlq#Zfk2;9`WB68bGQINV-ks<^HifmG)>`T}}SX9pM^?8TQ-uLhO zxc~pc!>5OH&2`SqoMmRtb!NuSgOKwesMo@! zHtMzTP5OX~tS6)6i#+-OGha?|r^$D!oF)t8LsrW40an&8(+89+WHylukr9%avBXEo znzELRk+o&4E1{6mnX*tm zDrd_%@(KC0D_vNZKERwOeYXsHpb?JBuizVE(YSd*4&u9Kh0 zjdGLREWeUl@&o2yUmu77l zE9=O*vYu>UTS2=t8_Rf^AY02eGEugZ?PUkA`m-<7E-ja2*;#gxDelO^nzT!^o9r%o z$eyydJMvsI?b7Tg`^!{!?zw}sOLKr6B-7*&nI*I32$>^u-37ii${ks77wz)Mf?C{0Ub46BEBnh-d7m68)8t^8?)J`@%N3nDR52NH zxXhGUGF#@zk?zd=5=QuJTSmz|Ia-d9kIBbnk$h6ll}|~o5lDe+1mqe4xkf;)5s+&H zeAfMW#(YKr&4u!L`GPC`Z6ap|<|4URz9e6fZ^)%`ndBZ9mS+TU{;P~2eiKA7g19-T zVk3w%=GX`#GJ=TQTiiL45k&LPa;@~dJgx>k?|wM}_fk7f&!0r?FhgX7jFeHbrmQ7n zWNjIX1=Nn+Sl6wbw}hVBtS=kLhO)7iX(F4-X0o}AlPzR|yj$KQ+sL-Eo$Mr&m2zj9 zBD=~S`m~qqE&Iy;GF9Fu2g)=#Sf;yE^E0Va=1|3C$l)?mX31=sBS*UZ^OLDlwjV~x zJULp9kz=)P;z93Ab`J#w%6a{e5Sjux|Dek%{k!}5qcCQrzd@~rnOzI)E?p~TP2 zi?UQ+X039)EApzmCVwEuKHiV=C+SSySZ?xb7>3DkS;@*;Sy@)Gbo{EanyfAv zUm?DRWR!()v}CM>Fk@m*_u;5%GA4$xvW~1P>&XVTbsRO##xh4nJY)hvF(pRN^X%`WwHB~eZI|WRP-`!dN<*( z;+)p3CacRxSwlw2Xjv0qrgdI=WDTv;tSw_@9a&e_lZ=Eu zl}_t4Iig~`>+!~-aL_eEHnGfev(-vgj3z6r>u>d1LPo?CWpu@nJq`i9LW{u6W`et=MzUcvprd~ z+-6%m)!k-WAhRuyt2W}NO0L}q7nt0E<-6Ta6w*>n#+;EcXQWR=dPAfiM8=$vF=wnT zW8LcMjX8ti3WxJsvx#gfo5|)fPPUK<@@{#LY$MyscCwS~A$!T*vajqfQ{{bfpiGm4 zCFde@?y_?c9IBWMIb3GSESW8H+io*tU$4d2+NIBOjBG%Od%toGYJ_PxI5k zZti9?=gHs5`SQ2&S+5Cu?>V_pJ}=*PW#qm}u9oj{Ls(7jb~FDh*UI$%;Xf9yCtgtrP%Xpa}Tgx^wQMQxqC9UW2n$~)d)`PSj7PIi(#WG~rU_Lco*s=QAQlxcFX94a&9 zaG5EyWVXzaxpI`ulcVJr`IvlM7Re{&T=|q-z|r%_?1LOV&HA!|Y$%guXUX+A%Wyr8 zTo>awIYCa5)8tH9C?A!x-FeddK(3QKwS$#c%Sv9&!U)cDP3}#^DwYoC zxn?z4T}H|pGD=3vnv&lx3&;EXc8UCUiLtVdtSjqDW@XJjNgFnqm4(d8LS|(lv$Bv` zS;(v`WL6e3D+@a)uTDxQS$39PWQsdCdk@D@vzzQLd&r)$w>$G_Q_geEezLzzbr)yv z;23HSkb`8J93r!1wj3dIBy*P7x6E0>hpcqyO{|Q#TTxG=dr>Zui{(r575Ro-DwoOS za)sMpm`Tn3ws0vmcgKtk)Le75JnOZYIhV1$Mi1Ag^vufgA;nKOy~(uXfASo}da}N3 zAREeL*;%H@u5zp#Cnv}$a+;he3+1D7wwxoMkk7i0r=`-8&4u!L`2t%L(2~tXa*+sh<(X2w*`HEpeuWoOw%_LhBRKiOaM%f-wD+JMO~7sxLc$S)Vj zFBix!7sxLc$S)VjFBh1rtsABAIQR9muKswLZ_-arF9L4{+RRGtp+Pny0y&e%2+8;} z;V8+NGhuoaq*p4>W%rf zD}CfB%bKfYI_I#59^#H`86qQOq>Pd^Wi1&aYs*;o>4-$j?V;(G8$Rn*n;yw|nYmCt zFJJJ&ALge?xkxUSFUeQr8uvs`lI zmkH#TiH8?*4QBGY0`j{8^1A|3uE_5SNaKqu&-rD7dXCg{q@E-79I59>JxA&} zQqPfkjvbT&*D#NKV%IP@R!*V9U9*|~I87Fq-1ohLZ&b9JGyNoc(VK32(OfM<6!#~^ z{Yi0u;tt4X-KFXK=*`T9@_D(!otqX;%{q^7yR*1Tu9nJc>UhiRfzwv=rZ2aehtxc@ zc2HsJ0NR1coC=JP{60#UUq+GNM3K9ukh`XkyQYx4rZC;BF+G=>XWG{E(VF+sn)l%u z9KKg;Ix|jXh>VbtGD`B~4wk7UV`Oa^E7QH0X^hHBMlPn;wUG;4BpJCN%*X}4A{n_L zyi_uBL70&XT)`e=zq>MI*5gBmZI4VjX?q0OBdDHX%6HZ?AUy-pGaxkxsXIumL3#$H zXFz%eq-Vetq?AlPm+)1pTe>6isuf-l@@KK;_|n3~KJQ@JAH$rGeErA+B%Sb9@- zC_U>BuTJHPn)K#ddIcX?dQ-}j-cqGEN9oPC^d`@}BfZlFIi$B~@?p~3TEIMCcd#Il zYx5rq#_-(EOH)4!-np@s2MRJsb$3A`=^ZJEC%u!CUn9N0vzFhw3k9v{hnOKgIhFH( zQ&SQ-4>0S?2C|`StYw-==8v;H^T)BdWd1l|=8q%u$C3Ht$oz3+{x~v!9GO3k%pb>O zrQBKaYdZPzYdZGOr@bWe%2|?m<;c8pWL`NkuN;|Ij?61Z=9MFJ$&tC_$Xs${E;%xn z9GOdw%q7Q>?n*(d%_S$yc^T%((UQ63#EjMYjFS`O6gf@Kl!fw9Ia|(=%tI#y=Aq*g ziYZd~Ny%(<;+c((i~=H~fXFByJ}+Nz7f0uFCS)#>i{(r575Ro-Dwj!~3&ip(+&5FY zQakoMc*tto870zB{I+Q^3-ImY|M4?6S+}tlAGmMk|!0iF1g#4QJOptn@{&he(@%SeG2cF-^zpXuskA<$rJLV$uYoeC>zUonIK!s zHZoDRlkH`S=M^-xJIk<}>@IuAo^q`FVcIpHqr$Y^c2q!)3dm7mnmy$bn zfHM5+WS&XjzM8y=7QQ$C8ZG?F#6#A?A8Z=D<1S5}$9ip@yovSNF?l6x^!?-wtkLnw zYgwbMlNa$U=gSiY2U}g)gS%O~bCW;ws#EVzJy@c&?YQ*S$%LW940UjATv7Bhb7j*h<`?C~n(A195n2aD;;N3v`-DU}S_LP{l*CR_b|@EO+T zSiWuP=?Ozwn+p@NS)0-c8%X2egloaKoacSQocy!yhX=cRKe!(sOy>&M{7E{Kb8s_6 zhRJZrIe7kfI|oP3!LgS6A^#ahea+f3R@RYqWj&eXN*?Uad9*E=EIZ3CvbW>Bhigf* zpX@JFS-ub=Mw@;9BwrzRz{ogvU9XHuL2}eA=CN z;2=->zA*8m?Y{?;*?$)a|HYjfGS_M_pMK%aKN!hgJV}_biwEmdrX>?5^Q3B9=394Z z;z?#GbLY*t&ncD7<0nxn#S=@dRK~TYS6LFt-IVT!i6vHR#(hq1mk7V-4oxg2x34GK zQ?QRryhc73@JmW@5cyoft#YTkG9jLPPEGvaMm~0I+dSUxGcTUF!15Wp$?~cG2jw$Q z`Q#`ctN({4E>J%C%4ZSzoErBL`D`DTVfjorYWX1fjQ>da4CZM?Bi+%7;pB5tW7P78@m)C!zA!$65ty$noE>wd&~vc7B}8%jn^__T>^Dx1mXGETOTjH$5X-SQsE z$Oj+SF2XFz;9PEL?h`ILN` zQdr{sTF#Tdk@Mwm<+HAgWuB7@<@53dJ@M~FxkxUSFUeQr8*-^!CYQ?k18d};Zuv$$DO0LnjtbmM#?BzQ!@99WtjVg%>6> zCPNOFnKDae%N#k3zKSRQT;V<*nMw{{j@)C%T}t#FS2m_5sh-96T|8gy#I6x?YuYz*PpkZ{=Gd~U^X%^8_i0(|fsqI7nICx@ zD8d5^GWXGRTW&CcYlWzD|MInJ7$rnY|J&gINw&A!PgV$DwEZe-1lkIH7v zPG&D>oeq&^813s~UKIIU9l3yft|0m3b+L8I8^rm!t+Umm3nRwz`S}rJ?JPYnl3Xv1 zoJ_7|Bd*x7VAK`uea_EW%RFsGj-6rWMUYUQBJ$rscV@qSy7NwD(G5Rh z;z=W?JdIdOBYp6VG`LICUC2IQX=EQz8uKlUoWn|^Kxq^x4aPr6mh`fRQWghCyhd4U^YST+f7};ESse1l zXm7QmL=KK>PMP#80&fIzW#i7}t{~5i*@wt;OYT%!@ed>7ZQFAfk;9(cCFF1}_Yq3_ zeC||oI86+{f9I~`s&b@PH^=Vo&dM!hyl@z<^oEXjmzg+8IZHWLy)x3C_tH3e5;I@o za>q07*?zKC#A={Qd zasm5laL$%si&rmed9c;%$lJ+^DVZ&t&zd`V+Q|y`VNY)fPk)Q-^#`^voGs+d1~R}% zwzhPHXM1?0UBiAsc%?fx@-u3C+291L?IY*0hs!AY_uRpe@3Q^dN0hPshexhu`%h74 zzI5A0u4E5i%CTz_lXLj&QufHUk&DW6$X=oxdRh*IEr&eIA#I~_Xs#S)DThIp!$`Y! zJdEU!)!K5%dBk!UvEFh(%VCjnXzf+CtE=oT?87VBt!?k+Y_jXwoYSPYBj-BloynR? zdZ&otT}C4ga-n=)at=rCoWmjKaL73vat?=_!y)Hz$T=J?lbpj5rbXSKPaXMk z1kd<$`)k-W_^KLraSms$mW)zrgO|q6j|3zO*O@Ghr47J{E zS70sUpZ8CtHl569PHp=7etQ=fIZ~S}W~ahjr?U>8mvOU=53g@)l=X?VK*F@Ztb^79 z2lTRh=s#%V8TO8=gIQaw1tKjlYX|w98n&8zwq>oi7MN+Z)}#dvkF*xZT1Sxf?qL^5 z`#>s37+TCEYd6EJ?d{K4Z~gnQ3)UYGJ55R_hOeYO@e7l+x2!%lzHQI%I*__h-)^OE ze`4Rx($Qg6 zMXb@g89O*?#Ph9gZd<0^&AKLIKI`}wzBS5yh-2N_j5qyh?#s-fybFnf>R*X-|S z-1*GMc?;SvGkXRL)Vsau{xl$&rvS&mYA|86|7VS~5n~ma$%&AsLLU939?@krlJPFhTPSL+|`EZo|jT&zwjb=wGqQzZOC11$oxoTW+ZY~8**10a#tI2R~yce z+|@?-S!zj5dJc1;d|pzkLLRXr$gn}wf;hq)+lS7#-}I1QD3S9^Qusiwlb^_qa+BOF zzmi+zR#}XLsZ-&kv)x@7vV+lmlQ#t+Z?D5$k})#E-^e|3ue->%_9?ty@(UBo9F&LU z5qV6WkSFC?ZhKkJxb0c|QT`;INt-!4bS&ex77ml)vXU!JFJj!*tSlKb8#c(sZILlE zWXud1w?)Qnk#SpWVEOWUg4tNc%LLh4wvmaloop|Y+`0QV+L$ltFy@Pl`66S!n8H4+ zXumcfzcyfZ$*&EB`LzLiyB}HSzOtX}FH_xj!+9Q>93ThDG&w|O$!s}7=Ez)ke0VEb z>fzzN?9658e8z=05*{z}tPdLAigvj*gtJet#{Hkr z{?4To1)LMpE_td3;jg_qLo#S{VZ$pDIkLz;s)%bj&-n8G?VM+r+%17TI}CYt81n2e zQhat}n!&vvm{kv&-XYT);mS@(NJkyLY&oski*;%H@u9B8FIN4erj#bP! zIYCa5)8tH9C?A!x31A)@fnxHbw3>Mea65?lwj4Hbw3>Mea65?l#4{&Re4SR;I6P%Kgyq^^J=8q-KI5@!#Vb_uV}R_>a0?;5g?7H_HXJP71@5Xkc&FvV@U z|21Y6n%&&4`xkOAxP^Pjo|5|~iQ)c9>?gU4l5na!FvMECIY17QX>y3nlG$>E%#pe7 zAm8G75IEL-%^n`-j`eO#i#PM-6!#TdF;z~J1@a*)Q+^>rj^&qz*jrWqasNV&<)!$n zmw5jwJC@)78rMI63{B@)&KLv7^6&58Oh0jXu>GR+?}XQQjqm@QKI8A912~q~yMH&w z@|Yo93wdq$bgNfqpuOjKcW58_50e?NY3DeKf1kG0j^d<$fIhVvJxWXJ7~K|q35PfN zupERR(z|??#*8o-A|qs^jFL5FEg2(gOP)-aR!Hx1Ds4Qyi^&WJY#^E8K$zJG$ZP~m zk<3OQOz)Dq+3+3~2h1Y_0_`MgK*2Q|4_N(b#EM}ElE#KoBzl+V}$3M%p z@_qN=0DCH$$A~a^g2kdgY-IsBCXe9d3qgU=yi}@2kCW?UI*!Q`d_nN2kCW?UI%}0 zmszVH2=t9^g1j#+XxI)szl)kOPjuVZ;7%g(ZkOmQEjb)nZWxo3~`yDQv7_LN*xr{&S> znEfPI)`U~tH)-SPb<6>BkW7<9WR}d9BV>-ub$j{ND7S|_Jl1Vy-NwGW0rW)2-LJuP43E{kP0rfhVR7vR;Qz-*l@&$I|P3H(&w1 z&OhjTI6E1zf?ns9v?O|+rfG%rI$!sBnO>(ZG4whE=hEx6))hqTz!~&CwNqEq_v{#0 zK;L72?M@HMrtdjOUi3X_UFdtx6aLnV9XN+x=U3`)(lCZIl6|RV)cVT(lc>e#`P9(| z{7C)s`oCuL7*ivu5&WU7%2Ll!RuQQ??APKxJM7ouzU|mIm-_5ro1*Tk$kzO`XUBk% zved(D&6QMpANIP`GPc9(Z*M2R(yyIeOZF;Z3nEjw{`D%6dwtg8i{7DJ=^Y`XZC-^w z%V}eO>tB;K`Z}qAb*Z14Y3tJ8-U?H`E;iGkgBRW_pZu?;P6~eQMfH1^y?m&DU0aua z_D=eZ{Z`m$65&63P5LciUH+^8%eF54US(a@^;=}?($B^LtMXm068g>Ksw}o&k?qZX zvutnTH*9wSYg&zrxmsq+L+#3>PbhojV*i(IU-VvX`vO@Pa+ps_T!XuP{a>)_=l(m$ z0W}hPu75gL%jf#PM&DNDp6zVmHTH87X4=_o|8&k*tMz8T5iZ~w{1oMEX)LyN?GwQ@ z_#r;!clUmCSU>Y?FS1W4>*w`-)vL@epwIWQ=ggax&EP-zUIFQ~yN9_0UR>Wnr1Xn> z8?sJ)Nt^2=KK+~go-`KdeHDlMm2wO+Lu7=Elu@##tR-V)ZOQY~`q)TX8Q*Ozo5-fJ z8CGOAoqZZ7TgU`?x4cKTk!@u=*-7@0y<~6MSN508+a_h^ZR0@6jBUb$WxCtlXDlOW z=1|3C$l)?mGOL^AnbnQV=SJp)V6J3VH{m=vT8@#A$;V}pd{WMpPstak6{~E{C@zwV zC38lJc}2bwxm@8+^=U=DIm41G-6xdN+iq{?p^S!^tK|=rUVXby6n~Pmx)7^o4KcTLfLlu)Dhs#WvC7Joex0v&UBi)tWjj3xEX66&-$_u_tqwtL6LdOrIH?C7J6aZ|@|$QErl(#I%o(XC(dn7X=3Gb8pB{MIGIVcayBl4I$Ay1lI)tC(>^K%Ku zOJ?8_ZY`O6OE^(7_m*&b$rweS`8Gy@-DG#!Lo!A|{8(3d@9P{t!g^2Q$WgiX3)I~I z;Eh0k^y>CnK>dA_Pygb|i2tkIMN0iWPt2F@IMVC(;yCWjEVAE%V%?#n`jp}2duK7K zdJ2Ec<;O9cCP18J3{o_PCmzbW|PCG_uB75M~VNdWNiB0mDWOV zi!1Fph>>2Fdn7&|ugnC~s-x}mJ+X|5a0`m%v+D3c{m2IJEd*;O(I zPRuwtK~9m=$spmcI z&M=dDj?Db+dEI9IA~S!HnZL-)Uu5PlGV>Ri`HRf;L}vbCvHKCXk&E5`e}VKFAw)(9 zkr6^6-$SE8qI35x{Q=HWR#4S zH6`PX-KN@jBQoBIj5i{EFEZYUj5i|Vjop)Myb&31M8+GD@kV645gBhp#v76GMr6DZ zlib;!NBs`Uksg@T7^MSlM>}EeV!%<7^R%v;xEi?Jt{+zeX1<)F_^EQ5ERYXb znQ}FNoZXjpkD?V)BDBKOJ?t)y6>d$p>GTz!c6*wAaI#w$a=X}VGP#v?n@nF(CuJ$W z*dOUOo!^>H(gXdMd#{H4ReGtCZawKk{@$$% zeaKR89=|&+_lo%4X_+^R{$#ah{nn#J)`=9#S+YMyQ~R%^RHJ5J?0TBod_3hDe$_dZ zyn%Xls!JK`c&2q1uGh*s=d-5&?7ELN{YO{&HZQDeG3T%Kx~`=sjUfC-cbfJ6i~BNV zAN@?luKVoFHD%YWkyZ;+X_{EAj z{ar)J;Y`;E(mtJXoo%?1a++dSC7uK^n)qhLXmaE@N5mvXq9LaU*Y2 zW$sDJdP?kSr`I`;_foc4kKWmSC4E4gba3+d&| z%CZV&GRf}0>oV8wzr*S>Qr3`BGFp~jRdOd)${e3Nsj#+;m33rYSx+{w^{1CJ8_Rf^ zAY02eGEugZ?PU`6Y$5fzVuyLWBgpbfmYro6$+KjW?fz}Eo9r%masn|uCFk#iIe*6i za**WwofyvFk@I)VmLnwR@5Io8Qn=o97g84UOq27h&s51XeFztroDoGg+fHltx~#F< zo$?xOEjoD%ZSCWBo9()~b1COYP5G2xJCoPgb#-UWvgt6-T3i=?_4cZzOrqtKc1fh= zm|R77-oU+Lj5>6BlQX4D$%AQ2Cz2P@mQEqFL6WC()aA^VwL918E^<5C$>uqIlw3qF zcZA-AdG^GA!MB*X<-X=y;eHZZWzQqLmQ*G9jpucInKi4Jd>7w4)ajbF;Epfwrexoh zCG(zN86qQOq>Pd^B~M;u$r#C#R|&_ubFJoZt#OHPW7$MDmCa;x87EuF1bMf-N4Ak| zWjonP^1OLc?j?K6zOujMxi);tb8T>-Op}9Uy1URZm1_-isA4kYaG5EyBu~8|1)g_< zw)c6emmDSY8OEgWMZ~+#7?m-}XnX{UYrbX}?JOMcOaYev$TzBP8vYFvmfp72{aD z|B|zjAKQP&*@&4hr&uY_uFYw(Kt4q7^*I|s+I5AFj19S+$##u?q+>@~*RhVw7IDXj z`I2~hGd5@0NzA9_#YwAejz`k#95+jnmUG;^);5yk<_~Qnt%qoD^IuLTEvLO5ZGVAp zm$g6b#d9}moB9W6<)0+lJvAGXCfUdrZnL@1cJ`b!mZNcL(rb)4Ur8(=?Necz zxuks|X{n8iwO`LT>cylt>6N^sr8Y*E^ctgmWl4(}Q@w=Gy5mU;ZA_IcvmRWfj6Xv*YOU-~YH{?HT|1pZ~Z|+E2ap zN#*vOTm0YuxZ<`4Z3%a={Yu9#S9kt#ZzWKB`Rs@GYu%3tG44crO7j2y$I)V3N!uN_ zmukP(!tVQan{URs(`^q^wn~ed_Oo?V>$T-iw4*lv&wt!UiSgy1+(`RG`TuV&?XJ~3 z?T#gGc6->S{fW2oz4iG9w&jX_r%e$xW2!rq*tq<&dPi?A^Z)mMceH%HG)nR>3%P%9 zd$>HRybRpgf8Ag0m;B3D{^g(lTCR7LrIx(?IqN^mU1UF<l&LLm;UC(+Yx_arYyW*6-Ff@2{oy-T`R|9D z|1OX9yw<00hW_>c?u_M09%p`DMv7ZqUQetn+=aF~+^K)vUq4Tc&sop^`H%bD%JRma z>OZejH>2(UH|rZcjJDUd>CVr!%%`n8mdD*#_X{_d{nsULe%|6=Ke+X8cOkLWt$$Q# zH>kV}Zp9E_+i$eN+p%r7yY094h5MZKK7C`G>fbD%dUx#e^7VD=+YNFn6A!sV?8^_W z?Y8UZ#kTv%G5&9VXg8bPsAy?yp^TpbF=)~edN`=-A>EDRj&KzjsKte z@B7xRywOiwZNI{uCw)8i{_IctS-1aryPVv$n{D%Ey=Hx`+P?nR`gOCtTluw_wMq9fXfAinv-?*vzs|)4ZJ)^`G-! z@L%*7`%C?0{&N5K{(t)K_9VE5E6t2VL_!JBB&fx391Iwg6ctJ zP$P&6qJvsN?VwIjFK84r4w?i_gJwbVATEdxS_BC}tKjaSW6&w+9`p$A4SEH=gFeB1 z!N6cpkQNLMh6L$BMld|c46=gkU__7;j0|#vQ9)iXHkc4h3?>DWgDJt(pdgqYJRHmj zW(I}9qrt3TPVg8zV{j2C>0UZ};a6S-w*EOUggx*AzUaT`g|R0V6aJn5JHku-rCv4m z%Q9kq@BiMb?!U>q-NO90{I_tG&zmOLTkr7c8h;Hj>@&h2`X3Tz&v{k+kNuAc7yH}E zYp1`HPxt!!@tA)M*>l97_0JM!|6!?L>P7fvewi28p5zy^tNvBb_pb-6HTyH%3k@m+ z6+Pefs#lTy${X+4w^jI*{Y$udP@QmOz%x2*PkWWv*U`kp1oilCgP;N5Y8W)+(?&rf zuQGeTF`u&ky{eQzbFT)a5a)$c67ejP5F`+88MGwaDrm)$t%Ej%6N5zJ+XYF4DJkNU zgJi-zf}X_p4tjf0LEnHkCkFk3e&pCcNF_WlNb@2nL7o|FB}hzWkWDxz$YnkAf;?iz z1>;zLd@!E){D3z-Q=$`ze=vBE_$k2@;tPTT;vWtkM#`6%!l00?n-$E$*}-gLth7Tz z9${`~80FpjMtMKsKj~GZ=Fjy)t@fkUfIu}MTs5GoYCxc}k5t)*(=L{?jMab|w2%KJ zY^5I1PX0s+R`#Kk{W@aSQv88#NF{foAf9(^+w^-U@X?YCa()VtBZcEu;zGI&#l-^=+d`bKg z{+7gN>u+TI?Y||2OXBv#N5;Jwm%|b_KUqRtN!(rb*OpMcEvIGq7b(R>*!NltCVfk{ zyrx4N zM>V?MU{Qmo>rbt-rgl`!yqa}ur@V8)d@KvxS*cucE+k)-E*TIfpXYft1CpZv%%iUEW{szxWm_?6{w3)c3 zl9uuKUB;Hw<|t@k}*G4OtEs@Ci%6H+;?jEy>g%2FOSJHTBbyvRm=r>QI@eU z=lfjUu?}4SA#bE>KG(mlc*e1s&*yB#4Dll4dQ#?Qm<*Q{YP zYJ{cVQnu1Etrcz~+sXDaNpj^zI-O*)VtRYEn)M6%D%@YD%E4NOaWa-qml=v5E;ALA zB^f_snH+81NSQ11wB&ewI#J;%3eS);B~S2X-_DY=6*EWS$IOsNtn?|0kRq+N|u{S(f*@`v>^x5@4Dj4Uy|7VO2(gYBFJ#5T<2 zEWj*Q%r?1Qo{=T8)O}ZXH#Or}{UV>|L^oXR^PFhBt{7)hOUx>=x{Q{MY{`JP>G5e( zFQRc0?ayqbn2xfO9B8>vZ%poB!oi9kBGYB2%#zuXyBA1#iku;5%GsvZl5PDe_!C#m zu?;43t!)0@l{K8f^|6KDl56CL@?%-7Z*7y?Pa=j=f-IT=#V= zWsH<@{moX!gel{C&sZ5FWlRiZjFd4_#<3TyjFB=%%9wb{7%5|Pa= zM#>l|uen|P%o@C`0vc}2}DZh|4R(?qN zA?1gZA5wlu`61QI@*m`gxScR?@lbwpZOn-7~Mc{lO=cki|Qb z5;8;F{@80ls0@?gvVtuiRFsuv6)j&?;cBwFVj>l;;YG%la{gdOE1vtqNSXV?ko&`s z`@@j?!;t&Ku%W)&$kvdWVe7*gJo$14kIlUY>n`Q$rdqH*h}V({3b$0am6mU@4t%%qp1?orp1 zsq3CsOIAgz%V+DnNnJisXAX7Qe36sFjnrie|4!kh?gHUuilHvYaAZ|Hb2C};9mUg< z2(MFkz5Gxy8@$NcyQs_N$BHRdj@#sRxl_yUmHXs=c}$+sG9~h?VlK#wlCvn*pMDfC zyHmC1QCZ2g0q{+Ms0c|Ff&wHr#-Y}Pubh^ zYM-VqTRit4Aom|2=aDSWnJ{u5i5x4CV+C@ozzi+Pod+z>af29+8_013bF?=`%3R4k z2Q10)girI8`$WZXY$2vVF%L_QGsJM5L1x~vH)ctWIm9sYmKg3=sPh_inVRNJpyrm; zW}Ma?t@S!J*IX=@y3&{{)K-h3wnjff4Ylw(xn6GYB8d5mELJ+(B<}$s&7E3iuiPj1 z%VYA4mf`mvmOQJN3-Y2Yb>GL>asAV3?Wm7O2w#y`-T9dBsE-!!OzNW<;?Bivpgx*m zGF(=$<*AP*cf?^8EnijPYO=awcv1|XMk^en@79*FvW~1P>q*|e!}5)6t*C>x4%ESz zO;!i7x%U7^t2p*sbLyZi$(Rx0mI}Ah@~su-Jw1HNSQ4M|o*u%yrw5t&h#j?LC&~NT zh+({mC3{NhV6D2;L5uG%Q{_P0d(=TQO%7HHLu9(l(2~Ptred;Ww#-xfc!e3mBFBk} znW7lRvxs3l3uh>1rYzK+nI&f{W{$#-nYzOGhc}Naj6ZutTw$0T-=k-7_OsWU#dU_o zyd_sDevQH(O0F~LXSvS6j}=qQ9dq+Inw#6@8Cha-g+Xfc(o@w-50MYa`>=p|e>%F9 zdcQI%fqHMg=w6R%NA0)p?-X9@c1Lff4Ok3w)S?fuj<(EOauuImqgB|aYZS9i;q~%E z#cXi@R--X%ZGNm6=D?EgHpx9)gm-HBy>g%2FOSJHTBbyvRm=r>QI@)GtW}x&BKkP( zWGCS(@~Zo)`gYoh#XFOBVurZCMsKH`m|-$pRV(ZY`aG9x? zESW9y6hB_!eC0J!F;f&%pqPi{48_cph1xT-FnoAw?rc*;V)kvp?p0ANh z4K+EZsQL^w)WV!o5MHJ*{d3iQb~Zr_XA{Vi5?O|`3BtV5iST;K*#t2gyvEh%(Qlia zO%PM8ytc{hl6RQ#E$#|M-c^RYs|^3+PRvgGZfEMHaOYLd5)62n`WScZ|c8r^MV z4Wkv$I}J&*wv3g`;v}Z7rJta1ONCo$ z$<_)puai#`^(pf@3AdL?vZI#lB$E}>LreCQ^x^z!!hBD@#e7d>z9$Z}eMsFj)8t^K zFhp`UBkRdbPnKk+C-P=sVzOkmWUP?=z*r$NR*1~;WXbXRG+((-RLm5`6e#9lIYTis zWuf-cEIC^-a};KNXpKI09#Qofj~igsw>H3EQge4#%cr(B@gu3H=2FkAveEj`%5|xy z7W0-|WpkXUrxx>}++g=v(PNq)D~78!Qsb%(xoShM%aH3bEOnQvt)Q-*ud;`lWjd3Z zWmb{ZWwhjddBnGp9c3rU%-U)z*xx4eYjLpRhsbo9DYIm@950zgO`0?0Ov!sttL$Ol zuwCq%yV)xNV`nF-E)06Rjl@i#T_34hM7uU$bbBL~(ylH1JB61i{HDTh$#)dP^W|A` zoxZxkd(He1m&fE;c|l&3rS5#y1?#w z-P$r%){%8(J=svpH?p;&o!Yl}OGl*-IFgvny_%I)aU{7^wTRyxEM?|j6K<(61STfE`4CBmLs6E4MLP~)# zXJY0k{Fup+gxX@iueu$Tx==fi{nRMzbuWkd_s^gU_0N3KeHu)4VR7No_N^ z-i)Y8Z8N#vM6NfnfC2Bv1AZy}+@-;#oa=Sbu zOC39P1ckS8GUV62r9_VM>iKrG{LC5yLeYQc}cF zQb{e()_Zo0+S{-Vcpo4KIb+ zQ+T(AZD8GY@QVlUstJ3Sbvqtr_j7Lz&n6^vYwHwCj&>aCaPpY=d!=b{5`D4!O&Htv>kZ@YUrO>k5k@4QrZ@3 z&yl_qV$aMsc>=0`&h`QQf~9fEOSOF-`iN~y=q%FsI@F$6ek8O=X{;iR?d&_!2<&ee0o}UnV z&(A5(-XU~4#GY_n!n*KO`cQtS@Vby5H_*#_WmBwZdoEC%9Q%xC zrFwTfX*FaKu{HRf=hc&SWgS@?W9|MH?~W&phUiJ7A$rniNFm$vH&0I*_4K4sWMBDu z(kL@edD3Wzo;1o6t9jCBh@Ld+-|?hT(&0&?Jjt3Tje2_0DDtFHW^M7LQBO}AWf`6{ z%6ECvXb5vAykBVTCdn8%StiQSG7c-+UAO^HS9GVToqu3oFR{CDiQz8XU?pwrk{_e} zl4y6~`kOs&736!NcGr-1kfrtoJ#FtXr_5T{)69|Oc|NQy&-3vu^(fYs=lQUn)ONx= z&qwz@|Ci6b&&1fXWY>Ar6T-7dqblG1iZD;T=KiCpZ1oN=pL-C^Z(LcB%QjZyb3L#Y zql+xuTK14rWC!_6Iaaoj9Wm7IH1ry?E){*d2l5c>!kva5^ET|^sQhV28FJqha^ID= ziLEXnOmFRdMxML94Q%y?)O+r`Lhiewmfze43v=HUb)EaJ`1Bjn|BO0%o?6a*SKbo- z9bW`qf@~x^$p>Xa*+dSM&16e?muxIs$bdcO`G04QIB%h(S~Ll1(xGj1vkv}}y4T0t z|5MCQe(CipasEnnvAY11Sl4%WW39$LT@HK<_MTbu)g+t@vTy z>vzOoxfMU$d&B$4mUn%ENg+3vZxNDWPu3;9pMUR%Ti?sL`90hI&|0_Rv+j%!`TMQ- z%%8=-cOyPLWayCFYrEoBY}%;XvHNeOF~s{1p44%3U2iUZb6vwj?)X+{gj-nktgQhX~uojVXNBXmG#dq>vEswu`_-E;N_glInx6=Q^t>v@1^Z)iWOZVA!Z z<8Olvytjbs(qg{wXYjAsoW%pX?It2TvQc5 zH2AbtkNA+Lk=MPTVWTR3Sjw!FU)H><=D^_oZzBEbjT=QaY48g!xBS3w^XF&p zj*;4NYJONqj|Y2Q|3?ttFg%E1OQPbULOM)NZP+H(wkz_NEy4#r5E}B!0i7B*sTxE? z$47SR-I)6}?hQUl&uZMgO++wmU`pmaP5dUE;`|l$T2!@d46gsq558xke1E`woqIR% zyG7j^r!A@7wgn{t>b_rwhV}SWYLB8{)VVwVftL3b4s974+B*B!!Ip*%laA$% z|K-pbsktuq^?bZMje{@@79c~Zl%yXHnGL^G?l`Lf6sgJ-)-HsE+Mw9jkOPk(hhAO za3x^t$Xv~4#E0DcH=+98JscHiN+JK-HIL=txk01kV6 zg&^2)yTLB?)nel-1|dhR#rpMnb^hzm_qpw3t)c3fol9Fii`70ZkGG>vdHgVsyLfIb zZ)IZf93^Jmjh-qt(f?MP7`f1mf+t@Y{@Vzs|K{vO7F zmAviu9Y=aFh^j;hYEf5HSP=W)*lt3Uq67nas5CRwo@Fx(O0YgGq>Hj_V zy(!vVZ?XTsPaNzy^YrGPbI(2fo}=HEEP6QCISNTf#%TbT+2V1V@W0X#KZofC5+7y8 zZ0=jRl%5G+Ch-c{dlt(v1*+YI??Rfw@>RvtX~@ia&F%J(P%T4>c7P5A3VE+sEB)7~2gtjrsS_g-lM;FRU)#z>81EZq>wIDEC(uSW2M$Ag4 z^;!p0kyY#JpI$fj(Dj>4UR&iy6MM>)UAN92e$A@hW4p~?^N|pWg>o7F8;oY78GFLl z$?dLT&YQa!O|R*|=>$mdRmCUz;Kb7se7oYGBsfGO@a@9K0jHBEy`OkcdcMMTatTcE zpMgH2ViebiHLAAe8BhstfU$D?nUd;<8D-(OmqD%$*ZmM&H$t)w`$ZZ88z?bYo15I- zj~#xib89Eo*=XU1LNuc1`of;H)EN!*TUm5=C35z z7G%xNS?@9BH~K$z6%HP)CO`BQS^Yrsk;Hov>lYOhgn_D^dZt)BDV&)Qki~mexRkL4 zFMG!L$TH=JTzkR-gKS+@yod86;rT-@dnNdG_!EL8HCO@o2hO*DoejeiiLmyp9U}cs z8$$FC3?$c<&P#BvZNS@);WyHX&9o-mLSEu$0c&DcarsJcwYYS%tUcJZ`~*tSU6Bs- zMv?9^NzlXJ>p-`2pFn~x20PFKm-7VGh!w@v9XU_9cHc49l64^~^sQp6v-b{?@zOhn z72%GIXR9K~cnLlP%5fdC_QMVDo>am|P-KANZQ?7sPY=S|{v^WPzh3_pI}M7^_Zn_k zktvx@Hybd8-7Y6tStPh=1L*5EJ1xfICc&yoZ5j5KBOaH}EeI;7))?}LPC;Z`LSrZV zrQq7*hi`6}qJC$D2@#9Rq|$1cxjn*O8f*mx#)`n1|Na{|HFt2zAJ)pe0BiDbvfbwMEEf~q?e?2&CC_VX6=N(| z^1a zcU_YexXp#z==OyDJKgi}w-d9^!0ok-E&SQJ{ik7aU6>%tT zVWt%X)rfWUIQk61u~Mu^hO5Q#I|(c4=?+wbsVV=B4oH&TPh$ViW?$kIpo3c+vf>`+rVo2wV4kiR1W!(ZP|Y zI?|IVQ12;TiQk3+q7utOCV}7BpaD7(q>f08r;bH%Yb32!yBuJ0eCCRxX=^lE&4fl| zZ+V~2WYP(OM(Gz+q9g4PKPf~)2BCz83d9$$ebf@Q3Z|Gby08#=h2V=>MaIbG__h!X z8<~*!g`WMJKaq(H9xtx_+Ff*T1C8IuMhWrk9|ONC@MGZdL=QPQ=FKj4jIir|;u=I)Ui(X1!X{Xm zprn=m4U2^IIJC?PTj~xr?#jE+3_{Xr9~Dv|l@K(r|JPUbt!?wwxf>n}z)AX)Y^1D> zhDXEfi|(YfQrev}Uk6w46>`ox%z0>uO>ucI!O32c;H!wD5YLssTfnFbA6I;x%VOzy zvPGrm4`7}X;ud)Rcbo!##Uj}y=MhsBp=fI^mpb>0s_a}XRtt7TnYR*y`Ec1pD~uez zu40e6`ovvueBl1p<ar}zsj7>gt%iP$>yT+DYa@|CB_~uksx{DeoXn8tK5v7n9(-&^@gOCyP^pZzub)`#gDF5`2XT z-0K>EcSE=1B{;k;=I6h&;qSQDCH>v9;$2*$@pW;pOM>r2uYb~X(t0UAC&721Z(9#e zUY7)4RQ$KRF8n^Xge5rt{qB1xJ}1GqDrnYs=l!I4CHOGw}XeA>lcMX(OM69a>%gu<#S%H;8Iv6%Ev~XW9}2!e8>VJoXvwt6O1Ll#CbHb_EG=DQs7|x(A5p% zPnUB!zT9}#0`aWPvPV$+L&UM84?mAH?5*WB(cvPV<=GxDlN6C~IzS#X z{tw=b_!;E7T3k~|o(&8g&&8sjZpapdNYrXX47oAHy;wMeZa2IBidQYFD5|zMgmB;M zluiGD8T8;E}D;}re0Wo=|se7d-ahfZcQvtqCo@xx{cPKD;~-l|j%-DchYrW11y z-7q6CgSr<>=>$Io;R)zKH4Dv+d{(Qiz?}NEzvVX=x4-7p;g{m9avx#~;+`qsM^R6C zhvFHshoJBzXQ-ndS}h(*BMXm2XA7bvckI5&@WKmDsl2G@LPRqhvA`)IH;CI%2zNHG zZAD;#n;b#=(6Lcb@HZEQPnPyfW_E2UvsX&C4=ZgnLm0~h>dD{0!(AP&Oy?Z(N-NN3 zIr78%tsPJqYi%JG+g&{cm`Oor+$~bvSZ5+0KgYBy`$V(fc$l?o6DYZ)K)%lv&*=}JP ze>hWiYho%-Fy2Z#pI(4VyMI#n6ezj{D-8>z{llVc9Lu(`I-cgn@j0QF9y5?-M7&Tr zcFO=7)$CZZTJ=WbxHXiE2zR@4&M{-ysfz_gJs8C3^%`YAnR{Vikii=o|C2j9z24`x0LtTTTZDv$aiSoVTq*{?WmND_rN-rij$JoySao-dH`{3+K+5}fcPIPly`cvjX`gpUl>hzBt*tM?Ytx6;$w_w%Pg zmHwy46_0SLplVXM0Vh(XO1mQ!(ishpgwe4S%_2kyQObd^8N%E|6p_84v>XmPMuNGS zf~eeg+$X-unG65J;-boqjTOb03K5?MeX)W=2c{UT;=!FQ#OHHU%hpK5Wp$*t4z8>y zS^oXrv|VIZ?pu4E-tLKb!;yB9<*WPWa*1_?5F1=9v{qUa9H1Dba3@wuxmY=>P1bOa zrQyh8nZli_v|w*GcE!M`=3u}K^D!3E3z1V3gQ}EGc>Jp)+cJ!u`CgX+qwP5RU$(mM zt90%=zapZRfXDP57x=Ar2y9k(`2~NEZB577wHNqJ>?Hq9?k~Pr+TWXK$CB$-!Wa8U zP>nbNeNOwE#7_3ZJAPyfJ1P4Lk{@Q~;;BEL336$;2}xUt|HtRH@MA`KEweH{ef8q< z)gvyI37bbE^a&vz)-jW)PcKi;AF8SBvS^i^v!86b{XjFn=fOk2^e1tkSs@lona>QK zULBaZ1Jg!3P8wFsSH}7J6&#Z#wwd#_1Sbnqg6~rNx(`nLF2Pq7=>64w4{^2x-;OD7 zJvdo@5`0n0dE<9yWnV*rk1-{u;MwchszC3r?)%BolAa${JjpF3JTDfw1t-BrQ3Xhr zp9Ck%jo`4n2+l3PocwpQeSg>czHc4){@lN>VxARQoYMO{>*@Xd#&wakv+?&2C_dPS z4_SWFx@+j>F27g!F^?fh@FB%7BslT}l5Zx#ClycdJb^^7zv;fGE$1yHc<1*uBL7{S zRNSf%K8llCq>~CBGpjTS5i(ZIgEwlm#{2C)~=+J z&+!o7iG85R5oOK3s8Ds`f8K>}XQO@a2fOe^c6%SZ_nxindUWf9*#iOi5cUlg&j693 zjXjgMErxwWz^#us2=vf4wrXTvx~aZ}wi5vs$f7q>S%{Z1{B3u1VnC^&DqDp0pl z(BX4Jbg1FE`_HUVt2t~IekUY-D#L@pJ=C!&gg0aLL$YPr?rn7(YbRYUaITXWk7OSi zojh1YN(2=gAvII~sn-ny*ea|E<&@fPD>=1R8v>XWC?Kv z6BD39Zf1@w88*dOa3C67ikbWYleg&1E@btAkY3u<@yTC&L{>YrpM1q!F1}aZPbWXO z5}f3m1m7;_-CISIZfd)_X{Ibhy}k14*)<=olt->P`5@BJiKr1uOfKF!a+^gNws z34TEFJZMCmA5eB0l=Tp08OWys@e;^)^9mAtu4{rKd%iX1Tw5ENP{nPi;9R@Y6Se(* z>M(+nWx?Vpxs}4U-Cs(a`EE3Y+}QTjeX!If%5_O_lK%vUy>m55X9@ah z8H%Vn7?x@WaI-^5GKzJ>r-1k4F^evzTps}q$Ya_G(uMdhIgCPIkBdzdd29*KN3|A{ zR_(UsEJ4u`Er+vlgGTh$>s424OLEKg`O566yyr30mbL}?nrRj1*x*C zMf2Y}&x^xd_;eROi%y}P=P7&rc22d8e>c|syP59afj;8yE`0L_xTg!B-2fM4xLR!9 z-dmNn3vG;cplvbKea9TlJy9ruGp+Rl3bmpPsk?}M$!Id|x<_+)+ascMN3F)rLGG{* z-g&F}5#hc&FLTExoLy>S8=k^@iPD zTj|2LDBju!w|3zRG}Ev1yg>DE((acPsFUPyP!3&uJ8-_s-RFB;RE3W3z2|4$=Z9r4 zK;o`Xbm2o}Mbdkq^|_yBq)5^JAUdYuOvBD9mk9 zH~m`Q#LfG>?Q&$O=7@&pw!}xa1`0^2W$1AB&Qqt?it zkHqIEVBweC*_DV|`K`=wO5Hp*=83U&e=4bSH)%3F-aeQlAuUCoAYNPHhx4n_6 zs~4>0s!OBS88m`G(F|p==F`41*eu4K%vf*~$FgoMV^QHZ!LcDv@!*N({O-J|v2$Sa z-G`e>o%Z;fUiPXzelyjXWwl1l+ILuc-*q?Dj0TpPSXxfaY==z8Bog)!>>zw-DPp8f zsJW<=VtSCEQg}$(N!ml^F3e=yPOn3yRh2@8;kYF-nD7ra90}jlVrK8@;P3&q9%;th z0TwpsoVuW|ys#@fyDRN!S8cyRL`OK7cRjDFvnv}hJQj-$Z$xW z0}|9qEA2ujj(6Vie?)Z`+K6@D@DcP7?Yz07_|!TI4i$7uw}3rrQvA<@_#YCTEn+`d zpq*W*<3A8$KUZ}K86G4c{zSNwl*{|dqoGarAGHKcN}W!LazJHqz^@JI9A=G4r*+t} zmXM@MQd*4|*tAI(ooJY~2DLR|%TE*o#)zT5r*ZtXy96P*WsC~ZXq#Gpa>wL-*CrkD zo?@9u9XJxJ%-^~xvAeBS#)s#(rDhHi_Jq%?MGf$z3LMJZeF*;+IlKrwMOZ;wv9qWr z5T7-+LQ!0MkC=V7I1AUIw&2Xe&jxesB<^mqFNkqD*6Gp6rGgwuXm?n7NCQIGq|=&a z%k80}PQIm3_mC=VF;r`83THR(8z?PRlM^e&Xv-}+Gto`)3EybCv=r5aCyOJ6L@f>m zu8VR1X#B>PEno4XovGUK`OViaWpNDr#hvs1?a^6-iN%yg^V%=l*DfDAS(x9wCzT#R z9UJHG$8fenh#tzP3o_x{6UF1OT--5#axy!(|H_K5mI;lt+_B)~*7A}4;n9PY@<>G4 zJUX6W>+V`1H=6OP##i(HX5P`5iQaK+`1(Ed`1tXbCOMRj7UeVa7`Hb5T|Qgy;&PnM z++*B&Ah=p=-P}1v9~DX0OVH--4)n7k>3Rv;2+B~w!*#v%<~hZS@Fpd&oyD6;*C$a2 zNp(0~UC#s27esu6hJU4`;UBsvEl!_Dr(c{pXTID~Au@CXyDHo?f`43*|aof|jp47fRJ1AD7<&I&nEBJw0}+_cZBsf@;Jns>FM6((BUG z7=YDTHEK{h(4lKM6e_C!&65LE-A6nAIe|2{^oGF_wnmW$^b_DsaHb>&Apv_9v#g^t zTsM>U`seIob<>r%)>(P~mO>*twx|@mjY51e81oHHrH>y7kM1}4+lAV6-lIyYobg6F zTfP1I=>yeh!VxdH?mj+z?d}?_QCwHB_ahaf7Ity`_-Eog`V4+-!fj*g53D`O9ufYB z$J|?FC~)HsXBjp^9YyMhJ1V(Jfw(mc2{PlU{J9NR4hm{CmDSiS4mT6+T5rT@RK84n z852dNs4;qqOQY$m)^4%84E99IZqNxzrApkZ+H1%!3;)dmevQ)(OUC!}%QZ?_Lrqs-_w*#l~Q&OS7q)asx*T;*!T1G$@+yJ0cD z*{jqU^eXM9>+->7Dd84P3442?=rBdsp42*GUbDyS@fgh()98uek>}Lr_f@R*z3ol6 z9csedeeT`24<5UBoYmV0ZoCa{Z*$j?e0hOtaU>kLWcU^5z?R|xzC#>0E*l7r9pd~- z(64dXAVC|Y?mI*gye7S4g!y{!;3q_aSEvdR+KJxsP0&r0GjhD%@EKE&eN$Nbf#82u zG;BaQed`&Mks)97lu&`@-NLgAWG|7vk?jRfBHaLp8-bDuSSLFXJS`tISxL3%D6$V! z;yQy)>vRTM0~tg?b}+TxWY#D}VOFv;U@nTHPNxyX_|&H8Xw7Wa+2OwWM0=!UsFj0D zyZ2^{LD#_QHMa~}d?t-S8@Tc1uY0HrlTb;PhHre-e+HQ5d?es;Kl*KM>*JmUI$&g;$f zA7U=%I_4V_{O`cO61=hg7aXpEv}~hbEUow>KLdxhlMLlha4)Lo95t@v&axW$=2PJ? z4F4DA^9HNIVx{Bq&7$BxsBKBOEGfu{yeyaMAU(GYRgRUu>O!iTO3IHw*V) ze7)Oly3YURw{_P?KCZOBvZlA{4K(G?&h}9{EfS^G;uzN*XJcP2^xi`nQhNKawgVmK z8d8D|sW=oGvbT?&w@?0@^d!Cg2-lO+Ukv_(^cPQJ&dN7XGfGhDFEU7DLC!-z7BK}}{i9g8b=FlI2KcH|q{oz-1v=$VAhP0Lv{vfzrn%Pef&9KI|~6m`W6w$!1BLgQ7KDV(ayK?&k5a`|mtD=nWfP=8#`Cbl~){ zS^eN`MRN#VFQQTdl?dW|SR2cN4@&TQE^!?lUbyu2y!9609_ORN^DlK>5^q4tKwh$+ zLCVb2>{`mlBG8NqPA&-h#U%*DeP$Av_-{#Ia?3#Dl0v!&9KcD1k46HDlC{rE*Zq!g zNMqE%Lg&)cXd&r|+I+}QuQsI8X+@nyC59(v5{bMsziTRyvKm}ov(2jussqJeA33RZ zx)AX*jBlP`hA1J!QEhnx$ryBqViwRRkG&&Zq1+&1 zGHFSfbg1*MqCqFBjYgds-lr7EEQNDv#xw@ARihFaYDQJ$qYA&FHRrFn<@y65M@$y_ zxvcN_J&)Yq4A`g=f;0r309wH~WgD1<^Y+p_{X^uFtbZfJdv@ zF0LqzCZlqW>qX%=Yj57xTHgAiOdn>!TjmD`hf-Hvv&+VHAJGJk5W&^Z&uvJbt8Czuri-9qFDA+6~`Dan5(P))S zFR26yLC2y7f$5cEbYd)!tvIzhF}7(ukS#l`HfVZM9L{K&H2v1yd$Z8=jlI|0RF_yZ zebx2HBerZG#8z6I7@0~q4EBJmP-4wUaL5JapKADm+4O-U@#IKiqZuG+IROzJ1*`y^ z&+}R|7o|zxwz6-+05It^AjDuTi*Vg4Qvz|#2E^mIwlgpq-nVX-%0mTnF_j0(nN0AZ!9Q_%d!c8Z9-+7*FVEOr` z;x!T+Ie5^f3m=!Gz|#BSbMfa9Exm^LO#?~%M^F-yj(h|;_9R!?OR*)$u8VLRJ>HXk zkaPcOuT`VmWl(Aiac^y|Y|v<2p$kyA7%`|gX>$W^o!U5YO`qeSHp9j|^p2~>Pi7aV zF33IOx!8;s^sVk1%3V2h>jzKB5sz;JLyH0(^5l3Txo$kp=0UkNl^uB8lgbi@D-W-v*w|H*@vT#N=(hPC z$;85BhIS?c3G!{elc)!QG#x;0s|SZogz8Cxw=iYB3n#yY!_{Jg`#yM!kfE4{{PY}2 zGdZU5Z64EbBSH%Oxgn;(z0&idwjrGHHYuF(>I-9gzWl^R@IUkQP9UR)88Q+w4)byb zfGG59ie<>~EmQ^2p&Z3BWcY&Op&p!kH4ayc<$I*3XEAS(I9;OWnBuj4Pt!?~;8n%j z`rxq2%X(b$b-wuv-=_LH2i%E?~UM0T!L>D^r9P(%5>mO!6K|d%D?gp2^EmD~* z@d?GkI^kxIMnqJdp90uBbST^&2ZEBH&W*Z#I1nR+iReTqG;+hzXvO*!Q_?FP3Dr&f zjA#vCG3A6>Xisoy0H?!$(03@ZuYnU1y=LuS5cHta5g2+t9hc}_gbjKEgoU&++NYX) zPTtYCPaWJLsm2HoL|29X5zvhtMv?kXa(M8d?vhCo;R=_QJysXG~Vn6Z!C%=iuR3b8qw5Yjz53>&^^}29{&)_|W`P zvbkM6gexqm;J`Qje*6P`igX&agxDqn9PnVGTdyP@4)SZ=& zGDd7_CaAFdPXxUhL1Q$Sv~*Lao0@8sVA~kWeW|JZp?LA&@h0P`sa2$=4mAbgK4+Qg zTXNylMCPi$e9L>Ta6{&wo2443EY&jhDGj{-8T-_&Y^LZL(4&2j5W#i$ z$)v;;53aFA+rWTDWwBXQOew7$`=qw&MF03?xC~C4dSJP^rQ~GcT&P(|xWXt%?-fnPi7a&KD*PURB5Y-pr(f-p=PYOt)8APcOoUWS8CO}AGo{13WG;xGu~{~n`q=5_N3pDh&vpm zb`nJ1xG(Xh&t0g6eW{S&;;5#A^>}jT$Z+_`4L6^NZ@Fy-H0s3j+aTD+cs@^)b1g#b z(-+Lg&GU1D`H_@er7@Yb`oi*5H;V6xI8B&ytkG)J!g=EO;_&f_yFc^F>XtiZdlCHT z=#IK={_x2|^ENq#&lGO($CbkK$#Qi_Ne0>}UKsA_S$bF%u7G^g{uc4AN`qFda=P`Q zq{APYu4YP3wNblI+&twFp8rx5F6`U85_9AN+HllXZ^l}Y^mQ+sXZeL`c)gfJM85^D z+srw1)9b$h4y6^mQLM>O@~JA%4FvRZX^Yla6YOW|xzZ_;aAj$o^r#w|wp zZdxt*ZU!FLl#|`4n~IN*8j_>&!JCf-9YI+})hfXq`&Ofdu)E){s4PtkPsQv8yT8y^ zNOj;?l5phM@RPhC%dUUmvP*)KbuYnNidXmG{EQPEGL7fw_|yC}OV9?l>!f#(oh!Yg zEZfU!)GUF5UHBMw;e2bUDv)dMu90j@={>^=NxstiSB3E|e3Y%M|Bw9qY8NM1n*`@_ zxr5tm-{1AV?_bCH{=R=btK8P5_4NLJ;E2>-?kn$WbM+oYO%(1nG&tY+Ldq| zQoKaM9aTjAw3<|`@hT$ec~lX}&$n@lv-kGSZ}Z;-&!Vq8!~6l>`2dzCF(XMv1h*)8 zB5Tif3t|(@E2WXSc*G<8V3NjX*OZ1l5Ml!p^O;-?*%Gx@qqkcrTxts!!vU10qX`64 zI=#MDJ!eqsMYm7wGMR$R;6o_;(6x>1;#e{<63_2mnRX*Y($Y{>A49cEB)qa~(Qi$9 z!?jIG=cdhrdXo{y-eaTlrot}&FU}jUe(J5gEsL~%3?Sw^Ndi(1WIW>dbwG4=4kEvU z6G=nid8M{T=djkNmh%33NM#wCi4T{xdTrl<3?%JZgI43P`Rb?&5|z6J0VmWM4-M9{ zCSd9Esof@1$PgeTudA1qhNHfz(fsD@QZu`2WdD}u6%T#`v@QmPK)Avs)OKynQVV2 zlkoYH(Nuu>%7v^Uh+Ta9=&?HNsO+MYE9T{gc*BgD4NjqRwp zGg+l?Amg!?R%f>C&Kscxf4lZ8%pG5+jty}4I7XBnCP%O=0x1{=9u1hD7zG9sxl_a= zoW~{n$+jY+elng9no5FNuXa}oF|E!UNx3v`r^A8Uhbm_}5-$XFj$}Y11RHUkT4~bS z-64A(E{t558kpNINf8j7o66^8`n1g)%Yn)3omrw7jnioaVE(;?Ub)v!uO&wpwa}Zq43CX*j+UEQU{Lalf%s*>fi;F$&;`g);?)yySzxJZfIRpylZFLPE~H#?A@nl9J(or{ zDC7fI;bxh|EojX~Z^Qv-SEtpg_o()`ZakHosQQ`NWwhhshE_e9tc*s~sLW97bsCkS zw9I~bRz@^@R>qW@sZ@etw~AV&GBLU>-+JzSchp(ofqO+~Ea$ekkhgG|Y)*d+_ZLsD zWQ6^%xphxy?v7ceJUf|scCrpL8HeIGWuPnvojiIyAGF5%v165Xq0FK zmro<=3oC0+hTVn;C}0uQqBc3XQfl4#+}j3N;RVmLt2IlHee~@k{W*`DU;U=pVBKvF zFjlz&u4M3a}?!HOU9nwS|ZZz`1Z94sw zqwZAcwJK4obLibld)V)DX;gxLa74CW5TBTFqtzp|X6ipj1&rW{lmzwqsppy5EO&bU&$E9pj;iVs&#kG@gef`89ro zx^$``Dd_i5|0IkGkAn`avu+}H?u1j{8}we0{I>-1i=7H)4Zc7V!Yo~o?(~Bc*tet} zW_#lyt)N?7ylN`E>H3+-(n&OiTq^fkMM_;D-z(palP-G)1`>ZF2%dqt^8Dd)X8Y>c z<}ed}g*)F2nKHgobhX6FA?C3999B+!*5I#fxIx`nH_oky6+)fJm!&&>`q&ry+N{yN zUEZ{3I+9FCfbV<7K-=zEY>`pF=mK0y@t|cMP(VlhJf}WxjPs24@c_p2G zJhYB20-6SlXT+H9Now{+tGH6=EkRjyj*fhQ`Q@Ki7O_*g_3a#A4dp^TRH24;9~lzw zohyvz!Td>4ogFBgGX9$1V!tp2J~+6X%c`FEU~d@+Np+i$_EQ3@Ny^kmtu6SJ?!a7R zB}oByMAtrb3F~09mBQL%m$Zu9dQ=$WJg^zrIW^Z@vS~?nGjir~VUbx(U1WW1&YpH^ z3<-T+H@~` zNo3!#)z)_|j*_tc-Sut4t-vVCyH*kdgMnzXWBgVxNnY$Y^{YjO9|jWNd^(9{RGoR4 zDC{=mMSU!`_JVb!}%kVng~gz1Ul z?YV=ypK#2rct-XqS%x!;_up5R`LCLPbf^z+mSM7t-oxZ+1jx-^NS z1etSI$14rtkH5fOED)rf!6#5B;Sk)pYV?RR6WSRXn#>E|5G@glGo?Sh&}museSYOv zyKt}08#Lof5iPN1#u6t2aocjBkaY=H>f;&vL6gsok_O%oOZ+}=$7z^;#2d0+?J6Yn zF=QWK7EasVG}v~Du^eO8s`GRIB8#;cwFmmM&zZv^1g+69%heuylPw*7S+!=5d5V#@ zd$RU!!z|7mA7%Ee=j~B+@ns6{`Y(kHGy(Jz^3F1A150v|cxVX!MP>-@?*n;gWwKOU zyzc?b+7r7Xf%2)b1}^7|>6P(iVb{=fL{M&SY`O|JLR*^jk>ar%RcO{*pW6#-ZSfXc zcw)dcIFeo(td1e_T4cr9^uqYb%c z4D)o)zr1V2x@}FLH0<^9(tJ3^{!i%4hj_Ou5`FV%qR?sAH;=2gyyM0>@K8Y5=RfmE z?-WtrZ?q9yTZDwhe|J0Da=6@y5TaA&#b1iILTwa?HuesIG-1EnZgAc% z;kb4qBiyN@8Fd)x(cu*w-l56<1K*F1#04ShusgL(qcsHFwus5>(`S>rKeW8)kx+m4 znV$(t;T%GMKs(TWK#3mFy@rPB?}?8(vSGiMSzJ1eON)y)T3y18qCA;e)S*pH9ojmd zd6yY{#tC0y2#S;afSdNO0vEt?K&*}M?FGfVLW(Ql?%{MGDlSQ@l~j=l=sM*^K1ibs z4AqkAWr2NxgcO)b>OS{`NQcd%6dwtjnIS#AVvWSSLAO(fdN6U}(Ob)rgR3x_Qqv{?|@ds4{9l%~A~w_bS25Hi4QWvN2M@tD8d zumxj&RN)1!7CkQaj6~T(*`@>SXmKa_%f7mw4TL96B93gRom;!By^sP%e1-gsz6!gb z7E35g649~fYobB;Ju0NiRDYl%%|Ja$xUWH*!W-sD-CpEP$WY=Bh~P*v@XG5^22WkfzbzC3;^~)eRw=i><|dihULLsi4z=dgr*C+UT66WySX(Kjp1f)M z-iLRnyW+f%&}@C)?j3h+R{3F-Vkh3hex|?g);n*|t508h&m+9n3yY`QqTB8cW zj<2(0Rd(gJhs@b?pt$FLohndUQ*L1WjyxBIGtp505$K4toyHIDK5!1}b77v=EB$C{ z-sw%=I}xfU(~nbpqpMv&FUeNm29ssAV%B+=srd8MPzUwXsVAz z*+HQCTs>Q)L&RMy+R8Kc(56WZy|_^Be93%8r`YM03Y=`hr^V~I1-G``5dea(nqZH~ zQn>e>sA@IXdu1@UAMiiqmxQTf<+)ti=T7(LE|4t;)d+sUM?caVvLO4? z8~teBp!L1*p(``RnS&*mqf6U|rb2we0!dTYZQ=oWUC5AK-pV$RziR^#+nsmK3!6_& zgvYO%xX+r6Yo?AU#ll?LU*w}A-q3B^x=}CEr%Ojjc<_P^k0l@V6Tp`HQe9jno0;qK z3+=;uq?h_nnTjHp2#$UGUypky%l!( zT{53i=P=H4s=!cN=yRdDuu83o&rMd1*K&&r{m67X=dPFxPhLH-_Fb{V@CPGdrFCHT zFnhnXm{rdn#SU%BNf8>@STk<9NjFL`t>VkU-%SG;}28~Z%1bVf?E`BXMx0K|&bz7>Pe{_ih4jlq)T)Wma z?7Hrhl0E&iOn8I+O}BSunJl8EV&OOJH_~JZYiD-JdB&PBN0FvbWcfe}(w{-B?oq4` z^?8zC8J4=g@vtp9lPO-*)pIP#xDr`2ck*O5;9J^Xn7?DI`a76wBgTwHBU>K5a?`OZ ztmIQxt{zyuYg=5YY#g}H>X{sIr5gTiFFqhLZL0W{-E9B;b5|Yy&*6Av^oo0@7azWw zXQ3gHc)xBnWrZZWmd*C{3@;<5hG;^kQ#WY5gVPxHt!M#%`B3X6+f`5!5TTMALUvUf z#b^{d(Qxye;((ISlKZ1og;-cGsKw2b?Jek1d3cOWETt&U3$MLkC$pH~8%SI|Jma(u zz{WZ{w54EG7BARNjkMXX#4m&9HNsMbq+D}j%wmI8g$gOTUWTr7@EjO0140Nb3AapN zzde6PCxFp&9VJ0Z)|LKFZFA2}Q#wj4^L**;+gF!14vV z^3Ol#V14-bJySCe-!zkJ9=v_ifmiKr9=Lte_^k(9&>=do_`{$)={8*clx$i}lRRF~ zc!Q55%4Tgk9;dBQqgyDy#6k~Bp&ekw?&p?>=E(S!!%DG!Y@Qi~WTdCCzZov@R%~J- zrJ35V5>hj3?-qjTgnQK-OWWJb67XnOEb+L7+h30`ZSCmhp`E1|i?mW*&CL*S{R1M& z!nSSA<>K1k#9W2Qtq!;+D6BHx;w~q&I2lQtjDH1YRHcLT7WG@+w z{fyL!x{63xo96cA1hKLu@2+|c33q;2qgq;w&HBv{Pt?_EV++1%na`}T-R!;fLldi4 zRkhlPe$REgw^|H8?N}MqR7vW~Cw0$T`@I<#QAFM~=?%gU_&$tXwl0S=E1eBs^a3t6 zd2qOEau_wcpzWqqOvYgKCs?L1&Z$mz7^RzXifx`nIFxrXku{WZRWynnLZdiIFcrhUHKrQIYvq)cr}hpOJV zH=A-S`ZGzVa8esd+IH!!He-{5p)&}U#)2Bb&~k*V`)t{`epRqV(t%~8*X&yRKu1Ec ze;1PDF@w#bS^H5o;J2qziSzhL9}?afsyHQr7M3;;)ERVRsz5M9eyl`MYcllqNYN9s z*DEh`Mh#|lXT%6-d|TDxb!b#9TMD--MVt~jwu7!&Rl1vw3@&I&!l+5y!mB56yy-}* zQS3uWE;{v8%sGB;tMRqqNF=c(86UQ3eRJjPR3tW)i6mTly)rQW(8b!2vyj_l@kd-E z{xtKLEq+_TG7?R*zq)vHa;cNx&*fOKNIb#A9u%peU=1>}5U4mMl&nP{jw(;3vPnqS zHZ>6(lv)V7(jICe7)S;~PFG{5^P!QT!21Y}TtA6U zf^$a~7HiFuo2(JknzE=_>rm=*)7w*toB#SDkaYd$>)SBV@NvZn$y0^UOlLUf(&>GA z-jrkhF!JPxf5$fDhoSR9KKl^C8u6{FV8!Ijrg|ZH>B#m6Oai0YVm zLnBEr-=S|L$w0v%Q5{kivw$8)-3PEakCf#rzm(ik21kzjx!vfrBsF!{UUZ(yPC0ob z7)@!@+0sv5J`(qR%Rk>lmGAxG58$E-@OcOD5y_G~C+{j5RTsla-bdQMioj~Z8p>tl z?dX}2mp3OysHfv+zVM83gA}1`qKLM{8>Ze!rn=;-Ukt0RMU8Y01>vEiW+l)4CLtA3 z3A)9H%vm?Gd>tMOB&=5@Y4FY6dK6aQU{BfxIvdr8$O`%L{A4?;xQTO-O(MOHSSn58 zQe(zwCvxXFCv$L#Be`BDMeCB?^jI%oa_N(~qyQZSv!2y_E_7yo95Y3M)p^d5?9UTV zZTPlhX2Q^?$Q^lMeYOWe@!#jsIUPGCti7>A{%?18kb?E>-O7|08%f7fh`&&f`#kj; ze2d!uSUk*h@D4UiM0b{R-@lKx4_n;;tzmRob1obsveNBA#A&Wu>L%d@be27 z6P!V^{<0HAms4DDo~`Brb3+qK zNhTrrr_VWX-?l{k=)=2myJB`%rC@8HTCVuo2dB~GZf;Sj%#1JPCl6MXLUy4Q-5WRC zG0b2(b(tw7fP>O+H_(% z&g!RkWUoD)5)6)n-fGnAjoYKgP9~Oj$4{KjpTM+P%$l90nX-`d&|xar3l3PT=MN!u zrr&OXg<$KC8a?dO_)=H z8eO;-ybBAUUcPGHgIADx1Y<2C_PVF{F1NSw0|I-#u=Y>Hr_ZRvG1H~vYrlC$eU3p- z1?c$}PEXmg;%5y)gcnVM8PON~vKmaE#=*<48qZw!tXfTDL-HcZzMgT%=_KrbR0L6m z6PYSXAM#8g*^1O1nU^n-@K50+ZisXd5H{jdqAnUk@j^Ik6e5$7&i3ME z=7Lx7N+~z-3FvZ33mi0jsJ2px1>0FfN7&ANU;XOr*3g3;g)Nf6!&)vsJKHbtE+a{cm1Fs|a*;A(DOjT( z5?zp^1 zd_LOph^?IYW~yH1nB{0BgkH(X^Z_Fak(k8IE`p!h>lfG%jg zIws41e&0P?qoT5P;JJ(YZ<%nT0pL-Cxw0~p+PSr^Qbwnaj+OTGBGe$ z((>BGv)%2EE7b9}O^NZ={ufr;N6Cj%AaXzzVYoZpS2fqI`62q*MHLfod`J_h`Mj^#J$P5RpMH455Ck3HsO}=O(-6 zZimWzRtXmvu^I9%!6QtfS)yB>cxgT*#R!DglY6}HK#s_L8~6S% z-JO);9wk2LS^d3E`QfEadM0FmMMn!0-P?5ktE0xKv{?#boVO?nr+})u*$!j2hT5l2 zVU)60LaIK0UDTZLyh72X``|YOoxhsmGPb;>iNqZi^fa|m%3^3~4r>j=y15M$NA$SM z)7?R-2@wZ0fhEa%K`L1ejCDq-I8fG%TkkYy6}HZkMDbfYLsCN<#;k4?dqY-{h3t%2 z{c2~vYKP7Hr=xD1vP$@LW~_?c39Ibydh=Dm_nliHB8pIfAFs7Lg~<}q-4OAWGE^g$ zVS8h~&-#BNGV=(RUtXMW=@!Hg$uEl8P^I3@l4bRz@bAz?F`9P+UrcPLxCR1oD632n zYMx1^lqRwes4>2C^EKP5E>Rd~iK!zaL&qldNkdDU*ts*Y@7|>tT-C$R-PYVQoma0V zh2Y?-B|3TNt_3!8<4Uo(I{HM*>o<&^d=A$)+4AZQlggoMchzZq&2_AV@L*rT`UsC- zay)qDx%f(SVUy%;8Mn`Aei5&<=FdMMKmSI|FQTaXuY}FmdE9W|(m~dvFmVxpr-*0A z!qxElL*_s9{8Z32wo-5G#LSEI{yCrWHMmy#S|}CVaOp_@sPHHy$gX5vO;Fp@<`(vQS>+pYwOQ{D(uWJem#Ho#OiF;X ztewv%f=8gr@Gcn{kyw#z1o9?%?xo+8?8sDzBrKHuwWd*V`rb#QhIR^n&wa;7oi@$j zh7m=podHE3^P<^%a^V+Y>ufwanq`b;4XKLUUHox0Uv)DGRAlcWFrnl|6)zqqDT{yb6{d}jWncCw z;RET0&F|I>qO@I{j(IJEG2w<#%eJcgoERNz`R;q4@P_-%5%aL}cgmz+)H^1!LU=Ij zDg|~bY4slvlx1OTXRwHit(Y_w83-S1z9czmrV@I>;!%O+h0kEh624^^vJ^cznck8P z42A?1%~=|SbXbMFqg~*3s?bJ4N6N(U>51K-5@A7Vi!5!pz|`A#wtjSLfw93ugRO-G z69$h@)=)XnG9)!8wl}sZh4|*iE@5Wuz zgrco4tsI(NKw~M)F3rr<24_OU4KyE5*f^I2xGi?JZ38+J9eIxd5FNgN+@-tE7z2~s z(P!F3Q8wKv!SC8)`i*7#C^8FmxfyniFnx|8X4P+~+CTF{VrMQN_>585$M^AbE6*vz z=Xc_Kis&V#;BpxK#VE$`G+B?{6~%L^#uCsq!rJRDuR2W7R0?OF>aTR`mBC%0z7kF; zO%KkY?oE-(v^{8r2Le-4Ii4Z96P3GmB|c*d&Y45|hTCGdz2ce7qJ8WK&L6PfUhX_v zw*Hrpd_Te&rOaf%9Zb9;+hZF{Us|_>MOy_s*xtu0k-mE@XW6}Eb8_<-Q4^S()^pfJ zHpf0h#U%W{yO(&lYmOXizo4Yc zId@Scul;Qo@G+>mzL!brI3-}_^&hWq#pRGMDcrc%prA{N(&#-RDi5Gh9UG!$=?o?N zW%JT!%$CE}Z@B+PV-}WCb20P91O0#9z&d1=Poc!_>?F^^`#upr~FYUeQad5d1`UnZJW|VC&p_BhFg1T(^rDI_>?C09`s2` z(d_}qV_erF?@6lfp_ZDkBG9HIt;w{cTlpdn7Lkqd?eP^1&e<~*A4%^$As!Ulqvd+A zmD7&ztht(p-Z;M_?hU7h;)tGKYi!R8Vtu8O%U2Ta!L~D8 zykV#^l@Nr5nbsI&VcvgW((fCcvmt9=JN}?plB7UuiVW>*9 zZxsKzHR==7#=pQtJQEX@69Nrf{-}c%aCm!CBO4oNBFPCQ0gQpkTdw2|k`s>mxBQ;Ge;`~o@ ztHA%~osWb99(lSEW-p;{Gdk&sC2(v;u}yK6;#wIe5Vz$>66O84Nd-%ui;$8Vd$w8`-o6Xn^UQ+B>GovWb=UpD8aj6PY;G6BJUEZT zm-br(RVEUw_{086EtDFHD8+#t>|3RAJeS+nu8+~vPL0;#@g;FJh~*;DsxR&-*VaDH zTY|z75muPNgvK`P&Fl{b%+>P-nAd7@YJ=|5meuU(y~Rzo;V}(G(whl~LWKICt{>&BchX!%Vjh;tqS&0hklAu}jR zU7}YfR80bpXeBc;7se0(%{1(H>Dn_ou2Q#_zppRmJPtk{u#%XXo?9|B#)8>=s%dtb zJ$AcK7}!xB+deotF+H%K@afJ8^iMYiM?48@dLWcehdiRoX2qr3L_5kVwe(y-P|j7* z?-snkyUP)8PkUR%)TCi1e{}5+ ziBaf2B$8Z-1V|&(S1=8nXZCOUCXFbj-0b_h46DnHWWa|{fJrX)angB zUDy&R7tg#i`79eX5>3%R9U9w%-0XI4HDw?qpoCn!!kF5&K_{ltttL%8A@KoLLZ*(A zp&*{8jb_9kIRM}wa8(9`M}Cey7SCza@#Hn^g`3(cmBnqP!5qQT+Dp+qSZg$3_@&xq zEm(s}XRI6^bQx(Qt2x9)H$9cg#-=fsR6|&@Nc&MN8mpK znJ=9j@Q8$o#tpk3@iOYMc~-k9ZqqcWzRxU<1yg>f6-}D_wNfgiJt(py(;AH0zNVUi zxYb}X=G?jB)ToMq3-(W8rwZW~QcHV|07>ZUfcoA>|e z-6)pitHcnQ-%@`oobUcs^0Wg#z6d%=mc!GnSsr6XCK>%_ckQ-xp&r_={Jg+aXu_a3 z)^`jYWIB^cr?=P&G*UTMiTGm{qus7jm!?~dND|jF(Ze=YuZ{KaVdazWxCNs}Y`Q}q zIT-Wp`@*}=ir{WI)TtnXR~bXMS1tXw5#u>*62qmV7#*h;|4xl2 zd8=pbm;vIC$+TZe5w=R^=#ZlIH)Nx-;UFq>z1KeoVL}w<2 z+`Al~5wDe>r}?OAnx=}+@^lk?@1oCA4?X_(FpWB&xzxmGlA3xWq+#{VahmAH~FW8 zA#h)mYDC*S9MdoAI3x1I1zjR3SguZ+ya#VT^ZkCbh2HB*hrMJEDm|%2EEiM?Ize>f z+E&3~DC~HXBoElmF4tKD)O`1&TP7Og0ePHH1E#4jlQwzG)^g&r;79+iQ&*KNK~nr@ z2@5(qYUssjto~rXPUJ-r)GZ?+nfuQ`{Sn$+t^S~+`gD4qBNUUm1jjB_$LWohpf#W} zpx6RBQ&w*|8~O|K$?Iqse7S0@SynyW^_-w2!WB=ASu4cYp+1Vsovn7Sb~$oM-_P38ki<(_7Q#Y10q^6dw4lyBI4zz+SIut2v!X&i8V}{&ZY2}JRzq&gJ3!3=*&7rxe z_KeS0cDQ5Xr54;`>;_5rzr&hhWHTWaC|PZtquzhcCExLE>*8saEKJDq8x7PYL$5RS zVeRFYjm-rG`z4~vbr*XVTSJTsQBTmH1bsWh#XpobG&`BJWEzqq1jSFEKzy{rl(~^> zH~0UuW!LD55v4U?@Rd?Nx7Oz*Qh>Qd%;&sSuag2;mra*MRLazOjx#-CvkWG1fjdK|7)`YE>QUoBhcE zx;=!-mAYNq=$Wp4rnqf*Hf6C%Y=j-i?_FI8%652)xGZ~pYY^N;*6P25yQb+*KXDi8 zp*a~Z$y)74PwGkvgb>^SPJ)8gZ2as-<5h|ITIU$ApH-^`n71mlAR~;!{3NS3J!*gBK(}ZhIsaNd>y0A#)DI&0!khKE-6yt%#N?8%uOUScH zxx!B9g^bCLZw+RI1BqQEzvBip9%>h6GHxf2GL%Av;kYF-nD7ra90}jlVrK8@;P8Q; z<}%FDA7Aj57j|W5ccne;Dg{HXxpCm?13ccGTK^UMzMw?T66@fyM~W1I!m>BSWsX$G z(_?)s_}|(qXgcC^`yCj|qO;a8kf$oD$0mxKQp%`4uS!p6hYsS_!_>_WioX-A@u8?Y z!eS=9OBe9e4^Q54TY4<TTebc_Vd$;`|myzuJz8R%zntxC95 zwt%zXk;Hb->BvyM9fAM}RHbQfKZZ)lkDX@*VLGZ(Dm55xAqWW94{y)H1*vQwh>q?m za$iH|&P6a}P|$hOYrj+81CQnWK3-~Zae;h5gXCk#{zIA-w_u9h;zYJH*Y5Fb8fOBt z5D>BYBRZ#S<$Fh&L5`FZfrNZW$d`E_fcX?8Nhtq+#eD~WWL24NopUSas#|q$<(zX@ zbyaoFai)86PY%ogGXpXMNDz=LK?D=)V%SA=RWQ9(7sKkh?0e`MLEjTyU0rw8MR8Hd zuCOxI?>pyKb@%l2z&Nh&4Rm$iQ1_njpFe)*{}u0Lx+zIYSVsn@gC2}MI?6J{Yv^$_p1_WwO zBm!x>&f&ZoB)NJE+BfUzoJv{G2Dn-YNNw7Q==y}UGbK~P@3GXw$emVNn1s~4cR=+q?(e6 z0bS+)%k?zk!wf)%$%7*RCIc&Uz@k*FfK>YA@JO16B;$mAmz$7M06u~o3K#e-vc=#s z4oDt7iixSHGZc(_hU~tOT`r5RRMJ7WddfjtEa6P(dD!GjJZz##xUy+ytnMMhm$)64 zCw+!sCKQtMq0p$`Vig4Ji<}E4kfWtQ%$~G=mQttFKN}oEVCP2Y8Ux;uS}MjSo`-7+8Y`iQ1#Uj@Upb z2`!q)YrsZ@<*SsW0*mZXGw~vDCRX0UhSE1xzQCcn#bvGQopxvEuIAbw8 z-6R!#$k7sI!_!AUPgylLP1Ko4ilLlqmbx+#SC7x#`ga|CbiSSA`4R$v+&6J#=D+d$9qWb|+;i69+ifgm_h<0RGA zQeUmUs6~$2EIx;vvDysbj7y4ItpU+hF>4&#cTCT>+XH zm7SiaQ_v+ClBlF!Y8l`ioYZ9m*ukK&#MvZ+F~GILRyVq&)qO?lbU6XPv)GsG*2EBl zusL)z+>o8Qo=hTwBu0|Q1tz4{7*QHLIv%wYUvuHUF_Y+9s#)X6CBsLrtZ%rcmYFI> zT~dH}%z{CnzUiqAf3ZQOL42&p<70Mc&<9jkvmC0+aS$vvSKF2ckzq*xlp!rZTh$W38QKhQ8hP@s@ZrY8)ngiDqz$j>>+hbdJHmS z{#@IHpg5yA+owG;sz9hw-z}!s1p~!DAnf)lJ_06xF08ft4f*pwvQkYxvoM?@;|g>T zD;4#8mZ^s=R7Un7XciWQt_d1vG=Gxgosm0Rrd%yU*V&hpMSx|}R=Sy?sdx^5yyQ7_ zCX|TKR)Y>9pslD3s%LKbRbe3Pql&DK0a;S=P+g;ON+zpwEkB}z2Z*NkdJ+3!s@&=d zi#s;Bl8%!?LUH5)n&|58vS$*@jzl%L>iRJ%GA!xMss%3p`GA}f4O@F;6dnCE5iur5 z$TyTRxPiR;Fq$aPeF2xxMVO0#C7Id<4$9>iR2+D5AdBxDdnYs37q(eNR~Y7xaA( zC5S1Ht9C>3FOfZZ4&q+zL*am{KYSG4&1cwvIF!NXbmTi879{kNU13uIK^e8l3Om1b z=3~*G>6uMkvR1F>-WQD;-WO}7;n{H69JE5)nT^_G}JOp5>;Dc+oKqVGCp z*ARFN*sdQz(OS-?>u`7o^qsoW>`wA~>+ja;@R>&=$-d!A=AEr~on=qk&RVA5>JTM5 zgkIXA;jZBN%^II0c&M3!<_v6|PUKZFc6e1(uC}4iv@_2puV06mx}Y2bz*gsSy+TR* zSkypSFeElM8i7~aW-~ZDfs$n>{($1auaL7Yf|3_W1L;_S0Ws6ed-O zoi5e-UG0BhGOlCJ@g8!&-m39|bqK4SZ3XRkFPKaWu|#abku}^Bv8Yj4(NhNid)ojI zGA)k}*~y?|4s)a$7g0)M$R$LkLg{+c>Mb;f*m6H561}pkl`AhVf&1aU?4hIQq*ja) z!<7lejpsLqHYbwJbTrhQ&6oFV902N#B3H)K^ju(v8a(R{s9w=>WgXYeA;=ZyG51|? z3zumCZi%mNMffu*q4@T-63H*Tu3xR$aNTTVUDW393paPx)k104kWto^2Gv4!TfID! z7KBaxp{c0NQ4jmNJ-){70!rGY@@Y8SGLF%!w`{w7&}@krO@rqRAP+MLD|~ozU~@LV zxo7ZLoEo&wn9*)D7>pDCgM-8K8%D-oW*^HK>^T5>fPOS zxF>)(hCaGnp;35+3XQs^`-4%xC6o#4JSW#>bOt56milV(m4z9z*nW-z2g8e@-l1S` zVD#!6XHTxk*tKf`B?gZiKdB_+)V0@)Pwy%lfzqW*Z5E(`sA_eMTXWVe-%Ad*o;Zuv z=hf-6?f_Pv4YI|;sre)fLYtbC+>s4=akfQ3$^TeIt*vwZd&+{ILY#k0nNaxpITC~Z zJy2#6eEn?yC!+1}j5skqqkMe2csx?EB@w1;j}U;1V4_UdY%gNQW4Yg$vso6|XiMi) zAt&Ugs|V+w&K88SdN|0!S))=|pO-N;$hO@4Bf5xT(w#N3<}0w2&K6zs`d|D4v)YB8 zlRBdyS-6p35a!?pw0JJP3I9PQ6|G$5u7P?q3c!RH>+p# zZm3Z6+jpi<6UtG)N+1X7F}T%lsTH(5g1#$CqoN6-(!G{ZF^)jJTr)t1mgbOJ(T-5T zwc{Ot^S|f7?$E&A!Nf==-oJj+e0X-ZnwEn;i3(AxVA1I4d{1z4yf+q4l!hbTDt3l6 zq-bDd&h)y2Z1;y$%?EK3RYOU&9;^G|^+J74eITf&HgCM(ixkFRJ2|v1=d0($?uiKJ zv2NaK8;@O_>dRrv>n!?F;_8AHv5N?E<DnEG=#xg?;-r2e(~429C;*{R}&0SM6dP^`u#9W2ernXKTQLKYP-Q>Hau#eD(Bw`j55C znVcNp*i$DW$M88ay9FGRd0k|fUMzruK*?Ea2cLDxKDA2>BVU!f7&u$S;LCn7CRywj zM=7rJMFNtP%aX%qts5{DP(je~RSEp z;v5JQA5J!QH3$g}?X8Z^sRu8fYMOk_d}TD}(e7B_C>XE1a_)w{i?&x&*IcTP^p`Un zW39aZd#V&gB}&%zUeNJqOlLjga(4Z1hKm49x32IZ*%W_nj`{E|LLp6zhn>D zdw16-j`U$9_+BVxE3|hx^{VdTBcYtfoVU-HRBvCJ<$s~er1h#1oJBvNlohZKX{(tDv&j*{E{%HJEv27X$CFvTLFDyydg3 zSIXJa;pMacwRE;R$!LdL6#Q-27Xe+Sq`T>357ZY-^}g=4lF&~DJC<`E`9CFrle%2rcF8s-*8XO1+; zW2xA4EP}q)wo^vownYaOAo=DMJ_ybaXGNN)IKkYVxy{UD)J&lFSkg&zBxQ-Yyybkx zxoju9116(rc7}wYUdqK%8He6s^wr8Kt1sYjNTSA?ECpRDrzZ|XTyFH~kQk5HUE@=} z^|jgUIrEZX+_Mp0k#s+CHM(QfimMtN-I%ci+zy|tqtOl1-CM(j!Fn{_!;(F56fWzX z!1k6g+JvF|2X!lit!5B}Y&_t`!LFqvl)Q$vUccNemml4-cDfZ$+6|(`Un`|ocFU6p zdd*IsE&y2&_j(hNq@Q?8`OI_4Xlrq)XZ1OcEVX-p^ls{02L z<0VTbUxM$_>T(x;kh@m$Q^A_i2PU4bGYDsfbwpDR}lC<-4GeMHa^zNWtIpV??n*YhlQ{FTpCWbMJ4P_i>2< zt@z{!CL4Oi+tOr9jEMyXNBfUre@Lqnu*Fg%km=0HdyQBHab14;H=0$W3#yc`-|{(&vN4OwGVs{ynd&6 zbJ?)32t{Bz(uhFfKDVgsUEz*wCf<+&{LXg--qE^>ZOzizEP9>Gp0x$Jr?wb2XoNuj zP_Q%*9Dl<>i)=#eVgVbp9J!HVz+w&s{Ebq=pw<9zbl?Z>AABa18cuJ2|E>E)SF}*q zI?JWda4s|`@7*7*@2nqu$5u+_wx&@UoMed<>z$bge74Yc@XA4{?9*U_C0V==_GK67 z3Mg-YWtQnmQ?H>CHJS@J4P$nv?4Uvqx$?`p1SZ`ku~^Ej7qnW9Kz9H*p#G8;mVXC< z-w79m6xANc3cYu9$s`HPZr#3ZE~M6L`Y!D$AIh$|i>j@~ckN7adxTq!6I?%?V|*E| zIjqwqZ`~>l^^EMs$5(lETHaHZ$g6A}UG2*Gvr`rfB0sBkhoN65Z6&4f4?K-k@;hXo zP*!|XT=IL+c*WJl<5y6mK#G_i5En9gw?kYwz?=wL>8D$F>O9)6t-bY`RHO^Qt)A{! z&-sJdzOa9^UvJbzIG&8-(DM< zi<5MyHkydkquJ3!Yr}XSTie#wv*hbs!+i#8o3ypY=T%oKTo$VuxSP{}n{e9A3JWc- zc}8sta_h9o0YG&O&;=Eeh4wkjn(v%@wY&g5QyMY`7=uO6oN)}+U}TF=JFG8GS5BWJ zATmA;TnBKtglr*N&A6dD{|X5LUVDMYsHtCk>_}mWw@f}TKXK9U>9Pg9_5y>`Fn(B4 zqJhE7XQnTkz?%F$wCVd`5dfQaI&B)|?wU-O=G^fl*gOW&&67p`P2biWL(VTL&=U)4 zbz-I)sxhqQ-X zaM>k-SKvNtEF11DTMfP^?*p)Yms-OqxDD=Eu?F-u?N0sRq2bfu?ir+BLwmQo6cSy6 z^DGhg>?b!(Up{eK$US%hc6^Y}fk%fGv~H7$`nC?Qa4KER-KK#eO-X8&8wP+_mKmm< zctvx^of@EMK>_tiC1p-K!^-}c`Z!0pGQA?WxSpc`MW(}IavAf1#?}Cp!S99vY&)pH zk+_b;2m!3~jF5njN)bYXcD-3GxWe{KDn>~({RhASKjV{pUP>*okX|oO1_SAjt_(h( zDISnT&e!wExyX3KT749uC82lmSBakgWR3oKUNwY%N-yZAS9W9V7&`budeP2W4eto{ z&BOW}KEz}PcA1bN;WnIQ`)yJ}uZG!*UC_|)0M#_HW9S8}0jy+Yp@M4au#t7kHCHfu zur*lzb`}xf_6Lt=Fsfo`2oxb?-s9(M3*Du;f(b$Ja5>`8N!eJwKTMpJ^4!)C5i+Bh z?8cfuV)Mzxa$e}ZRqQtNm^q=3-;pP$-dXjnUL0jipl=zcw%}W(X*rC=pXO3 zdd6o$#omBh@3a@QK(fcNjaQZj#>PWk=ggNnePp(DdruMih_fe2O&qW3VE{$*{nGSK zXrZrd=cH_F%OKn35G3oP60_j|gSMu2cDttEF3{HtC%*ot+U-lX_H>_k?`kx_f9D2j zj3zT^c^*B2+%p(OBR<}YEUE%>IjU-;CL$rSku)})IP%R!6@BDHcMsir;sG`~krcvdGv9g+u*Qe#F7r1rv!;z#&yHTcvs&AJVb8u>X0@|S+v;gOOOxQN1+FJRxtOjWWlJB#EHg;ri0pw69u7EZVgtf=g?w_%J1CsW1A%eHyM2y2TGxoUIpAOIPG_1g9w zz&gcJgj&vwxAp=>0~8?)w>etH2egTG3`DbgJW=l6xd~<6 zgEmtktk#18yOF!ZY-tAl0i<&hG{_JVAL4b{}gFUUPT|;|J+TYIwI3zhOY>n2=ObX6n*S z!qSZi?wp&xNaATHab4s55-9imV!{esEi!-xsxk6|`Ow?gOfYxBkMShz@^1Hm4LKkk zoK_X(0YZ^65Jgrf!C~Ug%=Ux@;;GE{Oz$%Nb=O>hhOMnlLb88drf;57S9c{lW-(~J zlFJ|L*+d@^bCWf1|JYoRYBpo*~bn zRmdB|__-pSmj&@s>JpTsyqpG&Os^qxy$Xv@rdlRxXcF0 z5DV{ECpBwPxJ@gget?BMF^AEuH@VDScW9~_n;VXdHLd!?(-ZqKuoT=xMSg?+xKtjd z*MKUT(I%-0vM$bso8=XalgDT@BU0M>7A054T;~brszI+Cg||3p>qL}#8l_kl`C+R> zdbYU|HX-|aGSzwrGZ=;Jdz_v?oJmDc#GWl5uF+!{Rt{W9NiNA<>3AJmU*vIH>wn4e zbbe@Smy4=rE*%|N$Otqb@=+p*Zn&gD z3S%+ZXU34o25;C(gv$C{U}7j^6TM_49AiC4MKtnX^igmfRw07B`5AY?1i?+lqLoY> zFSmC{Epk6v2xmcpZ0&3uSRY4*DA6rzb?Q>KG?MdJsIxMh9N3VzQDOMP@r8qN9d*Qq z&hdtaD<*8HtES7t5lXdk7kz|e`+aVo&!Ly}4umHv^E)>OrIef<+n5@f~OBX3)B0WN`t6J>*>BQrJ)hKBRyI!%#C`oyYl79f|Qu< zPEIsMvDD~~4sT9prW&cPbS98Fl$uyau83@njf}^e;cy?P=N>+*-)he)%a5yGCby2h z4Cc|{*(hDideEL#mhmeww!LIWQrOtQzVX5NIM$}0McNd<@5B z`Yl|4bDa&!4>7-t|1!QMlQj8uyrn}={U?TH+|4r0e+pMq^e>q%PTzF}ba8qwxv=$} z_SN^Tym}qGdLP-_dJI>4@!enXtMA2k)AU`chwz(Sf1vdk=4600RX@jd2h(A}qre)K zIcZdzx^*P%Cgw5grCwCmr4}D_j%FYqdNS$T-1-}n3+d;;bBL;sRLL}+0b7z;ILcyO zmT5f2uHq(zrKMB*dJE>#W!@suj{{8x1GD5e;0u zP+)>RsrmpV<$N9a`ZuAXZY^O8iYA>!=khedj%=9tg9&4>NC{eN)EFHE2`mfYdcj*a zsfV&vxOcHH94((f%k7X_m`EHWDq0l-SB5x;e;%hgz_>Ag4}oxf=aGA_ScNAZWJ_?|cZJwiLY|EJH`{SB1$vI6r}B|06&DfLA%6;^)yu`gdp} z?&q|AKe2LJzn`KD7~wHI@B0{G1FI&+TMMA{PYfiI(lb=4@w4GIO%*~c7-Oe1WQYFnOjwVg@yc5YT& z?zmOZoqsha{nVeA{WUla#acO7ji?P;6BIIk_x|Deg^)(wH8BRFcF1}Zu(#)eZnO?X zH-d1Ue9;o}NXeuqRfjUkfrJ#X>tuu3W${a)@pAX3Tz0&tl6RTif;w*0KN1dj;&G3c z<;T)5X2`oS9=!buFHkaTx1m)_i;NKjmtf2JFhTGuCgLeeB0xwW=kay34s@4-)u@0D zxGhT^&{WzT&`Cz4Bg;BK5^f1w{G*892EFc7!UZ38CNY?hqBgx`FuTo44>o7l?g40P zi0D)gl3O{oxrhu(E~|b<&Y?!czuVjm;0Ugtd@DBwoJDa8!;zr0Fhz~&@mzKyou0_% z#?$y_7m1K(F`Jl6wu_rF7N$|a)h|Ifv)Pm-_E#zmPK9GQ!H`WBH#Bh!(~I56auBn# z#OpvDM{Cjh$HKNacDm)f(b1Gv_SkID7Q)_bo!#r#Xf0~7>NTONqBZ0SP6PrIL07es4cQx z7EIQNH8>lyM`g?~v@^jC$reyW&@1K~jO&oU07@GFL6IDQVqkXHU?Ayg^v}}|*zWIs z!2Y4kOzu1Qx!?Z5#)s|qXUEdt%{ISVe~fDh{-IOn7H9T@BRmF(gbnSC15I&Ga{%}K zMBzZNP1*TTau4vaC%}R3#*FjPk@-+`h66*ga;(@ut?RnpdELNG&Ks&DU7zn7sodbW zq3=fLwbjY$XSzl{JMg#mhcPC9hktB{9)9@Y))TBvThP0&D!t1ny+eH-jLI+xlMj)D ze9QpJARiK1_X_0Tka`Rw%C1tw@>OaOTD>P<{ql;dUlvYW-@b}BcAl)Ie-A!NQ2XE> zdh_D9*qe}!A?l|`gcI+ssxcBox!qQzkA+1LfQy=Uvz_rB?(n4H~n@aVCgp4XgrU?*&X#b@v|L;Gn;RfsfW7$qS+ zz@MhBt$rGP@w87nK&rj-g?sD<2joi#D4HlAU zumRIT!YdlAAL}%@Km+yjrlnG;u%f|Jdst1R63_+L!!OK&*E$_km#9dtSAS{j>zdYY zr|I)O!ppY^J;HP7+2gpgTe(x$Y>Me-R0!}JiD`PVM-Z+RdS14I_S$*`#fs#31ss#D zSfMF)jFtC*>XV9WCqE$<(%(YgfS(X?Ip&c7;)S}odkf;Gw#K=ep@k&iDP?NODJRgzLGth9e9TfBt#Q(Oc&i@9Z=xC_KT&;ww`$Ect$zO+ z?_(<-&sTj0&u6`5&!;Ny;!EU5G=Xu3RMH4_f^6Y(GhJ`WI=4*RFQYumk3M_Z=f3N` z;}RXQsfowmTBOhSJtREreSi%d%Xt41d7|?KQFN2ESWoL@Hx$i!`d2Tvez^1inrJ<% zZv7JJnO8o6@HXtANjBG=7Ifas-|6+YT9{yrf*bGJs@y5F*EB_U5swePtb?LYp9jlO zqeUCZuYe0{fkz@#lDVdhtH`EWHVJXMw;Y4encn z1ZIFXumiV-kk^ zdJto~k@-jzCk+3DD$Bir2wVZd(N;#edjVErntNXr4X++!E@kFp;;T;wSS$(6UNMSl zJzEF1^{Uka2L~GSS+~pXL!NAPcPiHpby(-E<=jRCO;tuxP9oGdr#GBa@a3}Dky0AS zWkw^CJygsP-_V8j!mf$(!hqhnxw~g`K2i-jp(VN;dZ*4EO${ZTl~R}#NTjRo8kkS^ zAMCNZU9qUy6n1!$k~*_zQF#877}*&M$0|Fz z7EwMhG*qjMMqi#3?xrta!d6afF+hHS)woS{c$K$Q*V&uHJV+3JAPqKpiib(jcfppH zlzEIFl0S7^rULgiL9#Jo3JE`|Z$f=IS+Y6w=3v=dZukX_E!G{4O*BISt>zu@T*~G3 z^~Od7#3b43#2x|?Ec=SRU{sz$ZzwlkRugX?yh|W*W&Lg!b_GRxCnu5OhYI1|iOI*4 zy&+e`;xxLf_Mjt}mjcC9(k&Gm6Z!1kgw%|C`kV1&PgsnYMd;T$d%zj1Na1QW>Gbx^ zHv)sbvecXO^fh9nTj~$yhbq~ePag2)hq|&ktm53_w}f@{DP+TFAQPVoaG%aXN<&I@ zXs8`|fZtdNHYm!}J2uW;x3?~+vsQ2YPnWE!e{TJT7W9!`)4-H*>_tvd%J2q75IduKh^o{5CVZp4M zy8ciPvV3&r(HwEcIl=Qtw)iC#{5q8!T|vdPm2+BlC!Ydm0t1#T3s3taOVl ziaI|ql#+TVDAepRf9Phj$KvS)j62p7PMRrws?+PvD3m(A?u^2TCrKerG*%BXFC(4= zHHf78;!>kzFi|i@7ohtoidCUI4>0KZg&Exbn$pUKE640;^|^hSC|>g>BhlDVx!_9t@`1ADBC z3n+ii{8#kj?ra)RY=p|-R5W|d2sMUWf`oP(B@?}NM(;8bt;M1a426!t)}p)a#J6csB-WvDkcN!pJRwBX{ZPJ;27UKBhz4qj|!HWzGZuF-LB4Cij!=x?O{WqQ4u7N+y%(I{NgGpQ`NdpXBH z=_9*Fk=ZvjkETK?}}QtQyt30(UkzXr+8uCkvm%ER^qe&~y> zyU67yjIDcy1gr`)Pa^(cG0bvf?sIV|{=KzyjjkfFvn|p;ep+j5=@`WPn(O zLoMk1rqueRJt}$P_Qbk`!D6(#?WO?2DQ4N4T$ft9qP0*xLUnS?-n_mD%m}r*oS06H zS=Zd^-}$#+*thQsf4kGawW%{BzlhB#VpXZl`r`FXdkjVmP;_YVOK36n+;IFCC=|4Z zRHfoTZqEjR21AG*g@7G(6dWXa4?W%e^Pr(<|Mo^j2-Fg|}`qOW#JH5Pqp!>BWGtXF5nNxM_D@sr8`v`3BKE;`xmR$cG0fOB+1N@GBVHEI`~ z%KhQZ3ure+44zHogp-leQ&I($S(c+PxmcaBC83BKn2_N06p3uSsE@X;4^CyS7>{Ny znp}VJ;Lydhn|q1WPhOt7a^_?H;H9p?vB}rXuDfy~()Y$Ua-<_NF9CJSDm5c3iz-bn zr=MN?5cIoV`cNwcs$$Yd6 zwx@mlW&4H=x+|~LUv#8=WOu*%VvJRvDnp(mKjuF3ra- z-E51Qj-Wh{9Eu1i$kdyy%6+j|)7_UGMUE8e7JD*PX&{m4k*)}3e2C5K-N`^8OmVV*WB*1< z!KI+xAoS!et#4`@x>gfe}Q@!8sVv9zVGxr43U2=v>pgQv3CEzoDoQ5EMA zS}h{Eh&95Ufd;pgSLO&v>zZ;3ejIs-l7DXO$PjfSlu)A~J?GCk$&O&_i_94yhn{s^ zzjO48ysvcbf$Y|6=Pc(s=Ba*F&E=3|kH(iA-MJF*!8ax3i&3SgJbT*nF-gu+dEz! zDofeO__o%g6b!C~V9!ecvXA#>M`Vq~g<;)YUbs_&fM=e9?(E;-kTlx8AYb0{cl z)$TM{b$@?oqB*w^BmNYPU4S1p<}9Z5BGzVaClaqip{Zf2@JMuokwVjk4T=CoV|y9S zgOze}hm95QCaDlo^S+VZLGKXWsG$NYlR|&W&yueQ|0sNge#MH4pQGmwP&c@Jz#9p{ z8N2z4xhM@c<6o;(DjEEogL0ox0}4?RX(ZfJmC5W>WvcazKV|8^V4V6~u3#b6b>6lr z^t&{8g;K*;2Y!GkI@@u>k|Z;KHiL7)ufp{;Z~*WEe0J130N|LDUZEf`c}N0E_R`h| z={4l`vnl8MfmG~pDoH;1hZgk3sH3(z`7J0Nnm6JSjEOY5)1VtJraI;v2)x7O&~8Ae zupA(Ipk?6>Sy>3!Iy?@l}c3Pb}jb5FOCz~NE zn6>W_-lo?2L)L)R-{lV1L^-4mYnxq(k*IexsLk1Bt3PinU({0xj^)M=HF7ioBu63^f51L*>7dac#Lg0&6oy6yF7D3b#yp7(AizXe9RKx;H=vpi^SVE|h55HG zQe|iWGEYl?Rb|Hq7mv^gB#V~*8cc&As`L!K3AD_N_YT)?+mEN`+TCrg{ejA+BvE$< zlj^DY+P(<~83?t0KoAh1;eT;nx@q88#+To>D>Zl7lzFdXO1sUr*|p_bg=fzkP*}ED z@t(r6yVIOyqqVbW-8yh=Eq%(&T92Ibpeh#oe|kMThPRpSLrrikea6L(H{$*bdZVS! z@=A}4x^Ce2Kc{8ab2z@9A3x8JnF@6uKYrnHc71`URo@^t^Qc3fxImRtKpQ-OC$inE zaf}fV1Ps-qz)qDNC}h$wBLsKpWu2P60-e-?;ZY_VVvxI91=GADon`e-0& zb>_m}gj39gPCVsFI-U6tJD0~YB#S>@{3^MUK8g48can|i)*UhWr0eBp9ayO))g936 zr}?T;iBVlN39F!sJS4Qnh{jX(klU)y3_2~$iqyE-ECdh!$`f`j6V1!Dlo5@pj4*uBtRezaW6_X*Yt5_4SB2K~h4S0-{K>vVsl4R0%inyG{&vv?fk!g#Kl#^&eZv*e=vA{LBl_5V7?in5#b3?TD6kL24D5p3Hn;X{JE5 zBTACYB`vcBQIZ9P(Aju`V@pvE#^XFr2X$tDe5gO_?wihZjRAOq9EA13?diw`g9Eb> zWCh&V+7jt+$kDE@XrwPG2-D=#xd8~zP$OR2Q4)*2w|j?sQX{F<@LaNYZ({~2efka! z^d9J&@mAv_)4|9&!^1F_D_@#>##aHtwfj9|38r#8PBtkn1F-M~F=GWZvL3 zQy-XGpcAY#TuO)@r!3gzU|a~Rvl|DC372dY4F)L|7Q#Y)Q|sBN#}*`RhYsLjIyOMB z9y>A+VW+Gfn~tda#!lQedUODzy8_v*_|{$l%nf+gGW46g#~;x;xrHq3X#Ik=Ug{?^WZ=Zx=(kU74AH?8UkcGr zoj6=)h%X=#34I(~ovFxfgsB`ZWXy{L?ooGWR{Y)F)Ya+Z$)F(2{4^C1T6ZeHg&F!q zuF9U+S@)>{F@~y3U(~$(%jKg_(mbqn_MOefWi}`sBZnP^+kc)7)hIj=)b2or&AkJbW2)z8BYWb0sB+FNn7xZL0)p? z&5+`K5x|d>_ziaU!jx#Qs4o`R=LF5{#e;#GNLnxD z*dO29PSmOG4dZN(aYY!3hywh@f9qI~kE}IbM9atYzjeHluQHsFxEHGKpm}IZT0t0K zTHb|Z8$9f{;aJU&?}irCf)=C|G|F|=%{156asIWf<2+6Nb?N@Yz)h5(<7fpPzyGl6 zDj-fT!Lc5Kh`>*_fZY@;^^+@?b%>6Bf_5CmeKmHUVC2u)iN#ldKA{y%ydp5-rL@kE z%|oo+@aoeT`T@-WCJWOXYORms{JE-k(mXT=tzhNPIjXvsW+7z(@kDzx;1JKy^Pxqv zm6O2U%1K4rYOf7uu!1eI{nRVNXRmy<6sO&(R=zL{37VQLmqw!kJmtwi3}9AcDCCbj zu$K|yr?)T^roz%4Gq?i|v!y$%!9|iiB(UltxiCuuO z>^}W9@Tv@K@vlOf>bR_6P)q@(N#cOzi6r<@@;%=t$PbP5({D%N*%w=H_qA>!SNX`5 zGp(<+zKTj=1T(i8JC1+M_A+pfGaH1}wYg7MLz`}1v*q;A6))BK#xWwITDejNar_NTcQV5 z@8M76u@LT*z*CQ+CJu?Y0Q!Sb`n{;v8)ZMZzd*-TUjjlK+Oh~EnRN#{#6EV!fr3_o z=(lX34aT@%^{=|2`={z1t=`?n+^{jNj5x`S4MmOCeUm7GPy(n4Eldh3W z*dOKaI`RI?XioK0LC5g5BH{dijMA?PRd8mcbQoGUju7_{IEnK`zGFQxP3 z0YMMCf)*OrF`-J{avgo6^kI4zoxk*$@80+Go=uQL0y3BLcEaZ=>pp*%d=0%zdVo&6 z-}~AteILYKSvrat`zWJO%vZYE_JV4)?~lybWlLWq+UIo3>*#2)OE1TwenYi{Q7F+q z)eAsa*qEKcXcGzyU#oz3q8C`JOg+>}cX{&gewV$Te1JsCB7*kV`Yr-$>Z5&PHXJH? z#Y{L{^ae=W7s(+2<_mJ&WgWeMyo-K^<8}G-pfpIFE}3%{qWxU2btDkU#3VUs_lSMI za4z9-Wa*`rco|#suw~EhHFxFwK9C`s<6B8DFl++a3hz)66*C2TYY)e*6(}}q&|fos zjJfl8+wpwpM$mNeGKg=5YFUTwB7O_>73%jogOmy`vl)IdZ#h2gHewU9StHQ4lM%i@n{k7_flD>L4QWrl5uTt(>ddPOQstJQ>hfJ6{8 zjZ(l4;JvTLT-K_iWKLaUF8FgW1>MzH6dx~se@}j zwZoiFerk9@LZTMC+dQ)JsfTt+Aq~|U#A8cO-HxYLKo2LaTE197#%ztOO~WU@t-2;f zSE9=ltt@<{Yp+P$$`}p_GTIeCrCq#XU@j-E;*;BQhud72ebIm@hAb{yMe@Z$E^BP% zDBNZY!cU7WS+dKRjY$$%FtT6L8hNu2giUV8G^aT`MC7bJWkh$?<<>js4JI~OcFHEJ zHaOrt_q-;XCyk&9GIX;S&nPj^69xtn6E737{fvDcoknnY%+u{>%pJm35J78j zUxjDLc*Y@NGoAtKx!sPnr=uej8A~A!<=M@(CZeahJP%5%H=Z&DzQPiFp{fYl&=l-w zLtwcLYYsi}Derx5?Lk-GcKKT4T@;MiQF+s;GQPG#-tTh*HyClde3XRW;$nZh++J`W z!5H)UVlkgL*7=Q1g5ok@$1T5N*XbJS?`nG5rqGko#po$Y)8zev9{Txst(#^K2W2Fy<8aa#KIts{)!{_Yk5wf0A)a;H%-R5WsLarkn{xp32 zPa$*u-*aI_e(nJ8hllt~WYS6|c(NQAel=FOLL{B@%KWfMA0-yy{$)KCJe^r25@P`1 zi!9R&5170+P{p>daCg9uJ^Lh5^SGi(pB#$QN8P1FycTd56Ny?NMAG4M4#i|6$ui>v zf|Yzyc#~qEK?P#UV+iVEg8|T)v-dBgtQc^OheO$D#N{v8efCTNxesD-VN)>RD4@=? z%kPT%tl5IY<#VwoE^3GdniU9^X;y7HzEZcUX?-PD;#G8*y~aqaFJjA@Q$R)!TO`vc@X^yQtKTz9=0Hhss+(;|l`v;8|ZnpVKvpDvmogi&ntS zUmSO=5v|bV#~tfIEA;T=j{cA5DaUR76*+JCaYz5u3L`vDbc(#-=4oTsfx`ssglUhcNM*|+--5+$w`z6?W-q*nf;HrAea{=0EcBuW!+t%rQ1#KUSEF-!!;2&-XF@Z(E*w_pS@u?z9ZKE$ zeq!gQT5aRbBuV1jlQ@?M*N&e*&v-DO#N@38bDby=#2QWnLA1x1Zf5CKsHLrJ1Zj93#jy}Xya^t`2wZtoMK-dKKP zN$s73mG=`uzr*v~ijuljxH|&Q#a&%dwYt1%`or%K=G#Z8_l^pre{QzYosc>0gdFZz zR#vh!?A^mVkp5nzPhN%y^%4cjpuE12Wh?7%e);5j;1_i(A$BLLYD!BwN7Ca7vHyw? zRoBXrn^y^2DcVb?BE7b{WM$cg6>qO2#Ckv4b8b~lUH#IC&H_SGiwW_&YgKL8s%5I} zn~)yuCsyxMQ?Sd2V4827aavB9I!gD2{tvsGgk?wR6(a~sLhUt2WFyjoO zonnG>=Fc*a&xqY1liH_vmxgHx`3rweyTtmnL;!A-s6~ujc`YP_FcD7#E9*CTK~>#C1F$hf_s3A21`65a$8sO;X{flUv}gBX`2zMxKTL9C;D`%j75c zzmR?K50dlnTggTE?W7xiFLfo9x>FzcepC-Xjb_6)(CgtZrls)9=qmUeCq^4+1Hv~` zj0k!wy$$~DbTj-r=-u$|r4PV=m_7{uQTizS$LM45pQ2B}e}+B>|5f@b{5R+u@ZX|u z!GDjw2meF*A^eZ&C-6U~U%>x{ehdF+x)c5`+6?~;Jp=y|XiaG^#q6VlbO`dt}2Vci@@Kaa{{4|yZe=N&@pT)A^8(1FvX>2pr3fz`vmiQNQ$9oqnZ6WauT3)=$!2s;A*B5Q-+&jv6U6avPNkR&V- zM;aeJVQ}CUVqa4s8$HSJJPWfhUnn7H15pS$Latyy9zKfh5`9~;8eza+fBW6{KkVGK z`?o!N_w7G$@X+BSN1KivZ$5GI)ajNpXV0C#(0Z}0{ZdC~S9ecuU;n`1(C~R;h{!R~vGEDIM14|nN^1JJ%p61B zq{&mJ&n&ur$j_}+(*E1Yzxgw;NVYhU)+j^Y&=84=-k)ukFgUgs)Z z$+!yE6EP zXM6GT^769vxcbV?O%D*Szyw_R=2uy6Ztm>avvY7Q9GfIzl%n~aOB>+fIT;r(B_W|A zU2Lt0q=lCuwkbv@U;elzW~r$mJ6nvIHWQRxG#>PtmYP~NGg+0M;TSq)O4hW@dCRKP zV~g^aW~IBk6ciMfPn?%FWl5tdcE$MVRpEwYefIby$Iy(7;$@kWG7Bb8E?6|7GC9>S z(_paoQ>hk>N0Fep%akcoGK+Ni(=y7G@v#}{WqFgmbmJWzgA+jXIfaFW#UN450^Zo! zvrCF5YVrf@Q*^r6nCyj%%JYj>71hp9un)-3&rK+poH%)9ttR${1(nMSOADtI6~@`8 zWMm|i&sdzWASo%orr;7~80(aSkBF3#P_JrWTcVq+aV zeF7X@+)|UQG!eNWAwE`8IvYiRO_j6rxN*tJp7FXO@9~VeyIWhw`4~J^*N@4XU{L4I zP0{)UM_9+C`ix7TGj-g|>H5&n=+Rmb$T0_an8awk;|;z#uW|Y-AH`n@{uwC8us$t2 z)h97GCoVQ4BG7a3q6Dxo%)!5bl@OwZbHrjH&Q`LXJV+g>3#mga((KrG=!nQCL@v?M zX~rYKvHlE`2#<)28WSB88)sq^Y3XCfWsc9z$(uN7^0es%vu4jJn!jLiammt(W!1Iy zH{U8DBC!o1;ZE&2CsbG?e1WygVXVOHj2vdfo*k6K0*Pg>$YBNXWDm+=CGlo!{VHJOJsMXJ|^gwQACu&r6xjs}ri<#>CNO!DNg*Ja#5L0hb} zVmXjoM)9N!@v(Be0P_3GVaPn-^-_8z(rDzc3L!#fks7j+l#ptYPwH{6B9){RazPD3 zOUZ0fhNqRI@HD_n0XHDF2;oXnL6#vOclBfyx;UN0;a7(;YVmXxV(Wla9l}+(*C4h6 z&kNA9siYA3D)CGUs0J9+BQzVKa)fRIF14sZE2og~DnqI|5eF@Bs0J=&NTo&kWn$Xd zXvI{(1*n;)H7|K8N-7m&c?r>YHW&9=5jS4DmLvc}?zCo@U0JUQn-X#(@Z)VNAxq$M zJZ}Qb@z5fU*PBIVis5?DMEhS-$daz5Sb;fJct4q_S%7ebSmzY@wP<+*+Qcba4Vbqx zOT_SIsO?L(AKLz4yVthaF4S&~-DbNp_Nn%p?GHGNcX-d? z3x}T_eI0Lg+~W9v;}ecAISx8SIW2Zt?sTKmdZ+K51D#`>)0_>?Go7z@u5@m2ZgjrS z`Dy2WI)CoG%lU}&S?6vS>SE_I*X1kMT-O<{3tgAF{^}OtcCXvt+@5!P%k5uoKf3L6 z7u>Vlr@GH`f8YH}_n+MlxSw(FbRY4s_HgwG@QCtA^T_om@L1qc=5d3^tsYxE?)P}Y z<3*3RJ;!*i@_fbfea|mFcX}T5Jnc2s>p5@gUFf~d`!C+lc)#lXzW0~jzj`0>KI7f# zJ>sMBarX)GiSbGG$@MAl+32&~=Py17eM^0-eOLQ#^u5>jG2a2dseXU=d&BQzzi<6I z{D%G2{w|QuBK=eRv;C*}&+{+$zrlZ_|2_WS`0omc4A2LR4_Fa!Q^5NHUk2>dGVOZp z0qrU6CGAk4I?yF>P2j769|V3CxHIrz;Mu^gKx2?5$So)^C?+U1C^x7uXlYP=(1xJ< zgB}lhA?QRhX#hmgr&kmm+K0mxHydiv3_(S1;4}UBC)9~-Z_k_Q`Xc>P`g8gY{cw^psVM3FWS8V6$)BX? zQ+`WLN?n)coK}|heR^d2_Vlx3v&KG^;gWHE#`oh?<5I^>827}u;mj$SPh@tDH;jK| zeE)>S6DlUuP53PsmpC;|e z56{0X|AqXw^G{B8nQWN+*yJ;l+b0iAv6`}Z%I>MLQ`4pzrXHA9KPu1_q!-*)@bwJsj14n(&2*W$X6EO!LS}8B_28^Mvpr`A&yJm) zHv8V$rwY>wA1G{@vv5w|+?jJ9Dbg0z7ww;yJn!Lo=jP|me{lZc1(Oy$yWrGy9@kxW z-E-FsE}XLP!G(txp1eNw`mNWWxc>b0y^H29dTvqw;{3%AEN)tSZgKbG;bL`hcJZ|0 z`Nd0%UoZZs_?zNgB{n7QC4nW;B`GD@CDTggmpon4w`AIqzbrYuT^(g~%{m3A)8 zTDpDd&a&{bm&=vqcIBSsA?4BKrRA@czgzxkg{oq1#g1i$W$TxHT*a3C ztCrUiek z_f(J6sA^nmLTdCixizIV&((~q3LX8Wt(v-O$*PU3?p^iRs#jM1va0)r88^Ig!#g+Z zs?DgauDz}HuG&XxpQ(MR_RZQ)Yk#O6s#Djw)dkhX)uq(s)-A8Qx9(tlaD8(9+X9XC$9@y8p_-c*0np_`ZAd~&tpYX8+y zt5a7`Uw!xLmNo0v{BzCcYrbEz>lUY5N^W`QR>iGLZhiAMyW4KQ?e*KvubsNKaP7ji zOV_@&_M^35uKjWC?zM;3o>+T+ZP)E_w~xDh=Ix7azv=cZw?BIOJGXy#`=Q&r*V(P} zT352}j&+Z$d#zE~IIZ!ojjuMgt&dwjcm2cbpV*+;@Z5%1H@vgqlMO#@*t6lthV~78 z8_7oXM%RtL8$&j3-}utT!_&JNE@b&*CuMy zwYl0^+IsCP+SjyiYv0#?sQm;S*F7*OFf4FPU~CWzQUy5#IfLJN2l)q$37QmC98?zi zL-^UY;Xwv{Dpa;u>*FC;K1E)Gwv4ynDf&8E@DtrlPtuFfMtL%Sxdk7wZ_tAM0ufYl z3&No5d0?~!MB$?dQH)jODwZggD{2*6m5xepWr#9XY{5FT;3>4gMeC{c)oR5S=(K5n z(1NJZ7C4~=UZX8oiWZzf3!o8ZkD-&ULp#K4K$2ZOxs{Z#O^Dd|mUq z&AR4E%~PAR@XKgUKfdqy{^MU9??#S)9rr)(e%zjrW6-D`8$8x_?5AU|6LMtP!7p9b zsGx~NS)alkq)X@;ww;$OxC+px3GRZY;4Op*VHhQn{6&N)0UBZ<8lxtYr?k9;WIToD zU$|fRPWVCiMcAwOO!19khhi^JsrU`je$37Fy{vjJYgG z-LoJm7m@49B1odQk=rq^HbUcjFZnw(zAr)F`xg6&eZ+niJlS_b1$mF`g!XqYbiZxT z`Sy}QYE5ma19gMe*N-OCRE*(q(DzQJi=gja3T^L7_9HvPb_nk51@;!Z#QInV8-z@H zTqtABw2ti&JlF|g6+0!kvcI$Egk?e?JI3A>ZeY*SdbXE6&7NY%_X4vI=U^nbe zJc%D<{7^{jv1A&Vh@IkO5)2u=29k3HG}kKyuHXC%xsNSvY#Z9v(U#L22VOg^yC~y_z^OWbYf?7iHwJaJd<>h zTrz+?cpsTbtw;f^0h7oGnE`G76f#8esG7{CHe@cfhlatCETGP0K6N73LFKX#y7k4> zgOpHjQVglzlax|lax+aJJ6Qbg}`KoAq=o*+etR9W;wN#~QN>2>6JT0&l>734MYHmxS_LRbF|t)aQ( z1{y^kp|goK`4@SBP6OxdAsM8D%%OJJ%d>>BlK}t2L1+%!1MG4+C{r*5A^xN z;GC2(CNKrFVlK>;xkHcd0dDGxHNl$&vM?5o^#Rrr+Q+n5C4yKm3t@iXwxLYTl+2lV z(U({xeVIkkSJ)W(DvPGCu^76Aspwg@jGkka*lqR0axqA6qeJv{7E9k?arB=op1#Qv z=vz!j-)4#Q9j2%6vLyN*OQ9dIRQe%HqaU$!`Y{_zKVcd4Q#OwNi)GT!*m(Lmn?S!{ zS@bKGL%(LZ+-kw{=yz-){hm#tKd^lIBb!WjuqpH>HkBFZ&ukjq$)?j^SOML|X3$^R zOuC!RqQ9}(bPp?}d)XYikIkj~Vf8w|=Fx*}K0U-1(8KIHdW0>cN7?nXi7leX*kXDM z9iYcqF>Pig^aNW%PqI>aiY=w5Ss86%<@5}zpzpI}`X$Sz=hA&8T$yU-f8UD&yXbS zWh1ep9Yc;o$I%S!!U+;Zj*;n9L8d|%R!zgnDjG?a(Ll1C29p&ugjCT`QV9)PEgeJZ zX$)zgvE)VyZ553s57QasuXGl9lopc5=v?wREh10QdE{?&4tawvC$G~=@=v;gyh*Fr zZuT47$#$_{*)QxoyTIOKZIBTB1slOi2oPKZSby-#M8Ez69M>LUesMntj@#EbdHuSY3a&UWUuIdS6*9EO1`M7tSBKL!&Y8J-WB~f zME@nx|GVfvA^Lw6{rhWbmsXRltEwATl8v=D)>e~sbq%ZP$eMZ{!&hl;JLSF;#|{@2 zZE_6r!A*qt2;p{L?mLLSgg+z`#Mp^G*J1PZnftKhqAvvWhzj#X4G8vH8RE}9#Z)+@ zfK?GTUgB%L(Q1~eU3I_msIp3Ft2n55Tk(`)ts+~Y7rqf56V`(Z&lS8e&fj2ngICSN zD)cKfOzFtY%i|X7I|ze}{3U8M#oP!T;9NK^f$PN;*j0YY>*P6M3zb6>#4x_4{CwV+ zQb-|F3I>_vb=U_!W1k@}ery{w@svHpo)F98&p$(3)yU7Cw|M6|aTVoOV+VQz(hVY( zLJHxfO8CbTSB}3hQp77B$q!UAZ#iiHwkiEzEJP*^N15{f{D`NBNpSBY2<5esfn zO@iGt1#+DLOR0g_z)lW9)JE1bc~n%Kn8?K4)LB z!>pYRv0*mCjF8b`x)%g)v4ylAB7{Pc42LWk2`O?6WXKpHR)`bgg#_TD5h*|*%RP_q ztLzP81Bvbf;=m5EL&T9CXUB;XWT!#mENBEb;wN|r9wbr-5`w@fuf%a1+s^J{ce8ug zz3e{hdLDod@OhN^8ru5?NPA^{`$@WUwu>L6mHRosk`6MF+tVGNUv2 zpR3@8x`KeCmDoQ7?R^P2e#*W8j)&P{VAl@2ixV3XRKP9-?eRn3Nqxf4X-IvjU>89U zKlUnM3R&|q&h|t>>u5c9h~$_Ev324S=vD$H+Q=62+f z>XO=}BKIH_rTp}R)MhTFGTO;)`M6`PH+$lY5!{Xw2OY5WH?sA3qJs4B22Lvs;w*uA zN{fOgTgz@oSssv(wn8ov zrJqsRM+xc2n%OWr<_P)6U6g>lAp?bt;>CHD)#$93+QnBo%H9CE z?IULGvliROXBnsS1f+3AEu2C)X+l&&HcpQiggjxQFiB41E0!sx;8YP$oA9*e?J}Ep zoc__66$(fV?vPeA!c_3uOkoQA3BqKN@8-i#HA^u=Oi>`FKz`smO-zA)0HrcyO!(^H zjy0kXeLM-fstjNrFLFdRpLwI4kgq3vRryj};duBF$a4&w{5kl4VSk7JFnbpMddyo! zJ3vL#h-K6ryC}|`__(gaS*?0Lx-pN%`AILajp7_#_h)7)X8l8EYsa;C^-JK^d`0=3 zeS#H*^KH(x4})*FgL7YVb@?5B{s(K$AMyG>Ig0N9g_iR94Q@M#y+39-c$vh3-jMfT zgLL@c^C57)ZDwoLwbt0@vFb>aco(#gsPhFVau^h8zjo`dO{qU@_aD*g&&*QOh~Zp) zC#XZ|i-a&IqGPqjf&X7lc*l62Rg)C<5zaPT%bmqD$+_U}BBtf(|3@b`r3M)?l3eky`>8*xV^d5c~h+1Z$L?hqE%SB=VYWE{S3$7YDrj z0sr5)7cHqVZgD(Ls&~f*kTfTO*a+s`JmbVM<>*w{5iyrWns)r zAW`(6XoI=iN%Y`=S2^|~@CQxl2I6MvP@ebyrKA0jT>`Im%o%eG2ipF~Q6q^kcdQ2E z;5Oj>2zb)7Bwpm@CjQBJ-<8grH6ZRS;$zNZg)fPc^GetjQbwI8c$A5kK{v+vmdGQ) zN5D%{L_WfKI_C>ZjDy?>I5{KE)lZ2Qd#V(n6%PDe5J(oQz12cTrv+!kpLe zGCshVmiVg==cB;EU6F|APor!j+y_urJ!kAuG@=Y%|8L+EJ0+Y)aomk(T9nQC`ChnT zOKiDkMWya(}|f6Hs8X?^4C zjp$@LUO`JUp3d88qLp|@-6BmwFupcmymCHQi@D3`4gSpeAn4}JX)UOYU-5YM0Qlfa z!3NLx_>IMW8|U7HOt?IxMS41j`)Rm|c;1e-1!FD6 z9F%!yjLd_qIG190moa2sppgbkgrl5R|tHdY0*$U7Vj^o1XW5LK@fIDw@JseNV=^(Eu z;u^r|=tp9>GtrUr=21GD^Ep?fGoRbMJkW%`Ydp)aQ{c3t--E6bQ78Bo=MQWi87tDd z+V~3av|{$K!n^}qz~9CC_CTv-&YQrW(MAC{hjacb^5R6)Q%=0`W{bCQC!Svft(JlY z7<1wn;eBsf#}d#UKBhh-zI=`gcA$AVS;TU{V^)$>T18-Yz&MKm4*ocE8i+p3M*L5} zUk6;X!GE@rJh(gImcuQAOM@$en-5nHR|*F$zwx5r1e$b%cD~?UI5f{P9_#~$v7Z_sIe4B2E%*fNaPwiw%A^NLAl*#@#Ao+GwpF7qGf5!k zwi|amUVLVNzTN;WjeG|_3Nd_^Q@O%NBH+W+zWZn=slic<>`gAKPfx8iI8Jrq!5**$#BGcg(!tpfR5gJMs;0=#F zdXupo?TM1z0<*9wF32Ko3}Eg;gcOn?tRPe9J;ok!EMZ&;J{VuOkpRBKro-Sz7+*p; z2~pZ$%x)#+vU?EsYBRV3VeVGQcb<;J{O&|spo=5WrbsRWcWxCQ0~Z4~Nxt(icl>#< z9BzQVeh0#5;U>c^lH+;09Jp*aQ@JT}cnaL z;Y?+5&HGY0oTM?s5`8{-%&`B)EhQyU-AQX z-~QxB8bGx)kOt9U8iJNYLJ!hM!)Q3H+L1JhjG<#_G%RJ&kRW%^I9S4eCId8{?4${> zbth6i+L(m7luT1doM^{SN1L0WPf4H|u<2*Q&O9FN`-M&*yJ!}4AUQM_GrEH2k==A6 zcK`XX`cJ_*)M?ymPVR#h?sf78NyHmsdXfZd`YhOwlVOuP34OsFI+qq<2G67O=>piD zQ^{|1A?ElVaE(P|FU}4W(-NG?C>75Tl+y~jj8=j-;nV_ML96IWS`Dju202e_$VYS) zf1ixjfwR?<(|D&W7q&skX1p2}+FR(Yuo>Pa+K*vthV5`Y?1-WrnN*XNIC*2h8SfEn z-)mrT-a;nAx=dlcp9HJpopdW~jN9mTdKbN$-UIvMz4Sgnp>bBh7J0!;`xJq z*r6YV75XvyIA$ZaU_S->%05^ppC&iLKK~3P#|F{5{SR)ZCevW^ehD_tm+33ucCXUc zaO&p`SUzXqWWYb^n|Pb;ZCLl;A+yM8tRc7HM9};6189mrf}Q_k*xNs$pVEKP&-mMI z^h^2`-tc^yeuEPn|0J_vk1vE}{#*JTR=c_M2b@M2qCe6du+aZRiugNl^cT8|%)_~e zU%|`Q;ncuyI)>WKOWayTER5(pr1LYVQNhHMh_QFfAl;iRH7&YznOE zcvp`V;9b3$Y!;i%3fUYsmld&jY(87Su44_vL z73@#y>m>^S+W_t1Cbk*x``y8?oL zvsXrKJ^#eXpto@9=N;JnYz zu!;X7TE)42d=F0H>|^_J%IKh|b34M0;-t|r=-!&y33ig5Vy9UP&iI^V=R_NND}Q70 z-|OY@f{_b!&2HeI9?*$Hu7Wku_X?|-m7o@^1r5#(bm8<&kI6m-8{Mdd3KmxfSe*QE zQb|Fcg;o9;@+GvAcF;yUW5>YlSMIQ2c?w=Q&E*5_^PA)?$OhT4AKPLjD#q$E0et;y z@-}$~8ua(@mg`G6pYShezcu6^f}h}zb<7&N_suvJwUulq8*nZ_OBRzekRI2QFJOZU z=GHx&n)(D1v~1%eo4A#aJZx^~6LdnNpog|RShUQ z-T?SJ$s+fY$GA;^JW3wHNvyvL8PIcoBiaYXLu;NT>gD-couX!*TMWnt<~9Q}E$&>Q zNSFt!C%1+!ge7zlETP4sRkT!CDwGN3LIqA;RpPwP3h~5FHBRfS5^lh$ojRQ3X%KD{ zZW3-5Rtsx{TZCJo?>&rF`!1Z<*e~3M6B~zc#_0gggB>Nugtfx$!aAW5*5(bWtR=N& zHOG}Unx7wPLdX+)MQDqP# zr6RLsRCDAibL1*=WK?tX8pCLDauyk@>?|vTjDV{kvEuf$*FSXjxogB6)bD0t|+N( zSXoulP;XTrHPNP^uBxPNnS|ji`DTV_QnG5c7;7`zqVc)}8PPO@q7Woj6`EyB7SV{; zC1k1QjJ7d8AvaON)*!dZkgS|@xeW%nz&x1}22-2lwipa{bFOH^9J!n3NZq8IQ(Fn5 z&JjCHRV1NZbQx{yqNSB(wPkgcb*iF@+L9Z~Z028nC?S@oSIrk6$)HU0yj0Z!u{h0w z(eb4!8EqJ*oH$4m4ALOQJwYlUTW)Z65{EHWDIojJu}vGFD5)UQm^Z$f?Rk`vf@2IOfQ8<;Z2{$T;RCY05^+kBoWBjy{Jlv+`licBTi-M~e{YiJ@9N7BrGb%`q*`r0e`Sj2 zrK#44#c9@DIrj7kBCmFwp>iMj7GMMh9tSg>$7DvAnWjDAjw47Y#AL%e2UjgViWFihe@m%uuMRS zbpw_OEb&3WGC?I7B3>`a4!FyBNU{T984pQX04&!pNeS_KgWL{DiU2IBzeFmnFe^OoIX`fFAvBh zoxy?AQ=gz0*9V-smEr6wVcqC+=gU)9N-}$rlqD_oN;oU)nlKLYN<$=3CRL)`Rf%$w z6Xm&;gmuo6aikz>lyOvCgK;Fv^h%WbJ4vo2$yABl4~g=GNtEfDD36CE6XTF4WTH&( zB$R^d|gF<;(T! zWqkA|JWc6Lu!#<)dSrZ)HPm^1aB~vCVQ3Wx;#5j^n95hQVCQB|xlIOrMOWJP%mI)^9#{kO&lJ+=&1TM`+-IOOiglk?}v z_2$XlkSFKMlkz3W%XX5yTqnsZV^V^gF4c6G)5|MZk}gl0^@+MG8AW2E1w6_)X5nTwbhQDWMa&y#YmgcD^hGn(FlaV1)&&$}|e-U5HU1wMKUeDxOi=`HZn=U9|K zik~f?Sf+>0m-(0!HOfWh@=S1++#U-UTvVoKf|wsKM<^$jtXx?lj`GAbJ)2P}1?35p zh?R0pS#3>heYpyX3Vy4!41l=7F+GeD{tg zsIC+X5X+C3Cs@3^a>q*(Q5P=@67gB_EN7OwZdFOCSP3SfE=5`eaZl5f)yw6`O@ORjj3GjzDP*8Yl&LruuT*IxGQF3H9Z6cqd{>S#4cq1%G5` ziWbY}sOsVq;;qZ8Y9J!xxfqk6%T}$dtmXoLU0G>O^-?J|A<-JE4E9C!Wu_RBUFs5a zSsEm)uM}%n7#eD8q>?3ZMHerLC%B8V26vHb=;9&f@OGhIo6?5b+A@g32wN|$sj4aw zY0is6j7G#WTYov2bvc-epiLc!Q-w|X<>=JWV3q+v)oQV~M7*#+k?@n4xK1a@(`bj( z9-S;d#)E6Fs%of1b~UF557?}1sH(4ARV5)>d1K{L-hb+{8yZTg_~YuDdQ(_aQCkLD zi4CbLtK+#H6u+sO+_VMqCxQ}MuW~b5l-AiVF)ZK zUF~QV9_`=wL|OVxj8Cx^w=g`hwYY2HAx;UTk@#SuEDtBjJAy=+5{dCy*5dBTEQj8X zZ%s;;)ZBo(bIgHc^Q4>v@s<%+l+IIJQ96&fqVy~=zx`;(nN_As5dmwtDY2FIrV!^j zu@aNi$=d;)yq(ZV(n3N`s<nwY4Cvdx~ zbP^Z7m0GmD5-a}Zl`VArw?JpV4O;fcpksd(8vjpV2i}P{caOu~*8zJdgGJUEcGVyn zMXTw%co)(OcBV+!lG0c;-n+OBwv>xP0?zAaLFYaT8ug`k$Mr^NzBdbZ3l9rV2>-wr zcHR{}5xx?3D5zqBVv=HpVxFQ{u}txz;(}7I%uwbkE0n90Pb!~RzOH;v`7h=t@ygmK6R72McuCM zw`SHh)~?pR)*;r>)_Us<>pbfM>-pA8tXEjqS>Iy)y!BD*)0zZ)edjgJd7E^b2W=j+ z`MXWC?HJoc+p)Gewo`2Hw0+HXpKX(Ei*1`-m|dma4R)*T8tv}1yU*?syQl45w0q0$ zW4mwccG~T?J7#yruHCNBp4g|`Z?kW)Z?o^UH#$snxX0m$!zqVWhi->qN2Q~kW31zK zj-`$(9N%&L)bU%#UmOoQo^ZV2*zGvtWaZ@O+TtV@i`a+iBu{_66S%L^`VxP0L9h0BjFyIl^soNzhs z(&;kjD!AIXy14q_TRu}hHj&7cAi`*V_JL=Zr*6!Bl?(H7r9_6lc zPj}CDpW;5-{W|wj_bT^#_uJezxxeoIp8FtPrn2#f@ksVq>QUuU@A0U|kY|=>zUK_j zd7j0d%RJxnZ1Ozg+3wlrMZGj$E?z!f!Cqs$61_6Ka=oT{&GlOBRq0jhb&J;quWerM zd41;fowuWRk@v&iJMgJcjgPaBx6fRkMLy*|)jl`+to7OBtMVP=I~(8m+3x$G@6Wz_ zeUJE_@@@6)@*VP1_}Tio;L>w~u9Yd@EQ`hrPtMsQB>eZ-= zV=~6vG3K{u|LC&lH)7N=*)b2td=t|e(-k{5c75!>@UhgqxXp3j$GgUt#xIYrjsG-$ zI3YXX&V*lde)v*Pj83o1(ADbJ=+^7D>OR(erQ4zVEs-Ru6CD#h6SavEU_Pmd4T)dt zJ@m8n_vjziKdwKJi*laMX~vg+R_4B$d%|FAm|(cs@PVN>PnWkcuRiZxeD5c5V*14F ziQ6XrFiA6M@}$j^KA!Z|q#cuXPdb#Z&UeiB%-7~$mtUH{BEK&G$^7T@U(bIp|6lpv z=I_kkH`!tGtjYIH{%MNs6qhO9Q|3&0a!S)w|Ecq)-Zk~-X-U)8;(I`MO?znCW7EEw z&Zb*WcbuL-ee?7q1t|p;1rHS*n!#qwo$=_5XJ(w5abZUPOfoZbX6?*RW}cWiJS%0^ z##t}SIx#zP_LSM%Xa73;!0h(I$in!-*@g29iwnyOR~FV6t}EP9cz5ALg|8Jh&xx8d zS^mwMvv^L`oV9Zvo%8gZ7w5b&XUCi~bK~aTICt&bhvptHQWiNC`4t5hjVaOym znpm{F=!T-(ita3WsOYhxe-wRQba z{eSVg6hBq`V)1*$Ul;E#K2h9VJXjJ`l3tQmQc!Y#$u~CV!+gWz3tfSniJhnWoJh!~8d~^A~%FkCQD*P&@SJYN) zsd%K~<%(}Ac2u0I7+DsyZ1%E}Wj8N-WZ8$yPFDt0W>+q$Twi%#<&%~FsC>2Zoyt!t zzpDJP^4H1(mB%VuDlb-cR}L)~mTQ)WFRxhs&*dL1@0vi?@wfT#Ua>+D4_jPtIpNnx zgN{n^{M*`NG#a(W)_Np5NUIGA3H0{v=s0ug?CEZ8Vp2?WL~Lki)Hxhk(%3k;JCdlz zb#@^kAzCk^(Zeq&C^#sv(KAr%;o{xdoq&(YhJ}U@H@DsUPaZ#h;n>d`q&o2CI!XTX zbvV$D3!O?wFGr>AwW;I~V{kf>tYbK@c~u!WKxUT0Ycz$FgO0+NYju-z@R)UiH&3!z zD#KK-Z4?f8Sz_XPWz3hOX{4Gsk)-xXu(t*5U;!VrfNd;bPYYPEfY~U#7VQ}4;9zU# z;@)mFju?*~J$(Fve{6hsc=)js3dM;N$ItX392VAcaL>Mdy}jqppFY#t+UgrMCN5sD zPiSo=9gXW$fq|i8Vvja82DG-GJ=u(pPIuxvh4u~(7cX8o-+Hk*!Ex+=2s%lhbQ5J+}al@q>dG+J?1T}E0;M&9)4x%kR&Ld=-t z)G;A3emy-s#3e8+F(oA>IVH%2;ak==0bwcQQc@@hZ(L^+7^vy)+r9hf(W6aGjrK-D z`?~w6N>Frl3C5Ae0RCC)pir&t@WnG{@L}x8$N-JCHRWu<8eg z+NoS>H(P z8aIl)u?$e6jg7DGe~61+qr+Ky`_}&gpK{cg(AjyZPtAu}@^D*AON+|t$^mN6$M>b7 z*vtt?)86UoN?4=A#S8d`a(hAo#(QwEKSul&)M#|DoFQ`iZ$*6>|7m@F=ML>Te6gSP zU+n%9ReM`j{d?o(EDAFpFZi;<74W%H*u?X2DqrS#5?}1&Ni6tc_h=eZt}~2U@Wod~ z(@5iiH_IFoOU(K#o?F6ST0HOiH}Lt<=hFCVk;k6|g~*VEn-vL>aVtUJ#a%fx0<=b> zQR$du$dQLeN5`c;D{&~r>k=a)i3ev?X49n6&(u2uHdacNQhDkW=c6q#F+sQj1EqPu zzfh9i-P*=EP*+#i`BSG(o*WoBvcIWqV9-Vz7Z>Ma?7DF3=-$11@ecqy_v{%SR@W^*)a3^B3Dro;-C*r8;$T z(8W_ZGBPqSLN}VPk|$7%d7lhfz$YzWwFP`R|IKI(J%7IOe`?XD|D{E@0fTW@_FP)p zBm?^CB(75}hnkzU+PG-`Nqs**p9rnCxq0_qJP|umpOuzId@TFXuBoY8E!f$~?61*A zgn2h|Im=G$^Rwqd>9r#9Ei_3(EABL1jcN7mWerGoB3W%tKbG-`g2 zw^3v?ZxyqEdBNt-Ez<{BJa@E!4_Uyr7I3@;Y%>ar5=VreEj~5l~g^>x*!ukm-$$o;E%q@%SH!wAiR%h%^EI4ns$dw}>K-s_E#(zVjNZ zD`u@bGD+ocjTBEkI!vD>LljO#AR&6`p zfBE%q_r928Txd);PBPxE{@Nxy$X8^Pkm8+feRm%`2{Ha?qekQ8=n@zhsSWXW0BbpN zrn`GwVj$%Bz((7^KnEk;w&j`c_wPS+L>^OIUUa`OHYJ+xTtY&E;=;oR20D6-#-X8+ zMz2e~y?*;oUO0dL9Igw;T3TFOg8UsEoQO@Mt!m5mzdie`mxqUpMUazYUV1l`MnxDPwo9>-`TF=uCwj`S>=vWt?iwB--V4=Z+Ay~dwW}3YtMzfU%vb9yPy4X@?yVZ zFt%x7u8pLn@m_cA{Iyzt4;yAp+mAIJZ93IHz#P4`a$oWH&Gdh=JkoRK!0v+=dP(nv zPF{52?-c!eJ<)$r zfO{<9&n@7gQJ8<>ZXFq#V7zS|;x>zYBFXtfyigg}KS;%kmQ%_3E?th1uJIkp|6 zZ~}}om)qC%~_xY| zfMYBD{b3-spbX`v{wL*LJhtn{Ur)C6wVgaaTE(9k4q|OmE6MqnizPr0~KqLMQ z6zMp7?wm$LmBamm{b*!&&wz1+P-mBpj`nV2LRJn^@xSG9cc6l~=7+DHW`d&_=Cka{;{!{tC$%nwkBFo12@PPW3x_ zhJ*$B21!~EzQ0JlVvlV%CN(ZLE-KtVaO`#Ynd17NkdzV^H<%I^6&$F>JnSU?X%qRz zKhR>U>ju@!a9h*S(;cGDB$n@F0t2;;jvk))ODG36IMRFm`0?Z2YHN93@^?Q|EZ434 zv8kC!`lPrqfgzcTW{k(0FG`#~)?fygdR>!~lTuP*f=Q^Q1Q6ELG|U$!5rO_*P}C)W z^K|0j*4Jz7JH2lo{w59VNv`__)SWimd8v0`c)0ugg?6)%hBkH^?Y;fA{ysjgwpMC2 zXFKws>vxm}T_ZV-y%l34gF}rBN}p{(N(VO=TU$FPS65HySg*~F#F6n3YPQgPX+m|8 z`s@jr$>~!j&zzDxc3e_)w9?Vl&EDQOJX|k!Lelt2IXRJ0KKKhPybzzHHlPPmbTK5| zk`7KIR8VvuIoNdS%;3)UwxPje`}XhOC-w@ug$(rd_p}f6wMsoQG&CrU zh?oB_uyc{I^VAG>57~PKdD*-DIYeXs45H?&*KDo@p$c=3q929%I7?NL0i%sSHoeYv zUS58|5fKpquFf9b?yiH>H`Lq7p7rjIm!v<%x+L8Jb7}@ftHrj0ihlq1O1NpNL{1tr3*Dj{Snr|7S{+IKBo5nA6e60>)P+<>!1o(xu~l9b7qi(N;ZY&$Nh2 zhy%xt+1vYj+uAue(5`L;;relYaUFR&Q8&6Z9c}N}_t^)#dR$$c22V7Z1XQ&sBVIau z@WOhHN^x;lU%x0!OW25-kN>zfbA{u7T=A8gaIIj*&({KO)Rq*JT02W_87LOKR`} zrToh=LgGgTt*kU^Yg^kXQ|IU9<`}ZlL-FsKQDJUMDj3xc&MF6o(9pQ(NN?kXlkKj0 zlG13Uux>jIEnick$FY4AynUJZEALHlY?&cjB7Us=*M^eY7#zw`C@q?i6>Oxvi zLnnFmV#k?Fm#nN@T%5cDLZUQnPIh+oPOi>z#0}`!bV}%qX*$tKHB9A%sq2?8V_9Vm zJ3BIRY0$yL$~kb%nAilp;bJ%7SegN&X_V7?xX@(tra-kuA+eM3VEYwOt9@#CT+LbO_~ zm#4F<@8BRl%pmkL=He0Sa&yP=6CyfpO~+dYjRfBz z8XUaTXY$eM zD)gHWx%cqVqh~I34-7O~?>FSd26}neSX)ayI0Gew{eB6Go|fZh2JnA!1N7>`+%6Y( zMbpi-$@a)*-uCWMcpLAx6Zo6r(`PRZ3=Iu+ox{eY`S`g3g&)_t`#D*Sz|?{NOjf#^ z{%qIX%gO0@3-OK~6BXdDXgl8AtUwO%$#udo%(Lsgtn3|~oLt;(g|?<6hfcv%(ha$` zxwUJ+xZZDMVAwM;B`qvCBshp%YHRONy7)#$M*2HDX;dyu;wh$G_69X=l3HPkX}v1u z;%JPxnh4gdK%|ibUs=+H(Nw0AF6T4F^r)d+5XXxtMsZcHj;msTk-L;juD?U9-~Gz^ z`_=fg#}#FNbyZ1kTorStPl*{#)VtPmOJkQ5R zXYb(PAWU1hPFxfmwf+GRv>la$&;s-`K>=HP?@%|!rn|3A|KRzKL3>p%SlO_#uT86I zJ$CGb%c;{Bn0HiEWU$-bJv;Ze4QgCqmTL6UIJvmGdU(57cQhS6a%p6!zxU#a6DK=* zN9cO~8I$#PLmj72Ge7*pw1>aiIJDkJ+1*Vdqy6lht$MmUy9U(m0pX0PdtgO7+b2<+ ze-$aN6IXX@S68pn$tfx-KGNG=gVO+x-p*bguJ+wgQU03YftEdc_8jkKn!qU1+o<{K zoBan59y&BUY{b8)D6KZ)!_tcNbi~-`&~NJ(s0|44vgtmyf8XAtr{tA+4(f^G3Ne0l zcMn6iIdtjFp^k3ktm$wYa|sC#4e|9A1_#ac%v+JoeAe1nz#|rLzXjYi3Ukdu%9*q6z_p{bwPhEs zUw{4W$6tTlbF#f1dPis;Z3N-By?gika=7o(K|K6*S5tfcuuDjg*13_4G&bUp58Y_1 z(tP>z=kt2tXwp)2eM9H+en~xz^D~+|4%G(#0 z`+tLpU_!;FdxT;cRw<})xaWVu>vrIk`Jdy$JUA-k=bx>tIAvUoohOg{wsYssJqNBy z9Pd%$Xk!vSdGnQ5UU_fVxgoEpuTGu%S{k!l`;j7y3_wxS**&Ca`{{@8cl>g^f9T|) zqs=ET_6&5k+c_wP1_t{3h6mc4&^NywYUv=(prnShH8uts369o4aXjJ_8tU19;D=w% z96o&XI2q|17(RLKYzO{ozR@8jCe&Snb>#5D{ms1!uNbLj{@t*UzAmoX?ZTWH5H!|- zfj$n_Bkir7YGZ$22p^}tN-Y^`ywg)5^qf9D&gTb5*zCWjP7kpu}eI_F%WE9ctpfA3XwR|6nH z%0++&ufqH9`@{Y3|J{4Po6ZX5wiZ}?b~^(I^o|b?=ku9tp->cwiog;qQav<$bThm@ zb^G?6jrG;#`8#*+Yzy_Ybb5Mtyd{9ZAzI9U0e7Z35Av&MX?$g44-G_ii-}Aovz5%| zGO0{iPy`T{FY;vYVW`DrDw`FGWr#=9$oLhowwZ!%GvFE+si5vpZ}H+YGc#eKLMvBV z8-oE2l_vg>-Mmom^Z9JxyQgVgsZflE4INDhN|AM65mvv9VV{5!)gxHw&%v1)P-=C@<61? ztC9iaqjfs_`vHgdBigZqFW%h);_9Ox)J}g^yXWCH*lWoLe7-Les}Tv|ZdD_)cR1X_ z+@U+FYuDTzLWl35s(pir3=s-C`l!e0?=pSGwjr+RnDgivo?A1ikQ>dxV6dUV>Cg#j zXb|{!)1?xnw%dD=INN|9&<=PbJNCVilAWo#2_q5t6RB(#E~jih z4kx$VV(#i167w^09263~4TH$oQ{lvt4 zqAwxFdUknfb-g+Tn3;Kf{~3zK5;Maq*qZtW`uhg^``Q~?U=TG18=E?snJBy`EmiK0ZQs`aviZ5Ou&l>nTX*heeE6E}zNeu)Olc^wzttz4qEKXLeG2 z3}(Q>r^4|2l}bF3aMCOimeM)Otx-pWjc;f3AIly<@I+&EO7H zj(fI)D0+_Ewhl$V=eNkM>rnJuc>f)?wT)V9>~*AeP2vL@D;qnN2N^3tRw*le;D9l! zj_Zf7A096v8asa9LCZ3kq=Xql$;4`UkS9!}Q!ww0TCG|pNheD>DLyO;_=unum*d;( zYwO`uS)g`e>bSI~&CNApWKyEq6cQ+HHtnQZz)x>%2;4Q)!JXRN+=CVFwkmk>)y3M( z5(p%zrKN>1H^aU~)>E;VgTa8Euuy`3sI2vRd!Yx0TfOkQs?`>!$<>Z)XoVvK0}@dJ zP}@#fZE0gXb-leoJ&JwZLsd+|Yap8xmJ6uXH|fe(wlre5w3W;nWSE)PIJiiLPowpox8HvAP2dc0 zh}~uAN@X&gLJ`}X0uZrp>{2BpvoNYSmpx$ETB=RmW!y3| zT-)54hn@ZZ6~8{HL%>~LcWtX(e*v-$nO(?r&EyZLU_ z2rB7#PL0$Ky;4D9!98v}%x;=jDBLh8>^`5*t`-&|n~0d(iNvxM6!7KAXg%DJQ8;H( z>9W#lx9MaOk>H>;_3v+l4b!`(eSW5dIozgZ+IiR?j#s3>*89O~-QwU1nS>Zzx$ zU3mDJ7eDiv&pi7S^(0LP#S$P(JRKfU&(Ch{Yy%-*2^Yf)K;vepfUExSIXz7~Y~-ja zGh50eHgSG-ZZlm{!Yg7{h=qLi>6tjt{qybpPQyf8auUXl$W6OD)3p13Ct*$ywuNKp z54D8Kl0gI(ArkOIEwrX36l!qVl}b}6XqE|Jnb0C0qDya2(Fhy5p_;yv)A*42XKhf4 zsmg(U?#2V$ib~V?^TGEc&u%}6wSBOZie!lrh-DUdLU+XS=T*fn6P#k{iD#oF~} zAwhpGK>&eFHl0DX#A3o|Xm4+KON+Zm`r6#sTHe}PUYCfy7<8YmBTJcUBwt`X|^hV`C9cY9xZlx81DfsKss279S>H9KdpJ|Sj(v1muIPc%6?l^ zyS~@AHMLI<-YycFmr!Bt)3HT~$JES^v)6-yyfH|BihB9kyj5-Z;3b_kEP? z=c?N0_j)dJJNDP)6er4UN3+E|?d<6p9UJJjT5Wba;@yj-e1*sJ`Mh>Vu$h84PGpkq z&dkoyy7{^FbfvRB1XO+K=`8irRKmCh$!|XUPNaj{zF9AcH?{>Gf1}rA65<#u; z0iVZdGZ_s)h;UAlO4T~8*3cc5DHU?LLQtwSA`;`eXl8MC3eCvp=XZ(-`O(Tn$Y;}l z4nb|uzCA@+6U{HY{nH=*3oU*5pI?6G*3bX?kN)J~4EU2;JO|qsf3p+VM6PdEK%-_)LU$8gX`~g<#&yiLgMukFQ z_PAQfD-`rQ>{fX(pDkkuT3b8&`Wp@6VkwtOLI#Tk4q&I-?L-C>ZPA$AP&utmCvp_L zflzygW^ZxvHX2pUW1Pgk!cKT~>K6J_JioRXS>H<4ooT;^dmNlcxkJ~F#dvIF)$GEiSdd3^?>J ze-O5?h|T~(bV~UGfqF_M79KAnEmC^ieJN~zQu?KU879c}IH{hghuR2*hnDy?)F zJZ<+yX&Y&ItXP2rdAut7?WvgdW~Twz-kI4hz7JyiZAdT{j^Tv8wX%Sm(Z%glMWa!x zR4P$1xx2d?iKk#-l2?ZKw91)SIKH;FwIVL8Ex-Mg|K?{UvUcz$YOuP7?+Ww8zqWjng>ar=A34xGDNeyym2lphb< zv-g#MQdh~RQfw#I#Ana-OrF)@UKkr|Z1TE;^<728Hq;3k@Vg2Hr2?aIAa#?|Y1bet z5@LFK>ThzmrD}g`Hx-JF`h=hR_xbm6@A0lSr^V%JYI4cZ13A9KYnI8p-e6~6-{|P* zK=+}HpS#-?lDyL%x($JtC4i>EV3V~XNWguD1PR;`FRBvVisc_bD7Gib8H?_$q@&Sf zp;X8xlZ}o2-8kAJE~vlXVs$mEt?<*|di5p_4MDHPYB#8KB=JX%*(6(kWkoDLdGgxB$B!SM=sks8xAEblN6(*s`b%H?+LKo< zJoM;=vyB%oU2?dbE+B*cR(uU#ym0cQSez-~20mm!;qV%2qt1~~Dt|}2&8ZRdsJ*qN z1zIyZJ2$s7{|k+#x?t7JQzAKTd3(8<9k;dzR6ygjwRbStaZ0T)9*NYlFYKiz0U-}z=z>ATS zF&AW@7ijsyLbxorI5k5qLJNyYL3^9e*HC~SSL#e?#iG;cjd~KuWY!yWI;>~6NvD*{ zIm|>vRurCv)wJxh{dw)#euIpCiv=)_y>obCV&cTHiLt?+p8lb}K;RRf`0Qt%#OsMC zKK9%vKH>3b^?0(Up1yMR^05xD2aXilAT#+#h6lUgNJX%_*C48pDg zsB!vzzsZ2`P|!(WMQv_Q)kXZVlKAQ*N!4A+xvOtNu2E5b1185>ng_!`E)fcZQl*+7 znZ31IC<56nN~LDE%hMS$3LKeIIxmqNJ$mk;fdPM@vz6+iT|Az~*VJ+1_{30WyWeRC zP+~B}{`rr;`*+ds+#+~J7M7x=iYSrf$s=FOE$NpXA1t9ZeqY#afWR4w-th z*{yVLXBW0twt)Efd^X$GHqz1B51XvB6?3iyX{7nQ3=Ax)5CZ&4))3W5cNai}QKnRS zpo`f;HoCbbmv3$H^XcsfrO=2enZxZY*p}xud3Kl8spc)+S*n{~JzTGR4HArOZ_mub zaN67r7h!YZ#GFIe5+)U>+Pk`1Y%*C4pN7NHmF?~3=D|M3)esDHH8)cM`nsMOno14^ z-FAKp6exsp#jvojT~ryAI=kKGbW}B9-9%&O_h-?5A96|CxF+;%ni@yYjeyVX!VJ?3mYa@IvDkfVwovswtVbl{ z_qBWdejcmG7@tG^4CONZH8PcvybK1uL~`lUQ%_#N>-_l!x1WpyURIaux#xcKi=X<` zr=p;55(9s&dmq+anj<1?KRvkD_#6ZAm zZRXx%=gbRey@%b0Tn&vLJLZ_lP$-0Bi#;hJcf8qo+M&`}I_76!%dl}=t!LP4MQ+5UY^@dsff?~&p>=6A@ zX}$;pM=lk!Nie5QJ$eO90i7e`WZUa*37#15@HM$`GRJ# z^UTxFf9BaweBu*NKK4jMgPDYIJ^$Qek3Vv$epMB!l8rIL=w*b)j(qY2)4A6??M|N+Phc* zE0wC=ySKnIv)oR0x4^zSUG3p?_4AOwr;dAS8r)M1k(3pRdY_-_$3OnjOE;a)AN}aZ zKVDd%3N&5ELj_uzB;x27qX(F(sBOZ7wQDdMtR~Yo*E(5ey*yqeyC;!}_{k|8if`>w zB9lQIkIR&NVX49=0ht1^FqJYPjq)cy;c0DVGnK`_m3cBRiQKkp#EF&prKQEVKtS`P zl^cdiK7;c;6*ifyXl^$a3-5&4@e!dt8eA9$EuVcZ4))Va+^65bcSrgBL`EbMR`a}= zw5_qR(~mxV<*~;ff9$cx9)0$SCmar|%>h9g`cICGjgOCyjg6cd9i^VY5rj(uTP717 z=vQfbd1)!bGZ+kJvDt&|0vZW~111nwB%*8U8}rk3mIo70+6z3Vi>&s0a-gtEqP??w zV07&0=nx4P=<6Nrc1W?MiOr$Dk@M0|yzs&cPhFk>!b2+xiQO723|(&xu_q)I(hLRP z{eaB&Z=+|%ff=Z*=4d=y*ac~sw5j*x$*Y%{udSoe+Z3{qC^(5& zX)xHEFp(PlxC4FN?(>JbI&iEjODfTH83nr+7LpQXC-u=@X4l`@&1E(-xonO?T%Bfh zZXU#M%gf<>u`JQBV@j~tA&oAv9p5YEH`i7QW&^^yd|vPR`k^d6HtEhA>>lhL?1Smk zAEMm!q)yZXXN5ZxCi)opd*IWe>jn-UywWz9*UDu$JPXNG#iV2B4^8^vkxxH!_3G83 z(IZUfcXgP}QU$UTtu~uOV>Ul>_3D)?7l%V#UMNC|BhY*1>amI8ku&|sD$#15dJ1-0 zb?M%F%W#K6UL?f?0*z2NEAxez*VkTu=_PtMz+ zHaEN79i3wjUwiuOxeFK0KJoCyD_5_2d|rPuHk8g&PArLXJQW2aKAR3o-=erop4ZCZQ#_O0pFFxV_~#`_*Et;_({j+RvV z2Y>&wx8J_`?(_<~`gz3n?XTa>Ic<4%VzBx2*@vHe?zt~~;hSH*h&B5kQSb17Mm53z z3@+yX5uD6_ftt5}iVBE-B7OE#&wlE~&%W^dm(HNfTi_f6^;^(TkbEqky= zY9u<~4(R|*tpg-dr>!^On?%05FrJ9kpR9AGjrDLeF4QP^wA0tn)o#mX#NwSQF%!Dv zA6|Lq?VC5zC5+GMI+;<+%X=bNi0FoE_H0kw;3xr$imK5}MAy|$7v2aJ=3)YjJQ>u=qhT0<%h zc8ILrspc~5?vQ7H`0B+|qn!ZzTbsMjkk{#}=gujWB_4Xdsk6Pkt*57}2hSiC7gZuE zGd;bSlt?TVr`e>^`wbdR2q$U1NU1z`?up09uZJ%@a{kmq<0npBx$}1Q{RE zYMYxrDzYMwHaE#X8oa`e>K~In_TbDOw5nC|da|W^F2sA0HAyDD3=cHEUbcKD4VLiQ>APIw@$5h{ymZHoum9=rI>}Wcoew>Y7;R+gFz8sQ>WlifKg7sk6+$e2VAvplpki<;(Z{?784t!(<@}!!xVP+ z99RHnhw1MC9)kgnPvCD>sQcL~Y`5(*;IU^&Y~KLSIqUNK_nlEDQ*Fj9Exq&J3SO(L z>(fh1BJtMN#!lMN+5&^lg%lpE(5BH)w!;%j!<_R}s{2WIPmrBmgMUq=a#~TUSY|ND z(m2npMWB*XDWy{9@OlBl_+9q;?y5ik5_C&<7rfQlA1@FX$=5+<6wW|KRF<4}uJ zC4qlXqJtf6b$Xl~y(9{m5cLB3YXInJvl%50qgV|aSc*g7DD0dgEr{EgMDn_*QY=l5 zS43NDtJt{W5(83>p$~zOnq@qZ2($;KTm_IR!QOx#ke5&_M9x=ZLrV&Av$x;dQm9>^ zg=Y5a#~^1YlhLRjh-(*ww`QWjADqYcB<}CiYWLY9nwzycxl~hU*pi3>&0du(qUt=V zny`d`q7Gs8apxOmttu2Q*RPPx4C!dyKhDqp5GoCH7au9Dnw@2gG$ymf?q(Co&DA7w zHWxj^uTTdv(GTaLpM}BL-%nAOSRhKtB#_T=$>W%ABnN%tJ=hz-rN9_SGSF-J=L$Ce zT%&go_>Pr^iSIal=In)s*VbVLehARCsyh8X zs7_{HGZ}+?zSH@Ugkbi%cG{A}++z2-kBZ$g*fDn|7#$dBLL31JUU4o8{ z0c&7p15WvRUOAF_pB6d5^q{Dm-N@`?%?`Z*y3Jr}Jks0I4UD?CJ6|+6`wY&(haY}; zqO+kX0QU3IfzFmL@%qZzdORm0kwqGbM4`~=oc+Q+(A0F&(nT%-tKNusWt=p)bPp$WSWJywxThuMzPDS5eDN929F;3 zRV0%V(OAipjB5P1l1>E8c^K&t!i&m!MHpK%pez=OWtm=XYul$Bt>4hN5xvU_Zuhqh zb%?+;?x2D=MmQWg9aRjcqpSPJzj}1#u#txo0t`@|*y^`w_(p|P%&xnyKn9Tdraa<*;U3~?^m5**JPZtKiE$fYta=X;-_or_U#qm717w= z{ont5b7MOxRLFLA0BB|RHshHjv`{)1E2ZXN{r`T@)7@;7fauUrsi>?bp;T)$m)CLl zp54q799Sv5p)aN);S9yA;0$iz$H04ZLijN64`5KO+T>LBG|C(lROF80z-o& z#-YWn5t|*lG9n`kT6qbP5x$4@diYi&E$v<}Epj-Y(c^bgkxC&^D#ZK>ZOiPX*?5R5 z`PdtG<`y=02tx{g${4HbJ$}s(&&nV;biL2Fc^NW6Z~f>;4}uACkdR;-N0$3TKg{0G zw!7aBdz}R4esEN+H4iiUx$4lh>(F%;oAJTl%i*13uNhlQqveMasZ4%zWjET=ps80k zcLy0E#)!l~A#{qCa@s~yqA``~wXUTutcr~fph!d%t8M=iSxL=0IdErQF}7FM*v+;Y zdC@)X&bt|qhGeb5cEIlaO;iVM<~e*`mtC)O+l5q zv$4seX65?}$fpttuUz@J<73ii?DjPEZ^+5SL<)%oNZBcslM6Su;j%4@^>&wDXKZa% z3*D{F=$o#9#pztq4-F24Y+9Yh+~!vx#Ru`$nu<`al7VVZqE}HoipQsU0vYX$#dct; zgQo{33{Ez2V2xft`cA*HkjS!@8EJQL{8$glpfrZ877~W?6v9v@dV>D?eR8m?yML&2 zKXm2taT2Cd-J=Y0)9myt8;3HfuZvx=iscq!S4BB&Ez~0yh>XHP zYipYf5qo-Lt*Fm^?vY0?UAXq4t*FPbE3)yIECBU@R#bb{OMM3Y5Rl8;ylO?Z(PFb8 zS-Vsgsx9!k3@_ZivoKQ%AJfDn7N{pQ-c?Y%*6 zXBfpDopMy(Qpl(dKPqo|Jf!Z{=DG@HkK2PmnDyjVwNhI?;=e+nt!YwctAU-j4EDUa zd1rlVWi0|!B_09rge;^@r?buHbX##wkSO)=TG>Edv%Q_t1BWtZ?5bVR2MO)qe}Ny! z2M&3#vFQ2fbfJ=2M>I@eVWC*c>~5xIM!QKaRC!Tt$+@;T7m>JH{Kzb{NEQ~W=LL4H zSh%%9blbR2fmYQTDL90Ax!5|u2&Yr^A;57(UEHBDpGUTsl8L)C83-yWFC>xk@sXnu znMigih<3{&aRns+vayXhJ|Cew!Uu(Wzz2U1ksciXVfWVkN=j@WO1VBH^L$7LvXM$9 zJ^$(*%3)LRgj%H-Nn=ifz~#^=^?sun^UvY1sDyE>vbaEHZGsWgWI@W?0FHtRbq*mj zC7DX6QOQN|b%drTQ_-|aBvMdVzap6k<`cFHWqAh-=bI&^ud!-Fk!ZK0?aZCSZk6!Eh;ap1L=kg|dt5e1@9=aA1p5*`EW+8}<&>&Gg{!L4%n z622hXgP1V9C=7h5bfiJ2Ry8;G4-O47w;maxMzQJ+-kN087K?b2omBb2-O0VYon767 zWBY>Ei8%<5#Rv-%$oB6?Bm0IG^hnU<(A&Fzt9w4Pwrnz$sQsJW`?-G` zcRO;d%UIy6ECG-atCVV2cYg!QtPP(#hk7f+6KBqVb)`r~xKb)*ip9o8g;E!`@BBX*Td*0_w=>)qP*Pava6 zp-`&@DY`A2t?*R{svR4@Oq#cBiN~At|kzBC^Ff6F&m39 zbB|`HW-NxZw`_*Bw0{fFG3;>|upq4st)MRH?(PV917PcEJ?e6Gw0M9o={)^M2YU4S zzJb1u_7UvLr_NtIcI;SxBXxoff+$mGb+@!MyG#~?o_I)HdO1*b+(9SGWdW#{6y-7v z{v(PfkO;(T1@gQr#cEVFu~Uxr4i5Cz2qTb+M93IHHvwL~e6PUJWDT_=>kb%SYin~rZz-23Q4_RH>n%SRqp5kT!OWjkqKI?Lu2^$pKE9_|hhuZZo zA?3JCqO+M49St8|Yf(PT8xl!Ym`5Ywca~Ysz)D{miMsS332YrO8 zCJD<|yCWQSAbRWI zLmk{F?)Sa`s6>Q&R#xAIIeqsup1k8=)193?J)ND1Gwbf{CE0|aQbYBNf#IQkM$;ob z5P_TbYm?>7B3gEOdMRExu=BC43b|ILKGsXY+ifmHgKJ}r6pucE)j_5t~RPxCA*gMzy;W~{kLdkJQ_vHA7aM=D@FGz zN+of~H#RpT)fAK(iF8>c6y9EnWGQ04xb;+O{ii>eUr6cnMt4MTQ^8Y_YY0aItrP_{Lk9tm?Vhv_S~{pi;1V z%7Mwl&QKEf<7sPeZXy$;SOM!=1y!0_+@GZ(-W%Hz>G9`I_d9V}M z6C~r?7#klMKK3gJCS@dh;t528-luOKM7X*RW2GZFgP~Z;X38pyrK<~T7a^6fyL)I} zsYE-gLpH=M-`au(flLP@3ax7IC{VSLK*ILEnZ|Wd{jjlf<5{%ZS*fJ!d&W8j`NPYM;0!bzm?FhYPK6ha)bT5BDJq$ai}o>b6eH5{n6uaIxGgG#bGx8`mcZPv!8kK#m|591Q@bz;J-CDNaHrt_%|qJZ8NUD7ta(^u*Cp36vqkw5wFQ zIuOFlaGW(a6Xqc1edKo&!o1eb(Nkl{A#5F!PPBJg%VjHd{E!r=&O!Q#p&Y1zg@UN_dkyipBWehv769Yzu|E4@zvzu2IIDA>cEtO>#5fNpGOR zEEZ8v_&I@EuTt#nsMKGl{5ozphw^uJHp!-+S zFs!8+lubl@FP}Fl$mn#{Q!u;h?XxG|`+A?(kjb)q5JpV+kv9)BX-XtwF~EZwZPGrU zw0i12Fd@D9&f8V%oF$@r=b!)jfBSEL|5NTw>W%5?H@I2DoChXGhX;D@WezcB5+b!n zj*cBW2K_Q|;%Jux7M|JPbL7OePk!=~*G|B@jy{R(h8csPPK!yIiK5qQB_G6?#+-sh zB7wNHxr+D@!VjFvSGv1Lh7fDSm_!l@1vVoCFldL#W9K8eGndQfQ&dU>@&*Gz-(aUS zm`nVMP$a8hf1JdQvXe=%QbGNB0>~NSS8{P0A>n4z(!%oE%G%2EdX8se=jeAaE60^$ zDqZ5oGOIHXV_{FEYeK9;8>~X0b#N>uBM}%ZC`tgPIBbY*Z8jVHi%p=FapHL4RPcF} zmA1e#C;~U}c;v~XDf03wuiji>#bC{}H-53Yy1X=9r2yBiuD7F&nIjVB?N8UBHj*iJm2>W>fz{_ncK@s;PFeDbNM9zy=b(b0)>=gyx$f8qG?K)?w=(^frVPjvdd zuAZ}>yx2cTeTJFAI=Q1M)Z{T}Bikt6L+e*oZ@=-zj7Db=L^gJLb;oLkj4Z&NIOFWG znwBe3lYC+Ey{SE&ksZlR$UM-En)TR@VV(7{<}~ID=smklLZNA7q|0k_b@a4#fT66d zy|E<_ICbj$$>E_BpFKl;3y<^X)PJG=EB^lXw5^ua8B*VP?@f29CBfPrSCk zJ@LW3}p4a;eH}N3B)Z?NGr0zljPht85`+ z!&fPP^z%sCt%NXlJG=XW4u`G1)jX-$n!Ek4ue|cw?0Pf?zGabzv(H~cYt5G~pE-5$ z>_ZQ|52*9eK_lt!{oYqU^XccGr@qcuY&R6e`+_@@9|ktk9)ykHHuG^So@l|NR%>71 zuYeET_~-x;<9TKx8+r#2J<*SP!vKQ1CXOG+pR*@V%4A}R3}r*{hbI;vJ9hjy0K#)t zqfBTp!0bDI;o(m{@#M27TikYWL)SQP-)GQmQt6~R70X4o_t|a_Ax5mVQ>DrU>l3JQ z34Gz6xNu>(89*X8$C)$AUD!z{Y?@~}LLPfd%Oj6m``D+R|H>D>@U`Fk%5Qx7V~;&H zHZnduapJ_O8g|mtBRG4OdiKEp6k(ODSD&mS>!nuYaxQy@V)l$&PA-RBsJo66d#->K z=Ay!C>Fyqu2(k%2A0a5+qvIFPoPGGh#nVT+J3A3SriX*a!|ZI`gFXBnK+nco;qZH` z&0EK(TkV$*q!4CCkN5+QfCwEZu2!qlR;}Q3=U^mX^*VJ*hXeUW$h~|X`8vP${4%h$f`I~rh0@@agG7bhrvzh-3YVK zv*#0Xith!+(Aa)YIOm{jHSDJg(TZcN8{%G&PTf;;htknTchxiT=V);}6fzi0CYw!; z65lwxtBe4YEzqbP#P`!4x@WToAtzNG<}?ClFpq~ENuN)$ySukT>h&yrOH@g%Zzl?M z;v5{ad&$ntj_X85wHoeAI55Q~x7A{kYVCmZnk@OXtrTi4kP!SpXlP<$;@st9{Un5| zy|d94Y9pb(O`{+*4N;U4JgSvkVmmSE<4a?}aF%xpl%k=t0rSWvOvmHtbf(A`fMVX^ zwth<`puB#kJOOiuhhqSzPo97zywz3uLseG!_J^sgauzKFkb2b8b$^hpWnuy#8xx;; z7-)l+i~-sX_?`%As30>HMkvds=dH5o*&U8LEGvVZQ_R8B$E?>TM`N|co7TEj6>cR7UQBCzeN4*0Nv0C3a~`a01lYeZ**S?w z?AvIcF_+92iUOq`?!d+-95R6wt1F;t$%{>`@P!VwH#yZv+j5~-^vHVA>jBqTea%hC zX$h^b$0{IZLc*v*suF=bE|VpR5^&(Bkt_^Ym=HX(g>1fuGArqI_MAd|f#VuYYwNGn z`i+m!JiAk~W6{B(GAv@@fe)0$?f-yCrzjRgVo5%)u>=~Kt#N34WQ5&A2*g>2KN`Gt zL%^kvspS=vl}GCP(D74`oPFfdI0@9`+kg)k3O1`oYjWv8@>i>QaDOWhj#bXu5$Dns zy-_aav;D>Jk#uzo9XoQjAL{*GS63cAPo2VA0i#m`2ea<`qVDOws;-x2e^o;;p4))S zqB7bPnj+}GIoA+O8XyGi9>fq;V<6y2I^bY(z^Z|pUdzG)*u_dSw^$_20H!0JWqvz- z&_@pk0qaD^sg#Pqq~Yc%lZWB+v~&}XEa7mxKVUj-Way^Z4vuz!@ra z8#rr+>-DT?-18r40;d(*`%GkACi9bjx^-AXH82{=bhn1W!Up@59)1vA!RL0WD}_Tk zYOPL39WWs#`3fc1F5`I#_*#1rK*+iR_x*sZ>yYys)|`fC7$b@dHs=MD)?9#47w{TF zK?;b=kd`w3!XQ)#`;6=w$WYZftn78VcFnGsT-}NZ*J2bN#)@e&=;ZujX?+F7d$bml zA(23>g9Z?lKwan!2DvD<0UlKsB|)X33SZyQz+bv`>()w!Kvvn}q#7|EsG$W0c!yM4 zs?}j)P2mR&l+FE5l*Fhb!pEJ5$EhEUMKxrg^w%=*gQkWN%-Y!PO+1l#dg4pEq z@rA+QvC+;(mk$ItUIR*^G@#C&K&k^+YEi4SdJu(}3VXMH{?5&tZ@(4YSesrz0Y~tr zDwgEJLbg;)>~3yXD=V{7h-+Sa_PTKBdb!4`e;NJN=y~fc&x61d0<|KMc`zJ;ZFj#N z_Bu+{S633&Je2I`hdF6Whd!5K8XxR^FFYFTHDPS&J~q-9>Ke8~a3q=#Ke(~vw_?-M zD@oL^reM=4wI+ioykP%8a|sl`gjd+&_q)7i1)r0G(Z+-->|R#d127ru;2%g*AC=9R z(MB%2S|AW4R@Q|=VOd}>2LLvjLH;Tc^Z7b`BTQhYsj*S(3<0Za_qzPv?pCm#5K5*> zz9gnfN}~caHIohbWDGumaAGf+B0MBjdU&i4PDsRR5p1}+34FjN#GZG_{=oG)tA9$I{$a1G%y-0!9Qesk{@FxQsOhzirWH#ZwfK{2sF*h2HZ@l@-w`R7ZggHG)%>jZV zIPun2Jw`0u+){pkm+%kKDm`wo5|>g5+$m?LI|2xiE`&XqI7$!g+xwv&DO4C}bRx=Qf2XFFX4&nqesesqQE}Mp zZsfoyF)IUn|HH>Vw4HM0N;P-&!`UeZ&WI$YreBiY0hu4b6~Zztl1qIpE<~K64&WZ* z+ZV#cvPfgIOS1F7_{A?~@=6cP(?$W<#c96UwRa;Vl*+Vruiv3nD0O-{)~F7*5Q4Xa zFF;_3UrnXnd+)uiOl)-2&#sDR&}L^@z{7qV*@zM{&S+eQ0;tI(w4q*)*k(jtpkP)f zLZzKbzS3U!gspMUB|)WX}MMm2X#Z zmI9*W+<`{FQDwwXpwNd!4gLy+M4N+}E%Vf#X1}{!_WBW|X!8NG0dPjb9$NTr5RtUO za1hFBhJzkdVHUHvvat?D8s9@stKO`U2@+7WOn@R{rV*ehlBtwBF>yEKl6&l2VzpW| zpM%|-d9_Lc>~($h1sYLo-(pt&IgAbM=LvbaR6}n^(Cc%#c6OE)H+PkwHshCbIV44b z1s3^S;L&gFZ8IS7I%gz(qw@zG3f|xT;D0c}3pL(ERIznfPwrIN%R za19ROxlqj%f&6^Ct`&PtEoO`;l0igeK$X;7v|LRIftXh4^|cZc5?aWhF89I@*?y?9 z!m(F&?l~E3aX%UfYMm_gL@p;Vdu+WueF2NHh=8P6bPt)3WwWY@3rK3Fud`*pu)2!H z#OCd{r|~M4AQrf9p#&QIcge=~>=)6qgF3xRY1j$x?(CwBwt?9B4oRkVLFj z{{xa3_eAZX7N_14>^S(gF2ZjmbwLikS6>%olAlOdqsIOZA}s@F9Bl1Fif_D2egu=X z9_&TZ;^aq{zrKebhs+^ofuTn{(8H}(qrVGD?rq*?7qVyd{)SczavM@sX{B0qusbZM ztf5ew3`#+Ta(kRI0aivTE!Vl&6@3l&Z*G<_`v;HpNuVNMgSX=dxax-5`dah`qr0PP z5RuM92t1+XYSkOB-sAUn!`F>#xjP`j5 zviLs!ag%9ZTik8?!|vB66^g{6_@;xlKD*-nl02CYS?xLmFIjCClU@^Og&`aGFy2Ac zq44qHjg4q3jR2N%nTL9mV&2~F&c;U$DQBLVM6!$(QpjWi8$=u)8Xhl%bn z+)J(x7wyCD4EuG;naCCdS_3H|jNl}XWag)@Poa*}3dzEO{=!r7L_Y~E9Y*ueqV`FvbD=D}(E5A%ws0w|9ul#HI!CkMa?Z1rg4Q+$$IXhDt-nEyKeAW590}#gVx&>CA+8 zwl>y_xt-0}>f)kMi`l5Kcs;Op%t%ZVq*Ab}ByX(i5iO$!JsQtJvIpAQbg0G4iznkq z2ovd4?jSH!KF62%eY7)fP~h2AMzcv?hJMe;1VY3;$ffCwUTL(pg-93+0$D&XY7O}; zS^>PQ@az!oYLQG1xYZysnv538Ptz?b5EG$NP=8Rdd+XhsZ@nE!;?8Is2~?Xa3-enA zk%PEILTcfpL2e6motS9kEly3n_wLPimy<%Nh>FE=3}2$Qd6DVoa~PDAhW=xL7v&iy#hGoK9JkYx0x?Wyc1&AwFQZtThtN4;(%c0N z)fwt;Yy=p1UE=eqDe+H!zK!B4o$wL)eeA0Lb^HbaD~Zjix27<;7I%@hZi8>fVb(}1 zc=#+B`zrz&jCrj_km3iMM!f0yd8$mR=sBQw65FvPe!aA~7%LS~j}P^&*P={O6J4bQ zS1W1-ZYJOcM)HHj!aehUAR~bf(XGgoaA3Q1*&UL6oDf~^h?e=jSO{h=svz!L0IW+I+aQ)F_RlAcxj}N?d>TMOQb05 z<&&GxcU??@ZzJLMy@u zMG(st9Se(??Bc;Lrm8C~_4(Y=3~T})Ezuj2$scL;KjO~K%-OhDfP+5EJt0UMwE4Zw zIe>3>mLj>LL?MD-cz$-4s=!i}AvK!lFn*En>bfgs9;sTYl?p_Bp4#d2x-DQnOz$xP z?K|;Fa#n9S0`+^)#k z9CT`=GKImc0ZlJ}LyN3}N;xpI6B50rsexlf9z{<&QHVSi+uaWDfUDQy@_KWWL?VMv z%Vd-T;=HaY%hgIDB`htxwYajjy^XoK6-^bz8U_12OfIHGEFc(VHoApChkO}FMY#qm z(d1|ddG&g|MlMbv);%pn;9<}#&g0oBjg35~1sqwl3OvG6UNMi55Hgh~8&O8Ri1S{l z2;x*eil>`|>DA?(ETwZ>bka&}d1-MwQ&bwcab$Y5Tq6A_M-d(JYH|n@D6)L#_Tnz0 zQYeJwmw8mNEJWrhG#@1`ZBBs&U?(p&1hM>D{RRs93PXz8NC?@}$`m4r4yw{vTE8>5 zi!~4prx2MmX&1`12E9tkryLG-J{sF4OqttxflP08u&Y~$9;MdqU^G#yuW1VxW{4@A=PqCQZnYsBM27DTH;?UU8w>G_kkH|(m15QUm z(1pqs!mOw{bh=%DU{%G9kG#eiY0FhL<+iO4Tbh?jW)hSBq4JzRovORZeP5 zd&E)8>F+QW276_^=T!^7iM0+LwX?ek#$4oFY4u7amhE-)T{OP25vRo#1JOEib`~?y z$9wTGQl#4MQ-_WAu>)zV>c7|}Vx|}9ck(;aEfn?*w91>UZd?i+f76;vI1Y5|;>h9L^ z8WNRC(XCyGof8tEU9Xo{(zy99(U6RjfgOxX$=5*OvS<=pSZ`2F=`JOj;LGqw!!p7{os0jDV{ zNmD9zyqHgPRGQKmjm#&Ll!`^l@rhJ|Eg2_t9$ymQ-Ay7!C2?ISGdR>TnmuoKpdE8L z9!~*JqBN_=*A(g$MsZg+j#0C>d+&619QcfN+OL#)zjeU$>z}HY!%%7b@ z32qKCZGZ0Ay4TW?hK?(9?mtR^6 zQ&M~-7Y1o)G*jkH>clDy0(uonm6G(Cs+7yoQjNg@O$mF2T>;;MG#xr4=4-G$5F8pA z?Q1a0GqGe8-4HJd)%ZZK!F-iTgp~rRhMmdfX>CcYRgtaSq!d8$OX2r+V}ghjU=1mY zBuY|A*1*f=g#wAe2{JC3pfah;$M@n%cuEt5HfS#)kVt6hUU&_d!&*4W^$nR<{5-Wu zbJtdcxX2m94do+v*e?}CQc zpAdYw;=*1zo5~en7t6IKi^HmS8>JXtWKQc`O%^0Jq85)#%tysF)Q3@tv}(D8Qqi_N zsX>y7W>T?OJY6V}I*dlSO$`!_$wof5ejAKV5DHPH9AF28foCd3q7#rIyFR-+yB^<5 zX1Uo&a_jsUaG6wqztYxLfgp%6@L|*P`GSHLH_sva_Xl3Pl_zS(WNsBi( zDe?xp%cyZ5kyW@Yq-4;LnnG1WGVjNPZub4{f`)6CXx?vgS+<9JmeIZ9s_w1V*v#q? zi@DXqTnV(?)luBjTgoeS#jH|SDtbKWwACgS^Ne==npLw6eZ&7RadLMNMgmv$eyVAG^JF{^H1d$M5!0+vSfqIu_W=I>pVT+f` zpxlEE2d1HN8MZwE?~;i)*>{v6-+cF%E0n9d4J8}@h@Dr=uY}obVs{N2{~Ge>5tK@( zyMciVnO`ZDj<#+Wwerh%SCdKZS5Kj>fhLa>R?p#=)C0h@s~2!-TWfnyiz=N~HusTpA8OTBCPlHSH(p=J(R_1jEBlm8D=>8SqKJo1 zrjxO}sjbbJ!-Pa}$9QxPAMx!th#h{}`Q)U;Z*4WxqTIskZ%oA|#T9MqP`_6~^CL4e ze;`8Zkm37Gzrj$VWYH)|)Wng+K~Lh4r{F9>=CigoobYkFWW@xDf~n#6RREg-Vr1_+YO$BM%nU2hgF3 zwUu|@y*abIxfP@C^(Hrm%x2Hd$x;n_;r+uy}JqSms0$aPw2#` z>b)BAjt;ex!q_oO3x&dBL?DPzGqfh1l!`L|45CS?l3yxUbpiAJ$o#Ea3z3bDVt8sQ zTv%LWzAr5mGh%5nnk|?3N@;bcHsk)XJz$$CVjzP7Aju@f!f>Ie<|JZ1S0q+f_vBJ3 z^SQcM2n)s4-z)KZUN}tS?PKL7ezY}dtWGv4JgH9w&C;}@CCefyN^U-I*>ab0#ii@#WehW zyJX`Z{?niR!=3fpw=0!GY0?p$pS$_;%P-#q@yGn)!g8cixij_NJ3srIAN*p4n=yR1$!Gyi>s@*ZZE{PHsyPBv$x)`zWP=y#n&NdPbo^T;v~5eM`2YB4?KFb zf>+7oScTN7B4SB=xxMHP;&c@*t47A-iNP`>MZnAYUSTg?5*l3~8JYCv*29#^BFGd( zvf{#>xt%;E*BP`@VP*%L^j3;5;l{uR*>#B4=3}xbH2Q1TLZLtdbq3oPIo9UO-{!5Z zv+MsCQy+W5-$bf_zomZpO|kwgo8#H3cs!~z0N zn6W5iGO)nlyq}4Lllep%2TfZko7$b9n_J$FB;xqj{QOoXmrkXN$fS1iL`oRj8ij~Q zbu?;)srALV_ujatT~?EoIk>q{aT>=(GF4Ot z1WrP+A^OLtf_?0C^7wLaVE6PWdzukgs8V~K7BkgIkMHb+_fQUw65zxkK*gM7G>r5v zn7)8%AU~!gsY4q_+7Ef<@;TI@g{1?YY4qj#PDajC>9iW92GC$t*Svc`PLz5vS)625 zGIBb|0n_EQnhYu_8kDNQ&%_be%jG(iR4pvakcK zElQIdleMI9w&B{Pqk_yM#4+W-N+lBUahOk`&<~l#DWuAM$T+gtTy8vl6Jo4N zB~!^|Qz-a+5|k*>Ar!*_pDh%QE%JB&&(Ee;-~8YIRHH(2Rz#&^?EVBI`j(baz;ZdSKzQlX1s;bGvz8zwXo1lvh~zCI zu20w<)<_-YqLE~1OB9Z@wO(yaeh#Ahaf;6S%P6JF_AqZZgvB=i? zdrN>4F~vc(m5=T0Mq|-rQK-OC!R8`bL;>7QEW<&@?Drt8%WO0e+-M+I7E#6g`uy7V zZnDS|0nddm+mzYdP2!}G+}+F=ZB~cbs1or|`*|De{(#qnl{~92-XUWusieU`f#S^3 z4D2_u9ANzCz(E>M#CEsttl*MNFp6JF5HCYAk)^~Ms{?75NC?%d)J+PamsCoLE$DOF z>@IJ6gI1|x-c+kB4UKr$?y#s~#GaU z)=8N`T}6{sh%s0Ta~r!1fg1=W>|9C&Vkc z+%`1lcHvwu)0muYjVMplQ>q}*SV^u}d#J&NXutjhQu?VP%xi^Kfnif<CITxP{8<){7>Cu;LYp z-rhF1$=uUp(uifI04P(s1O7f_Fu_%cLUhC61RDk2uShHuIvfF?&u5gDw(q?2&J3_j z@Muv&A*LFrJGQsuI}7vk+nBt1vksgAdaWF016Ku?iK4M#xRnx8wMPhiSSZ%moJB5di<4#eBC zu#wHJukRN5d{mc%Y1PoEk?`1+@Ett)xQxo+WCGCgJ;2L300S!T=tOUa)rvAJ0cT4) zkko;RiP1(+mltUWCR zK@%1?RpE({A*AICG$>WYlVe*5c61IPDAjJ4h;g*zGz)|G?(WVl0v8a8*uR9oWo>si z6dE3YXGWu8|28)@g*Ad zuDrJr>+bQm|9|4%1ip$Q?fzbrcaX zBwymP6#DfK=y_*cry zkDtDL{f?Qd?af4)r{`TWzE5v%QbKt#hC-TR95AdGmqu_ZfUBKFf~`!DXCAdMD+1dF+tHWxDK@4##BzdY-Qo+7jxeO zL;UpSEpC{e?>WR-qv>>gMUQe9S~Rp3GZsdf-Oc-Jnp9 z+qYXCICvb-M8qwmVB9*n9U0ls;DPBD%@)HOaQ60>EpDaN;_};#7T?ZW-=^UV9&ET< zcjL-MxiwgE@ydmY${P(=uq4{Nd-7aWjjnv~SaHv&n5fnX{&BH-U9G8w2kH%JRtr&w zCwH-hh;_6Omz*ucRg5&RT~qcJ!oy(?%*|~peSUN1hTqP#IM&iDa&2F^4E)p!caFYhp zb&FI%dWvV;c-6k0pHH_)dwU**dU{ z<*T_ED+HWE+59ZUA%-FU54TAi3t-g4?H%& ze#VhX;gRo{X~(RUyHuId7U8k=d@u5Wo}IxpN@OJd+e-nnCT@ljf2S5fs=XQ<#1V>= zrCw@wTbiQEm(fL9W&CvT1a8h_gu3o%xsrvG1+@n2r?$n824j(vBv}f9zgs&#B4+K@JSBa#ofd;8is ztOfX`^6aUj2g?qhH*gcsUW;>)&i`L)F?c8CSJ#XD7P-2=pb&tL*EIvB= z#EJa}akHE~B%i$=ie>LDy2F-RIHL#>^KM;FhzJ9Zup@MuAuxh^rQ)WwTe@+%J`Q>Vc7fq?3ptaXHWl<6cyes zAtC8Ld62W_(0#NnDiqIV#Pg*ZqOc!?YHB4LEMX0Sw#Z$(bm15LPRHZXhwkf;F!Ec8xUZZ(M2c^|vMB_rmQliHL3C zF@1|`8?9ABY?K*~k!y#L#&c)SoIZQ4$#AS_wvWP_7TI;+6QuZ1 zjJr<a$+ps(%+8(L_!wHvWtj=7^YD#q-`Q>pKTG}s0&(Nc+s)0_ANTe`{k;6$D$V|>^@J%!Hy#yL1o0Z(J=%*gnB=}2HqmWG#grUY}Tx4c!E0iNVSo20OiKy=L5z0t$pCch$RllE`!gerYqjBk+4 zlVN>RyEyEVikZUux;)q)d&~7qii_|{RYn?~35UgQ!M5NqoOxI=*f@{>s271UsKDsP zKqu)D?%m`Q+OeGnel-ypSzl%E6XTFe_XV%MIdj@;LvfpFB_4LWQe~uhV+QVq?ib;& zV{M?_rt>rX(4s~jlNN#{(5co#X8-sb)?O`-dYFEF_1s@^)r1!V$)h(zHs4k zle>G9BYhriypI06&f6oc)%6-I5o=X#J=a8X=Z_XsH`^uHLa_xBdWtl8HlOA#(_eUb zH(Ki!WNnCH3-Z(!g}UR>nes&Tc9CI0n57M{VK21U=(Om%I_2dj&uWWr9g3xbb+~x) z^>=f3$8#mDv60+w2@SfBCYxQ0Hm94v~3&ZlcsK_>$k4nwA+kBZ$#0sV_EgJ+nC?+sH^h|4D_nA zYXf7~uU)UfFQ>6M$}`RT!kwCW9n(N}YpgrCJrFyVJl(ujlNzYGdwF=qL`Py*!L~>* z&^;~UsyulS`$k;7f~f=@7UpJPb<^z|j?o>^>U=f7{W2*W(34^ zdY}`grdmXn7CCh2z=xli^vdLCo*#>)&SBUU2V=ReUE2f)Ibx8jEH79`Qv;Ul1qCg_ zZooD#FCTC0MDE$j2lHxyP~VtvOxe15c>Chn2r=RKrBQ5JH_VaQT4PbTyjlkav`xUm zinw+OXm9Yu%5W^!@b!&|Y$H#z?B2dzVp50rwvH5^M2ZP+dc)0YAvS1rb#+vjXIPMz zyH`M9KxlN7a`OuC$5KF5SD%)E>90GAn*reQW4CU0X^R0^5Y)}bJF0!>P93{rXV!lG z9q}e1-k=y;D|f%>cAcXlY!MxL4DQ>tM_SO2$Bvx6bm`35+x68No=kb>XKQ~Uh6wa* zsM7+%6I%heu!^w}Jg18a$|9U&mPtL<%v_Jw0^Dh;fsq{m1jqKL5hx7pBgbK6>o) zFH9Q$3?A-0GA+gt>EL860!~1U#J0Y__d^2*Jo3a7*h#_>5AI@md!hQ!U>j(Iy*!!% z!`-zy%^i<`N$k=#6zI~H_D^f7ZP05m&I*YN^+@xssXTYF);%P^-P2l~L_ePOm(N$> zX>wjo?(pO7=^hZ`UVHJJbuGtsH^Rd!ynCLB4wJ ztqY9-VeR8%{B^6>({aA(j_bt_q3op^|g57wOde-PhkB0y&s5A zYWJ_|+!4YZ&&_Vv8J9PvHJ{(Sd?!5n)Zn|F&#~20r|#Xl_Le#x;z*gA&^px9!#^egTUfSt8jYeAJJzdrP_jl+#aQK+V2M&G6akjq}LyMLQ7mpZKm^iN)|OBD_QQKD~QA z^w?u~aH%6+X}`LiJzajVqOyEIt15c}t5-hCSTnX$_$m!$#cDaoNM){oT5u>)x$!!y6Bws=sY%5JxnA zq3vveL9Onl;j*jg0y=7IyhGh<9b#xcF^Op&{xASMXu^@Q{n{_|~??H@_C& zJRIMo#}C6YP#<^0P^Jg8Er9E}Z`5J=f~{-PeMuee!!F;6?LtE^R`9`dge*IiJA8}! z{G;c4pw@f!59A|iT0Y|=A+clUqz>|sj}&hxo%10d)~}!OkdH-rcx+%=L}2SM{J^XE zgFdc#B@MvSXYAvp_mKWW_-%qb!~|=%8yf7JC&~VxPdtJ9iZZyz#KDlELx(*4@WVs! z;2Mk^F>J($GXPtYVIMhcBxhior+ao6w=#n55f<6E|B*)w96x^QhW*(uPRFulj?L>m z1RF;=zVjCX0YSGe-MrCQUwQs)Ls*Cm#p2@bnLoErfycG)hFx)!v7o$1N9ay!fO`kr z6^Ola1wWZMc^XYi%Q^SR(b_2OQLVq$T}#qpHJj#*AT_E=ol(bBfyz;v)MqG# z_f(3StzJ?S)MzzQ4N<+3%d194MM1ktNNbHR51nXt73q9ytikuo#`g0|E=;EM#f8x; zK83X1SX$`@KZdF>Iw|j%FOj>~Qc!*vVDwczA@oKL}h14+l=0)cksyTJhI(*q@se~U*&h*2)Mm{n4V3f}fYCN$PpR4$s!>1gd1NiL0Cl8-2d|tw5g5&>a z{2zu7#wLl$_$1(i5m#a$J|0T7zl+aJd@AudiO*qt3h~Lu2XVFEh|fBFKE)>kpLBfY zBk#I1?=L7nr|yVGjlx?~1ED)^Dhah^Lzx@ulcu}p=U1G7w8{)PSW+P+vAU`8T<62{ zAk8<2oqg@x$6_h%H{RH6Af0(~e~2u$CAR@2T9|eWrZ1pV}h# zZ7bU%@6DuR|G=%}9)G_Uc9%13u2XXP6SsdSmJKb!-Wy`A^XSJ;$(6zn(4J>|r~@EgqkUIG5z_bc4>?kG84D!#}RKIVJH}lFO~l5vLo< z!0xXN%D^Gvd>xX+vPoiT78d90IEwRa=8vhh!I?YUQY2-0wSnf?Uu~x2+hmd3jB`rj zhE@R_*e1D6$A2nC{llYy2#+=dk5@n|5_|I=2`x%9H`ph>qjhQKW)D#qn;)AMyD@fM ztV{aT>6YP?Tt~$%{hko6qb_NFi?H{GnBDB?OO6~&h_$3bNcIq;oo=(8(lDpwN^PJs zzU0`Bv2n3svA(e`Nw$h1(S8(eacWYelvoT( z33N#doo=r=B{}-?J^m&+kB)UpBgK!rVqDS?y9?ZUJEbo8mJkE$ih&(DBv_MOimq`= zD6i-<;xD?)=~m#Bb~vRiPHBTvTI-ZPcE+;Y=>`pPq&CkfK}#HYLH-=+LRTFApeqh( zlqJPD&m;O#%PqRUQ|gXblH{DDW1W)CDalc99u*(-hX*V>B&DKo?%)}h6m<-8Nfc}& zD%UBYrX6mmX@`WGc1WnVW^T(E7WM0J!}&TSoTWp;Svn+~r9+Z*?O}124mX^oL&AA9 z^JmW=&c@+}GmpYKgE&`*BwzW8#Q8cTo~_7{IAi-O66rdm5~qZ-kHpzWZjN+GICF;^ z&fFp4%p;dY;#?!4#SRG??2slorLj(Fq*HQ*6=?`#>Fqp8!?S;6A{Z8 zN+X<-%WY5#x1RLZ+3AKwVG;fjZsGOe*PT*@Q##?4%A6A1!V4H;htq9~Q`+E^)>={o z(r|?M*m4VBZb=dG;i*;*EJ@s~5SCl`a;G%UDb2KE37_JWpxO3UI5gWK&2t_dq*OS| zARJ}jkm8+^9F34LSd6Q}-9!qjcS_eSNsfk9@aTyaZmeG%D+t>GZuy7O2ItYWB87b% zwmd8~Y#!v9PHBo$8t;@wIi*LPl7#Kgu-%<*NlxkGuvllzmLzUg2+J+W>Cfhryq&Qq zr{v6OXpPg&W=WyYcKa*TY2Bg6oNi9*4uwrS{N>_C+-8wOVciZ1*6omBZw^WPEu%kJ zn!^ni?~q{e4r!A3kyj`z-u?<5>6C^zrQR4_^NK38yVLETOVAhll|!gYg625ff}OE= zB5hs1*=jAfUz1#8R@+tQm=!IE{UxJtdBsJzMvShjA{p_>tMxqFIlF|^TvEB+&34#U zXdP{h61R__Z26X3v$6LN$bo&Fonjqzr5oxxqb#JeQGB@aVvQ(l>nsU=TsipE9-l44 zm0GB)temxAOLx|Xvvgh8J+}E)u57cN(o0r3yRM3C6P!m!TT*Px5wd+0?sT&av;5fx zI;CW%89gqBsk}$4V98su{M_p305dd;vk2%D7=B_dg z>2H;8vr@yk*&Y=v;uzN^J-)(1G-*f3{{9)Z*xV}^-V-|dRURi>_zq{Opw>1i<9SbMAI zx|(&hL(Q(EmZ7ysmG&igtx~}sim{JGU;hgYY&%^ft#_Ao%=52!X zl3uP#_9sH}(aXz*?Z*tH*41cLGOlc>!)^3_PfB@Z!?qp9>{uQAZ7QyYTYJ6y(0j#~ zAjLsSDLw_MuU`Ir@8Tnr^1kn~Z5=|K*2{l?xA+IRrR(M1hl_LI)}WUc7Zf80szIwR z-hsRs>b73~SN~$n$ztuOI#?lY8nn@W5T$ti7UxTqnw%oO>$K{!L>_rbt9Bgu4>_WR zs}mdd@Q9CAeNb!xX9o#o1t|{UL?SP@DJQo7-rI}_3fV`=H9e$yeXFqR(d?T7hkNa+W=;Nk?!~&LFp7U&Tg~MrbzmaBge{j6?{I9BbpXWjqHVVtLficKUT8-^$7U% zHuRD;yFU|2Z!B4vmn4!_U9yD!+UQ3QOv;OcTc}=jAnbq~ZKF3G7+Lxyq?gQLb9_t6 z=vIE9ccGL}8^ilRmr|W>jRz989TR^_&6d6cN44q$&^Aab^`??Pl}d@P)XPg&6^j0^ zG`vfemF%HgW68p8e-a6Kd5s~G^t#fXUK8M!g!AZ}BenX3(W-P+34WUjx1*&I+b}<` zf1(fmLr;c?)vI#SOJC#Brjq&+(VkSr0tX7?>zF-d;bq`3o`>s29++Pn_iNz zvtA_RB`P;vjv|&JjO9Gi^`La1sWEr%_9*x>v9m(b&H|D2MirYo8q#s4vR|h^(`n0N zQsivf>y4#}xm`rks!LWamK>Glv>_1wE6 zsXr;TB$!gUR_*akiJQ3TJBl~&N4cnXni?0^Z-W-9Wyap(KW#(q)iNwEJ;e}v5F%xN znYbau!u?@L!%em@*)|`?XQoKb+ZfgE9g9aol3!NX?RUEUh%w zYt=cc;m=S9P2A<%KN|9E@vRo?$8zS+9RX>Del&Ac&TL9~naeVlK}v%aOemg7x2DY3 zimr-e4$FMG2=*gGipu&{C#BnxTkN?Je<>TWB;D6#R-r)tNJ3e>5#ghWj{b^xc=Rq;KF#g zdFscC&dga33BSQA^qeEDx~Jh?R8p8nNiW)!-$Z|>i*o;XKqRfYXfxBjY3N1q`KOTX zO;amcR!}7pj`}l=2>odBm~64(2tBWO#FqJx{Pn8*O+}OF)>PcH5MgojSaIh$-jvFV z<9ACA+8dn;f54qALmO@MEgH+(nUx~v(B61__c-?WZOmc&#_dBLYtxMT3RkC9LW)4n zPZjEv^r9pALq*b07Zv|eG$2B6Eczi!tYEQn-)AqAHcIWImT3{l`FiHOld3N)5lQ#b zstc3omY^RiJUn{}!oo%h3-^5nX{;HrwM)@>y6J^k-yIQ2Z!FxHC1*HRssD7dhea%( zii9yiZNY|pus+pbL=~(pSWCB`3qCHw_(a_>{EFfWZ}MoHg4BIoc(kEl-oB2A&(j#W z?@PB}NIi___hz|y(rtRdVFPV8Vy-S2wW=%J9ydnpOPQ6+SUMH-FZ>gwb_LxtPf&WK zASpEqQj_uazPtNio5;&^1#;I=saH~vOHDV26|O0O79rLhd+QClD7A%_AieAtvn3X+ zGF@8mo;xfH(ia6OGdGE(=M~sXa43JTa+sJ3*6>G?-uNpT2Du6L)^|* zx9eU>caS!5Zz4iy8t!4scZ3V0gtm3>maMgK3pBo1*>Oe=q^@Rw>RzyeZrfLe?G?@K zs?^>y3=yd3twbHc?Gb4ev!+0vk|N*xBE5vYJs~{_H+!r2g8c~6lkcz|BV%_@V;TIo?R7t4pZ8_ z>%_Ov5&bK}YY%z=xV>d;**j)$XC7U;YX?>*U@T@1+hf};TCmvoV%LW0umEk5u_kkk zJvD9O?i!KwBIUjJ8@roQyU);Vpq`i4k#39iyj?RXy>DdfD$zG0me-mZXLx3gf%Lwy zeOK-tN%w7~rcbl`Lx{gnDpD#V-!AES8CjHiN?B$`K>kgN#qO9_Mp4?nJ9xK;NGNqr z`%$GnKV_FtUe%PA8*6u+dnEz>)-^Tmp71SF*OsI14!j~Q|8kV|o?ZEnmKke4FJXwU zltR8GwdIHfxqvjl*q*<3mss$FO6@GSyXkrP%PGABn|g`TW2nzB)d~1}4EpxjU(tdg zEG&4}Il8Ug_1?=8%VWmUT`5Z1f$`?BUHLoH>Gs90=`X)VY5T6pyQJ-X*jTgsWrW^G~L}*yIb15#R$7$r_}!pr9K>E4}q{BQ|e)4?95`l{5C~OqKEP2&cE>c49EBM zoge=-3`aT{TmI_1^Dg}?-I+>16CHlO*g5z2Yj|Y)&S^WP{`#21c6k1<9IZ+>Su3QY zT&LKzxsOuYuiDer^LEPl>FQynsc=Y9SaB4t`l`27PnCk7aLiNht1ncl`cL)W>VK)P z)PA*A9Z*NrPF1cht0HwpT~$A;>*}`pMcvVC>bw@FMPo5@Yb{yx()wsaw6@wK+HbW^ z+V8ag(1vO+YO}TBD8oN!&uH_sH?^_a0_|;Woc0d1Y`nHidsmyRy|1m%rr-+YkJ?o2 zW4vF{{)G2*Z7tq2v~}8lYBROJ;yqW}jQ6YBw|Kv%<>LJZEl=C7y{>(){eal_XnVE! zT8UPQG!ANKw1rwF-Yc~mTDA6}c1Nq#KGGVryV@sOlkTDYUpgMjp{>&c^Z@M(JxCAM z*6X2qsP-2tc1ZtO|5-b% zU)8T^Kk9XQy>=8o({tC3t1hS*^nP-^s*iPiM&kbve0t*}#}n|0!Uq8&}IsQlcqpiYc89umL)@I^^HeVZ$&nSEz#iu_$-SJ7n z2iAcaRvOw6OmfKTHKnTXk=9pQ!xDUUAq%-^TShCDcL3k9dJR@AtqxYN>3M&pg{doA z_5b>TZdsVU$oalMj&9T2=WlwWS|qJ{+mqiXLCQwhHS~8`&)Z>dFRy8h+q&);kE6%6 zyK4OR((V;#jrnuGhuy=i@q3T&Gaw;E{vd|U(DS}uPiZ{d22$FF7VjHM+u?SY(sAeY z+uRh%?_RC?&DqwG{LSB?UsMHpr+m*BVU4O>56hphM%r5BH}^ApTw3*a#Ug26XoJ4* ziq=c!PX>J-m!AqZ6xy0~^oLU4zLL`aMvs89hQIl6Lk+>-{U{S168s}ahPtFx&pSgu z-yr^aN<~`r8he_b!|iptp)9|fPN@J=swHhgog$V@xDBJU(0QKoQsl@K97*PpA0Tyv zgq4n1l{T4@C$uOBZH=z^s-eiyd|Yj`Qt}H!=ga(bO-J)XrOf}%A8PLVPR{I~zRdjX zd&GlzGk*gABmA!gFb}$1CH}vAh2`FD?k4Fs|8B|Vwk^Aya_RO6GvD#8+m3lkeYZir zndRH1#9F+}gZ6VV^LJF5$G^*Q9>j`6{jd5a6x1$#5B3Tw)epnv7-rb8b+KNr{856w z^r7ir>tACXsO9efh%=dso!#iOT3l82}JZ1)k^6{gB-sA5XOyE-V^c7N0Jn-Suj(tz3Y2+mRV*l^F6NvHn zf7Rf~wd~dUS+jcE4(Jb3-!>ibOUzRk^AyHBg<-ZZ<|&MM3d5H${B(x@STq;mIx}2n zhU?4_of)Dt&hdT8zy6_F^YIXr;;C)R^n(cDi&7CDH{XDJxv+)bF#4S!esfQWU+}n@ zy7e3Q1(iSIJ(JrTHFaroI%?|1ws}&MImb{=<#7KHWsf+NT0c_%qj@PWP+x6c+FGyg zGb`a4aQVPu*TRZB-=Iw3ydWm^Gr%L%DrE6PPBBh#-QjkVX zZYt8avTY;MxVFt-(#Rc(G^%q)A&uK`*=OEDyhU`mZPw(>lr(ZD!;(R!aW!`X(zung z3~5wun}E`P->gEsR_qs8VwXiD&0Fxh8gYG$c&~y*=8c?hkoVir3TpCS5^(;fayue) z+%4qx1M|nN6A|C_ z>{=<8t%-VW(|5U3deVvE1AW}_qD8aX&(`0HIA2OZbYAjZ>t_^>>|xBqD&}Do)2+_(m(%`*Oqm1P^`#o$eiTzn6<`i`uZe~Rb|Fl?nn=5*SeJ ztn#(z$*f#?6W5e&|7;rq;1s;@;44`2lD1P-)1`C%5T;Z^U_y;k?$>Zq^j9DT*^E{ z|F3-LL4P;a*V9%U;dqSUU3zF%$+_5%piN3NE5#polzI|IobqoeYj{I zb?GnkY&8Au%+~q826?pE^UUwJGsm0fvzDXW=Qa<=W0p->HpJSJBd(WOfjr#Jmg{A_ zEWJd&FE-Dw!^U?>zEC#J+A@1M_`kMp5bSPA%W%!&v+AJQb#BhE=ft1o*ZPk*OI>Ay z^0${m=}P!T9_pI4tteyBFVx?1^CHTjSvXg`R%+INNykyQmagNxEWK|Y&b*53^8Xe;p&J(4;Wx%mrYBWE9K z9XG#5e|c;|m{}<{v2vH)KlI~osIPDFe>3d3MY!fMBCQ&#gEoT2^?}Hu3tbX&V ztKYQ3$$#^*Wv`IY=kinP7(cKNw)dr07%5}XiRR%LkE6eK_OUE?E0p~|##L)vE><}q zU5Cxsk2%VvUiv7Md5eCtrCqcP*DSsomp|0MKl+{V_I?1i=ZvqJpGr&rdEBAjkZ0*H?qp50j{l?o7%QNEkn^^5yLlP7Wb~)YkvGv*vkZrQ8w!+pWpJOS!$g(;y7Y&hs@)fzY%*j^Tik3FWr;x zZek1eaf?}Ql{5U6Ht%;Noa1}6y=`Dz=P{Q0wK3klW^JIOpTSi(+Yz(ObfTl&K_!>MLCvcj#2$q9x)4$pW~u`j&Y#7!#-$hpU-5tVvPMRi|!yk zq{H+v7Ix(s_E6<&<1xPGc#r;Wy3Wzehkfw+%N5crl(ca^wfLNcZT;e^GncP_Bw1~s zWpmByt}Z+A*F#zD5svaY0_pxJV|V0P7dy9fuX$)WcL~=ltm80d2F9WP;`J-^)ZaWV zI^bvzYX4pz%%7UICDj;fxZCq?{w(7T_zyzg>S#Bb=fEYq;Xhsl8s!8pMCuuJ3aFg0TXw8}R>z{oHUo1|OWSb!}rGYun?oD0y&$rIc@tnuBRu^e!JK_@e{tTxlAG&iX|~P0f>9eiZ zzp6vKKg@=rU319)gK?A&n18ySbln9lL)k83jY6Kqp_9Xj$`%vsjJmgRegw0SBjvrzR~c|;|^@8n1i{w4?E zT;no~wHg{ib1Em+Z%6 zU_L!i1!I+Ps0zbsqzJ4^idHeIHTJ-X!`ynjYNrxZd({EE;&f3DsBY>(m5en@{nP;L zmNOXh@QlrYNt1`@@av3N2Oq^+f4iTWJt zq5iDaVP(`t{1)L`m8r5-j@qj7)gJ8XREQN)#aJ^{qDrx9>Yyr9hty$p1nZ}cVg1xk zSUGi4om1!41$9wfQrEDK>V~?ds?{A;uNu@{)riNGU@3;?j;-Uo@H-%1&0h=90`cpd zVC*0jio5dRSlt|{MPbcVYy6rgPD{k@QeCxf*kvkN>!J13dSMM#U#%bR;1AFSY7b$p z>0qqMdPI9n8-{gQBeci0k=m2mQ`*zob69`%yfy(Vv|iGtVV%`XZ5GyQ&C%xaj=udq ze)GHcN3rTa?%?y@JrrY3ifD%p>gxBXM+4h8d*khnx(oz^@pea@Mu3rc%Pv~6@D+!e z)L55z@)&AULw$AwrA{@~2XBpaYp`|=)bC)pNF8ga<;TF`sAml|{RAkrt+BqtP~&4D zk41fJsPPHlMAWy&8aGhqQ<3@%ybaX-EbtA~zQOu8)Z0pSOnL`z1G1gO$AV60;w~B)EYco1zO`R zdZWCdIj|+s9?(nkQW4N0AJ7-;xZI#W(W;f`4c7L=X>nkpmIx+kNnls4I|wZTduhGk zzqi&~#X+C?Bh&zG2=LC~n-;0SG`@)C^#pVpoRpVM%+T(nAsLa(NQ zGql+%STsuoaE*)~*U0#Bjf+iIh~Pe*tdWW0N|$J^bcy0hmuOwq$h78qmsqZMiR4O| zHeB@*gHm|~HD#b2Mf2RKd2ZA^H)visTq5wcYn~r8uN{s_OY)EPPg<1_YF-F6&z+j* zPR(z@bp&(=);LaBeD)W25LzgE=0R@A>%)W0a|UljE(j`|lz{qv;$c~bv8sehi( zzx~kK61;WlUm*1F2-?}Bc*jE%k3s&|x|l>=45u!JLl?1GJ6bV3OOYChUra$GWt&?A z6pb{fk@D+CAIQE~UDgh*n?Hya4w{6wibp#a4A};K4Whn!qg4})6fKN!Xrbs|4D|0Y z)md6K$p1_WTT=^LQwt-Zg>#VlTn_`4Gqt;f3L_y^1QUhv@Gy&ov<9Cly9 zee?KEzAriH`hCg$H};#Dv@vN!l1GP%4x2iRY+u-ZL;GRvW7@|gY>6M=c5G~ZbVk_f zu*vccKHz`JyUDw(eT?@S@7MU5nQCbqgE=y z{C?(5wO)mNq)?7Qf9$J{n@2MiDpRRH7HB=q8>y!>%=o;!9PR#ktiOpxd4o7oziNUu z(QH^fM*AH(MTpkVOSS*wIJBD}dMC}ixEjk@NQ3ky1Ib|0Muw7MWH=dN9$x*88g1VB zaESU1i5$#a#T-;1w`x@dYF?$LV08!NRs=JlT#+Als82$Rx50`2g9C#P1pqLo(Ti9897GhWjJrW8`ph1o=4m z1o;#>io~oOLSfz=e3rz#8sz85@#F;ZwtG!qJ6f3b}|(CDTZh z_v-HIKh2s|)1|!O|10y>s;g=vxruxp<=uUi4dpE~NN+Nb3?^-4C>chElM!n0s{R<6 zbXe5|*Fi$${X_h+!#w@rX8dMVm_dF(t~9U11#1GqRpe^&Bk~jSb8;>DXL236-aNA^ z3ukj`d$$WATSwI$&MPxC#pDZCuF%Ppr zMjK!)iHG|^#>fN2JOg6BOE!|Qx}{HQiiGAub|*bZnbp~V-wg}#TWBzV3?zfdU^0Zn zuPZ;Cr$NUN4|E)aj)Tx~5IPP`zXVmAA06OeNDu9c}vGGG0bIe(T-q*q>K3OJ7@y7FcMI-ee#dOxnm$GK>r-5z_~;jOh&8 z{|NJDMw8fQMjmS8Y(_b1;|yFjm{&6@pc4wRf!gv+8zgT!_=?YvHa;R_8hiyU-z2_POoOk>OK;*g*mA*( zHm!YUNpVF2e(&+V`PXMQAV=Bb*C53&r*%Oeq*lz-dg392_c3DL zXWzmVDru13WFQ$#Vg?p|Ldh^PoQyCpyg5uonI|&3sAw{VY)!_JeaU|00CFHXTB$et zqlFVrq05WpRB{G6i+qEeN4`nEMJ^!UA=AyO_pYnOB?u@*H`dyg*(gFOk>G%Sh`E)2$`zNX&I2CksXalF_YN6)gjK0_nL>NwD zECe}{j3T4S){=iMmW(44$t1EX*^TT@CX+o#*vB-?pD^9t=7Hs-@ViV&r7zi!>`x9b z%T_$84J02j%a$M022mbN4j~_wGSnV1%U9HDkCDU3;p7POAmV?V97#S&K1DuFK1Yrx zpC>1f6U`qpVqn3-Ddx!xZ*7Ko`2AJdOmY@Eo1DWu%w=u-2^{RK*!`6jZV+^(?W68c`KXL#$kc6!-n<%yp;%+-a zy+}?aXOOeVH^_P9o8(*M0`eU)-K<&a23r>{A(xWN$ahKD`m$QFbr7}=!q!39`aCzW zbr7}=!q!39ItW__Ve24l9fYleuyt^~@>#f9TI;7*Nox&$rF{M{7PcmMv#$Y z6d6snmi)ukg>hsenM8IayOG_=WU>bdD@TgG%?ryaVC9llU$P(BpM(xCoe3)!K12>8 z2a`j{ho$sjG&^D;{5adIU2B>5EiH2EAko_wC1Ku$ETzBdF`E=14% zUIMIK@;Q^7Mb0MYFb{KCkDsDPuUWhRJ-W~!y~#i_n6#0hWEdGvMwm5e%V6av7te;3 z3uDOEWGva&yodpLKgt8hfh4RveV$l3h*k+r0r?J@&R<|H zCYO*)$z|lbGfif;8)mt$XBj@!A)egd4BQ7ut?!nGN0T-7LbKx5m`*` zCre0J&@d4ar0ULvoV zx6@+9B9SgE5`;y9ut=~$`Mnkaixf7JrVtkCw`2q?(r@wWut;(7AidOY(yL*SLRjSD z&SH@uEE0r8g0M&s774;4L0BXRi%d_4MP6RK0Tw9?CnLy6GK!2QTTA|7k-|7KkxU}H zlHJJeWHQ-_8@wUC0N>ZsdbxGTDdh%b5F-1IU5oU>+SxK0-c54kt&DkCRW3Pm!ZYv@EIf zrDXxf&}A(7EIE#Rj>H-+ob?3qx8~({cBzS!e@9LtUmz!wQ<%3G$*JTFau)dpIgfmk ze2ZK_zRegGlJC$Zh4LaYl}sbk%?l|DFk%rdA(xWN$al&2%}nHc1(`v9K&~{uf9Edd zcZ93R)#OLyC*>;hMJWr3(!^y!^sE|_6V0KGMa2H`A1tRj3X1t zB(f{njqFY)lRd~@X5(A6Xe&`}Xe%Y=zGOeLKRLjxfcrr5A#xBom>fbrETxCGa(_xL z+DhRtayU7{EKXg8wo*8fe3E>Me42cY98W$^P9P_mIf(6dX6~Z?Xe)1~PDWcPr9G3J zMb0MYFb{KCkIOK6Iq-H0MlV8x^dx8DValpM*B=%3JCDCL5H`+n-{5B5Wj0 zA-Xi7LAsM3B<7VC9D~IOF|Pzhkdb5*8BMm9bYU^VI5Lq;BD<2^$nIn^*@Nt9p2wJ> zw^@St`!Ke?WIwV$Ilw%&pb8cve25%G4km|?kC?Y`^f7W6Ih;ff_Le_vML3dtl0-iS zm#0beQIOF`fzOlZsUS}@tKag3tyJT@@aqi7FPb%PrD7&oIE|cPo<`1RlC#L!LNyZ> zb8bHFGm!@AO$L&|q>T(E!^m(l!YrEKA7h0(Z=6tZWLq+xY)2-L?a2;g64`}(fb2#- zNG6kg$id`L@)7beayU7He4KoOe2N@JjyA8%ABOu}!ZCCiOFm1EBcCJ3lM~3_nl(6= ziIjgwP9k3*CzEfJ3&|965t&M+k?$*?H)CMI!VK~QGFz$nFUdU+a4VTl;vNWGa1R75 zB#X#maz9x@9ss9feVMR~JVYKQkB~>nW8`tNocxLWnLJ6JBca=f4WnWZS7+cQ^14!Q z9Kbp?;VrUSsI``(vPTpcM<$ZEZwHsIWH+)qnN0Q|dz-cMM#-pk-ZIQPhat8XRp6T? zSTiJ?CRDiZU{<}p5jrJoOU9G!$ON)I*?~+VyO0l%-N*;YWU>#5t8c_Pl*A|>GDi6z z#`oX|@^SJB@+oo@IffidK1<@4C|4gnU*PEqNGBB1bd3_H?k^5gS#wb#_ ziOe>u=gdQ&CEQBpli1Y(E(Ij^vw)0!Tfky+KUqQ^Ft5BGhdxVKMjj#$lSjy-SeI~`5O)t(n$@qCqb7x`NbF$)`6KcZ@^f-6`Dbz+x!$arqoXD(=bS)I zo_TE^YErmKh_dN8<0i@mwBJeUHwo9Qc~2B0#CNZDl{-nVCdi$nIsK8#<8v#J%hDIh zaAk@P{#D-51aZv@dXu5&HMO$L&|q>T(E!^m(l!rVP)A?~}YR|m={ z5R@?-tca8`9IS|3iPYYcIb?7ZxthcrGF(0(F^3F!E%|2>bI5SP-1%&jqzZUd?&bMT zJ%aO$n(2x25N;w5U`>ZE{R3D=qJMyVm_+{o`6!A00Wx|95IqBko&iM90HS9A&y(mG zAfsmh(KF1{r3V1f1AyoOK=c40dH@hT0Eiv{>`tNwfQ%jhoPl#mm%G~FEONHct^;#U z$-D&!9RQ&NU?3SxLI>ai9RR~f=m2Es04O?u`=}&z04`F;r&!0Qa8DGqGW#>B6%e%o zqEchElM$p3=Ar6l1;eI~&zJ_AJ`8ySN(F5W_SbE~ zemWJiWIvs=v);pgI+td>j{S75%#6c+I?z8|bZR0>3vtXWVH^n(2V}%C;{@Z7eS|7z zjb|K>GLG&N$BYva$BYS#BMMK?{Vn1>j5um$eTg`(LEd0i&02@M+to7?u*c87nN#!} z6*Mah@$H#$1o7R5TxedIE_u2-a~$G3Hfx>!i&C?;aIJXUa8H!J5G&O|Z!(Y!CT(OW z8AgVa5hP|ZryoXtEsP;sld&XbBjJwONN@l-keoukNKPd&D+oWB4+P&J=aFxcZ;=bg zcSz{;^b*l&5IPM)r@@tG?W|<9^1@Z*YVsrU6Y_I%E%|2>`@_Khdh_C}kto-*v${Z& zN@k`*lZ2Z{{FVsq%|Wt^JVYKQkB~>nW8`u3^2{9cwi4)k5^Vhz&ijFpf+llgO@QH?ljKO!gr0o2D7VsBPE-4fQ_=+HrH%Z0smi zGedUe67C@jadfm^Og|;&+3^dpR$a}Sg8jIJXh#tiT0Ol$uLDbDM=X^5|AwWN{T{s; zX^`GzAQ?>B$WStj3@0PVNb~xPc#MFBZOM4D9hpG3Cp(ZyWEb)QvK#pznN0Q}2a`j| zN65#>;p7PNaq82uf9AVS5DkE(GgidI|x5_UBaOukJnBvZ&mWGb0Pq96MGBTDPjTYONcHRt1%+ML`5a zKx8xL`@CN7Y*=lzkNfxE58G$veVsWobLL!g%{6o8%%P8j^pTK064FOP`bbD032C*Y zQ0MPI<0^aS>x_$e=f*fy_&{-NVT8*a{o}9$m4^yIXtu-s?EaU!xSem!C%| zUZUkcW$O^g^S#qz$#e6HP2P6@!&9%rvt)!j-ky8yoU_LK(&gz~XUwS=aGk~#PQ7?`?VRi^z{un8Jcv?3py?$E0m2X9ehhK17jZ&uQ)3Tljc zd?$EEmQ5tj=TEIudaS_aJ|A4p7|q|dY`fB{u=JMi)Y-Rx^6aIyd*}0wH`>{!KH7V> zS(0tbZn3kM9@N>l>Fm}2a`ruT_VU|w_Il~qu2VZXd(WxGrW1s3^B-Nl0Zsd8Ijdov zuv6R7jIgB-lHLQ$EWJI;bGeqC@EL#ivVEktdD%+#Wj?s9OsUS)JEqRktv1$!jP+o+ zNP8wsONO*z$XE~3N5Ld9S;RkFUd9~ZF?z2wkzI-i)8-*<9^yR`2k#N$Jwm)ki1!Hb z9wFW%#CwEzj}Y$>;ypsVM~L?b@gCtgrCcvIicKP3B}efpAzmfKtAu!!5U&#ARYJT< zh*t^mDj}XE#FK=0k`PZ4;z>e0Nr)#2Sp~6tqIr^p@gyOhB*c@1c#@DeXSnv$#52TI z;@RSb;#%>m;@8BBMZ8c_zzcCekAse;{5b{$KG&;?Knm;xELTM6BKN&1m9FOS@&Aw$rM%EN9KI zh~*{xds;^sPvwpKoTTO37(W>ghzE^Wr4=J5W0j0GPF5T5{QAe1W%9kIBMZL7Q!L|n zu~}>pTSe^avP>H}4Lu!w-?sF0z8vH%yPR=S*vQG=bNE;B*oyTJnxqZLn{&)sN$Jt>d$x+~M~YPqz0}PT6T~Hq1u%8&7$PR=0WaOtb8# z;6M6@mK@?)*RdscVc*|}*tAn#q6KbRV!g~JS(A-RuBHXPbBfIzJj>B7{{B;LVHacL zcK^^Rukm&RmSxE`%IzZMHcq)MvfLJJqh>cITujXx@kA5o@yPR5@;|oZLFGSD`A@X` z7u}%zV=ezhuV4W-F3D8>tCjysYGm7zd}`#KCD&0S7G^yrVam0r%*q9=T&tP!p%xZ3 zTe+6JW#tNG-jehlQ@Qw7D)~oSEiB$=rC5Bsl>%BRJS)ZGi&TncE5+g~trSaMvQjLu zUhdG6mw1n1G3Pz%-0V0@mXr3OC2Op2TDXn0kJs6IDaRIP(t{jZoKFv8Txn$g<%gDB zL=NvQo^JPf$!gw9$9v)*o(umDi+C>l!*k)8DV__&bAfm+5YGi-=^&m9#NI(X7l_q^ zcpv;5rc$O`DHlIzrCj`+N?B*6Tv%tNTy(RQY4M9zCc>0y@spg#+by*=fAQYpOR?13 z(K58PJKM$$i`SaAF22b|^^0wf>UXT-;3B8C^RmO-A>ln(sQcsDo6~YaU~EE~e%#OQUe?SNUK?=G^C4*Vl9oXeM;j7 zrLjS2+@f-{sT{?@G!`=NMj8|C>bh=M8Y`?E3!hUOw<`_X2|QuZBBjAz-jo0@08&d% zV4Y+7NgZ5|e!kds3w6Dv`Y`EjS;Tvclw$*T=L7B`V}^xuydU^4QNMTldls(u?xjYz zlEa>^4e00JIuE0tJGv;PziT0TpZm{uccP#B7q#~0P@^0sDu*9h4htVt4o%hq7QSfZ z?p|v-bls~Qa+Skc<-HZgjt%@De9}VXk?b zT@~aR;ao|cL%VJw&xqNkYp-|D;r@)Px{3Q!kK`F=!tC|jpBeKmphV-l%P7%^u3O1@ zHf1SdRh6~JkHo{`5%H*zbxOuaF-jaEMvF0GtQaT8i?t$Jyx<@uHLmavbzf^wn+Wr? z39d9UBETjvA~60=#0yoM+0=C#_h?7gQtr{)U9Z?Z>MG?PZ9%?KF*k|yBnysWH$Nmb zdJ@7fD!j!%yx<<}rja|*{X^M{E^zn%0< zd$%pvs`Q$ap7kPoI;#c|A$hUGwJ!<8K zlsoEC<+E4U?vc;21>d$l17bNk?;y`j3p&Yj)5+cBxpBb?@_eWBZt{GVqx4Xnms$@6 z=|4KTgH9N_HG)zcT)?{x{+9AXc2(`ndemA*o zg%9|DUl2lWZ_WQ1xxI_zIe%lv9OY0-jg9kAg3%J(W2qbkdAX`?}_t|GU~p6 z##6kHw{iYjJg$f4CG+LThdLZex21zor@v+1{nX*c`A7Le#)k{$@EwIM^VfRc=c?=* z6saBO*!M&_E})G)kK}*-w>$3j?)0}4zSsYdqxbvwFR-33d_MjFF+;H!k@Ky0%I&y~ zug1Ji%zya%;Xl#H4m37t{y66Xo@tHjXl9IJ42a=kv=}SKiwR<)m?S1U^|{wu{dL&8 zea3V#L(CMZSLF3#qu3<2J5EctJw=7AXClsN;u+#9@oe!zajp1O@oVD6B6o~)Z+0TW zuH}x!cu#Q03g+zR`CVH3OFX|b-XZ?cN$jX%RgUpK@e%O}@kx@aFZ@~Zf{xJ|@nAb(rr*(+h@D&Q`0ADVi% zJrRKiL|VoCyKSD~SS41Av<>8SwnsUu6OMIE zW~Rb8Rh%YHXC)%*)x?=%n>bsXD|U(9;zDte$m)bL54}E^`vY2C?wI5FnLEd9$Impb z6jxEJ?K~qko-LkZ^mxiO-+KAO^A4Fu*D;g1i&sv*hq(*m6CyL~^VaZ{ENA)?*s0;z zsi%1!^J3EmW_FDMF{;T5aBHzVAvR!;z+#$Xz?h@bkkDa`nS=EgvuVGfz$eL2fno{_Y%5zve zA|4fw`R~uUi}l^ck3}DwekGPVZO&9Ib?(XQSl`W2H&nD1c9B}xMR;snBjXxllsH0+ z7GuO%F;2wRw=d#dn78Ldvs%@dBqoa~Vyc)XX4rKxcWcZR3&mowOe_~G#43LaWvf=0 z)v9xEVhm+vtMj+a31`i!|jvCeIw;RFNkS zgr}nwx3W&jI8$sBXNz;iF0or&C@vD2vm{@}u_t%X!|kMmr%}(*SOVipah3l(cVxAA zws?+su1a;D(Y^_F@*3|N(|mfzciM09zAp)1hHBqny(5zAc<1s~ywQgV-{IfZzLlPG zHmKkdc(*qEG*?*HtV#y$u3}VS3mJDLaAeJo6GfM`sVMwrH5E}-uVGtVzv0)G!2C-of z8wRmq5E}-uVY8O9-=Yy42C-of8wRmq@HL4TIP)hz*-D*=!hL_DU)~-E0{7V#B5_!iHIz=qE%!VT0J{ z|FgIo8)h62&*E)*3%7`^;smQ(Y}lb0&Db#GRB@U(oxGM}!;CY`Pd$=Vx7w^}?E9y-bevnza<`b^DBFtXI;^ z?O3lj+kSxcGCmqQvr)%G*2 z*C9$per?faz2G+gT_ila;V9M%Zg(;%3D%2}prvh3V!f>WI!*-E3x`s4#5fp>P1-W` zQLN6PY5QqC??T^kre2HwI#VA-f1PRLY_vA*QMBNLX%RNEoEk-aA0y1javI+X)v4l%5FS{!a{F^$J7a>xX-lykF;i|J&zD=bDbHfl zx!Pt@-PO38JF$K0JWG3O6=~l;wUqjem|9G#_f5^Eex0f5miE*{OM7ZG_4wPV;nc`M z&Z0)9UCI-kn;mCz1C|Ps_EBb%tn}wu=^>?`dY<<^$C+Z|@7Jb9+vseX&9phwqM3`l znd93S`F=R%0?L|^5KCD6%7LSO0TW3l>vx>$@ zF-jaEMvF0GtQaSI*5m{@2q*7$91>vz`i#Sna#EqO0H)O00r;9Vh zS>kMQj@a(MM4Xewd6IO93&c*bOY9aGiHrTi%_Gq=OCM_lmx{~8<>DpcrQ$mA>*D3& z6(Y8+buv0~Y|^F9AN?Z}yYab<_lb{)Pl!+YAGRK3+-OIi7M~HH6`vC~>KlEV{6iB~ z;&T}{i!X>TiZ6*Ti}XmuVRb1fZ&mnJ@pW;V$b2Pn-WGR=?~1#`eg2_|@8E424~PfF zk5rPw;t}zv5xq7>ihMDX@CY$lj1gnSI5A$#u)Oetj94jHC}M{Qmx)*)!WAMGh;X%7 zs}$<2RQ7DP^&p;(k!Q1zXS0xJvyf-AaEX(VHI9D4NWU;)19S2tCSA*%ym6(t%0HCV zi9cdITRg`|pJ&8clwD0LynVt-TH#$2Z>AMmnE5eU-u?b>C#}b`I56RMJPYF!A}g%a zE)Pz$dE7m%_GMi=Ym@jqc4eHc;epJ9O)nt2-OA8grgJrR=I z{GH=7t>1B0TE8=Xx%E3O)~i3+V$WFjwAi<1x3w^v>F;itXSSeazx6xBq2Gb@J1xmr zszvMA+X5}w-OL_S_yIf3tAO;QEoa#ra7!CKvXLISrOkYZ=3?(ocr&?e9e<1UqvLP4 ze!2O2j~Tj_^{n7v<;D19Mo~sqUcd}7Q)HC{5>`pTMzKjG2P8aKg=dJYO(4wL1jyP1 z$l3(R+62hj1jyP1c$tV#(af_j@qe^J;db#3@sC{DQhUD;-X}gHJ|R9S;u~=EX^|@< z{H$m`(o#k|N1A(Rea6k=3*w97OXADoD_7_Nb@{*tWp~%){6~dqu3;l7n{Wvu~lS- zrSTA>lVdH#j82TqwL=L`hg(7pUlnp5o^HxWpwfZ zX`U{w6jxD(WOucAws?-n3=5@VhNbb4Jy&hMoYt@hANw(XU-J)X`^G0kT69Y@yVE8+ zi4Bj@ivL*uZCdf;_20HORzH(cY$42(jE0BY^ZlPWYw&u*PAc~JVt2zKp5*Mta@^y8 zBk3UdJl({ae1HGAw`k*!ag>#4_1DtI_cz$uz}K3x&AV<~<8AjJsJI4I_{ut#p)h{f zMxK|6Tl^gjk0|GGa^6_~C^>Iz+`!!NkOq4u@_xfXE31E?aV6URLBn;{gN)mb4qF%< zYq-vItl=foF~~I(U&CEK*0_lDj*WYT^o}*I!&-dQz?a#%!W+$I)c??IMq?AD|C^PV zb2H6mG(?!qXk3GLOg{7&HU7SFC8YFzV+1L^KW>HHnMQ|_9c~!M**hDSW3O)ZkBqyQ zT0B<27VqC_c+plyluxzNG}M^)4^MHNaraV=!}Yh>IAWZwr+a0bL!BK&LJ7vzQD^%} z=a=|DTkCJe|1q+*8M3w+vbGtrwi&Xv8M3w+vbGtrwizb-H`SM7@s5-o#^M?IDjcNk zK-v!EX&tN=>8A)ciS3khp3NIU>^Sn%#52TI;@RSb;#%>m;@8BB#mmJ1p`LS@=`r3R z{?UJ>-e!7?_lb{)Pl!*7Pl->9&xp^8yaB{n8>y$;O%ul2J1xe|;tS%7;!EPo;w$17 ze_LX-=?w8-6<-&(iQC1u#U0|i;x6%h#~C{rySKBU4ZCMNARZJyQh5%GM@0M#N_Y%i zc!$|)jLh_m)l3hJuoP?+4vZ2*Yr4&JFtMa z;Wb(KUT1jG)wIjI8`j&b0rJQE-Gm<(pAc!O)!WTh1YX30v9mUbn~8tD&B4Hz{R3m~ zG)pp$)oYdxCaMkF$2UIwzZIT^A9bjjHvs(uwNIG`Rd*X6)Z4Y@(;TjxL;jD~K7~cy zjiu!YdF^5InyPJ0*w&h79KMy|Z?8?X8T8uwiTiqOgIU?y8ul)9{kICEup7pJ7%oPO zv0}WKASQ}QVzQXxe^h$|<0d2fLqPV2fb0(e*&hP3KLlid2*~~rko_Sb`$ItXhk)!4 z0ofk{juqJ-f-w6-K=y}#><$J{UIRxL%?=_U(IpGO-A;IK*IhI zko_Sb`$NDkk^LbEvp)n}409PbS(yDHAp1i=_J@G%4*}U90r67Ao7M2a^7%)H~SydlyUF( zP{P~%T{Zg{wePIuO;zy@@%R2wV*WwgNJ*|_*7-2;_xZ;u)d%7M@u0}~o;Zx}A>(_< z_#Phf4_DuVEi!&A`bKP#F(3{RL&ahKk=k5*Gb3{YwO88Q0Ay|eju4p}Ak5qVWNrX5 zHvpL%sM&=rqMXh6Jw+ zPEgEAl=(`$GUHTnnmApYAFrp&yhx|tq#tztiG zY)#X`Y(}zz^`7Du{0T-u{t<{qSH3}sKB}BYdb=v^eQL%p%$u;bhxwyl`J1bX@k2&N zZnqUQRWr>GsanE$yQ6IHncWp@@M6-kmfAda)e`fysu)iaZnxR&s+stDhpHyy>m96W z#?#qeg{PyK5BM9aShYjA)I64|V#>XvD%a+dtJ1yg{?@9}z)IpIV(%iccYGrpdiZ=^ z?B&d{dc$9FCh~ABJ#sx?4NJ8MwrZ^JpRE}3k5}I5zgw%HQL@0k96Idbjb-~I>XJa;SSJuUvfyJG3!W6VLE<-Z+yp8rBQTJh=s z`p+qiJj>r-feuwX6pUGUkNve3Ec!e6dj4+pX8(fM;rEm>k2ati{>Dh%5C2d_oBwtN zJHDVhoZ}xm@g5BRZ8J1||NMg%kFsy9nA#`n`|BSuUF+4ijgdiJuzLMxMUVe(1-p9M zIYAxiJ4b098~F8EA?^QCo8Ti**Zv%R= zF{p1A!|scJfV%5Hr#N%q%uj~wdeN_AcF!uW^mj%sRgBWgtMxantknM?lD%~*uUB1c zi$td?Bm9k(Kky%?O+8(?rvIGsNb0w&iu8Jdxm2zR{xw=Y!MgqAmzewF_#blLcK6+b z@@x9y_Z|PNzh(mlAM=k?UT-#Gu*^xW?2QwYV_S6Ge<|3;EiN{vSAPeeS$XB)U!M$< zPb%ka_y5+bpJ~1IX6bG2m09>NLB0Fr`Jeu~S4WQwI-fi&E;@`2IPo8CVO_soAN{1> zp+h?7U-h~#6@Ra_UD7@@&<^#Rbp0)rxB0JHp43-N`4imx+jQ*p6ZPF>egFKItgOL! zeKPy&4{AsLht%8AimCp~1J~FWm$tO7Km3=V4iTTdeQBrfQP+Lv#FwFK|N75t{U@dV zBX(_NFQLaj>pONrKlRk7+d1s_o&G<2+eF`qgZ}y-R6K;;&^riUSV@|ZvQ#KIG;u8PI@=^IM=53=703B_mHwuU!V3b^WIOB zn#}q_{~z?f);C%oml*7${oZoZw=+*z(cOQ3ZX|Ydba?;o69SRM{gi(R*iB3SpXla3 zbaa1YOB0)6@xOzac-HYf+hMc+Ol>i!7>h(a2;EE>uCRH zv}@YoiDwM_;<|h5eL&V1`v3X;ah>0~UvV#U*STMJFL&3w-*B&Vzv*7(e#`x~d$s#r z_Zs(F_xtX3?hoDT-5cB+-3{(9+?(8Ax;MMOa)0gK>i*Wf%e~vZ&%NJ$z1V=iH6%Cii)Fv-^VkqWiM@io3<#>b~l}=Dy*+>Avm0-H9Ui@e3&67Ljmskg#A%{$Zkl6RK3%3JN7>z(JF@2&AJ@GkV$dSCUv z=3VAp?p@)n|D50OZCPixGF%)YhKliGtQaLGi9^LiahQ0D*a$smHTUW(&QSLP_Y%H@ zc$xckxZeF1yxP4Qe%JjjUzNSa{T|_K-D?Sd-~GN5%Kf{J@Q>UZk^j{FDUzSNKPSAw z-QdJ?M}NUl?kSRAxxeCjvbVUm5a-t}UmkLQ+T|qrV#&q_kAS$+AE;Y2SDd8WsAd(o&YzM`A$Wg|zg_;PN( zm+z#YT?I}U8pij0(XnE}C0+?}%Di$U6frsh4tgP$c>%hbG zUDgT6un#_y&NWu49AXK z&iz>Lev^BHB_aH6_uGUAbTojDUW3GRG)y`gBOMK3XMTwMdY6?X*qa;RjnZ1v)MROD z820BTzA!tWuPM^h0M_VM;+u8`uu6B4uW4rp8hIa*`_aoV>1B-cG8S9)XX0b8I2zQ; zzq)@V{@>hZkYl&_$}*M^;J! zsl%kH0qJO%baVvv?ZaLj9b&ePyKXufDjoH(aLfZsM~6yBqokujy^NDy4#nc-5GNPC zjFeu64${j|>7|DaEGOkkuM*aIb;KWwUJgYsTgVHmh<*gM6Pt)WVHJ^J7m3;FErh0# zQP{&3ob^fli<8a`N9WK0=~+<2hGH4lbFS%GgfxsbduUjMG%P_HmM9HNkcK5n!&0ST zY0|KGX;`{6EQwlNZSzjfUD-EeU#B%| zU7K+&p-$wF^8bnq$3nsX(zj+*WR|4gZT|&hh@7FAoMkC-tT#TXuP{kwNiYrjt1we< z{AaN$lnXhLcJEo_->Lujt#mEV%hMO7x1~p?ZA;ysx;^!-)az3clio_WJ@#DQ3%;wNZfx8ZFj%xzn1z#_geA$l=vC5-cE=>gjp8Qp1;yVYZWUh@cZhowXRr9a zB>Tk!;z9rUq@(U3|Hags+>gY=;t}zvcuYJl_V~}HUhDcs&k=byD)lBWL>wZ9iechV zakvl^aP&iKH%_&mg%_+$DB4LWi_aX`Ny-1iTX8D^_?(z6uB)RAM2UEs*d1Aiv zCGQ!$b0_YQSE!i93YREmnIz?6h2&KVSBo`bonnp^TNI~NoTT)pD9$u-x;V?~&1)0+ z79`<0I(M$vE_Nv90eEOC{tceRe5tC;7B=Zk9; z{{n?CRQ$E#MUs3?yjZ+M#P(A^>lEj5rFn%B4<2nqqo)TonqS+{=-o+$XtWWHPP_<> zwlEq^c!Pf^{z^33BxrQP8>Z37(P;Qv$=zG+2mSZr_o2~y5^hDKjfce};!*LKcwFr9 z-${B1jfQBnF<^2u+BifE6~n}#;&3rS@go%;p>UkYR}ndvuOdReiU?E0R54A=6tn!@ z@%zwd%Qx4F!#mCs^PMvi7nw$**X(pp3TQOp62)Y{dnD|457B7k>~~L?{qA9nSf`j{ zMKqc?Xf&Lp^rt8eUq2-Nba9r|8yan7?|V2$=gt+|#SX<>Aa+X9CGymh@-LNqng3o= zAsTHGG@2aGl>97lm9BTSj`G!0V)E5fc)o~66aNB*FI0TK-$My6l7t8$CtXYFw#aG20;vU7>E50wue(`{K(0@BV z70rDwz7)+h9u|*?N5x~}ak0nW=-q_oLNwPHFgcoQ93qB_Vd7A6xX5!o;zuexLg6@( zuLp82Uk`+QJrJgdsbZSQKHJE%>??j~uBDU9DD+0nl-!74dve@b+2T9VT#IS8_)av} zVwOo#E}AXA6V0_a)nbiUrca--+g0oXeHw6-Kl*O?ui2Z!_N|dY*fMzcc0r_d;>4 z_*L;D@oVD6BHy|r=4B#Zxg*S1?%;ay8zN7Bk$h9UO8l01wfJ5C$=C|_8il_nUMtD> z{XcNlbqfDT^6SMPi#Lcr5pNWKs`x)wc!R$qI-OQ!DgRQEn?-A3srb^C@~p%1#)>nM<4Wm7wh5k86TG9QN{d=$Q#O> z#T&|yHYUyP(i_3&C4WIFzpU^p;ucA^im!@qO8%C(Ly~tD-X+p+ksAFL zd|yXdnM#iP6+R#y^xqn>0voWC8v95*EFKY$ipRv`VvoN$whkKru>r<_$*}>(A!4W) zCJq&cixG++sqhGe<3zrS$GJ&jvX~;KifLk|nB~7iOumXozPbLpF*o3k8S|YbF;{zh zH)zC7*Z@n7?*K?yPBF)dEsE1BPEz`Olalz;#OdNJt2bAa+X9C3cHTC12)06I+1|uq#@r9M6=T=ey*%O4qwuN6%Hv^ThMT zHHv?M!WSz3T9Ie4r1>@RV(}93GR3EzlJezB^9my)7g~gKF1^&IsC0U%J;QIKmonPe z;tpD&g>7tc2d&D&v?OBMsNxQdD(;}&m>eGr$$bj*P5}}dRop=zHpIJ?KFo*@hU7Wr zwNcz8@?9U|vtv5kD!wZ6ogO566lbsaz9jp_1L8sd#mKAB;(tc2L5q!t#UtWT@tAmA z?D3xqKaLhdwAdIhIa+KSB8G}#;!tt87@_!)3Xf1Y#y=F1>BT9`_v%Q2@6|!RR|olC z9prm;knh#OOfk!UCGskC*>cZyhDTM=pBW2vv_$eUh0Db%v0AJV`Dz{Kjuj{A+$oYz z6Q_%_tY*+t<7{z`;?EV^#SX<>Aa+X9C3cIfo#yITg$#M?1TIxfzLz@uHZ<4DaHb?Y zr{?Nb>AF@+ey-x2C!Q}}AYQ1LYejk$(xF#@^eT{E1+LT4%azU*MvWWppvRh~9%~$2 zg3fLqzM3BEqoIfBv5afQuZkCmUlT7DS;I@5%S6`j5@roATrYk@yi)w8c$N4q@oJG( zy~Jl#FZ{kUJiNyJk;2!DKNfEgeN$QRv+gXM(l zM84=oc)j=y@k;TV;#K0e#H+4~NnX&=E#g-3RdI*- zzPMjJAo4W~?@_nM-xKyFEIGuI8zaOKVhpd4C1ROPo-O7(S?TCIiO6V_n5XOLYLQVV5=NbnQ761WWQ<9eF(zb;2^nL; z%XQWjMtWxInWi)qTNhwX@vYDss56MiGt%}EciK6kImgg7Hc}k2!A6SkImbbQR|)Yd zA>JU|YvfrVaYN3rxFP2_1z6Et-c#1whwQR`9@4{ys)r}cEDNM>hxF(0LDplx!`Vjq zb0p6x1>Vvn9p2J~d&IpWmce<$EQ9lgSq6w@fLI2IO@P@VHi0lU0b&y%764)aAQk{( z0U#CtVgVrS9n#(*?H$tIoj0t<2w}F}8LiOP~JvzT-*8ny}&%)MoI`_KLsZ- zWGVA(8LsVyn!&1uP>#Ru*^Z|Fzz6H!mEUELS^xRKT6_B_q@5?Pd}GUdixgu5*Kx)Z z9x3`C66TFf=OFJO?Dg#ZqE|xn#%4%`(&bqmv0e|bPqM!wkV>pQ?w5%5LWpDO2A<-H zKl}N*5AmkJoB^X_rp5!fGi$5Q9smDoF0 zf%W!2LEsLXJq-MiSnoQ8mbznFlfvC~oNPFhJPvV%)CuV_7wA-w$4k2AcOiR-GVjIi z{ob|Y@u549cw>6c+wWbe^Hy-)R%Zq0{hjCI>}BfQ%6ac1VV>UY=Db6JI_31f`YgOjI~`udG^h&q0a3dyN0@@ysMTalC6 zyW3;eP{QmQ%29R=wL8ii7Hh0F-Eq`rq??Y8cj74D|H2CIPSV&)s(<4iabFz1&jW9@H`=^tLV4?eQ!LiO5PN%s8se#gyICc8 z#?EjzGegVXJkB*reKlHmzhiq7L^#WddmkI1qXF#DYUfX)s@_%*W7&Doq#ylJ-CxJc<$shDUBMjNNj)}cj2%TMS44(8ixgilJQoJ6VH+N z*&T>FMaGMO&?Vk3-iUFWQhtrFcW|7WJ&XM_)#CN`4vzB!dk4q4+TOvTM7)E;Zavm! zOs6)oXAgIodu{b*dkycPR{u$OIhwWAS!3_uIP_!A(sjVe7n8+wF-y!8GsIjmM?6U! zDb|VwqGvkGT9$)OzzO3rV{&2&Dr530+@JOQ;qYPiU{6FLYiB%r!vZ_7(aCa7LRG3O zYHDiZD&rCp!w4kgRoB!M78MN%3k}upKxI*3VNo8xLNl`yqMl3s$Iy5$CO78o!sO7f zyq@EEVZ+1Xi{b)1rVLLS?u91|&&jSU@X9~_YeH+1>$xR4ZgxrRlF%!ccbo5HY0*jrCc&`w-KXp*-qQC^_;eR=6X8&1> zIP1y&)VllSr#Mpw$!$%VF}9cZo2vf2n4(l76FCJ)F1$NS}-#DApk$^zf)m4^oAu2o5`mRL8` zcU`Kb{@5G(W4B*4a9wxz$8L6J4623o{qphbC!*Ty`S^s?C%d$c{?u>nkI%QW2FbtI zpX;(IT%@h^4qW9u=+yE4UT<4*!>V(V@~R38!@{DxgoLD`%7oyrg!s^~BwAB-Wmryi zlUH3GW{s#eA+9neA+0z&(mk`={cuQ5eq&)rL3Y@KVI?`qRimc4-s7Vhb8|yKKAxTt zaiBaVFJ|WCmvb^k#d}F1CI4IBm_V~htvqRi+p)Q%aHtn}E3GgxkU4qWU-G@^qJo7( zrlnM+hS09u2^7_-rO;EnhiFsf&UkEql_RFArj~-xuq!JP!{P}hR3uW^(7d9=#EQy_ znrbd4FBfrqXk2ZRS5)fpD>R|DFg@V8vH7vJXE&_5!Yi0oyZW4_FHQVT^ys3{S*Mh> zm1dSFyY95Htr@O6W@1iGc6g4PGq%9JIjtyCZQX6}%5E+X_fDJD*g2-yb!W|JoS5!< zTmfE(J9=V4Ocx|br(QI@<5v-t^(8Au&%0pyl56MG*Co_9C)F(b z`iv=8p4FV0RYOyDLg{F+6IY#+;C}8B$GI2u+|j4F|E|>T?2#jTHh;dNn}%B1Td)MCF>3tQ+^r^i zTYFzC`=o7f|4*=$KU~tBZ>_wqZ0?(bO@SLw<9^#3x0^Px{x2t1>WOxs_kOmZ!DF0p zU$8uZ0d4+#MZ0#8Mt_k~x*wjP;i^CNptNy%v4zgH(&V7rG~eXt#3Iv)pw#-MpmasQ z^z?xBV!<>P&=b-hIkog8Eou!Ww_fZGr8RI?i+Zu(SsnP+I?L{;daywYC#|ozJnMU@@C%a>uA^quFPiEr&Nt> zaPb~j9PHTg8b=|(nD(VL+Y1|t6n^%c;Z)Qr`6Y|#^lGm zm7W>lrA61}XSGMq``M}~-(1}inG#d>_V@**W9F}!yyUuh;Yl$UCDYxRN1Jg{QTUG| zq9X7MgJqLe(+;K8e^+g)%(8SrdAIY6L2}dUpnOH29B+kd?3Yipon`vdH(d$FuW^fI z&n^9Z)Jec;aGU}x}I(%KwKFdx23!A0PSe%Z-MZ zrTn`ztwkEx?fgWV6O@~#1?4MD9&FKWGS`V8;c^E~sPeu(tkO-kn(nLKV1;`>u?ju0 z3j2F~)74sZ@)lAqbS7CzoOX9Mf8~3)_4HL~wx;s4^DG+FF?~w*gSw%ATatzodYu$< zY*PEQGBIvDXeEtDI#K;aP`cs~lUj|`Ym^d{&fTl4LYf?~7O1NV)HqZ6th?1?uRJs` zq10lhpAb7V(7w;4HIkZT52n%V|5<5ReT|( zsZ4dlb8?2yjE>1pjL&kttPvrRL&C#cx2n;-GpQs|MlW@Ade4L3y^wM!zjIW3bP?3) ziro}UdT4k5lcSDc%c-O!wV}CYnWMZgN|uzANk@@Kd5dgACmuKY1o>N#}lyFgaPGS7oxWd@-&uaBIKOSEe?YbxB1QKg&Z;Tn8^nWYHMC7E8 zPPr*%^lY zy&mOg?{W6PVphm4vRNQzSgiZ}7dtp`rS8vD6Jr8HCk-9qg_QQJJ;CY)uiM=jKXOuI zMr2G%Z1K#G5C7W|_0~K!OwBLot@)e!t=S;C_gm+Hn@p+8s5-kN0o&ibwz?qU-<7t^ z?dbX4r<8Gf-SOIenyGaTCf;>KPluoP=vrbm>)$$fL8f zqr;+yef*+bE8nMOX68Yw-ysZ;i}K>)<9oYio8gG-^Rrx9a%J4$K0M%c_nbQ{W|$kW z?&FVHX+z!U+?Wg93nT3+)s@^^knVZWUUEcIM9;jGilA4O#_;jYnA~V9c~3YqjP6r| zyY7!Sako_}+DMss6JNAkgWApKEaKq7{1+~6VCbOcg=PoKZsQ!DT4GbWoek3LvDg}F zx?eug#xDKw&5IAlU*`Oj^QeQK4|HczY{uKo#T^cs`}i-Ww21ZOCUDr{n+&*9JX+#j5B)%@a-3$Ex~bmf9kBNtr0plMNKR&MLErl!S> zSy|1?qnp2cdCT~(pVivBYTfvzudix7sk&=bZPP_78f&^%)zz+D*=(~7$`hTkJeeW< zPq_}7d;X6Fh#9cN|G6yQKTgz_V7YGO{;%M^+j#jo=|xa(eqB&Lc;;uX#>PSU0_RT} z&6YAY-mBi)8D3Fm2a96Yas9YJk$tb8;&4$8Zn9}dVhIu6P^iT_Ane0fhn z`CNPx(_`hYkxfvp{QKjZUlo+MJ5LR|KJ%4=@@D*28|?Py~pkP`_3CUequSE{Fn1<`_D%w1AXNgIRC_Q^m2XAK#c{Q(f*y@9`695Bxe*YJjpF8_zx|?JGG{#xXg9_LwoQJv5U$BiTvxCGb49q|#}n^!+n- zNr`gNa+v*=uS}oKtn%RLj7qw@%7oroVe?WeY6At#;mvTpM@pKRQs`+Zo}6*!_3a}& z)-TMMnCafwKauy47vhcXc_Mv6M)i`(On6WF>g=j7=|bP&R8jP^xv2g5=fiA{*17QFgf-ir!7sboKX>*9u=GMN$0G* z$Spp(xoGaBk<~R(<}y6#oi@F=ab$w)_S_k8)t3|oV!8jCIkDLhd#q6!2+ke_dz3IM zUuK{(q2Lo;i?=_&x;$%iV^#LzZ_F#|>tb?RS4>P9Rhp4kmYY7J=kibVHd}`zrJvMV z&<UkjG}}IkvR#`c|3=WNKTFj zt3A0ap=4qy1O4bvRImA!qh{9?&25g3D#`eA+NhMA)T*2>C)DCdK5E*)yqvql%bq;8 zaBQLJ!XvJSr7&$+gUzsOnfw3XY6h8;ent7B7nU?&U`|w_|K=LqpA2--Sn%NWnn$+| zi^GiRY_sb&cI1xsN^R`;JC3^S>AjB~AfC}&fSRX0;6nRoyk?4ulJbfw`{0T$oW;Ji4JIKBlmIVp(cKZC*m=sMo9=t8Co4 z8k;L(6$-}V3zTl~6NN8U(2oZ0&KD}P8$EC%tj>eww>z;ucqa;i(h*kUz0$D2BHdrKk?r zgUXz+fZf?B&;8Npx!I}l4Q3h^FHgBVt~le*GbY6wN>-UIpL&)_X^Vj?iu5&3PQap zq3*l&ZAI?K|LC0-$?>w;Q1_TSuj_>u(2c&-15S2uUbObJ^SIt!G37IJX&P1Z>(9=J zyQrKr7h`K~XixL)E++aT?CQ8%Cbhfxzq*Tw+^@m%iO$WsUs>F*&B2jtob`bT@z#ZN zqXv&s!@}&5r@2-A^Ur=xM(BuCn$twB-Yh z>5G)|#QD--d2Q6e(>aYgo>grJ<<`Q2@^0tHgXGqhg7Ot7$n9BKP(IiBYj6Cic6a*a z6P%tw>6jNCjNi`JIqmKT^EK}~C~xLDw|V`@14HH22j#8oMl>L|(SXU#8%M6uKxpsr zse{fNI6kNx9JkTHiRah4edk-gedQTA|HN|jUB|%jCcYrkTRv}dz(xbXa?f)P4Z2Pn z4Fu)moz1=Ry%Xwdva?6e_=5HF=l=MOeBpW^{l5Ie0?L;;;^Y1y@8`6NQmj}&J_K`A zm@cOH?0+^>g$D^Wd6fX8hJ?AT|4fVxM5RaVbSvWHlS|T)M@M+WlcFhNH&9zqzq+~ZjNH8Dl6h;|Gg9*-%tOoZM%E08DV%ok zgIAhe4P!QKKfdkM!6hQ&z7jPBtVETWf&TIZ2Fk}AGCRiQ=}K%GE&cZ~@k1!+fpJOE zF~xCF86&(Q!z022>9r$=W)I6P%n6UmiVO@(7@nGwFk)!vi17I0_@{G=G80M{jLE#7 z*{Y#2L%+urR5#ic6rSj>%}voxY;AJSnCq zMpI`Yi49Fx^&Gj}y(_gMC8Z*zXO^{kT^p@_s(q_tpuYdhs~hA8{lB@;UkzA_|2Nkh z@J{THgV%3X;a9RED_m?ltu`n(s}PjWbtevzo7M*9?e1jR@?liwwP-EBDp|f%TZ`uQ zDsN5#+AG~XK`X+Tc153SDr}-PkvVLO9!k_OPEUv^tIfT9Qd?qN+|bDMCGEb@bPJXM#AUPo=jhP$D7EVh@8k3}n)ug=WtW%rwrxm7+%fPATX|~o3 zuIHL~o~!4fwSCRep3ms9h+3m5e}7vf5FZ*JU*6lXzihqt9Vsax(V0w_mgWzOpEE2z zfu@-lK7YuF*zmaG_-D$;=jD!PVV3pw>e0`0NOQSNgViHe^`$c$KMz|C? z(A3;~L0xP^)4q0@jtQ)qC(u+^XRO-V7lD36ni50*(nd25C5!8dfpni z0iTiK;3}qt>zd@l{a!WPJ=xlQP--<`(lDN(K1&UFe7j_y`<@rVDlg_}a@15A3fT}n z$(EoM724pJ0aB5sxr56WdFIkLmk?!rgRK!Ptip|R7X{cYZ+6Md>0=juxgw&tB%?8E z=#co7gv843vGeB86g-@|>vLy}FLJ%nStm7Sx^7HrRBq9viqZ+ASYnpf#FR~P#?XXe z5m|0Vd~Qv4*V2L+GYX@#dD7yJ{LmedS)Nx=kd;0>HEKwWU6;xq$mY8KsH?5JB&T2M z^-CKj4M@-ImriR6N~cl-5A{lI<kUKpIE-rA=iy%ybafdjq{4OApUX2T$Gmb(W#?Cv`R{I9r!6Bs8e4 zTG49VQc+?JgQ+5^v0I74+oavy6Wi*#zf=)%QfW@@@UYOhw?VO=m_r!&ZUyjiPKsRTE^@tsa|L zbISOMXVhn+)q}A9sSC3T)U%sC;oJ#@la`LFJGnGD zDJ3Cv*wErpQ4<%BYO5U6k(`=3ZB}&S`E#q5&o0fa%Sp^~b0WhNh9xJ3ty(!@Wqn0{ z{K%?gYv;jyeY9Yc>{p$0i&^}j9IF$QhX(4kO7SFrgq5;C_9W-AL9xU8V~^{NZP)Zl zf9x9P5%Tbyk<@w}wO%-|?oG9gWej@h!8_15)|c@t3fjWBpe8YY=z5LI$F_cDdQJ;Z zgzZk0aVKO4zZbx3ZjZ{DfWwNNQuZQ zC@)NpNQ-u}V`IjxoZPsymeyb;y(tjJ%7F?8f9`~}1G-CJyxd;f;B^JpeX)!1Mh;$b zaLX6Jq~2?q&|CYgunTJ27rzdzBm08)V5mFsi{A#XaP&y^{j`ve0x3K(U`D>Ljj$Xj zsS-mIR%8xzE&g$B5{|M>E~7D;kf*D)DL*&RuzXxb?VO6jdC6Y*$d=KK`Qs8R(?jx` zGix$Rrmm=QE6eO` z6@T-##j~$yD=u9)F>lty63-jAeB77L?bU7fBieDQliOEHt6PdRq}PC0;|=thL^pHN z+0En6n=x`)U3Slgoay7TTIynw(`L@O-A^bh8aFGt<-)m>FPf27dvaaq%=5=57ABcZ za~ER3CRr;*KgrEn@7CVddxP3zQ2u7GJd{iCkF7Rsv1!wD2Bg+@gVJ$*Qm-~p(l4Dg z=WohyD)*o-uJxEUstFDBpTp6%{&U*K_onX+f<$aNt7Iel@to~`|Ht2_GR z%8mrDvc|c)_bMrktWi*|()85|?@0F5N^S2O1i@O_-Jg21*0lwt<_ia}fBfmeJiQar zp3ID@tw0XOHk%fV-K0_RXzs&7?s|%?)k(6+w}P7Bizh`)ner^grozpH<)H+}Mok}^ z?JjlcAxgValhdcn%xyk%{P=SwR!)!b6RVyt+Hbq3v=!I3aYX@iqAm0RwSalpxAJ;zwDnVI*w&QeO$YcXl5X6Qw8e3Mi~RzrBm_zpoQ z!+zj2*pN5)NQ>$Vdd?Py1|6sNt*fyaeK(_fbp`JhWf#qkvlFuG%DMMqs)RdQP9;^u<#l+?wW zo)3zfaQmDd=S*jClhRUQ|cUXqfQ!~|>nh!B>LHRaWHmJi8KNsbHzGRM~x zj~|&>)I6%7XoM|!iml2huXk_AZz?L8Svt3&Y}!fOy!aW4FY7j7qd4G(aGx*L_nhC%73sJEtxxNluJS7#cbxd}{H8$*D8Snoe(+ z^pzV9F_@itZlOoRO5}x+4;Yg~x~IX3n2=Zf5rQ z^JmSwcv526u#Y!QTVCOo<)^!$#novw%UYUOHklTuVo=A@Q}Hyaz-~6TKMeJ-HF~&c z%z~J(Z(%QCyf&#)Qc_@2S|H z#^y_6>q^!;_uO+&FY``8reSqcZF5#@{@u$UTK*Oh?)|%VF&6dJzu8L6-)!m&^=E7W z>t)w`QhuBGy+MXiq`WPXiJUCY9a00^t2=90g>mFofBW142CZ?Nicf=1{>bU1)^SJ2 zf5?{nkofh+`tZurv`%(*$BUfzzJ%X2VueyDrB!rj!M9bVlUYx3%VR@aBjdVR6U{$v z!RVz5E*sAxX(l~uu$%6I3J-J$bZ4a1k#Eqd8mmSwiKNw|pQTkTR;^rqNUP=lpw)Hu zK&vYYEjvr{ogvrR*7yr9L8R}ga!A}97bHKV)roh}XL0|Sh5J&Z)$woBY7BYKss`Sa zZ=d@E-dp(z{-WG?Pa}>!!Jk=wo_$St7FscVkMtKQrl(otbf4KU-Sd_A)}QbA6#XsJ zOXmTB!}#h)aUQ5L6S+g7RKeYr^5!U|vHf&3x(pNvN{H(U1ohCQ6+@=Di>JH=*!vLcUoJoe^=qCUJrz|UO>j-&fh$z}0TOwy>Taq-g6NurH_NMqf zVj-6b-V8;ZPruCKH_d!3TI;uNvinW*YELX>+3zp7bbCv2D}&E&Damv_Ci;kJs{D!o4ljMmNDqS!Fc!7?izlpo(+zBKC$|G*)M>q`lD@ z{0e(h=)H~UG^A*hL}UY*Okz2!0X%s_3k>{w#ec)oy7?&&qL0GAgR5Yj9p-jN62GM? z&5Y$WEU)o&W@t4m{p*5d=p-t}^7tD%RY9)5Tmbt_R#tYyUb_zxQO!*{mEEE2oA0l=LEJZI zxfa;HrLi+st;J{IhbBuE8664SN9?)rEj)Lj@$JM1>D$3WuLTL>)6YXQR-uli z1KK8$hs+0^B}y1rg9=H7SRaj0Gc;^b=9Hb?TEAb%+Aza4;&t%YFL6;Naabj&Qll~U zvR!*vJC0USNCUI=ZxWvc7l3a@2xB+hl%LRIrz_Tkl5fT{T3CGU>glPg=jX1TnZA0w zJm#~+htdO6q3E1>@|q2sZ(W?edU5%-4e601<-+dy?8uSo*cIG^Ej@+K3AG>moEjb= zJRNeEufvACoX&%Pn)818G%P>G(<4liwJ3jf`2bmq8RX}N8=oa>@dK;=CQR0%{5PAH zn%@s=QC{7Au({g&{w>YVlC+mUTjo|Se=4UKX+Qj_4ai{?@=t5UYSYt&eL||s-!d`W zcp@lI`ScX!%qZVN7B~MiqToE2hNxS?8ur}yyXO1rL(zPHeQ1>THDW7NMD?_{=DTCO z4tDj~4DlLt>3m_|%KIC?n(Tti%0~fb8Nb;`KXRp*3z~zX|F277*Cp7Uz27|*l`Z*% zN9VNmo))Ys`@(JBE5Z01aU2 z9+CXb++cxsD3;VP$J~jL+KKs*{fK`Y8p+O<{Pj-;X9s(C!rMH*Av%22&jNdknE{_( zu!M`TV$JIxV~(FNTP|4{mv2zDR2cOQk}F0r&IgJ;gX#3-mQ?TdiMVv7STQ-B6}VR* zu)>5K;LnleF2O~|B^)dybaJ$*cZ6oyUQo-JCw~e?> zjU#V*JCeyNe3ROSwc1cTLb-^6g6iD^lW~7m@3cC-qNm(rl_c`h4Z#6_i$!nljeGjS z!APge!Xi%Q?~nXJ(HX$6aJK;s-MAt7#@)eh9A77&17C-b4 z-mOQlqYC-9q;$?8PdtNGwJM!6)(BH@uZ>27S=Js^RudAkQ%P?z8mWYWBc-4@IaTsa zVKFTDE4iM5)L1OJ&{NuM_E}<&W+vm7n9rZ^cXfH=14&1*5U_fZkHsc)J;6{WXfh{< z3dN}q!7D*VyBp<_W$?nrD&L#5S_??4-Lwn&vy^T33XTc>9b8IDpB|Pz4NNpY4gL2f z!h{DB`2qOVHgj&&Ol$=rV8=k;a8-Z+)E+mqT~3kYS>uiso)fE0N*-y+u>~dLN6u-l z2pp*j}*E7`{CY*1TOSa?|hSClU{2L!lN6Nw2Og^{KY&N?e z9ow8qr+V#{bgeM6Syo=?$6+&el4$#i*p`)xjFE*&)+lZzuP%5p3*k-R@8 z)c>b29uE7Rk)%ycd_wttL^vfsjYwYF^`8=AwX0A0AQs4_>d&0b(?l!0L*$NavENSf zRF-S8-;SR0`9irh1oWoU0D}ABK)eoE1TxgFu zePr9?#O68osz0*}iMs{(2Hi;CmzM4}5A&F*C1?y4056A(^RNxr1?gJv=QmecCk>U> zp`l()yW^|;_Did^Bh6LvSxBqH%~h^JN~>k=`}aa`1t#RrQiL9P?u~CI4U#@h8sz(% z>s*7BzGY%e`Se{}6O=wZ)p4@%>AM=cAsr6uZM#|!nt#`PzZHtRLoL2)qqBM z9~xu}RHSJURLytCxSlQHu8F0gYm}V~l7~yi-T3u<6_(jd3zp^>S??0rT(S~3hzLTI0w1;{?A=lGx7~3a7J?&%`iJD_iD5hsF~r96 zkeNwg20V>73#4C^$C^-t93!}pmMK1am)4*W%3mw!%srjvPMuA+H**}n0Fo)KNjLl< zZ^UWRyf%GU)77P+{|<@lc1O_J2>@8F?|yVy@m2jySO2cF$K}j9KV#>>vM#&dTECuI ztg)EI5{p^uza~5aGswRo*>+f>4`$_fw_?GWOwRYjNZinOI9o^>c65LwRB-|9ryKQc? zMC|PH8;mjMo4qbo-!rOgr|&vf*3oIPXlLJ&NZ4)(7DwvO2Se5%3wMcHm0-6y_l&Yl zgHvWeDGm+)W#X1GXKz9cKuwdS`nz^qH%fTsu<>&_F5laCaQyRielD~Q%XoM5({w7* zYMI|Nd4KXMaTqk3P`;brZRxwKat|HpnPj6%-!f5de46`$rPZ+=bgsxC!5rcj$X8b? zGav_!FH~^Isdv=HuUgWknado~e>#z|YC>J?RrQ}5?Y7Tm``ns9SB70^Pie%|ceR3W)M{c>t znfBWJ@N{cU8e?~Q+jv4$3w0$NK`Qo(aD8j)Z?y-xT#_N*56Fjjt`Jv>xFEE&H4w0H z6&TeS>jzkSwpUZby$+M#jOsKgtm>!^*a`!V)sa@)(gg#01Hxm9$C!W;tVXjoGEvyL zZp>BasIZ&am8iuk@?Jku_7*fa+?V8<>@7G1VItEOTuSVf^0weL1I6IZ|3iO&yfzcj zdi=JeR;}u`c3YygM5Tr}o>8S!8#Mo@f6yQCsa3WvhtF*HCvK!Kwye$*W793f2b7rx(xVb00*jL<~kF)t=Vmix& z`4S4rK#@FKmAZ0jaF(PKBCd#zlGI%Wl1r+ih3f98Rw&>MjPO>MipmplF?f{QyGpX3 z#YcDtu&vv#mgnN(x4)7xm*S%^GMl}n082s! z^z<#t>igvqNCi7x;@k10!kM4dk_p`?Xk7Ey`DV&(d&Qz$Dh$LlAZSg#ZR}f|U%PJ# zG?aM1(zW}}qCs0k{cE&nTW{a?k=kR7wLX;ayRwPnm+a$aD^&Vw2tZI^jF+A%L?LKf zFndX+D;9aRLIA?2TX*G28MQwuZtZQ~H)xQeP;{Om;(lv;bFFi#UbayGy7G`UC}#8d zbp504Picjt^?X+%!Xlm=B2EnNa;hrT{~;hki5D6&z+m@Lsb2!WHUjPIz{C!iV^(#S zM?X9b$5d?NSfH5cwpVMOTW$=E?y0}?l;A$6RtE)f>W%|Duc_(GhTblV?v1aTxc*@E zxifD;=b!xafKZBhdlVu{; zKPfT_9VZ!0LH%-(87`Mi5R)pA&T}?hr#QWqQ`k9r`o`4GbWd=XE#coE9+}F10}ZGg zsjh2K{}1zw8oDiA)9Lh?q`p+O@cux|wmaC9aa^d6W$edHepd~KT!J^I8*v6q=iAaz zeZ*Sq`*LgY-_$Z8LWkka zks5cXq(#q(^*Yh9FLHvU(@-wW5AO2UE1}aihHKI%+`=f{X>D5D?>qS<_h@mb#XF&o zy@J!^fEK*Bdt)IvrNKD?9i2@y83|4Z@@j}uc%!w-C6fQPyLnbUkx@L zy;Gj4@AjsQs^`SFyYc}$DkRK~PQ6oSG8jxYXQu;A)q?gw-X-7ONBC`qLan?Osqzx9 zxuyPHUge^N$K;iu1JEbWldoW}4xV%Y(k!=fq9y@Q6xwbQkH!amPLEsPsk0Qae#avF zwQ}x1Oa{aU;#Q%{o1Zc#Le2m~*E);7bNI%8y5;X$SyA(-|HM+MJ&(TO`f0!_ck%)! zf_W{&jvkeGJ;`75UNh7ymG=~p^ob|12XN4KeL`WPP0&Hjjohn-kQZLv3V8ju!2qJ9 z#7)|%a6JnBlZrXw?)sool_f)qt{{N!mIk!cBU(dp67Q4EN|n(XUNJ4K{AK0W!WDo% zO*;AN4uR-yGNx`ZV__OL3`fPq@M%ZF?RU9cDz&;(<@9L%$!sdzsrTqNKb^?>Ebnw> ziyQyVZc&XrpN(kE7n=M=i^1n|!(HV!>kMip77UXIE79h@+Vpt3+UQ@^RYA@MMK zO}cCo@)1;|J>#$Vp?z{#`Tctxs7NCYAYYhzG(96~g+C&5Crcfn)l>_}J;_`Tmo5|~ zG~>Qd{J!c)S-1`is~6la#gch9yWnpYXYRjD5Eg%c7Z*RcH2c~s@#4)VuDbGx%dflk zivEaDN@ypqT;BbvZCd$m;Nm>Yexbkp>T9ktYA?O$j=S~1v<{d? zeIhUo9~XFW7_aXErV$U~YO$0rh^Dknp=5U;Dgf}?1Qp3)A|dt&_*v|5`Lgq+mYYEo z=Y?mg`DL&4TA?CNHgB>xE;=&15HoNZXRgxLk?3%Y*~~v~6)TN?odh50ZBGHdB5+7* zTAS`Jai~O#5+ki}gbMG1+II5C4R%YAbovnoq^fwS7wdt?8>-v;WNMWNx>IEKxQqRJ z+u{}gseX=N!U2B_FwL#HThhZJz?MCotTuF`F2HJZfxfp?c1T3 zf^cf&pX-KjD3EKDGNowgd^oM7K%UA05U$TaJM6BmXoS6tg*dbzl9SgdyCLSypzKJ* z!>Ok8+v4RCu@GG}nRff(XgM!r|0*8>!AWwNtkWPxHc7`gg<)DEKm01Yl$y*oZBp1j z5;4!p2B|#ZK+-AhMen4_hWy-N*rF6MCh+{-5>0wc#p`7nn3Lbi7y#iuc_Hv-EI zMO7m6Hh?*no4Y~9V|-(c1ApF4)6wGT1QOw!WXp%JyRhUDMJr3P^5h;PL>_B)M~WzI z&M#;lO$1Ji%Xj?oeI=0n7Z4XO=#v1^e$xr1(yH}R}vD2x9G0*>LquvIX4@>A>su72I}k-p76HkaMZ%&~~nKUZ(ibiV9IZ@EL( zMD7(uUu8)q9&GK!bdqZ#gMzdb#{S}iVausmd=`5d>)5g`<+OLpUbUfSzwEVJYGaIS zI|w~t{UOMxZCCXVa>8Nfw{2=2ig?xkQ!Xi)e5-c%b@lJ%`M8mV=TG~**Cnb+jU;;uK7U?b0#woIK@(Vgwiwt*jU3&~za&g|=I{f3%#hV{IzcChs+46}_CZ-v+UL%Ntfan206z5t7RP&}a#r>7s zh?0|PVDXFP5)_VvgM7yh)}J|YB~9Hf?7)a zQ_^c5rzQi7a*H4&5LO4S2`*CQ#9iDr5*>;&W;Fb_!n{H4E5A``xfd3)pZadLq&TnPf{xOd$dj3X_tl+1g%9kw zX-isoPy$U6Go%B&PF2`^&C=@rpyjPqe_&{rdGx|tCPbwN{5o|0$jFUHYo~4=DsjUT zz;Yfp7I-E%JgL&MSGM6|kqyBz8Q$V@u2s zCq}tI*6m(r#IR%vhq_+~PtC<0w#!HYU}K zNEYdkv(KZCIkSVJuw^Mc<7L9|S6ll@abnr&7#Q@<@6YGv5&8@1cVD(^qsj0K96mwV zY%oO)q3VCsb!kfn$9G)R2l+{?;qNdljU@W$xyp3vO(hE>7rWrBNKDjtl{9 z64fk`iIG8XsFKz>YzNBb*)Y7L*-}bXDgNAFE#@qXH(WIq-#D4Jh8)fa3!3ycmA-d=W1vpzKWa}miQfQS zUc_w{at-rfy&}?1MQVSA4;fzE54jWeOu>5!hwmll?n8DbvZ27^)935d=2Z4HPT#-E zmz?Txu$jN{gO@-_Ptdc9#vIb8sAg@)PWn?>`6Uv1CsR zAHhjN=OOD8?J(ClN+rM~nN_@YYL&BJtU84I4B@EN@AF%lBO{t_V~B{gLneIDP%`*< z|E4}sFnMCmwxJR0u!yzOCSnpj!Gqk!|1Lh&Y>bqR zHoBQJH%IcU;HBvpzJj-Hqd)%hpCwK&dv+jTr$z%5m zKo0$qdtoFN))0;SW&1T~w-Ke7tndWfNw)%ac~DXt{kIKNI@ECDHK!xBz)yh_(vVh< zRq~*>^^b_VkoV)?056Z1u0!t_RHbm^ws+Twm$p$myRiNSPSLHo1E-7EOJJrOBc9qf zaq4KV_&W~0oV$Fs(cV_am~NKZyQy$STVx;JjwguylV+yG$=`eo$dbc%=!9tAKZQbGNkTAmzvWJ`2xNR zsWj5D5tQEI3(!ick<`VtT9%yzQn-x5m|Rh!vJj01Dzoftl8Ray^C2?3qjqOc#H6=V2SI^di=8-I(S}e%l=gAz?SU! zyXijkK-!XzL!`VglD51z52}LuJDgXBcow0#$hEbVE+b%G<=d0*9a& zk<~Bt1%`14aViBVWeZO@s*x-zu(-w-!R?ai;z0o@%RVT ztVxrlskfm>{Hh@>{)!HVceC5-+@?H-E{Dv7obz9CT?8b9b+s{Pp~_#bdxcb^W0O<}S`w z6CPcm?T%9fcf=YX=D{7gCo}^^_{J;jG$9_TKlI#E@Z@u7H+k9!PZ*db4=u6)T4Wec zraQaP7?iln0O1YblV=K!?QY1gvki}V5=O8fp(6|err4c4QL*H3kq_#eBY2yW=#X_r zFcA-V{Lg=Wob$x91ypgIk#-DN-wIfv!Sk35221fIq|^EH+L$`es)!vKxU9{chd3jb zu}!A_Y0jrPgiUXr6lsJvuO-yTvniBljjX>vzUIt9nRK5N4WJDqNr`W61c!bv4*lj( zKnG9b8qmnQCvQN$Ii7OvV6=7`u-f;+<6P*A{JiJ{f6hqxU5XEy>}?pLJjqM4wI!Pz z^+04TB?;Z+4C8_ayu6CRW53>e$M$sZ&g9Ez^6`?76nx*aRPu(NOUOut;)h9-V4qsQ(n!4Hk*YApKy!GJ16`Rto z{)DGC?qZh*2NRKs(P{FanqI4S<(Y~+34ni&!;iT!6hW2jx&O;9{;h2A{mE%hz=KgldC-Q2yPJC1Ip<*#I|ErK?JU@8rhSE%?vpp-T5ftQv)~@1r6=n)^ zH_s&JXp@LpZ4%y$9pFWxs*P0|X+tndDDeekMgHMaB+3Y*hqq*`MuYwKmMoaUy16o0 z%@4OMxnzAzNq{+JzKl1?vFqdBVfbEK@*T^ThTgu;tzBbtXxPsj{eHdKXUz9I&R}#z zHvFWsGpJGz>>XkoPob}KjJ6vcCx5kY`KZikNN0!KZo_3W9CkJU3lCf%;q>mau##G^ zdQMi7jNeV1caxq`CYAMEMGTJ_uve8M0qvQjA;ipEB2nYxUlj#MPwKtrmN3%06=@CATH}E^vj^C6hmHglq$ukh8xB;Jl2!%+Qe`rH)CLew%=Jrb%zKLcRdVdxCM``XM z6EST8s^?N;?BRkk^e|xf#NQ_3Dz6|ToX;tI|H=Qy9;9i9ow1-QdHA9W3mA7ufmUVM zq1YUC$UYlkePi_@b6C+}P*3F}CBylJC z-%?I0@{(@Df-+MZ5lrf<&#jh9H6hj2Hxe$JzT2ugD6&^?dikyb_FEFt^XJuu4v%ON zC5&#}79+0Px96rU<(*?Ov7TU&(cRs>dvDofa2eRlm0R=q9V6eXxV;92!KQn5Trr^< zx^!m=ypvD&rd`lXUOxFS^9@_x4oa&%AB*5825% z_J-=A8l{F>;}DiSi&PqN;!ax8xf)HZ>)(k3^IW%ij%MP@8JOc8-{zAI=mehQ5KEbv zTd5Z-KF@RY`1u`^cmA*V+4Ql?bJVys(y%L=XVU6Vze_OJSI*=%--O)px1QNY-U=D} zRueL;^C&al$vfrs2WTg9?i=m=b$ops`FqqqT*`@iXJftG@$#1ScQn>_$dggRz|=h= zeg5zH_mn%{reILq$k*R2udg6OQ$#JOk7w7L;1QsTKOPgLpde}nG@4XvD0Uq;4TCZC zfXwW3{m$LO@`n|F#+tbrLE>Kx9)ylHB=Hff zO!cT>@t@W>qjJ19^Gp%@?8}h)Oo;ka(Vk+?*y!%Rj+wS*Ho(%1E1X*TRmfb4hL_UX z!r1h%rP%*rB9DlM?MhGKZAwK^_sRpF|9aN5H6(oz3)} zw{1XRr9&e#o5GA$j!qQ!=hT&W*M*x0mJ$mCJ7*4D#Mtn@{>d9+KKJm{lG&PDsPyj~ z*7PkVO1lOE;mR{x4{q2HpU4O-!**^MoCypM*rNk6i=>D7cy2OT%`H@KA-YYss3uzwgG~#hnHDP~IC=>I1VxJE*>G$-Zh&&s&@Q=4BULbXM_k zL7h*(PRjE+s}wjX^Pi>`qJIuexk#iC%Y~k!xT(2`=RD)=LiX0%!?)LOQh3SPOfdZT z_w3*MKwDlvhH#u|iXmHh68!Hgz$G-^R^F$;D;cJ1R9x9BL<*dms3F-@c$-PF7c_t6 z#Uos~ROkT;uQ1n|N!704w)=5ErmFu7DZJo@WIa$t54Rp#{CSqMv>tYolkdhe+75Yj zvlgq7Jl{gK?YZ`z1o{5}+1rF?IvmvD7fK+mIxiNdoDx)_@FJrGteupY(ux|5MwKah zo_2rJWMtd2$wGZcA-R>!2CW*+)jjE(^tQ*pE>xz4{*zyf4qDjvj%?d@(*^@;`#R5_w?j<-?5WY&lfJO4PW~9r7K47zUp4eKJOek zbmR2$D~^wA98zsYKBgHya{I>FTd~A5&>sl zx9cwvoUk!KkXUNyXx;*IwXY^0}jIt}wvzJgMos_e%_=aE&lE#A8=aeM|LC70; zaYvKfs2!eNIb)1jCN3UJY@PB9l&O1jOlu0oe1ik-jk8SaI=nc3tTukhXn)Nc7%vru z1L5)R*;k*~^Tw+@dYx9=X>}UHJ>f2$9zN?=?yAj|G2-sV$>h+1V)cTl%I@OAg@fSU zlq~j3_Ip$s_mJQicTuYoHZ+``HHp)u z!UXE(dIwO+#O|~CpF>e@- zEm--h`L4!W_9R?SZg6E^Vn*7P&EL(Q1Al-QjeomH@K-68A$0`9*Mp(8Kfunce_j`9 zU(cn!sXTy=sRePwgAyw~_5++pkTif6-Ab;T)q<8J7b?}wDw|vLbmr@F8Fif|ZL-e| zcXOo@NZ8%g;Pl>nWN^Oz<8=Z1iKq%OVd$zIrLDcFdej+*kB!$Z-rDn%(?CnHvH?IF z=2dE_kwvo%k#LPJE9g zr9S#9aE)(qF2Y|^=L_CfpL*-Oy}3>udgOllWD2BH-}2@peq;j)dozvZ9NGw zm=<+D%k5Y4dq`-SYUx+JTOY7Gi>_eK6|7{O<+79A*ZAB4Mhlq=b{{~mtPi0d8?+>7 zb;MswAun(m488TMZ-yiTD%y1sqx`fYypnp3<>5)3>K76&k3(O1#V7{x#ixp)rVFfw$4s_r(UPi z<430rvk!dMbT6nh3anZ|3sCGOxz#w@^=~B~SB~&N<4!_faG<5Oa=>)+Qm1POj)EVh z@v;SKhn&}k!%kH~ zP_fZhzJfYenY4FY@AqqZL~-)pLji5-4e1-u)=E^1Zl+a@2U#M-DkyptGxh(lC2eRv z%7%mCbp6W#FLiRtKw+uh&!Vm#7ykXMG14VuWG*lT2wlhlMt7+O7a%vfA`k(wTDdLC zx@>!>kx8Z8e$Gh2PoHKBL61oMfL6d5@Uy;$4cEWWxc5z5t^eEiQ!1RY)D0JMhj+u} z+LcfPtl$An?Me}kt&)KQ*MwM7e#Qk8=S2WeBGkv_yV=mZ)`LZ8YD9oY?h*@#DqP|L zLOx?l!zXef{{e^oO+e^xg^+0&)blyIfXTrxo*@sy{giUdznkRwi*r%7sl0Q^(*w4Jzg+B@#` z4qyu*YoeA;PTGawt}jHh9#L!S`P*3s z9BF96`T|8jA`;=snH~JX;hv#6FDX(cXOVIN` zwLwX&@q-!CYR@}p&zV3Zy%WW8jpkJiaZenqp)m^^00rfvOZhK~oNOpKbPB5$Wk7_u zDITfsm#@szA#fiizF(qMNtr>sW^cPnt+1RXnJO!)?*J>fDI)Rp$yR!lL6NF#po|d< zJnb51M)D<+!Mdvn7I?n!vgS*!2&{9ZkT|5+77B>#ECG#DtS<+sDU+ay#WR9Qm|eXc z4!WoYVo!1y>GE^Do?!#~PJ!mL+5}J!+k?SLSprqg{> z(G2SwE)6mECL7ozx9f#Zav1V7eY?%BmyCfnC}eFJ7a7>&%&n<^Hf}SiA*}$GBmgh8 zge|?3wX(wtsCp%=Q9m~lQ$Q$wT?cA0HM0upqJm$(wH8GTr%n7Qh1~?HI|tX5o6tuT zmj$8iYxM(QXn9$XN+y81srwp>+%nIWV)i1MQ>yhatFupsU=h~4+Wj_vL7IK6g_&XDiz9^1WjE2! zFTD2l^km|(jLRj%_NN^6`lCKvEz_MeG~EfS(CpXFhgx^0lCbh?pi`PY#yy0@LKTLF z-`CC>lT}-u21t4l@ePr#1f6|jDp32-G0;bII@t<)Ju!o}X-4e>$LQ_VGs=Vsv~DNK z@Ei26B&-8kc%_mlSp#TOxg|+~&RKGjUt>rnZY0kv7j)y7O|b)9Q;}(wX!ua)%muZ! z$s#?Y!N{a}^6Ew5_EkCy0l6@D>DapSMgS>=Cttt~%##0XO=Ypfr6s+RKrfdiM^p?7 zj#OE!0j!{0iz|}w#|=*7#?hXo+)1}rj|=ux%06oh`gERQ-=~}rv~)5TaocJ^PhYHH zGOCOl`yn%ej)brNa|!Bld@Jipr;V}NFByBc-KP`4F8aBh=(V@ah8!sR1*abFX{&v2t+!zl&y?(EP5w={uUd0;5} z-gV_PWQpv*o1sqn5)A-xuplTqfaHitL`5;Obk4?C$MA<$iYcJ~o4u#c=}4F@S^tdN zqbmt!l*dI}BL=JAWAOAlTK#l!B64tObzJt-vGT=sbJs6?+E6wYj1PwVC9}n{9Edwv zHP5;{N#DG-yURBjY`W+8JTZw=@5c}&a?df{Jsz80&Ely6C`2?zyF>g1sNDts{o#&F zWI5hySjs{L%@)*r(uNc&S&Ipt8H|JXM!UM>{xP>x+rxfg_IX400i(s|(RT$y#z4PkhqE`}taziCYWr!QOBMDE zc>H}X7CmBc7@l>h{obH?U8DQ^nw0+MKwLnG7firhC}HE6b0__TIa9XBQ7FQ(8QK{Y z?)CW4>X}%@ej&Ogo~{CKWH-ZAVNw2VSxG?^m0Aj=D7xsrV2 z#wWRD)^Id-xhHNh1#F(2$*c=yovyeUL!X_+E{(bW(o+qyd8)fptG5|6E;LLs!AoMc zsrr)NIn8VvSQzNDE%br|Lsx5b7FXJgfk0tJ@XBF2H_45}wnv7Hqo=t=t2U?% zX0u-B4CO=dgycO^sg0@d1*_VdC+()VIbJS?d?N#qpsV3PT0B($PmmeK(#0Yu?}%*i zkW8Rt;x^io-Q8SB{1r}gNH`tBG_G)beb@J%b91AI`b!ZqJWQE1-KKTC-{(Ol6Pi`8!Qz7?(tHbuv#!nBe&nnG@97?p!g?#-_e+pVbd|Plpii#Z*O70qQ*}r0*rI!uW z|MQ$@S_*SewLeB1XwwDHZn7n4h_ryKfO|#1;zq4(Cz1xZHX_!J`>Nv(()}71Be@1n z?|E0t&Kb#YBn$mmOIT}-*ZpboY^k_6;{f1v!BVIYv}g1byCiK^cDRYM1B9KW!?+8s zkI=sQ*csi%NriaC%*lP=M@iLp*cV`L0k;t9!t;QccOT-ix7E|tP_v{^b~GwS=Vc{a z=DiKK7pVESoLrRvHyZCMs`qc{mnIza+rss-3#26?P3t>Vw{ZoL!w3Gc9q=jM!WM)z z-IFpn9C9fTEC~f63ub<~boLETHur3cilmroxR%?I)^0-U+6`z;|Ji(5o zc0F3^GTn3qw?@m)!v8KDJ5Buj=M zrRm;op_P=xWRaP;HV`8`2~g`H!lDuz`!LMdNG~dY#b5fJ+zHN;I@;qP5=o8qFdi`z z1aE&#ipVG&S%S|3ZcDgCl(T_Lm}6%hCL%o<b@5Ms3XdDKE zNP;WUC`l1&#d9Vtp2FeA{=6?KW3K5w1RJ}0DZ4lrW)WuerhL(|&+c-n)LLD)+Z3PcJ-&bA zHRDbUY|j>*lQ(Rgd)bn&x*g@wl#{%V!zfaY1#xrnuY#0q%Nf3T&d?IOXJ0%Qh!w9a ztAiR|;!$Vr2jZDG4E|cJSnBu!ep0l~f>AOrkp3;#!IJr(;`-BBn?X{viM`I0$ag2B zi7l}+TR_p-o`GU&^w&X4HRTzaD-@S)jhXSv%~t|7t%+OPOvQ5Tx`Esr)GjHDouYKe z&yxG*lh=*>JVY;z8rV~&k!aa7l`h4NCW&|Bp^p?wmrl-!?N z8;L(WS?Dsvs3=p2-NDm@d|r?o?!A;Fj_eYiBaRF^K5bT7rQFVc!mZb!=dAo$;rr+( ztC8o|NtvSbx8LHQDjgwBmZD5AfIkOl&ODzOnvg7tmUKt(YZE%E;@a7r&KJ{*LlJgZ zjL#3`P=$)9QgS*obJa|I`arpV!Te`O(d?=07}o5)enbu^ORzMNTw-}ppv8;o)}`bVOZWak>+ z>^)?OzvWa;SY}VD{&X6jYbzweUJ1uX2|g(TNl?SaAiCnzCS^F$6iZ}0VRf9(OqW(^ zFVbpxk-mYiw#@Py+C{5~T%j=~eXm3MUjA9a(I@$;535?S-0?7eiYq@yo;c<0cOx1@ zKH(NVTLM|i;X)~F)Ry^9+;u@_uK9vh*eOJo4snAPjxEJgz|;GG%&Zb??b$W5aABpv zO-V$x_y~~D4$5;vrh*}o=B0)c9oFYrYyA@SvD;0_s6J4eB_q4Jig(S_3X zzU_zHE{{W_GnO3v)$zehAu<%5tYwxXe(&t8IW*ccxPfWL267Ye;eI6sZnxG7y_E#! zRQ49*X?L<Ql58r7&k|t2sV#m#t5fU4i5wt=}$PI~s#R9lhLLN$GErenl!E=~dX%?^YAWT2-o{3+`0?GsUkZ z&6&KHq?Z!5xXyPDnzMs@Fkf+2it`ms2U5s#YO4LMQd=~~^IBenx0u45k($XBtHaU!R=+QH!tCr% zCr4_Lz;GxTPmZS_%X?gbQaDm6`XiN0DqHTij?Uo5X6w(hKY$13=u+Tq+h-YKV;PxNCJ05!>C&wy3v8hjtrc*}T1(r@#ZLq!a{$ z)%ZcBkO+9NlQxQTn^>idlER5Ik~kLB(IVoc3jbO~Rf=jlzANW;TI_m*!H_L<&n#p% z3>ZB|{5w73EX3l2VX7+%FNCU_bF-tR&G~j6)#x->o%-{3&1{J!nXq}jXFMtw80D5r z9nXPXvjFbxc;tWCDujEt`zv3rN?fxIBdV=L&)(o7`DPBt4nf?*h8C8)3(UCDj(}3 zfq{<^+lBR(=W3uOW=Z7z#sEa7h(1VzMO%PUcsbX_Pm&EkCeeTcR~fqg=b$^rf7e_1 zcw>nVD~2xN1QrygS5i#`v@X0x?wojtza~9b%7fPeYB)DmdlZ`jxtR(tttlH|wWgh` z0FG?<0gTsZ1)hM8YPI_}$Erpd@DG-d5r6k;-~kW$5SIWC^|s(r0*AzH?_@hZ!F0)v zYTji9CLsauVFr1ip*{J^VuFbZV5M4e*3p0q1AVyf)e-kOj1|Cm zn(*Ci9=5{=OYr?hJOKER+XOkm%L5vCl3{|MPzi^F3IgwyeQr!9)Y}UUsb3w{hshLk)|7gojzPb}- z%b9@DXZ-H!RqU^;WsNpS`Uc0xR(sMVorQQg_PTA=SpZn*KmVt_Be`S&=ag;%-S^jQ zzj=lP^Z#Y@+{2F3Wlv(qKHm2hdO+J9b~Kd&C<9=i5dXNmPG;G8Uh|a9S@YK2k;0aV zp6XD=mbR44OS^v2#PoLrh7sn_`cl>5!7(OWJ4pUsKHX#(+L4E51(OCsPfjIy{`V=L zdk;N#BTxM{uJ_nK;IRVh9e?Pn>pT>>5b%Tl1(LF$#5q%(6>+*;tN&U~lUmAFD7Ew; zmu*kA=R_PnLwYF}U8M1Tvqj)VPLpE_o4mCSyd=lnl#^;Rw=eol{cG!5*U#lrKT`aF z1VM{MO{Wo9S>zc-Hi~RR_rjM$9dya9D8R2%k~MhF8AiWzM+4T*F16r@SklRKq;S^h zM*rk>dVPXmj0QV9U9$my<~`?-dqmOUMIIftQT8a&*ZMn>Gh0s`E$&^tfis1U*TQ|K zI5MPEW~H0g$t8K6oK~Fec#>O>@E5{*{JpG;N_sgD%;2(6`%vkEk_L+GL3)EMV!PAE zwRJ>#G}NVuMfP8i8@p_NUX0ZWHEltK31$f5V+_1kz?Z4ERMR${2&^fSvA7QNzU-xi)aIWR12J^46EY5Ge{ zZpB~z4VTi0RH4_;Qxq#LrAiRTC8eb}H%_3%I7p={X{mowwL-~da!(h>c5QHEe17VR zsm!)pckH=!b22x#E4O2OB%rDPF}tOVN!PXAwV~Z3!Qnl*dvZQz+jqxO?S`WxBS&wj z4IbM%VrIVFJxhiS*Da1+d$3yCbz<<~T?-mq3)XQ6T|+j4XW>S|-&wYyWKc?su#n87 z$eDZ$k_1ojt}~j~`Q2=Xr8k$Du7}gn`-mG4yH#$s7!_q@b0~w9RY>7A{G0dmk zabT+(&k-^go!B49!Kf2D1_65o=W3JWO=|~S@@|tSPdQ8UV61LDHs}hBojhLM1L~d(Z+SF*)q0^kpUt7?JF>I^Ev# zrh)X*NX*f@p)#{u`;YxgxuB-JZFVv~cA`Hy-;)XIT?U=g%)(AbsSvPbXG@;p>81SO z(HN>P0;&4}5!uRr0Yq~RTUquXwL0I)!_+pqQihhZ!=}M6t*$H_LK_9iTAk0#@APk6 z?6evTc71uo>lo-MOmynBI=jW;%nTFn`ZWsUldjgy6Zf}^T|6BiiXICp_e0NR2#2&6BqW$b!^?p`kx742$8Oj_OuO@my zIN9pUtni(}%A*jMRKJT`BSttYQFFCJ;<5FKeSI=6RN1Ple9^{T4se(V?7A zQ-9pOoFCg=_0C?|h60^C1fuERh@}0|bf?+iWfP(3E41IyTT_#pGaHVVl z$R?tl#vmpQi7I$q!O=vm9DiRGZUA3lsl68$jM32}Gb5W)4yOm7P?s}3<0+?(S^6eY zBU}3&qbDYNhrN2=KsGc|(2JHx#k}K9C*nsh-anYDc^t5_f=0blZ%y}Y+Pf{_Ou92u zo0DS~kEME-^ZwcKOsE*>JFo%wmi+C%W}iSWGgzE10v9S1ZUF|LAqK|_95kBG+03r6 z(Kaw1slS*;w!pjMma=5J?~UUl!^?{!1%L;f=OK?g7q z1}?z!<=(8)XugJMRw70zC+VQzty+zf8U>(nSdx2zm_4>}GLxT8Z5&ICEhS5PtBKJ> zVPntKr6ZH`iwCOxf@XxGnTY#Jv(Y6TYDYDCl==S4>$~=;X}RQT8~aN!!MN^ z4&VgMsbMcgz(Pj7L+=dd7kBT9MvIxLP07iNM=R&uxiNqI=++{e#*y_N*x1v*i4HJL zk`9;uCc#5dS~Oq}p;nGc=_e2DWwv|3{wC-Ap9 z{O!4O!4t#<=O;b2fp#vKMIpPhfAb)?;HS=+3u1SNu)6}^-QNHxfoMqBiq}-3(%>i& z$$QykDNHT0A2HFHcGh3e#*?P{N7#Mg%P&7t|1;CE%bYP2v6Z;5npw?N@WkVIVgg)6 zBfPes@VghUiyVRnBlP-O5OXwQdT zy$M^sn(!2yg2i*JcvqYg8*3YH^u^luDtOIG~7&J*6L>(r75aLs1?K?u& z_kGdJ1_vR1&wm-~53qz^A7u9W?^qlofpGw@`M~?=3}P$)%tl3f2lQ*TShl%D8*~5u z_ld84b&~x^^_}3a%$jto)|q zqa9au7@C?Tv9_1FVsV!z5;rG&zGTwpgNu!IOsy<;{7c7i>Qd%H1QuKw<_8@B4-{&y z_Zff$ZI{O8i#TI(TjV2l)4+HT`oF^;EGIn+v!LH;JdZrKN#TPaf35Jr4w1bc>vQ<- zC;9q^kf%=T8omzrgs%WTe4f+u2N_rzy)VGh0B#rYeg6X7b`n$=XG6efMX5qbg6ry( z5D0~RLdquZ>8I>1-rins{pcSWn-e<5R?;0G#q;_1*lbSPTGb}C%8_>cCIKo?M@lhFU zogJ!Pki(6n@O9xPdSaa&A7}T-(^=Y)PnN*cBX?Jy86e*fvxk ztI>1`wxY)n3c=d&7y~nY|4hK>>7@GsY83sTMnCke4p$YGwEVvk%9rq;LmShgCnC~x zVp@9VjfF?d4_Du3eouZX_jUaDp6*8m-eZ0^zfkx}e(0-}ubH11`Ps;)@DHo~Y~+b2 zo~Zwf_=2PUyzo|Z%*2JZL7r_M78WM!&&S0dp-QIhUB;?RICkM`()AA%oggbzhY?(+8~-jbGhNSgBy z{yB-aK#CD>iL=)OH!AR!d(Ij+`%|}e-_mzS_igEk)F)CC$=gh~_1xZlb9y%Y(NyVU zeIH*3Hv|)Q_g!gsX=!(9c)=OyHh}!oqa0^Yr>EIb7MPpjT$Tj{UGtqOB&jyvfiJo7 zH&eoA`8ym}{M!VLKt3eaF|_ia@b;B2aSa1m`$NKoSB$ElD6x(>BAsEJ6(kXJ5#tGp zF0Ir$muWqr{uP zsW{M6O6|;J0(Mjg&ks4r$K07}NF%5tLs{3x4X&Y0Ne1D@Z)sMX=+`tyF4+%)9c zu+fzril_xmsG4z)jXQ_t!$LH_e?Gr6RYE>*$vXqUkMsee&8Q364{tW&zb+)GX+@V~ zGh@2Xs6W3V{&qxs>3(s9;Krxi(x;dhDOYW&GIL{=H$Eo(?j_TRC|)a$fNErU4yySO zkDK8&yAt;)Nw0BFKGgAHE@OMx_t^p9!A`R$Y{(OLaYl$eN|LcceQ{T>-3V;})@+x? z?hU)530vd~s~3b9p&yKe6#c+EKj&P%K)2e1r_(8mK}shlYK><53RR zx=^iwVC@6t@9%gB--7+}?-!7T`X}~nAr347iQh{ zZ*AB!q}9UdXj0`J zF^9uxcZynz+8={Lq7hWkxp%Ag7}CoV=2QS$uuG+~cDlm$F4b$r*WzuFl5{rJpEtV9 zc5l$B5(oC*bpN6cch1Xli`UH>TsAY-F`!0>);9H~gy@Fl)Z&A89v`MNoq|MK;Ai^n zj!zM7%d=$u7FoEAXj?qe@s^ce0yD*xx4~Yz15+OM0oNMIJ6Wx8GhTr7r<^TL<$Qk_ zd_WqJ%Srw~zDb&XNp3�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 Rd;dj;NmtxEv~;BVKLGXUr+WYZ literal 0 HcmV?d00001 diff --git a/fixtures/fonts/Lora/OFL.txt b/fixtures/fonts/Lora/OFL.txt new file mode 100644 index 0000000000..3f0fcd615f --- /dev/null +++ b/fixtures/fonts/Lora/OFL.txt @@ -0,0 +1,93 @@ +Copyright 2011 The Lora Project Authors (https://github.com/cyrealtype/Lora-Cyrillic), with Reserved Font Name "Lora". + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://openfontlicense.org + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/fixtures/fonts/Lora/README.txt b/fixtures/fonts/Lora/README.txt new file mode 100644 index 0000000000..c5dd0ebda3 --- /dev/null +++ b/fixtures/fonts/Lora/README.txt @@ -0,0 +1,71 @@ +Lora Variable Font +================== + +This download contains Lora as both variable fonts and static fonts. + +Lora is a variable font with this axis: + wght + +This means all the styles are contained in these files: + Lora-VariableFont_wght.ttf + Lora-Italic-VariableFont_wght.ttf + +If your app fully supports variable fonts, you can now pick intermediate styles +that aren’t available as static fonts. Not all apps support variable fonts, and +in those cases you can use the static font files for Lora: + static/Lora-Regular.ttf + static/Lora-Medium.ttf + static/Lora-SemiBold.ttf + static/Lora-Bold.ttf + static/Lora-Italic.ttf + static/Lora-MediumItalic.ttf + static/Lora-SemiBoldItalic.ttf + static/Lora-BoldItalic.ttf + +Get started +----------- + +1. Install the font files you want to use + +2. Use your app's font picker to view the font family and all the +available styles + +Learn more about variable fonts +------------------------------- + + https://developers.google.com/web/fundamentals/design-and-ux/typography/variable-fonts + https://variablefonts.typenetwork.com + https://medium.com/variable-fonts + +In desktop apps + + https://theblog.adobe.com/can-variable-fonts-illustrator-cc + https://helpx.adobe.com/nz/photoshop/using/fonts.html#variable_fonts + +Online + + https://developers.google.com/fonts/docs/getting_started + https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_Fonts/Variable_Fonts_Guide + https://developer.microsoft.com/en-us/microsoft-edge/testdrive/demos/variable-fonts + +Installing fonts + + MacOS: https://support.apple.com/en-us/HT201749 + Linux: https://www.google.com/search?q=how+to+install+a+font+on+gnu%2Blinux + Windows: https://support.microsoft.com/en-us/help/314960/how-to-install-or-remove-a-font-in-windows + +Android Apps + + https://developers.google.com/fonts/docs/android + https://developer.android.com/guide/topics/ui/look-and-feel/downloadable-fonts + +License +------- +Please read the full license text (OFL.txt) to understand the permissions, +restrictions and requirements for usage, redistribution, and modification. + +You can use them in your products & projects – print or digital, +commercial or otherwise. + +This isn't legal advice, please consider consulting a lawyer and see the full +license for all details. diff --git a/fixtures/fonts/README.md b/fixtures/fonts/README.md index 15848224cc..ae2b853526 100644 --- a/fixtures/fonts/README.md +++ b/fixtures/fonts/README.md @@ -8,26 +8,28 @@ Fonts from Google Fonts and other sources. For variable fonts, only the VF file | family | variable | static | test | description | url | | ------------------- | -------- | ------ | ---------- | ---------------------------------------------------------------------------- | ------------------------------------------------------- | +| AR One Sans | Yes | - | | Modern Arabic variable font with excellent readability | https://fonts.google.com/specimen/AR+One+Sans | | Adobe Blank | No | 1 | | Special-purpose font for testing, forces CSS "no fallback" | https://github.com/adobe-fonts/adobe-blank | | Adobe NotDef | No | 1 | | Special-purpose font for testing, renders ".notdef" glyph for all characters | https://github.com/adobe-fonts/adobe-notdef | | Allerta | No | 1 | | Its the smallest one available. (16kb) | https://fonts.google.com/specimen/Allerta | -| AR One Sans | Yes | - | | Modern Arabic variable font with excellent readability | https://fonts.google.com/specimen/AR+One+Sans | | Bungee | No | 1 | | Bold display font with strong geometric characteristics | https://fonts.google.com/specimen/Bungee | | Bytesized | No | 1 | | Monospace font designed for code and technical content | https://fonts.google.com/specimen/Bytesized | | Caveat | Yes | - | | Handwriting-style variable font with natural flow | https://fonts.google.com/specimen/Caveat | -| Geo | No | 2 | | Geometric sans-serif with Regular and Italic variants | https://fonts.google.com/specimen/Geo | | Geist | Yes | - | | Modern system font designed for UI and web applications | https://fonts.google.com/specimen/Geist | +| Geo | No | 2 | | Geometric sans-serif with Regular and Italic variants | https://fonts.google.com/specimen/Geo | +| Inconsolata | Yes | - | | Monospace. Grida theme web default mono | https://fonts.google.com/specimen/Inconsolata | | Inter | Yes | - | | Modern sans-serif designed for computer screens | https://fonts.google.com/specimen/Inter | +| Lora | Yes | - | | Serif. Grida theme web default serif | https://fonts.google.com/specimen/Lora | | Molle | No | 1 | | Italic-only font family, rare example of single italic variant | https://fonts.google.com/specimen/Molle | +| Noto Color Emoji | No | 1 | | Color emoji font with comprehensive Unicode emoji support | https://fonts.google.com/noto/specimen/Noto+Color+Emoji | | Noto Sans | Yes | - | | Universal sans-serif font supporting Latin, Greek, and Cyrillic | https://fonts.google.com/specimen/Noto+Sans | +| Noto Sans Arabic | Yes | - | | Arabic variant of Noto Sans with full Arabic character support | https://fonts.google.com/specimen/Noto+Sans+Arabic | +| Noto Sans HK | Yes | - | | Hong Kong variant of Noto Sans with Traditional Chinese character support | https://fonts.google.com/specimen/Noto+Sans+HK | +| Noto Sans Hebrew | Yes | - | | Hebrew variant of Noto Sans with full Hebrew character support | https://fonts.google.com/specimen/Noto+Sans+Hebrew | | Noto Sans JP | Yes | - | | Japanese variant of Noto Sans with full Japanese character support | https://fonts.google.com/specimen/Noto+Sans+JP | | Noto Sans KR | Yes | - | | Korean variant of Noto Sans with full Korean character support | https://fonts.google.com/specimen/Noto+Sans+KR | | Noto Sans SC | Yes | - | | Simplified Chinese variant of Noto Sans with full Chinese character support | https://fonts.google.com/specimen/Noto+Sans+SC | | Noto Sans TC | Yes | - | | Traditional Chinese variant of Noto Sans with full Chinese character support | https://fonts.google.com/specimen/Noto+Sans+TC | -| Noto Sans Hebrew | Yes | - | | Hebrew variant of Noto Sans with full Hebrew character support | https://fonts.google.com/specimen/Noto+Sans+Hebrew | -| Noto Sans HK | Yes | - | | Hong Kong variant of Noto Sans with Traditional Chinese character support | https://fonts.google.com/specimen/Noto+Sans+HK | -| Noto Sans Arabic | Yes | - | | Arabic variant of Noto Sans with full Arabic character support | https://fonts.google.com/specimen/Noto+Sans+Arabic | -| Noto Color Emoji | No | 1 | | Color emoji font with comprehensive Unicode emoji support | https://fonts.google.com/noto/specimen/Noto+Color+Emoji | | Noto Serif | Yes | - | | Universal serif font supporting Latin, Greek, and Cyrillic | https://fonts.google.com/specimen/Noto+Serif | | PT Serif | No | 4 | | Serif font family with Regular, Bold, Italic, and BoldItalic | https://fonts.google.com/specimen/PT+Serif | | Pretendard | No | 9 | | Korean sans-serif (static). See [Pretendard](#pretendard) below. | https://github.com/orioncactus/pretendard | @@ -39,6 +41,10 @@ Fonts from Google Fonts and other sources. For variable fonts, only the VF file ## Notable fixtures +### Grida forms theme defaults + +Inter (sans), Lora (serif), and Inconsolata (mono) are the default theme fonts used by Grida forms on the web. Useful fixtures when rendering or testing forms that rely on these theme defaults. + ### Pretendard Non-Google font actively used in Korea. Includes both static (family: "Pretendard") and variable ("Pretendard Variable"). **They do not alias** — designs may reference either; both must be supplied if used together. Good fixture for fragmented font distribution where one project ships two distinct family names. From c43b8ff63aa65298878af7baa6dc865d4498de68 Mon Sep 17 00:00:00 2001 From: Universe Date: Tue, 3 Mar 2026 21:03:23 +0900 Subject: [PATCH 05/15] add text fixtures --- fixtures/text/README.md | 17 + fixtures/text/hamlet.txt | 6696 ++++++++++++++++++++++++++++++++++++++ fixtures/text/lorem.txt | 1 + 3 files changed, 6714 insertions(+) create mode 100644 fixtures/text/README.md create mode 100644 fixtures/text/hamlet.txt create mode 100644 fixtures/text/lorem.txt diff --git a/fixtures/text/README.md b/fixtures/text/README.md new file mode 100644 index 0000000000..569415a74e --- /dev/null +++ b/fixtures/text/README.md @@ -0,0 +1,17 @@ +> text fixtures for testing and examples + +## What's included? + +Plain text samples used for layout, editing, and rendering tests. Header and footer attributions have been intentionally removed where applicable to keep fixtures focused on content. + +## Text Fixtures Attribution + +| file | source | description | url | +| ---------- | ----------------- | -------------------------------------------------------- | ---------------------------------------------------- | +| hamlet.txt | Project Gutenberg | William Shakespeare, _Hamlet_ (full play, public domain) | https://www.gutenberg.org/cache/epub/1524/pg1524.txt | +| lorem.txt | — | Lorem ipsum placeholder text | — | + +## See also + +- [Project Gutenberg](https://www.gutenberg.org/) — Free eBooks (public domain) +- [Project Gutenberg License](https://www.gutenberg.org/license) diff --git a/fixtures/text/hamlet.txt b/fixtures/text/hamlet.txt new file mode 100644 index 0000000000..f70bc6890b --- /dev/null +++ b/fixtures/text/hamlet.txt @@ -0,0 +1,6696 @@ +THE TRAGEDY OF HAMLET, PRINCE OF DENMARK + +by William Shakespeare + + + + +Contents + + ACT I + Scene I. Elsinore. A platform before the Castle + Scene II. Elsinore. A room of state in the Castle + Scene III. A room in Polonius’s house + Scene IV. The platform + Scene V. A more remote part of the Castle + + ACT II + Scene I. A room in Polonius’s house + Scene II. A room in the Castle + + ACT III + Scene I. A room in the Castle + Scene II. A hall in the Castle + Scene III. A room in the Castle + Scene IV. Another room in the Castle + + ACT IV + Scene I. A room in the Castle + Scene II. Another room in the Castle + Scene III. Another room in the Castle + Scene IV. A plain in Denmark + Scene V. Elsinore. A room in the Castle + Scene VI. Another room in the Castle + Scene VII. Another room in the Castle + + ACT V + Scene I. A churchyard + Scene II. A hall in the Castle + + + + +Dramatis Personæ + +HAMLET, Prince of Denmark +CLAUDIUS, King of Denmark, Hamlet’s uncle +The GHOST of the late king, Hamlet’s father +GERTRUDE, the Queen, Hamlet’s mother, now wife of Claudius +POLONIUS, Lord Chamberlain +LAERTES, Son to Polonius +OPHELIA, Daughter to Polonius +HORATIO, Friend to Hamlet +FORTINBRAS, Prince of Norway +VOLTEMAND, Courtier +CORNELIUS, Courtier +ROSENCRANTZ, Courtier +GUILDENSTERN, Courtier +MARCELLUS, Officer +BARNARDO, Officer +FRANCISCO, a Soldier +OSRIC, Courtier +REYNALDO, Servant to Polonius +Players +A Gentleman, Courtier +A Priest +Two Clowns, Grave-diggers +A Captain +English Ambassadors. +Lords, Ladies, Officers, Soldiers, Sailors, Messengers, and Attendants + +SCENE. Elsinore. + + + + +ACT I + +SCENE I. Elsinore. A platform before the Castle. + + +Enter Francisco and Barnardo, two sentinels. + +BARNARDO. +Who’s there? + +FRANCISCO. +Nay, answer me. Stand and unfold yourself. + +BARNARDO. +Long live the King! + +FRANCISCO. +Barnardo? + +BARNARDO. +He. + +FRANCISCO. +You come most carefully upon your hour. + +BARNARDO. +’Tis now struck twelve. Get thee to bed, Francisco. + +FRANCISCO. +For this relief much thanks. ’Tis bitter cold, +And I am sick at heart. + +BARNARDO. +Have you had quiet guard? + +FRANCISCO. +Not a mouse stirring. + +BARNARDO. +Well, good night. +If you do meet Horatio and Marcellus, +The rivals of my watch, bid them make haste. + +Enter Horatio and Marcellus. + +FRANCISCO. +I think I hear them. Stand, ho! Who is there? + +HORATIO. +Friends to this ground. + +MARCELLUS. +And liegemen to the Dane. + +FRANCISCO. +Give you good night. + +MARCELLUS. +O, farewell, honest soldier, who hath reliev’d you? + +FRANCISCO. +Barnardo has my place. Give you good-night. + +[_Exit._] + +MARCELLUS. +Holla, Barnardo! + +BARNARDO. +Say, what, is Horatio there? + +HORATIO. +A piece of him. + +BARNARDO. +Welcome, Horatio. Welcome, good Marcellus. + +MARCELLUS. +What, has this thing appear’d again tonight? + +BARNARDO. +I have seen nothing. + +MARCELLUS. +Horatio says ’tis but our fantasy, +And will not let belief take hold of him +Touching this dreaded sight, twice seen of us. +Therefore I have entreated him along +With us to watch the minutes of this night, +That if again this apparition come +He may approve our eyes and speak to it. + +HORATIO. +Tush, tush, ’twill not appear. + +BARNARDO. +Sit down awhile, +And let us once again assail your ears, +That are so fortified against our story, +What we two nights have seen. + +HORATIO. +Well, sit we down, +And let us hear Barnardo speak of this. + +BARNARDO. +Last night of all, +When yond same star that’s westward from the pole, +Had made his course t’illume that part of heaven +Where now it burns, Marcellus and myself, +The bell then beating one— + +MARCELLUS. +Peace, break thee off. Look where it comes again. + +Enter Ghost. + +BARNARDO. +In the same figure, like the King that’s dead. + +MARCELLUS. +Thou art a scholar; speak to it, Horatio. + +BARNARDO. +Looks it not like the King? Mark it, Horatio. + +HORATIO. +Most like. It harrows me with fear and wonder. + +BARNARDO +It would be spoke to. + +MARCELLUS. +Question it, Horatio. + +HORATIO. +What art thou that usurp’st this time of night, +Together with that fair and warlike form +In which the majesty of buried Denmark +Did sometimes march? By heaven I charge thee speak. + +MARCELLUS. +It is offended. + +BARNARDO. +See, it stalks away. + +HORATIO. +Stay! speak, speak! I charge thee speak! + +[_Exit Ghost._] + +MARCELLUS. +’Tis gone, and will not answer. + +BARNARDO. +How now, Horatio! You tremble and look pale. +Is not this something more than fantasy? +What think you on’t? + +HORATIO. +Before my God, I might not this believe +Without the sensible and true avouch +Of mine own eyes. + +MARCELLUS. +Is it not like the King? + +HORATIO. +As thou art to thyself: +Such was the very armour he had on +When he th’ambitious Norway combated; +So frown’d he once, when in an angry parle +He smote the sledded Polacks on the ice. +’Tis strange. + +MARCELLUS. +Thus twice before, and jump at this dead hour, +With martial stalk hath he gone by our watch. + +HORATIO. +In what particular thought to work I know not; +But in the gross and scope of my opinion, +This bodes some strange eruption to our state. + +MARCELLUS. +Good now, sit down, and tell me, he that knows, +Why this same strict and most observant watch +So nightly toils the subject of the land, +And why such daily cast of brazen cannon +And foreign mart for implements of war; +Why such impress of shipwrights, whose sore task +Does not divide the Sunday from the week. +What might be toward, that this sweaty haste +Doth make the night joint-labourer with the day: +Who is’t that can inform me? + +HORATIO. +That can I; +At least, the whisper goes so. Our last King, +Whose image even but now appear’d to us, +Was, as you know, by Fortinbras of Norway, +Thereto prick’d on by a most emulate pride, +Dar’d to the combat; in which our valiant Hamlet, +For so this side of our known world esteem’d him, +Did slay this Fortinbras; who by a seal’d compact, +Well ratified by law and heraldry, +Did forfeit, with his life, all those his lands +Which he stood seiz’d of, to the conqueror; +Against the which, a moiety competent +Was gaged by our King; which had return’d +To the inheritance of Fortinbras, +Had he been vanquisher; as by the same cov’nant +And carriage of the article design’d, +His fell to Hamlet. Now, sir, young Fortinbras, +Of unimproved mettle, hot and full, +Hath in the skirts of Norway, here and there, +Shark’d up a list of lawless resolutes, +For food and diet, to some enterprise +That hath a stomach in’t; which is no other, +As it doth well appear unto our state, +But to recover of us by strong hand +And terms compulsatory, those foresaid lands +So by his father lost. And this, I take it, +Is the main motive of our preparations, +The source of this our watch, and the chief head +Of this post-haste and rummage in the land. + +BARNARDO. +I think it be no other but e’en so: +Well may it sort that this portentous figure +Comes armed through our watch so like the King +That was and is the question of these wars. + +HORATIO. +A mote it is to trouble the mind’s eye. +In the most high and palmy state of Rome, +A little ere the mightiest Julius fell, +The graves stood tenantless and the sheeted dead +Did squeak and gibber in the Roman streets; +As stars with trains of fire and dews of blood, +Disasters in the sun; and the moist star, +Upon whose influence Neptune’s empire stands, +Was sick almost to doomsday with eclipse. +And even the like precurse of fierce events, +As harbingers preceding still the fates +And prologue to the omen coming on, +Have heaven and earth together demonstrated +Unto our climatures and countrymen. + +Re-enter Ghost. + +But, soft, behold! Lo, where it comes again! +I’ll cross it, though it blast me. Stay, illusion! +If thou hast any sound, or use of voice, +Speak to me. +If there be any good thing to be done, +That may to thee do ease, and grace to me, +Speak to me. +If thou art privy to thy country’s fate, +Which, happily, foreknowing may avoid, +O speak! +Or if thou hast uphoarded in thy life +Extorted treasure in the womb of earth, +For which, they say, you spirits oft walk in death, +Speak of it. Stay, and speak! + +[_The cock crows._] + +Stop it, Marcellus! + +MARCELLUS. +Shall I strike at it with my partisan? + +HORATIO. +Do, if it will not stand. + +BARNARDO. +’Tis here! + +HORATIO. +’Tis here! + +[_Exit Ghost._] + +MARCELLUS. +’Tis gone! +We do it wrong, being so majestical, +To offer it the show of violence, +For it is as the air, invulnerable, +And our vain blows malicious mockery. + +BARNARDO. +It was about to speak, when the cock crew. + +HORATIO. +And then it started, like a guilty thing +Upon a fearful summons. I have heard +The cock, that is the trumpet to the morn, +Doth with his lofty and shrill-sounding throat +Awake the god of day; and at his warning, +Whether in sea or fire, in earth or air, +Th’extravagant and erring spirit hies +To his confine. And of the truth herein +This present object made probation. + +MARCELLUS. +It faded on the crowing of the cock. +Some say that ever ’gainst that season comes +Wherein our Saviour’s birth is celebrated, +The bird of dawning singeth all night long; +And then, they say, no spirit dare stir abroad, +The nights are wholesome, then no planets strike, +No fairy takes, nor witch hath power to charm; +So hallow’d and so gracious is the time. + +HORATIO. +So have I heard, and do in part believe it. +But look, the morn in russet mantle clad, +Walks o’er the dew of yon high eastward hill. +Break we our watch up, and by my advice, +Let us impart what we have seen tonight +Unto young Hamlet; for upon my life, +This spirit, dumb to us, will speak to him. +Do you consent we shall acquaint him with it, +As needful in our loves, fitting our duty? + +MARCELLUS. +Let’s do’t, I pray, and I this morning know +Where we shall find him most conveniently. + +[_Exeunt._] + + SCENE II. Elsinore. A room of state in the Castle. + +Enter Claudius King of Denmark, Gertrude the Queen, Hamlet, Polonius, +Laertes, Voltemand, +Cornelius, Lords and Attendant. + +KING. +Though yet of Hamlet our dear brother’s death +The memory be green, and that it us befitted +To bear our hearts in grief, and our whole kingdom +To be contracted in one brow of woe; +Yet so far hath discretion fought with nature +That we with wisest sorrow think on him, +Together with remembrance of ourselves. +Therefore our sometime sister, now our queen, +Th’imperial jointress to this warlike state, +Have we, as ’twere with a defeated joy, +With one auspicious and one dropping eye, +With mirth in funeral, and with dirge in marriage, +In equal scale weighing delight and dole, +Taken to wife; nor have we herein barr’d +Your better wisdoms, which have freely gone +With this affair along. For all, our thanks. +Now follows, that you know young Fortinbras, +Holding a weak supposal of our worth, +Or thinking by our late dear brother’s death +Our state to be disjoint and out of frame, +Colleagued with this dream of his advantage, +He hath not fail’d to pester us with message, +Importing the surrender of those lands +Lost by his father, with all bonds of law, +To our most valiant brother. So much for him. +Now for ourself and for this time of meeting: +Thus much the business is: we have here writ +To Norway, uncle of young Fortinbras, +Who, impotent and bed-rid, scarcely hears +Of this his nephew’s purpose, to suppress +His further gait herein; in that the levies, +The lists, and full proportions are all made +Out of his subject: and we here dispatch +You, good Cornelius, and you, Voltemand, +For bearers of this greeting to old Norway, +Giving to you no further personal power +To business with the King, more than the scope +Of these dilated articles allow. +Farewell; and let your haste commend your duty. + +CORNELIUS and VOLTEMAND. +In that, and all things, will we show our duty. + +KING. +We doubt it nothing: heartily farewell. + +[_Exeunt Voltemand and Cornelius._] + +And now, Laertes, what’s the news with you? +You told us of some suit. What is’t, Laertes? +You cannot speak of reason to the Dane, +And lose your voice. What wouldst thou beg, Laertes, +That shall not be my offer, not thy asking? +The head is not more native to the heart, +The hand more instrumental to the mouth, +Than is the throne of Denmark to thy father. +What wouldst thou have, Laertes? + +LAERTES. +Dread my lord, +Your leave and favour to return to France, +From whence though willingly I came to Denmark +To show my duty in your coronation; +Yet now I must confess, that duty done, +My thoughts and wishes bend again toward France, +And bow them to your gracious leave and pardon. + +KING. +Have you your father’s leave? What says Polonius? + +POLONIUS. +He hath, my lord, wrung from me my slow leave +By laboursome petition; and at last +Upon his will I seal’d my hard consent. +I do beseech you give him leave to go. + +KING. +Take thy fair hour, Laertes; time be thine, +And thy best graces spend it at thy will! +But now, my cousin Hamlet, and my son— + +HAMLET. +[_Aside._] A little more than kin, and less than kind. + +KING. +How is it that the clouds still hang on you? + +HAMLET. +Not so, my lord, I am too much i’ the sun. + +QUEEN. +Good Hamlet, cast thy nighted colour off, +And let thine eye look like a friend on Denmark. +Do not for ever with thy vailed lids +Seek for thy noble father in the dust. +Thou know’st ’tis common, all that lives must die, +Passing through nature to eternity. + +HAMLET. +Ay, madam, it is common. + +QUEEN. +If it be, +Why seems it so particular with thee? + +HAMLET. +Seems, madam! Nay, it is; I know not seems. +’Tis not alone my inky cloak, good mother, +Nor customary suits of solemn black, +Nor windy suspiration of forc’d breath, +No, nor the fruitful river in the eye, +Nor the dejected haviour of the visage, +Together with all forms, moods, shows of grief, +That can denote me truly. These indeed seem, +For they are actions that a man might play; +But I have that within which passeth show; +These but the trappings and the suits of woe. + +KING. +’Tis sweet and commendable in your nature, Hamlet, +To give these mourning duties to your father; +But you must know, your father lost a father, +That father lost, lost his, and the survivor bound +In filial obligation, for some term +To do obsequious sorrow. But to persevere +In obstinate condolement is a course +Of impious stubbornness. ’Tis unmanly grief, +It shows a will most incorrect to heaven, +A heart unfortified, a mind impatient, +An understanding simple and unschool’d; +For what we know must be, and is as common +As any the most vulgar thing to sense, +Why should we in our peevish opposition +Take it to heart? Fie, ’tis a fault to heaven, +A fault against the dead, a fault to nature, +To reason most absurd, whose common theme +Is death of fathers, and who still hath cried, +From the first corse till he that died today, +‘This must be so.’ We pray you throw to earth +This unprevailing woe, and think of us +As of a father; for let the world take note +You are the most immediate to our throne, +And with no less nobility of love +Than that which dearest father bears his son +Do I impart toward you. For your intent +In going back to school in Wittenberg, +It is most retrograde to our desire: +And we beseech you bend you to remain +Here in the cheer and comfort of our eye, +Our chiefest courtier, cousin, and our son. + +QUEEN. +Let not thy mother lose her prayers, Hamlet. +I pray thee stay with us; go not to Wittenberg. + +HAMLET. +I shall in all my best obey you, madam. + +KING. +Why, ’tis a loving and a fair reply. +Be as ourself in Denmark. Madam, come; +This gentle and unforc’d accord of Hamlet +Sits smiling to my heart; in grace whereof, +No jocund health that Denmark drinks today +But the great cannon to the clouds shall tell, +And the King’s rouse the heaven shall bruit again, +Re-speaking earthly thunder. Come away. + +[_Exeunt all but Hamlet._] + +HAMLET. +O that this too too solid flesh would melt, +Thaw, and resolve itself into a dew! +Or that the Everlasting had not fix’d +His canon ’gainst self-slaughter. O God! O God! +How weary, stale, flat, and unprofitable +Seem to me all the uses of this world! +Fie on’t! Oh fie! ’tis an unweeded garden +That grows to seed; things rank and gross in nature +Possess it merely. That it should come to this! +But two months dead—nay, not so much, not two: +So excellent a king; that was to this +Hyperion to a satyr; so loving to my mother, +That he might not beteem the winds of heaven +Visit her face too roughly. Heaven and earth! +Must I remember? Why, she would hang on him +As if increase of appetite had grown +By what it fed on; and yet, within a month— +Let me not think on’t—Frailty, thy name is woman! +A little month, or ere those shoes were old +With which she followed my poor father’s body +Like Niobe, all tears.—Why she, even she— +O God! A beast that wants discourse of reason +Would have mourn’d longer,—married with mine uncle, +My father’s brother; but no more like my father +Than I to Hercules. Within a month, +Ere yet the salt of most unrighteous tears +Had left the flushing in her galled eyes, +She married. O most wicked speed, to post +With such dexterity to incestuous sheets! +It is not, nor it cannot come to good. +But break, my heart, for I must hold my tongue. + +Enter Horatio, Marcellus and Barnardo. + +HORATIO. +Hail to your lordship! + +HAMLET. +I am glad to see you well: +Horatio, or I do forget myself. + +HORATIO. +The same, my lord, +And your poor servant ever. + +HAMLET. +Sir, my good friend; +I’ll change that name with you: +And what make you from Wittenberg, Horatio?— +Marcellus? + +MARCELLUS. +My good lord. + +HAMLET. +I am very glad to see you.—Good even, sir.— +But what, in faith, make you from Wittenberg? + +HORATIO. +A truant disposition, good my lord. + +HAMLET. +I would not hear your enemy say so; +Nor shall you do my ear that violence, +To make it truster of your own report +Against yourself. I know you are no truant. +But what is your affair in Elsinore? +We’ll teach you to drink deep ere you depart. + +HORATIO. +My lord, I came to see your father’s funeral. + +HAMLET. +I prithee do not mock me, fellow-student. +I think it was to see my mother’s wedding. + +HORATIO. +Indeed, my lord, it follow’d hard upon. + +HAMLET. +Thrift, thrift, Horatio! The funeral bak’d meats +Did coldly furnish forth the marriage tables. +Would I had met my dearest foe in heaven +Or ever I had seen that day, Horatio. +My father,—methinks I see my father. + +HORATIO. +Where, my lord? + +HAMLET. +In my mind’s eye, Horatio. + +HORATIO. +I saw him once; he was a goodly king. + +HAMLET. +He was a man, take him for all in all, +I shall not look upon his like again. + +HORATIO. +My lord, I think I saw him yesternight. + +HAMLET. +Saw? Who? + +HORATIO. +My lord, the King your father. + +HAMLET. +The King my father! + +HORATIO. +Season your admiration for a while +With an attent ear, till I may deliver +Upon the witness of these gentlemen +This marvel to you. + +HAMLET. +For God’s love let me hear. + +HORATIO. +Two nights together had these gentlemen, +Marcellus and Barnardo, on their watch +In the dead waste and middle of the night, +Been thus encounter’d. A figure like your father, +Armed at point exactly, cap-à-pie, +Appears before them, and with solemn march +Goes slow and stately by them: thrice he walk’d +By their oppress’d and fear-surprised eyes, +Within his truncheon’s length; whilst they, distill’d +Almost to jelly with the act of fear, +Stand dumb, and speak not to him. This to me +In dreadful secrecy impart they did, +And I with them the third night kept the watch, +Where, as they had deliver’d, both in time, +Form of the thing, each word made true and good, +The apparition comes. I knew your father; +These hands are not more like. + +HAMLET. +But where was this? + +MARCELLUS. +My lord, upon the platform where we watch. + +HAMLET. +Did you not speak to it? + +HORATIO. +My lord, I did; +But answer made it none: yet once methought +It lifted up it head, and did address +Itself to motion, like as it would speak. +But even then the morning cock crew loud, +And at the sound it shrunk in haste away, +And vanish’d from our sight. + +HAMLET. +’Tis very strange. + +HORATIO. +As I do live, my honour’d lord, ’tis true; +And we did think it writ down in our duty +To let you know of it. + +HAMLET. +Indeed, indeed, sirs, but this troubles me. +Hold you the watch tonight? + +MARCELLUS and BARNARDO. +We do, my lord. + +HAMLET. +Arm’d, say you? + +Both. +Arm’d, my lord. + +HAMLET. +From top to toe? + +BOTH. +My lord, from head to foot. + +HAMLET. +Then saw you not his face? + +HORATIO. +O yes, my lord, he wore his beaver up. + +HAMLET. +What, look’d he frowningly? + +HORATIO. +A countenance more in sorrow than in anger. + +HAMLET. +Pale, or red? + +HORATIO. +Nay, very pale. + +HAMLET. +And fix’d his eyes upon you? + +HORATIO. +Most constantly. + +HAMLET. +I would I had been there. + +HORATIO. +It would have much amaz’d you. + +HAMLET. +Very like, very like. Stay’d it long? + +HORATIO. +While one with moderate haste might tell a hundred. + +MARCELLUS and BARNARDO. +Longer, longer. + +HORATIO. +Not when I saw’t. + +HAMLET. +His beard was grizzled, no? + +HORATIO. +It was, as I have seen it in his life, +A sable silver’d. + +HAMLET. +I will watch tonight; +Perchance ’twill walk again. + +HORATIO. +I warrant you it will. + +HAMLET. +If it assume my noble father’s person, +I’ll speak to it, though hell itself should gape +And bid me hold my peace. I pray you all, +If you have hitherto conceal’d this sight, +Let it be tenable in your silence still; +And whatsoever else shall hap tonight, +Give it an understanding, but no tongue. +I will requite your loves. So, fare ye well. +Upon the platform ’twixt eleven and twelve, +I’ll visit you. + +ALL. +Our duty to your honour. + +HAMLET. +Your loves, as mine to you: farewell. + +[_Exeunt Horatio, Marcellus and Barnardo._] + +My father’s spirit in arms! All is not well; +I doubt some foul play: would the night were come! +Till then sit still, my soul: foul deeds will rise, +Though all the earth o’erwhelm them, to men’s eyes. + +[_Exit._] + + SCENE III. A room in Polonius’s house. + +Enter Laertes and Ophelia. + +LAERTES. +My necessaries are embark’d. Farewell. +And, sister, as the winds give benefit +And convoy is assistant, do not sleep, +But let me hear from you. + +OPHELIA. +Do you doubt that? + +LAERTES. +For Hamlet, and the trifling of his favour, +Hold it a fashion and a toy in blood; +A violet in the youth of primy nature, +Forward, not permanent, sweet, not lasting; +The perfume and suppliance of a minute; +No more. + +OPHELIA. +No more but so? + +LAERTES. +Think it no more. +For nature crescent does not grow alone +In thews and bulk; but as this temple waxes, +The inward service of the mind and soul +Grows wide withal. Perhaps he loves you now, +And now no soil nor cautel doth besmirch +The virtue of his will; but you must fear, +His greatness weigh’d, his will is not his own; +For he himself is subject to his birth: +He may not, as unvalu’d persons do, +Carve for himself; for on his choice depends +The sanctity and health of this whole state; +And therefore must his choice be circumscrib’d +Unto the voice and yielding of that body +Whereof he is the head. Then if he says he loves you, +It fits your wisdom so far to believe it +As he in his particular act and place +May give his saying deed; which is no further +Than the main voice of Denmark goes withal. +Then weigh what loss your honour may sustain +If with too credent ear you list his songs, +Or lose your heart, or your chaste treasure open +To his unmaster’d importunity. +Fear it, Ophelia, fear it, my dear sister; +And keep you in the rear of your affection, +Out of the shot and danger of desire. +The chariest maid is prodigal enough +If she unmask her beauty to the moon. +Virtue itself ’scapes not calumnious strokes: +The canker galls the infants of the spring +Too oft before their buttons be disclos’d, +And in the morn and liquid dew of youth +Contagious blastments are most imminent. +Be wary then, best safety lies in fear. +Youth to itself rebels, though none else near. + +OPHELIA. +I shall th’effect of this good lesson keep +As watchman to my heart. But good my brother, +Do not as some ungracious pastors do, +Show me the steep and thorny way to heaven; +Whilst like a puff’d and reckless libertine +Himself the primrose path of dalliance treads, +And recks not his own rede. + +LAERTES. +O, fear me not. +I stay too long. But here my father comes. + +Enter Polonius. + +A double blessing is a double grace; +Occasion smiles upon a second leave. + +POLONIUS. +Yet here, Laertes? Aboard, aboard, for shame. +The wind sits in the shoulder of your sail, +And you are stay’d for. There, my blessing with you. + +[_Laying his hand on Laertes’s head._] + +And these few precepts in thy memory +Look thou character. Give thy thoughts no tongue, +Nor any unproportion’d thought his act. +Be thou familiar, but by no means vulgar. +Those friends thou hast, and their adoption tried, +Grapple them unto thy soul with hoops of steel; +But do not dull thy palm with entertainment +Of each new-hatch’d, unfledg’d comrade. Beware +Of entrance to a quarrel; but being in, +Bear’t that th’opposed may beware of thee. +Give every man thine ear, but few thy voice: +Take each man’s censure, but reserve thy judgement. +Costly thy habit as thy purse can buy, +But not express’d in fancy; rich, not gaudy: +For the apparel oft proclaims the man; +And they in France of the best rank and station +Are of a most select and generous chief in that. +Neither a borrower nor a lender be: +For loan oft loses both itself and friend; +And borrowing dulls the edge of husbandry. +This above all: to thine own self be true; +And it must follow, as the night the day, +Thou canst not then be false to any man. +Farewell: my blessing season this in thee. + +LAERTES. +Most humbly do I take my leave, my lord. + +POLONIUS. +The time invites you; go, your servants tend. + +LAERTES. +Farewell, Ophelia, and remember well +What I have said to you. + +OPHELIA. +’Tis in my memory lock’d, +And you yourself shall keep the key of it. + +LAERTES. +Farewell. + +[_Exit._] + +POLONIUS. +What is’t, Ophelia, he hath said to you? + +OPHELIA. +So please you, something touching the Lord Hamlet. + +POLONIUS. +Marry, well bethought: +’Tis told me he hath very oft of late +Given private time to you; and you yourself +Have of your audience been most free and bounteous. +If it be so,—as so ’tis put on me, +And that in way of caution,—I must tell you +You do not understand yourself so clearly +As it behoves my daughter and your honour. +What is between you? Give me up the truth. + +OPHELIA. +He hath, my lord, of late made many tenders +Of his affection to me. + +POLONIUS. +Affection! Pooh! You speak like a green girl, +Unsifted in such perilous circumstance. +Do you believe his tenders, as you call them? + +OPHELIA. +I do not know, my lord, what I should think. + +POLONIUS. +Marry, I’ll teach you; think yourself a baby; +That you have ta’en these tenders for true pay, +Which are not sterling. Tender yourself more dearly; +Or,—not to crack the wind of the poor phrase, +Running it thus,—you’ll tender me a fool. + +OPHELIA. +My lord, he hath importun’d me with love +In honourable fashion. + +POLONIUS. +Ay, fashion you may call it; go to, go to. + +OPHELIA. +And hath given countenance to his speech, my lord, +With almost all the holy vows of heaven. + +POLONIUS. +Ay, springes to catch woodcocks. I do know, +When the blood burns, how prodigal the soul +Lends the tongue vows: these blazes, daughter, +Giving more light than heat, extinct in both, +Even in their promise, as it is a-making, +You must not take for fire. From this time +Be something scanter of your maiden presence; +Set your entreatments at a higher rate +Than a command to parley. For Lord Hamlet, +Believe so much in him that he is young; +And with a larger tether may he walk +Than may be given you. In few, Ophelia, +Do not believe his vows; for they are brokers, +Not of that dye which their investments show, +But mere implorators of unholy suits, +Breathing like sanctified and pious bawds, +The better to beguile. This is for all: +I would not, in plain terms, from this time forth +Have you so slander any moment leisure +As to give words or talk with the Lord Hamlet. +Look to’t, I charge you; come your ways. + +OPHELIA. +I shall obey, my lord. + +[_Exeunt._] + + SCENE IV. The platform. + +Enter Hamlet, Horatio and Marcellus. + +HAMLET. +The air bites shrewdly; it is very cold. + +HORATIO. +It is a nipping and an eager air. + +HAMLET. +What hour now? + +HORATIO. +I think it lacks of twelve. + +MARCELLUS. +No, it is struck. + +HORATIO. +Indeed? I heard it not. It then draws near the season +Wherein the spirit held his wont to walk. + +[_A flourish of trumpets, and ordnance shot off within._] + +What does this mean, my lord? + +HAMLET. +The King doth wake tonight and takes his rouse, +Keeps wassail, and the swaggering upspring reels; +And as he drains his draughts of Rhenish down, +The kettle-drum and trumpet thus bray out +The triumph of his pledge. + +HORATIO. +Is it a custom? + +HAMLET. +Ay marry is’t; +And to my mind, though I am native here, +And to the manner born, it is a custom +More honour’d in the breach than the observance. +This heavy-headed revel east and west +Makes us traduc’d and tax’d of other nations: +They clepe us drunkards, and with swinish phrase +Soil our addition; and indeed it takes +From our achievements, though perform’d at height, +The pith and marrow of our attribute. +So oft it chances in particular men +That for some vicious mole of nature in them, +As in their birth, wherein they are not guilty, +Since nature cannot choose his origin, +By their o’ergrowth of some complexion, +Oft breaking down the pales and forts of reason; +Or by some habit, that too much o’erleavens +The form of plausive manners;—that these men, +Carrying, I say, the stamp of one defect, +Being Nature’s livery or Fortune’s star,— +His virtues else,—be they as pure as grace, +As infinite as man may undergo, +Shall in the general censure take corruption +From that particular fault. The dram of evil +Doth all the noble substance of a doubt +To his own scandal. + +HORATIO. +Look, my lord, it comes! + +Enter Ghost. + +HAMLET. +Angels and ministers of grace defend us! +Be thou a spirit of health or goblin damn’d, +Bring with thee airs from heaven or blasts from hell, +Be thy intents wicked or charitable, +Thou com’st in such a questionable shape +That I will speak to thee. I’ll call thee Hamlet, +King, father, royal Dane. O, answer me! +Let me not burst in ignorance; but tell +Why thy canoniz’d bones, hearsed in death, +Have burst their cerements; why the sepulchre, +Wherein we saw thee quietly inurn’d, +Hath op’d his ponderous and marble jaws +To cast thee up again! What may this mean, +That thou, dead corse, again in complete steel, +Revisit’st thus the glimpses of the moon, +Making night hideous, and we fools of nature +So horridly to shake our disposition +With thoughts beyond the reaches of our souls? +Say, why is this? Wherefore? What should we do? + +[_Ghost beckons Hamlet._] + +HORATIO. +It beckons you to go away with it, +As if it some impartment did desire +To you alone. + +MARCELLUS. +Look with what courteous action +It waves you to a more removed ground. +But do not go with it. + +HORATIO. +No, by no means. + +HAMLET. +It will not speak; then will I follow it. + +HORATIO. +Do not, my lord. + +HAMLET. +Why, what should be the fear? +I do not set my life at a pin’s fee; +And for my soul, what can it do to that, +Being a thing immortal as itself? +It waves me forth again. I’ll follow it. + +HORATIO. +What if it tempt you toward the flood, my lord, +Or to the dreadful summit of the cliff +That beetles o’er his base into the sea, +And there assume some other horrible form +Which might deprive your sovereignty of reason, +And draw you into madness? Think of it. +The very place puts toys of desperation, +Without more motive, into every brain +That looks so many fathoms to the sea +And hears it roar beneath. + +HAMLET. +It waves me still. +Go on, I’ll follow thee. + +MARCELLUS. +You shall not go, my lord. + +HAMLET. +Hold off your hands. + +HORATIO. +Be rul’d; you shall not go. + +HAMLET. +My fate cries out, +And makes each petty artery in this body +As hardy as the Nemean lion’s nerve. + +[_Ghost beckons._] + +Still am I call’d. Unhand me, gentlemen. + +[_Breaking free from them._] + +By heaven, I’ll make a ghost of him that lets me. +I say, away!—Go on, I’ll follow thee. + +[_Exeunt Ghost and Hamlet._] + +HORATIO. +He waxes desperate with imagination. + +MARCELLUS. +Let’s follow; ’tis not fit thus to obey him. + +HORATIO. +Have after. To what issue will this come? + +MARCELLUS. +Something is rotten in the state of Denmark. + +HORATIO. +Heaven will direct it. + +MARCELLUS. +Nay, let’s follow him. + +[_Exeunt._] + + SCENE V. A more remote part of the Castle. + +Enter Ghost and Hamlet. + +HAMLET. +Whither wilt thou lead me? Speak, I’ll go no further. + +GHOST. +Mark me. + +HAMLET. +I will. + +GHOST. +My hour is almost come, +When I to sulph’rous and tormenting flames +Must render up myself. + +HAMLET. +Alas, poor ghost! + +GHOST. +Pity me not, but lend thy serious hearing +To what I shall unfold. + +HAMLET. +Speak, I am bound to hear. + +GHOST. +So art thou to revenge, when thou shalt hear. + +HAMLET. +What? + +GHOST. +I am thy father’s spirit, +Doom’d for a certain term to walk the night, +And for the day confin’d to fast in fires, +Till the foul crimes done in my days of nature +Are burnt and purg’d away. But that I am forbid +To tell the secrets of my prison-house, +I could a tale unfold whose lightest word +Would harrow up thy soul; freeze thy young blood, +Make thy two eyes like stars start from their spheres, +Thy knotted and combined locks to part, +And each particular hair to stand on end +Like quills upon the fretful porpentine. +But this eternal blazon must not be +To ears of flesh and blood. List, list, O, list! +If thou didst ever thy dear father love— + +HAMLET. +O God! + +GHOST. +Revenge his foul and most unnatural murder. + +HAMLET. +Murder! + +GHOST. +Murder most foul, as in the best it is; +But this most foul, strange, and unnatural. + +HAMLET. +Haste me to know’t, that I, with wings as swift +As meditation or the thoughts of love +May sweep to my revenge. + +GHOST. +I find thee apt; +And duller shouldst thou be than the fat weed +That rots itself in ease on Lethe wharf, +Wouldst thou not stir in this. Now, Hamlet, hear. +’Tis given out that, sleeping in my orchard, +A serpent stung me; so the whole ear of Denmark +Is by a forged process of my death +Rankly abus’d; but know, thou noble youth, +The serpent that did sting thy father’s life +Now wears his crown. + +HAMLET. +O my prophetic soul! +Mine uncle! + +GHOST. +Ay, that incestuous, that adulterate beast, +With witchcraft of his wit, with traitorous gifts,— +O wicked wit, and gifts, that have the power +So to seduce!—won to his shameful lust +The will of my most seeming-virtuous queen. +O Hamlet, what a falling off was there, +From me, whose love was of that dignity +That it went hand in hand even with the vow +I made to her in marriage; and to decline +Upon a wretch whose natural gifts were poor +To those of mine. But virtue, as it never will be mov’d, +Though lewdness court it in a shape of heaven; +So lust, though to a radiant angel link’d, +Will sate itself in a celestial bed +And prey on garbage. +But soft! methinks I scent the morning air; +Brief let me be. Sleeping within my orchard, +My custom always of the afternoon, +Upon my secure hour thy uncle stole +With juice of cursed hebenon in a vial, +And in the porches of my ears did pour +The leperous distilment, whose effect +Holds such an enmity with blood of man +That swift as quicksilver it courses through +The natural gates and alleys of the body; +And with a sudden vigour it doth posset +And curd, like eager droppings into milk, +The thin and wholesome blood. So did it mine; +And a most instant tetter bark’d about, +Most lazar-like, with vile and loathsome crust +All my smooth body. +Thus was I, sleeping, by a brother’s hand, +Of life, of crown, of queen at once dispatch’d: +Cut off even in the blossoms of my sin, +Unhous’led, disappointed, unanel’d; +No reckoning made, but sent to my account +With all my imperfections on my head. +O horrible! O horrible! most horrible! +If thou hast nature in thee, bear it not; +Let not the royal bed of Denmark be +A couch for luxury and damned incest. +But howsoever thou pursu’st this act, +Taint not thy mind, nor let thy soul contrive +Against thy mother aught; leave her to heaven, +And to those thorns that in her bosom lodge, +To prick and sting her. Fare thee well at once! +The glow-worm shows the matin to be near, +And ’gins to pale his uneffectual fire. +Adieu, adieu, adieu. Remember me. + +[_Exit._] + +HAMLET. +O all you host of heaven! O earth! What else? +And shall I couple hell? O, fie! Hold, my heart; +And you, my sinews, grow not instant old, +But bear me stiffly up. Remember thee? +Ay, thou poor ghost, while memory holds a seat +In this distracted globe. Remember thee? +Yea, from the table of my memory +I’ll wipe away all trivial fond records, +All saws of books, all forms, all pressures past, +That youth and observation copied there; +And thy commandment all alone shall live +Within the book and volume of my brain, +Unmix’d with baser matter. Yes, by heaven! +O most pernicious woman! +O villain, villain, smiling damned villain! +My tables. Meet it is I set it down, +That one may smile, and smile, and be a villain! +At least I am sure it may be so in Denmark. + +[_Writing._] + +So, uncle, there you are. Now to my word; +It is ‘Adieu, adieu, remember me.’ +I have sworn’t. + +HORATIO and MARCELLUS. +[_Within._] My lord, my lord. + +MARCELLUS. +[_Within._] Lord Hamlet. + +HORATIO. +[_Within._] Heaven secure him. + +HAMLET. +So be it! + +MARCELLUS. +[_Within._] Illo, ho, ho, my lord! + +HAMLET. +Hillo, ho, ho, boy! Come, bird, come. + +Enter Horatio and Marcellus. + +MARCELLUS. +How is’t, my noble lord? + +HORATIO. +What news, my lord? + +HAMLET. +O, wonderful! + +HORATIO. +Good my lord, tell it. + +HAMLET. +No, you’ll reveal it. + +HORATIO. +Not I, my lord, by heaven. + +MARCELLUS. +Nor I, my lord. + +HAMLET. +How say you then, would heart of man once think it?— +But you’ll be secret? + +HORATIO and MARCELLUS. +Ay, by heaven, my lord. + +HAMLET. +There’s ne’er a villain dwelling in all Denmark +But he’s an arrant knave. + +HORATIO. +There needs no ghost, my lord, come from the grave +To tell us this. + +HAMLET. +Why, right; you are i’ the right; +And so, without more circumstance at all, +I hold it fit that we shake hands and part: +You, as your business and desire shall point you,— +For every man hath business and desire, +Such as it is;—and for my own poor part, +Look you, I’ll go pray. + +HORATIO. +These are but wild and whirling words, my lord. + +HAMLET. +I’m sorry they offend you, heartily; +Yes faith, heartily. + +HORATIO. +There’s no offence, my lord. + +HAMLET. +Yes, by Saint Patrick, but there is, Horatio, +And much offence too. Touching this vision here, +It is an honest ghost, that let me tell you. +For your desire to know what is between us, +O’ermaster’t as you may. And now, good friends, +As you are friends, scholars, and soldiers, +Give me one poor request. + +HORATIO. +What is’t, my lord? We will. + +HAMLET. +Never make known what you have seen tonight. + +HORATIO and MARCELLUS. +My lord, we will not. + +HAMLET. +Nay, but swear’t. + +HORATIO. +In faith, my lord, not I. + +MARCELLUS. +Nor I, my lord, in faith. + +HAMLET. +Upon my sword. + +MARCELLUS. +We have sworn, my lord, already. + +HAMLET. +Indeed, upon my sword, indeed. + +GHOST. +[_Cries under the stage._] Swear. + +HAMLET. +Ha, ha boy, sayst thou so? Art thou there, truepenny? +Come on, you hear this fellow in the cellarage. +Consent to swear. + +HORATIO. +Propose the oath, my lord. + +HAMLET. +Never to speak of this that you have seen. +Swear by my sword. + +GHOST. +[_Beneath._] Swear. + +HAMLET. +_Hic et ubique?_ Then we’ll shift our ground. +Come hither, gentlemen, +And lay your hands again upon my sword. +Never to speak of this that you have heard. +Swear by my sword. + +GHOST. +[_Beneath._] Swear. + +HAMLET. +Well said, old mole! Canst work i’ th’earth so fast? +A worthy pioner! Once more remove, good friends. + +HORATIO. +O day and night, but this is wondrous strange. + +HAMLET. +And therefore as a stranger give it welcome. +There are more things in heaven and earth, Horatio, +Than are dreamt of in your philosophy. But come, +Here, as before, never, so help you mercy, +How strange or odd soe’er I bear myself,— +As I perchance hereafter shall think meet +To put an antic disposition on— +That you, at such times seeing me, never shall, +With arms encumber’d thus, or this head-shake, +Or by pronouncing of some doubtful phrase, +As ‘Well, we know’, or ‘We could and if we would’, +Or ‘If we list to speak’; or ‘There be and if they might’, +Or such ambiguous giving out, to note +That you know aught of me:—this not to do. +So grace and mercy at your most need help you, +Swear. + +GHOST. +[_Beneath._] Swear. + +HAMLET. +Rest, rest, perturbed spirit. So, gentlemen, +With all my love I do commend me to you; +And what so poor a man as Hamlet is +May do t’express his love and friending to you, +God willing, shall not lack. Let us go in together, +And still your fingers on your lips, I pray. +The time is out of joint. O cursed spite, +That ever I was born to set it right. +Nay, come, let’s go together. + +[_Exeunt._] + + + + +ACT II + +SCENE I. A room in Polonius’s house. + + +Enter Polonius and Reynaldo. + +POLONIUS. +Give him this money and these notes, Reynaldo. + +REYNALDO. +I will, my lord. + +POLONIUS. +You shall do marvellous wisely, good Reynaldo, +Before you visit him, to make inquiry +Of his behaviour. + +REYNALDO. +My lord, I did intend it. + +POLONIUS. +Marry, well said; very well said. Look you, sir, +Enquire me first what Danskers are in Paris; +And how, and who, what means, and where they keep, +What company, at what expense; and finding +By this encompassment and drift of question, +That they do know my son, come you more nearer +Than your particular demands will touch it. +Take you as ’twere some distant knowledge of him, +As thus, ‘I know his father and his friends, +And in part him’—do you mark this, Reynaldo? + +REYNALDO. +Ay, very well, my lord. + +POLONIUS. +‘And in part him, but,’ you may say, ‘not well; +But if’t be he I mean, he’s very wild; +Addicted so and so;’ and there put on him +What forgeries you please; marry, none so rank +As may dishonour him; take heed of that; +But, sir, such wanton, wild, and usual slips +As are companions noted and most known +To youth and liberty. + +REYNALDO. +As gaming, my lord? + +POLONIUS. +Ay, or drinking, fencing, swearing, +Quarrelling, drabbing. You may go so far. + +REYNALDO. +My lord, that would dishonour him. + +POLONIUS. +Faith no, as you may season it in the charge. +You must not put another scandal on him, +That he is open to incontinency; +That’s not my meaning: but breathe his faults so quaintly +That they may seem the taints of liberty; +The flash and outbreak of a fiery mind, +A savageness in unreclaimed blood, +Of general assault. + +REYNALDO. +But my good lord— + +POLONIUS. +Wherefore should you do this? + +REYNALDO. +Ay, my lord, I would know that. + +POLONIUS. +Marry, sir, here’s my drift, +And I believe it is a fetch of warrant. +You laying these slight sullies on my son, +As ’twere a thing a little soil’d i’ th’ working, +Mark you, +Your party in converse, him you would sound, +Having ever seen in the prenominate crimes +The youth you breathe of guilty, be assur’d +He closes with you in this consequence; +‘Good sir,’ or so; or ‘friend,’ or ‘gentleman’— +According to the phrase or the addition +Of man and country. + +REYNALDO. +Very good, my lord. + +POLONIUS. +And then, sir, does he this,— +He does—What was I about to say? +By the mass, I was about to say something. Where did I leave? + +REYNALDO. +At ‘closes in the consequence.’ +At ‘friend or so,’ and ‘gentleman.’ + +POLONIUS. +At ‘closes in the consequence’ ay, marry! +He closes with you thus: ‘I know the gentleman, +I saw him yesterday, or t’other day, +Or then, or then, with such and such; and, as you say, +There was he gaming, there o’ertook in’s rouse, +There falling out at tennis’: or perchance, +‘I saw him enter such a house of sale’— +_Videlicet_, a brothel, or so forth. See you now; +Your bait of falsehood takes this carp of truth; +And thus do we of wisdom and of reach, +With windlasses, and with assays of bias, +By indirections find directions out. +So by my former lecture and advice +Shall you my son. You have me, have you not? + +REYNALDO. +My lord, I have. + +POLONIUS. +God b’ wi’ you, fare you well. + +REYNALDO. +Good my lord. + +POLONIUS. +Observe his inclination in yourself. + +REYNALDO. +I shall, my lord. + +POLONIUS. +And let him ply his music. + +REYNALDO. +Well, my lord. + +POLONIUS. +Farewell. + +[_Exit Reynaldo._] + +Enter Ophelia. + +How now, Ophelia, what’s the matter? + +OPHELIA. +Alas, my lord, I have been so affrighted. + +POLONIUS. +With what, in the name of God? + +OPHELIA. +My lord, as I was sewing in my chamber, +Lord Hamlet, with his doublet all unbrac’d, +No hat upon his head, his stockings foul’d, +Ungart’red, and down-gyved to his ankle, +Pale as his shirt, his knees knocking each other, +And with a look so piteous in purport +As if he had been loosed out of hell +To speak of horrors, he comes before me. + +POLONIUS. +Mad for thy love? + +OPHELIA. +My lord, I do not know, but truly I do fear it. + +POLONIUS. +What said he? + +OPHELIA. +He took me by the wrist and held me hard; +Then goes he to the length of all his arm; +And with his other hand thus o’er his brow, +He falls to such perusal of my face +As he would draw it. Long stay’d he so, +At last,—a little shaking of mine arm, +And thrice his head thus waving up and down, +He rais’d a sigh so piteous and profound +As it did seem to shatter all his bulk +And end his being. That done, he lets me go, +And with his head over his shoulder turn’d +He seem’d to find his way without his eyes, +For out o’ doors he went without their help, +And to the last bended their light on me. + +POLONIUS. +Come, go with me. I will go seek the King. +This is the very ecstasy of love, +Whose violent property fordoes itself, +And leads the will to desperate undertakings, +As oft as any passion under heaven +That does afflict our natures. I am sorry,— +What, have you given him any hard words of late? + +OPHELIA. +No, my good lord; but as you did command, +I did repel his letters and denied +His access to me. + +POLONIUS. +That hath made him mad. +I am sorry that with better heed and judgement +I had not quoted him. I fear’d he did but trifle, +And meant to wreck thee. But beshrew my jealousy! +It seems it is as proper to our age +To cast beyond ourselves in our opinions +As it is common for the younger sort +To lack discretion. Come, go we to the King. +This must be known, which, being kept close, might move +More grief to hide than hate to utter love. + +[_Exeunt._] + + SCENE II. A room in the Castle. + +Enter King, Queen, Rosencrantz, Guildenstern and Attendants. + +KING. +Welcome, dear Rosencrantz and Guildenstern. +Moreover that we much did long to see you, +The need we have to use you did provoke +Our hasty sending. Something have you heard +Of Hamlet’s transformation; so I call it, +Since nor th’exterior nor the inward man +Resembles that it was. What it should be, +More than his father’s death, that thus hath put him +So much from th’understanding of himself, +I cannot dream of. I entreat you both +That, being of so young days brought up with him, +And since so neighbour’d to his youth and humour, +That you vouchsafe your rest here in our court +Some little time, so by your companies +To draw him on to pleasures and to gather, +So much as from occasion you may glean, +Whether aught to us unknown afflicts him thus +That, open’d, lies within our remedy. + +QUEEN. +Good gentlemen, he hath much talk’d of you, +And sure I am, two men there are not living +To whom he more adheres. If it will please you +To show us so much gentry and good will +As to expend your time with us awhile, +For the supply and profit of our hope, +Your visitation shall receive such thanks +As fits a king’s remembrance. + +ROSENCRANTZ. +Both your majesties +Might, by the sovereign power you have of us, +Put your dread pleasures more into command +Than to entreaty. + +GUILDENSTERN. +We both obey, +And here give up ourselves, in the full bent, +To lay our service freely at your feet +To be commanded. + +KING. +Thanks, Rosencrantz and gentle Guildenstern. + +QUEEN. +Thanks, Guildenstern and gentle Rosencrantz. +And I beseech you instantly to visit +My too much changed son. Go, some of you, +And bring these gentlemen where Hamlet is. + +GUILDENSTERN. +Heavens make our presence and our practices +Pleasant and helpful to him. + +QUEEN. +Ay, amen. + +[_Exeunt Rosencrantz, Guildenstern and some Attendants._] + +Enter Polonius. + +POLONIUS. +Th’ambassadors from Norway, my good lord, +Are joyfully return’d. + +KING. +Thou still hast been the father of good news. + +POLONIUS. +Have I, my lord? Assure you, my good liege, +I hold my duty, as I hold my soul, +Both to my God and to my gracious King: +And I do think,—or else this brain of mine +Hunts not the trail of policy so sure +As it hath us’d to do—that I have found +The very cause of Hamlet’s lunacy. + +KING. +O speak of that, that do I long to hear. + +POLONIUS. +Give first admittance to th’ambassadors; +My news shall be the fruit to that great feast. + +KING. +Thyself do grace to them, and bring them in. + +[_Exit Polonius._] + +He tells me, my sweet queen, that he hath found +The head and source of all your son’s distemper. + +QUEEN. +I doubt it is no other but the main, +His father’s death and our o’erhasty marriage. + +KING. +Well, we shall sift him. + +Enter Polonius with Voltemand and Cornelius. + +Welcome, my good friends! +Say, Voltemand, what from our brother Norway? + +VOLTEMAND. +Most fair return of greetings and desires. +Upon our first, he sent out to suppress +His nephew’s levies, which to him appear’d +To be a preparation ’gainst the Polack; +But better look’d into, he truly found +It was against your Highness; whereat griev’d, +That so his sickness, age, and impotence +Was falsely borne in hand, sends out arrests +On Fortinbras; which he, in brief, obeys, +Receives rebuke from Norway; and in fine, +Makes vow before his uncle never more +To give th’assay of arms against your Majesty. +Whereon old Norway, overcome with joy, +Gives him three thousand crowns in annual fee, +And his commission to employ those soldiers +So levied as before, against the Polack: +With an entreaty, herein further shown, +[_Gives a paper._] +That it might please you to give quiet pass +Through your dominions for this enterprise, +On such regards of safety and allowance +As therein are set down. + +KING. +It likes us well; +And at our more consider’d time we’ll read, +Answer, and think upon this business. +Meantime we thank you for your well-took labour. +Go to your rest, at night we’ll feast together:. +Most welcome home. + +[_Exeunt Voltemand and Cornelius._] + +POLONIUS. +This business is well ended. +My liege and madam, to expostulate +What majesty should be, what duty is, +Why day is day, night night, and time is time +Were nothing but to waste night, day and time. +Therefore, since brevity is the soul of wit, +And tediousness the limbs and outward flourishes, +I will be brief. Your noble son is mad. +Mad call I it; for to define true madness, +What is’t but to be nothing else but mad? +But let that go. + +QUEEN. +More matter, with less art. + +POLONIUS. +Madam, I swear I use no art at all. +That he is mad, ’tis true: ’tis true ’tis pity; +And pity ’tis ’tis true. A foolish figure, +But farewell it, for I will use no art. +Mad let us grant him then. And now remains +That we find out the cause of this effect, +Or rather say, the cause of this defect, +For this effect defective comes by cause. +Thus it remains, and the remainder thus. Perpend, +I have a daughter—have whilst she is mine— +Who in her duty and obedience, mark, +Hath given me this. Now gather, and surmise. +[_Reads._] +_To the celestial, and my soul’s idol, the most beautified Ophelia_— +That’s an ill phrase, a vile phrase; ‘beautified’ is a vile +phrase: but you shall hear. +[_Reads._] +_these; in her excellent white bosom, these, &c._ + +QUEEN. +Came this from Hamlet to her? + +POLONIUS. +Good madam, stay awhile; I will be faithful. +[_Reads._] + _Doubt thou the stars are fire, + Doubt that the sun doth move, + Doubt truth to be a liar, + But never doubt I love. +O dear Ophelia, I am ill at these numbers. I have not art to reckon my +groans. But that I love thee best, O most best, believe it. Adieu. + Thine evermore, most dear lady, whilst this machine is to him, + HAMLET._ +This in obedience hath my daughter show’d me; +And more above, hath his solicitings, +As they fell out by time, by means, and place, +All given to mine ear. + +KING. +But how hath she receiv’d his love? + +POLONIUS. +What do you think of me? + +KING. +As of a man faithful and honourable. + +POLONIUS. +I would fain prove so. But what might you think, +When I had seen this hot love on the wing, +As I perceiv’d it, I must tell you that, +Before my daughter told me, what might you, +Or my dear Majesty your queen here, think, +If I had play’d the desk or table-book, +Or given my heart a winking, mute and dumb, +Or look’d upon this love with idle sight, +What might you think? No, I went round to work, +And my young mistress thus I did bespeak: +‘Lord Hamlet is a prince, out of thy star. +This must not be.’ And then I precepts gave her, +That she should lock herself from his resort, +Admit no messengers, receive no tokens. +Which done, she took the fruits of my advice, +And he, repulsed,—a short tale to make— +Fell into a sadness, then into a fast, +Thence to a watch, thence into a weakness, +Thence to a lightness, and, by this declension, +Into the madness wherein now he raves, +And all we wail for. + +KING. +Do you think ’tis this? + +QUEEN. +It may be, very likely. + +POLONIUS. +Hath there been such a time, I’d fain know that, +That I have positively said ‘’Tis so,’ +When it prov’d otherwise? + +KING. +Not that I know. + +POLONIUS. +Take this from this, if this be otherwise. +[_Points to his head and shoulder._] +If circumstances lead me, I will find +Where truth is hid, though it were hid indeed +Within the centre. + +KING. +How may we try it further? + +POLONIUS. +You know sometimes he walks four hours together +Here in the lobby. + +QUEEN. +So he does indeed. + +POLONIUS. +At such a time I’ll loose my daughter to him. +Be you and I behind an arras then, +Mark the encounter. If he love her not, +And be not from his reason fall’n thereon, +Let me be no assistant for a state, +But keep a farm and carters. + +KING. +We will try it. + +Enter Hamlet, reading. + +QUEEN. +But look where sadly the poor wretch comes reading. + +POLONIUS. +Away, I do beseech you, both away +I’ll board him presently. O, give me leave. + +[_Exeunt King, Queen and Attendants._] + +How does my good Lord Hamlet? + +HAMLET. +Well, God-a-mercy. + +POLONIUS. +Do you know me, my lord? + +HAMLET. +Excellent well. You are a fishmonger. + +POLONIUS. +Not I, my lord. + +HAMLET. +Then I would you were so honest a man. + +POLONIUS. +Honest, my lord? + +HAMLET. +Ay sir, to be honest, as this world goes, is to be one man picked out +of ten thousand. + +POLONIUS. +That’s very true, my lord. + +HAMLET. +For if the sun breed maggots in a dead dog, being a good kissing +carrion,— +Have you a daughter? + +POLONIUS. +I have, my lord. + +HAMLET. +Let her not walk i’ th’ sun. Conception is a blessing, but not as your +daughter may conceive. Friend, look to’t. + +POLONIUS. +How say you by that? [_Aside._] Still harping on my daughter. Yet he +knew me not at first; he said I was a fishmonger. He is far gone, far +gone. And truly in my youth I suffered much extremity for love; very +near this. I’ll speak to him again.—What do you read, my lord? + +HAMLET. +Words, words, words. + +POLONIUS. +What is the matter, my lord? + +HAMLET. +Between who? + +POLONIUS. +I mean the matter that you read, my lord. + +HAMLET. +Slanders, sir. For the satirical slave says here that old men have grey +beards; that their faces are wrinkled; their eyes purging thick amber +and plum-tree gum; and that they have a plentiful lack of wit, together +with most weak hams. All which, sir, though I most powerfully and +potently believe, yet I hold it not honesty to have it thus set down. +For you yourself, sir, should be old as I am, if like a crab you could +go backward. + +POLONIUS. +[_Aside._] Though this be madness, yet there is method in’t.— +Will you walk out of the air, my lord? + +HAMLET. +Into my grave? + +POLONIUS. +Indeed, that is out o’ the air. [_Aside._] How pregnant sometimes his +replies are! A happiness that often madness hits on, which reason and +sanity could not so prosperously be delivered of. I will leave him and +suddenly contrive the means of meeting between him and my daughter. +My honourable lord, I will most humbly take my leave of you. + +HAMLET. +You cannot, sir, take from me anything that I will more willingly part +withal, except my life, except my life, except my life. + +POLONIUS. +Fare you well, my lord. + +HAMLET. +These tedious old fools. + +Enter Rosencrantz and Guildenstern. + +POLONIUS. +You go to seek the Lord Hamlet; there he is. + +ROSENCRANTZ. +[_To Polonius._] God save you, sir. + +[_Exit Polonius._] + +GUILDENSTERN. +My honoured lord! + +ROSENCRANTZ. +My most dear lord! + +HAMLET. +My excellent good friends! How dost thou, Guildenstern? Ah, +Rosencrantz. Good lads, how do ye both? + +ROSENCRANTZ. +As the indifferent children of the earth. + +GUILDENSTERN. +Happy in that we are not over-happy; +On Fortune’s cap we are not the very button. + +HAMLET. +Nor the soles of her shoe? + +ROSENCRANTZ. +Neither, my lord. + +HAMLET. +Then you live about her waist, or in the middle of her favours? + +GUILDENSTERN. +Faith, her privates we. + +HAMLET. +In the secret parts of Fortune? O, most true; she is a strumpet. What’s +the news? + +ROSENCRANTZ. +None, my lord, but that the world’s grown honest. + +HAMLET. +Then is doomsday near. But your news is not true. Let me question more +in particular. What have you, my good friends, deserved at the hands of +Fortune, that she sends you to prison hither? + +GUILDENSTERN. +Prison, my lord? + +HAMLET. +Denmark’s a prison. + +ROSENCRANTZ. +Then is the world one. + +HAMLET. +A goodly one; in which there are many confines, wards, and dungeons, +Denmark being one o’ th’ worst. + +ROSENCRANTZ. +We think not so, my lord. + +HAMLET. +Why, then ’tis none to you; for there is nothing either good or bad but +thinking makes it so. To me it is a prison. + +ROSENCRANTZ. +Why, then your ambition makes it one; ’tis too narrow for your mind. + +HAMLET. +O God, I could be bounded in a nutshell, and count myself a king of +infinite space, were it not that I have bad dreams. + +GUILDENSTERN. +Which dreams, indeed, are ambition; for the very substance of the +ambitious is merely the shadow of a dream. + +HAMLET. +A dream itself is but a shadow. + +ROSENCRANTZ. +Truly, and I hold ambition of so airy and light a quality that it is +but a shadow’s shadow. + +HAMLET. +Then are our beggars bodies, and our monarchs and outstretch’d heroes +the beggars’ shadows. Shall we to th’ court? For, by my fay, I cannot +reason. + +ROSENCRANTZ and GUILDENSTERN. +We’ll wait upon you. + +HAMLET. +No such matter. I will not sort you with the rest of my servants; for, +to speak to you like an honest man, I am most dreadfully attended. But, +in the beaten way of friendship, what make you at Elsinore? + +ROSENCRANTZ. +To visit you, my lord, no other occasion. + +HAMLET. +Beggar that I am, I am even poor in thanks; but I thank you. And sure, +dear friends, my thanks are too dear a halfpenny. Were you not sent +for? Is it your own inclining? Is it a free visitation? Come, deal +justly with me. Come, come; nay, speak. + +GUILDENSTERN. +What should we say, my lord? + +HAMLET. +Why, anything. But to the purpose. You were sent for; and there is a +kind of confession in your looks, which your modesties have not craft +enough to colour. I know the good King and Queen have sent for you. + +ROSENCRANTZ. +To what end, my lord? + +HAMLET. +That you must teach me. But let me conjure you, by the rights of our +fellowship, by the consonancy of our youth, by the obligation of our +ever-preserved love, and by what more dear a better proposer could +charge you withal, be even and direct with me, whether you were sent +for or no. + +ROSENCRANTZ. +[_To Guildenstern._] What say you? + +HAMLET. +[_Aside._] Nay, then I have an eye of you. If you love me, hold not +off. + +GUILDENSTERN. +My lord, we were sent for. + +HAMLET. +I will tell you why; so shall my anticipation prevent your discovery, +and your secrecy to the King and Queen moult no feather. I have of +late, but wherefore I know not, lost all my mirth, forgone all custom +of exercises; and indeed, it goes so heavily with my disposition that +this goodly frame the earth, seems to me a sterile promontory; this +most excellent canopy the air, look you, this brave o’erhanging +firmament, this majestical roof fretted with golden fire, why, it +appears no other thing to me than a foul and pestilent congregation of +vapours. What a piece of work is man, how noble in reason, how infinite +in faculties, in form and moving, how express and admirable; in action +how like an angel, in apprehension, how like a god: the beauty of the +world, the paragon of animals. And yet, to me, what is this +quintessence of dust? Man delights not me; no, nor woman neither, +though by your smiling you seem to say so. + +ROSENCRANTZ. +My lord, there was no such stuff in my thoughts. + +HAMLET. +Why did you laugh then, when I said ‘Man delights not me’? + +ROSENCRANTZ. +To think, my lord, if you delight not in man, what Lenten entertainment +the players shall receive from you. We coted them on the way, and +hither are they coming to offer you service. + +HAMLET. +He that plays the king shall be welcome,—his Majesty shall have tribute +of me; the adventurous knight shall use his foil and target; the lover +shall not sigh gratis, the humorous man shall end his part in peace; +the clown shall make those laugh whose lungs are tickle o’ th’ sere; +and the lady shall say her mind freely, or the blank verse shall halt +for’t. What players are they? + +ROSENCRANTZ. +Even those you were wont to take such delight in—the tragedians of the +city. + +HAMLET. +How chances it they travel? Their residence, both in reputation and +profit, was better both ways. + +ROSENCRANTZ. +I think their inhibition comes by the means of the late innovation. + +HAMLET. +Do they hold the same estimation they did when I was in the city? Are +they so followed? + +ROSENCRANTZ. +No, indeed, they are not. + +HAMLET. +How comes it? Do they grow rusty? + +ROSENCRANTZ. +Nay, their endeavour keeps in the wonted pace; but there is, sir, an +aerie of children, little eyases, that cry out on the top of question, +and are most tyrannically clapped for’t. These are now the fashion, and +so berattle the common stages—so they call them—that many wearing +rapiers are afraid of goose-quills and dare scarce come thither. + +HAMLET. +What, are they children? Who maintains ’em? How are they escoted? Will +they pursue the quality no longer than they can sing? Will they not say +afterwards, if they should grow themselves to common players—as it is +most like, if their means are no better—their writers do them wrong to +make them exclaim against their own succession? + +ROSENCRANTZ. +Faith, there has been much to do on both sides; and the nation holds it +no sin to tarre them to controversy. There was for a while, no money +bid for argument unless the poet and the player went to cuffs in the +question. + +HAMLET. +Is’t possible? + +GUILDENSTERN. +O, there has been much throwing about of brains. + +HAMLET. +Do the boys carry it away? + +ROSENCRANTZ. +Ay, that they do, my lord. Hercules and his load too. + +HAMLET. +It is not very strange; for my uncle is King of Denmark, and those that +would make mouths at him while my father lived, give twenty, forty, +fifty, a hundred ducats apiece for his picture in little. ’Sblood, +there is something in this more than natural, if philosophy could find +it out. + +[_Flourish of trumpets within._] + +GUILDENSTERN. +There are the players. + +HAMLET. +Gentlemen, you are welcome to Elsinore. Your hands, come. The +appurtenance of welcome is fashion and ceremony. Let me comply with you +in this garb, lest my extent to the players, which I tell you must show +fairly outward, should more appear like entertainment than yours. You +are welcome. But my uncle-father and aunt-mother are deceived. + +GUILDENSTERN. +In what, my dear lord? + +HAMLET. +I am but mad north-north-west. When the wind is southerly, I know a +hawk from a handsaw. + +Enter Polonius. + +POLONIUS. +Well be with you, gentlemen. + +HAMLET. +Hark you, Guildenstern, and you too, at each ear a hearer. That great +baby you see there is not yet out of his swaddling clouts. + +ROSENCRANTZ. +Happily he’s the second time come to them; for they say an old man is +twice a child. + +HAMLET. +I will prophesy he comes to tell me of the players. Mark it.—You say +right, sir: for a Monday morning ’twas so indeed. + +POLONIUS. +My lord, I have news to tell you. + +HAMLET. +My lord, I have news to tell you. When Roscius was an actor in Rome— + +POLONIUS. +The actors are come hither, my lord. + +HAMLET. +Buzz, buzz. + +POLONIUS. +Upon my honour. + +HAMLET. +Then came each actor on his ass— + +POLONIUS. +The best actors in the world, either for tragedy, comedy, history, +pastoral, pastoral-comical, historical-pastoral, tragical-historical, +tragical-comical-historical-pastoral, scene individable, or poem +unlimited. Seneca cannot be too heavy, nor Plautus too light, for the +law of writ and the liberty. These are the only men. + +HAMLET. +O Jephthah, judge of Israel, what a treasure hadst thou! + +POLONIUS. +What treasure had he, my lord? + +HAMLET. +Why— + ’One fair daughter, and no more, + The which he loved passing well.’ + +POLONIUS. +[_Aside._] Still on my daughter. + +HAMLET. +Am I not i’ th’ right, old Jephthah? + +POLONIUS. +If you call me Jephthah, my lord, I have a daughter that I love passing +well. + +HAMLET. +Nay, that follows not. + +POLONIUS. +What follows then, my lord? + +HAMLET. +Why, + As by lot, God wot, +and then, you know, + It came to pass, as most like it was. +The first row of the pious chanson will show you more. For look where +my abridgement comes. + +Enter four or five Players. + +You are welcome, masters, welcome all. I am glad to see thee well. +Welcome, good friends. O, my old friend! Thy face is valanc’d since I +saw thee last. Com’st thou to beard me in Denmark? What, my young lady +and mistress! By’r lady, your ladyship is nearer to heaven than when I +saw you last, by the altitude of a chopine. Pray God your voice, like a +piece of uncurrent gold, be not cracked within the ring. Masters, you +are all welcome. We’ll e’en to’t like French falconers, fly at anything +we see. We’ll have a speech straight. Come, give us a taste of your +quality. Come, a passionate speech. + +FIRST PLAYER. +What speech, my lord? + +HAMLET. +I heard thee speak me a speech once, but it was never acted, or if it +was, not above once, for the play, I remember, pleased not the million, +’twas caviare to the general. But it was—as I received it, and others, +whose judgements in such matters cried in the top of mine—an excellent +play, well digested in the scenes, set down with as much modesty as +cunning. I remember one said there were no sallets in the lines to make +the matter savoury, nor no matter in the phrase that might indite the +author of affectation, but called it an honest method, as wholesome as +sweet, and by very much more handsome than fine. One speech in it, I +chiefly loved. ’Twas Aeneas’ tale to Dido, and thereabout of it +especially where he speaks of Priam’s slaughter. If it live in your +memory, begin at this line, let me see, let me see: + _The rugged Pyrrhus, like th’ Hyrcanian beast,—_ +It is not so: it begins with Pyrrhus— + _The rugged Pyrrhus, he whose sable arms, + Black as his purpose, did the night resemble + When he lay couched in the ominous horse, + Hath now this dread and black complexion smear’d + With heraldry more dismal. Head to foot + Now is he total gules, horridly trick’d + With blood of fathers, mothers, daughters, sons, + Bak’d and impasted with the parching streets, + That lend a tyrannous and a damned light + To their vile murders. Roasted in wrath and fire, + And thus o’ersized with coagulate gore, + With eyes like carbuncles, the hellish Pyrrhus + Old grandsire Priam seeks._ +So, proceed you. + +POLONIUS. +’Fore God, my lord, well spoken, with good accent and good discretion. + +FIRST PLAYER. + _Anon he finds him, + Striking too short at Greeks. His antique sword, + Rebellious to his arm, lies where it falls, + Repugnant to command. Unequal match’d, + Pyrrhus at Priam drives, in rage strikes wide; + But with the whiff and wind of his fell sword + Th’unnerved father falls. Then senseless Ilium, + Seeming to feel this blow, with flaming top + Stoops to his base, and with a hideous crash + Takes prisoner Pyrrhus’ ear. For lo, his sword, + Which was declining on the milky head + Of reverend Priam, seem’d i’ th’air to stick. + So, as a painted tyrant, Pyrrhus stood, + And like a neutral to his will and matter, + Did nothing. + But as we often see against some storm, + A silence in the heavens, the rack stand still, + The bold winds speechless, and the orb below + As hush as death, anon the dreadful thunder + Doth rend the region; so after Pyrrhus’ pause, + Aroused vengeance sets him new a-work, + And never did the Cyclops’ hammers fall + On Mars’s armour, forg’d for proof eterne, + With less remorse than Pyrrhus’ bleeding sword + Now falls on Priam. + Out, out, thou strumpet Fortune! All you gods, + In general synod, take away her power; + Break all the spokes and fellies from her wheel, + And bowl the round nave down the hill of heaven, + As low as to the fiends._ + +POLONIUS. +This is too long. + +HAMLET. +It shall to the barber’s, with your beard.—Prithee say on. +He’s for a jig or a tale of bawdry, or he sleeps. +Say on; come to Hecuba. + +FIRST PLAYER. + _But who, O who, had seen the mobled queen,—_ + +HAMLET. +‘The mobled queen’? + +POLONIUS. +That’s good! ‘Mobled queen’ is good. + +FIRST PLAYER. + _Run barefoot up and down, threat’ning the flames + With bisson rheum. A clout upon that head + Where late the diadem stood, and for a robe, + About her lank and all o’erteemed loins, + A blanket, in th’alarm of fear caught up— + Who this had seen, with tongue in venom steep’d, + ’Gainst Fortune’s state would treason have pronounc’d. + But if the gods themselves did see her then, + When she saw Pyrrhus make malicious sport + In mincing with his sword her husband’s limbs, + The instant burst of clamour that she made,— + Unless things mortal move them not at all,— + Would have made milch the burning eyes of heaven, + And passion in the gods._ + +POLONIUS. +Look, where he has not turn’d his colour, and has tears in’s eyes. Pray +you, no more. + +HAMLET. +’Tis well. I’ll have thee speak out the rest of this soon.—Good my +lord, will you see the players well bestowed? Do you hear, let them be +well used; for they are the abstracts and brief chronicles of the time. +After your death you were better have a bad epitaph than their ill +report while you live. + +POLONIUS. +My lord, I will use them according to their desert. + +HAMLET. +God’s bodikin, man, much better. Use every man after his desert, and who +should ’scape whipping? Use them after your own honour and dignity. The +less they deserve, the more merit is in your bounty. Take them in. + +POLONIUS. +Come, sirs. + +HAMLET. +Follow him, friends. We’ll hear a play tomorrow. + +[_Exeunt Polonius with all the Players but the First._] + +Dost thou hear me, old friend? Can you play _The Murder of Gonzago_? + +FIRST PLAYER. +Ay, my lord. + +HAMLET. +We’ll ha’t tomorrow night. You could for a need study a speech of some +dozen or sixteen lines, which I would set down and insert in’t, could +you not? + +FIRST PLAYER. +Ay, my lord. + +HAMLET. +Very well. Follow that lord, and look you mock him not. + +[_Exit First Player._] + +[_To Rosencrantz and Guildenstern_] My good friends, I’ll leave you +till night. You are welcome to Elsinore. + +ROSENCRANTZ. +Good my lord. + +[_Exeunt Rosencrantz and Guildenstern._] + +HAMLET. +Ay, so, God b’ wi’ ye. Now I am alone. +O what a rogue and peasant slave am I! +Is it not monstrous that this player here, +But in a fiction, in a dream of passion, +Could force his soul so to his own conceit +That from her working all his visage wan’d; +Tears in his eyes, distraction in’s aspect, +A broken voice, and his whole function suiting +With forms to his conceit? And all for nothing! +For Hecuba? +What’s Hecuba to him, or he to Hecuba, +That he should weep for her? What would he do, +Had he the motive and the cue for passion +That I have? He would drown the stage with tears +And cleave the general ear with horrid speech; +Make mad the guilty, and appal the free, +Confound the ignorant, and amaze indeed, +The very faculties of eyes and ears. Yet I, +A dull and muddy-mettled rascal, peak +Like John-a-dreams, unpregnant of my cause, +And can say nothing. No, not for a king +Upon whose property and most dear life +A damn’d defeat was made. Am I a coward? +Who calls me villain, breaks my pate across? +Plucks off my beard and blows it in my face? +Tweaks me by the nose, gives me the lie i’ th’ throat +As deep as to the lungs? Who does me this? +Ha! ’Swounds, I should take it: for it cannot be +But I am pigeon-liver’d, and lack gall +To make oppression bitter, or ere this +I should have fatted all the region kites +With this slave’s offal. Bloody, bawdy villain! +Remorseless, treacherous, lecherous, kindless villain! +Oh vengeance! +Why, what an ass am I! This is most brave, +That I, the son of a dear father murder’d, +Prompted to my revenge by heaven and hell, +Must, like a whore, unpack my heart with words +And fall a-cursing like a very drab, +A scullion! Fie upon’t! Foh! +About, my brain! I have heard +That guilty creatures sitting at a play, +Have by the very cunning of the scene, +Been struck so to the soul that presently +They have proclaim’d their malefactions. +For murder, though it have no tongue, will speak +With most miraculous organ. I’ll have these players +Play something like the murder of my father +Before mine uncle. I’ll observe his looks; +I’ll tent him to the quick. If he but blench, +I know my course. The spirit that I have seen +May be the devil, and the devil hath power +T’assume a pleasing shape, yea, and perhaps +Out of my weakness and my melancholy, +As he is very potent with such spirits, +Abuses me to damn me. I’ll have grounds +More relative than this. The play’s the thing +Wherein I’ll catch the conscience of the King. + +[_Exit._] + + + + +ACT III + +SCENE I. A room in the Castle. + + +Enter King, Queen, Polonius, Ophelia, Rosencrantz and Guildenstern. + +KING. +And can you by no drift of circumstance +Get from him why he puts on this confusion, +Grating so harshly all his days of quiet +With turbulent and dangerous lunacy? + +ROSENCRANTZ. +He does confess he feels himself distracted, +But from what cause he will by no means speak. + +GUILDENSTERN. +Nor do we find him forward to be sounded, +But with a crafty madness keeps aloof +When we would bring him on to some confession +Of his true state. + +QUEEN. +Did he receive you well? + +ROSENCRANTZ. +Most like a gentleman. + +GUILDENSTERN. +But with much forcing of his disposition. + +ROSENCRANTZ. +Niggard of question, but of our demands, +Most free in his reply. + +QUEEN. +Did you assay him to any pastime? + +ROSENCRANTZ. +Madam, it so fell out that certain players +We o’er-raught on the way. Of these we told him, +And there did seem in him a kind of joy +To hear of it. They are about the court, +And, as I think, they have already order +This night to play before him. + +POLONIUS. +’Tis most true; +And he beseech’d me to entreat your Majesties +To hear and see the matter. + +KING. +With all my heart; and it doth much content me +To hear him so inclin’d. +Good gentlemen, give him a further edge, +And drive his purpose on to these delights. + +ROSENCRANTZ. +We shall, my lord. + +[_Exeunt Rosencrantz and Guildenstern._] + +KING. +Sweet Gertrude, leave us too, +For we have closely sent for Hamlet hither, +That he, as ’twere by accident, may here +Affront Ophelia. +Her father and myself, lawful espials, +Will so bestow ourselves that, seeing unseen, +We may of their encounter frankly judge, +And gather by him, as he is behav’d, +If’t be th’affliction of his love or no +That thus he suffers for. + +QUEEN. +I shall obey you. +And for your part, Ophelia, I do wish +That your good beauties be the happy cause +Of Hamlet’s wildness: so shall I hope your virtues +Will bring him to his wonted way again, +To both your honours. + +OPHELIA. +Madam, I wish it may. + +[_Exit Queen._] + +POLONIUS. +Ophelia, walk you here.—Gracious, so please you, +We will bestow ourselves.—[_To Ophelia._] Read on this book, +That show of such an exercise may colour +Your loneliness.—We are oft to blame in this, +’Tis too much prov’d, that with devotion’s visage +And pious action we do sugar o’er +The devil himself. + +KING. +[_Aside._] O ’tis too true! +How smart a lash that speech doth give my conscience! +The harlot’s cheek, beautied with plastering art, +Is not more ugly to the thing that helps it +Than is my deed to my most painted word. +O heavy burden! + +POLONIUS. +I hear him coming. Let’s withdraw, my lord. + +[_Exeunt King and Polonius._] + +Enter Hamlet. + +HAMLET. +To be, or not to be, that is the question: +Whether ’tis nobler in the mind to suffer +The slings and arrows of outrageous fortune, +Or to take arms against a sea of troubles, +And by opposing end them? To die—to sleep, +No more; and by a sleep to say we end +The heart-ache, and the thousand natural shocks +That flesh is heir to: ’tis a consummation +Devoutly to be wish’d. To die, to sleep. +To sleep, perchance to dream—ay, there’s the rub, +For in that sleep of death what dreams may come, +When we have shuffled off this mortal coil, +Must give us pause. There’s the respect +That makes calamity of so long life. +For who would bear the whips and scorns of time, +The oppressor’s wrong, the proud man’s contumely, +The pangs of dispriz’d love, the law’s delay, +The insolence of office, and the spurns +That patient merit of the unworthy takes, +When he himself might his quietus make +With a bare bodkin? Who would these fardels bear, +To grunt and sweat under a weary life, +But that the dread of something after death, +The undiscover’d country, from whose bourn +No traveller returns, puzzles the will, +And makes us rather bear those ills we have +Than fly to others that we know not of? +Thus conscience does make cowards of us all, +And thus the native hue of resolution +Is sicklied o’er with the pale cast of thought, +And enterprises of great pith and moment, +With this regard their currents turn awry +And lose the name of action. Soft you now, +The fair Ophelia! Nymph, in thy orisons +Be all my sins remember’d. + +OPHELIA. +Good my lord, +How does your honour for this many a day? + +HAMLET. +I humbly thank you; well, well, well. + +OPHELIA. +My lord, I have remembrances of yours +That I have longed long to re-deliver. +I pray you, now receive them. + +HAMLET. +No, not I. +I never gave you aught. + +OPHELIA. +My honour’d lord, you know right well you did, +And with them words of so sweet breath compos’d +As made the things more rich; their perfume lost, +Take these again; for to the noble mind +Rich gifts wax poor when givers prove unkind. +There, my lord. + +HAMLET. +Ha, ha! Are you honest? + +OPHELIA. +My lord? + +HAMLET. +Are you fair? + +OPHELIA. +What means your lordship? + +HAMLET. +That if you be honest and fair, your honesty should admit no discourse +to your beauty. + +OPHELIA. +Could beauty, my lord, have better commerce than with honesty? + +HAMLET. +Ay, truly; for the power of beauty will sooner transform honesty from +what it is to a bawd than the force of honesty can translate beauty +into his likeness. This was sometime a paradox, but now the time gives +it proof. I did love you once. + +OPHELIA. +Indeed, my lord, you made me believe so. + +HAMLET. +You should not have believed me; for virtue cannot so inoculate our old +stock but we shall relish of it. I loved you not. + +OPHELIA. +I was the more deceived. + +HAMLET. +Get thee to a nunnery. Why wouldst thou be a breeder of sinners? I am +myself indifferent honest; but yet I could accuse me of such things +that it were better my mother had not borne me. I am very proud, +revengeful, ambitious, with more offences at my beck than I have +thoughts to put them in, imagination to give them shape, or time to act +them in. What should such fellows as I do crawling between earth and +heaven? We are arrant knaves all, believe none of us. Go thy ways to a +nunnery. Where’s your father? + +OPHELIA. +At home, my lord. + +HAMLET. +Let the doors be shut upon him, that he may play the fool nowhere but +in’s own house. Farewell. + +OPHELIA. +O help him, you sweet heavens! + +HAMLET. +If thou dost marry, I’ll give thee this plague for thy dowry. Be thou +as chaste as ice, as pure as snow, thou shalt not escape calumny. Get +thee to a nunnery, go: farewell. Or if thou wilt needs marry, marry a +fool; for wise men know well enough what monsters you make of them. To +a nunnery, go; and quickly too. Farewell. + +OPHELIA. +O heavenly powers, restore him! + +HAMLET. +I have heard of your paintings too, well enough. God hath given you one +face, and you make yourselves another. You jig, you amble, and you +lisp, and nickname God’s creatures, and make your wantonness your +ignorance. Go to, I’ll no more on’t, it hath made me mad. I say, we +will have no more marriages. Those that are married already, all but +one, shall live; the rest shall keep as they are. To a nunnery, go. + +[_Exit._] + +OPHELIA. +O, what a noble mind is here o’erthrown! +The courtier’s, soldier’s, scholar’s, eye, tongue, sword, +Th’expectancy and rose of the fair state, +The glass of fashion and the mould of form, +Th’observ’d of all observers, quite, quite down! +And I, of ladies most deject and wretched, +That suck’d the honey of his music vows, +Now see that noble and most sovereign reason, +Like sweet bells jangled out of tune and harsh, +That unmatch’d form and feature of blown youth +Blasted with ecstasy. O woe is me, +T’have seen what I have seen, see what I see. + +Enter King and Polonius. + +KING. +Love? His affections do not that way tend, +Nor what he spake, though it lack’d form a little, +Was not like madness. There’s something in his soul +O’er which his melancholy sits on brood, +And I do doubt the hatch and the disclose +Will be some danger, which for to prevent, +I have in quick determination +Thus set it down: he shall with speed to England +For the demand of our neglected tribute: +Haply the seas and countries different, +With variable objects, shall expel +This something settled matter in his heart, +Whereon his brains still beating puts him thus +From fashion of himself. What think you on’t? + +POLONIUS. +It shall do well. But yet do I believe +The origin and commencement of his grief +Sprung from neglected love. How now, Ophelia? +You need not tell us what Lord Hamlet said, +We heard it all. My lord, do as you please, +But if you hold it fit, after the play, +Let his queen mother all alone entreat him +To show his grief, let her be round with him, +And I’ll be plac’d, so please you, in the ear +Of all their conference. If she find him not, +To England send him; or confine him where +Your wisdom best shall think. + +KING. +It shall be so. +Madness in great ones must not unwatch’d go. + +[_Exeunt._] + + SCENE II. A hall in the Castle. + +Enter Hamlet and certain Players. + +HAMLET. +Speak the speech, I pray you, as I pronounced it to you, trippingly on +the tongue. But if you mouth it, as many of your players do, I had as +lief the town-crier spoke my lines. Nor do not saw the air too much +with your hand, thus, but use all gently; for in the very torrent, +tempest, and, as I may say, whirlwind of passion, you must acquire and +beget a temperance that may give it smoothness. O, it offends me to the +soul to hear a robustious periwig-pated fellow tear a passion to +tatters, to very rags, to split the ears of the groundlings, who, for +the most part, are capable of nothing but inexplicable dumb shows and +noise. I would have such a fellow whipped for o’erdoing Termagant. It +out-Herods Herod. Pray you avoid it. + +FIRST PLAYER. +I warrant your honour. + +HAMLET. +Be not too tame neither; but let your own discretion be your tutor. +Suit the action to the word, the word to the action, with this special +observance, that you o’erstep not the modesty of nature; for anything +so overdone is from the purpose of playing, whose end, both at the +first and now, was and is, to hold as ’twere the mirror up to nature; +to show virtue her own feature, scorn her own image, and the very age +and body of the time his form and pressure. Now, this overdone, or come +tardy off, though it make the unskilful laugh, cannot but make the +judicious grieve; the censure of the which one must in your allowance +o’erweigh a whole theatre of others. O, there be players that I have +seen play—and heard others praise, and that highly—not to speak it +profanely, that, neither having the accent of Christians, nor the gait +of Christian, pagan, nor man, have so strutted and bellowed that I have +thought some of Nature’s journeymen had made men, and not made them +well, they imitated humanity so abominably. + +FIRST PLAYER. +I hope we have reform’d that indifferently with us, sir. + +HAMLET. +O reform it altogether. And let those that play your clowns speak no +more than is set down for them. For there be of them that will +themselves laugh, to set on some quantity of barren spectators to laugh +too, though in the meantime some necessary question of the play be then +to be considered. That’s villainous, and shows a most pitiful ambition +in the fool that uses it. Go make you ready. + +[_Exeunt Players._] + +Enter Polonius, Rosencrantz and Guildenstern. + +How now, my lord? +Will the King hear this piece of work? + +POLONIUS. +And the Queen too, and that presently. + +HAMLET. +Bid the players make haste. + +[_Exit Polonius._] + +Will you two help to hasten them? + +ROSENCRANTZ and GUILDENSTERN. +We will, my lord. + +[_Exeunt Rosencrantz and Guildenstern._] + +HAMLET. +What ho, Horatio! + +Enter Horatio. + +HORATIO. +Here, sweet lord, at your service. + +HAMLET. +Horatio, thou art e’en as just a man +As e’er my conversation cop’d withal. + +HORATIO. +O my dear lord. + +HAMLET. +Nay, do not think I flatter; +For what advancement may I hope from thee, +That no revenue hast, but thy good spirits +To feed and clothe thee? Why should the poor be flatter’d? +No, let the candied tongue lick absurd pomp, +And crook the pregnant hinges of the knee +Where thrift may follow fawning. Dost thou hear? +Since my dear soul was mistress of her choice, +And could of men distinguish, her election +Hath seal’d thee for herself. For thou hast been +As one, in suffering all, that suffers nothing, +A man that Fortune’s buffets and rewards +Hast ta’en with equal thanks. And blessed are those +Whose blood and judgement are so well co-mingled +That they are not a pipe for Fortune’s finger +To sound what stop she please. Give me that man +That is not passion’s slave, and I will wear him +In my heart’s core, ay, in my heart of heart, +As I do thee. Something too much of this. +There is a play tonight before the King. +One scene of it comes near the circumstance +Which I have told thee, of my father’s death. +I prithee, when thou see’st that act a-foot, +Even with the very comment of thy soul +Observe mine uncle. If his occulted guilt +Do not itself unkennel in one speech, +It is a damned ghost that we have seen; +And my imaginations are as foul +As Vulcan’s stithy. Give him heedful note; +For I mine eyes will rivet to his face; +And after we will both our judgements join +In censure of his seeming. + +HORATIO. +Well, my lord. +If he steal aught the whilst this play is playing, +And ’scape detecting, I will pay the theft. + +HAMLET. +They are coming to the play. I must be idle. +Get you a place. + +Danish march. A flourish. Enter King, Queen, Polonius, Ophelia, +Rosencrantz, Guildenstern and others. + +KING. +How fares our cousin Hamlet? + +HAMLET. +Excellent, i’ faith; of the chameleon’s dish: I eat the air, +promise-crammed: you cannot feed capons so. + +KING. +I have nothing with this answer, Hamlet; these words are not mine. + +HAMLET. +No, nor mine now. [_To Polonius._] My lord, you play’d once i’ +th’university, you say? + +POLONIUS. +That did I, my lord, and was accounted a good actor. + +HAMLET. +What did you enact? + +POLONIUS. +I did enact Julius Caesar. I was kill’d i’ th’ Capitol. Brutus killed +me. + +HAMLET. +It was a brute part of him to kill so capital a calf there. Be the +players ready? + +ROSENCRANTZ. +Ay, my lord; they stay upon your patience. + +QUEEN. +Come hither, my dear Hamlet, sit by me. + +HAMLET. +No, good mother, here’s metal more attractive. + +POLONIUS. +[_To the King._] O ho! do you mark that? + +HAMLET. +Lady, shall I lie in your lap? + +[_Lying down at Ophelia’s feet._] + +OPHELIA. +No, my lord. + +HAMLET. +I mean, my head upon your lap? + +OPHELIA. +Ay, my lord. + +HAMLET. +Do you think I meant country matters? + +OPHELIA. +I think nothing, my lord. + +HAMLET. +That’s a fair thought to lie between maids’ legs. + +OPHELIA. +What is, my lord? + +HAMLET. +Nothing. + +OPHELIA. +You are merry, my lord. + +HAMLET. +Who, I? + +OPHELIA. +Ay, my lord. + +HAMLET. +O God, your only jig-maker! What should a man do but be merry? For look +you how cheerfully my mother looks, and my father died within’s two +hours. + +OPHELIA. +Nay, ’tis twice two months, my lord. + +HAMLET. +So long? Nay then, let the devil wear black, for I’ll have a suit of +sables. O heavens! die two months ago, and not forgotten yet? Then +there’s hope a great man’s memory may outlive his life half a year. But +by’r lady, he must build churches then; or else shall he suffer not +thinking on, with the hobby-horse, whose epitaph is ‘For, O, for O, the +hobby-horse is forgot!’ + +Trumpets sound. The dumb show enters. + +_Enter a King and a Queen very lovingly; the Queen embracing him and he +her. She kneels, and makes show of protestation unto him. He takes her +up, and declines his head upon her neck. Lays him down upon a bank of +flowers. She, seeing him asleep, leaves him. Anon comes in a fellow, +takes off his crown, kisses it, pours poison in the King’s ears, and +exits. The Queen returns, finds the King dead, and makes passionate +action. The Poisoner with some three or four Mutes, comes in again, +seeming to lament with her. The dead body is carried away. The Poisoner +woos the Queen with gifts. She seems loth and unwilling awhile, but in +the end accepts his love._ + +[_Exeunt._] + +OPHELIA. +What means this, my lord? + +HAMLET. +Marry, this is miching mallecho; it means mischief. + +OPHELIA. +Belike this show imports the argument of the play. + +Enter Prologue. + +HAMLET. +We shall know by this fellow: the players cannot keep counsel; they’ll +tell all. + +OPHELIA. +Will they tell us what this show meant? + +HAMLET. +Ay, or any show that you’ll show him. Be not you ashamed to show, he’ll +not shame to tell you what it means. + +OPHELIA. +You are naught, you are naught: I’ll mark the play. + +PROLOGUE. + _For us, and for our tragedy, + Here stooping to your clemency, + We beg your hearing patiently._ + +HAMLET. +Is this a prologue, or the posy of a ring? + +OPHELIA. +’Tis brief, my lord. + +HAMLET. +As woman’s love. + +Enter a King and a Queen. + +PLAYER KING. +Full thirty times hath Phoebus’ cart gone round +Neptune’s salt wash and Tellus’ orbed ground, +And thirty dozen moons with borrow’d sheen +About the world have times twelve thirties been, +Since love our hearts, and Hymen did our hands +Unite commutual in most sacred bands. + +PLAYER QUEEN. +So many journeys may the sun and moon +Make us again count o’er ere love be done. +But, woe is me, you are so sick of late, +So far from cheer and from your former state, +That I distrust you. Yet, though I distrust, +Discomfort you, my lord, it nothing must: +For women’s fear and love holds quantity, +In neither aught, or in extremity. +Now what my love is, proof hath made you know, +And as my love is siz’d, my fear is so. +Where love is great, the littlest doubts are fear; +Where little fears grow great, great love grows there. + +PLAYER KING. +Faith, I must leave thee, love, and shortly too: +My operant powers their functions leave to do: +And thou shalt live in this fair world behind, +Honour’d, belov’d, and haply one as kind +For husband shalt thou— + +PLAYER QUEEN. +O confound the rest. +Such love must needs be treason in my breast. +In second husband let me be accurst! +None wed the second but who kill’d the first. + +HAMLET. +[_Aside._] Wormwood, wormwood. + +PLAYER QUEEN. +The instances that second marriage move +Are base respects of thrift, but none of love. +A second time I kill my husband dead, +When second husband kisses me in bed. + +PLAYER KING. +I do believe you think what now you speak; +But what we do determine, oft we break. +Purpose is but the slave to memory, +Of violent birth, but poor validity: +Which now, like fruit unripe, sticks on the tree, +But fall unshaken when they mellow be. +Most necessary ’tis that we forget +To pay ourselves what to ourselves is debt. +What to ourselves in passion we propose, +The passion ending, doth the purpose lose. +The violence of either grief or joy +Their own enactures with themselves destroy. +Where joy most revels, grief doth most lament; +Grief joys, joy grieves, on slender accident. +This world is not for aye; nor ’tis not strange +That even our loves should with our fortunes change, +For ’tis a question left us yet to prove, +Whether love lead fortune, or else fortune love. +The great man down, you mark his favourite flies, +The poor advanc’d makes friends of enemies; +And hitherto doth love on fortune tend: +For who not needs shall never lack a friend, +And who in want a hollow friend doth try, +Directly seasons him his enemy. +But orderly to end where I begun, +Our wills and fates do so contrary run +That our devices still are overthrown. +Our thoughts are ours, their ends none of our own. +So think thou wilt no second husband wed, +But die thy thoughts when thy first lord is dead. + +PLAYER QUEEN. +Nor earth to me give food, nor heaven light, +Sport and repose lock from me day and night, +To desperation turn my trust and hope, +An anchor’s cheer in prison be my scope, +Each opposite that blanks the face of joy, +Meet what I would have well, and it destroy! +Both here and hence pursue me lasting strife, +If, once a widow, ever I be wife. + +HAMLET. +[_To Ophelia._] If she should break it now. + +PLAYER KING. +’Tis deeply sworn. Sweet, leave me here awhile. +My spirits grow dull, and fain I would beguile +The tedious day with sleep. +[_Sleeps._] + +PLAYER QUEEN. +Sleep rock thy brain, +And never come mischance between us twain. + +[_Exit._] + +HAMLET. +Madam, how like you this play? + +QUEEN. +The lady protests too much, methinks. + +HAMLET. +O, but she’ll keep her word. + +KING. +Have you heard the argument? Is there no offence in’t? + +HAMLET. +No, no, they do but jest, poison in jest; no offence i’ th’ world. + +KING. +What do you call the play? + +HAMLET. +_The Mousetrap._ Marry, how? Tropically. This play is the image of a +murder done in Vienna. Gonzago is the Duke’s name, his wife Baptista: +you shall see anon; ’tis a knavish piece of work: but what o’ that? +Your majesty, and we that have free souls, it touches us not. Let the +gall’d jade wince; our withers are unwrung. + +Enter Lucianus. + +This is one Lucianus, nephew to the King. + +OPHELIA. +You are a good chorus, my lord. + +HAMLET. +I could interpret between you and your love, if I could see the puppets +dallying. + +OPHELIA. +You are keen, my lord, you are keen. + +HAMLET. +It would cost you a groaning to take off my edge. + +OPHELIA. +Still better, and worse. + +HAMLET. +So you mistake your husbands.—Begin, murderer. Pox, leave thy damnable +faces, and begin. Come, the croaking raven doth bellow for revenge. + +LUCIANUS. +Thoughts black, hands apt, drugs fit, and time agreeing, +Confederate season, else no creature seeing; +Thou mixture rank, of midnight weeds collected, +With Hecate’s ban thrice blasted, thrice infected, +Thy natural magic and dire property +On wholesome life usurp immediately. + +[_Pours the poison into the sleeper’s ears._] + +HAMLET. +He poisons him i’ th’garden for’s estate. His name’s Gonzago. The story +is extant, and written in very choice Italian. You shall see anon how +the murderer gets the love of Gonzago’s wife. + +OPHELIA. +The King rises. + +HAMLET. +What, frighted with false fire? + +QUEEN. +How fares my lord? + +POLONIUS. +Give o’er the play. + +KING. +Give me some light. Away. + +All. +Lights, lights, lights. + +[_Exeunt all but Hamlet and Horatio._] + +HAMLET. + Why, let the strucken deer go weep, + The hart ungalled play; + For some must watch, while some must sleep, + So runs the world away. +Would not this, sir, and a forest of feathers, if the rest of my +fortunes turn Turk with me; with two Provincial roses on my razed +shoes, get me a fellowship in a cry of players, sir? + +HORATIO. +Half a share. + +HAMLET. +A whole one, I. + For thou dost know, O Damon dear, + This realm dismantled was + Of Jove himself, and now reigns here + A very, very—pajock. + +HORATIO. +You might have rhymed. + +HAMLET. +O good Horatio, I’ll take the ghost’s word for a thousand pound. Didst +perceive? + +HORATIO. +Very well, my lord. + +HAMLET. +Upon the talk of the poisoning? + +HORATIO. +I did very well note him. + +HAMLET. +Ah, ha! Come, some music. Come, the recorders. + For if the king like not the comedy, + Why then, belike he likes it not, perdie. +Come, some music. + +Enter Rosencrantz and Guildenstern. + +GUILDENSTERN. +Good my lord, vouchsafe me a word with you. + +HAMLET. +Sir, a whole history. + +GUILDENSTERN. +The King, sir— + +HAMLET. +Ay, sir, what of him? + +GUILDENSTERN. +Is in his retirement, marvellous distempered. + +HAMLET. +With drink, sir? + +GUILDENSTERN. +No, my lord; rather with choler. + +HAMLET. +Your wisdom should show itself more richer to signify this to the +doctor, for me to put him to his purgation would perhaps plunge him +into far more choler. + +GUILDENSTERN. +Good my lord, put your discourse into some frame, and start not so +wildly from my affair. + +HAMLET. +I am tame, sir, pronounce. + +GUILDENSTERN. +The Queen your mother, in most great affliction of spirit, hath sent me +to you. + +HAMLET. +You are welcome. + +GUILDENSTERN. +Nay, good my lord, this courtesy is not of the right breed. If it shall +please you to make me a wholesome answer, I will do your mother’s +commandment; if not, your pardon and my return shall be the end of my +business. + +HAMLET. +Sir, I cannot. + +GUILDENSTERN. +What, my lord? + +HAMLET. +Make you a wholesome answer. My wit’s diseased. But, sir, such answer +as I can make, you shall command; or rather, as you say, my mother. +Therefore no more, but to the matter. My mother, you say,— + +ROSENCRANTZ. +Then thus she says: your behaviour hath struck her into amazement and +admiration. + +HAMLET. +O wonderful son, that can so stonish a mother! But is there no sequel +at the heels of this mother’s admiration? + +ROSENCRANTZ. +She desires to speak with you in her closet ere you go to bed. + +HAMLET. +We shall obey, were she ten times our mother. Have you any further +trade with us? + +ROSENCRANTZ. +My lord, you once did love me. + +HAMLET. +And so I do still, by these pickers and stealers. + +ROSENCRANTZ. +Good my lord, what is your cause of distemper? You do surely bar the +door upon your own liberty if you deny your griefs to your friend. + +HAMLET. +Sir, I lack advancement. + +ROSENCRANTZ. +How can that be, when you have the voice of the King himself for your +succession in Denmark? + +HAMLET. +Ay, sir, but while the grass grows—the proverb is something musty. + +Re-enter the Players with recorders. + +O, the recorders. Let me see one.—To withdraw with you, why do you go +about to recover the wind of me, as if you would drive me into a toil? + +GUILDENSTERN. +O my lord, if my duty be too bold, my love is too unmannerly. + +HAMLET. +I do not well understand that. Will you play upon this pipe? + +GUILDENSTERN. +My lord, I cannot. + +HAMLET. +I pray you. + +GUILDENSTERN. +Believe me, I cannot. + +HAMLET. +I do beseech you. + +GUILDENSTERN. +I know no touch of it, my lord. + +HAMLET. +’Tis as easy as lying: govern these ventages with your finger and +thumb, give it breath with your mouth, and it will discourse most +eloquent music. Look you, these are the stops. + +GUILDENSTERN. +But these cannot I command to any utterance of harmony. I have not the +skill. + +HAMLET. +Why, look you now, how unworthy a thing you make of me! You would play +upon me; you would seem to know my stops; you would pluck out the heart +of my mystery; you would sound me from my lowest note to the top of my +compass; and there is much music, excellent voice, in this little +organ, yet cannot you make it speak. ’Sblood, do you think I am easier +to be played on than a pipe? Call me what instrument you will, though +you can fret me, you cannot play upon me. + +Enter Polonius. + +God bless you, sir. + +POLONIUS. +My lord, the Queen would speak with you, and presently. + +HAMLET. +Do you see yonder cloud that’s almost in shape of a camel? + +POLONIUS. +By the mass, and ’tis like a camel indeed. + +HAMLET. +Methinks it is like a weasel. + +POLONIUS. +It is backed like a weasel. + +HAMLET. +Or like a whale. + +POLONIUS. +Very like a whale. + +HAMLET. +Then will I come to my mother by and by.—They fool me to the top of my +bent.—I will come by and by. + +POLONIUS. +I will say so. + +[_Exit._] + +HAMLET. +By and by is easily said. Leave me, friends. + +[_Exeunt all but Hamlet._] + +’Tis now the very witching time of night, +When churchyards yawn, and hell itself breathes out +Contagion to this world. Now could I drink hot blood, +And do such bitter business as the day +Would quake to look on. Soft now, to my mother. +O heart, lose not thy nature; let not ever +The soul of Nero enter this firm bosom: +Let me be cruel, not unnatural. +I will speak daggers to her, but use none; +My tongue and soul in this be hypocrites. +How in my words somever she be shent, +To give them seals never, my soul, consent. + +[_Exit._] + + SCENE III. A room in the Castle. + +Enter King, Rosencrantz and Guildenstern. + +KING. +I like him not, nor stands it safe with us +To let his madness range. Therefore prepare you, +I your commission will forthwith dispatch, +And he to England shall along with you. +The terms of our estate may not endure +Hazard so near us as doth hourly grow +Out of his lunacies. + +GUILDENSTERN. +We will ourselves provide. +Most holy and religious fear it is +To keep those many many bodies safe +That live and feed upon your Majesty. + +ROSENCRANTZ. +The single and peculiar life is bound +With all the strength and armour of the mind, +To keep itself from ’noyance; but much more +That spirit upon whose weal depend and rest +The lives of many. The cease of majesty +Dies not alone; but like a gulf doth draw +What’s near it with it. It is a massy wheel +Fix’d on the summit of the highest mount, +To whose huge spokes ten thousand lesser things +Are mortis’d and adjoin’d; which when it falls, +Each small annexment, petty consequence, +Attends the boist’rous ruin. Never alone +Did the King sigh, but with a general groan. + +KING. +Arm you, I pray you, to this speedy voyage; +For we will fetters put upon this fear, +Which now goes too free-footed. + +ROSENCRANTZ and GUILDENSTERN. +We will haste us. + +[_Exeunt Rosencrantz and Guildenstern._] + +Enter Polonius. + +POLONIUS. +My lord, he’s going to his mother’s closet. +Behind the arras I’ll convey myself +To hear the process. I’ll warrant she’ll tax him home, +And as you said, and wisely was it said, +’Tis meet that some more audience than a mother, +Since nature makes them partial, should o’erhear +The speech of vantage. Fare you well, my liege, +I’ll call upon you ere you go to bed, +And tell you what I know. + +KING. +Thanks, dear my lord. + +[_Exit Polonius._] + +O, my offence is rank, it smells to heaven; +It hath the primal eldest curse upon’t,— +A brother’s murder! Pray can I not, +Though inclination be as sharp as will: +My stronger guilt defeats my strong intent, +And, like a man to double business bound, +I stand in pause where I shall first begin, +And both neglect. What if this cursed hand +Were thicker than itself with brother’s blood, +Is there not rain enough in the sweet heavens +To wash it white as snow? Whereto serves mercy +But to confront the visage of offence? +And what’s in prayer but this twofold force, +To be forestalled ere we come to fall, +Or pardon’d being down? Then I’ll look up. +My fault is past. But O, what form of prayer +Can serve my turn? Forgive me my foul murder! +That cannot be; since I am still possess’d +Of those effects for which I did the murder,— +My crown, mine own ambition, and my queen. +May one be pardon’d and retain th’offence? +In the corrupted currents of this world +Offence’s gilded hand may shove by justice, +And oft ’tis seen the wicked prize itself +Buys out the law. But ’tis not so above; +There is no shuffling, there the action lies +In his true nature, and we ourselves compell’d +Even to the teeth and forehead of our faults, +To give in evidence. What then? What rests? +Try what repentance can. What can it not? +Yet what can it, when one cannot repent? +O wretched state! O bosom black as death! +O limed soul, that struggling to be free, +Art more engag’d! Help, angels! Make assay: +Bow, stubborn knees; and heart with strings of steel, +Be soft as sinews of the new-born babe. +All may be well. + +[_Retires and kneels._] + +Enter Hamlet. + +HAMLET. +Now might I do it pat, now he is praying. +And now I’ll do’t. And so he goes to heaven; +And so am I reveng’d. That would be scann’d: +A villain kills my father, and for that +I, his sole son, do this same villain send +To heaven. O, this is hire and salary, not revenge. +He took my father grossly, full of bread, +With all his crimes broad blown, as flush as May; +And how his audit stands, who knows save heaven? +But in our circumstance and course of thought, +’Tis heavy with him. And am I then reveng’d, +To take him in the purging of his soul, +When he is fit and season’d for his passage? No. +Up, sword, and know thou a more horrid hent: +When he is drunk asleep; or in his rage, +Or in th’incestuous pleasure of his bed, +At gaming, swearing; or about some act +That has no relish of salvation in’t, +Then trip him, that his heels may kick at heaven, +And that his soul may be as damn’d and black +As hell, whereto it goes. My mother stays. +This physic but prolongs thy sickly days. + +[_Exit._] + +The King rises and advances. + +KING. +My words fly up, my thoughts remain below. +Words without thoughts never to heaven go. + +[_Exit._] + + SCENE IV. Another room in the Castle. + +Enter Queen and Polonius. + +POLONIUS. +He will come straight. Look you lay home to him, +Tell him his pranks have been too broad to bear with, +And that your Grace hath screen’d and stood between +Much heat and him. I’ll silence me e’en here. +Pray you be round with him. + +HAMLET. +[_Within._] Mother, mother, mother. + +QUEEN. +I’ll warrant you, Fear me not. +Withdraw, I hear him coming. + +[_Polonius goes behind the arras._] + +Enter Hamlet. + +HAMLET. +Now, mother, what’s the matter? + +QUEEN. +Hamlet, thou hast thy father much offended. + +HAMLET. +Mother, you have my father much offended. + +QUEEN. +Come, come, you answer with an idle tongue. + +HAMLET. +Go, go, you question with a wicked tongue. + +QUEEN. +Why, how now, Hamlet? + +HAMLET. +What’s the matter now? + +QUEEN. +Have you forgot me? + +HAMLET. +No, by the rood, not so. +You are the Queen, your husband’s brother’s wife, +And, would it were not so. You are my mother. + +QUEEN. +Nay, then I’ll set those to you that can speak. + +HAMLET. +Come, come, and sit you down, you shall not budge. +You go not till I set you up a glass +Where you may see the inmost part of you. + +QUEEN. +What wilt thou do? Thou wilt not murder me? +Help, help, ho! + +POLONIUS. +[_Behind._] What, ho! help, help, help! + +HAMLET. +How now? A rat? [_Draws._] +Dead for a ducat, dead! + +[_Makes a pass through the arras._] + +POLONIUS. +[_Behind._] O, I am slain! + +[_Falls and dies._] + +QUEEN. +O me, what hast thou done? + +HAMLET. +Nay, I know not. Is it the King? + +[_Draws forth Polonius._] + +QUEEN. +O what a rash and bloody deed is this! + +HAMLET. +A bloody deed. Almost as bad, good mother, +As kill a king and marry with his brother. + +QUEEN. +As kill a king? + +HAMLET. +Ay, lady, ’twas my word.— +[_To Polonius._] Thou wretched, rash, intruding fool, farewell! +I took thee for thy better. Take thy fortune, +Thou find’st to be too busy is some danger.— +Leave wringing of your hands. Peace, sit you down, +And let me wring your heart, for so I shall, +If it be made of penetrable stuff; +If damned custom have not braz’d it so, +That it is proof and bulwark against sense. + +QUEEN. +What have I done, that thou dar’st wag thy tongue +In noise so rude against me? + +HAMLET. +Such an act +That blurs the grace and blush of modesty, +Calls virtue hypocrite, takes off the rose +From the fair forehead of an innocent love, +And sets a blister there. Makes marriage vows +As false as dicers’ oaths. O such a deed +As from the body of contraction plucks +The very soul, and sweet religion makes +A rhapsody of words. Heaven’s face doth glow, +Yea this solidity and compound mass, +With tristful visage, as against the doom, +Is thought-sick at the act. + +QUEEN. +Ay me, what act, +That roars so loud, and thunders in the index? + +HAMLET. +Look here upon this picture, and on this, +The counterfeit presentment of two brothers. +See what a grace was seated on this brow, +Hyperion’s curls, the front of Jove himself, +An eye like Mars, to threaten and command, +A station like the herald Mercury +New lighted on a heaven-kissing hill: +A combination and a form indeed, +Where every god did seem to set his seal, +To give the world assurance of a man. +This was your husband. Look you now what follows. +Here is your husband, like a mildew’d ear +Blasting his wholesome brother. Have you eyes? +Could you on this fair mountain leave to feed, +And batten on this moor? Ha! have you eyes? +You cannot call it love; for at your age +The hey-day in the blood is tame, it’s humble, +And waits upon the judgement: and what judgement +Would step from this to this? Sense sure you have, +Else could you not have motion; but sure that sense +Is apoplex’d, for madness would not err +Nor sense to ecstacy was ne’er so thrall’d +But it reserv’d some quantity of choice +To serve in such a difference. What devil was’t +That thus hath cozen’d you at hoodman-blind? +Eyes without feeling, feeling without sight, +Ears without hands or eyes, smelling sans all, +Or but a sickly part of one true sense +Could not so mope. O shame! where is thy blush? +Rebellious hell, +If thou canst mutine in a matron’s bones, +To flaming youth let virtue be as wax, +And melt in her own fire. Proclaim no shame +When the compulsive ardour gives the charge, +Since frost itself as actively doth burn, +And reason panders will. + +QUEEN. +O Hamlet, speak no more. +Thou turn’st mine eyes into my very soul, +And there I see such black and grained spots +As will not leave their tinct. + +HAMLET. +Nay, but to live +In the rank sweat of an enseamed bed, +Stew’d in corruption, honeying and making love +Over the nasty sty. + +QUEEN. +O speak to me no more; +These words like daggers enter in mine ears; +No more, sweet Hamlet. + +HAMLET. +A murderer and a villain; +A slave that is not twentieth part the tithe +Of your precedent lord. A vice of kings, +A cutpurse of the empire and the rule, +That from a shelf the precious diadem stole +And put it in his pocket! + +QUEEN. +No more. + +HAMLET. +A king of shreds and patches!— + +Enter Ghost. + +Save me and hover o’er me with your wings, +You heavenly guards! What would your gracious figure? + +QUEEN. +Alas, he’s mad. + +HAMLET. +Do you not come your tardy son to chide, +That, laps’d in time and passion, lets go by +The important acting of your dread command? +O say! + +GHOST. +Do not forget. This visitation +Is but to whet thy almost blunted purpose. +But look, amazement on thy mother sits. +O step between her and her fighting soul. +Conceit in weakest bodies strongest works. +Speak to her, Hamlet. + +HAMLET. +How is it with you, lady? + +QUEEN. +Alas, how is’t with you, +That you do bend your eye on vacancy, +And with the incorporal air do hold discourse? +Forth at your eyes your spirits wildly peep, +And, as the sleeping soldiers in the alarm, +Your bedded hairs, like life in excrements, +Start up and stand an end. O gentle son, +Upon the heat and flame of thy distemper +Sprinkle cool patience. Whereon do you look? + +HAMLET. +On him, on him! Look you how pale he glares, +His form and cause conjoin’d, preaching to stones, +Would make them capable.—Do not look upon me, +Lest with this piteous action you convert +My stern effects. Then what I have to do +Will want true colour; tears perchance for blood. + +QUEEN. +To whom do you speak this? + +HAMLET. +Do you see nothing there? + +QUEEN. +Nothing at all; yet all that is I see. + +HAMLET. +Nor did you nothing hear? + +QUEEN. +No, nothing but ourselves. + +HAMLET. +Why, look you there! look how it steals away! +My father, in his habit as he liv’d! +Look where he goes even now out at the portal. + +[_Exit Ghost._] + +QUEEN. +This is the very coinage of your brain. +This bodiless creation ecstasy +Is very cunning in. + +HAMLET. +Ecstasy! +My pulse as yours doth temperately keep time, +And makes as healthful music. It is not madness +That I have utter’d. Bring me to the test, +And I the matter will re-word; which madness +Would gambol from. Mother, for love of grace, +Lay not that flattering unction to your soul +That not your trespass, but my madness speaks. +It will but skin and film the ulcerous place, +Whilst rank corruption, mining all within, +Infects unseen. Confess yourself to heaven, +Repent what’s past, avoid what is to come; +And do not spread the compost on the weeds, +To make them ranker. Forgive me this my virtue; +For in the fatness of these pursy times +Virtue itself of vice must pardon beg, +Yea, curb and woo for leave to do him good. + +QUEEN. +O Hamlet, thou hast cleft my heart in twain. + +HAMLET. +O throw away the worser part of it, +And live the purer with the other half. +Good night. But go not to mine uncle’s bed. +Assume a virtue, if you have it not. +That monster custom, who all sense doth eat, +Of habits evil, is angel yet in this, +That to the use of actions fair and good +He likewise gives a frock or livery +That aptly is put on. Refrain tonight, +And that shall lend a kind of easiness +To the next abstinence. The next more easy; +For use almost can change the stamp of nature, +And either curb the devil, or throw him out +With wondrous potency. Once more, good night, +And when you are desirous to be bles’d, +I’ll blessing beg of you. For this same lord +[_Pointing to Polonius._] +I do repent; but heaven hath pleas’d it so, +To punish me with this, and this with me, +That I must be their scourge and minister. +I will bestow him, and will answer well +The death I gave him. So again, good night. +I must be cruel, only to be kind: +Thus bad begins, and worse remains behind. +One word more, good lady. + +QUEEN. +What shall I do? + +HAMLET. +Not this, by no means, that I bid you do: +Let the bloat King tempt you again to bed, +Pinch wanton on your cheek, call you his mouse, +And let him, for a pair of reechy kisses, +Or paddling in your neck with his damn’d fingers, +Make you to ravel all this matter out, +That I essentially am not in madness, +But mad in craft. ’Twere good you let him know, +For who that’s but a queen, fair, sober, wise, +Would from a paddock, from a bat, a gib, +Such dear concernings hide? Who would do so? +No, in despite of sense and secrecy, +Unpeg the basket on the house’s top, +Let the birds fly, and like the famous ape, +To try conclusions, in the basket creep +And break your own neck down. + +QUEEN. +Be thou assur’d, if words be made of breath, +And breath of life, I have no life to breathe +What thou hast said to me. + +HAMLET. +I must to England, you know that? + +QUEEN. +Alack, +I had forgot. ’Tis so concluded on. + +HAMLET. +There’s letters seal’d: and my two schoolfellows, +Whom I will trust as I will adders fang’d,— +They bear the mandate, they must sweep my way +And marshal me to knavery. Let it work; +For ’tis the sport to have the enginer +Hoist with his own petard, and ’t shall go hard +But I will delve one yard below their mines +And blow them at the moon. O, ’tis most sweet, +When in one line two crafts directly meet. +This man shall set me packing. +I’ll lug the guts into the neighbour room. +Mother, good night. Indeed, this counsellor +Is now most still, most secret, and most grave, +Who was in life a foolish prating knave. +Come, sir, to draw toward an end with you. +Good night, mother. + +[_Exit Hamlet dragging out Polonius._] + + + + +ACT IV + +SCENE I. A room in the Castle. + + +Enter King, Queen, Rosencrantz and Guildenstern. + +KING. +There’s matter in these sighs. These profound heaves +You must translate; ’tis fit we understand them. +Where is your son? + +QUEEN. +Bestow this place on us a little while. + +[_To Rosencrantz and Guildenstern, who go out._] + +Ah, my good lord, what have I seen tonight! + +KING. +What, Gertrude? How does Hamlet? + +QUEEN. +Mad as the sea and wind, when both contend +Which is the mightier. In his lawless fit +Behind the arras hearing something stir, +Whips out his rapier, cries ‘A rat, a rat!’ +And in this brainish apprehension kills +The unseen good old man. + +KING. +O heavy deed! +It had been so with us, had we been there. +His liberty is full of threats to all; +To you yourself, to us, to everyone. +Alas, how shall this bloody deed be answer’d? +It will be laid to us, whose providence +Should have kept short, restrain’d, and out of haunt +This mad young man. But so much was our love +We would not understand what was most fit, +But like the owner of a foul disease, +To keep it from divulging, let it feed +Even on the pith of life. Where is he gone? + +QUEEN. +To draw apart the body he hath kill’d, +O’er whom his very madness, like some ore +Among a mineral of metals base, +Shows itself pure. He weeps for what is done. + +KING. +O Gertrude, come away! +The sun no sooner shall the mountains touch +But we will ship him hence, and this vile deed +We must with all our majesty and skill +Both countenance and excuse.—Ho, Guildenstern! + +Re-enter Rosencrantz and Guildenstern. + +Friends both, go join you with some further aid: +Hamlet in madness hath Polonius slain, +And from his mother’s closet hath he dragg’d him. +Go seek him out, speak fair, and bring the body +Into the chapel. I pray you haste in this. + +[_Exeunt Rosencrantz and Guildenstern._] + +Come, Gertrude, we’ll call up our wisest friends, +And let them know both what we mean to do +And what’s untimely done, so haply slander, +Whose whisper o’er the world’s diameter, +As level as the cannon to his blank, +Transports his poison’d shot, may miss our name, +And hit the woundless air. O, come away! +My soul is full of discord and dismay. + +[_Exeunt._] + + SCENE II. Another room in the Castle. + +Enter Hamlet. + +HAMLET. +Safely stowed. + +ROSENCRANTZ and GUILDENSTERN. +[_Within._] Hamlet! Lord Hamlet! + +HAMLET. +What noise? Who calls on Hamlet? O, here they come. + +Enter Rosencrantz and Guildenstern. + +ROSENCRANTZ. +What have you done, my lord, with the dead body? + +HAMLET. +Compounded it with dust, whereto ’tis kin. + +ROSENCRANTZ. +Tell us where ’tis, that we may take it thence, +And bear it to the chapel. + +HAMLET. +Do not believe it. + +ROSENCRANTZ. +Believe what? + +HAMLET. +That I can keep your counsel, and not mine own. Besides, to be demanded +of a sponge—what replication should be made by the son of a king? + +ROSENCRANTZ. +Take you me for a sponge, my lord? + +HAMLET. +Ay, sir; that soaks up the King’s countenance, his rewards, his +authorities. But such officers do the King best service in the end: he +keeps them, like an ape, in the corner of his jaw; first mouthed, to be +last swallowed: when he needs what you have gleaned, it is but +squeezing you, and, sponge, you shall be dry again. + +ROSENCRANTZ. +I understand you not, my lord. + +HAMLET. +I am glad of it. A knavish speech sleeps in a foolish ear. + +ROSENCRANTZ. +My lord, you must tell us where the body is and go with us to the King. + +HAMLET. +The body is with the King, but the King is not with the body. The King +is a thing— + +GUILDENSTERN. +A thing, my lord! + +HAMLET. +Of nothing. Bring me to him. Hide fox, and all after. + +[_Exeunt._] + + SCENE III. Another room in the Castle. + +Enter King, attended. + +KING. +I have sent to seek him and to find the body. +How dangerous is it that this man goes loose! +Yet must not we put the strong law on him: +He’s lov’d of the distracted multitude, +Who like not in their judgement, but their eyes; +And where ’tis so, th’offender’s scourge is weigh’d, +But never the offence. To bear all smooth and even, +This sudden sending him away must seem +Deliberate pause. Diseases desperate grown +By desperate appliance are reliev’d, +Or not at all. + +Enter Rosencrantz. + +How now? What hath befall’n? + +ROSENCRANTZ. +Where the dead body is bestow’d, my lord, +We cannot get from him. + +KING. +But where is he? + +ROSENCRANTZ. +Without, my lord, guarded, to know your pleasure. + +KING. +Bring him before us. + +ROSENCRANTZ. +Ho, Guildenstern! Bring in my lord. + +Enter Hamlet and Guildenstern. + +KING. +Now, Hamlet, where’s Polonius? + +HAMLET. +At supper. + +KING. +At supper? Where? + +HAMLET. +Not where he eats, but where he is eaten. A certain convocation of +politic worms are e’en at him. Your worm is your only emperor for diet. +We fat all creatures else to fat us, and we fat ourselves for maggots. +Your fat king and your lean beggar is but variable service,—two dishes, +but to one table. That’s the end. + +KING. +Alas, alas! + +HAMLET. +A man may fish with the worm that hath eat of a king, and eat of the +fish that hath fed of that worm. + +KING. +What dost thou mean by this? + +HAMLET. +Nothing but to show you how a king may go a progress through the guts +of a beggar. + +KING. +Where is Polonius? + +HAMLET. +In heaven. Send thither to see. If your messenger find him not there, +seek him i’ th’other place yourself. But indeed, if you find him not +within this month, you shall nose him as you go up the stairs into the +lobby. + +KING. +[_To some Attendants._] Go seek him there. + +HAMLET. +He will stay till you come. + +[_Exeunt Attendants._] + +KING. +Hamlet, this deed, for thine especial safety,— +Which we do tender, as we dearly grieve +For that which thou hast done,—must send thee hence +With fiery quickness. Therefore prepare thyself; +The bark is ready, and the wind at help, +Th’associates tend, and everything is bent +For England. + +HAMLET. +For England? + +KING. +Ay, Hamlet. + +HAMLET. +Good. + +KING. +So is it, if thou knew’st our purposes. + +HAMLET. +I see a cherub that sees them. But, come; for England! Farewell, dear +mother. + +KING. +Thy loving father, Hamlet. + +HAMLET. +My mother. Father and mother is man and wife; man and wife is one +flesh; and so, my mother. Come, for England. + +[_Exit._] + +KING. +Follow him at foot. Tempt him with speed aboard; +Delay it not; I’ll have him hence tonight. +Away, for everything is seal’d and done +That else leans on th’affair. Pray you make haste. + +[_Exeunt Rosencrantz and Guildenstern._] + +And England, if my love thou hold’st at aught,— +As my great power thereof may give thee sense, +Since yet thy cicatrice looks raw and red +After the Danish sword, and thy free awe +Pays homage to us,—thou mayst not coldly set +Our sovereign process, which imports at full, +By letters conjuring to that effect, +The present death of Hamlet. Do it, England; +For like the hectic in my blood he rages, +And thou must cure me. Till I know ’tis done, +Howe’er my haps, my joys were ne’er begun. + +[_Exit._] + + SCENE IV. A plain in Denmark. + +Enter Fortinbras and Forces marching. + +FORTINBRAS. +Go, Captain, from me greet the Danish king. +Tell him that by his license, Fortinbras +Craves the conveyance of a promis’d march +Over his kingdom. You know the rendezvous. +If that his Majesty would aught with us, +We shall express our duty in his eye; +And let him know so. + +CAPTAIN. +I will do’t, my lord. + +FORTINBRAS. +Go softly on. + +[_Exeunt all but the Captain._] + +Enter Hamlet, Rosencrantz, Guildenstern &c. + +HAMLET. +Good sir, whose powers are these? + +CAPTAIN. +They are of Norway, sir. + +HAMLET. +How purpos’d, sir, I pray you? + +CAPTAIN. +Against some part of Poland. + +HAMLET. +Who commands them, sir? + +CAPTAIN. +The nephew to old Norway, Fortinbras. + +HAMLET. +Goes it against the main of Poland, sir, +Or for some frontier? + +CAPTAIN. +Truly to speak, and with no addition, +We go to gain a little patch of ground +That hath in it no profit but the name. +To pay five ducats, five, I would not farm it; +Nor will it yield to Norway or the Pole +A ranker rate, should it be sold in fee. + +HAMLET. +Why, then the Polack never will defend it. + +CAPTAIN. +Yes, it is already garrison’d. + +HAMLET. +Two thousand souls and twenty thousand ducats +Will not debate the question of this straw! +This is th’imposthume of much wealth and peace, +That inward breaks, and shows no cause without +Why the man dies. I humbly thank you, sir. + +CAPTAIN. +God b’ wi’ you, sir. + +[_Exit._] + +ROSENCRANTZ. +Will’t please you go, my lord? + +HAMLET. +I’ll be with you straight. Go a little before. + +[_Exeunt all but Hamlet._] + +How all occasions do inform against me, +And spur my dull revenge. What is a man +If his chief good and market of his time +Be but to sleep and feed? A beast, no more. +Sure he that made us with such large discourse, +Looking before and after, gave us not +That capability and godlike reason +To fust in us unus’d. Now whether it be +Bestial oblivion, or some craven scruple +Of thinking too precisely on th’event,— +A thought which, quarter’d, hath but one part wisdom +And ever three parts coward,—I do not know +Why yet I live to say this thing’s to do, +Sith I have cause, and will, and strength, and means +To do’t. Examples gross as earth exhort me, +Witness this army of such mass and charge, +Led by a delicate and tender prince, +Whose spirit, with divine ambition puff’d, +Makes mouths at the invisible event, +Exposing what is mortal and unsure +To all that fortune, death, and danger dare, +Even for an eggshell. Rightly to be great +Is not to stir without great argument, +But greatly to find quarrel in a straw +When honour’s at the stake. How stand I then, +That have a father kill’d, a mother stain’d, +Excitements of my reason and my blood, +And let all sleep, while to my shame I see +The imminent death of twenty thousand men +That, for a fantasy and trick of fame, +Go to their graves like beds, fight for a plot +Whereon the numbers cannot try the cause, +Which is not tomb enough and continent +To hide the slain? O, from this time forth, +My thoughts be bloody or be nothing worth. + +[_Exit._] + + SCENE V. Elsinore. A room in the Castle. + +Enter Queen, Horatio and a Gentleman. + +QUEEN. +I will not speak with her. + +GENTLEMAN. +She is importunate, indeed distract. +Her mood will needs be pitied. + +QUEEN. +What would she have? + +GENTLEMAN. +She speaks much of her father; says she hears +There’s tricks i’ th’ world, and hems, and beats her heart, +Spurns enviously at straws, speaks things in doubt, +That carry but half sense. Her speech is nothing, +Yet the unshaped use of it doth move +The hearers to collection; they aim at it, +And botch the words up fit to their own thoughts, +Which, as her winks, and nods, and gestures yield them, +Indeed would make one think there might be thought, +Though nothing sure, yet much unhappily. +’Twere good she were spoken with, for she may strew +Dangerous conjectures in ill-breeding minds. + +QUEEN. +Let her come in. + +[_Exit Gentleman._] + +To my sick soul, as sin’s true nature is, +Each toy seems prologue to some great amiss. +So full of artless jealousy is guilt, +It spills itself in fearing to be spilt. + +Enter Ophelia. + +OPHELIA. +Where is the beauteous Majesty of Denmark? + +QUEEN. +How now, Ophelia? + +OPHELIA. +[_Sings._] + How should I your true love know + From another one? + By his cockle hat and staff + And his sandal shoon. + +QUEEN. +Alas, sweet lady, what imports this song? + +OPHELIA. +Say you? Nay, pray you mark. +[_Sings._] + He is dead and gone, lady, + He is dead and gone, + At his head a grass green turf, + At his heels a stone. + +QUEEN. +Nay, but Ophelia— + +OPHELIA. +Pray you mark. +[_Sings._] + White his shroud as the mountain snow. + +Enter King. + +QUEEN. +Alas, look here, my lord! + +OPHELIA. +[_Sings._] + Larded all with sweet flowers; + Which bewept to the grave did not go + With true-love showers. + +KING. +How do you, pretty lady? + +OPHELIA. +Well, God dild you! They say the owl was a baker’s daughter. Lord, we +know what we are, but know not what we may be. God be at your table! + +KING. +Conceit upon her father. + +OPHELIA. +Pray you, let’s have no words of this; but when they ask you what it +means, say you this: +[_Sings._] + Tomorrow is Saint Valentine’s day, + All in the morning betime, + And I a maid at your window, + To be your Valentine. + + Then up he rose and donn’d his clothes, + And dupp’d the chamber door, + Let in the maid, that out a maid + Never departed more. + +KING. +Pretty Ophelia! + +OPHELIA. +Indeed la, without an oath, I’ll make an end on’t. +[_Sings._] + By Gis and by Saint Charity, + Alack, and fie for shame! + Young men will do’t if they come to’t; + By Cock, they are to blame. + + Quoth she, before you tumbled me, + You promis’d me to wed. + So would I ha’ done, by yonder sun, + An thou hadst not come to my bed. + +KING. +How long hath she been thus? + +OPHELIA. +I hope all will be well. We must be patient. But I cannot choose but +weep, to think they would lay him i’ th’ cold ground. My brother shall +know of it. And so I thank you for your good counsel. Come, my coach! +Good night, ladies; good night, sweet ladies; good night, good night. + +[_Exit._] + +KING. +Follow her close; give her good watch, I pray you. + +[_Exit Horatio._] + +O, this is the poison of deep grief; it springs +All from her father’s death. O Gertrude, Gertrude, +When sorrows come, they come not single spies, +But in battalions. First, her father slain; +Next, your son gone; and he most violent author +Of his own just remove; the people muddied, +Thick, and unwholesome in their thoughts and whispers +For good Polonius’ death; and we have done but greenly +In hugger-mugger to inter him. Poor Ophelia +Divided from herself and her fair judgement, +Without the which we are pictures or mere beasts. +Last, and as much containing as all these, +Her brother is in secret come from France, +Feeds on his wonder, keeps himself in clouds, +And wants not buzzers to infect his ear +With pestilent speeches of his father’s death, +Wherein necessity, of matter beggar’d, +Will nothing stick our person to arraign +In ear and ear. O my dear Gertrude, this, +Like to a murdering piece, in many places +Gives me superfluous death. + +[_A noise within._] + +QUEEN. +Alack, what noise is this? + +KING. +Where are my Switzers? Let them guard the door. + +Enter a Gentleman. + +What is the matter? + +GENTLEMAN. +Save yourself, my lord. +The ocean, overpeering of his list, +Eats not the flats with more impetuous haste +Than young Laertes, in a riotous head, +O’erbears your offices. The rabble call him lord, +And, as the world were now but to begin, +Antiquity forgot, custom not known, +The ratifiers and props of every word, +They cry ‘Choose we! Laertes shall be king!’ +Caps, hands, and tongues applaud it to the clouds, +‘Laertes shall be king, Laertes king.’ + +QUEEN. +How cheerfully on the false trail they cry. +O, this is counter, you false Danish dogs. + +[_A noise within._] + +KING. +The doors are broke. + +Enter Laertes, armed; Danes following. + +LAERTES. +Where is this king?—Sirs, stand you all without. + +Danes. +No, let’s come in. + +LAERTES. +I pray you, give me leave. + +DANES. +We will, we will. + +[_They retire without the door._] + +LAERTES. +I thank you. Keep the door. O thou vile king, +Give me my father. + +QUEEN. +Calmly, good Laertes. + +LAERTES. +That drop of blood that’s calm proclaims me bastard; +Cries cuckold to my father, brands the harlot +Even here between the chaste unsmirched brow +Of my true mother. + +KING. +What is the cause, Laertes, +That thy rebellion looks so giant-like?— +Let him go, Gertrude. Do not fear our person. +There’s such divinity doth hedge a king, +That treason can but peep to what it would, +Acts little of his will.—Tell me, Laertes, +Why thou art thus incens’d.—Let him go, Gertrude:— +Speak, man. + +LAERTES. +Where is my father? + +KING. +Dead. + +QUEEN. +But not by him. + +KING. +Let him demand his fill. + +LAERTES. +How came he dead? I’ll not be juggled with. +To hell, allegiance! Vows, to the blackest devil! +Conscience and grace, to the profoundest pit! +I dare damnation. To this point I stand, +That both the worlds, I give to negligence, +Let come what comes; only I’ll be reveng’d +Most throughly for my father. + +KING. +Who shall stay you? + +LAERTES. +My will, not all the world. +And for my means, I’ll husband them so well, +They shall go far with little. + +KING. +Good Laertes, +If you desire to know the certainty +Of your dear father’s death, is’t writ in your revenge +That, sweepstake, you will draw both friend and foe, +Winner and loser? + +LAERTES. +None but his enemies. + +KING. +Will you know them then? + +LAERTES. +To his good friends thus wide I’ll ope my arms; +And, like the kind life-rendering pelican, +Repast them with my blood. + +KING. +Why, now you speak +Like a good child and a true gentleman. +That I am guiltless of your father’s death, +And am most sensibly in grief for it, +It shall as level to your judgement ’pear +As day does to your eye. + +DANES. +[_Within._] Let her come in. + +LAERTES. +How now! What noise is that? + +Re-enter Ophelia, fantastically dressed with straws and flowers. + +O heat, dry up my brains. Tears seven times salt, +Burn out the sense and virtue of mine eye. +By heaven, thy madness shall be paid by weight, +Till our scale turn the beam. O rose of May! +Dear maid, kind sister, sweet Ophelia! +O heavens, is’t possible a young maid’s wits +Should be as mortal as an old man’s life? +Nature is fine in love, and where ’tis fine, +It sends some precious instance of itself +After the thing it loves. + +OPHELIA. +[_Sings._] + They bore him barefac’d on the bier, + Hey non nonny, nonny, hey nonny + And on his grave rain’d many a tear.— + Fare you well, my dove! + +LAERTES. +Hadst thou thy wits, and didst persuade revenge, +It could not move thus. + +OPHELIA. +You must sing ‘Down a-down, and you call him a-down-a.’ O, how the +wheel becomes it! It is the false steward that stole his master’s +daughter. + +LAERTES. +This nothing’s more than matter. + +OPHELIA. +There’s rosemary, that’s for remembrance; pray love, remember. And +there is pansies, that’s for thoughts. + +LAERTES. +A document in madness, thoughts and remembrance fitted. + +OPHELIA. +There’s fennel for you, and columbines. There’s rue for you; and here’s +some for me. We may call it herb of grace o’ Sundays. O you must wear +your rue with a difference. There’s a daisy. I would give you some +violets, but they wither’d all when my father died. They say he made a +good end. +[_Sings._] + For bonny sweet Robin is all my joy. + +LAERTES. +Thought and affliction, passion, hell itself +She turns to favour and to prettiness. + +OPHELIA. +[_Sings._] + And will he not come again? + And will he not come again? + No, no, he is dead, + Go to thy death-bed, + He never will come again. + + His beard was as white as snow, + All flaxen was his poll. + He is gone, he is gone, + And we cast away moan. + God ha’ mercy on his soul. + +And of all Christian souls, I pray God. God b’ wi’ ye. + +[_Exit._] + +LAERTES. +Do you see this, O God? + +KING. +Laertes, I must commune with your grief, +Or you deny me right. Go but apart, +Make choice of whom your wisest friends you will, +And they shall hear and judge ’twixt you and me. +If by direct or by collateral hand +They find us touch’d, we will our kingdom give, +Our crown, our life, and all that we call ours +To you in satisfaction; but if not, +Be you content to lend your patience to us, +And we shall jointly labour with your soul +To give it due content. + +LAERTES. +Let this be so; +His means of death, his obscure burial,— +No trophy, sword, nor hatchment o’er his bones, +No noble rite, nor formal ostentation,— +Cry to be heard, as ’twere from heaven to earth, +That I must call’t in question. + +KING. +So you shall. +And where th’offence is let the great axe fall. +I pray you go with me. + +[_Exeunt._] + + SCENE VI. Another room in the Castle. + +Enter Horatio and a Servant. + +HORATIO. +What are they that would speak with me? + +SERVANT. +Sailors, sir. They say they have letters for you. + +HORATIO. +Let them come in. + +[_Exit Servant._] + +I do not know from what part of the world +I should be greeted, if not from Lord Hamlet. + +Enter Sailors. + +FIRST SAILOR. +God bless you, sir. + +HORATIO. +Let him bless thee too. + +FIRST SAILOR. +He shall, sir, and’t please him. There’s a letter for you, sir. It +comes from th’ambassador that was bound for England; if your name be +Horatio, as I am let to know it is. + +HORATIO. +[_Reads._] ‘Horatio, when thou shalt have overlooked this, give these +fellows some means to the King. They have letters for him. Ere we were +two days old at sea, a pirate of very warlike appointment gave us +chase. Finding ourselves too slow of sail, we put on a compelled +valour, and in the grapple I boarded them. On the instant they got +clear of our ship, so I alone became their prisoner. They have dealt +with me like thieves of mercy. But they knew what they did; I am to do +a good turn for them. Let the King have the letters I have sent, and +repair thou to me with as much haste as thou wouldst fly death. I have +words to speak in thine ear will make thee dumb; yet are they much too +light for the bore of the matter. These good fellows will bring thee +where I am. Rosencrantz and Guildenstern hold their course for England: +of them I have much to tell thee. Farewell. + He that thou knowest thine, + HAMLET.’ + +Come, I will give you way for these your letters, +And do’t the speedier, that you may direct me +To him from whom you brought them. + +[_Exeunt._] + + SCENE VII. Another room in the Castle. + +Enter King and Laertes. + +KING. +Now must your conscience my acquittance seal, +And you must put me in your heart for friend, +Sith you have heard, and with a knowing ear, +That he which hath your noble father slain +Pursu’d my life. + +LAERTES. +It well appears. But tell me +Why you proceeded not against these feats, +So crimeful and so capital in nature, +As by your safety, wisdom, all things else, +You mainly were stirr’d up. + +KING. +O, for two special reasons, +Which may to you, perhaps, seem much unsinew’d, +But yet to me they are strong. The Queen his mother +Lives almost by his looks; and for myself,— +My virtue or my plague, be it either which,— +She’s so conjunctive to my life and soul, +That, as the star moves not but in his sphere, +I could not but by her. The other motive, +Why to a public count I might not go, +Is the great love the general gender bear him, +Who, dipping all his faults in their affection, +Would like the spring that turneth wood to stone, +Convert his gyves to graces; so that my arrows, +Too slightly timber’d for so loud a wind, +Would have reverted to my bow again, +And not where I had aim’d them. + +LAERTES. +And so have I a noble father lost, +A sister driven into desperate terms, +Whose worth, if praises may go back again, +Stood challenger on mount of all the age +For her perfections. But my revenge will come. + +KING. +Break not your sleeps for that. You must not think +That we are made of stuff so flat and dull +That we can let our beard be shook with danger, +And think it pastime. You shortly shall hear more. +I lov’d your father, and we love ourself, +And that, I hope, will teach you to imagine— + +Enter a Messenger. + +How now? What news? + +MESSENGER. +Letters, my lord, from Hamlet. +This to your Majesty; this to the Queen. + +KING. +From Hamlet! Who brought them? + +MESSENGER. +Sailors, my lord, they say; I saw them not. +They were given me by Claudio. He receiv’d them +Of him that brought them. + +KING. +Laertes, you shall hear them. +Leave us. + +[_Exit Messenger._] + +[_Reads._] ‘High and mighty, you shall know I am set naked on your +kingdom. Tomorrow shall I beg leave to see your kingly eyes. When I +shall, first asking your pardon thereunto, recount the occasions of my +sudden and more strange return. + HAMLET.’ + +What should this mean? Are all the rest come back? +Or is it some abuse, and no such thing? + +LAERTES. +Know you the hand? + +KING. +’Tis Hamlet’s character. ‘Naked!’ +And in a postscript here he says ‘alone.’ +Can you advise me? + +LAERTES. +I am lost in it, my lord. But let him come, +It warms the very sickness in my heart +That I shall live and tell him to his teeth, +‘Thus diest thou.’ + +KING. +If it be so, Laertes,— +As how should it be so? How otherwise?— +Will you be rul’d by me? + +LAERTES. +Ay, my lord; +So you will not o’errule me to a peace. + +KING. +To thine own peace. If he be now return’d, +As checking at his voyage, and that he means +No more to undertake it, I will work him +To an exploit, now ripe in my device, +Under the which he shall not choose but fall; +And for his death no wind shall breathe, +But even his mother shall uncharge the practice +And call it accident. + +LAERTES. +My lord, I will be rul’d; +The rather if you could devise it so +That I might be the organ. + +KING. +It falls right. +You have been talk’d of since your travel much, +And that in Hamlet’s hearing, for a quality +Wherein they say you shine. Your sum of parts +Did not together pluck such envy from him +As did that one, and that, in my regard, +Of the unworthiest siege. + +LAERTES. +What part is that, my lord? + +KING. +A very riband in the cap of youth, +Yet needful too, for youth no less becomes +The light and careless livery that it wears +Than settled age his sables and his weeds, +Importing health and graveness. Two months since +Here was a gentleman of Normandy,— +I’ve seen myself, and serv’d against, the French, +And they can well on horseback, but this gallant +Had witchcraft in’t. He grew unto his seat, +And to such wondrous doing brought his horse, +As had he been incorps’d and demi-natur’d +With the brave beast. So far he topp’d my thought +That I in forgery of shapes and tricks, +Come short of what he did. + +LAERTES. +A Norman was’t? + +KING. +A Norman. + +LAERTES. +Upon my life, Lamord. + +KING. +The very same. + +LAERTES. +I know him well. He is the brooch indeed +And gem of all the nation. + +KING. +He made confession of you, +And gave you such a masterly report +For art and exercise in your defence, +And for your rapier most especially, +That he cried out ’twould be a sight indeed +If one could match you. The scrimers of their nation +He swore had neither motion, guard, nor eye, +If you oppos’d them. Sir, this report of his +Did Hamlet so envenom with his envy +That he could nothing do but wish and beg +Your sudden coming o’er to play with him. +Now, out of this,— + +LAERTES. +What out of this, my lord? + +KING. +Laertes, was your father dear to you? +Or are you like the painting of a sorrow, +A face without a heart? + +LAERTES. +Why ask you this? + +KING. +Not that I think you did not love your father, +But that I know love is begun by time, +And that I see, in passages of proof, +Time qualifies the spark and fire of it. +There lives within the very flame of love +A kind of wick or snuff that will abate it; +And nothing is at a like goodness still, +For goodness, growing to a pleurisy, +Dies in his own too much. That we would do, +We should do when we would; for this ‘would’ changes, +And hath abatements and delays as many +As there are tongues, are hands, are accidents; +And then this ‘should’ is like a spendthrift sigh +That hurts by easing. But to the quick o’ th’ulcer: +Hamlet comes back: what would you undertake +To show yourself your father’s son in deed, +More than in words? + +LAERTES. +To cut his throat i’ th’ church. + +KING. +No place, indeed, should murder sanctuarize; +Revenge should have no bounds. But good Laertes, +Will you do this, keep close within your chamber. +Hamlet return’d shall know you are come home: +We’ll put on those shall praise your excellence, +And set a double varnish on the fame +The Frenchman gave you, bring you in fine together +And wager on your heads. He, being remiss, +Most generous, and free from all contriving, +Will not peruse the foils; so that with ease, +Or with a little shuffling, you may choose +A sword unbated, and in a pass of practice, +Requite him for your father. + +LAERTES. +I will do’t. +And for that purpose I’ll anoint my sword. +I bought an unction of a mountebank +So mortal that, but dip a knife in it, +Where it draws blood no cataplasm so rare, +Collected from all simples that have virtue +Under the moon, can save the thing from death +This is but scratch’d withal. I’ll touch my point +With this contagion, that if I gall him slightly, +It may be death. + +KING. +Let’s further think of this, +Weigh what convenience both of time and means +May fit us to our shape. If this should fail, +And that our drift look through our bad performance. +’Twere better not assay’d. Therefore this project +Should have a back or second, that might hold +If this did blast in proof. Soft, let me see. +We’ll make a solemn wager on your cunnings,— +I ha’t! When in your motion you are hot and dry, +As make your bouts more violent to that end, +And that he calls for drink, I’ll have prepar’d him +A chalice for the nonce; whereon but sipping, +If he by chance escape your venom’d stuck, +Our purpose may hold there. + +Enter Queen. + +How now, sweet Queen? + +QUEEN. +One woe doth tread upon another’s heel, +So fast they follow. Your sister’s drown’d, Laertes. + +LAERTES. +Drown’d! O, where? + +QUEEN. +There is a willow grows aslant a brook, +That shows his hoary leaves in the glassy stream. +There with fantastic garlands did she make +Of crow-flowers, nettles, daisies, and long purples, +That liberal shepherds give a grosser name, +But our cold maids do dead men’s fingers call them. +There on the pendant boughs her coronet weeds +Clamb’ring to hang, an envious sliver broke, +When down her weedy trophies and herself +Fell in the weeping brook. Her clothes spread wide, +And mermaid-like, awhile they bore her up, +Which time she chaunted snatches of old tunes, +As one incapable of her own distress, +Or like a creature native and indued +Unto that element. But long it could not be +Till that her garments, heavy with their drink, +Pull’d the poor wretch from her melodious lay +To muddy death. + +LAERTES. +Alas, then she is drown’d? + +QUEEN. +Drown’d, drown’d. + +LAERTES. +Too much of water hast thou, poor Ophelia, +And therefore I forbid my tears. But yet +It is our trick; nature her custom holds, +Let shame say what it will. When these are gone, +The woman will be out. Adieu, my lord, +I have a speech of fire, that fain would blaze, +But that this folly douts it. + +[_Exit._] + +KING. +Let’s follow, Gertrude; +How much I had to do to calm his rage! +Now fear I this will give it start again; +Therefore let’s follow. + +[_Exeunt._] + + + + +ACT V + +SCENE I. A churchyard. + + +Enter two Clowns with spades, &c. + +FIRST CLOWN. +Is she to be buried in Christian burial, when she wilfully seeks her +own salvation? + +SECOND CLOWN. +I tell thee she is, and therefore make her grave straight. The crowner +hath sat on her, and finds it Christian burial. + +FIRST CLOWN. +How can that be, unless she drowned herself in her own defence? + +SECOND CLOWN. +Why, ’tis found so. + +FIRST CLOWN. +It must be _se offendendo_, it cannot be else. For here lies the point: +if I drown myself wittingly, it argues an act: and an act hath three +branches. It is to act, to do, and to perform: argal, she drowned +herself wittingly. + +SECOND CLOWN. +Nay, but hear you, goodman delver,— + +FIRST CLOWN. +Give me leave. Here lies the water; good. Here stands the man; good. If +the man go to this water and drown himself, it is, will he nill he, he +goes,—mark you that. But if the water come to him and drown him, he +drowns not himself. Argal, he that is not guilty of his own death +shortens not his own life. + +SECOND CLOWN. +But is this law? + +FIRST CLOWN. +Ay, marry, is’t, crowner’s quest law. + +SECOND CLOWN. +Will you ha’ the truth on’t? If this had not been a gentlewoman, she +should have been buried out o’ Christian burial. + +FIRST CLOWN. +Why, there thou say’st. And the more pity that great folk should have +countenance in this world to drown or hang themselves more than their +even Christian. Come, my spade. There is no ancient gentlemen but +gardeners, ditchers, and grave-makers: they hold up Adam’s profession. + +SECOND CLOWN. +Was he a gentleman? + +FIRST CLOWN. +He was the first that ever bore arms. + +SECOND CLOWN. +Why, he had none. + +FIRST CLOWN. +What, art a heathen? How dost thou understand the Scripture? The +Scripture says Adam digg’d. Could he dig without arms? I’ll put another +question to thee. If thou answerest me not to the purpose, confess +thyself— + +SECOND CLOWN. +Go to. + +FIRST CLOWN. +What is he that builds stronger than either the mason, the shipwright, +or the carpenter? + +SECOND CLOWN. +The gallows-maker; for that frame outlives a thousand tenants. + +FIRST CLOWN. +I like thy wit well in good faith, the gallows does well. But how does +it well? It does well to those that do ill. Now, thou dost ill to say +the gallows is built stronger than the church; argal, the gallows may +do well to thee. To’t again, come. + +SECOND CLOWN. +Who builds stronger than a mason, a shipwright, or a carpenter? + +FIRST CLOWN. +Ay, tell me that, and unyoke. + +SECOND CLOWN. +Marry, now I can tell. + +FIRST CLOWN. +To’t. + +SECOND CLOWN. +Mass, I cannot tell. + +Enter Hamlet and Horatio, at a distance. + +FIRST CLOWN. +Cudgel thy brains no more about it, for your dull ass will not mend his +pace with beating; and when you are asked this question next, say ‘a +grave-maker’. The houses he makes last till doomsday. Go, get thee to +Yaughan; fetch me a stoup of liquor. + +[_Exit Second Clown._] + +[_Digs and sings._] + + In youth when I did love, did love, + Methought it was very sweet; + To contract, O, the time for, a, my behove, + O methought there was nothing meet. + +HAMLET. +Has this fellow no feeling of his business, that he sings at +grave-making? + +HORATIO. +Custom hath made it in him a property of easiness. + +HAMLET. +’Tis e’en so; the hand of little employment hath the daintier sense. + +FIRST CLOWN. +[_Sings._] + But age with his stealing steps + Hath claw’d me in his clutch, + And hath shipp’d me into the land, + As if I had never been such. + +[_Throws up a skull._] + +HAMLET. +That skull had a tongue in it, and could sing once. How the knave jowls +it to th’ ground, as if ’twere Cain’s jawbone, that did the first +murder! This might be the pate of a politician which this ass now +o’er-offices, one that would circumvent God, might it not? + +HORATIO. +It might, my lord. + +HAMLET. +Or of a courtier, which could say ‘Good morrow, sweet lord! How dost +thou, good lord?’ This might be my lord such-a-one, that praised my +lord such-a-one’s horse when he meant to beg it, might it not? + +HORATIO. +Ay, my lord. + +HAMLET. +Why, e’en so: and now my Lady Worm’s; chapless, and knocked about the +mazard with a sexton’s spade. Here’s fine revolution, an we had the +trick to see’t. Did these bones cost no more the breeding but to play +at loggets with ’em? Mine ache to think on’t. + +FIRST CLOWN. +[_Sings._] + A pickaxe and a spade, a spade, + For and a shrouding-sheet; + O, a pit of clay for to be made + For such a guest is meet. + +[_Throws up another skull._] + +HAMLET. +There’s another. Why may not that be the skull of a lawyer? Where be +his quiddits now, his quillets, his cases, his tenures, and his tricks? +Why does he suffer this rude knave now to knock him about the sconce +with a dirty shovel, and will not tell him of his action of battery? +Hum. This fellow might be in’s time a great buyer of land, with his +statutes, his recognizances, his fines, his double vouchers, his +recoveries. Is this the fine of his fines, and the recovery of his +recoveries, to have his fine pate full of fine dirt? Will his vouchers +vouch him no more of his purchases, and double ones too, than the +length and breadth of a pair of indentures? The very conveyances of his +lands will scarcely lie in this box; and must the inheritor himself +have no more, ha? + +HORATIO. +Not a jot more, my lord. + +HAMLET. +Is not parchment made of sheep-skins? + +HORATIO. +Ay, my lord, and of calf-skins too. + +HAMLET. +They are sheep and calves which seek out assurance in that. I will +speak to this fellow.—Whose grave’s this, sir? + +FIRST CLOWN. +Mine, sir. +[_Sings._] + O, a pit of clay for to be made + For such a guest is meet. + +HAMLET. +I think it be thine indeed, for thou liest in’t. + +FIRST CLOWN. +You lie out on’t, sir, and therefore ’tis not yours. +For my part, I do not lie in’t, yet it is mine. + +HAMLET. +Thou dost lie in’t, to be in’t and say it is thine. ’Tis for the dead, +not for the quick; therefore thou liest. + +FIRST CLOWN. +’Tis a quick lie, sir; ’t will away again from me to you. + +HAMLET. +What man dost thou dig it for? + +FIRST CLOWN. +For no man, sir. + +HAMLET. +What woman then? + +FIRST CLOWN. +For none neither. + +HAMLET. +Who is to be buried in’t? + +FIRST CLOWN. +One that was a woman, sir; but, rest her soul, she’s dead. + +HAMLET. +How absolute the knave is! We must speak by the card, or equivocation +will undo us. By the Lord, Horatio, these three years I have taken note +of it, the age is grown so picked that the toe of the peasant comes so +near the heel of the courtier he galls his kibe.—How long hast thou +been a grave-maker? + +FIRST CLOWN. +Of all the days i’ th’ year, I came to’t that day that our last King +Hamlet o’ercame Fortinbras. + +HAMLET. +How long is that since? + +FIRST CLOWN. +Cannot you tell that? Every fool can tell that. It was the very day +that young Hamlet was born,—he that is mad, and sent into England. + +HAMLET. +Ay, marry, why was he sent into England? + +FIRST CLOWN. +Why, because he was mad; he shall recover his wits there; or if he do +not, it’s no great matter there. + +HAMLET. +Why? + +FIRST CLOWN. +’Twill not be seen in him there; there the men are as mad as he. + +HAMLET. +How came he mad? + +FIRST CLOWN. +Very strangely, they say. + +HAMLET. +How strangely? + +FIRST CLOWN. +Faith, e’en with losing his wits. + +HAMLET. +Upon what ground? + +FIRST CLOWN. +Why, here in Denmark. I have been sexton here, man and boy, thirty +years. + +HAMLET. +How long will a man lie i’ th’earth ere he rot? + +FIRST CLOWN. +Faith, if he be not rotten before he die,—as we have many pocky corses +nowadays that will scarce hold the laying in,—he will last you some +eight year or nine year. A tanner will last you nine year. + +HAMLET. +Why he more than another? + +FIRST CLOWN. +Why, sir, his hide is so tann’d with his trade that he will keep out +water a great while. And your water is a sore decayer of your whoreson +dead body. Here’s a skull now; this skull hath lain in the earth +three-and-twenty years. + +HAMLET. +Whose was it? + +FIRST CLOWN. +A whoreson, mad fellow’s it was. Whose do you think it was? + +HAMLET. +Nay, I know not. + +FIRST CLOWN. +A pestilence on him for a mad rogue! A pour’d a flagon of Rhenish on my +head once. This same skull, sir, was Yorick’s skull, the King’s jester. + +HAMLET. +This? + +FIRST CLOWN. +E’en that. + +HAMLET. +Let me see. [_Takes the skull._] Alas, poor Yorick. I knew him, +Horatio, a fellow of infinite jest, of most excellent fancy. He hath +borne me on his back a thousand times; and now, how abhorred in my +imagination it is! My gorge rises at it. Here hung those lips that I +have kiss’d I know not how oft. Where be your gibes now? your gambols? +your songs? your flashes of merriment, that were wont to set the table +on a roar? Not one now, to mock your own grinning? Quite chop-fallen? +Now get you to my lady’s chamber, and tell her, let her paint an inch +thick, to this favour she must come. Make her laugh at that.—Prithee, +Horatio, tell me one thing. + +HORATIO. +What’s that, my lord? + +HAMLET. +Dost thou think Alexander looked o’ this fashion i’ th’earth? + +HORATIO. +E’en so. + +HAMLET. +And smelt so? Pah! + +[_Throws down the skull._] + +HORATIO. +E’en so, my lord. + +HAMLET. +To what base uses we may return, Horatio! Why may not imagination trace +the noble dust of Alexander till he find it stopping a bung-hole? + +HORATIO. +’Twere to consider too curiously to consider so. + +HAMLET. +No, faith, not a jot. But to follow him thither with modesty enough, +and likelihood to lead it; as thus. Alexander died, Alexander was +buried, Alexander returneth into dust; the dust is earth; of earth we +make loam; and why of that loam whereto he was converted might they not +stop a beer-barrel? +Imperious Caesar, dead and turn’d to clay, +Might stop a hole to keep the wind away. +O, that that earth which kept the world in awe +Should patch a wall t’expel the winter’s flaw. +But soft! but soft! aside! Here comes the King. + +Enter priests, &c, in procession; the corpse of Ophelia, Laertes and +Mourners following; King, Queen, their Trains, &c. + +The Queen, the courtiers. Who is that they follow? +And with such maimed rites? This doth betoken +The corse they follow did with desperate hand +Fordo it own life. ’Twas of some estate. +Couch we awhile and mark. + +[_Retiring with Horatio._] + +LAERTES. +What ceremony else? + +HAMLET. +That is Laertes, a very noble youth. Mark. + +LAERTES. +What ceremony else? + +PRIEST. +Her obsequies have been as far enlarg’d +As we have warranties. Her death was doubtful; +And but that great command o’ersways the order, +She should in ground unsanctified have lodg’d +Till the last trumpet. For charitable prayers, +Shards, flints, and pebbles should be thrown on her. +Yet here she is allowed her virgin rites, +Her maiden strewments, and the bringing home +Of bell and burial. + +LAERTES. +Must there no more be done? + +PRIEST. +No more be done. +We should profane the service of the dead +To sing sage requiem and such rest to her +As to peace-parted souls. + +LAERTES. +Lay her i’ th’earth, +And from her fair and unpolluted flesh +May violets spring. I tell thee, churlish priest, +A minist’ring angel shall my sister be +When thou liest howling. + +HAMLET. +What, the fair Ophelia? + +QUEEN. +[_Scattering flowers._] Sweets to the sweet. Farewell. +I hop’d thou shouldst have been my Hamlet’s wife; +I thought thy bride-bed to have deck’d, sweet maid, +And not have strew’d thy grave. + +LAERTES. +O, treble woe +Fall ten times treble on that cursed head +Whose wicked deed thy most ingenious sense +Depriv’d thee of. Hold off the earth a while, +Till I have caught her once more in mine arms. +[_Leaps into the grave._] +Now pile your dust upon the quick and dead, +Till of this flat a mountain you have made, +To o’ertop old Pelion or the skyish head +Of blue Olympus. + +HAMLET. +[_Advancing._] +What is he whose grief +Bears such an emphasis? whose phrase of sorrow +Conjures the wand’ring stars, and makes them stand +Like wonder-wounded hearers? This is I, +Hamlet the Dane. +[_Leaps into the grave._] + +LAERTES. +[_Grappling with him._] The devil take thy soul! + +HAMLET. +Thou pray’st not well. +I prithee take thy fingers from my throat; +For though I am not splenative and rash, +Yet have I in me something dangerous, +Which let thy wiseness fear. Away thy hand! + +KING. +Pluck them asunder. + +QUEEN. +Hamlet! Hamlet! + +All. +Gentlemen! + +HORATIO. +Good my lord, be quiet. + +[_The Attendants part them, and they come out of the grave._] + +HAMLET. +Why, I will fight with him upon this theme +Until my eyelids will no longer wag. + +QUEEN. +O my son, what theme? + +HAMLET. +I lov’d Ophelia; forty thousand brothers +Could not, with all their quantity of love, +Make up my sum. What wilt thou do for her? + +KING. +O, he is mad, Laertes. + +QUEEN. +For love of God forbear him! + +HAMLET. +’Swounds, show me what thou’lt do: +Woul’t weep? woul’t fight? woul’t fast? woul’t tear thyself? +Woul’t drink up eisel? eat a crocodile? +I’ll do’t. Dost thou come here to whine? +To outface me with leaping in her grave? +Be buried quick with her, and so will I. +And if thou prate of mountains, let them throw +Millions of acres on us, till our ground, +Singeing his pate against the burning zone, +Make Ossa like a wart. Nay, an thou’lt mouth, +I’ll rant as well as thou. + +QUEEN. +This is mere madness: +And thus awhile the fit will work on him; +Anon, as patient as the female dove, +When that her golden couplets are disclos’d, +His silence will sit drooping. + +HAMLET. +Hear you, sir; +What is the reason that you use me thus? +I lov’d you ever. But it is no matter. +Let Hercules himself do what he may, +The cat will mew, and dog will have his day. + +[_Exit._] + +KING. +I pray thee, good Horatio, wait upon him. + +[_Exit Horatio._] + +[_To Laertes_] +Strengthen your patience in our last night’s speech; +We’ll put the matter to the present push.— +Good Gertrude, set some watch over your son. +This grave shall have a living monument. +An hour of quiet shortly shall we see; +Till then in patience our proceeding be. + +[_Exeunt._] + + SCENE II. A hall in the Castle. + +Enter Hamlet and Horatio. + +HAMLET. +So much for this, sir. Now let me see the other; +You do remember all the circumstance? + +HORATIO. +Remember it, my lord! + +HAMLET. +Sir, in my heart there was a kind of fighting +That would not let me sleep. Methought I lay +Worse than the mutinies in the bilboes. Rashly, +And prais’d be rashness for it,—let us know, +Our indiscretion sometime serves us well, +When our deep plots do pall; and that should teach us +There’s a divinity that shapes our ends, +Rough-hew them how we will. + +HORATIO. +That is most certain. + +HAMLET. +Up from my cabin, +My sea-gown scarf’d about me, in the dark +Grop’d I to find out them; had my desire, +Finger’d their packet, and in fine, withdrew +To mine own room again, making so bold, +My fears forgetting manners, to unseal +Their grand commission; where I found, Horatio, +Oh royal knavery! an exact command, +Larded with many several sorts of reasons, +Importing Denmark’s health, and England’s too, +With ho! such bugs and goblins in my life, +That on the supervise, no leisure bated, +No, not to stay the grinding of the axe, +My head should be struck off. + +HORATIO. +Is’t possible? + +HAMLET. +Here’s the commission, read it at more leisure. +But wilt thou hear me how I did proceed? + +HORATIO. +I beseech you. + +HAMLET. +Being thus benetted round with villanies,— +Or I could make a prologue to my brains, +They had begun the play,—I sat me down, +Devis’d a new commission, wrote it fair: +I once did hold it, as our statists do, +A baseness to write fair, and labour’d much +How to forget that learning; but, sir, now +It did me yeoman’s service. Wilt thou know +The effect of what I wrote? + +HORATIO. +Ay, good my lord. + +HAMLET. +An earnest conjuration from the King, +As England was his faithful tributary, +As love between them like the palm might flourish, +As peace should still her wheaten garland wear +And stand a comma ’tween their amities, +And many such-like ‘as’es of great charge, +That on the view and know of these contents, +Without debatement further, more or less, +He should the bearers put to sudden death, +Not shriving-time allow’d. + +HORATIO. +How was this seal’d? + +HAMLET. +Why, even in that was heaven ordinant. +I had my father’s signet in my purse, +Which was the model of that Danish seal: +Folded the writ up in the form of the other, +Subscrib’d it: gave’t th’impression; plac’d it safely, +The changeling never known. Now, the next day +Was our sea-fight, and what to this was sequent +Thou know’st already. + +HORATIO. +So Guildenstern and Rosencrantz go to’t. + +HAMLET. +Why, man, they did make love to this employment. +They are not near my conscience; their defeat +Does by their own insinuation grow. +’Tis dangerous when the baser nature comes +Between the pass and fell incensed points +Of mighty opposites. + +HORATIO. +Why, what a king is this! + +HAMLET. +Does it not, thinks’t thee, stand me now upon,— +He that hath kill’d my king, and whor’d my mother, +Popp’d in between th’election and my hopes, +Thrown out his angle for my proper life, +And with such cozenage—is’t not perfect conscience +To quit him with this arm? And is’t not to be damn’d +To let this canker of our nature come +In further evil? + +HORATIO. +It must be shortly known to him from England +What is the issue of the business there. + +HAMLET. +It will be short. The interim is mine; +And a man’s life’s no more than to say ‘One’. +But I am very sorry, good Horatio, +That to Laertes I forgot myself; +For by the image of my cause I see +The portraiture of his. I’ll court his favours. +But sure the bravery of his grief did put me +Into a tow’ring passion. + +HORATIO. +Peace, who comes here? + +Enter Osric. + +OSRIC. +Your lordship is right welcome back to Denmark. + +HAMLET. +I humbly thank you, sir. Dost know this waterfly? + +HORATIO. +No, my good lord. + +HAMLET. +Thy state is the more gracious; for ’tis a vice to know him. He hath +much land, and fertile; let a beast be lord of beasts, and his crib +shall stand at the king’s mess; ’tis a chough; but, as I say, spacious +in the possession of dirt. + +OSRIC. +Sweet lord, if your lordship were at leisure, I should impart a thing +to you from his Majesty. + +HAMLET. +I will receive it with all diligence of spirit. Put your bonnet to his +right use; ’tis for the head. + +OSRIC. +I thank your lordship, ’tis very hot. + +HAMLET. +No, believe me, ’tis very cold, the wind is northerly. + +OSRIC. +It is indifferent cold, my lord, indeed. + +HAMLET. +Methinks it is very sultry and hot for my complexion. + +OSRIC. +Exceedingly, my lord; it is very sultry,—as ’twere—I cannot tell how. +But, my lord, his Majesty bade me signify to you that he has laid a +great wager on your head. Sir, this is the matter,— + +HAMLET. +I beseech you, remember,— + +[_Hamlet moves him to put on his hat._] + +OSRIC. +Nay, in good faith; for mine ease, in good faith. Sir, here is newly +come to court Laertes; believe me, an absolute gentleman, full of most +excellent differences, of very soft society and great showing. Indeed, +to speak feelingly of him, he is the card or calendar of gentry; for +you shall find in him the continent of what part a gentleman would see. + +HAMLET. +Sir, his definement suffers no perdition in you, though I know, to +divide him inventorially would dizzy th’arithmetic of memory, and yet +but yaw neither, in respect of his quick sail. But, in the verity of +extolment, I take him to be a soul of great article and his infusion of +such dearth and rareness as, to make true diction of him, his semblable +is his mirror and who else would trace him his umbrage, nothing more. + +OSRIC. +Your lordship speaks most infallibly of him. + +HAMLET. +The concernancy, sir? Why do we wrap the gentleman in our more rawer +breath? + +OSRIC. +Sir? + +HORATIO. +Is’t not possible to understand in another tongue? You will do’t, sir, +really. + +HAMLET. +What imports the nomination of this gentleman? + +OSRIC. +Of Laertes? + +HORATIO. +His purse is empty already, all’s golden words are spent. + +HAMLET. +Of him, sir. + +OSRIC. +I know you are not ignorant,— + +HAMLET. +I would you did, sir; yet in faith if you did, it would not much +approve me. Well, sir? + +OSRIC. +You are not ignorant of what excellence Laertes is,— + +HAMLET. +I dare not confess that, lest I should compare with him in excellence; +but to know a man well were to know himself. + +OSRIC. +I mean, sir, for his weapon; but in the imputation laid on him, by them +in his meed he’s unfellowed. + +HAMLET. +What’s his weapon? + +OSRIC. +Rapier and dagger. + +HAMLET. +That’s two of his weapons. But well. + +OSRIC. +The King, sir, hath wager’d with him six Barbary horses, against the +which he has imponed, as I take it, six French rapiers and poniards, +with their assigns, as girdle, hangers, and so. Three of the carriages, +in faith, are very dear to fancy, very responsive to the hilts, most +delicate carriages, and of very liberal conceit. + +HAMLET. +What call you the carriages? + +HORATIO. +I knew you must be edified by the margin ere you had done. + +OSRIC. +The carriages, sir, are the hangers. + +HAMLET. +The phrase would be more german to the matter if we could carry cannon +by our sides. I would it might be hangers till then. But on. Six +Barbary horses against six French swords, their assigns, and three +liberal conceited carriages: that’s the French bet against the Danish. +Why is this all imponed, as you call it? + +OSRIC. +The King, sir, hath laid that in a dozen passes between you and him, he +shall not exceed you three hits. He hath laid on twelve for nine. And +it would come to immediate trial if your lordship would vouchsafe the +answer. + +HAMLET. +How if I answer no? + +OSRIC. +I mean, my lord, the opposition of your person in trial. + +HAMLET. +Sir, I will walk here in the hall. If it please his Majesty, it is the +breathing time of day with me. Let the foils be brought, the gentleman +willing, and the King hold his purpose, I will win for him if I can; if +not, I will gain nothing but my shame and the odd hits. + +OSRIC. +Shall I re-deliver you e’en so? + +HAMLET. +To this effect, sir; after what flourish your nature will. + +OSRIC. +I commend my duty to your lordship. + +HAMLET. +Yours, yours. + +[_Exit Osric._] + +He does well to commend it himself, there are no tongues else for’s +turn. + +HORATIO. +This lapwing runs away with the shell on his head. + +HAMLET. +He did comply with his dug before he suck’d it. Thus has he,—and many +more of the same bevy that I know the drossy age dotes on,— only got +the tune of the time and outward habit of encounter; a kind of yeasty +collection, which carries them through and through the most fanned and +winnowed opinions; and do but blow them to their trial, the bubbles are +out. + +Enter a Lord. + +LORD. +My lord, his Majesty commended him to you by young Osric, who brings +back to him that you attend him in the hall. He sends to know if your +pleasure hold to play with Laertes or that you will take longer time. + +HAMLET. +I am constant to my purposes, they follow the King’s pleasure. If his +fitness speaks, mine is ready. Now or whensoever, provided I be so able +as now. + +LORD. +The King and Queen and all are coming down. + +HAMLET. +In happy time. + +LORD. +The Queen desires you to use some gentle entertainment to Laertes +before you fall to play. + +HAMLET. +She well instructs me. + +[_Exit Lord._] + +HORATIO. +You will lose this wager, my lord. + +HAMLET. +I do not think so. Since he went into France, I have been in continual +practice. I shall win at the odds. But thou wouldst not think how ill +all’s here about my heart: but it is no matter. + +HORATIO. +Nay, good my lord. + +HAMLET. +It is but foolery; but it is such a kind of gain-giving as would +perhaps trouble a woman. + +HORATIO. +If your mind dislike anything, obey it. I will forestall their repair +hither, and say you are not fit. + +HAMLET. +Not a whit, we defy augury. There’s a special providence in the fall of +a sparrow. If it be now, ’tis not to come; if it be not to come, it +will be now; if it be not now, yet it will come. The readiness is all. +Since no man has aught of what he leaves, what is’t to leave betimes? + +Enter King, Queen, Laertes, Lords, Osric and Attendants with foils &c. + +KING. +Come, Hamlet, come, and take this hand from me. + +[_The King puts Laertes’s hand into Hamlet’s._] + +HAMLET. +Give me your pardon, sir. I have done you wrong; +But pardon’t as you are a gentleman. +This presence knows, and you must needs have heard, +How I am punish’d with sore distraction. +What I have done +That might your nature, honour, and exception +Roughly awake, I here proclaim was madness. +Was’t Hamlet wrong’d Laertes? Never Hamlet. +If Hamlet from himself be ta’en away, +And when he’s not himself does wrong Laertes, +Then Hamlet does it not, Hamlet denies it. +Who does it, then? His madness. If’t be so, +Hamlet is of the faction that is wrong’d; +His madness is poor Hamlet’s enemy. +Sir, in this audience, +Let my disclaiming from a purpos’d evil +Free me so far in your most generous thoughts +That I have shot my arrow o’er the house +And hurt my brother. + +LAERTES. +I am satisfied in nature, +Whose motive in this case should stir me most +To my revenge. But in my terms of honour +I stand aloof, and will no reconcilement +Till by some elder masters of known honour +I have a voice and precedent of peace +To keep my name ungor’d. But till that time +I do receive your offer’d love like love, +And will not wrong it. + +HAMLET. +I embrace it freely, +And will this brother’s wager frankly play.— +Give us the foils; come on. + +LAERTES. +Come, one for me. + +HAMLET. +I’ll be your foil, Laertes; in mine ignorance +Your skill shall like a star i’ th’ darkest night, +Stick fiery off indeed. + +LAERTES. +You mock me, sir. + +HAMLET. +No, by this hand. + +KING. +Give them the foils, young Osric. Cousin Hamlet, +You know the wager? + +HAMLET. +Very well, my lord. +Your Grace has laid the odds o’ the weaker side. + +KING. +I do not fear it. I have seen you both; +But since he is better’d, we have therefore odds. + +LAERTES. +This is too heavy. Let me see another. + +HAMLET. +This likes me well. These foils have all a length? + +[_They prepare to play._] + +OSRIC. +Ay, my good lord. + +KING. +Set me the stoups of wine upon that table. +If Hamlet give the first or second hit, +Or quit in answer of the third exchange, +Let all the battlements their ordnance fire; +The King shall drink to Hamlet’s better breath, +And in the cup an union shall he throw +Richer than that which four successive kings +In Denmark’s crown have worn. Give me the cups; +And let the kettle to the trumpet speak, +The trumpet to the cannoneer without, +The cannons to the heavens, the heavens to earth, +‘Now the King drinks to Hamlet.’ Come, begin. +And you, the judges, bear a wary eye. + +HAMLET. +Come on, sir. + +LAERTES. +Come, my lord. + +[_They play._] + +HAMLET. +One. + +LAERTES. +No. + +HAMLET. +Judgement. + +OSRIC. +A hit, a very palpable hit. + +LAERTES. +Well; again. + +KING. +Stay, give me drink. Hamlet, this pearl is thine; +Here’s to thy health. + +[_Trumpets sound, and cannon shot off within._] + +Give him the cup. + +HAMLET. +I’ll play this bout first; set it by awhile. + +[_They play._] + +Come. Another hit; what say you? + +LAERTES. +A touch, a touch, I do confess. + +KING. +Our son shall win. + +QUEEN. +He’s fat, and scant of breath. +Here, Hamlet, take my napkin, rub thy brows. +The Queen carouses to thy fortune, Hamlet. + +HAMLET. +Good madam. + +KING. +Gertrude, do not drink. + +QUEEN. +I will, my lord; I pray you pardon me. + +KING. +[_Aside._] It is the poison’d cup; it is too late. + +HAMLET. +I dare not drink yet, madam. By and by. + +QUEEN. +Come, let me wipe thy face. + +LAERTES. +My lord, I’ll hit him now. + +KING. +I do not think’t. + +LAERTES. +[_Aside._] And yet ’tis almost ’gainst my conscience. + +HAMLET. +Come for the third, Laertes. You do but dally. +I pray you pass with your best violence. +I am afeard you make a wanton of me. + +LAERTES. +Say you so? Come on. + +[_They play._] + +OSRIC. +Nothing neither way. + +LAERTES. +Have at you now. + +[_Laertes wounds Hamlet; then, in scuffling, they change rapiers, and +Hamlet wounds Laertes._] + +KING. +Part them; they are incens’d. + +HAMLET. +Nay, come again! + +[_The Queen falls._] + +OSRIC. +Look to the Queen there, ho! + +HORATIO. +They bleed on both sides. How is it, my lord? + +OSRIC. +How is’t, Laertes? + +LAERTES. +Why, as a woodcock to my own springe, Osric. +I am justly kill’d with mine own treachery. + +HAMLET. +How does the Queen? + +KING. +She swoons to see them bleed. + +QUEEN. +No, no, the drink, the drink! O my dear Hamlet! +The drink, the drink! I am poison’d. + +[_Dies._] + +HAMLET. +O villany! Ho! Let the door be lock’d: +Treachery! Seek it out. + +[_Laertes falls._] + +LAERTES. +It is here, Hamlet. Hamlet, thou art slain. +No medicine in the world can do thee good. +In thee there is not half an hour of life; +The treacherous instrument is in thy hand, +Unbated and envenom’d. The foul practice +Hath turn’d itself on me. Lo, here I lie, +Never to rise again. Thy mother’s poison’d. +I can no more. The King, the King’s to blame. + +HAMLET. +The point envenom’d too! +Then, venom, to thy work. + +[_Stabs the King._] + +OSRIC and LORDS. +Treason! treason! + +KING. +O yet defend me, friends. I am but hurt. + +HAMLET. +Here, thou incestuous, murderous, damned Dane, +Drink off this potion. Is thy union here? +Follow my mother. + +[_King dies._] + +LAERTES. +He is justly serv’d. +It is a poison temper’d by himself. +Exchange forgiveness with me, noble Hamlet. +Mine and my father’s death come not upon thee, +Nor thine on me. + +[_Dies._] + +HAMLET. +Heaven make thee free of it! I follow thee. +I am dead, Horatio. Wretched Queen, adieu. +You that look pale and tremble at this chance, +That are but mutes or audience to this act, +Had I but time,—as this fell sergeant, death, +Is strict in his arrest,—O, I could tell you,— +But let it be. Horatio, I am dead, +Thou liv’st; report me and my cause aright +To the unsatisfied. + +HORATIO. +Never believe it. +I am more an antique Roman than a Dane. +Here’s yet some liquor left. + +HAMLET. +As th’art a man, +Give me the cup. Let go; by Heaven, I’ll have’t. +O good Horatio, what a wounded name, +Things standing thus unknown, shall live behind me. +If thou didst ever hold me in thy heart, +Absent thee from felicity awhile, +And in this harsh world draw thy breath in pain, +To tell my story. + +[_March afar off, and shot within._] + +What warlike noise is this? + +OSRIC. +Young Fortinbras, with conquest come from Poland, +To the ambassadors of England gives +This warlike volley. + +HAMLET. +O, I die, Horatio. +The potent poison quite o’er-crows my spirit: +I cannot live to hear the news from England, +But I do prophesy th’election lights +On Fortinbras. He has my dying voice. +So tell him, with the occurrents more and less, +Which have solicited. The rest is silence. + +[_Dies._] + +HORATIO. +Now cracks a noble heart. Good night, sweet prince, +And flights of angels sing thee to thy rest. +Why does the drum come hither? + +[_March within._] + +Enter Fortinbras, the English Ambassadors and others. + +FORTINBRAS. +Where is this sight? + +HORATIO. +What is it you would see? +If aught of woe or wonder, cease your search. + +FORTINBRAS. +This quarry cries on havoc. O proud death, +What feast is toward in thine eternal cell, +That thou so many princes at a shot +So bloodily hast struck? + +FIRST AMBASSADOR. +The sight is dismal; +And our affairs from England come too late. +The ears are senseless that should give us hearing, +To tell him his commandment is fulfill’d, +That Rosencrantz and Guildenstern are dead. +Where should we have our thanks? + +HORATIO. +Not from his mouth, +Had it th’ability of life to thank you. +He never gave commandment for their death. +But since, so jump upon this bloody question, +You from the Polack wars, and you from England +Are here arriv’d, give order that these bodies +High on a stage be placed to the view, +And let me speak to th’ yet unknowing world +How these things came about. So shall you hear +Of carnal, bloody and unnatural acts, +Of accidental judgements, casual slaughters, +Of deaths put on by cunning and forc’d cause, +And, in this upshot, purposes mistook +Fall’n on the inventors’ heads. All this can I +Truly deliver. + +FORTINBRAS. +Let us haste to hear it, +And call the noblest to the audience. +For me, with sorrow I embrace my fortune. +I have some rights of memory in this kingdom, +Which now to claim my vantage doth invite me. + +HORATIO. +Of that I shall have also cause to speak, +And from his mouth whose voice will draw on more. +But let this same be presently perform’d, +Even while men’s minds are wild, lest more mischance +On plots and errors happen. + +FORTINBRAS. +Let four captains +Bear Hamlet like a soldier to the stage, +For he was likely, had he been put on, +To have prov’d most royally; and for his passage, +The soldiers’ music and the rites of war +Speak loudly for him. +Take up the bodies. Such a sight as this +Becomes the field, but here shows much amiss. +Go, bid the soldiers shoot. + +[_A dead march._] + +[_Exeunt, bearing off the bodies, after which a peal of ordnance is +shot off._] \ No newline at end of file diff --git a/fixtures/text/lorem.txt b/fixtures/text/lorem.txt new file mode 100644 index 0000000000..2db4caacc2 --- /dev/null +++ b/fixtures/text/lorem.txt @@ -0,0 +1 @@ +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc sollicitudin sollicitudin sem, quis efficitur turpis bibendum a. Vestibulum imperdiet auctor ligula, in placerat eros laoreet sagittis. Sed tincidunt est sodales ligula tempus, eget scelerisque enim bibendum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi vel lacus a turpis bibendum convallis. Integer vestibulum nibh ac urna dictum, non tincidunt nibh vehicula. Nunc efficitur luctus augue at pretium. Curabitur aliquam quam id sapien feugiat sollicitudin. Donec convallis suscipit tortor, nec fringilla enim elementum vitae. Etiam gravida nulla cursus ipsum molestie condimentum. Nulla ligula tellus, accumsan vitae pharetra id, vehicula ullamcorper libero. In pellentesque turpis non lorem luctus, lacinia fringilla diam ullamcorper. Duis at dapibus quam. Pellentesque ut est quis neque tincidunt tempus at sed tortor. \ No newline at end of file From 8f2e62248e397acbc9f28ee0c8f0c6dd742e5f8c Mon Sep 17 00:00:00 2001 From: Universe Date: Tue, 3 Mar 2026 21:18:40 +0900 Subject: [PATCH 06/15] feat: introduce performance model for text editing - Added a new document `impl-performance.md` detailing the performance model for text editing, focusing on viewport-based rendering and efficient text buffer representation. - Updated `attributed-text.md` to include a reference to the new performance model, enhancing the documentation's comprehensiveness. - The performance model outlines strategies for maintaining responsiveness in text editing applications, addressing critical paths such as layout, rendering, and offset conversion. This update aims to provide a clear framework for optimizing text editing performance in the grida-text-edit project. --- docs/wg/feat-text-editing/attributed-text.md | 1 + docs/wg/feat-text-editing/impl-performance.md | 319 ++++++++++++++++++ docs/wg/feat-text-editing/index.md | 2 +- 3 files changed, 321 insertions(+), 1 deletion(-) create mode 100644 docs/wg/feat-text-editing/impl-performance.md diff --git a/docs/wg/feat-text-editing/attributed-text.md b/docs/wg/feat-text-editing/attributed-text.md index beaceda20e..0211009a66 100644 --- a/docs/wg/feat-text-editing/attributed-text.md +++ b/docs/wg/feat-text-editing/attributed-text.md @@ -744,6 +744,7 @@ FlatBuffers schema evolution with the types defined in this document. Rust and T ### Internal references - Text Editing Manifesto: `docs/wg/feat-text-editing/index.md` +- Performance Model: `docs/wg/feat-text-editing/impl-performance.md` - Paragraph Roadmap: `docs/wg/feat-paragraph/index.md` - FlatBuffers Schema: `format/grida.fbs` - Figma kiwi schema reference: `.ref/figma/fig.kiwi` diff --git a/docs/wg/feat-text-editing/impl-performance.md b/docs/wg/feat-text-editing/impl-performance.md new file mode 100644 index 0000000000..f6836cec9f --- /dev/null +++ b/docs/wg/feat-text-editing/impl-performance.md @@ -0,0 +1,319 @@ +--- +id: impl-performance +title: "Text Editing: Performance Model" +--- + +## Motivation + +The text editing manifesto establishes that _"queries should be cheap, cacheable, and invalidated predictably"_ (incremental computation principle). This document specifies how to achieve that property at scale. + +A design tool text editor must remain responsive (sub-16ms frame budget at 60 fps) regardless of document size. The strategies here apply to both deployment targets: native Skia (OpenGL/Vulkan) and WASM/web (CanvasKit over WebGL). + +The core insight is that **text editing is a viewport problem, not a document problem**. The user sees ~50-100 lines at a time. Every operation — layout, rendering, hit-testing, offset conversion — should be proportional to the _visible_ region, not the total document size. + +## Scope + +This document covers performance-critical paths in the text editing pipeline: + +1. Text buffer representation and edit cost +2. Offset conversion (UTF-8 / UTF-16 / line-column) +3. Paragraph layout and caching +4. Rendering and viewport culling +5. WASM/web deployment constraints + +It does **not** cover: + +- Rich text data model performance (see `attributed-text.md` §Complexity analysis) +- Document-level layout (pagination, multi-node flow) +- Collaborative editing wire protocols + +## Principles + +### P1: O(log n) everything + +No operation on the text buffer or its derived indices should be O(n) in document size. This includes: character access, offset conversion, line lookup, line-height queries, and scroll position mapping. Tree-based data structures (ropes, B+ trees) provide this naturally. + +### P2: Incremental invalidation + +An edit that changes k characters should invalidate O(k + convergence) layout work, not O(n). The pipeline must represent edits as explicit deltas and propagate them through each stage (text → line breaks → paragraph layout → render), stopping as soon as the output converges with the pre-edit state. + +### P3: Viewport-first rendering + +Layout and rendering are prioritized for the visible region. Off-screen content is computed lazily as it scrolls into view. Scrollbar positioning uses pre-computed cumulative line heights, not rendered content. + +### P4: Amortize expensive work + +Skia `Paragraph` construction (shaping + line breaking) is the most expensive single operation. It must never happen more than once per edit, and never for content outside the viewport. Results must be cached and reused across frames until invalidated. + +### P5: Minimize cross-boundary traffic + +In a WASM deployment, every JS ↔ WASM boundary crossing copies data between address spaces. The API surface must be coarse-grained: batch operations on the WASM side, return aggregate results. Never cross the boundary per-character or per-line in a hot loop. + +## Text buffer + +### Why `String` is insufficient + +A contiguous `String` (or `Vec`) has O(n) insertion cost — every byte after the cursor must be shifted. For a 200 KB document, a single keystroke moves ~100 KB of memory. Combined with the full-text clone needed for undo snapshots, this makes every edit O(n) before layout even begins. + +### Rope / piece table + +The standard solution is a **rope**: a balanced tree of string chunks (typically 64–512 bytes per leaf). Edits splice the tree in O(log n). The same structure stores summary data at each internal node, enabling O(log n) queries on derived properties. + +**Node summaries** (following xi-editor's monoid-homomorphism framework and Zed's SumTree pattern): + +Each node in the rope stores a summary containing at least: + +| Field | Type | Purpose | +| ----------- | ----- | ---------------------------- | +| `len` | `u32` | UTF-8 byte count | +| `len_utf16` | `u32` | UTF-16 code unit count | +| `lines` | `u32` | Number of line breaks (`\n`) | +| `chars` | `u32` | Unicode scalar count | + +Summaries form a monoid: they can be combined (addition) and the identity is zero. A tree query walks from root to leaf, accumulating summaries, in O(log n). + +**Difficulty analysis** (optional, high-value optimization): + +If a chunk contains only ASCII bytes (all bytes < 128), then `len == len_utf16 == chars` and no grapheme or bidi analysis is needed. Storing a boolean `is_ascii` flag per chunk enables fast-path bypass for the common case. + +### Undo snapshots + +With a rope backed by reference-counted immutable nodes (`Arc`), cloning is O(1) — it increments a reference count at the root. Edits produce a new root that shares most of its structure with the old tree (persistent data structure / structural sharing). This eliminates the O(n) clone cost of snapshot-based undo. + +## Offset conversion + +### The problem + +Skia Paragraph operates in UTF-16 code unit offsets. Rust strings are UTF-8. The editing model uses UTF-8 byte offsets. Every geometry query (caret rect, selection rects, hit-testing) crosses this boundary at least once. + +A naive conversion — `text[..offset].encode_utf16().count()` — is O(n). If this is called per-line (e.g., to convert line metrics), the total cost becomes O(n × L) where L is the line count — effectively quadratic. + +### Solution: cumulative index in the rope + +When summaries in the rope store both `len` (UTF-8) and `len_utf16` (UTF-16), converting between offset systems is a single tree traversal: + +- **UTF-8 → UTF-16**: Seek to the target byte offset in the tree, accumulating `len_utf16` from the nodes traversed. Cost: O(log n). +- **UTF-16 → UTF-8**: Seek by `len_utf16` dimension, accumulating `len`. Cost: O(log n). +- **Byte offset → line number**: Seek by `len`, accumulating `lines`. Cost: O(log n). +- **Line number → byte offset**: Seek by `lines`, accumulating `len`. Cost: O(log n). + +All four conversions share the same tree traversal mechanism. No secondary data structure is needed. + +### Line-start index (alternative for simpler implementations) + +If a full rope is not yet in place, a `Vec` of line-start byte offsets provides O(log n) line lookup via binary search. This index is rebuilt after each edit (O(L) where L = line count), which is acceptable as an intermediate step. It should also store cumulative UTF-16 counts per line to avoid per-line conversion. + +### Caching derived offsets + +Geometry queries often need the same offset converted multiple times within a single frame (e.g., `caret_rect_at` needs the UTF-16 offset of the cursor, then `selection_rects` needs it again). A per-frame cache (or simply passing the converted value through the call chain) avoids redundant conversions. + +## Paragraph layout + +### Unit of layout + +Skia `Paragraph` is designed for a single block of styled text — a paragraph, a label, a text field. It is **not** designed for an entire document. Feeding a 200 KB document into a single `Paragraph` causes: + +- Full HarfBuzz shaping of the entire text on every rebuild +- Full ICU line-breaking analysis +- Full glyph positioning for all lines +- O(n) memory for glyph and line data + +The correct architecture is **one `Paragraph` per logical paragraph** (text between hard line breaks), or in some cases one per visual line. Each paragraph is laid out independently and cached. + +### Per-paragraph caching + +Each logical paragraph stores: + +- The `Paragraph` object (after `layout()`) +- The text content hash or version counter +- The layout constraints (width) at the time of layout +- Derived metrics: height, baseline, line count, line-start offsets + +A paragraph is invalidated (and its `Paragraph` object discarded) only when: + +- The text within that paragraph changes +- The style runs overlapping that paragraph change +- The layout width changes + +On a typical keystroke, **only one paragraph is invalidated** — the one containing the cursor. All other paragraphs retain their cached layout. + +### Re-layout vs. re-build + +Skia `Paragraph` supports `layout(width)` without rebuilding the internal shaping data. If only the layout width changes (e.g., window resize), call `layout()` on existing paragraphs rather than constructing new ones through `ParagraphBuilder`. If text or styles change, a full rebuild is required for the affected paragraph only. + +### Convergence-based re-wrapping + +When an edit changes the content of one paragraph, the line breaks in that paragraph may change, shifting the vertical position of all subsequent paragraphs. However, the _content_ and _layout_ of subsequent paragraphs is unchanged — only their y-offset shifts. + +If the edited paragraph's height after re-layout is the same as before, no subsequent paragraphs need any update at all. If the height changes, only y-offsets need updating (O(L) in the number of following paragraphs, or O(log n) with a height-indexed tree). + +xi-editor takes this further: when re-wrapping a paragraph, it compares the new line breaks with the old ones and stops as soon as they converge. This bounds the invalidation region to the edit site plus a small convergence tail. + +## Rendering and viewport + +### Viewport culling + +Given a scroll offset and a viewport height, compute the range of visible paragraphs using the cumulative height index (O(log n) with a tree, O(log L) with a line-start array). Only call `Paragraph::paint()` for paragraphs within this range. + +For selection rendering (`get_rects_for_range`), compute selection rectangles only for visible paragraphs that overlap the selection range. + +For caret rendering, compute the caret rectangle only for the paragraph containing the cursor. + +### Layer separation + +Separate the rendering into independent layers: + +| Layer | Invalidation trigger | Notes | +| ----------- | ------------------------------------------ | -------------------------------------------------------- | +| Text | Text or style change in visible paragraphs | Heaviest layer; cache aggressively | +| Selection | Selection range change | Lightweight geometry; recompute on selection change only | +| Caret | Cursor position change, blink timer | Single rectangle; near-zero cost | +| Diagnostics | Diagnostic range change | Wavy underlines; same geometry pipeline as selection | + +Each layer redraws independently. A cursor blink does not require re-rendering the text layer. + +### Caret rect caching + +`caret_rect_at` is called every frame (for rendering) and again for IME cursor area updates. The result depends only on: + +- The text content +- The cursor offset +- The layout width + +Cache the result and invalidate only when one of these changes. Within a single frame, compute it once and reuse. + +### Line metrics caching + +Skia `Paragraph::getLineMetrics()` returns per-line data (baseline, ascent, descent, width, start/end indices). This data is stable between frames if the paragraph hasn't been rebuilt. Cache the result per paragraph and convert the UTF-16 offsets to UTF-8 once, not on every query. + +## Glyph rendering optimization + +### Texture atlas (glyph cache) + +For both native and WASM targets, pre-rendering frequently used glyphs into a texture atlas provides significant speedup: + +- Rasterize each unique (glyph ID, font size, subpixel position) combination once into a GPU texture. +- Subsequent frames blit from the atlas (`drawImage`) instead of re-rasterizing text. +- ASCII text in common sizes hits the atlas almost exclusively. + +VS Code's terminal renderer reports 5–45x speedup from atlas-based rendering compared to per-character `fillText` calls. The same principle applies to Skia: `Paragraph::paint()` internally does glyph caching, but an application-level atlas for the caret line or selection overlay text avoids rebuilding visual state unnecessarily. + +### Fast paths for uniform-style paragraphs + +When a paragraph has a single style run (the common case for code and plain text), layout is cheaper: + +- No style switching in the `ParagraphBuilder` +- A single shaping run for the entire paragraph +- Simpler glyph positioning (no mixed metrics) + +The attributed text model already tracks run count per paragraph. Use this to select a fast path when possible. + +## WASM / CanvasKit deployment + +### Memory model + +WASM linear memory can grow but never shrink. Long-running editor sessions accumulate fragmentation. Mitigations: + +- **Arena allocators** for per-frame temporary data (selection rects, line metrics conversion results). Reset the arena each frame. +- **Object pools** for `Paragraph` objects. When a paragraph is invalidated, return its memory to the pool rather than deallocating. +- **Avoid temporary string allocations** in hot paths. Pre-allocate buffers for UTF-8 ↔ UTF-16 conversion. + +### Boundary crossing cost + +The JS ↔ WASM boundary requires copying data between the JS heap and WASM linear memory. Strings are especially expensive (encoding conversion + copy). + +**API design for WASM**: + +- Expose a `layout_viewport(scroll_y, viewport_height)` function that performs all layout, hit-testing, and geometry computation on the WASM side, returning a single result buffer containing all paint commands for the visible region. +- Expose `apply_edit(command)` that processes the edit entirely on the WASM side, including attributed text updates, paragraph invalidation, and viewport re-layout. +- Never expose `get_line_content(n)` or `style_at(offset)` as individual WASM exports called from JS in a loop. + +### CanvasKit surface management + +CanvasKit (Skia compiled to WASM) renders to a WebGL-backed `SkSurface`. Key considerations: + +- **WebGL context loss**: The browser can reclaim the WebGL context under memory pressure. The editor must handle context restoration by rebuilding the Skia surface and re-rendering. +- **Texture limits**: WebGL imposes maximum texture dimensions (commonly 4096×4096 or 8192×8192). Do not render the entire document to a single off-screen texture. +- **Frame synchronization**: Use `requestAnimationFrame` for rendering. Do not block the main thread with synchronous layout in response to input events. + +### Font loading + +Font data must be loaded into WASM memory and registered with Skia's font manager. This is a significant startup cost. + +- **Subset fonts** for initial load (only the glyphs needed for visible text). +- **Load fonts asynchronously** and trigger re-layout when a new font becomes available. +- **Cache font data** in IndexedDB or the browser cache to avoid re-downloading. + +## Complexity targets + +For a document of n bytes with L logical paragraphs and V visible lines: + +| Operation | Target | Notes | +| ------------------------- | --------------- | ---------------------------------------- | +| Character insertion | O(log n) | Rope splice | +| Undo snapshot | O(1) | Structural sharing (Arc) | +| UTF-8 → UTF-16 offset | O(log n) | Tree traversal with summary accumulation | +| Line number → byte offset | O(log n) | Tree traversal | +| Caret rect | O(1) amortized | Cached per edit; single-paragraph lookup | +| Selection rects (visible) | O(V) | Only visible paragraphs | +| Full draw (no change) | O(V) | Paint cached paragraphs | +| Full draw (after edit) | O(V + P) | P = one paragraph rebuild | +| Scroll (viewport shift) | O(V + log L) | Viewport lookup + paint new lines | +| Window resize | O(L) worst case | Re-layout all paragraphs (no reshape) | + +## Phased roadmap + +### Phase 0: Eliminate quadratic paths + +Remove all O(n × L) and O(n^2) patterns from the editing and rendering pipeline. This is a prerequisite for handling any non-trivial document. + +- Cache line metrics and converted offsets per layout cycle. +- Build a line-start byte-offset index after each layout. +- Cache caret rect per frame. + +### Phase 1: Per-paragraph layout + +Split the monolithic single-`Paragraph` architecture into per-logical-paragraph layout units. Each paragraph owns its `Paragraph` object, cached and invalidated independently. + +- Only the edited paragraph is rebuilt on a keystroke. +- Vertical positions of subsequent paragraphs are updated by offset (no re-layout). + +### Phase 2: Viewport culling + +Implement a viewport window that determines which paragraphs are visible. Only visible paragraphs are laid out and painted. + +- Cumulative height index for O(log n) scroll-to-line mapping. +- Lazy layout: paragraphs outside the viewport are laid out on demand as they scroll into view. + +### Phase 3: Rope buffer + +Replace the flat `String` text buffer with a rope, storing per-node summaries (UTF-8 length, UTF-16 length, line count). This provides O(log n) for all offset conversions, O(log n) edits, and O(1) undo snapshots via structural sharing. + +### Phase 4: WASM deployment optimization + +Coarse-grained WASM API surface. Arena allocators for per-frame temporaries. Font subsetting and async loading. Object pools for `Paragraph` instances. + +## References + +### Architecture references + +- xi-editor Rope Science series (incremental word wrapping, monoid summaries, line caching, minimal invalidation): https://xi-editor.io/docs/rope_science_00.html +- Zed's SumTree and GPU text rendering: https://zed.dev/blog +- VS Code's Piece Tree (text buffer evolution, line-break indexing, GC pressure): https://code.visualstudio.com/blogs/2018/03/23/text-buffer-reimplementation + +### Skia / CanvasKit + +- Skia Paragraph module: https://skia.org/docs/modules/skparagraph/ +- CanvasKit (Skia in WASM): https://skia.org/docs/user/modules/canvaskit/ +- Flutter rendering pipeline (Skia Paragraph integration): https://docs.flutter.dev/perf/rendering-performance + +### Text data structures + +- Raph Levien, "Rope Science" (monoid homomorphisms, difficulty analysis, incremental wrapping): https://xi-editor.io/docs/rope_science_00.html +- Martin Kleppmann, "Peritext" (rich text CRDT with style ranges): https://www.inkandswitch.com/peritext/ + +### Internal references + +- Text Editing Manifesto: `docs/wg/feat-text-editing/index.md` +- Attributed Text Data Model: `docs/wg/feat-text-editing/attributed-text.md` +- Paragraph Feature Roadmap: `docs/wg/feat-paragraph/index.md` diff --git a/docs/wg/feat-text-editing/index.md b/docs/wg/feat-text-editing/index.md index 3aa521ee60..623a1f1b21 100644 --- a/docs/wg/feat-text-editing/index.md +++ b/docs/wg/feat-text-editing/index.md @@ -32,7 +32,7 @@ This document proposes a **minimal but extensible** text editing model and geome - **Model vs view separation**: input state is pure data; geometry is queried; rendering is host-defined. - **Text positions are contracts**: cursoring and selection operate on _valid cursor stops_, not arbitrary integers. - **Determinism (scoped)**: for fixed font set, shaping engine, and layout constraints, the mapping (state) → geometry is a function (same input ⇒ same output). -- **Incremental computation**: queries should be cheap, cacheable, and invalidated predictably. +- **Incremental computation**: queries should be cheap, cacheable, and invalidated predictably. (See `impl-performance.md` for the full performance model.) - **Accessibility compatibility**: expose enough structure to bridge to platform accessibility layers. ## Core contracts (read this first) From aa38c004214194a3d92b664558d42b9edb2206f8 Mon Sep 17 00:00:00 2001 From: Universe Date: Wed, 4 Mar 2026 16:33:50 +0900 Subject: [PATCH 07/15] feat: add dev-only function key presets for text styling - Implemented function key presets for quick text color and font family changes in the text editor. - Added functionality for F1, F2, and F3 to set text color to black, red, and blue respectively. - Introduced F5, F6, and F7 to switch between the Inter, Lora, and Inconsolata font families. - Enhanced the demo text to include instructions for using these new features, improving the user experience for developers. This update aims to facilitate rapid styling adjustments during development, enhancing the text editing capabilities in the grida-text-edit project. --- .../examples/wd_text_editor.rs | 172 +++++++++++++++++- crates/grida-text-edit/src/skia_layout.rs | 72 ++++++-- 2 files changed, 217 insertions(+), 27 deletions(-) diff --git a/crates/grida-text-edit/examples/wd_text_editor.rs b/crates/grida-text-edit/examples/wd_text_editor.rs index 799729b0cc..df74c2b1a3 100644 --- a/crates/grida-text-edit/examples/wd_text_editor.rs +++ b/crates/grida-text-edit/examples/wd_text_editor.rs @@ -84,11 +84,17 @@ // [x] Per-run layout via Skia ParagraphBuilder (pushStyle/addText per run) // [x] Variable font axis interpolation (wght, opsz via FontArguments) // +// Dev-only function key presets (no GUI needed) +// [x] F1: black F2: red F3: blue set text color +// [x] F5: Inter (sans) F6: Lora (serif) F7: Inconsolata (mono) +// [x] Drag-and-drop .txt / .html files to load content +// // Not yet implemented // [ ] Scroll (vertical) // [ ] Visual-order bidi cursor movement use std::ffi::CString; +use std::fs; use std::num::NonZeroU32; use std::time::{Duration, Instant}; @@ -127,7 +133,7 @@ use grida_text_edit::{ SkiaLayoutEngine, TextEditorState, TextLayoutEngine, attributed_text::{ AttributedText, TextStyle as AttrTextStyle, - TextDecorationLine, + TextDecorationLine, TextFill, RGBA, html::{runs_to_html, html_to_attributed_text}, }, }; @@ -622,6 +628,41 @@ impl TextEditor { } } + // ----------------------------------------------------------------------- + // Dev-only: function key style presets + // ----------------------------------------------------------------------- + + /// Set a preset color on the selection or caret style. + fn dev_set_color(&mut self, color: RGBA) { + if let Some((lo, hi)) = self.selection_range() { + self.history.push(&self.snapshot(), EditKind::Style); + self.content.apply_style(lo, hi, |s| { + s.fill = TextFill::Solid(color); + }); + self.layout.invalidate(); + } else { + let mut style = self.caret_style_override.clone() + .unwrap_or_else(|| self.content.caret_style_at(self.state.cursor as u32).clone()); + style.fill = TextFill::Solid(color); + self.caret_style_override = Some(style); + } + } + + /// Set the font family on the selection or caret style. + fn dev_set_font(&mut self, family: &str) { + let family = family.to_string(); + if let Some((lo, hi)) = self.selection_range() { + self.history.push(&self.snapshot(), EditKind::Style); + self.content.apply_style(lo, hi, |s| { s.font_family = family.clone(); }); + self.layout.invalidate(); + } else { + let mut style = self.caret_style_override.clone() + .unwrap_or_else(|| self.content.caret_style_at(self.state.cursor as u32).clone()); + style.font_family = family; + self.caret_style_override = Some(style); + } + } + // ----------------------------------------------------------------------- // Rich paste: insert an AttributedText (from HTML clipboard) // ----------------------------------------------------------------------- @@ -1243,30 +1284,57 @@ impl ApplicationHandler for TextEditorApp { let inter_italic = include_bytes!( "../../../fixtures/fonts/Inter/Inter-Italic-VariableFont_opsz,wght.ttf" ); + let lora_upright = include_bytes!( + "../../../fixtures/fonts/Lora/Lora-VariableFont_wght.ttf" + ); + let lora_italic = include_bytes!( + "../../../fixtures/fonts/Lora/Lora-Italic-VariableFont_wght.ttf" + ); + let inconsolata = include_bytes!( + "../../../fixtures/fonts/Inconsolata/Inconsolata-VariableFont_wdth,wght.ttf" + ); editor.layout.add_font_family("Inter", &[inter_upright, inter_italic]); + editor.layout.add_font_family("Lora", &[lora_upright, lora_italic]); + editor.layout.add_font_family("Inconsolata", &[inconsolata]); editor.layout.config.font_families = vec!["Inter".into()]; editor.set_layout_width(w as f32); editor.set_layout_height(h as f32); let demo_text = concat!( - "Hello, World!\n", - "Type here to edit text. Use Cmd+B for bold, Cmd+I for italic, Cmd+U for underline.\n", + "Grida Rich Text Editor\n", + "\n", + "Formatting\n", + " Cmd+B bold Cmd+I italic\n", + " Cmd+U underline Cmd+Shift+X strikethrough\n", "\n", - "Select text and toggle styles, or toggle with no selection to set the typing style.\n", + "Font Size\n", + " Cmd+Shift+> increase Cmd+Shift+< decrease\n", + "\n", + "Fonts (dev)\n", + " F5 Inter (sans) F6 Lora (serif) F7 Inconsolata (mono)\n", + "\n", + "Colors (dev)\n", + " F1 black F2 red F3 blue\n", + "\n", + "Editing\n", + " Cmd+Z undo Cmd+Shift+Z redo\n", + " Cmd+C copy Cmd+X cut Cmd+V paste (with formatting)\n", + " Cmd+A select all\n", "\n", "The quick brown fox jumps over 13 lazy dogs.\n", - "fi fl ffi ffl (ligatures with Inter)\n", ); editor.content = AttributedText::new(demo_text, default_style.clone()); editor.state.text = demo_text.to_string(); - // Pre-style some demo text to show rich text capabilities. - // "Hello" bold - editor.content.apply_style(0, 5, |s| { s.font_weight = 700; }); - // "World" italic - editor.content.apply_style(7, 12, |s| { s.font_style_italic = true; }); + // Pre-style the title. + // "Grida Rich Text Editor" — bold, 24px + let title_end = "Grida Rich Text Editor".len(); + editor.content.apply_style(0, title_end, |s| { + s.font_weight = 700; + s.font_size = 24.0; + }); editor.state.cursor = editor.state.text.len(); @@ -1503,6 +1571,36 @@ impl ApplicationHandler for TextEditorApp { } } + // ------------------------------------------------------- + // Dev-only function key bindings + // ------------------------------------------------------- + // F1: black F2: red F3: blue — color presets + // F5: Inter (sans) F6: Lora (serif) F7: Inconsolata (mono) + Key::Named(NamedKey::F1) => { + inner.editor.dev_set_color(RGBA::BLACK); + inner.window.request_redraw(); + } + Key::Named(NamedKey::F2) => { + inner.editor.dev_set_color(RGBA { r: 0.9, g: 0.2, b: 0.2, a: 1.0 }); + inner.window.request_redraw(); + } + Key::Named(NamedKey::F3) => { + inner.editor.dev_set_color(RGBA { r: 0.2, g: 0.4, b: 0.9, a: 1.0 }); + inner.window.request_redraw(); + } + Key::Named(NamedKey::F5) => { + inner.editor.dev_set_font("Inter"); + inner.window.request_redraw(); + } + Key::Named(NamedKey::F6) => { + inner.editor.dev_set_font("Lora"); + inner.window.request_redraw(); + } + Key::Named(NamedKey::F7) => { + inner.editor.dev_set_font("Inconsolata"); + inner.window.request_redraw(); + } + Key::Character(c) if !cmd && inner.editor.preedit.is_none() => { @@ -1614,6 +1712,60 @@ impl ApplicationHandler for TextEditorApp { event_loop.set_control_flow(ControlFlow::WaitUntil(deadline)); } + // --------------------------------------------------------------- + // Dev-only: drag-and-drop .txt / .html files to load content + // --------------------------------------------------------------- + WindowEvent::DroppedFile(path) => { + let ext = path + .extension() + .and_then(|e| e.to_str()) + .map(|s| s.to_ascii_lowercase()); + + match ext.as_deref() { + Some("txt") => match fs::read_to_string(&path) { + Ok(content) => { + let default_style = inner.editor.content.default_style().clone(); + inner.editor.content = AttributedText::new(&content, default_style); + inner.editor.state.text = content; + inner.editor.state.cursor = inner.editor.state.text.len(); + inner.editor.state.anchor = None; + inner.editor.caret_style_override = None; + inner.editor.layout.invalidate(); + inner.editor.reset_blink(); + eprintln!("loaded plain text: {}", path.display()); + inner.window.request_redraw(); + } + Err(err) => { + eprintln!("failed to read {}: {err}", path.display()); + } + }, + Some("html" | "htm") => match fs::read_to_string(&path) { + Ok(html) => { + let base = inner.editor.content.default_style().clone(); + let content = html_to_attributed_text(&html, base); + inner.editor.state.text = content.text().to_owned(); + inner.editor.content = content; + inner.editor.state.cursor = inner.editor.state.text.len(); + inner.editor.state.anchor = None; + inner.editor.caret_style_override = None; + inner.editor.layout.invalidate(); + inner.editor.reset_blink(); + eprintln!("loaded HTML: {}", path.display()); + inner.window.request_redraw(); + } + Err(err) => { + eprintln!("failed to read {}: {err}", path.display()); + } + }, + _ => { + eprintln!( + "unsupported drop (expected .txt or .html): {}", + path.display() + ); + } + } + } + _ => {} } } diff --git a/crates/grida-text-edit/src/skia_layout.rs b/crates/grida-text-edit/src/skia_layout.rs index c58c32db1f..1a9189bd3c 100644 --- a/crates/grida-text-edit/src/skia_layout.rs +++ b/crates/grida-text-edit/src/skia_layout.rs @@ -98,6 +98,14 @@ pub struct SkiaLayoutEngine { pub font_size: f32, pub config: TextConfig, cached_text: String, + /// Persistent font provider accumulating all registered typefaces. + font_provider: TypefaceFontProvider, + /// Cached line metrics with UTF-8 offsets, invalidated together with `paragraph`. + /// + /// Skia returns line metrics in UTF-16 offsets. Converting to UTF-8 requires + /// a per-line `utf16_to_utf8_offset` call — O(n) each, O(n·L) total. + /// Caching the converted result avoids this cost on every frame / query. + cached_line_metrics: Option>, } impl SkiaLayoutEngine { @@ -117,6 +125,8 @@ impl SkiaLayoutEngine { font_size, config, cached_text: String::new(), + font_provider: TypefaceFontProvider::new(), + cached_line_metrics: None, } } @@ -125,6 +135,7 @@ impl SkiaLayoutEngine { self.config.font_size = size; self.font_size = size; self.paragraph = None; + self.cached_line_metrics = None; self } @@ -193,6 +204,7 @@ impl SkiaLayoutEngine { para.layout(self.layout_width); self.paragraph = Some(para); self.cached_text = text.to_owned(); + self.cached_line_metrics = None; } pub fn set_layout_width(&mut self, w: f32) { @@ -200,6 +212,7 @@ impl SkiaLayoutEngine { if (new_w - self.layout_width).abs() > 0.5 { self.layout_width = new_w; self.paragraph = None; + self.cached_line_metrics = None; } } @@ -210,34 +223,40 @@ impl SkiaLayoutEngine { } } - /// Register a font from raw TTF/OTF bytes. + /// Register a font from raw TTF/OTF bytes under `family`. /// - /// In environments where system fonts are unavailable (e.g. WASM/browser) - /// this is the only way to give Skia a font to shape with. + /// Multiple calls accumulate — all registered typefaces remain available. pub fn add_font_bytes(&mut self, family: &str, bytes: &[u8]) { let loader = FontMgr::new(); if let Some(tf) = loader.new_from_data(bytes, None) { - let mut provider = TypefaceFontProvider::new(); - provider.register_typeface(tf, Some(family)); - self.font_collection.set_asset_font_manager(Some(provider.into())); + self.font_provider.register_typeface(tf, Some(family)); + self.flush_font_provider(); } - self.paragraph = None; } - /// Register multiple fonts at once under the same family. Each byte slice - /// is a separate TTF/OTF file (e.g. regular, italic, bold, bold-italic). - /// Skia will match the correct typeface by weight/slant when building - /// paragraphs. + /// Register multiple font files under the same family at once. + /// + /// Each byte slice is a separate TTF/OTF file (e.g. regular, italic). + /// Multiple calls accumulate — all registered typefaces remain available. pub fn add_font_family(&mut self, family: &str, font_data: &[&[u8]]) { let loader = FontMgr::new(); - let mut provider = TypefaceFontProvider::new(); for bytes in font_data { if let Some(tf) = loader.new_from_data(bytes, None) { - provider.register_typeface(tf, Some(family)); + self.font_provider.register_typeface(tf, Some(family)); } } - self.font_collection.set_asset_font_manager(Some(provider.into())); + self.flush_font_provider(); + } + + /// Push the accumulated font provider into the font collection. + fn flush_font_provider(&mut self) { + // TypefaceFontProvider is consumed by `set_asset_font_manager` (via + // Into), but we need to keep accumulating. Clone it. + let provider_clone = self.font_provider.clone(); + self.font_collection + .set_asset_font_manager(Some(provider_clone.into())); self.paragraph = None; + self.cached_line_metrics = None; } /// Build and cache a paragraph from an [`AttributedText`], pushing @@ -267,7 +286,7 @@ impl SkiaLayoutEngine { let mut builder = ParagraphBuilder::new(¶_style, &self.font_collection); let text = at.text(); - let families: Vec<&str> = self.config.font_families.iter().map(|s| s.as_str()).collect(); + let fallback_families: Vec<&str> = self.config.font_families.iter().map(|s| s.as_str()).collect(); for run in at.runs() { let style = &run.style; @@ -276,7 +295,14 @@ impl SkiaLayoutEngine { // Font size ts.set_font_size(style.font_size); - // Font families + // Font families: use the run's font_family as primary, + // fall back to the config's family list. + let mut families: Vec<&str> = vec![style.font_family.as_str()]; + for f in &fallback_families { + if *f != style.font_family.as_str() { + families.push(f); + } + } ts.set_font_families(&families); // Font style (for typeface matching in FontCollection) @@ -404,12 +430,14 @@ impl SkiaLayoutEngine { para.layout(self.layout_width); self.paragraph = Some(para); self.cached_text = text.to_owned(); + self.cached_line_metrics = None; } /// Invalidate the cached paragraph so the next layout call rebuilds. /// Call this after modifying `font_collection` externally. pub fn invalidate(&mut self) { self.paragraph = None; + self.cached_line_metrics = None; } fn para(&mut self, text: &str) -> &Paragraph { @@ -428,7 +456,16 @@ impl SkiaLayoutEngine { impl TextLayoutEngine for SkiaLayoutEngine { fn line_metrics(&mut self, text: &str) -> Vec { - let skia = self.para(text).get_line_metrics(); + // Ensure the paragraph is up-to-date first — this may clear the cache + // if text or layout width changed, triggering a rebuild. + self.ensure_layout(text); + + // Return cached metrics if available (same paragraph, no rebuild). + if let Some(ref cached) = self.cached_line_metrics { + return cached.clone(); + } + + let skia = self.paragraph.as_ref().unwrap().get_line_metrics(); let mut result = Vec::with_capacity(skia.len()); let mut prev_end: usize = 0; @@ -466,6 +503,7 @@ impl TextLayoutEngine for SkiaLayoutEngine { } } + self.cached_line_metrics = Some(result.clone()); result } From ce8ebe622c5b9bdc2b25a6d63a41a44b497cff3f Mon Sep 17 00:00:00 2001 From: Universe Date: Wed, 4 Mar 2026 17:04:43 +0900 Subject: [PATCH 08/15] feat: implement per-paragraph layout blocks in Skia layout engine - Introduced a new `ParaBlock` struct to manage individual paragraph layouts, improving text rendering efficiency. - Updated the `SkiaLayoutEngine` to support a per-block architecture, allowing for selective rebuilding of affected text blocks on edits. - Enhanced the `ensure_layout` and `rebuild_blocks` methods to handle text splitting and layout caching, optimizing performance during text updates. This update aims to enhance the text layout capabilities in the grida-text-edit project, providing a more efficient and responsive editing experience. --- crates/grida-text-edit/src/skia_layout.rs | 519 +++++++++++++++------- 1 file changed, 367 insertions(+), 152 deletions(-) diff --git a/crates/grida-text-edit/src/skia_layout.rs b/crates/grida-text-edit/src/skia_layout.rs index 1a9189bd3c..ffff72148c 100644 --- a/crates/grida-text-edit/src/skia_layout.rs +++ b/crates/grida-text-edit/src/skia_layout.rs @@ -85,12 +85,41 @@ impl Default for TextConfig { } } +// --------------------------------------------------------------------------- +// Per-paragraph layout block +// --------------------------------------------------------------------------- + +/// A laid-out hard paragraph (text between `\n` boundaries). +/// +/// Each block owns its own Skia `Paragraph` object and caches UTF-8 line +/// metrics. On edit, only the affected block is rebuilt — all others retain +/// their cached layout. +struct ParaBlock { + /// UTF-8 byte offset of this block's first character in the full text. + byte_start: usize, + /// UTF-8 byte offset one past the last character (inclusive of trailing `\n`). + byte_end: usize, + /// Laid-out Skia paragraph for this block's text slice. + paragraph: Paragraph, + /// Cumulative y-offset (top of this block in layout-local space). + y_offset: f32, + /// Total height of this block (sum of all visual lines). + height: f32, + /// Pre-converted UTF-8 line metrics for this block. Offsets are relative to + /// the **full text** (not the block slice), so callers don't need to adjust. + line_metrics: Vec, +} + /// Skia-backed `TextLayoutEngine`. /// -/// Rebuilds the `Paragraph` lazily when text or layout_width changes. +/// Internally splits text on hard line breaks (`\n`) and maintains one Skia +/// `Paragraph` per block. On edit, only the affected block is rebuilt. /// No GPU or window required — pure CPU text layout. pub struct SkiaLayoutEngine { pub font_collection: FontCollection, + /// Legacy single-paragraph field kept for external access (e.g. `wd_text_editor` + /// preedit mode which builds its own paragraph). **Not used** by the + /// per-block layout path. pub paragraph: Option, pub layout_width: f32, pub layout_height: f32, @@ -100,12 +129,13 @@ pub struct SkiaLayoutEngine { cached_text: String, /// Persistent font provider accumulating all registered typefaces. font_provider: TypefaceFontProvider, - /// Cached line metrics with UTF-8 offsets, invalidated together with `paragraph`. - /// - /// Skia returns line metrics in UTF-16 offsets. Converting to UTF-8 requires - /// a per-line `utf16_to_utf8_offset` call — O(n) each, O(n·L) total. - /// Caching the converted result avoids this cost on every frame / query. - cached_line_metrics: Option>, + + // --- Per-block layout state --- + /// Laid-out paragraph blocks (one per hard paragraph). Empty when + /// the layout is invalid. + blocks: Vec, + /// Flattened line metrics across all blocks (cached, invalidated with blocks). + cached_line_metrics: Option>, } impl SkiaLayoutEngine { @@ -126,6 +156,7 @@ impl SkiaLayoutEngine { config, cached_text: String::new(), font_provider: TypefaceFontProvider::new(), + blocks: Vec::new(), cached_line_metrics: None, } } @@ -134,18 +165,134 @@ impl SkiaLayoutEngine { pub fn with_font_size(mut self, size: f32) -> Self { self.config.font_size = size; self.font_size = size; - self.paragraph = None; - self.cached_line_metrics = None; + self.invalidate(); self } + // ------------------------------------------------------------------- + // Layout: per-block architecture + // ------------------------------------------------------------------- + + /// Ensure the per-block layout is up-to-date for `text`. pub fn ensure_layout(&mut self, text: &str) { - if self.paragraph.is_none() || self.cached_text != text { - self.rebuild(text); + if !self.blocks.is_empty() && self.cached_text == text { + return; } + self.rebuild_blocks(text); } - fn rebuild(&mut self, text: &str) { + /// Full rebuild: split `text` on `\n` and lay out each block. + fn rebuild_blocks(&mut self, text: &str) { + self.blocks.clear(); + self.cached_line_metrics = None; + self.paragraph = None; + + let mut y_offset: f32 = 0.0; + let mut start = 0usize; + + // Split on hard line breaks. Each block's byte_end includes the `\n`, + // but we pass only the content (without the trailing `\n`) to Skia + // so it doesn't generate phantom empty lines for newlines. We handle + // inter-block spacing ourselves. + loop { + let has_newline; + let end = if let Some(pos) = text[start..].find('\n') { + has_newline = true; + start + pos + 1 // byte_end includes the `\n` + } else { + has_newline = false; + text.len() + }; + + // The content slice fed to Skia excludes the trailing `\n`. + let content_end = if has_newline { end - 1 } else { end }; + let content_slice = &text[start..content_end]; + let para = self.build_paragraph_for_slice(content_slice); + + // Line metrics use block-local baselines. Byte offsets are global. + let mut stored_lines = self.convert_block_line_metrics(¶, content_slice, start); + + // Empty content (e.g. line between two `\n`s): Skia may return 0 + // lines for "". Synthesize one so the block has vertical extent. + if stored_lines.is_empty() { + let skia_metrics = para.get_line_metrics(); + let (ascent, descent, baseline) = if let Some(lm) = skia_metrics.first() { + (lm.ascent as f32, lm.descent as f32, lm.baseline as f32) + } else { + (self.font_size, self.font_size * 0.2, self.font_size) + }; + stored_lines.push(LineMetrics { + start_index: start, + end_index: start, + baseline, + ascent, + descent, + }); + } + + // For the flattened line_metrics view, the line that owns the `\n` + // must have its end_index include the `\n` byte. + if has_newline { + if let Some(last) = stored_lines.last_mut() { + last.end_index = end; + } + } + + let height: f32 = if let Some(last) = stored_lines.last() { + last.baseline + last.descent + } else { + self.font_size * 1.2 + }; + + self.blocks.push(ParaBlock { + byte_start: start, + byte_end: end, + paragraph: para, + y_offset, + height, + line_metrics: stored_lines, + }); + + y_offset += height; + start = end; + + if start >= text.len() { + break; + } + } + + // Handle trailing `\n`: add a phantom empty block so the cursor can + // sit on the blank line after the last newline. + if text.ends_with('\n') && !text.is_empty() { + if let Some(last_block) = self.blocks.last() { + let last_lm = last_block.line_metrics.last(); + let (ascent, descent) = last_lm + .map(|lm| (lm.ascent, lm.descent)) + .unwrap_or((self.font_size, self.font_size * 0.2)); + let phantom = LineMetrics { + start_index: text.len(), + end_index: text.len(), + baseline: ascent, + ascent, + descent, + }; + let phantom_height = ascent + descent; + self.blocks.push(ParaBlock { + byte_start: text.len(), + byte_end: text.len(), + paragraph: self.build_paragraph_for_slice(""), + y_offset, + height: phantom_height, + line_metrics: vec![phantom], + }); + } + } + + self.cached_text = text.to_owned(); + } + + /// Build a Skia `Paragraph` for a text slice (uniform style from config). + fn build_paragraph_for_slice(&self, slice: &str) -> Paragraph { let mut para_style = ParagraphStyle::new(); para_style.set_apply_rounding_hack(false); para_style.set_text_align(self.config.text_align.to_skia()); @@ -164,8 +311,7 @@ impl SkiaLayoutEngine { let font_style = skia_safe::FontStyle::new(weight, skia_safe::font_style::Width::NORMAL, slant); ts.set_font_style(font_style); - // Variable font axis interpolation — push wght and opsz so that - // variable fonts actually render at the requested weight. + // Variable font axis interpolation { use skia_safe::font_arguments::variation_position::Coordinate; let coords = [ @@ -199,70 +345,84 @@ impl SkiaLayoutEngine { let mut builder = ParagraphBuilder::new(¶_style, &self.font_collection); builder.push_style(&ts); - builder.add_text(text); + builder.add_text(slice); let mut para = builder.build(); para.layout(self.layout_width); - self.paragraph = Some(para); - self.cached_text = text.to_owned(); - self.cached_line_metrics = None; + para } - pub fn set_layout_width(&mut self, w: f32) { - let new_w = w.max(1.0); - if (new_w - self.layout_width).abs() > 0.5 { - self.layout_width = new_w; - self.paragraph = None; - self.cached_line_metrics = None; - } - } + /// Convert Skia's UTF-16 line metrics to UTF-8 for a single block. + /// + /// `base_offset` is the byte offset of the block's start in the full text. + /// Baselines are stored **block-local** (as returned by Skia); the caller + /// adds the block's `y_offset` when producing global coordinates. + fn convert_block_line_metrics( + &self, + para: &Paragraph, + slice: &str, + base_offset: usize, + ) -> Vec { + let skia = para.get_line_metrics(); + let mut result = Vec::with_capacity(skia.len()); + let mut prev_end: usize = 0; - pub fn set_layout_height(&mut self, h: f32) { - let new_h = h.max(1.0); - if (new_h - self.layout_height).abs() > 0.5 { - self.layout_height = new_h; - } - } + for lm in &skia { + let local_start = utf16_to_utf8_offset(slice, lm.start_index); + let local_end = utf16_to_utf8_offset(slice, lm.end_including_newline).min(slice.len()); - /// Register a font from raw TTF/OTF bytes under `family`. - /// - /// Multiple calls accumulate — all registered typefaces remain available. - pub fn add_font_bytes(&mut self, family: &str, bytes: &[u8]) { - let loader = FontMgr::new(); - if let Some(tf) = loader.new_from_data(bytes, None) { - self.font_provider.register_typeface(tf, Some(family)); - self.flush_font_provider(); + let local_start = local_start.max(prev_end); + let local_end = local_end.max(local_start); + + result.push(LineMetrics { + start_index: base_offset + local_start, + end_index: base_offset + local_end, + baseline: lm.baseline as f32, // block-local + ascent: lm.ascent as f32, + descent: lm.descent as f32, + }); + prev_end = local_end; } + + result } - /// Register multiple font files under the same family at once. - /// - /// Each byte slice is a separate TTF/OTF file (e.g. regular, italic). - /// Multiple calls accumulate — all registered typefaces remain available. - pub fn add_font_family(&mut self, family: &str, font_data: &[&[u8]]) { - let loader = FontMgr::new(); - for bytes in font_data { - if let Some(tf) = loader.new_from_data(bytes, None) { - self.font_provider.register_typeface(tf, Some(family)); + /// Flatten all block line metrics into a single Vec, adjusting baselines. + fn flatten_line_metrics(&self) -> Vec { + let total_lines: usize = self.blocks.iter().map(|b| b.line_metrics.len()).sum(); + let mut result = Vec::with_capacity(total_lines); + for block in &self.blocks { + for lm in &block.line_metrics { + result.push(LineMetrics { + start_index: lm.start_index, + end_index: lm.end_index, + baseline: block.y_offset + lm.baseline, + ascent: lm.ascent, + descent: lm.descent, + }); } } - self.flush_font_provider(); + result } - /// Push the accumulated font provider into the font collection. - fn flush_font_provider(&mut self) { - // TypefaceFontProvider is consumed by `set_asset_font_manager` (via - // Into), but we need to keep accumulating. Clone it. - let provider_clone = self.font_provider.clone(); - self.font_collection - .set_asset_font_manager(Some(provider_clone.into())); - self.paragraph = None; - self.cached_line_metrics = None; + /// Find the block index that contains `byte_offset` in the full text. + fn block_index_for_offset(&self, byte_offset: usize) -> usize { + self.blocks + .iter() + .position(|b| byte_offset < b.byte_end || (b.byte_start == b.byte_end && byte_offset == b.byte_start)) + .unwrap_or(self.blocks.len().saturating_sub(1)) } - /// Build and cache a paragraph from an [`AttributedText`], pushing - /// per-run styles to `ParagraphBuilder`. This is the rich-text layout - /// path — each run gets its own font weight, slant, decoration, color, - /// etc. + // ------------------------------------------------------------------- + // Legacy single-paragraph paths (for attributed text / preedit) + // ------------------------------------------------------------------- + + /// Build and cache a monolithic paragraph from an [`AttributedText`], + /// pushing per-run styles to `ParagraphBuilder`. This is the rich-text + /// layout path — each run gets its own font weight, slant, decoration, + /// color, etc. + /// + /// NOTE: this populates the legacy `paragraph` field, NOT the per-block + /// architecture. The `wd_text_editor` uses this for rich text rendering. pub fn ensure_layout_attributed( &mut self, at: &crate::attributed_text::AttributedText, @@ -315,16 +475,12 @@ impl SkiaLayoutEngine { let width = skia_safe::font_style::Width::from(style.font_width as i32); ts.set_font_style(skia_safe::FontStyle::new(weight, width, slant)); - // Variable font axes (FontArguments) — this is what actually - // triggers weight/width/opsz interpolation on variable fonts. - // Without this, set_font_style only selects among registered - // typefaces but does NOT interpolate variable font axes. + // Variable font axes (FontArguments) { use skia_safe::font_arguments::variation_position::Coordinate; let mut coords: Vec = Vec::new(); - // User-specified custom font variations (e.g. CASL, MONO, slnt) for v in &style.font_variations { let bytes = v.axis.as_bytes(); let tag = skia_safe::FourByteTag::from(( @@ -336,13 +492,11 @@ impl SkiaLayoutEngine { coords.push(Coordinate { axis: tag, value: v.value }); } - // wght — always push from font_weight coords.push(Coordinate { axis: skia_safe::FourByteTag::from(('w', 'g', 'h', 't')), value: style.font_weight as f32, }); - // wdth — from font_width if not default (100.0) if (style.font_width - 100.0).abs() > f32::EPSILON { coords.push(Coordinate { axis: skia_safe::FourByteTag::from(('w', 'd', 't', 'h')), @@ -350,7 +504,6 @@ impl SkiaLayoutEngine { }); } - // opsz — auto = font_size match style.font_optical_sizing { at_mod::FontOpticalSizing::Auto => { coords.push(Coordinate { @@ -430,102 +583,126 @@ impl SkiaLayoutEngine { para.layout(self.layout_width); self.paragraph = Some(para); self.cached_text = text.to_owned(); + // Clear per-block state — the attributed path uses the legacy paragraph. + self.blocks.clear(); self.cached_line_metrics = None; } - /// Invalidate the cached paragraph so the next layout call rebuilds. + /// Invalidate all cached layout so the next call rebuilds. /// Call this after modifying `font_collection` externally. pub fn invalidate(&mut self) { self.paragraph = None; + self.blocks.clear(); self.cached_line_metrics = None; } - fn para(&mut self, text: &str) -> &Paragraph { - self.ensure_layout(text); - self.paragraph.as_ref().unwrap() + pub fn set_layout_width(&mut self, w: f32) { + let new_w = w.max(1.0); + if (new_w - self.layout_width).abs() > 0.5 { + self.layout_width = new_w; + self.invalidate(); + } + } + + pub fn set_layout_height(&mut self, h: f32) { + let new_h = h.max(1.0); + if (new_h - self.layout_height).abs() > 0.5 { + self.layout_height = new_h; + } + } + + /// Register a font from raw TTF/OTF bytes under `family`. + /// + /// Multiple calls accumulate — all registered typefaces remain available. + pub fn add_font_bytes(&mut self, family: &str, bytes: &[u8]) { + let loader = FontMgr::new(); + if let Some(tf) = loader.new_from_data(bytes, None) { + self.font_provider.register_typeface(tf, Some(family)); + self.flush_font_provider(); + } + } + + /// Register multiple font files under the same family at once. + /// + /// Each byte slice is a separate TTF/OTF file (e.g. regular, italic). + /// Multiple calls accumulate — all registered typefaces remain available. + pub fn add_font_family(&mut self, family: &str, font_data: &[&[u8]]) { + let loader = FontMgr::new(); + for bytes in font_data { + if let Some(tf) = loader.new_from_data(bytes, None) { + self.font_provider.register_typeface(tf, Some(family)); + } + } + self.flush_font_provider(); + } + + /// Push the accumulated font provider into the font collection. + fn flush_font_provider(&mut self) { + let provider_clone = self.font_provider.clone(); + self.font_collection + .set_asset_font_manager(Some(provider_clone.into())); + self.invalidate(); } /// Paint the laid-out paragraph at (0, 0). Used by the host to draw the /// current session text (and optional preedit) so typed content appears /// immediately without waiting for document commit. pub fn paint_paragraph(&mut self, canvas: &skia_safe::Canvas, text: &str) { - let para = self.para(text); - para.paint(canvas, Point::new(0.0, 0.0)); + self.ensure_layout(text); + for block in &self.blocks { + block.paragraph.paint(canvas, Point::new(0.0, block.y_offset)); + } } } impl TextLayoutEngine for SkiaLayoutEngine { fn line_metrics(&mut self, text: &str) -> Vec { - // Ensure the paragraph is up-to-date first — this may clear the cache - // if text or layout width changed, triggering a rebuild. self.ensure_layout(text); - // Return cached metrics if available (same paragraph, no rebuild). if let Some(ref cached) = self.cached_line_metrics { return cached.clone(); } - let skia = self.paragraph.as_ref().unwrap().get_line_metrics(); - let mut result = Vec::with_capacity(skia.len()); - let mut prev_end: usize = 0; - - for lm in &skia { - let start = utf16_to_utf8_offset(text, lm.start_index); - let end = utf16_to_utf8_offset(text, lm.end_including_newline).min(text.len()); - - let start = start.max(prev_end); - let end = end.max(start); - - result.push(LineMetrics { - start_index: start, - end_index: end, - baseline: lm.baseline as f32, - ascent: lm.ascent as f32, - descent: lm.descent as f32, - }); - prev_end = end; - } - - if !text.is_empty() && text.ends_with('\n') { - let last_end = result.last().map_or(0, |lm| lm.end_index); - if last_end < text.len() || result.last().map_or(true, |lm| lm.start_index < lm.end_index) { - if last_end <= text.len() { - let last = result.last().unwrap(); - let line_height = last.ascent + last.descent; - result.push(LineMetrics { - start_index: text.len(), - end_index: text.len(), - baseline: last.baseline + line_height, - ascent: last.ascent, - descent: last.descent, - }); - } - } - } - + let result = self.flatten_line_metrics(); self.cached_line_metrics = Some(result.clone()); result } fn position_at_point(&mut self, text: &str, x: f32, y: f32) -> usize { self.ensure_layout(text); - let para = self.paragraph.as_ref().unwrap(); - let metrics = para.get_line_metrics(); + // Find the block that contains this y coordinate. + let block_idx = self.blocks + .iter() + .position(|b| y < b.y_offset + b.height + 0.5) + .unwrap_or(self.blocks.len().saturating_sub(1)); + + if self.blocks.is_empty() { + return 0; + } + + let block = &self.blocks[block_idx]; + let local_y = y - block.y_offset; + let slice = &text[block.byte_start..block.byte_end]; + + // Check for empty/short lines first + let metrics = block.paragraph.get_line_metrics(); for lm in &metrics { let top = lm.baseline as f32 - lm.ascent as f32; let bot = lm.baseline as f32 + lm.descent as f32; - if y >= top - 0.5 && y <= bot + 0.5 { + if local_y >= top - 0.5 && local_y <= bot + 0.5 { if lm.end_index.saturating_sub(lm.start_index) <= 1 { - return utf16_to_utf8_offset(text, lm.start_index).min(text.len()); + return block.byte_start + + utf16_to_utf8_offset(slice, lm.start_index).min(slice.len()); } break; } } - let pwa = para.get_glyph_position_at_coordinate(Point::new(x, y)); - let raw = utf16_to_utf8_offset(text, pwa.position.max(0) as usize).min(text.len()); - snap_grapheme_boundary(text, raw) + let pwa = block.paragraph.get_glyph_position_at_coordinate(Point::new(x, local_y)); + let local_raw = utf16_to_utf8_offset(slice, pwa.position.max(0) as usize).min(slice.len()); + let global_raw = block.byte_start + local_raw; + snap_grapheme_boundary(text, global_raw) } fn caret_rect_at(&mut self, text: &str, offset: usize) -> CaretRect { @@ -547,11 +724,20 @@ impl TextLayoutEngine for SkiaLayoutEngine { let x = if offset <= lm.start_index { 0.0 } else { - self.ensure_layout(text); - let u16_end = utf8_to_utf16_offset(text, offset); - let cluster_start = prev_grapheme_boundary(text, offset); - let u16_start = utf8_to_utf16_offset(text, cluster_start); - let rects = self.paragraph.as_ref().unwrap().get_rects_for_range( + // Find the block for this offset + let block_idx = self.block_index_for_offset(offset); + let block = &self.blocks[block_idx]; + let local_offset = offset - block.byte_start; + let slice = &text[block.byte_start..block.byte_end]; + + let u16_end = utf8_to_utf16_offset(slice, local_offset); + let local_cluster_start = if local_offset > 0 { + prev_grapheme_boundary(slice, local_offset) + } else { + 0 + }; + let u16_start = utf8_to_utf16_offset(slice, local_cluster_start); + let rects = block.paragraph.get_rects_for_range( u16_start..u16_end, RectHeightStyle::Max, RectWidthStyle::Tight, @@ -564,11 +750,20 @@ impl TextLayoutEngine for SkiaLayoutEngine { fn word_boundary_at(&mut self, text: &str, offset: usize) -> (usize, usize) { self.ensure_layout(text); - let u16_pos = utf8_to_utf16_offset(text, offset) as u32; - let para = self.paragraph.as_ref().unwrap(); - let range = para.get_word_boundary(u16_pos); - let start = utf16_to_utf8_offset(text, range.start as usize); - let end = utf16_to_utf8_offset(text, range.end as usize); + + if self.blocks.is_empty() { + return (0, 0); + } + + let block_idx = self.block_index_for_offset(offset); + let block = &self.blocks[block_idx]; + let slice = &text[block.byte_start..block.byte_end]; + let local_offset = offset - block.byte_start; + + let u16_pos = utf8_to_utf16_offset(slice, local_offset) as u32; + let range = block.paragraph.get_word_boundary(u16_pos); + let start = block.byte_start + utf16_to_utf8_offset(slice, range.start as usize); + let end = block.byte_start + utf16_to_utf8_offset(slice, range.end as usize); (start, end) } @@ -582,24 +777,44 @@ impl TextLayoutEngine for SkiaLayoutEngine { if metrics.is_empty() { return Vec::new(); } + self.ensure_layout(text); - let para = self.paragraph.as_ref().unwrap(); - - let u16_lo = utf8_to_utf16_offset(text, start); - let u16_hi = utf8_to_utf16_offset(text, end); - - let raw = para.get_rects_for_range( - u16_lo..u16_hi, - skia_safe::textlayout::RectHeightStyle::Max, - skia_safe::textlayout::RectWidthStyle::Tight, - ); - - let mut rects: Vec = raw.iter().map(|tb| SelectionRect { - x: tb.rect.left(), - y: tb.rect.top(), - width: (tb.rect.right() - tb.rect.left()).max(0.0), - height: (tb.rect.bottom() - tb.rect.top()).max(0.0), - }).collect(); + + // Find blocks that overlap the selection range. + let mut rects: Vec = Vec::new(); + for block in &self.blocks { + if block.byte_start >= end || block.byte_end <= start { + continue; + } + // Clamp selection to this block's range + let sel_start = start.max(block.byte_start); + let sel_end = end.min(block.byte_end); + if sel_start >= sel_end && !(block.byte_start == block.byte_end) { + continue; + } + + let slice = &text[block.byte_start..block.byte_end]; + let local_start = sel_start - block.byte_start; + let local_end = sel_end - block.byte_start; + + let u16_lo = utf8_to_utf16_offset(slice, local_start); + let u16_hi = utf8_to_utf16_offset(slice, local_end); + + let raw = block.paragraph.get_rects_for_range( + u16_lo..u16_hi, + skia_safe::textlayout::RectHeightStyle::Max, + skia_safe::textlayout::RectWidthStyle::Tight, + ); + + for tb in &raw { + rects.push(SelectionRect { + x: tb.rect.left(), + y: tb.rect.top() + block.y_offset, + width: (tb.rect.right() - tb.rect.left()).max(0.0), + height: (tb.rect.bottom() - tb.rect.top()).max(0.0), + }); + } + } // Empty-line invariant: every selected line must have a visible rect. let first_line = line_index_for_offset_utf8(&metrics, start); From d81824585955460622e37ffd33ea9b29294a3f45 Mon Sep 17 00:00:00 2001 From: Universe Date: Wed, 4 Mar 2026 20:52:22 +0900 Subject: [PATCH 09/15] feat: implement vertical scrolling in text editor - Added vertical scrolling functionality to the text editor, allowing users to navigate through content that exceeds the viewport height. - Introduced a `scroll_y` property to manage the vertical scroll offset and implemented methods for scrolling and ensuring the cursor remains visible. - Updated the rendering logic to account for the scroll offset, ensuring proper display of text within the visible area. - Enhanced documentation to outline the new scrolling behavior and its integration with the text editing engine. This update aims to improve the user experience by providing a seamless scrolling mechanism in the grida-text-edit project. --- .../examples/wd_text_editor.rs | 116 +++++++++++-- crates/grida-text-edit/src/skia_layout.rs | 154 +++++++++++++++++- docs/wg/feat-text-editing/index.md | 32 ++++ 3 files changed, 287 insertions(+), 15 deletions(-) diff --git a/crates/grida-text-edit/examples/wd_text_editor.rs b/crates/grida-text-edit/examples/wd_text_editor.rs index df74c2b1a3..2bd5bf597f 100644 --- a/crates/grida-text-edit/examples/wd_text_editor.rs +++ b/crates/grida-text-edit/examples/wd_text_editor.rs @@ -89,8 +89,12 @@ // [x] F5: Inter (sans) F6: Lora (serif) F7: Inconsolata (mono) // [x] Drag-and-drop .txt / .html files to load content // +// Scroll +// [x] Vertical scroll (mouse wheel / trackpad) +// [x] Auto-scroll to keep cursor visible +// [x] Viewport clipping +// // Not yet implemented -// [ ] Scroll (vertical) // [ ] Visual-order bidi cursor movement use std::ffi::CString; @@ -121,7 +125,7 @@ use skia_safe::{ use winit::{ application::ApplicationHandler, dpi::{LogicalPosition, LogicalSize, PhysicalSize}, - event::{ElementState, Ime, MouseButton, WindowEvent}, + event::{ElementState, Ime, MouseButton, MouseScrollDelta, WindowEvent}, event_loop::{ActiveEventLoop, ControlFlow, EventLoop}, keyboard::{Key, KeyCode, ModifiersState, NamedKey, PhysicalKey}, window::{Window, WindowAttributes, WindowId}, @@ -375,6 +379,9 @@ struct TextEditor { /// Undo / redo history capturing both text and style state. history: GenericEditHistory, + + /// Vertical scroll offset in layout-local pixels. + scroll_y: f32, } impl TextEditor { @@ -394,6 +401,7 @@ impl TextEditor { preedit: None, empty_line_policy: config.empty_line_policy, history: GenericEditHistory::new(), + scroll_y: 0.0, } } @@ -411,6 +419,7 @@ impl TextEditor { self.content = snap.content; self.caret_style_override = None; self.layout.invalidate(); + self.ensure_cursor_visible(); } // ----------------------------------------------------------------------- @@ -426,6 +435,7 @@ impl TextEditor { self.state = apply_command(&self.state, cmd, &mut self.layout); self.sync_content_after_edit(&old_text, old_cursor); self.reset_blink(); + self.ensure_cursor_visible(); } fn apply_with_kind(&mut self, cmd: EditingCommand, kind: EditKind) { @@ -435,6 +445,7 @@ impl TextEditor { self.state = apply_command(&self.state, cmd, &mut self.layout); self.sync_content_after_edit(&old_text, old_cursor); self.reset_blink(); + self.ensure_cursor_visible(); } /// After apply_command changes `self.state.text`, diff and update @@ -863,6 +874,58 @@ impl TextEditor { fn set_layout_height(&mut self, h: f32) { self.layout.set_layout_height((h - PADDING * 2.0).max(1.0)); + self.clamp_scroll(); + } + + // ----------------------------------------------------------------------- + // Scroll + // ----------------------------------------------------------------------- + + /// Total content height from the layout engine. + fn content_height(&mut self) -> f32 { + let metrics = self.layout.line_metrics(&self.state.text); + if let Some(last) = metrics.last() { + last.baseline + last.descent + } else { + 0.0 + } + } + + /// Maximum scroll offset (content may be shorter than viewport). + fn max_scroll_y(&mut self) -> f32 { + (self.content_height() - self.layout.layout_height).max(0.0) + } + + /// Clamp scroll_y to valid range. + fn clamp_scroll(&mut self) { + let max = self.max_scroll_y(); + self.scroll_y = self.scroll_y.clamp(0.0, max); + } + + /// Adjust scroll offset by `delta` pixels (positive = scroll down). + fn scroll_by(&mut self, delta: f32) { + self.scroll_y += delta; + self.clamp_scroll(); + } + + /// Adjust scroll so the caret is within the visible viewport. + fn ensure_cursor_visible(&mut self) { + let cr = self.layout.caret_rect_at(&self.state.text, self.state.cursor); + let viewport_height = self.layout.layout_height; + let margin = cr.height; // one line of margin + + // Cursor above viewport + if cr.y < self.scroll_y + margin { + self.scroll_y = (cr.y - margin).max(0.0); + } + + // Cursor below viewport + let cursor_bottom = cr.y + cr.height; + if cursor_bottom > self.scroll_y + viewport_height - margin { + self.scroll_y = cursor_bottom - viewport_height + margin; + } + + self.clamp_scroll(); } // ----------------------------------------------------------------------- @@ -908,7 +971,20 @@ impl TextEditor { fn draw(&mut self, canvas: &skia_safe::Canvas) { canvas.clear(Color::WHITE); - let origin = Point::new(PADDING, PADDING); + + // Clip to the text area and translate by the scroll offset. + canvas.save(); + canvas.clip_rect( + Rect::from_xywh( + PADDING, + PADDING, + self.layout.layout_width, + self.layout.layout_height, + ), + None, + None, + ); + let origin = Point::new(PADDING, PADDING - self.scroll_y); let preedit = self.preedit.as_deref().filter(|p| !p.is_empty()).map(str::to_owned); @@ -1069,6 +1145,8 @@ impl TextEditor { canvas.draw_rect(cursor_rect, &cp); } } + + canvas.restore(); } } @@ -1633,11 +1711,23 @@ impl ApplicationHandler for TextEditorApp { } } + WindowEvent::MouseWheel { delta, .. } => { + let dy = match delta { + MouseScrollDelta::PixelDelta(pos) => pos.y as f32, + MouseScrollDelta::LineDelta(_, lines) => lines * FONT_SIZE * 3.0, + }; + inner.editor.scroll_by(-dy); + inner.window.request_redraw(); + } + WindowEvent::CursorMoved { position, .. } => { let x = position.x as f32; let y = position.y as f32; self.last_mouse_pos = (x, y); - inner.editor.on_mouse_move(x - PADDING, y - PADDING); + inner.editor.on_mouse_move( + x - PADDING, + y - PADDING + inner.editor.scroll_y, + ); if inner.editor.mouse_down { inner.window.request_redraw(); } @@ -1654,8 +1744,11 @@ impl ApplicationHandler for TextEditorApp { let local_y = y - PADDING; let shift = self.modifiers.shift_key(); + let scroll_y = inner.editor.scroll_y; + let layout_y = local_y + scroll_y; + if shift { - inner.editor.shift_click(local_x, local_y); + inner.editor.shift_click(local_x, layout_y); inner.window.request_redraw(); } else { let now = Instant::now(); @@ -1675,9 +1768,9 @@ impl ApplicationHandler for TextEditorApp { self.last_click_pos = (x, y); match self.click_count { - 1 => inner.editor.on_mouse_down(local_x, local_y), - 2 => inner.editor.select_word_at(local_x, local_y), - 3 => inner.editor.select_line_at(local_x, local_y), + 1 => inner.editor.on_mouse_down(local_x, layout_y), + 2 => inner.editor.select_word_at(local_x, layout_y), + 3 => inner.editor.select_line_at(local_x, layout_y), _ => inner.editor.select_all(), } inner.window.request_redraw(); @@ -1700,10 +1793,11 @@ impl ApplicationHandler for TextEditorApp { &inner.editor.state.text, inner.editor.state.cursor, ); + let scroll_y = inner.editor.scroll_y; inner.window.set_ime_cursor_area( LogicalPosition::new( (cr.x + PADDING) as f64, - (cr.y + PADDING) as f64, + (cr.y + PADDING - scroll_y) as f64, ), LogicalSize::new(1.0f64, cr.height as f64), ); @@ -1726,11 +1820,12 @@ impl ApplicationHandler for TextEditorApp { Ok(content) => { let default_style = inner.editor.content.default_style().clone(); inner.editor.content = AttributedText::new(&content, default_style); - inner.editor.state.text = content; + inner.editor.state.text = inner.editor.content.text().to_owned(); inner.editor.state.cursor = inner.editor.state.text.len(); inner.editor.state.anchor = None; inner.editor.caret_style_override = None; inner.editor.layout.invalidate(); + inner.editor.scroll_y = 0.0; inner.editor.reset_blink(); eprintln!("loaded plain text: {}", path.display()); inner.window.request_redraw(); @@ -1749,6 +1844,7 @@ impl ApplicationHandler for TextEditorApp { inner.editor.state.anchor = None; inner.editor.caret_style_override = None; inner.editor.layout.invalidate(); + inner.editor.scroll_y = 0.0; inner.editor.reset_blink(); eprintln!("loaded HTML: {}", path.display()); inner.window.request_redraw(); diff --git a/crates/grida-text-edit/src/skia_layout.rs b/crates/grida-text-edit/src/skia_layout.rs index ffff72148c..99521a95fb 100644 --- a/crates/grida-text-edit/src/skia_layout.rs +++ b/crates/grida-text-edit/src/skia_layout.rs @@ -173,14 +173,30 @@ impl SkiaLayoutEngine { // Layout: per-block architecture // ------------------------------------------------------------------- - /// Ensure the per-block layout is up-to-date for `text`. + /// Ensure layout is up-to-date for `text`. + /// + /// If an attributed (rich text) paragraph already exists for this text, + /// it is preserved — the per-block path is only used when no attributed + /// paragraph is available. pub fn ensure_layout(&mut self, text: &str) { + // If per-block layout is already current, nothing to do. if !self.blocks.is_empty() && self.cached_text == text { return; } + // If the attributed (single-paragraph) layout is current, skip + // the per-block rebuild — callers will use the legacy paragraph. + if self.paragraph.is_some() && self.cached_text == text { + return; + } self.rebuild_blocks(text); } + /// Returns true when the layout is backed by a single attributed + /// paragraph (rich text path) rather than per-block layout. + fn is_attributed_layout(&self) -> bool { + self.paragraph.is_some() && self.blocks.is_empty() + } + /// Full rebuild: split `text` on `\n` and lay out each block. fn rebuild_blocks(&mut self, text: &str) { self.blocks.clear(); @@ -404,6 +420,54 @@ impl SkiaLayoutEngine { result } + /// Convert line metrics from the legacy single paragraph (attributed path). + fn convert_single_para_line_metrics(&self, text: &str) -> Vec { + let para = self.paragraph.as_ref().unwrap(); + let skia = para.get_line_metrics(); + let mut result = Vec::with_capacity(skia.len()); + let mut prev_end: usize = 0; + + for lm in &skia { + let start = utf16_to_utf8_offset(text, lm.start_index); + let end = utf16_to_utf8_offset(text, lm.end_including_newline).min(text.len()); + + let start = start.max(prev_end); + let end = end.max(start); + + result.push(LineMetrics { + start_index: start, + end_index: end, + baseline: lm.baseline as f32, + ascent: lm.ascent as f32, + descent: lm.descent as f32, + }); + prev_end = end; + } + + if !text.is_empty() && text.ends_with('\n') { + let last_end = result.last().map_or(0, |lm| lm.end_index); + if last_end < text.len() + || result + .last() + .map_or(true, |lm| lm.start_index < lm.end_index) + { + if last_end <= text.len() { + let last = result.last().unwrap(); + let line_height = last.ascent + last.descent; + result.push(LineMetrics { + start_index: text.len(), + end_index: text.len(), + baseline: last.baseline + line_height, + ascent: last.ascent, + descent: last.descent, + }); + } + } + } + + result + } + /// Find the block index that contains `byte_offset` in the full text. fn block_index_for_offset(&self, byte_offset: usize) -> usize { self.blocks @@ -663,7 +727,12 @@ impl TextLayoutEngine for SkiaLayoutEngine { return cached.clone(); } - let result = self.flatten_line_metrics(); + let result = if self.is_attributed_layout() { + // Attributed (single-paragraph) path: convert from the legacy paragraph. + self.convert_single_para_line_metrics(text) + } else { + self.flatten_line_metrics() + }; self.cached_line_metrics = Some(result.clone()); result } @@ -671,7 +740,26 @@ impl TextLayoutEngine for SkiaLayoutEngine { fn position_at_point(&mut self, text: &str, x: f32, y: f32) -> usize { self.ensure_layout(text); - // Find the block that contains this y coordinate. + if self.is_attributed_layout() { + // Attributed (single-paragraph) path. + let para = self.paragraph.as_ref().unwrap(); + let metrics = para.get_line_metrics(); + for lm in &metrics { + let top = lm.baseline as f32 - lm.ascent as f32; + let bot = lm.baseline as f32 + lm.descent as f32; + if y >= top - 0.5 && y <= bot + 0.5 { + if lm.end_index.saturating_sub(lm.start_index) <= 1 { + return utf16_to_utf8_offset(text, lm.start_index).min(text.len()); + } + break; + } + } + let pwa = para.get_glyph_position_at_coordinate(Point::new(x, y)); + let raw = utf16_to_utf8_offset(text, pwa.position.max(0) as usize).min(text.len()); + return snap_grapheme_boundary(text, raw); + } + + // Per-block path. let block_idx = self.blocks .iter() .position(|b| y < b.y_offset + b.height + 0.5) @@ -723,8 +811,19 @@ impl TextLayoutEngine for SkiaLayoutEngine { let x = if offset <= lm.start_index { 0.0 + } else if self.is_attributed_layout() { + // Attributed (single-paragraph) path. + let u16_end = utf8_to_utf16_offset(text, offset); + let cluster_start = prev_grapheme_boundary(text, offset); + let u16_start = utf8_to_utf16_offset(text, cluster_start); + let rects = self.paragraph.as_ref().unwrap().get_rects_for_range( + u16_start..u16_end, + RectHeightStyle::Max, + RectWidthStyle::Tight, + ); + rects.iter().map(|tb| tb.rect.right()).fold(0.0_f32, f32::max) } else { - // Find the block for this offset + // Per-block path. let block_idx = self.block_index_for_offset(offset); let block = &self.blocks[block_idx]; let local_offset = offset - block.byte_start; @@ -751,6 +850,15 @@ impl TextLayoutEngine for SkiaLayoutEngine { fn word_boundary_at(&mut self, text: &str, offset: usize) -> (usize, usize) { self.ensure_layout(text); + if self.is_attributed_layout() { + let u16_pos = utf8_to_utf16_offset(text, offset) as u32; + let para = self.paragraph.as_ref().unwrap(); + let range = para.get_word_boundary(u16_pos); + let start = utf16_to_utf8_offset(text, range.start as usize); + let end = utf16_to_utf8_offset(text, range.end as usize); + return (start, end); + } + if self.blocks.is_empty() { return (0, 0); } @@ -780,7 +888,43 @@ impl TextLayoutEngine for SkiaLayoutEngine { self.ensure_layout(text); - // Find blocks that overlap the selection range. + if self.is_attributed_layout() { + // Attributed (single-paragraph) path. + let para = self.paragraph.as_ref().unwrap(); + let u16_lo = utf8_to_utf16_offset(text, start); + let u16_hi = utf8_to_utf16_offset(text, end); + let raw = para.get_rects_for_range( + u16_lo..u16_hi, + skia_safe::textlayout::RectHeightStyle::Max, + skia_safe::textlayout::RectWidthStyle::Tight, + ); + let mut rects: Vec = raw.iter().map(|tb| SelectionRect { + x: tb.rect.left(), + y: tb.rect.top(), + width: (tb.rect.right() - tb.rect.left()).max(0.0), + height: (tb.rect.bottom() - tb.rect.top()).max(0.0), + }).collect(); + + let first_line = line_index_for_offset_utf8(&metrics, start); + let last_line = line_index_for_offset_utf8(&metrics, end.saturating_sub(1).max(start)); + for idx in first_line..=last_line { + let lm = &metrics[idx]; + if !lm.is_empty_line(text) { continue; } + let mid_y = lm.baseline - lm.ascent * 0.5; + let already = rects.iter().any(|r| r.y <= mid_y && mid_y <= r.y + r.height); + if !already { + rects.push(SelectionRect { + x: 0.0, + y: lm.baseline - lm.ascent, + width: self.font_size * 0.5, + height: lm.ascent + lm.descent, + }); + } + } + return rects; + } + + // Per-block path: find blocks that overlap the selection range. let mut rects: Vec = Vec::new(); for block in &self.blocks { if block.byte_start >= end || block.byte_end <= start { diff --git a/docs/wg/feat-text-editing/index.md b/docs/wg/feat-text-editing/index.md index 623a1f1b21..7b272f79d8 100644 --- a/docs/wg/feat-text-editing/index.md +++ b/docs/wg/feat-text-editing/index.md @@ -308,6 +308,38 @@ Two viable strategies exist: Either is valid; choosing should be guided by performance goals, platform constraints, and correctness needs. +## Scroll + +When the text content exceeds the viewport height, the host introduces a **scroll offset** that shifts the visible window over the layout. The editing engine operates entirely in **layout-local coordinates** — it has no knowledge of scroll. The host is responsible for translating between screen coordinates and layout coordinates. + +### Coordinate contract + +All geometry returned by the engine (caret rects, selection rects, line metrics, diagnostic geometry) is in **layout-local space** — origin at the top-left of the text layout box, y increasing downward. The host applies the scroll transform: + +- **Screen → layout**: `layout_y = screen_y + scroll_offset` +- **Layout → screen**: `screen_y = layout_y - scroll_offset` + +All input coordinates passed to the engine (`position_at_point`, `ExtendTo`, `SelectWordAt`) must be in layout-local space. The host converts before calling. + +### Scroll adjustment + +The host maintains the scroll offset and adjusts it in response to: + +- **Direct scroll input** (mouse wheel, trackpad gesture, scrollbar drag). +- **Cursor-following**: after any operation that moves the cursor (typing, arrow keys, PageUp/PageDown, search-and-jump), the host checks whether the caret rect is within the visible viewport. If not, the host adjusts the scroll offset to bring the caret into view, with at least one line of margin above or below. +- **Content change**: when text is inserted or deleted, the content height may change. The host clamps the scroll offset to `[0, max(0, content_height - viewport_height)]`. +- **Viewport resize**: same clamping applies. + +### Engine requirements + +The engine provides the information the host needs to implement scroll: + +- **`line_metrics`**: per-line baselines, ascent, and descent — sufficient to compute total content height and determine which lines are visible for a given scroll offset. +- **`caret_rect_at`**: the caret's position in layout-local space — the host uses this to decide whether to auto-scroll. +- **`viewport_height`**: used by PageUp/PageDown to compute the jump distance in lines. + +The engine does **not** own the scroll offset, does not clip rendering, and does not filter geometry by visibility. These are host responsibilities. This separation keeps the engine stateless with respect to scroll and allows different hosts (canvas overlay, standalone editor, WASM) to implement scroll differently. + ## Text diagnostics and spellcheck indication (UX layer) This manifesto does **not** define how spelling or grammar analysis is performed, since dictionaries, language models, and platform services vary widely across environments (native OS, browser, WASM, custom dictionaries, etc.). From 093c0c72c186d29f1c9ae8ec2a01d32033d17b1c Mon Sep 17 00:00:00 2001 From: Universe Date: Wed, 4 Mar 2026 21:41:40 +0900 Subject: [PATCH 10/15] feat: optimize text editor performance with caret rectangle caching - Introduced a caching mechanism for the caret rectangle in the `TextEditor`, reducing redundant computations during rendering. - Updated the `apply_command` function to invalidate the cache when text or cursor changes occur, ensuring accurate caret positioning. - Enhanced the `SkiaLayoutEngine` to support incremental layout updates, allowing for efficient re-layout of only affected text blocks on edits. - Improved the `line_index_for_offset` function to utilize binary search for better performance in locating line indices. This update aims to enhance the responsiveness and efficiency of the text editing experience in the grida-text-edit project. --- .../examples/wd_text_editor.rs | 61 ++- crates/grida-text-edit/src/layout.rs | 14 +- crates/grida-text-edit/src/lib.rs | 156 ++++--- crates/grida-text-edit/src/skia_layout.rs | 389 +++++++++++++++++- 4 files changed, 536 insertions(+), 84 deletions(-) diff --git a/crates/grida-text-edit/examples/wd_text_editor.rs b/crates/grida-text-edit/examples/wd_text_editor.rs index 2bd5bf597f..5d8771b7dd 100644 --- a/crates/grida-text-edit/examples/wd_text_editor.rs +++ b/crates/grida-text-edit/examples/wd_text_editor.rs @@ -132,8 +132,8 @@ use winit::{ }; use grida_text_edit::{ - apply_command, utf8_to_utf16_offset, - EditKind, EditingCommand, GenericEditHistory, + apply_command_mut, utf8_to_utf16_offset, + CaretRect, EditKind, EditingCommand, GenericEditHistory, SkiaLayoutEngine, TextEditorState, TextLayoutEngine, attributed_text::{ AttributedText, TextStyle as AttrTextStyle, @@ -382,6 +382,10 @@ struct TextEditor { /// Vertical scroll offset in layout-local pixels. scroll_y: f32, + + /// Cached caret rectangle — avoids redundant recomputation within a frame. + /// Invalidated (set to `None`) whenever cursor or text changes. + cached_caret_rect: Option, } impl TextEditor { @@ -402,7 +406,23 @@ impl TextEditor { empty_line_policy: config.empty_line_policy, history: GenericEditHistory::new(), scroll_y: 0.0, + cached_caret_rect: None, + } + } + + /// Return the caret rectangle, using a per-frame cache. + fn caret_rect(&mut self) -> CaretRect { + if let Some(ref cr) = self.cached_caret_rect { + return cr.clone(); } + let cr = self.layout.caret_rect_at(&self.state.text, self.state.cursor); + self.cached_caret_rect = Some(cr.clone()); + cr + } + + /// Invalidate the cached caret rect (call after cursor/text changes). + fn invalidate_caret_cache(&mut self) { + self.cached_caret_rect = None; } /// Capture the current state + content as a snapshot for history. @@ -418,6 +438,7 @@ impl TextEditor { self.state = snap.state; self.content = snap.content; self.caret_style_override = None; + self.cached_caret_rect = None; self.layout.invalidate(); self.ensure_cursor_visible(); } @@ -427,13 +448,21 @@ impl TextEditor { // ----------------------------------------------------------------------- fn apply(&mut self, cmd: EditingCommand) { + let is_edit = cmd.edit_kind().is_some(); if let Some(kind) = cmd.edit_kind() { self.history.push(&self.snapshot(), kind); } let old_cursor = self.state.cursor; - let old_text = self.state.text.clone(); - self.state = apply_command(&self.state, cmd, &mut self.layout); - self.sync_content_after_edit(&old_text, old_cursor); + // Only clone the old text when the command may mutate it. + let old_text = if is_edit { Some(self.state.text.clone()) } else { None }; + apply_command_mut(&mut self.state, cmd, &mut self.layout); + self.invalidate_caret_cache(); + if let Some(old) = old_text { + self.sync_content_after_edit(&old, old_cursor); + } else if self.state.cursor != old_cursor { + // Pure cursor movement — clear caret style override. + self.caret_style_override = None; + } self.reset_blink(); self.ensure_cursor_visible(); } @@ -442,7 +471,8 @@ impl TextEditor { self.history.push(&self.snapshot(), kind); let old_cursor = self.state.cursor; let old_text = self.state.text.clone(); - self.state = apply_command(&self.state, cmd, &mut self.layout); + apply_command_mut(&mut self.state, cmd, &mut self.layout); + self.invalidate_caret_cache(); self.sync_content_after_edit(&old_text, old_cursor); self.reset_blink(); self.ensure_cursor_visible(); @@ -507,6 +537,12 @@ impl TextEditor { self.content.insert_with_style(prefix, inserted, insert_style); } + // Incrementally update the per-block layout so only the affected + // paragraph is re-shaped (instead of rebuilding all blocks). + let removed_bytes = old_end - prefix; + let inserted_bytes = new_end - prefix; + self.layout.notify_edit(new_text, prefix, removed_bytes, inserted_bytes); + // After any text edit, clear the override. self.caret_style_override = None; } @@ -820,6 +856,7 @@ impl TextEditor { self.state.cursor = pos; self.state.anchor = None; self.drag_anchor_utf8 = Some(pos); + self.invalidate_caret_cache(); self.reset_blink(); } @@ -840,6 +877,7 @@ impl TextEditor { self.drag_anchor_utf8 = Some(pos); self.state.cursor = pos; } + self.invalidate_caret_cache(); } fn on_mouse_up(&mut self) { @@ -910,7 +948,7 @@ impl TextEditor { /// Adjust scroll so the caret is within the visible viewport. fn ensure_cursor_visible(&mut self) { - let cr = self.layout.caret_rect_at(&self.state.text, self.state.cursor); + let cr = self.caret_rect(); let viewport_height = self.layout.layout_height; let margin = cr.height; // one line of margin @@ -1132,7 +1170,7 @@ impl TextEditor { // Cursor if self.cursor_visible && !self.has_selection() { - let cr = self.layout.caret_rect_at(&self.state.text, self.state.cursor); + let cr = self.caret_rect(); let cursor_rect = Rect::from_xywh( cr.x + origin.x - CURSOR_WIDTH / 2.0, cr.y + origin.y, @@ -1789,10 +1827,7 @@ impl ApplicationHandler for TextEditorApp { } inner.gl_skia.flush_and_present(); - let cr = inner.editor.layout.caret_rect_at( - &inner.editor.state.text, - inner.editor.state.cursor, - ); + let cr = inner.editor.caret_rect(); let scroll_y = inner.editor.scroll_y; inner.window.set_ime_cursor_area( LogicalPosition::new( @@ -1824,6 +1859,7 @@ impl ApplicationHandler for TextEditorApp { inner.editor.state.cursor = inner.editor.state.text.len(); inner.editor.state.anchor = None; inner.editor.caret_style_override = None; + inner.editor.cached_caret_rect = None; inner.editor.layout.invalidate(); inner.editor.scroll_y = 0.0; inner.editor.reset_blink(); @@ -1843,6 +1879,7 @@ impl ApplicationHandler for TextEditorApp { inner.editor.state.cursor = inner.editor.state.text.len(); inner.editor.state.anchor = None; inner.editor.caret_style_override = None; + inner.editor.cached_caret_rect = None; inner.editor.layout.invalidate(); inner.editor.scroll_y = 0.0; inner.editor.reset_blink(); diff --git a/crates/grida-text-edit/src/layout.rs b/crates/grida-text-edit/src/layout.rs index f68559623b..1618480ddb 100644 --- a/crates/grida-text-edit/src/layout.rs +++ b/crates/grida-text-edit/src/layout.rs @@ -118,12 +118,16 @@ pub trait TextLayoutEngine { /// Find which line contains `utf8_offset`. /// -/// Forward-scan: first line where `offset < end_index`. +/// Uses binary search on `end_index` (which is monotonically non-decreasing). +/// Semantics: returns the first line where `offset < end_index`. /// Falls back to the last line when offset is past all end indices. /// This is the same rule used by `caret_rect_at`. pub fn line_index_for_offset(metrics: &[LineMetrics], utf8_offset: usize) -> usize { - metrics - .iter() - .position(|lm| utf8_offset < lm.end_index) - .unwrap_or(metrics.len().saturating_sub(1)) + if metrics.is_empty() { + return 0; + } + // partition_point returns the first index where the predicate is false, + // i.e. the first line where end_index > utf8_offset. + let idx = metrics.partition_point(|lm| lm.end_index <= utf8_offset); + idx.min(metrics.len() - 1) } diff --git a/crates/grida-text-edit/src/lib.rs b/crates/grida-text-edit/src/lib.rs index afa479e519..ff2ddbf488 100644 --- a/crates/grida-text-edit/src/lib.rs +++ b/crates/grida-text-edit/src/lib.rs @@ -43,15 +43,29 @@ pub fn normalize_newlines(s: &str) -> String { // --------------------------------------------------------------------------- // Grapheme boundary helpers +// +// All three functions use a small local window around `pos` instead of +// scanning from byte 0, making them O(1) amortized regardless of position. +// The window size (64 bytes) covers the largest known grapheme clusters +// (complex emoji ZWJ sequences are ≤ ~28 bytes). // --------------------------------------------------------------------------- +/// Maximum lookback/lookahead in bytes for grapheme boundary scanning. +/// Must be larger than the longest grapheme cluster (emoji ZWJ family +/// sequences are ~28 bytes). 64 is a safe margin. +const GRAPHEME_WINDOW: usize = 64; + pub fn prev_grapheme_boundary(text: &str, pos: usize) -> usize { if pos == 0 { return 0; } - let mut prev = 0; - for (i, g) in text.grapheme_indices(true) { - let end = i + g.len(); + // Scan a small window ending at pos. + let window_start = floor_char_boundary(text, pos.saturating_sub(GRAPHEME_WINDOW)); + let slice = &text[window_start..pos.min(text.len())]; + let mut prev = window_start; + for (i, g) in slice.grapheme_indices(true) { + let abs_i = window_start + i; + let end = abs_i + g.len(); if end >= pos { return prev; } @@ -71,23 +85,35 @@ pub fn snap_grapheme_boundary(text: &str, pos: usize) -> usize { if pos == 0 { return 0; } - for (i, g) in text.grapheme_indices(true) { - let end = i + g.len(); - if i <= pos && pos < end { - return i; // pos is inside this cluster — snap to its start - } - if i > pos { - // overshot without a match (should not happen for well-formed UTF-8) - return i; + // Scan a small window around pos. + let window_start = floor_char_boundary(text, pos.saturating_sub(GRAPHEME_WINDOW)); + let window_end = ceil_char_boundary(text, (pos + GRAPHEME_WINDOW).min(text.len())); + let slice = &text[window_start..window_end]; + for (i, g) in slice.grapheme_indices(true) { + let abs_i = window_start + i; + let end = abs_i + g.len(); + if abs_i <= pos && pos < end { + return abs_i; // pos is inside this cluster — snap to its start + } + if abs_i > pos { + return abs_i; } } text.len() } pub fn next_grapheme_boundary(text: &str, pos: usize) -> usize { - for (i, g) in text.grapheme_indices(true) { - if i >= pos { - return i + g.len(); + if pos >= text.len() { + return text.len(); + } + // Scan a small window starting at pos. + let window_start = floor_char_boundary(text, pos); + let window_end = ceil_char_boundary(text, (pos + GRAPHEME_WINDOW).min(text.len())); + let slice = &text[window_start..window_end]; + for (i, g) in slice.grapheme_indices(true) { + let abs_i = window_start + i; + if abs_i >= pos { + return abs_i + g.len(); } } text.len() @@ -318,20 +344,21 @@ impl EditingCommand { // apply_command // --------------------------------------------------------------------------- -/// Apply a single editing command to `state`, returning the new state. +/// Apply a single editing command to `state` **in place**. /// /// Layout-dependent commands call into `layout`; pure commands do not. -/// The function is intentionally pure in the non-layout path (no side effects). -pub fn apply_command( - state: &TextEditorState, +/// Cursor-only commands are O(1) — no text is copied. +/// +/// For backward compatibility, [`apply_command`] is provided as a thin +/// wrapper that clones the state before delegating here. +pub fn apply_command_mut( + s: &mut TextEditorState, command: EditingCommand, layout: &mut dyn TextLayoutEngine, -) -> TextEditorState { - let mut s = state.clone(); - +) { match command { EditingCommand::Insert(text) => { - let pos = delete_selection_in_place(&mut s); + let pos = delete_selection_in_place(s); let normalized = normalize_newlines(&text); s.text.insert_str(pos, &normalized); s.cursor = pos + normalized.len(); @@ -340,7 +367,7 @@ pub fn apply_command( EditingCommand::Backspace => { if s.has_selection() { - s.cursor = delete_selection_in_place(&mut s); + s.cursor = delete_selection_in_place(s); } else if s.cursor > 0 { let prev = prev_grapheme_boundary(&s.text, s.cursor); s.text.drain(prev..s.cursor); @@ -351,7 +378,7 @@ pub fn apply_command( EditingCommand::BackspaceWord => { if s.has_selection() { - s.cursor = delete_selection_in_place(&mut s); + s.cursor = delete_selection_in_place(s); } else if s.cursor > 0 { let target = prev_word_segment_start(&s.text, s.cursor); s.text.drain(target..s.cursor); @@ -362,7 +389,7 @@ pub fn apply_command( EditingCommand::Delete => { if s.has_selection() { - s.cursor = delete_selection_in_place(&mut s); + s.cursor = delete_selection_in_place(s); } else if s.cursor < s.text.len() { let next = next_grapheme_boundary(&s.text, s.cursor); s.text.drain(s.cursor..next); @@ -372,7 +399,7 @@ pub fn apply_command( EditingCommand::DeleteWord => { if s.has_selection() { - s.cursor = delete_selection_in_place(&mut s); + s.cursor = delete_selection_in_place(s); } else if s.cursor < s.text.len() { let target = next_word_segment_end(&s.text, s.cursor); s.text.drain(s.cursor..target); @@ -386,9 +413,9 @@ pub fn apply_command( s.cursor = lo; s.anchor = None; } else { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); s.cursor = prev_grapheme_boundary(&s.text, s.cursor); - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } } @@ -398,22 +425,22 @@ pub fn apply_command( s.cursor = hi; s.anchor = None; } else { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); s.cursor = next_grapheme_boundary(&s.text, s.cursor); - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } } EditingCommand::MoveDocStart { extend } => { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); s.cursor = 0; - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } EditingCommand::MoveDocEnd { extend } => { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); s.cursor = s.text.len(); - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } EditingCommand::SelectAll => { @@ -428,7 +455,7 @@ pub fn apply_command( EditingCommand::BackspaceLine => { if s.has_selection() { - s.cursor = delete_selection_in_place(&mut s); + s.cursor = delete_selection_in_place(s); } else { let metrics = layout.line_metrics(&s.text); if !metrics.is_empty() { @@ -453,7 +480,7 @@ pub fn apply_command( EditingCommand::DeleteLine => { if s.has_selection() { - s.cursor = delete_selection_in_place(&mut s); + s.cursor = delete_selection_in_place(s); } else { let metrics = layout.line_metrics(&s.text); if !metrics.is_empty() { @@ -472,7 +499,7 @@ pub fn apply_command( } EditingCommand::MoveUp { extend } => { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); let metrics = layout.line_metrics(&s.text); if metrics.is_empty() { s.cursor = 0; @@ -491,11 +518,11 @@ pub fn apply_command( s.cursor = 0; } } - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } EditingCommand::MoveDown { extend } => { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); let metrics = layout.line_metrics(&s.text); if metrics.is_empty() { s.cursor = s.text.len(); @@ -514,11 +541,11 @@ pub fn apply_command( s.cursor = s.text.len(); } } - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } EditingCommand::MoveHome { extend } => { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); let metrics = layout.line_metrics(&s.text); if !metrics.is_empty() { let line_idx = line_index_for_offset_utf8(&metrics, s.cursor); @@ -526,11 +553,11 @@ pub fn apply_command( } else { s.cursor = 0; } - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } EditingCommand::MoveEnd { extend } => { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); let metrics = layout.line_metrics(&s.text); if !metrics.is_empty() { let line_idx = line_index_for_offset_utf8(&metrics, s.cursor); @@ -543,11 +570,11 @@ pub fn apply_command( } else { s.cursor = s.text.len(); } - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } EditingCommand::MovePageUp { extend } => { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); let metrics = layout.line_metrics(&s.text); if metrics.is_empty() { s.cursor = 0; @@ -568,11 +595,11 @@ pub fn apply_command( s.cursor = layout.position_at_point(&s.text, x, target_y.max(0.0)); } } - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } EditingCommand::MovePageDown { extend } => { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); let metrics = layout.line_metrics(&s.text); if metrics.is_empty() { s.cursor = s.text.len(); @@ -594,11 +621,11 @@ pub fn apply_command( s.cursor = layout.position_at_point(&s.text, x, target_y.max(0.0)); } } - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } EditingCommand::MoveWordLeft { extend } => { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); let mut pos = s.cursor; loop { let old = pos; @@ -619,11 +646,11 @@ pub fn apply_command( } } s.cursor = pos; - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } EditingCommand::MoveWordRight { extend } => { - set_anchor_if_extending(&mut s, extend); + set_anchor_if_extending(s, extend); let mut pos = s.cursor; loop { let old = pos; @@ -644,7 +671,7 @@ pub fn apply_command( } } s.cursor = pos.min(s.text.len()); - clear_anchor_if_not_extending(&mut s, extend); + clear_anchor_if_not_extending(s, extend); } EditingCommand::MoveTo { x, y } => { @@ -694,7 +721,20 @@ pub fn apply_command( s.anchor, s.text.len(), ); +} +/// Apply a single editing command to `state`, returning the new state. +/// +/// This is a convenience wrapper around [`apply_command_mut`] that clones the +/// state first. Prefer `apply_command_mut` in hot paths to avoid the O(n) +/// clone. +pub fn apply_command( + state: &TextEditorState, + command: EditingCommand, + layout: &mut dyn TextLayoutEngine, +) -> TextEditorState { + let mut s = state.clone(); + apply_command_mut(&mut s, command, layout); s } @@ -726,15 +766,17 @@ fn clear_anchor_if_not_extending(s: &mut TextEditorState, extend: bool) { /// Find which line contains `utf8_offset`. /// -/// Uses the forward-scan rule: the first line where `offset < end_index`. +/// Uses binary search on `end_index` (which is monotonically non-decreasing). +/// Semantics: returns the first line where `offset < end_index`. /// Falls back to the last line when `offset >= all end indices` (cursor at /// text end or on a trailing phantom line). This is the same rule used by /// `caret_rect_at`, ensuring editing commands and caret rendering always /// agree on which line the cursor belongs to. pub fn line_index_for_offset_utf8(metrics: &[LineMetrics], utf8_offset: usize) -> usize { - metrics - .iter() - .position(|lm| utf8_offset < lm.end_index) - .unwrap_or(metrics.len().saturating_sub(1)) + if metrics.is_empty() { + return 0; + } + let idx = metrics.partition_point(|lm| lm.end_index <= utf8_offset); + idx.min(metrics.len() - 1) } diff --git a/crates/grida-text-edit/src/skia_layout.rs b/crates/grida-text-edit/src/skia_layout.rs index 99521a95fb..3111e51d6c 100644 --- a/crates/grida-text-edit/src/skia_layout.rs +++ b/crates/grida-text-edit/src/skia_layout.rs @@ -307,6 +307,364 @@ impl SkiaLayoutEngine { self.cached_text = text.to_owned(); } + // ------------------------------------------------------------------- + // Incremental layout: rebuild only the affected block(s) + // ------------------------------------------------------------------- + + /// Notify the layout engine that a text edit occurred, enabling + /// incremental re-layout of only the affected paragraph block(s). + /// + /// Call this **after** mutating the text buffer but **before** the next + /// `ensure_layout` / `line_metrics` / `caret_rect_at` call. + /// + /// * `text` — the **new** (post-edit) text string. + /// * `edit_offset` — byte offset in the **old** text where the edit starts. + /// * `old_len` — number of bytes removed from the old text (0 for pure insert). + /// * `new_len` — number of bytes inserted into the new text (0 for pure delete). + /// + /// The method falls back to a full rebuild when: + /// - There is no existing block layout (first call or after `invalidate`). + /// - The edit spans across block boundaries in ways that change the + /// paragraph structure (inserting/removing `\n`). + pub fn notify_edit( + &mut self, + text: &str, + edit_offset: usize, + old_len: usize, + new_len: usize, + ) { + // If we are in attributed (single-paragraph) mode, or have no blocks, + // fall back to full rebuild. + if self.blocks.is_empty() || self.paragraph.is_some() { + self.rebuild_blocks(text); + return; + } + + let delta = new_len as isize - old_len as isize; + let old_edit_end = edit_offset + old_len; + + // Check whether the edit changed the paragraph structure (\n inserted + // or removed). If so, fall back to a targeted rebuild of the affected + // region rather than a full rebuild of the entire document. + let inserted_text = &text[edit_offset..edit_offset + new_len]; + let newlines_inserted = inserted_text.as_bytes().iter().filter(|&&b| b == b'\n').count(); + + // For the removed region, we need to count newlines that were in the + // old text. We can infer this from the block boundaries. + let newlines_removed = if old_len > 0 { + // Count how many block boundaries (each ends with \n except + // possibly the last) fall strictly inside the removed range. + self.blocks.iter().filter(|b| { + // A block boundary at byte_end (which includes the \n) is + // "removed" if byte_end falls within (edit_offset, old_edit_end]. + b.byte_end > edit_offset && b.byte_end <= old_edit_end + && b.byte_end < self.cached_text.len() // not the last block + }).count() + } else { + 0 + }; + + let structure_changed = newlines_inserted > 0 || newlines_removed > 0; + + if structure_changed { + // Paragraph structure changed — do a partial rebuild. + // Find the range of blocks that are affected by the edit. + let first_affected = self.blocks + .iter() + .position(|b| b.byte_end > edit_offset) + .unwrap_or(self.blocks.len().saturating_sub(1)); + + // The last affected block is the one that contains old_edit_end + // (in the old coordinate system). + let last_affected = self.blocks + .iter() + .position(|b| b.byte_end >= old_edit_end) + .unwrap_or(self.blocks.len().saturating_sub(1)); + + // Remove old phantom trailing block if present. + if let Some(last) = self.blocks.last() { + if last.byte_start == last.byte_end + && last.byte_start == self.cached_text.len() + && self.cached_text.ends_with('\n') + { + self.blocks.pop(); + } + } + + // Determine the byte range in the NEW text that we need to + // re-parse into blocks. + let rebuild_start = self.blocks.get(first_affected) + .map(|b| b.byte_start) + .unwrap_or(0); + let old_rebuild_end = self.blocks.get(last_affected) + .map(|b| b.byte_end) + .unwrap_or(self.cached_text.len()); + let rebuild_end = (old_rebuild_end as isize + delta).max(0) as usize; + let rebuild_end = rebuild_end.min(text.len()); + + let y_start = self.blocks.get(first_affected) + .map(|b| b.y_offset) + .unwrap_or(0.0); + + // Build new blocks for the affected region. + let mut new_blocks = Vec::new(); + let mut y_offset = y_start; + let mut start = rebuild_start; + + let region = &text[rebuild_start..rebuild_end]; + let mut cursor = 0usize; + + loop { + if cursor >= region.len() && start >= rebuild_end { + break; + } + let remaining = ®ion[cursor..]; + let has_newline; + let chunk_len = if let Some(pos) = remaining.find('\n') { + has_newline = true; + pos + 1 + } else { + has_newline = false; + remaining.len() + }; + + let end = start + chunk_len; + let content_end = if has_newline { end - 1 } else { end }; + let content_slice = &text[start..content_end]; + let para = self.build_paragraph_for_slice(content_slice); + + let mut stored_lines = self.convert_block_line_metrics(¶, content_slice, start); + + if stored_lines.is_empty() { + let skia_metrics = para.get_line_metrics(); + let (ascent, descent, baseline) = if let Some(lm) = skia_metrics.first() { + (lm.ascent as f32, lm.descent as f32, lm.baseline as f32) + } else { + (self.font_size, self.font_size * 0.2, self.font_size) + }; + stored_lines.push(LineMetrics { + start_index: start, + end_index: start, + baseline, + ascent, + descent, + }); + } + + if has_newline { + if let Some(last) = stored_lines.last_mut() { + last.end_index = end; + } + } + + let height: f32 = if let Some(last) = stored_lines.last() { + last.baseline + last.descent + } else { + self.font_size * 1.2 + }; + + new_blocks.push(ParaBlock { + byte_start: start, + byte_end: end, + paragraph: para, + y_offset, + height, + line_metrics: stored_lines, + }); + + y_offset += height; + cursor += chunk_len; + start = end; + + if start >= rebuild_end { + break; + } + } + + // Splice the new blocks into the existing blocks array. + let remove_count = (last_affected + 1).min(self.blocks.len()) - first_affected; + let tail_start = first_affected + remove_count; + let old_y_after = if tail_start < self.blocks.len() { + self.blocks[tail_start].y_offset + } else { + // No tail blocks. + y_offset // doesn't matter, no shifting needed + }; + + // Shift tail blocks: only byte offsets and y_offset change. + // Per-block lm.baseline is block-local (from Skia) and stays the same. + let y_delta = y_offset - old_y_after; + for block in &mut self.blocks[tail_start..] { + block.byte_start = (block.byte_start as isize + delta) as usize; + block.byte_end = (block.byte_end as isize + delta) as usize; + block.y_offset += y_delta; + for lm in &mut block.line_metrics { + lm.start_index = (lm.start_index as isize + delta) as usize; + lm.end_index = (lm.end_index as isize + delta) as usize; + } + } + + // Replace affected blocks with new blocks. + self.blocks.splice(first_affected..first_affected + remove_count, new_blocks); + + // Re-add phantom trailing block if needed. + if text.ends_with('\n') && !text.is_empty() { + // Remove old phantom if present. + if let Some(last) = self.blocks.last() { + if last.byte_start == last.byte_end && last.byte_start == text.len() { + self.blocks.pop(); + } + } + if let Some(last_block) = self.blocks.last() { + let last_lm = last_block.line_metrics.last(); + let (ascent, descent) = last_lm + .map(|lm| (lm.ascent, lm.descent)) + .unwrap_or((self.font_size, self.font_size * 0.2)); + let phantom_y = last_block.y_offset + last_block.height; + let phantom = LineMetrics { + start_index: text.len(), + end_index: text.len(), + baseline: ascent, + ascent, + descent, + }; + let phantom_height = ascent + descent; + self.blocks.push(ParaBlock { + byte_start: text.len(), + byte_end: text.len(), + paragraph: self.build_paragraph_for_slice(""), + y_offset: phantom_y, + height: phantom_height, + line_metrics: vec![phantom], + }); + } + } else { + // Remove phantom if text no longer ends with \n. + if let Some(last) = self.blocks.last() { + if last.byte_start == last.byte_end && last.byte_start == text.len() && !text.is_empty() { + self.blocks.pop(); + } + } + } + } else { + // No paragraph structure change — only one block is affected. + // Find it and rebuild just that block. + let block_idx = self.blocks + .iter() + .position(|b| edit_offset < b.byte_end || (b.byte_start == b.byte_end && edit_offset == b.byte_start)) + .unwrap_or(self.blocks.len().saturating_sub(1)); + + // Remove old phantom trailing block before modifying. + let had_phantom = if let Some(last) = self.blocks.last() { + last.byte_start == last.byte_end + && last.byte_start == self.cached_text.len() + && self.cached_text.ends_with('\n') + } else { + false + }; + if had_phantom { + self.blocks.pop(); + } + + // Rebuild the affected block. + if block_idx < self.blocks.len() { + let old_block = &self.blocks[block_idx]; + let old_height = old_block.height; + let new_start = old_block.byte_start; + let new_end = (old_block.byte_end as isize + delta) as usize; + let y_off = old_block.y_offset; + + let has_newline = new_end > 0 && new_end <= text.len() + && text.as_bytes().get(new_end - 1) == Some(&b'\n'); + let content_end = if has_newline { new_end - 1 } else { new_end }; + let content_slice = &text[new_start..content_end]; + let para = self.build_paragraph_for_slice(content_slice); + + let mut stored_lines = self.convert_block_line_metrics(¶, content_slice, new_start); + + if stored_lines.is_empty() { + let skia_metrics = para.get_line_metrics(); + let (ascent, descent, baseline) = if let Some(lm) = skia_metrics.first() { + (lm.ascent as f32, lm.descent as f32, lm.baseline as f32) + } else { + (self.font_size, self.font_size * 0.2, self.font_size) + }; + stored_lines.push(LineMetrics { + start_index: new_start, + end_index: new_start, + baseline, + ascent, + descent, + }); + } + + if has_newline { + if let Some(last) = stored_lines.last_mut() { + last.end_index = new_end; + } + } + + let new_height: f32 = if let Some(last) = stored_lines.last() { + last.baseline + last.descent + } else { + self.font_size * 1.2 + }; + + self.blocks[block_idx] = ParaBlock { + byte_start: new_start, + byte_end: new_end, + paragraph: para, + y_offset: y_off, + height: new_height, + line_metrics: stored_lines, + }; + + // Shift all subsequent blocks: byte offsets and y_offset. + // Per-block lm.baseline is block-local and stays the same. + let y_delta = new_height - old_height; + for block in &mut self.blocks[block_idx + 1..] { + block.byte_start = (block.byte_start as isize + delta) as usize; + block.byte_end = (block.byte_end as isize + delta) as usize; + block.y_offset += y_delta; + for lm in &mut block.line_metrics { + lm.start_index = (lm.start_index as isize + delta) as usize; + lm.end_index = (lm.end_index as isize + delta) as usize; + } + } + } + + // Re-add phantom trailing block if needed. + if text.ends_with('\n') && !text.is_empty() { + if let Some(last_block) = self.blocks.last() { + let last_lm = last_block.line_metrics.last(); + let (ascent, descent) = last_lm + .map(|lm| (lm.ascent, lm.descent)) + .unwrap_or((self.font_size, self.font_size * 0.2)); + let phantom_y = last_block.y_offset + last_block.height; + let phantom = LineMetrics { + start_index: text.len(), + end_index: text.len(), + baseline: ascent, + ascent, + descent, + }; + let phantom_height = ascent + descent; + self.blocks.push(ParaBlock { + byte_start: text.len(), + byte_end: text.len(), + paragraph: self.build_paragraph_for_slice(""), + y_offset: phantom_y, + height: phantom_height, + line_metrics: vec![phantom], + }); + } + } + } + + self.cached_text = text.to_owned(); + self.cached_line_metrics = None; + } + /// Build a Skia `Paragraph` for a text slice (uniform style from config). fn build_paragraph_for_slice(&self, slice: &str) -> Paragraph { let mut para_style = ParagraphStyle::new(); @@ -469,11 +827,24 @@ impl SkiaLayoutEngine { } /// Find the block index that contains `byte_offset` in the full text. + /// + /// Uses binary search on `byte_end` (monotonically non-decreasing). fn block_index_for_offset(&self, byte_offset: usize) -> usize { - self.blocks - .iter() - .position(|b| byte_offset < b.byte_end || (b.byte_start == b.byte_end && byte_offset == b.byte_start)) - .unwrap_or(self.blocks.len().saturating_sub(1)) + if self.blocks.is_empty() { + return 0; + } + let idx = self.blocks.partition_point(|b| b.byte_end <= byte_offset); + // Handle phantom zero-length blocks (byte_start == byte_end) at the + // exact offset. + let idx = idx.min(self.blocks.len() - 1); + // If we landed past the matching block, check if a zero-length block + // at this offset exists at the found position. + if self.blocks[idx].byte_start == self.blocks[idx].byte_end + && self.blocks[idx].byte_start == byte_offset + { + return idx; + } + idx } // ------------------------------------------------------------------- @@ -759,15 +1130,13 @@ impl TextLayoutEngine for SkiaLayoutEngine { return snap_grapheme_boundary(text, raw); } - // Per-block path. - let block_idx = self.blocks - .iter() - .position(|b| y < b.y_offset + b.height + 0.5) - .unwrap_or(self.blocks.len().saturating_sub(1)); - + // Per-block path: binary search by y position. if self.blocks.is_empty() { return 0; } + let block_idx = self.blocks + .partition_point(|b| b.y_offset + b.height + 0.5 <= y) + .min(self.blocks.len() - 1); let block = &self.blocks[block_idx]; let local_y = y - block.y_offset; From ada0dac5f2dffde129348405cb0fb047485e279a Mon Sep 17 00:00:00 2001 From: Universe Date: Thu, 5 Mar 2026 14:03:46 +0900 Subject: [PATCH 11/15] refactor: improve text editor selection and layout handling - Updated the text editor to utilize per-block layout for rich text, enhancing rendering efficiency. - Refined selection handling by implementing `selection_rects_for_range`, simplifying the selection rectangle calculations. - Adjusted cursor positioning logic to reset the cursor to the start after content updates. - Enhanced the Skia layout engine with incremental UTF-16 to UTF-8 offset conversion, optimizing performance during text rendering. This update aims to streamline text selection and layout processes, improving overall performance and user experience in the grida-text-edit project. --- .../examples/wd_text_editor.rs | 36 +- crates/grida-text-edit/src/skia_layout.rs | 450 ++++++++++++------ 2 files changed, 329 insertions(+), 157 deletions(-) diff --git a/crates/grida-text-edit/examples/wd_text_editor.rs b/crates/grida-text-edit/examples/wd_text_editor.rs index 5d8771b7dd..ce18ce01b8 100644 --- a/crates/grida-text-edit/examples/wd_text_editor.rs +++ b/crates/grida-text-edit/examples/wd_text_editor.rs @@ -1130,32 +1130,25 @@ impl TextEditor { &cp, ); } else { - // ---- normal mode (rich text) ---- + // ---- normal mode (rich text, per-block layout) ---- self.layout.ensure_layout_attributed(&self.content); - // Selection + // Selection (using per-block selection_rects_for_range) if let Some((lo, hi)) = self.selection_range() { if lo < hi { - let u16_lo = utf8_to_utf16_offset(&self.state.text, lo); - let u16_hi = utf8_to_utf16_offset(&self.state.text, hi); - let rects = selection_rects_with_empty_line_invariant( - self.layout.paragraph.as_ref().unwrap(), - &self.state.text, - u16_lo, - u16_hi, - self.layout.layout_width, - self.empty_line_policy, + let sel_rects = self.layout.selection_rects_for_range( + &self.state.text, lo, hi, ); let mut sp = Paint::default(); sp.set_color(Color::from_argb(80, 66, 133, 244)); sp.set_anti_alias(true); - for r in &rects { + for r in &sel_rects { canvas.draw_rect( Rect::from_ltrb( - r.left + origin.x, - r.top + origin.y, - r.right + origin.x, - r.bottom + origin.y, + r.x + origin.x, + r.y + origin.y, + r.x + r.width + origin.x, + r.y + r.height + origin.y, ), &sp, ); @@ -1163,10 +1156,9 @@ impl TextEditor { } } - // Text - if let Some(ref para) = self.layout.paragraph { - para.paint(canvas, origin); - } + // Text (per-block painting with origin offset) + self.layout.ensure_layout(&self.state.text); + self.layout.paint_paragraph_at(canvas, &self.state.text, origin); // Cursor if self.cursor_visible && !self.has_selection() { @@ -1856,7 +1848,7 @@ impl ApplicationHandler for TextEditorApp { let default_style = inner.editor.content.default_style().clone(); inner.editor.content = AttributedText::new(&content, default_style); inner.editor.state.text = inner.editor.content.text().to_owned(); - inner.editor.state.cursor = inner.editor.state.text.len(); + inner.editor.state.cursor = 0; inner.editor.state.anchor = None; inner.editor.caret_style_override = None; inner.editor.cached_caret_rect = None; @@ -1876,7 +1868,7 @@ impl ApplicationHandler for TextEditorApp { let content = html_to_attributed_text(&html, base); inner.editor.state.text = content.text().to_owned(); inner.editor.content = content; - inner.editor.state.cursor = inner.editor.state.text.len(); + inner.editor.state.cursor = 0; inner.editor.state.anchor = None; inner.editor.caret_style_override = None; inner.editor.cached_caret_rect = None; diff --git a/crates/grida-text-edit/src/skia_layout.rs b/crates/grida-text-edit/src/skia_layout.rs index 3111e51d6c..33675ffe6c 100644 --- a/crates/grida-text-edit/src/skia_layout.rs +++ b/crates/grida-text-edit/src/skia_layout.rs @@ -16,6 +16,158 @@ use crate::{ const DEFAULT_FONT_SIZE: f32 = 18.0; +/// Convert an [`AttributedText`] `TextStyle` to a Skia `TextStyle`. +/// +/// Extracted as a free function so it can be reused by both the monolithic +/// and per-block attributed layout paths. +fn attr_style_to_skia( + style: &crate::attributed_text::TextStyle, + fallback_families: &[&str], +) -> TextStyle { + use crate::attributed_text as at_mod; + + let mut ts = TextStyle::new(); + ts.set_font_size(style.font_size); + + let mut families: Vec<&str> = vec![style.font_family.as_str()]; + for f in fallback_families { + if *f != style.font_family.as_str() { + families.push(f); + } + } + ts.set_font_families(&families); + + let slant = if style.font_style_italic { + skia_safe::font_style::Slant::Italic + } else { + skia_safe::font_style::Slant::Upright + }; + let weight = skia_safe::font_style::Weight::from(style.font_weight as i32); + let width = skia_safe::font_style::Width::from(style.font_width as i32); + ts.set_font_style(skia_safe::FontStyle::new(weight, width, slant)); + + { + use skia_safe::font_arguments::variation_position::Coordinate; + let mut coords: Vec = Vec::new(); + + for v in &style.font_variations { + let bytes = v.axis.as_bytes(); + let tag = skia_safe::FourByteTag::from(( + *bytes.first().unwrap_or(&b' ') as char, + *bytes.get(1).unwrap_or(&b' ') as char, + *bytes.get(2).unwrap_or(&b' ') as char, + *bytes.get(3).unwrap_or(&b' ') as char, + )); + coords.push(Coordinate { axis: tag, value: v.value }); + } + + coords.push(Coordinate { + axis: skia_safe::FourByteTag::from(('w', 'g', 'h', 't')), + value: style.font_weight as f32, + }); + + if (style.font_width - 100.0).abs() > f32::EPSILON { + coords.push(Coordinate { + axis: skia_safe::FourByteTag::from(('w', 'd', 't', 'h')), + value: style.font_width, + }); + } + + match style.font_optical_sizing { + at_mod::FontOpticalSizing::Auto => { + coords.push(Coordinate { + axis: skia_safe::FourByteTag::from(('o', 'p', 's', 'z')), + value: style.font_size, + }); + } + at_mod::FontOpticalSizing::Fixed(v) => { + coords.push(Coordinate { + axis: skia_safe::FourByteTag::from(('o', 'p', 's', 'z')), + value: v, + }); + } + at_mod::FontOpticalSizing::None => {} + } + + let variation_position = skia_safe::font_arguments::VariationPosition { + coordinates: &coords, + }; + let font_args = + skia_safe::FontArguments::new().set_variation_design_position(variation_position); + ts.set_font_arguments(&font_args); + } + + match &style.fill { + at_mod::TextFill::Solid(rgba) => { + ts.set_color(Color::from_argb( + (rgba.a * 255.0) as u8, + (rgba.r * 255.0) as u8, + (rgba.g * 255.0) as u8, + (rgba.b * 255.0) as u8, + )); + } + } + + match style.letter_spacing { + at_mod::TextDimension::Fixed(v) => { + ts.set_letter_spacing(v); + } + _ => {} + } + + ts.add_font_feature("kern", if style.font_kerning { 1 } else { 0 }); + + for feat in &style.font_features { + ts.add_font_feature(feat.tag.clone(), if feat.value { 1 } else { 0 }); + } + + let mut deco = TextDecoration::NO_DECORATION; + match style.text_decoration_line { + at_mod::TextDecorationLine::Underline => { + deco = TextDecoration::UNDERLINE; + } + at_mod::TextDecorationLine::LineThrough => { + deco = TextDecoration::LINE_THROUGH; + } + at_mod::TextDecorationLine::Overline => { + deco = TextDecoration::OVERLINE; + } + at_mod::TextDecorationLine::None => {} + } + ts.set_decoration_type(deco); + + ts +} + +// --------------------------------------------------------------------------- +// Incremental UTF-16 → UTF-8 offset conversion +// --------------------------------------------------------------------------- + +/// Advance a running `(utf16_count, byte_offset)` cursor through `text` to +/// the given UTF-16 target, returning the corresponding UTF-8 byte offset. +/// +/// The caller must ensure that calls are made with **monotonically +/// non-decreasing** `target_u16` values so the iterator only moves forward. +/// This makes each call amortized O(1) instead of O(n). +fn incremental_u16_to_u8( + target_u16: usize, + text: &str, + run_u16: &mut usize, + run_byte: &mut usize, + iter: &mut std::iter::Peekable, +) -> usize { + while *run_u16 < target_u16 { + if let Some(&(byte_idx, ch)) = iter.peek() { + *run_u16 += ch.len_utf16(); + *run_byte = byte_idx + ch.len_utf8(); + iter.next(); + } else { + break; + } + } + (*run_byte).min(text.len()) +} + /// Horizontal text alignment. #[derive(Clone, Debug, PartialEq, Default)] pub enum TextAlign { @@ -740,9 +892,18 @@ impl SkiaLayoutEngine { let mut result = Vec::with_capacity(skia.len()); let mut prev_end: usize = 0; + // Incremental UTF-16 → UTF-8 tracking within this block slice. + let mut run_u16: usize = 0; + let mut run_byte: usize = 0; + let mut char_iter = slice.char_indices().peekable(); + for lm in &skia { - let local_start = utf16_to_utf8_offset(slice, lm.start_index); - let local_end = utf16_to_utf8_offset(slice, lm.end_including_newline).min(slice.len()); + let local_start = incremental_u16_to_u8( + lm.start_index, slice, &mut run_u16, &mut run_byte, &mut char_iter, + ); + let local_end = incremental_u16_to_u8( + lm.end_including_newline, slice, &mut run_u16, &mut run_byte, &mut char_iter, + ).min(slice.len()); let local_start = local_start.max(prev_end); let local_end = local_end.max(local_start); @@ -779,15 +940,26 @@ impl SkiaLayoutEngine { } /// Convert line metrics from the legacy single paragraph (attributed path). + /// + /// Uses incremental UTF-16 → UTF-8 offset tracking instead of scanning + /// from byte 0 per line, making this O(n) instead of O(n × L). fn convert_single_para_line_metrics(&self, text: &str) -> Vec { let para = self.paragraph.as_ref().unwrap(); let skia = para.get_line_metrics(); let mut result = Vec::with_capacity(skia.len()); let mut prev_end: usize = 0; + let mut run_u16: usize = 0; + let mut run_byte: usize = 0; + let mut char_iter = text.char_indices().peekable(); + for lm in &skia { - let start = utf16_to_utf8_offset(text, lm.start_index); - let end = utf16_to_utf8_offset(text, lm.end_including_newline).min(text.len()); + let start = incremental_u16_to_u8( + lm.start_index, text, &mut run_u16, &mut run_byte, &mut char_iter, + ); + let end = incremental_u16_to_u8( + lm.end_including_newline, text, &mut run_u16, &mut run_byte, &mut char_iter, + ).min(text.len()); let start = start.max(prev_end); let end = end.max(start); @@ -848,179 +1020,173 @@ impl SkiaLayoutEngine { } // ------------------------------------------------------------------- - // Legacy single-paragraph paths (for attributed text / preedit) + // Attributed text layout (per-block, rich text) // ------------------------------------------------------------------- - /// Build and cache a monolithic paragraph from an [`AttributedText`], - /// pushing per-run styles to `ParagraphBuilder`. This is the rich-text - /// layout path — each run gets its own font weight, slant, decoration, - /// color, etc. + /// Ensure layout is up-to-date for the given [`AttributedText`]. /// - /// NOTE: this populates the legacy `paragraph` field, NOT the per-block - /// architecture. The `wd_text_editor` uses this for rich text rendering. + /// Uses the same per-block architecture as plain text, but pushes + /// per-run styles within each block. This avoids feeding the entire + /// document into a single Skia `Paragraph`. pub fn ensure_layout_attributed( &mut self, at: &crate::attributed_text::AttributedText, ) { - if self.paragraph.is_some() && self.cached_text == at.text() { + if !self.blocks.is_empty() && self.cached_text == at.text() { return; } - self.rebuild_attributed(at); + self.rebuild_blocks_attributed(at); } - fn rebuild_attributed( - &mut self, - at: &crate::attributed_text::AttributedText, - ) { - use crate::attributed_text as at_mod; - + /// Build a Skia `Paragraph` for a block slice with per-run styling + /// from the given runs. Runs are clipped to the block range. + fn build_paragraph_for_attributed_slice( + &self, + text: &str, + block_start: usize, + block_content_end: usize, + runs: &[crate::attributed_text::StyledRun], + ) -> Paragraph { let mut para_style = ParagraphStyle::new(); para_style.set_apply_rounding_hack(false); para_style.set_text_align(self.config.text_align.to_skia()); let mut builder = ParagraphBuilder::new(¶_style, &self.font_collection); + let fallback_families: Vec<&str> = + self.config.font_families.iter().map(|s| s.as_str()).collect(); - let text = at.text(); - let fallback_families: Vec<&str> = self.config.font_families.iter().map(|s| s.as_str()).collect(); - - for run in at.runs() { - let style = &run.style; + // If no runs overlap, use config defaults. + if runs.is_empty() || block_start >= block_content_end { let mut ts = TextStyle::new(); - - // Font size - ts.set_font_size(style.font_size); - - // Font families: use the run's font_family as primary, - // fall back to the config's family list. - let mut families: Vec<&str> = vec![style.font_family.as_str()]; - for f in &fallback_families { - if *f != style.font_family.as_str() { - families.push(f); + ts.set_font_size(self.config.font_size); + let families: Vec<&str> = self.config.font_families.iter().map(|s| s.as_str()).collect(); + ts.set_font_families(&families); + builder.push_style(&ts); + builder.add_text(&text[block_start..block_content_end]); + } else { + for run in runs { + // Clip run to block range. + let run_start = (run.start as usize).max(block_start); + let run_end = (run.end as usize).min(block_content_end); + if run_start >= run_end { + continue; } + + let ts = attr_style_to_skia(&run.style, &fallback_families); + builder.push_style(&ts); + builder.add_text(&text[run_start..run_end]); } - ts.set_font_families(&families); + } + + let mut para = builder.build(); + para.layout(self.layout_width); + para + } + + /// Full rebuild of per-block layout from an [`AttributedText`]. + fn rebuild_blocks_attributed( + &mut self, + at: &crate::attributed_text::AttributedText, + ) { + self.blocks.clear(); + self.cached_line_metrics = None; + self.paragraph = None; + + let text = at.text(); + let mut y_offset: f32 = 0.0; + let mut start = 0usize; - // Font style (for typeface matching in FontCollection) - let slant = if style.font_style_italic { - skia_safe::font_style::Slant::Italic + loop { + let has_newline; + let end = if let Some(pos) = text[start..].find('\n') { + has_newline = true; + start + pos + 1 } else { - skia_safe::font_style::Slant::Upright + has_newline = false; + text.len() }; - let weight = skia_safe::font_style::Weight::from(style.font_weight as i32); - let width = skia_safe::font_style::Width::from(style.font_width as i32); - ts.set_font_style(skia_safe::FontStyle::new(weight, width, slant)); - - // Variable font axes (FontArguments) - { - use skia_safe::font_arguments::variation_position::Coordinate; - - let mut coords: Vec = Vec::new(); - - for v in &style.font_variations { - let bytes = v.axis.as_bytes(); - let tag = skia_safe::FourByteTag::from(( - *bytes.first().unwrap_or(&b' ') as char, - *bytes.get(1).unwrap_or(&b' ') as char, - *bytes.get(2).unwrap_or(&b' ') as char, - *bytes.get(3).unwrap_or(&b' ') as char, - )); - coords.push(Coordinate { axis: tag, value: v.value }); - } - coords.push(Coordinate { - axis: skia_safe::FourByteTag::from(('w', 'g', 'h', 't')), - value: style.font_weight as f32, - }); + let content_end = if has_newline { end - 1 } else { end }; - if (style.font_width - 100.0).abs() > f32::EPSILON { - coords.push(Coordinate { - axis: skia_safe::FourByteTag::from(('w', 'd', 't', 'h')), - value: style.font_width, - }); - } + // Get runs overlapping this block. + let runs = at.runs_in_range(start as u32, content_end as u32); + let para = self.build_paragraph_for_attributed_slice(text, start, content_end, runs); - match style.font_optical_sizing { - at_mod::FontOpticalSizing::Auto => { - coords.push(Coordinate { - axis: skia_safe::FourByteTag::from(('o', 'p', 's', 'z')), - value: style.font_size, - }); - } - at_mod::FontOpticalSizing::Fixed(v) => { - coords.push(Coordinate { - axis: skia_safe::FourByteTag::from(('o', 'p', 's', 'z')), - value: v, - }); - } - at_mod::FontOpticalSizing::None => {} - } + let content_slice = &text[start..content_end]; + let mut stored_lines = self.convert_block_line_metrics(¶, content_slice, start); - let variation_position = skia_safe::font_arguments::VariationPosition { - coordinates: &coords, + if stored_lines.is_empty() { + let skia_metrics = para.get_line_metrics(); + let (ascent, descent, baseline) = if let Some(lm) = skia_metrics.first() { + (lm.ascent as f32, lm.descent as f32, lm.baseline as f32) + } else { + (self.font_size, self.font_size * 0.2, self.font_size) }; - let font_args = skia_safe::FontArguments::new() - .set_variation_design_position(variation_position); - ts.set_font_arguments(&font_args); + stored_lines.push(LineMetrics { + start_index: start, + end_index: start, + baseline, + ascent, + descent, + }); } - // Color / fill - match &style.fill { - at_mod::TextFill::Solid(rgba) => { - ts.set_color(Color::from_argb( - (rgba.a * 255.0) as u8, - (rgba.r * 255.0) as u8, - (rgba.g * 255.0) as u8, - (rgba.b * 255.0) as u8, - )); + if has_newline { + if let Some(last) = stored_lines.last_mut() { + last.end_index = end; } } - // Letter spacing - match style.letter_spacing { - at_mod::TextDimension::Fixed(v) => { ts.set_letter_spacing(v); } - _ => {} - } + let height: f32 = if let Some(last) = stored_lines.last() { + last.baseline + last.descent + } else { + self.font_size * 1.2 + }; - // Kerning - ts.add_font_feature("kern", if style.font_kerning { 1 } else { 0 }); + self.blocks.push(ParaBlock { + byte_start: start, + byte_end: end, + paragraph: para, + y_offset, + height, + line_metrics: stored_lines, + }); - // User font features - for feat in &style.font_features { - ts.add_font_feature(feat.tag.clone(), if feat.value { 1 } else { 0 }); - } + y_offset += height; + start = end; - // Decoration - let mut deco = TextDecoration::NO_DECORATION; - match style.text_decoration_line { - at_mod::TextDecorationLine::Underline => { - deco = TextDecoration::UNDERLINE; - } - at_mod::TextDecorationLine::LineThrough => { - deco = TextDecoration::LINE_THROUGH; - } - at_mod::TextDecorationLine::Overline => { - deco = TextDecoration::OVERLINE; - } - at_mod::TextDecorationLine::None => {} + if start >= text.len() { + break; } - ts.set_decoration_type(deco); - - builder.push_style(&ts); + } - let start = run.start as usize; - let end = run.end as usize; - if start < end && end <= text.len() { - builder.add_text(&text[start..end]); + // Trailing newline phantom block. + if text.ends_with('\n') && !text.is_empty() { + if let Some(last_block) = self.blocks.last() { + let last_lm = last_block.line_metrics.last(); + let (ascent, descent) = last_lm + .map(|lm| (lm.ascent, lm.descent)) + .unwrap_or((self.font_size, self.font_size * 0.2)); + let phantom = LineMetrics { + start_index: text.len(), + end_index: text.len(), + baseline: ascent, + ascent, + descent, + }; + let phantom_height = ascent + descent; + self.blocks.push(ParaBlock { + byte_start: text.len(), + byte_end: text.len(), + paragraph: self.build_paragraph_for_slice(""), + y_offset, + height: phantom_height, + line_metrics: vec![phantom], + }); } } - let mut para = builder.build(); - para.layout(self.layout_width); - self.paragraph = Some(para); self.cached_text = text.to_owned(); - // Clear per-block state — the attributed path uses the legacy paragraph. - self.blocks.clear(); - self.cached_line_metrics = None; } /// Invalidate all cached layout so the next call rebuilds. @@ -1088,6 +1254,20 @@ impl SkiaLayoutEngine { block.paragraph.paint(canvas, Point::new(0.0, block.y_offset)); } } + + /// Paint the laid-out paragraph at the given origin offset. + pub fn paint_paragraph_at( + &self, + canvas: &skia_safe::Canvas, + _text: &str, + origin: Point, + ) { + for block in &self.blocks { + block + .paragraph + .paint(canvas, Point::new(origin.x, origin.y + block.y_offset)); + } + } } impl TextLayoutEngine for SkiaLayoutEngine { From 080c7ee94ce0a25ddd292a02d19de21ea612a650 Mon Sep 17 00:00:00 2001 From: Universe Date: Thu, 5 Mar 2026 16:27:58 +0900 Subject: [PATCH 12/15] refactor: enhance text editing command handling and layout synchronization - Introduced `EditDelta` struct to encapsulate text mutation details, optimizing the `apply_command_mut` function for better performance and clarity. - Updated the `TextEditor` to utilize the new delta structure for efficient content synchronization after edits, replacing the previous text diffing approach. - Enhanced the `GenericEditHistory` with merge detection to avoid unnecessary snapshot creation, improving history management efficiency. - Refined layout handling in the `SkiaLayoutEngine` to ensure accurate updates for attributed text, preventing layout inconsistencies during rich text editing. This update aims to streamline command processing and layout synchronization, enhancing the overall performance and user experience in the grida-text-edit project. --- .../examples/wd_text_editor.rs | 115 +++++------ crates/grida-text-edit/src/history.rs | 26 +++ crates/grida-text-edit/src/lib.rs | 151 +++++++++++++-- crates/grida-text-edit/src/skia_layout.rs | 183 ++---------------- 4 files changed, 225 insertions(+), 250 deletions(-) diff --git a/crates/grida-text-edit/examples/wd_text_editor.rs b/crates/grida-text-edit/examples/wd_text_editor.rs index ce18ce01b8..03d061a8e3 100644 --- a/crates/grida-text-edit/examples/wd_text_editor.rs +++ b/crates/grida-text-edit/examples/wd_text_editor.rs @@ -133,7 +133,7 @@ use winit::{ use grida_text_edit::{ apply_command_mut, utf8_to_utf16_offset, - CaretRect, EditKind, EditingCommand, GenericEditHistory, + CaretRect, EditDelta, EditKind, EditingCommand, GenericEditHistory, SkiaLayoutEngine, TextEditorState, TextLayoutEngine, attributed_text::{ AttributedText, TextStyle as AttrTextStyle, @@ -440,6 +440,7 @@ impl TextEditor { self.caret_style_override = None; self.cached_caret_rect = None; self.layout.invalidate(); + self.layout.ensure_layout_attributed(&self.content); self.ensure_cursor_visible(); } @@ -448,100 +449,67 @@ impl TextEditor { // ----------------------------------------------------------------------- fn apply(&mut self, cmd: EditingCommand) { - let is_edit = cmd.edit_kind().is_some(); if let Some(kind) = cmd.edit_kind() { - self.history.push(&self.snapshot(), kind); + if !self.history.would_merge(kind) { + self.history.push(&self.snapshot(), kind); + } else { + self.history.push_merge(kind); + } } let old_cursor = self.state.cursor; - // Only clone the old text when the command may mutate it. - let old_text = if is_edit { Some(self.state.text.clone()) } else { None }; - apply_command_mut(&mut self.state, cmd, &mut self.layout); + let delta = apply_command_mut(&mut self.state, cmd, &mut self.layout); self.invalidate_caret_cache(); - if let Some(old) = old_text { - self.sync_content_after_edit(&old, old_cursor); + if let Some(d) = delta { + self.sync_content_with_delta(&d, old_cursor); } else if self.state.cursor != old_cursor { - // Pure cursor movement — clear caret style override. self.caret_style_override = None; } self.reset_blink(); + // Ensure attributed layout is up-to-date before querying caret geometry, + // otherwise ensure_cursor_visible triggers the plain-text rebuild path. + self.layout.ensure_layout_attributed(&self.content); self.ensure_cursor_visible(); } fn apply_with_kind(&mut self, cmd: EditingCommand, kind: EditKind) { - self.history.push(&self.snapshot(), kind); + if !self.history.would_merge(kind) { + self.history.push(&self.snapshot(), kind); + } else { + self.history.push_merge(kind); + } let old_cursor = self.state.cursor; - let old_text = self.state.text.clone(); - apply_command_mut(&mut self.state, cmd, &mut self.layout); + let delta = apply_command_mut(&mut self.state, cmd, &mut self.layout); self.invalidate_caret_cache(); - self.sync_content_after_edit(&old_text, old_cursor); + if let Some(d) = delta { + self.sync_content_with_delta(&d, old_cursor); + } self.reset_blink(); + self.layout.ensure_layout_attributed(&self.content); self.ensure_cursor_visible(); } - /// After apply_command changes `self.state.text`, diff and update - /// `self.content` (the AttributedText) to keep runs in sync. - fn sync_content_after_edit(&mut self, old_text: &str, old_cursor: usize) { - let new_text = &self.state.text; - if old_text == new_text { - // Pure cursor movement — clear caret style override. - if self.state.cursor != old_cursor { - self.caret_style_override = None; - } - return; - } - - // Determine the effective style for inserted text. + /// Update the `AttributedText` content and incremental layout using + /// the edit delta returned by `apply_command_mut` — O(1) offset lookup + /// instead of O(n) text diff. + fn sync_content_with_delta(&mut self, delta: &EditDelta, old_cursor: usize) { let insert_style = self.caret_style_override.clone() .unwrap_or_else(|| self.content.caret_style_at(old_cursor as u32).clone()); - let old_len = old_text.len(); - let new_len = new_text.len(); + let old_end = delta.offset + delta.old_len; + let new_end = delta.offset + delta.new_len; - // Find common prefix length (byte level, snap to char boundaries). - let mut prefix = 0; - for (a, b) in old_text.bytes().zip(new_text.bytes()) { - if a != b { break; } - prefix += 1; + if delta.old_len > 0 { + self.content.delete(delta.offset, old_end); } - while prefix > 0 && !old_text.is_char_boundary(prefix) { - prefix -= 1; - } - while prefix > 0 && prefix < new_len && !new_text.is_char_boundary(prefix) { - prefix -= 1; - } - - // Find common suffix length. - let mut suffix = 0; - let max_suffix = old_len.min(new_len) - prefix; - for i in 0..max_suffix { - let oi = old_len - 1 - i; - let ni = new_len - 1 - i; - if old_text.as_bytes()[oi] != new_text.as_bytes()[ni] { break; } - suffix += 1; - } - while suffix > 0 && !old_text.is_char_boundary(old_len - suffix) { - suffix -= 1; - } - while suffix > 0 && !new_text.is_char_boundary(new_len - suffix) { - suffix -= 1; + if delta.new_len > 0 { + let inserted = &self.state.text[delta.offset..new_end]; + self.content.insert_with_style(delta.offset, inserted, insert_style); } - let old_end = old_len - suffix; - let new_end = new_len - suffix; - - if old_end > prefix { - self.content.delete(prefix, old_end); - } - if new_end > prefix { - let inserted = &new_text[prefix..new_end]; - self.content.insert_with_style(prefix, inserted, insert_style); - } - - // Incrementally update the per-block layout so only the affected - // paragraph is re-shaped (instead of rebuilding all blocks). - let removed_bytes = old_end - prefix; - let inserted_bytes = new_end - prefix; - self.layout.notify_edit(new_text, prefix, removed_bytes, inserted_bytes); + // Invalidate the per-block layout so the next ensure_layout_attributed + // call rebuilds the affected blocks. (notify_edit is designed for the + // plain-text path and doesn't handle per-run attributed styles.) + self.layout.invalidate(); // After any text edit, clear the override. self.caret_style_override = None; @@ -947,6 +915,12 @@ impl TextEditor { } /// Adjust scroll so the caret is within the visible viewport. + /// + /// **Ordering constraint:** `ensure_layout_attributed` must be called + /// before this method when editing rich text. `caret_rect()` internally + /// triggers `ensure_layout` (the plain-text path); if the attributed + /// blocks haven't been built yet, `ensure_layout` would rebuild them + /// with uniform styling, losing all formatting. fn ensure_cursor_visible(&mut self) { let cr = self.caret_rect(); let viewport_height = self.layout.layout_height; @@ -1157,7 +1131,6 @@ impl TextEditor { } // Text (per-block painting with origin offset) - self.layout.ensure_layout(&self.state.text); self.layout.paint_paragraph_at(canvas, &self.state.text, origin); // Cursor diff --git a/crates/grida-text-edit/src/history.rs b/crates/grida-text-edit/src/history.rs index c36cc4a462..f8870d5a33 100644 --- a/crates/grida-text-edit/src/history.rs +++ b/crates/grida-text-edit/src/history.rs @@ -81,6 +81,32 @@ impl GenericEditHistory { self.redo_stack.clear(); } + /// Returns `true` if a `push` with this `kind` would merge into the + /// existing top-of-stack entry (i.e. the snapshot would be discarded). + /// + /// Use this to avoid expensive snapshot creation when the result would + /// be thrown away anyway. + pub fn would_merge(&self, kind: EditKind) -> bool { + if kind.is_mergeable() { + if let Some(top) = self.undo_stack.last() { + return top.kind == kind && top.timestamp.elapsed() < self.merge_timeout; + } + } + false + } + + /// Record a merge into the top-of-stack entry without creating a snapshot. + /// + /// Call this when `would_merge(kind)` returned `true` to update the + /// merge timestamp and clear the redo stack without allocating. + pub fn push_merge(&mut self, kind: EditKind) { + debug_assert!(self.would_merge(kind), "push_merge called when would_merge is false"); + if let Some(top) = self.undo_stack.last_mut() { + top.timestamp = Instant::now(); + } + self.redo_stack.clear(); + } + /// Record the state **before** an edit. /// /// If the top of the undo stack has the same mergeable kind and the diff --git a/crates/grida-text-edit/src/lib.rs b/crates/grida-text-edit/src/lib.rs index ff2ddbf488..44b1c31d8b 100644 --- a/crates/grida-text-edit/src/lib.rs +++ b/crates/grida-text-edit/src/lib.rs @@ -150,8 +150,16 @@ pub fn ceil_char_boundary(text: &str, pos: usize) -> usize { // --------------------------------------------------------------------------- // Word segment boundary (UAX #29) +// +// Uses a local window around `offset` instead of scanning from byte 0, +// making it O(1) amortized regardless of position in the document. // --------------------------------------------------------------------------- +/// Size of the lookback/lookahead window for word boundary scanning. +/// UAX#29 word segmentation is context-dependent but the context is local; +/// 256 bytes covers even long compound words in CJK/Thai. +const WORD_WINDOW: usize = 256; + /// Find the UAX #29 word segment containing `offset`. /// /// Returns `(start, end)` — the byte range of the segment. This is the @@ -160,15 +168,23 @@ pub fn ceil_char_boundary(text: &str, pos: usize) -> usize { /// boundaries without requiring a layout engine. pub fn word_segment_at(text: &str, offset: usize) -> (usize, usize) { let offset = offset.min(text.len()); + + // Scan a local window around offset. + let window_start = floor_char_boundary(text, offset.saturating_sub(WORD_WINDOW)); + let window_end = ceil_char_boundary(text, (offset + WORD_WINDOW).min(text.len())); + let slice = &text[window_start..window_end]; + let local_offset = offset - window_start; + let mut last_start = 0usize; - for (byte_idx, segment) in text.split_word_bound_indices() { + for (byte_idx, segment) in slice.split_word_bound_indices() { let end = byte_idx + segment.len(); - if byte_idx <= offset && offset < end { - return (byte_idx, end); + if byte_idx <= local_offset && local_offset < end { + return (window_start + byte_idx, window_start + end); } last_start = byte_idx; } - (last_start, text.len()) + // offset is at or past the last segment boundary in the window. + (window_start + last_start, window_end) } /// Find the start of the previous word segment from `pos`. @@ -344,67 +360,126 @@ impl EditingCommand { // apply_command // --------------------------------------------------------------------------- +/// Describes a text mutation performed by [`apply_command_mut`]. +/// +/// * `offset` — byte offset in the **old** text where the edit starts. +/// * `old_len` — number of bytes removed (0 for pure insert). +/// * `new_len` — number of bytes inserted (0 for pure delete). +/// +/// Cursor-only commands return `None` instead. +#[derive(Clone, Debug, PartialEq)] +pub struct EditDelta { + pub offset: usize, + pub old_len: usize, + pub new_len: usize, +} + /// Apply a single editing command to `state` **in place**. /// /// Layout-dependent commands call into `layout`; pure commands do not. /// Cursor-only commands are O(1) — no text is copied. /// +/// Returns `Some(EditDelta)` when the text buffer was mutated, `None` for +/// cursor-only commands. The caller can use this to update dependent data +/// structures (e.g. attributed text runs, incremental layout) without +/// re-diffing the text. +/// /// For backward compatibility, [`apply_command`] is provided as a thin /// wrapper that clones the state before delegating here. pub fn apply_command_mut( s: &mut TextEditorState, command: EditingCommand, layout: &mut dyn TextLayoutEngine, -) { - match command { +) -> Option { + let delta = match command { EditingCommand::Insert(text) => { + let sel = s.selection_range(); let pos = delete_selection_in_place(s); + let sel_removed = sel.map_or(0, |(lo, hi)| hi - lo); let normalized = normalize_newlines(&text); + let inserted = normalized.len(); s.text.insert_str(pos, &normalized); - s.cursor = pos + normalized.len(); + s.cursor = pos + inserted; s.anchor = None; + Some(EditDelta { offset: pos, old_len: sel_removed, new_len: inserted }) } EditingCommand::Backspace => { if s.has_selection() { + let (lo, hi) = s.selection_range().unwrap(); + let removed = hi - lo; s.cursor = delete_selection_in_place(s); + s.anchor = None; + Some(EditDelta { offset: lo, old_len: removed, new_len: 0 }) } else if s.cursor > 0 { let prev = prev_grapheme_boundary(&s.text, s.cursor); + let removed = s.cursor - prev; s.text.drain(prev..s.cursor); s.cursor = prev; + s.anchor = None; + Some(EditDelta { offset: prev, old_len: removed, new_len: 0 }) + } else { + s.anchor = None; + None } - s.anchor = None; } EditingCommand::BackspaceWord => { if s.has_selection() { + let (lo, hi) = s.selection_range().unwrap(); + let removed = hi - lo; s.cursor = delete_selection_in_place(s); + s.anchor = None; + Some(EditDelta { offset: lo, old_len: removed, new_len: 0 }) } else if s.cursor > 0 { let target = prev_word_segment_start(&s.text, s.cursor); + let removed = s.cursor - target; s.text.drain(target..s.cursor); s.cursor = target; + s.anchor = None; + Some(EditDelta { offset: target, old_len: removed, new_len: 0 }) + } else { + s.anchor = None; + None } - s.anchor = None; } EditingCommand::Delete => { if s.has_selection() { + let (lo, hi) = s.selection_range().unwrap(); + let removed = hi - lo; s.cursor = delete_selection_in_place(s); + s.anchor = None; + Some(EditDelta { offset: lo, old_len: removed, new_len: 0 }) } else if s.cursor < s.text.len() { let next = next_grapheme_boundary(&s.text, s.cursor); + let removed = next - s.cursor; s.text.drain(s.cursor..next); + s.anchor = None; + Some(EditDelta { offset: s.cursor, old_len: removed, new_len: 0 }) + } else { + s.anchor = None; + None } - s.anchor = None; } EditingCommand::DeleteWord => { if s.has_selection() { + let (lo, hi) = s.selection_range().unwrap(); + let removed = hi - lo; s.cursor = delete_selection_in_place(s); + s.anchor = None; + Some(EditDelta { offset: lo, old_len: removed, new_len: 0 }) } else if s.cursor < s.text.len() { let target = next_word_segment_end(&s.text, s.cursor); + let removed = target - s.cursor; s.text.drain(s.cursor..target); + s.anchor = None; + Some(EditDelta { offset: s.cursor, old_len: removed, new_len: 0 }) + } else { + s.anchor = None; + None } - s.anchor = None; } EditingCommand::MoveLeft { extend } => { @@ -417,6 +492,7 @@ pub fn apply_command_mut( s.cursor = prev_grapheme_boundary(&s.text, s.cursor); clear_anchor_if_not_extending(s, extend); } + None } EditingCommand::MoveRight { extend } => { @@ -429,58 +505,80 @@ pub fn apply_command_mut( s.cursor = next_grapheme_boundary(&s.text, s.cursor); clear_anchor_if_not_extending(s, extend); } + None } EditingCommand::MoveDocStart { extend } => { set_anchor_if_extending(s, extend); s.cursor = 0; clear_anchor_if_not_extending(s, extend); + None } EditingCommand::MoveDocEnd { extend } => { set_anchor_if_extending(s, extend); s.cursor = s.text.len(); clear_anchor_if_not_extending(s, extend); + None } EditingCommand::SelectAll => { s.anchor = Some(0); s.cursor = s.text.len(); + None } EditingCommand::SetCursorPos { pos, anchor } => { s.cursor = snap_grapheme_boundary(&s.text, pos); s.anchor = anchor.map(|a| snap_grapheme_boundary(&s.text, a)); + None } EditingCommand::BackspaceLine => { - if s.has_selection() { + let d = if s.has_selection() { + let (lo, hi) = s.selection_range().unwrap(); + let removed = hi - lo; s.cursor = delete_selection_in_place(s); + Some(EditDelta { offset: lo, old_len: removed, new_len: 0 }) } else { let metrics = layout.line_metrics(&s.text); if !metrics.is_empty() { let line_idx = line_index_for_offset_utf8(&metrics, s.cursor); let line_start = metrics[line_idx].start_index; if line_start < s.cursor { + let removed = s.cursor - line_start; s.text.drain(line_start..s.cursor); s.cursor = line_start; + Some(EditDelta { offset: line_start, old_len: removed, new_len: 0 }) } else if s.cursor > 0 { let prev = prev_grapheme_boundary(&s.text, s.cursor); + let removed = s.cursor - prev; s.text.drain(prev..s.cursor); s.cursor = prev; + Some(EditDelta { offset: prev, old_len: removed, new_len: 0 }) + } else { + None } } else if s.cursor > 0 { let prev = prev_grapheme_boundary(&s.text, s.cursor); + let removed = s.cursor - prev; s.text.drain(prev..s.cursor); s.cursor = prev; + Some(EditDelta { offset: prev, old_len: removed, new_len: 0 }) + } else { + None } - } + }; s.anchor = None; + d } EditingCommand::DeleteLine => { - if s.has_selection() { + let d = if s.has_selection() { + let (lo, hi) = s.selection_range().unwrap(); + let removed = hi - lo; s.cursor = delete_selection_in_place(s); + Some(EditDelta { offset: lo, old_len: removed, new_len: 0 }) } else { let metrics = layout.line_metrics(&s.text); if !metrics.is_empty() { @@ -491,11 +589,18 @@ pub fn apply_command_mut( line_end = prev_grapheme_boundary(&s.text, line_end); } if s.cursor < line_end { + let removed = line_end - s.cursor; s.text.drain(s.cursor..line_end); + Some(EditDelta { offset: s.cursor, old_len: removed, new_len: 0 }) + } else { + None } + } else { + None } - } + }; s.anchor = None; + d } EditingCommand::MoveUp { extend } => { @@ -519,6 +624,7 @@ pub fn apply_command_mut( } } clear_anchor_if_not_extending(s, extend); + None } EditingCommand::MoveDown { extend } => { @@ -542,6 +648,7 @@ pub fn apply_command_mut( } } clear_anchor_if_not_extending(s, extend); + None } EditingCommand::MoveHome { extend } => { @@ -554,6 +661,7 @@ pub fn apply_command_mut( s.cursor = 0; } clear_anchor_if_not_extending(s, extend); + None } EditingCommand::MoveEnd { extend } => { @@ -571,6 +679,7 @@ pub fn apply_command_mut( s.cursor = s.text.len(); } clear_anchor_if_not_extending(s, extend); + None } EditingCommand::MovePageUp { extend } => { @@ -596,6 +705,7 @@ pub fn apply_command_mut( } } clear_anchor_if_not_extending(s, extend); + None } EditingCommand::MovePageDown { extend } => { @@ -622,6 +732,7 @@ pub fn apply_command_mut( } } clear_anchor_if_not_extending(s, extend); + None } EditingCommand::MoveWordLeft { extend } => { @@ -647,6 +758,7 @@ pub fn apply_command_mut( } s.cursor = pos; clear_anchor_if_not_extending(s, extend); + None } EditingCommand::MoveWordRight { extend } => { @@ -672,12 +784,14 @@ pub fn apply_command_mut( } s.cursor = pos.min(s.text.len()); clear_anchor_if_not_extending(s, extend); + None } EditingCommand::MoveTo { x, y } => { let pos = layout.position_at_point(&s.text, x, y); s.cursor = pos; s.anchor = None; + None } EditingCommand::ExtendTo { x, y } => { @@ -685,6 +799,7 @@ pub fn apply_command_mut( s.anchor = Some(s.cursor); } s.cursor = layout.position_at_point(&s.text, x, y); + None } EditingCommand::SelectWordAt { x, y } => { @@ -692,6 +807,7 @@ pub fn apply_command_mut( let (start, end) = layout.word_boundary_at(&s.text, pos); s.anchor = Some(start); s.cursor = end; + None } EditingCommand::SelectLineAt { x, y } => { @@ -706,8 +822,9 @@ pub fn apply_command_mut( s.anchor = Some(lm.start_index); s.cursor = lm.end_index.min(s.text.len()); } + None } - } + }; debug_assert!( s.cursor <= s.text.len() && s.text.is_char_boundary(s.cursor), @@ -721,6 +838,8 @@ pub fn apply_command_mut( s.anchor, s.text.len(), ); + + delta } /// Apply a single editing command to `state`, returning the new state. diff --git a/crates/grida-text-edit/src/skia_layout.rs b/crates/grida-text-edit/src/skia_layout.rs index 33675ffe6c..36db496711 100644 --- a/crates/grida-text-edit/src/skia_layout.rs +++ b/crates/grida-text-edit/src/skia_layout.rs @@ -269,9 +269,9 @@ struct ParaBlock { /// No GPU or window required — pure CPU text layout. pub struct SkiaLayoutEngine { pub font_collection: FontCollection, - /// Legacy single-paragraph field kept for external access (e.g. `wd_text_editor` - /// preedit mode which builds its own paragraph). **Not used** by the - /// per-block layout path. + /// **Preedit-only.** Temporary single-paragraph used by the host for + /// inline IME composition rendering. Not used by any layout or editing + /// path — all text layout goes through the per-block architecture. pub paragraph: Option, pub layout_width: f32, pub layout_height: f32, @@ -327,28 +327,19 @@ impl SkiaLayoutEngine { /// Ensure layout is up-to-date for `text`. /// - /// If an attributed (rich text) paragraph already exists for this text, - /// it is preserved — the per-block path is only used when no attributed - /// paragraph is available. + /// Ensure per-block layout is up-to-date for the given plain text. + /// + /// For attributed (rich text) layout, use [`ensure_layout_attributed`] + /// instead — it must be called **before** any method that internally + /// calls `ensure_layout` (e.g. `caret_rect_at`, `line_metrics`) to + /// prevent the plain-text builder from overwriting styled blocks. pub fn ensure_layout(&mut self, text: &str) { - // If per-block layout is already current, nothing to do. if !self.blocks.is_empty() && self.cached_text == text { return; } - // If the attributed (single-paragraph) layout is current, skip - // the per-block rebuild — callers will use the legacy paragraph. - if self.paragraph.is_some() && self.cached_text == text { - return; - } self.rebuild_blocks(text); } - /// Returns true when the layout is backed by a single attributed - /// paragraph (rich text path) rather than per-block layout. - fn is_attributed_layout(&self) -> bool { - self.paragraph.is_some() && self.blocks.is_empty() - } - /// Full rebuild: split `text` on `\n` and lay out each block. fn rebuild_blocks(&mut self, text: &str) { self.blocks.clear(); @@ -478,6 +469,11 @@ impl SkiaLayoutEngine { /// - There is no existing block layout (first call or after `invalidate`). /// - The edit spans across block boundaries in ways that change the /// paragraph structure (inserting/removing `\n`). + /// + /// **Plain-text only.** This method rebuilds affected blocks using + /// `build_paragraph_for_slice` which applies uniform styling from + /// `TextConfig`. For attributed (rich text) layout, call `invalidate()` + /// instead and let `ensure_layout_attributed` do a full rebuild. pub fn notify_edit( &mut self, text: &str, @@ -485,9 +481,7 @@ impl SkiaLayoutEngine { old_len: usize, new_len: usize, ) { - // If we are in attributed (single-paragraph) mode, or have no blocks, - // fall back to full rebuild. - if self.blocks.is_empty() || self.paragraph.is_some() { + if self.blocks.is_empty() { self.rebuild_blocks(text); return; } @@ -939,65 +933,6 @@ impl SkiaLayoutEngine { result } - /// Convert line metrics from the legacy single paragraph (attributed path). - /// - /// Uses incremental UTF-16 → UTF-8 offset tracking instead of scanning - /// from byte 0 per line, making this O(n) instead of O(n × L). - fn convert_single_para_line_metrics(&self, text: &str) -> Vec { - let para = self.paragraph.as_ref().unwrap(); - let skia = para.get_line_metrics(); - let mut result = Vec::with_capacity(skia.len()); - let mut prev_end: usize = 0; - - let mut run_u16: usize = 0; - let mut run_byte: usize = 0; - let mut char_iter = text.char_indices().peekable(); - - for lm in &skia { - let start = incremental_u16_to_u8( - lm.start_index, text, &mut run_u16, &mut run_byte, &mut char_iter, - ); - let end = incremental_u16_to_u8( - lm.end_including_newline, text, &mut run_u16, &mut run_byte, &mut char_iter, - ).min(text.len()); - - let start = start.max(prev_end); - let end = end.max(start); - - result.push(LineMetrics { - start_index: start, - end_index: end, - baseline: lm.baseline as f32, - ascent: lm.ascent as f32, - descent: lm.descent as f32, - }); - prev_end = end; - } - - if !text.is_empty() && text.ends_with('\n') { - let last_end = result.last().map_or(0, |lm| lm.end_index); - if last_end < text.len() - || result - .last() - .map_or(true, |lm| lm.start_index < lm.end_index) - { - if last_end <= text.len() { - let last = result.last().unwrap(); - let line_height = last.ascent + last.descent; - result.push(LineMetrics { - start_index: text.len(), - end_index: text.len(), - baseline: last.baseline + line_height, - ascent: last.ascent, - descent: last.descent, - }); - } - } - } - - result - } - /// Find the block index that contains `byte_offset` in the full text. /// /// Uses binary search on `byte_end` (monotonically non-decreasing). @@ -1190,10 +1125,13 @@ impl SkiaLayoutEngine { } /// Invalidate all cached layout so the next call rebuilds. - /// Call this after modifying `font_collection` externally. + /// + /// After calling this, the next `ensure_layout` or + /// `ensure_layout_attributed` will do a full per-block rebuild. pub fn invalidate(&mut self) { self.paragraph = None; self.blocks.clear(); + self.cached_text.clear(); self.cached_line_metrics = None; } @@ -1278,12 +1216,7 @@ impl TextLayoutEngine for SkiaLayoutEngine { return cached.clone(); } - let result = if self.is_attributed_layout() { - // Attributed (single-paragraph) path: convert from the legacy paragraph. - self.convert_single_para_line_metrics(text) - } else { - self.flatten_line_metrics() - }; + let result = self.flatten_line_metrics(); self.cached_line_metrics = Some(result.clone()); result } @@ -1291,25 +1224,6 @@ impl TextLayoutEngine for SkiaLayoutEngine { fn position_at_point(&mut self, text: &str, x: f32, y: f32) -> usize { self.ensure_layout(text); - if self.is_attributed_layout() { - // Attributed (single-paragraph) path. - let para = self.paragraph.as_ref().unwrap(); - let metrics = para.get_line_metrics(); - for lm in &metrics { - let top = lm.baseline as f32 - lm.ascent as f32; - let bot = lm.baseline as f32 + lm.descent as f32; - if y >= top - 0.5 && y <= bot + 0.5 { - if lm.end_index.saturating_sub(lm.start_index) <= 1 { - return utf16_to_utf8_offset(text, lm.start_index).min(text.len()); - } - break; - } - } - let pwa = para.get_glyph_position_at_coordinate(Point::new(x, y)); - let raw = utf16_to_utf8_offset(text, pwa.position.max(0) as usize).min(text.len()); - return snap_grapheme_boundary(text, raw); - } - // Per-block path: binary search by y position. if self.blocks.is_empty() { return 0; @@ -1360,19 +1274,7 @@ impl TextLayoutEngine for SkiaLayoutEngine { let x = if offset <= lm.start_index { 0.0 - } else if self.is_attributed_layout() { - // Attributed (single-paragraph) path. - let u16_end = utf8_to_utf16_offset(text, offset); - let cluster_start = prev_grapheme_boundary(text, offset); - let u16_start = utf8_to_utf16_offset(text, cluster_start); - let rects = self.paragraph.as_ref().unwrap().get_rects_for_range( - u16_start..u16_end, - RectHeightStyle::Max, - RectWidthStyle::Tight, - ); - rects.iter().map(|tb| tb.rect.right()).fold(0.0_f32, f32::max) } else { - // Per-block path. let block_idx = self.block_index_for_offset(offset); let block = &self.blocks[block_idx]; let local_offset = offset - block.byte_start; @@ -1399,15 +1301,6 @@ impl TextLayoutEngine for SkiaLayoutEngine { fn word_boundary_at(&mut self, text: &str, offset: usize) -> (usize, usize) { self.ensure_layout(text); - if self.is_attributed_layout() { - let u16_pos = utf8_to_utf16_offset(text, offset) as u32; - let para = self.paragraph.as_ref().unwrap(); - let range = para.get_word_boundary(u16_pos); - let start = utf16_to_utf8_offset(text, range.start as usize); - let end = utf16_to_utf8_offset(text, range.end as usize); - return (start, end); - } - if self.blocks.is_empty() { return (0, 0); } @@ -1437,42 +1330,6 @@ impl TextLayoutEngine for SkiaLayoutEngine { self.ensure_layout(text); - if self.is_attributed_layout() { - // Attributed (single-paragraph) path. - let para = self.paragraph.as_ref().unwrap(); - let u16_lo = utf8_to_utf16_offset(text, start); - let u16_hi = utf8_to_utf16_offset(text, end); - let raw = para.get_rects_for_range( - u16_lo..u16_hi, - skia_safe::textlayout::RectHeightStyle::Max, - skia_safe::textlayout::RectWidthStyle::Tight, - ); - let mut rects: Vec = raw.iter().map(|tb| SelectionRect { - x: tb.rect.left(), - y: tb.rect.top(), - width: (tb.rect.right() - tb.rect.left()).max(0.0), - height: (tb.rect.bottom() - tb.rect.top()).max(0.0), - }).collect(); - - let first_line = line_index_for_offset_utf8(&metrics, start); - let last_line = line_index_for_offset_utf8(&metrics, end.saturating_sub(1).max(start)); - for idx in first_line..=last_line { - let lm = &metrics[idx]; - if !lm.is_empty_line(text) { continue; } - let mid_y = lm.baseline - lm.ascent * 0.5; - let already = rects.iter().any(|r| r.y <= mid_y && mid_y <= r.y + r.height); - if !already { - rects.push(SelectionRect { - x: 0.0, - y: lm.baseline - lm.ascent, - width: self.font_size * 0.5, - height: lm.ascent + lm.descent, - }); - } - } - return rects; - } - // Per-block path: find blocks that overlap the selection range. let mut rects: Vec = Vec::new(); for block in &self.blocks { From b4e4d2ad478f739daceee87488636a46b65103e4 Mon Sep 17 00:00:00 2001 From: Universe Date: Fri, 6 Mar 2026 00:54:31 +0900 Subject: [PATCH 13/15] feat: add text fixture files for ASCII, bidirectional, and CJK text - Introduced new text fixture files: `ascii.txt`, `bidi.txt`, and `cjk.txt` for testing layout and rendering. - `ascii.txt` contains printable ASCII characters and punctuation. - `bidi.txt` includes bidirectional text examples in Arabic and Hebrew, along with embedded LTR/RTL content. - `cjk.txt` features Chinese, Japanese, and Korean text samples, addressing layout and rendering requirements for CJK scripts. - Updated README.md to document the new text fixtures and their purposes. This update enhances the testing capabilities for diverse text layouts in the grida-text-edit project. --- fixtures/text/README.md | 3 +++ fixtures/text/ascii.txt | 9 +++++++++ fixtures/text/bidi.txt | 23 +++++++++++++++++++++++ fixtures/text/cjk.txt | 16 ++++++++++++++++ 4 files changed, 51 insertions(+) create mode 100644 fixtures/text/ascii.txt create mode 100644 fixtures/text/bidi.txt create mode 100644 fixtures/text/cjk.txt diff --git a/fixtures/text/README.md b/fixtures/text/README.md index 569415a74e..8cb94f8bcd 100644 --- a/fixtures/text/README.md +++ b/fixtures/text/README.md @@ -8,6 +8,9 @@ Plain text samples used for layout, editing, and rendering tests. Header and foo | file | source | description | url | | ---------- | ----------------- | -------------------------------------------------------- | ---------------------------------------------------- | +| ascii.txt | — | ASCII-only text (printable chars, punctuation) | — | +| bidi.txt | — | Bidirectional text (Arabic, Hebrew, embedded LTR/RTL) | — | +| cjk.txt | — | CJK text (Chinese, Japanese, Korean, mixed) | — | | hamlet.txt | Project Gutenberg | William Shakespeare, _Hamlet_ (full play, public domain) | https://www.gutenberg.org/cache/epub/1524/pg1524.txt | | lorem.txt | — | Lorem ipsum placeholder text | — | diff --git a/fixtures/text/ascii.txt b/fixtures/text/ascii.txt new file mode 100644 index 0000000000..72c3c8723a --- /dev/null +++ b/fixtures/text/ascii.txt @@ -0,0 +1,9 @@ +The quick brown fox jumps over the lazy dog. +THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. +0123456789 + +Punctuation: ! " # $ % & ' ( ) * + , - . / : ; < = > ? @ [ \ ] ^ _ ` { | } ~ + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +ASCII range: 32-126 (printable) diff --git a/fixtures/text/bidi.txt b/fixtures/text/bidi.txt new file mode 100644 index 0000000000..fb45564871 --- /dev/null +++ b/fixtures/text/bidi.txt @@ -0,0 +1,23 @@ +Bidirectional (bidi) text fixtures for RTL/LTR layout tests. + +Arabic (RTL): +مرحبا بالعالم! هذا نص عربي للاختبار. العربية تكتب من اليمين إلى اليسار. +Translation: Hello world! This is Arabic text for testing. + +Hebrew (RTL): +שלום עולם! זהו טקסט עברי לבדיקה. העברית נכתבת מימין לשמאל. +Translation: Hello world! This is Hebrew text for testing. + +Embedded LTR in RTL (numbers, English): +السعر 123 دولار للعنصر item الأول. Price: $99.99 + +Embedded RTL in LTR: +The Arabic word مرحبا means hello. The Hebrew word שלום also means peace. + +Neutral characters and mirrors: +(hello) [world] - parentheses and brackets in bidi. +Bidi brackets: ]close[ ]open[ and )right( )left( +Digits: ١٢٣ 123 (Arabic numerals vs ASCII) + +Complex embedding: +HEBREW: עברית | ARABIC: عربية | ENGLISH: Latin script diff --git a/fixtures/text/cjk.txt b/fixtures/text/cjk.txt new file mode 100644 index 0000000000..c1585b6a5b --- /dev/null +++ b/fixtures/text/cjk.txt @@ -0,0 +1,16 @@ +CJK text fixtures for layout and rendering tests. + +Chinese (Simplified): +你好,世界!这是一段中文测试文本。中文排版需要特殊处理,因为每个汉字通常占用等宽空间。字体回退和竖排书写是常见的需求。 + +Chinese (Traditional): +你好,世界!這是一段繁體中文測試文本。繁體字與簡體字在字形上有所不同。 + +Japanese: +こんにちは、世界!これは日本語のテストテキストです。日本語は漢字、ひらがな、カタカナを混在させて使用します。例:今日は良い天気ですね。 + +Korean: +안녕하세요, 세계! 이것은 한국어 테스트 텍스트입니다. 한글은 조합형 문자로 구성되며,각 글자마다 다른 너비를 가질 수 있습니다. + +Mixed CJK (mixed script line): +日本語と中文와 한국어를 같은 줄에混在시킬 수 있습니다。 From 0652bb4935bf05757c0f9b5c1e88d502b0ed36c2 Mon Sep 17 00:00:00 2001 From: Universe Date: Fri, 6 Mar 2026 03:08:10 +0900 Subject: [PATCH 14/15] feat: add left offset to LineMetrics and enhance layout handling - Introduced a new `left` field in `LineMetrics` to represent the X offset of the line's left edge in layout-local space, improving text alignment handling. - Updated the `SimpleLayoutEngine` to initialize the `left` value to `0.0`. - Enhanced the `SkiaLayoutEngine` to calculate the `left` offset based on text alignment for empty lines, ensuring accurate layout metrics during rendering. - Added a new method `empty_line_left` in the `TextAlign` enum to determine the left offset for empty lines based on the layout width. This update aims to improve text alignment and layout accuracy in the grida-text-edit project. --- crates/grida-text-edit/src/layout.rs | 6 + crates/grida-text-edit/src/simple_layout.rs | 1 + crates/grida-text-edit/src/skia_layout.rs | 127 +++++++++++++++++++- 3 files changed, 132 insertions(+), 2 deletions(-) diff --git a/crates/grida-text-edit/src/layout.rs b/crates/grida-text-edit/src/layout.rs index 1618480ddb..7270cfd584 100644 --- a/crates/grida-text-edit/src/layout.rs +++ b/crates/grida-text-edit/src/layout.rs @@ -14,6 +14,12 @@ pub struct LineMetrics { pub ascent: f32, /// Distance below baseline to the bottom of the line. pub descent: f32, + /// X offset of the line's left edge in layout-local space. + /// + /// For left-aligned text this is `0.0`. For center-aligned text it is + /// `(layout_width - content_width) / 2`, etc. Comes from Skia's + /// `LineMetrics::left` field; `SimpleLayoutEngine` always sets `0.0`. + pub left: f32, } impl LineMetrics { diff --git a/crates/grida-text-edit/src/simple_layout.rs b/crates/grida-text-edit/src/simple_layout.rs index 04687761c5..d480fc6577 100644 --- a/crates/grida-text-edit/src/simple_layout.rs +++ b/crates/grida-text-edit/src/simple_layout.rs @@ -55,6 +55,7 @@ impl SimpleLayoutEngine { baseline, ascent, descent, + left: 0.0, }); if !has_nl { diff --git a/crates/grida-text-edit/src/skia_layout.rs b/crates/grida-text-edit/src/skia_layout.rs index 36db496711..d3f0e43fde 100644 --- a/crates/grida-text-edit/src/skia_layout.rs +++ b/crates/grida-text-edit/src/skia_layout.rs @@ -187,6 +187,15 @@ impl TextAlign { Self::Justify => skia_safe::textlayout::TextAlign::Justify, } } + + /// X offset for an empty line (zero content width) at the given layout width. + pub fn empty_line_left(&self, layout_width: f32) -> f32 { + match self { + Self::Left | Self::Justify => 0.0, + Self::Center => layout_width / 2.0, + Self::Right => layout_width, + } + } } /// Configuration for the Skia text layout paragraph. @@ -386,6 +395,7 @@ impl SkiaLayoutEngine { baseline, ascent, descent, + left: self.config.text_align.empty_line_left(self.layout_width), }); } @@ -434,6 +444,7 @@ impl SkiaLayoutEngine { baseline: ascent, ascent, descent, + left: self.config.text_align.empty_line_left(self.layout_width), }; let phantom_height = ascent + descent; self.blocks.push(ParaBlock { @@ -594,6 +605,7 @@ impl SkiaLayoutEngine { baseline, ascent, descent, + left: self.config.text_align.empty_line_left(self.layout_width), }); } @@ -673,6 +685,7 @@ impl SkiaLayoutEngine { baseline: ascent, ascent, descent, + left: self.config.text_align.empty_line_left(self.layout_width), }; let phantom_height = ascent + descent; self.blocks.push(ParaBlock { @@ -741,6 +754,7 @@ impl SkiaLayoutEngine { baseline, ascent, descent, + left: self.config.text_align.empty_line_left(self.layout_width), }); } @@ -793,6 +807,7 @@ impl SkiaLayoutEngine { baseline: ascent, ascent, descent, + left: self.config.text_align.empty_line_left(self.layout_width), }; let phantom_height = ascent + descent; self.blocks.push(ParaBlock { @@ -908,6 +923,7 @@ impl SkiaLayoutEngine { baseline: lm.baseline as f32, // block-local ascent: lm.ascent as f32, descent: lm.descent as f32, + left: lm.left as f32, }); prev_end = local_end; } @@ -927,6 +943,7 @@ impl SkiaLayoutEngine { baseline: block.y_offset + lm.baseline, ascent: lm.ascent, descent: lm.descent, + left: lm.left, }); } } @@ -1063,6 +1080,7 @@ impl SkiaLayoutEngine { baseline, ascent, descent, + left: self.config.text_align.empty_line_left(self.layout_width), }); } @@ -1108,6 +1126,7 @@ impl SkiaLayoutEngine { baseline: ascent, ascent, descent, + left: self.config.text_align.empty_line_left(self.layout_width), }; let phantom_height = ascent + descent; self.blocks.push(ParaBlock { @@ -1183,6 +1202,110 @@ impl SkiaLayoutEngine { self.invalidate(); } + // ------------------------------------------------------------------- + // Preedit (IME composition) support + // ------------------------------------------------------------------- + + /// Build a display string and a laid-out `Paragraph` with the preedit + /// text spliced in at `cursor` and styled with an underline. + /// + /// Returns `(display_text, paragraph, preedit_byte_range)` where + /// `preedit_byte_range` is the UTF-8 byte range of the preedit segment + /// within `display_text`. + /// + /// The caller uses the returned paragraph for rendering and the byte + /// range for caret positioning (caret goes at `preedit_byte_range.end`). + pub fn build_preedit_paragraph( + &self, + content: &crate::attributed_text::AttributedText, + cursor: usize, + preedit: &str, + ) -> (String, Paragraph, std::ops::Range) { + let text = content.text(); + let pre = &text[..cursor]; + let post = &text[cursor..]; + let display_text = format!("{}{}{}", pre, preedit, post); + + let preedit_start = cursor; + let preedit_end = cursor + preedit.len(); + + let mut para_style = ParagraphStyle::new(); + para_style.set_apply_rounding_hack(false); + para_style.set_text_align(self.config.text_align.to_skia()); + + if let Some(lh) = self.config.line_height { + let mut strut = skia_safe::textlayout::StrutStyle::new(); + strut.set_strut_enabled(true); + strut.set_force_strut_height(true); + strut.set_height(lh); + para_style.set_strut_style(strut); + } + + let fallback_families: Vec<&str> = + self.config.font_families.iter().map(|s| s.as_str()).collect(); + + let mut builder = ParagraphBuilder::new(¶_style, &self.font_collection); + + // Build runs from the AttributedText, but splice the preedit at + // the cursor. We walk three regions: [0..cursor), [cursor..cursor) + // with preedit inserted, [cursor..end). + // + // For each region of the original text we push the attributed runs. + // The preedit itself gets the caret style with an underline added. + let runs = content.runs(); + let preedit_style = content.caret_style_at(cursor as u32); + + // --- Region 1: text before cursor --- + for run in runs { + let run_start = run.start as usize; + let run_end = run.end as usize; + // Clip to [0..cursor) + let s = run_start.max(0).min(cursor); + let e = run_end.min(cursor); + if s >= e { + continue; + } + let ts = attr_style_to_skia(&run.style, &fallback_families); + builder.push_style(&ts); + builder.add_text(&text[s..e]); + } + + // --- Region 2: preedit text (underlined) --- + { + let mut ts = attr_style_to_skia(preedit_style, &fallback_families); + ts.set_decoration_type(TextDecoration::UNDERLINE); + builder.push_style(&ts); + builder.add_text(preedit); + } + + // --- Region 3: text after cursor --- + for run in runs { + let run_start = run.start as usize; + let run_end = run.end as usize; + // Clip to [cursor..text.len()) + let s = run_start.max(cursor); + let e = run_end.min(text.len()); + if s >= e { + continue; + } + let ts = attr_style_to_skia(&run.style, &fallback_families); + builder.push_style(&ts); + builder.add_text(&text[s..e]); + } + + // Handle empty document: if no runs produced any text, push a + // default-styled empty span so the paragraph has valid metrics. + if runs.is_empty() && pre.is_empty() && post.is_empty() { + let ts = attr_style_to_skia(preedit_style, &fallback_families); + builder.push_style(&ts); + } + + let mut para = builder.build(); + para.layout(self.layout_width); + + (display_text, para, preedit_start..preedit_end) + } + /// Paint the laid-out paragraph at (0, 0). Used by the host to draw the /// current session text (and optional preedit) so typed content appears /// immediately without waiting for document commit. @@ -1273,7 +1396,7 @@ impl TextLayoutEngine for SkiaLayoutEngine { let height = lm.ascent + lm.descent; let x = if offset <= lm.start_index { - 0.0 + lm.left } else { let block_idx = self.block_index_for_offset(offset); let block = &self.blocks[block_idx]; @@ -1379,7 +1502,7 @@ impl TextLayoutEngine for SkiaLayoutEngine { }); if !already { rects.push(SelectionRect { - x: 0.0, + x: lm.left, y: lm.baseline - lm.ascent, width: self.font_size * 0.5, height: lm.ascent + lm.descent, From 66b9072ecb26cb4e3bc8b020d719dcf224f198a3 Mon Sep 17 00:00:00 2001 From: Universe Date: Sun, 8 Mar 2026 00:31:06 +0900 Subject: [PATCH 15/15] chore: enhance text editor functionality - Added support for ignoring AI-related markdown files in `.gitignore`. - Refactored the `apply` and `apply_with_kind` methods in `TextEditor` to improve history management during text edits, ensuring accurate snapshot handling. - Enhanced the HTML parsing logic to handle malformed HTML more gracefully and updated tests to reflect these changes. - Introduced a new `generation` field in `AttributedText` to track mutations, optimizing layout updates in the `SkiaLayoutEngine`. These changes aim to improve the overall functionality and robustness of the text editing experience in the grida-text-edit project. --- .gitignore | 6 +- .../examples/wd_text_editor.rs | 93 ++-- .../src/attributed_text/html.rs | 77 +++- .../src/attributed_text/mod.rs | 43 +- crates/grida-text-edit/src/lib.rs | 25 +- crates/grida-text-edit/src/skia_layout.rs | 25 +- docs/wg/feat-text-editing/attributed-text.md | 24 +- fixtures/text/README.md | 15 +- fixtures/text/cjk.txt | 2 +- fixtures/text/editor.txt | 420 ++++++++++++++++++ 10 files changed, 648 insertions(+), 82 deletions(-) create mode 100644 fixtures/text/editor.txt diff --git a/.gitignore b/.gitignore index 5eee4b56a2..06365b0320 100644 --- a/.gitignore +++ b/.gitignore @@ -211,4 +211,8 @@ node-compile-cache/ # Byte-compiled / optimized / DLL files __pycache__/ *.py[codz] -*$py.class \ No newline at end of file +*$py.class + + +# AI & Tooling +*.plan.md \ No newline at end of file diff --git a/crates/grida-text-edit/examples/wd_text_editor.rs b/crates/grida-text-edit/examples/wd_text_editor.rs index 03d061a8e3..51f2660ecd 100644 --- a/crates/grida-text-edit/examples/wd_text_editor.rs +++ b/crates/grida-text-edit/examples/wd_text_editor.rs @@ -449,17 +449,28 @@ impl TextEditor { // ----------------------------------------------------------------------- fn apply(&mut self, cmd: EditingCommand) { - if let Some(kind) = cmd.edit_kind() { - if !self.history.would_merge(kind) { - self.history.push(&self.snapshot(), kind); + let kind = cmd.edit_kind(); + // Capture snapshot BEFORE mutation — history.push requires the + // pre-edit state so that undo restores the correct document. + let pre_snapshot = kind.and_then(|k| { + if !self.history.would_merge(k) { + Some((self.snapshot(), k)) } else { - self.history.push_merge(kind); + None } - } + }); + let merge_kind = if pre_snapshot.is_none() { kind } else { None }; + let old_cursor = self.state.cursor; let delta = apply_command_mut(&mut self.state, cmd, &mut self.layout); self.invalidate_caret_cache(); if let Some(d) = delta { + // The edit mutated the document — record history. + if let Some((snap, k)) = pre_snapshot { + self.history.push(&snap, k); + } else if let Some(k) = merge_kind { + self.history.push_merge(k); + } self.sync_content_with_delta(&d, old_cursor); } else if self.state.cursor != old_cursor { self.caret_style_override = None; @@ -472,16 +483,26 @@ impl TextEditor { } fn apply_with_kind(&mut self, cmd: EditingCommand, kind: EditKind) { - if !self.history.would_merge(kind) { - self.history.push(&self.snapshot(), kind); + // Capture snapshot BEFORE mutation. + let pre_snapshot = if !self.history.would_merge(kind) { + Some(self.snapshot()) } else { - self.history.push_merge(kind); - } + None + }; + let old_cursor = self.state.cursor; let delta = apply_command_mut(&mut self.state, cmd, &mut self.layout); self.invalidate_caret_cache(); if let Some(d) = delta { + // The edit mutated the document — record history. + if let Some(snap) = pre_snapshot { + self.history.push(&snap, kind); + } else { + self.history.push_merge(kind); + } self.sync_content_with_delta(&d, old_cursor); + } else if self.state.cursor != old_cursor { + self.caret_style_override = None; } self.reset_blink(); self.layout.ensure_layout_attributed(&self.content); @@ -876,6 +897,12 @@ impl TextEditor { fn set_layout_width(&mut self, w: f32) { self.layout.set_layout_width((w - PADDING * 2.0).max(1.0)); + // After a width change the layout is invalidated; rebuild + // immediately from the attributed content so the plain-text + // `ensure_layout` path (called internally by trait methods) + // doesn't clobber per-run styles. + self.layout.ensure_layout_attributed(&self.content); + self.cached_caret_rect = None; } fn set_layout_height(&mut self, h: f32) { @@ -1638,14 +1665,20 @@ impl ApplicationHandler for TextEditorApp { } PhysicalKey::Code(KeyCode::KeyV) => { // Try HTML first (preserves formatting), fall back to plain text. + let mut handled = false; if let Ok(html) = self.clipboard.get().html() { let base = inner.editor.content.default_style().clone(); - let pasted = html_to_attributed_text(&html, base); - inner.editor.paste_attributed(&pasted); - inner.window.request_redraw(); - } else if let Ok(text) = self.clipboard.get_text() { - inner.editor.insert_text(&text); - inner.window.request_redraw(); + if let Ok(pasted) = html_to_attributed_text(&html, base) { + inner.editor.paste_attributed(&pasted); + inner.window.request_redraw(); + handled = true; + } + } + if !handled { + if let Ok(text) = self.clipboard.get_text() { + inner.editor.insert_text(&text); + inner.window.request_redraw(); + } } } _ => {} @@ -1838,18 +1871,24 @@ impl ApplicationHandler for TextEditorApp { Some("html" | "htm") => match fs::read_to_string(&path) { Ok(html) => { let base = inner.editor.content.default_style().clone(); - let content = html_to_attributed_text(&html, base); - inner.editor.state.text = content.text().to_owned(); - inner.editor.content = content; - inner.editor.state.cursor = 0; - inner.editor.state.anchor = None; - inner.editor.caret_style_override = None; - inner.editor.cached_caret_rect = None; - inner.editor.layout.invalidate(); - inner.editor.scroll_y = 0.0; - inner.editor.reset_blink(); - eprintln!("loaded HTML: {}", path.display()); - inner.window.request_redraw(); + match html_to_attributed_text(&html, base) { + Ok(content) => { + inner.editor.state.text = content.text().to_owned(); + inner.editor.content = content; + inner.editor.state.cursor = 0; + inner.editor.state.anchor = None; + inner.editor.caret_style_override = None; + inner.editor.cached_caret_rect = None; + inner.editor.layout.invalidate(); + inner.editor.scroll_y = 0.0; + inner.editor.reset_blink(); + eprintln!("loaded HTML: {}", path.display()); + inner.window.request_redraw(); + } + Err(e) => { + eprintln!("malformed HTML in {}: {e}", path.display()); + } + } } Err(err) => { eprintln!("failed to read {}: {err}", path.display()); diff --git a/crates/grida-text-edit/src/attributed_text/html.rs b/crates/grida-text-edit/src/attributed_text/html.rs index ad0e856bab..627e70477d 100644 --- a/crates/grida-text-edit/src/attributed_text/html.rs +++ b/crates/grida-text-edit/src/attributed_text/html.rs @@ -72,8 +72,12 @@ fn style_to_css(style: &TextStyle, default: &TextStyle) -> String { if style.font_weight != default.font_weight { parts.push(format!("font-weight:{}", style.font_weight)); } - if style.font_style_italic != default.font_style_italic && style.font_style_italic { - parts.push("font-style:italic".into()); + if style.font_style_italic != default.font_style_italic { + parts.push(if style.font_style_italic { + "font-style:italic".into() + } else { + "font-style:normal".into() + }); } if (style.font_size - default.font_size).abs() > f32::EPSILON { parts.push(format!("font-size:{}px", style.font_size)); @@ -136,12 +140,15 @@ fn html_escape_into(out: &mut String, s: &str) { /// Unknown tags are ignored (their text content is still captured). /// HTML entities `&`, `<`, `>`, `"`, `&#NNN;`, `&#xHH;` are /// decoded. -pub fn html_to_attributed_text(html: &str, base_style: TextStyle) -> AttributedText { +pub fn html_to_attributed_text( + html: &str, + base_style: TextStyle, +) -> Result { let mut parser = HtmlParser::new(html, base_style.clone()); parser.parse(); if parser.text.is_empty() { - return AttributedText::empty(base_style); + return Ok(AttributedText::empty(base_style)); } // Build runs from the collected segments. @@ -171,7 +178,7 @@ pub fn html_to_attributed_text(html: &str, base_style: TextStyle) -> AttributedT }); } - AttributedText::from_parts(parser.text, base_style, Default::default(), runs) + AttributedText::try_from_parts(parser.text, base_style, Default::default(), runs) } // --------------------------------------------------------------------------- @@ -192,6 +199,18 @@ struct HtmlParser { style_stack: Vec, } +/// Returns `true` for HTML tags that represent block-level elements. +/// Closing a block element inserts a newline to preserve paragraph structure. +fn is_block_tag(tag: &str) -> bool { + matches!( + tag, + "p" | "div" | "h1" | "h2" | "h3" | "h4" | "h5" | "h6" + | "li" | "ul" | "ol" | "blockquote" | "pre" + | "section" | "article" | "header" | "footer" + | "tr" | "table" + ) +} + impl HtmlParser { fn new(html: &str, base_style: TextStyle) -> Self { Self { @@ -216,8 +235,17 @@ impl HtmlParser { self.push_char(ch); } else { let ch = self.input[self.pos]; - self.push_char(ch); self.pos += 1; + // HTML whitespace collapsing: \n, \r, \t → space, + // and consecutive whitespace collapses to a single space. + if ch == '\n' || ch == '\r' || ch == '\t' { + // Collapse to a single space (skip if previous char was already a space). + if !self.text.ends_with(' ') && !self.text.ends_with('\n') && !self.text.is_empty() { + self.push_char(' '); + } + } else { + self.push_char(ch); + } } } } @@ -247,6 +275,14 @@ impl HtmlParser { // Tag name let tag_name = self.read_ident().to_lowercase(); + // Skip comments (), directives (, ), + // and namespaced tags () — read_ident() yields an empty or + // unparsable name for these because '!', '?', ':' are not ident chars. + if tag_name.is_empty() { + self.skip_to_tag_end(); + return; + } + // Self-closing or void tags if tag_name == "br" { self.skip_to_tag_end(); @@ -259,6 +295,11 @@ impl HtmlParser { if self.style_stack.len() > 1 { self.style_stack.pop(); } + // Block-level closing tags insert a newline (unless already at + // line start, to avoid double newlines from adjacent blocks). + if is_block_tag(&tag_name) && !self.text.is_empty() && !self.text.ends_with('\n') { + self.push_char('\n'); + } return; } @@ -270,6 +311,12 @@ impl HtmlParser { break; } let attr_name = self.read_ident().to_lowercase(); + if attr_name.is_empty() { + // Non-ident char (e.g. '=', '!', '?', ':') — skip to end of + // tag to avoid an infinite loop. + self.skip_to_tag_end(); + return; + } self.skip_whitespace(); if self.pos < self.input.len() && self.input[self.pos] == '=' { self.pos += 1; // skip '=' @@ -566,14 +613,14 @@ mod tests { #[test] fn deserialize_plain_text() { - let at = html_to_attributed_text("Hello", base()); + let at = html_to_attributed_text("Hello", base()).unwrap(); assert_eq!(at.text(), "Hello"); assert_eq!(at.runs().len(), 1); } #[test] fn deserialize_bold_tag() { - let at = html_to_attributed_text("ABC", base()); + let at = html_to_attributed_text("ABC", base()).unwrap(); assert_eq!(at.text(), "ABC"); assert_eq!(at.runs().len(), 3); assert_eq!(at.runs()[0].style.font_weight, 400); @@ -583,14 +630,14 @@ mod tests { #[test] fn deserialize_italic_tag() { - let at = html_to_attributed_text("italic", base()); + let at = html_to_attributed_text("italic", base()).unwrap(); assert_eq!(at.text(), "italic"); assert!(at.runs()[0].style.font_style_italic); } #[test] fn deserialize_underline_tag() { - let at = html_to_attributed_text("under", base()); + let at = html_to_attributed_text("under", base()).unwrap(); assert_eq!(at.runs()[0].style.text_decoration_line, TextDecorationLine::Underline); } @@ -599,7 +646,7 @@ mod tests { let at = html_to_attributed_text( r#"red bold"#, base(), - ); + ).unwrap(); assert_eq!(at.text(), "red bold"); assert_eq!(at.runs()[0].style.font_weight, 700); assert_eq!(at.runs()[0].style.font_size, 24.0); @@ -611,7 +658,7 @@ mod tests { #[test] fn deserialize_nested_tags() { - let at = html_to_attributed_text("bold italic", base()); + let at = html_to_attributed_text("bold italic", base()).unwrap(); assert_eq!(at.text(), "bold italic"); assert_eq!(at.runs()[0].style.font_weight, 700); assert!(at.runs()[0].style.font_style_italic); @@ -619,13 +666,13 @@ mod tests { #[test] fn deserialize_br_tag() { - let at = html_to_attributed_text("A
B", base()); + let at = html_to_attributed_text("A
B", base()).unwrap(); assert_eq!(at.text(), "A\nB"); } #[test] fn deserialize_entities() { - let at = html_to_attributed_text("<&>", base()); + let at = html_to_attributed_text("<&>", base()).unwrap(); assert_eq!(at.text(), "<&>"); } @@ -636,7 +683,7 @@ mod tests { original.apply_style(6, 11, |s| s.font_style_italic = true); let html = runs_to_html(&original, 0, original.len()); - let restored = html_to_attributed_text(&html, base()); + let restored = html_to_attributed_text(&html, base()).unwrap(); assert_eq!(restored.text(), "Hello World!"); // Bold "Hello" diff --git a/crates/grida-text-edit/src/attributed_text/mod.rs b/crates/grida-text-edit/src/attributed_text/mod.rs index 4032814e8c..154c74c0d3 100644 --- a/crates/grida-text-edit/src/attributed_text/mod.rs +++ b/crates/grida-text-edit/src/attributed_text/mod.rs @@ -455,6 +455,13 @@ pub struct AttributedText { paragraph_style: ParagraphStyle, /// Ordered, non-overlapping, gap-free, maximal runs. runs: Vec, + /// Monotonic counter incremented on every mutation (text or style). + /// + /// Used by [`SkiaLayoutEngine::ensure_layout_attributed`] to detect + /// style-only changes that leave the text bytes unchanged but require + /// a paragraph rebuild. + #[serde(skip)] + generation: u64, } impl AttributedText { @@ -472,6 +479,7 @@ impl AttributedText { default_style: style, paragraph_style: ParagraphStyle::default(), runs, + generation: 0, }; debug_assert!(this.check_invariants().is_ok(), "{:?}", this.check_invariants()); this @@ -485,6 +493,7 @@ impl AttributedText { default_style: style, paragraph_style: ParagraphStyle::default(), runs, + generation: 0, } } @@ -498,13 +507,28 @@ impl AttributedText { paragraph_style: ParagraphStyle, runs: Vec, ) -> Self { - let this = Self { text, default_style, paragraph_style, runs }; + let this = Self { text, default_style, paragraph_style, runs, generation: 0 }; if let Err(e) = this.check_invariants() { panic!("AttributedText::from_parts: invariant violated: {e}"); } this } + /// Non-panicking variant of [`from_parts`](Self::from_parts). + /// + /// Returns `Err(InvariantError)` when the runs violate structural + /// invariants (gaps, overlaps, out-of-bounds offsets, etc.). + pub fn try_from_parts( + text: String, + default_style: TextStyle, + paragraph_style: ParagraphStyle, + runs: Vec, + ) -> Result { + let this = Self { text, default_style, paragraph_style, runs, generation: 0 }; + this.check_invariants()?; + Ok(this) + } + // ----------------------------------------------------------------------- // Accessors // ----------------------------------------------------------------------- @@ -515,6 +539,15 @@ impl AttributedText { &self.text } + /// Monotonic generation counter. Incremented on every mutation + /// (text insert/delete **and** style changes). Layout engines use + /// this to detect when a rebuild is needed even if the text bytes + /// are unchanged. + #[inline] + pub fn generation(&self) -> u64 { + self.generation + } + /// Byte length of the text. #[inline] pub fn len(&self) -> usize { @@ -649,6 +682,7 @@ impl AttributedText { if s.is_empty() { return; } + self.generation += 1; debug_assert!( pos <= self.text.len() && (pos == self.text.len() || self.text.is_char_boundary(pos)), "insert pos {pos} is not a valid boundary in text of len {}", @@ -692,6 +726,7 @@ impl AttributedText { if s.is_empty() { return; } + self.generation += 1; debug_assert!( pos <= self.text.len() && (pos == self.text.len() || self.text.is_char_boundary(pos)), "insert_with_style pos {pos} is not a valid boundary in text of len {}", @@ -743,6 +778,7 @@ impl AttributedText { if lo >= hi || lo >= self.text.len() { return; } + self.generation += 1; let hi = hi.min(self.text.len()); debug_assert!( self.text.is_char_boundary(lo) && self.text.is_char_boundary(hi), @@ -817,11 +853,16 @@ impl AttributedText { if lo >= hi || self.text.is_empty() { return; } + self.generation += 1; let lo = lo.min(self.text.len()); let hi = hi.min(self.text.len()); if lo >= hi { return; } + debug_assert!( + self.text.is_char_boundary(lo) && self.text.is_char_boundary(hi), + "apply_style range [{lo}, {hi}) contains invalid char boundaries" + ); let lo32 = lo as u32; let hi32 = hi as u32; diff --git a/crates/grida-text-edit/src/lib.rs b/crates/grida-text-edit/src/lib.rs index 44b1c31d8b..1af25f2754 100644 --- a/crates/grida-text-edit/src/lib.rs +++ b/crates/grida-text-edit/src/lib.rs @@ -155,36 +155,29 @@ pub fn ceil_char_boundary(text: &str, pos: usize) -> usize { // making it O(1) amortized regardless of position in the document. // --------------------------------------------------------------------------- -/// Size of the lookback/lookahead window for word boundary scanning. -/// UAX#29 word segmentation is context-dependent but the context is local; -/// 256 bytes covers even long compound words in CJK/Thai. -const WORD_WINDOW: usize = 256; - /// Find the UAX #29 word segment containing `offset`. /// /// Returns `(start, end)` — the byte range of the segment. This is the /// standalone equivalent of `TextLayoutEngine::word_boundary_at` so that /// pure editing commands (`BackspaceWord`, `DeleteWord`) can resolve word /// boundaries without requiring a layout engine. +/// +/// The iterator is lazy — it stops as soon as the segment containing +/// `offset` is found, so cost is proportional to the offset position, +/// not the full document length. pub fn word_segment_at(text: &str, offset: usize) -> (usize, usize) { let offset = offset.min(text.len()); - // Scan a local window around offset. - let window_start = floor_char_boundary(text, offset.saturating_sub(WORD_WINDOW)); - let window_end = ceil_char_boundary(text, (offset + WORD_WINDOW).min(text.len())); - let slice = &text[window_start..window_end]; - let local_offset = offset - window_start; - let mut last_start = 0usize; - for (byte_idx, segment) in slice.split_word_bound_indices() { + for (byte_idx, segment) in text.split_word_bound_indices() { let end = byte_idx + segment.len(); - if byte_idx <= local_offset && local_offset < end { - return (window_start + byte_idx, window_start + end); + if byte_idx <= offset && offset < end { + return (byte_idx, end); } last_start = byte_idx; } - // offset is at or past the last segment boundary in the window. - (window_start + last_start, window_end) + // offset is at or past the last segment boundary. + (last_start, text.len()) } /// Find the start of the previous word segment from `pos`. diff --git a/crates/grida-text-edit/src/skia_layout.rs b/crates/grida-text-edit/src/skia_layout.rs index d3f0e43fde..d2c95965a7 100644 --- a/crates/grida-text-edit/src/skia_layout.rs +++ b/crates/grida-text-edit/src/skia_layout.rs @@ -297,6 +297,11 @@ pub struct SkiaLayoutEngine { blocks: Vec, /// Flattened line metrics across all blocks (cached, invalidated with blocks). cached_line_metrics: Option>, + /// Tracks the [`AttributedText::generation`] seen by the last + /// `ensure_layout_attributed` call. When the generation advances + /// (e.g. a style-only change), the cache is invalidated even though + /// the text bytes are unchanged. + cached_attributed_generation: u64, } impl SkiaLayoutEngine { @@ -319,6 +324,7 @@ impl SkiaLayoutEngine { font_provider: TypefaceFontProvider::new(), blocks: Vec::new(), cached_line_metrics: None, + cached_attributed_generation: 0, } } @@ -533,11 +539,21 @@ impl SkiaLayoutEngine { // The last affected block is the one that contains old_edit_end // (in the old coordinate system). - let last_affected = self.blocks + let mut last_affected = self.blocks .iter() .position(|b| b.byte_end >= old_edit_end) .unwrap_or(self.blocks.len().saturating_sub(1)); + // When newlines were removed, the block starting at old_edit_end + // must also be included: deleting '\n' merges two paragraphs, so + // the following block needs to be re-parsed together with the + // preceding one. + if newlines_removed > 0 { + if let Some(next_idx) = self.blocks.iter().position(|b| b.byte_start == old_edit_end) { + last_affected = last_affected.max(next_idx); + } + } + // Remove old phantom trailing block if present. if let Some(last) = self.blocks.last() { if last.byte_start == last.byte_end @@ -984,9 +1000,14 @@ impl SkiaLayoutEngine { &mut self, at: &crate::attributed_text::AttributedText, ) { - if !self.blocks.is_empty() && self.cached_text == at.text() { + let gen = at.generation(); + if !self.blocks.is_empty() + && self.cached_text == at.text() + && self.cached_attributed_generation == gen + { return; } + self.cached_attributed_generation = gen; self.rebuild_blocks_attributed(at); } diff --git a/docs/wg/feat-text-editing/attributed-text.md b/docs/wg/feat-text-editing/attributed-text.md index 0211009a66..906cb24758 100644 --- a/docs/wg/feat-text-editing/attributed-text.md +++ b/docs/wg/feat-text-editing/attributed-text.md @@ -93,7 +93,7 @@ Key properties: Figma's model is unique. It uses three layers: -``` +```text TextData { characters: string // flat plain text characterStyleIDs: uint[] // per-character style ID @@ -113,7 +113,7 @@ Additionally, Figma cleanly separates **authoring data** (`TextData`: characters Figma also stores per-line metadata via `TextLineData`: -``` +```text TextLineData { lineType: LineType // PLAIN | ORDERED_LIST | UNORDERED_LIST | BLOCKQUOTE | HEADER styleId: int @@ -241,7 +241,7 @@ The caret style can be **overridden** by explicit user action (e.g., clicking "B ### The core structure -``` +```text AttributedText = (text, default_style, paragraph_style, runs) ``` @@ -455,7 +455,7 @@ Given a style mutation `f: TextStyle -> TextStyle`: ### Split at offset -``` +```rust split_at(runs, offset) -> () ``` @@ -463,7 +463,7 @@ If `offset` falls exactly on a run boundary, no-op. Otherwise, find the run cont ### Merge adjacent equal runs (coalesce) -``` +```rust coalesce(runs) -> () ``` @@ -475,7 +475,7 @@ This is Apple's "automatic coalescing" behavior. Android's span model notably la ### Style at offset -``` +```rust style_at(offset: u32) -> &TextStyle ``` @@ -485,7 +485,7 @@ Binary search on runs (O(log k)). For an offset exactly at a run boundary, retur ### Caret style at offset -``` +```rust caret_style_at(offset: u32) -> &TextStyle ``` @@ -493,7 +493,7 @@ At a run boundary, returns the **upstream** run's style (the run that ends at `o ### Runs in range -``` +```rust runs_in_range(lo: u32, hi: u32) -> &[StyledRun] ``` @@ -583,7 +583,7 @@ The attributed text model is designed to **layer on top of** a plain-text editin The integration follows a two-layer architecture: -``` +```rust RichTextEditorState content: AttributedText // text + runs cursor: usize // caret position (UTF-8 byte offset) @@ -630,7 +630,7 @@ Contrast with Figma's O(1) per-character lookup (direct array index). Our O(log For a text block with `k` runs: -``` +```text AttributedText: text: 24 bytes (String: ptr + len + cap) default_style: ~120 bytes (fixed fields + vecs) @@ -647,7 +647,7 @@ StyledRun: For a typical text block with 5 runs: ~24 + 120 + 48 + 24 + 640 = ~856 bytes. This is compact enough for thousands of text nodes in a design document. -**Comparison with Figma**: For a 500-character paragraph with 5 style changes, Figma stores 500 _ 4 = 2000 bytes for `characterStyleIDs` alone, plus the override table. Our model stores 5 _ 128 = 640 bytes for runs, plus the default style. The run-based model uses ~3x less memory for this typical case. +**Comparison with Figma**: For a 500-character paragraph with 5 style changes, Figma stores 500 × 4 = 2000 bytes for `characterStyleIDs` alone, plus the override table. Our model stores 5 × 128 = 640 bytes for runs, plus the default style. The run-based model uses ~3x less memory for this typical case. **Future optimization**: Style interning (`Arc`) can reduce per-run cost to ~16 bytes when many runs share the same style. This is an implementation optimization, not a model change. @@ -655,7 +655,7 @@ For a typical text block with 5 runs: ~24 + 120 + 48 + 24 + 640 = ~856 bytes. Th The model exposes mutation only through methods that maintain invariants. Direct field access is restricted to reads. The key methods form a closed algebra: -``` +```rust fn insert(&mut self, pos: usize, s: &str) fn delete(&mut self, lo: usize, hi: usize) fn apply_style(&mut self, lo: usize, hi: usize, f: impl Fn(&mut TextStyle)) diff --git a/fixtures/text/README.md b/fixtures/text/README.md index 8cb94f8bcd..ee152baeea 100644 --- a/fixtures/text/README.md +++ b/fixtures/text/README.md @@ -6,13 +6,14 @@ Plain text samples used for layout, editing, and rendering tests. Header and foo ## Text Fixtures Attribution -| file | source | description | url | -| ---------- | ----------------- | -------------------------------------------------------- | ---------------------------------------------------- | -| ascii.txt | — | ASCII-only text (printable chars, punctuation) | — | -| bidi.txt | — | Bidirectional text (Arabic, Hebrew, embedded LTR/RTL) | — | -| cjk.txt | — | CJK text (Chinese, Japanese, Korean, mixed) | — | -| hamlet.txt | Project Gutenberg | William Shakespeare, _Hamlet_ (full play, public domain) | https://www.gutenberg.org/cache/epub/1524/pg1524.txt | -| lorem.txt | — | Lorem ipsum placeholder text | — | +| file | source | description | url | +| ---------- | ----------------- | ------------------------------------------------------------------------------------- | ---------------------------------------------------- | +| editor.txt | — | **Comprehensive single fixture** for full-featured editors: scripts, bidi, edge cases | — | +| ascii.txt | — | ASCII-only text (printable chars, punctuation) | — | +| bidi.txt | — | Bidirectional text (Arabic, Hebrew, embedded LTR/RTL) | — | +| cjk.txt | — | CJK text (Chinese, Japanese, Korean, mixed) | — | +| hamlet.txt | Project Gutenberg | William Shakespeare, _Hamlet_ (full play, public domain) | https://www.gutenberg.org/cache/epub/1524/pg1524.txt | +| lorem.txt | — | Lorem ipsum placeholder text | — | ## See also diff --git a/fixtures/text/cjk.txt b/fixtures/text/cjk.txt index c1585b6a5b..d361b78d48 100644 --- a/fixtures/text/cjk.txt +++ b/fixtures/text/cjk.txt @@ -10,7 +10,7 @@ Japanese: こんにちは、世界!これは日本語のテストテキストです。日本語は漢字、ひらがな、カタカナを混在させて使用します。例:今日は良い天気ですね。 Korean: -안녕하세요, 세계! 이것은 한국어 테스트 텍스트입니다. 한글은 조합형 문자로 구성되며,각 글자마다 다른 너비를 가질 수 있습니다. +안녕하세요, 세계! 이것은 한국어 테스트 텍스트입니다. 한글은 조합형 문자로 구성되며, 각 글자마다 다른 너비를 가질 수 있습니다. Mixed CJK (mixed script line): 日本語と中文와 한국어를 같은 줄에混在시킬 수 있습니다。 diff --git a/fixtures/text/editor.txt b/fixtures/text/editor.txt new file mode 100644 index 0000000000..b8ae1b1a5c --- /dev/null +++ b/fixtures/text/editor.txt @@ -0,0 +1,420 @@ +================================================================================ +UNIVERSAL TEXT EDITOR FIXTURE +Comprehensive Unicode test corpus for full-featured text editors and layout engines +================================================================================ + +Purpose +------- +This file is designed to stress-test text engines, editors, layout systems, +renderers, diff tools, storage systems, and search pipelines. + +It intentionally includes: + +• Multiple writing systems +• Bidirectional text +• Complex shaping scripts +• Combining characters +• Emoji and symbols +• Invisible characters +• Numeral systems +• Long lines and whitespace edge cases +• Stress-test sequences + +It is suitable for testing: + +• Unicode correctness +• Grapheme cluster handling +• Cursor movement +• Selection behavior +• Line wrapping +• Font fallback +• Bi-directional layout +• Normalization (NFC/NFD) +• Storage integrity +• Rendering stability + +================================================================================ +1. ASCII — BASIC LATIN +================================================================================ + +The quick brown fox jumps over the lazy dog. +THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG. +0123456789 + +Punctuation: +! " # $ % & ' ( ) * + , - . / +: ; < = > ? @ +[ \ ] ^ _ ` +{ | } ~ + +Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor +incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis +nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. + +ASCII printable range: 32–126 + +================================================================================ +2. LATIN EXTENDED — ACCENTS & DIACRITICS +================================================================================ + +Àà Áá Ââ Ãã Ää Åå Ææ Çç +Èè Éé Êê Ëë +Ìì Íí Îî Ïï +Ññ +Òò Óó Ôô Õõ Öö Øø +Ùù Úú Ûû Üü +Ýý ß + +Example words: + +Café +naïve +façade +résumé +Zoë +Beyoncé + +Müller +Zürich +São Paulo +Malmö +Łódź +İstanbul + +Crème brûlée +crêpe +naïve +coöperate +reëlect + +================================================================================ +3. CYRILLIC +================================================================================ + +Russian +Привет, мир! Это русский текст для тестирования. +Кириллица используется во многих славянских языках. + +Ukrainian +Привіт, світ! Це український текст. +Українська має літери і, ї, є, ґ. + +Bulgarian +Здравей, свят! Това е български текст. +Българската азбука има 30 букви. + +Serbian (Cyrillic) +Здраво свете! Ово је српски текст на ћирилици. + +================================================================================ +4. GREEK +================================================================================ + +Modern Greek +Γεια σου κόσμε! Αυτό είναι κείμενο στα ελληνικά. +Το ελληνικό αλφάβητο έχει 24 γράμματα. + +Alphabet pairs +Αα Ββ Γγ Δδ Εε Ζζ Ηη Θθ +Ιι Κκ Λλ Μμ Νν Ξξ Οο +Ππ Ρρ Σσς Ττ Υυ Φφ +Χχ Ψψ Ωω + +================================================================================ +5. CJK — CHINESE +================================================================================ + +Simplified Chinese +你好,世界!这是一段中文测试文本。 +中文排版需要特殊处理,因为每个汉字通常占用等宽空间。 +字体回退和竖排书写是常见需求。 + +Traditional Chinese +你好,世界!這是一段繁體中文測試文本。 +繁體字與簡體字在字形上有所不同。 + +================================================================================ +6. CJK — JAPANESE +================================================================================ + +Japanese +こんにちは、世界! +これは日本語のテストテキストです。 +日本語は漢字、ひらがな、カタカナを混在させて使用します。 +今日は良い天気ですね。 + +Hiragana +あいうえお +かきくけこ +さしすせそ +たちつてと +なにぬねの + +Katakana +アイウエオ +カキクケコ +サシスセソ +タチツテト +ナニヌネノ + +================================================================================ +7. CJK — KOREAN +================================================================================ + +Korean +안녕하세요, 세계! +이것은 한국어 테스트 텍스트입니다. +한글은 조합형 문자로 구성되며 각 글자는 다양한 너비를 가질 수 있습니다. + +Hangul syllables +가나다라마바사아자차카타파하 +거너더러머버서어저처커터퍼허 + +================================================================================ +8. MIXED CJK LINE +================================================================================ + +日本語と中文와 한국어를 같은 줄에混在시킬 수 있습니다。 + +================================================================================ +9. INDIC SCRIPTS +================================================================================ + +Hindi (Devanagari) +नमस्ते दुनिया! +यह हिंदी परीक्षण पाठ है। +देवनागरी लिपि बाएं से दाएं लिखी जाती है। + +Bengali +নমস্কার বিশ্ব! +এটি বাংলা পরীক্ষার পাঠ। +বাংলা বর্ণমালায় স্বরবর্ণ ও ব্যঞ্জনবর্ণ রয়েছে। + +Tamil +வணக்கம் உலகம்! +இது தமிழ் சோதனை உரை. + +Telugu +నమస్కారం ప్రపంచం! +ఇది తెలుగు పరీక్షా వచనం. + +================================================================================ +10. THAI +================================================================================ + +Thai +สวัสดีครับ! +นี่คือข้อความทดสอบภาษาไทย +ภาษาไทยมีการรวมตัวของพยัญชนะและสระที่ซับซ้อน + +Thai without spaces +ภาษาไทยไม่มีช่องว่างระหว่างคำในบางกรณี + +================================================================================ +11. BIDIRECTIONAL — ARABIC +================================================================================ + +Arabic (RTL) +مرحبا بالعالم! +هذا نص عربي للاختبار. +العربية تكتب من اليمين إلى اليسار. + +Arabic numerals +١٢٣٤٥٦٧٨٩٠ + +Western numerals +1234567890 + +================================================================================ +12. BIDIRECTIONAL — HEBREW +================================================================================ + +Hebrew (RTL) +שלום עולם! +זהו טקסט עברי לבדיקה. +העברית נכתבת מימין לשמאל. + +================================================================================ +13. BIDIRECTIONAL — EMBEDDING +================================================================================ + +Embedded LTR in RTL +السعر 123 دولار للعنصر item الأول. + +Embedded RTL in LTR +The Arabic word مرحبا means hello. +The Hebrew word שלום also means peace. + +Complex example +HEBREW: עברית +ARABIC: عربية +ENGLISH: Latin script + +================================================================================ +14. BIDIRECTIONAL — MIRRORING +================================================================================ + +(hello) +[world] + +]close[ ]open[ +)right( )left( + +Digits +١٢٣ 123 + +================================================================================ +15. ARMENIAN & GEORGIAN +================================================================================ + +Armenian +Բարև աշխարհ! +Սա հայերեն փորձարկման տեքստ է։ + +Georgian +გამარჯობა მსოფლიო! +ეს ქართული ტექსტია ტესტირებისთვის. + +================================================================================ +16. EMOJI & SYMBOLS +================================================================================ + +Emoji +😀 😃 😄 😁 🎉 🚀 ✨ 💻 📝 🔤 🌍 中文 🇯🇵 🇰🇷 + +Emoji in text +Hello 😊 world! +今日は ☀️ 良い天気です。 +مرحبا 🌟 + +Symbols +© ® ™ € £ ¥ § ¶ † ‡ • … — – ′ ″ + +Mathematics +∑ ∏ ∫ ∂ ∇ ± × ÷ +≈ ≠ ≤ ≥ ∞ √ + +================================================================================ +17. COMBINING CHARACTERS +================================================================================ + +Combining sequences +e + ́ → é +n + ̃ → ñ +o + ̈ → ö + +Decomposed form +café + +Precomposed form +café + +Thai combining +ก + า = กา + +================================================================================ +18. ZERO WIDTH & INVISIBLE CHARACTERS +================================================================================ + +Zero width space +word​word​word + +Zero width joiner +word⁠word⁠word + +Zero width non-joiner +word‌word‌word + +Soft hyphen +super­cali­fragilistic + +Word joiner +no⁠break + +================================================================================ +19. NUMERAL SYSTEMS +================================================================================ + +Western +0 1 2 3 4 5 6 7 8 9 + +Arabic-Indic +٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ + +Persian +۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ + +Devanagari +० १ २ ३ ४ ५ ६ ७ ८ ९ + +Bengali +০ ১ ২ ৩ ৪ ৫ ৬ ৭ ৮ ৯ + +Tamil +௦ ௧ ௨ ௩ ௪ ௫ ௬ ௭ ௮ ௯ + +Thai +๐ ๑ ๒ ๓ ๔ ๕ ๖ ๗ ๘ ๙ + +Roman +I II III IV V VI VII VIII IX X + +================================================================================ +20. TABS & WHITESPACE +================================================================================ + +Tab separated values + +Leading spaces + four spaces + +Trailing spaces +line with spaces + +Mixed + tab then spaces then more + +================================================================================ +21. LONG LINE WRAPPING +================================================================================ + +Short line. + +Medium line that may wrap depending on viewport width and editor layout +behavior. + +Very long line without natural breakpoints to test soft wrapping and horizontal +scrolling behavior in text editors and layout engines: The quick brown fox +jumps over the lazy dog and then runs through the forest to find the red +squirrel who was hiding in the old oak tree near the bubbling brook. + +================================================================================ +22. MINIMAL LINES +================================================================================ + +(empty line above) + +A +AB + +Single character lines. + +================================================================================ +23. STRESS TEST +================================================================================ + +aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa + +日本語日本語日本語日本語日本語日本語日本語日本語日本語日本語 + +مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا مرحبا + +================================================================================ +24. MIXED SCRIPT STRESS LINE +================================================================================ + +Hello 你好 مرحبا שלום こんにちは 안녕하세요 Привет Γεια σου नमस्ते สวัสดี 😀 + +================================================================================ +END OF FIXTURE +================================================================================ \ No newline at end of file