From 95d49c469b26af9c4c7b2ee868bafc743fb2ad53 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 08:03:19 +0300 Subject: [PATCH 01/21] A chapter that was never in the book, and three gates that would have said so The developer guide shipped four structural defects that every existing gate passed. All four are reproducible by rendering the book and reading the result back, which is what the new checks do. "Working with the native Linux port" was not in the manual. Its include sat on the line directly after Working-With-Windows.asciidoc, whose last line is a paragraph, so after include expansion the Linux chapter's title became a continuation of that paragraph and the whole chapter rendered as sections 94.9-94.13 of the Windows one. Asciidoctor reports nothing for this. Accessibility-Semantics, MCP-Headless-API and Media-And-Audio opened with a level-0 heading. Under doctype:book that makes each a *part*, promotes its own sections to chapters and deletes its title from the sequence -- 22 of the book's "chapters" were really sections of those three. Six documents were included by nothing. component-selector.asciidoc (the live ComponentSelector API), SVG-Transcoder.asciidoc and Video-Capture-Constraints are now in the book; Casual-Game-Programming is superseded by the card-game case study in Game Development and is deleted with its two images; cn1libs.asciidoc was a stub; Home.asciidoc's live links move into the preface. Eight cross-references pointed at ids that do not exist. Asciidoctor renders <> as an ordinary link to nowhere, so these looked fine. The Application Lifecycle sidebar the introduction cites twice was written all along -- it just had no anchor. Sixteen links to codenameone.com/manual/.html: the site serves /manual and /manual/ exactly, with no splat. Each sat in an HTML/PDF fork whose commented-out PDF branch was already the internal xref that works in both backends, so the fork is collapsed onto it. The ~90 chapters are now grouped into parts. Index.asciidoc, which is the introduction and not an index, is renamed accordingly. New gates, all four verified to fail when the defect they describe is reintroduced: check-guide-structure.py every document is in the book or declared out of it; no chapter silently becomes a part; every included title survives into the rendered output check-guide-xrefs.py every internal link resolves, and none renders as a bare "[some-id]" check-missing-code-blocks.py prose that promises a listing and is followed by a hole -- 409 of them, from the snippet extraction in bbdc6058f0, recoverable from bbdc6058f0~1 check-guide-links.py links checked against the paths the site actually serves, derived from _redirects and the content tree rather than a hand-written list The last two carry shrinking baselines, in the shape of cast-semantics-baseline. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/developer-guide-docs.yml | 14 + .../ComponentSelectorJava001Snippet.java | 20 +- .../ComponentSelectorJava007Snippet.java | 82 ++++ .../developer-guide/About-This-Guide.asciidoc | 14 +- .../Accessibility-Semantics.asciidoc | 30 +- .../developer-guide/Advanced-Theming.asciidoc | 8 +- .../Advanced-Topics-Under-The-Hood.asciidoc | 8 +- docs/developer-guide/Analytics.asciidoc | 1 + docs/developer-guide/Animations.asciidoc | 20 - .../Authentication-And-Identity.asciidoc | 1 + .../Casual-Game-Programming.asciidoc | 80 ---- docs/developer-guide/Events.asciidoc | 14 +- docs/developer-guide/Home.asciidoc | 26 -- .../{Index.asciidoc => Introduction.asciidoc} | 4 +- .../developer-guide/MCP-Headless-API.asciidoc | 18 +- docs/developer-guide/Media-And-Audio.asciidoc | 12 +- .../Miscellaneous-Features.asciidoc | 6 - docs/developer-guide/SVG-Transcoder.asciidoc | 23 +- docs/developer-guide/Testing-with-JUnit.adoc | 2 +- .../The-Components-Of-Codename-One.asciidoc | 32 +- .../Video-Capture-Constraints.asciidoc | 6 +- docs/developer-guide/Wearables.asciidoc | 17 +- .../developer-guide/Working-With-iOS.asciidoc | 1 + docs/developer-guide/appendix_goal_test.adoc | 1 + docs/developer-guide/basics.asciidoc | 1 + docs/developer-guide/cn1libs.asciidoc | 3 - .../component-selector.asciidoc | 18 +- docs/developer-guide/developer-guide.asciidoc | 131 ++++-- docs/developer-guide/img/game-figure-3.png | Bin 198269 -> 0 bytes docs/developer-guide/img/gaming-fig2.png | Bin 121397 -> 0 bytes docs/developer-guide/io.asciidoc | 13 - docs/developer-guide/languagetool-accept.txt | 21 + docs/developer-guide/security.asciidoc | 3 +- scripts/developer-guide/check-guide-links.py | 161 +++++++ .../developer-guide/check-guide-structure.py | 231 ++++++++++ scripts/developer-guide/check-guide-xrefs.py | 113 +++++ .../check-missing-code-blocks.py | 145 ++++++ .../developer-guide/guide-links-baseline.txt | 41 ++ .../missing-code-blocks-baseline.txt | 412 ++++++++++++++++++ 39 files changed, 1400 insertions(+), 333 deletions(-) create mode 100644 docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava007Snippet.java delete mode 100644 docs/developer-guide/Casual-Game-Programming.asciidoc delete mode 100644 docs/developer-guide/Home.asciidoc rename docs/developer-guide/{Index.asciidoc => Introduction.asciidoc} (99%) delete mode 100644 docs/developer-guide/cn1libs.asciidoc delete mode 100644 docs/developer-guide/img/game-figure-3.png delete mode 100644 docs/developer-guide/img/gaming-fig2.png create mode 100755 scripts/developer-guide/check-guide-links.py create mode 100755 scripts/developer-guide/check-guide-structure.py create mode 100755 scripts/developer-guide/check-guide-xrefs.py create mode 100755 scripts/developer-guide/check-missing-code-blocks.py create mode 100644 scripts/developer-guide/guide-links-baseline.txt create mode 100644 scripts/developer-guide/missing-code-blocks-baseline.txt diff --git a/.github/workflows/developer-guide-docs.yml b/.github/workflows/developer-guide-docs.yml index 2d0df8a5a1b..f893155d096 100644 --- a/.github/workflows/developer-guide-docs.yml +++ b/.github/workflows/developer-guide-docs.yml @@ -271,6 +271,20 @@ jobs: echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2 fi + # These four cover defects the gates above are structurally blind to: a + # chapter swallowed by the one before it, a cross-reference that resolves + # to nothing, prose that promises a code block which is not there, and a + # link the website does not serve. Asciidoctor reports none of them, and + # every one of them shipped. The structure and cross-reference checks + # render the book, so they must run after the Asciidoctor install. + - name: Check developer guide structure, cross-references, code blocks and links + run: | + set -euo pipefail + python3 scripts/developer-guide/check-guide-structure.py + python3 scripts/developer-guide/check-guide-xrefs.py + python3 scripts/developer-guide/check-missing-code-blocks.py + python3 scripts/developer-guide/check-guide-links.py + - name: Build Developer Guide HTML and PDF if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true' run: | diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava001Snippet.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava001Snippet.java index 180075e89e8..8c4252dc331 100644 --- a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava001Snippet.java +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava001Snippet.java @@ -57,18 +57,18 @@ class ComponentSelectorJava001Snippet { Resources theme; void snippet() throws Exception { // tag::component-selector-java-001[] - // null + // ... - Button slideUp = $(new Button("Slide Up")) // - .setIcon(FontImage.MATERIAL_EXPAND_LESS) // - .addActionListener(e->{ // - $(e) // - .getParent() // - .find(">*") // - .slideUpAndWait(1000) // - .slideDownAndWait(1000); // + Button slideUp = $(new Button("Slide Up")) // <1> + .setIcon(FontImage.MATERIAL_EXPAND_LESS) // <2> + .addActionListener(e->{ // <3> + $(e) // <4> + .getParent() // <5> + .find(">*") // <6> + .slideUpAndWait(1000) // <7> + .slideDownAndWait(1000); // <8> }) - .asComponent(Button.class); // + .asComponent(Button.class); // <9> // end::component-selector-java-001[] } } diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava007Snippet.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava007Snippet.java new file mode 100644 index 00000000000..d0a80b4d414 --- /dev/null +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava007Snippet.java @@ -0,0 +1,82 @@ +package com.codenameone.developerguide.snippets.generated; + +import com.codename1.gpu.*; +import com.codename1.ui.*; +import com.codename1.ui.animations.*; +import com.codename1.ui.events.*; +import com.codename1.ui.geom.*; +import com.codename1.ui.layouts.*; +import com.codename1.ui.list.*; +import com.codename1.ui.plaf.*; +import com.codename1.ui.util.*; +import com.codename1.components.*; +import com.codename1.charts.models.*; +import com.codename1.charts.renderers.*; +import com.codename1.charts.views.*; +import com.codename1.capture.*; +import com.codename1.io.*; +import com.codename1.l10n.*; +import com.codename1.location.*; +import com.codename1.maps.*; +import com.codename1.media.*; +import com.codename1.messaging.*; +import com.codename1.payment.*; +import com.codename1.processing.*; +import com.codename1.properties.*; +import com.codename1.push.*; +import com.codename1.security.*; +import com.codename1.social.*; +import com.codename1.ui.spinner.*; +import java.io.*; +import java.util.*; +import static com.codename1.ui.ComponentSelector.$; + +class ComponentSelectorJava007Snippet { + + Object context; + Object url; + Object value; + Object body; + Object event; + String apiKey = "test-key"; + String myHttpsURL = "https://example.com"; + java.util.List validKeysList = new java.util.ArrayList<>(); + Image myImage; + Graphics graphics; + Graphics g; + GraphicsDevice device; + Form form; + Form hi; + Container cnt; + Container myForm; + Component component; + Button button; + MultiButton myMultiButton; + Label label; + BrowserComponent browserComponent; + Resources theme; + void snippet() throws Exception { + // tag::component-selector-java-007[] + Button replace = $(new Button("Replace Fade/Slide")) + .setIcon(FontImage.MATERIAL_REDEEM) + .addActionListener(e->{ + $(e).getParent() + .find(">*") // <1> + .replaceAndWait(c->{ // <2> + return $(new Label("Replacement")) // <3> + .putClientProperty("origComponent", c) // <4> + .asComponent(); + }, CommonTransitions.createFade(1000)) // <5> + .replaceAndWait(c->{ + Component orig = (Component)c.getClientProperty("origComponent"); + if (orig != null) { + c.putClientProperty("origComponent", null); + return orig; // <6> + } + return c; + }, CommonTransitions.createCover(CommonTransitions.SLIDE_HORIZONTAL, false, 1000)); // <7> + }) + .asComponent(Button.class); + // end::component-selector-java-007[] + } +} diff --git a/docs/developer-guide/About-This-Guide.asciidoc b/docs/developer-guide/About-This-Guide.asciidoc index 831222c4c9a..e6e54b3b600 100644 --- a/docs/developer-guide/About-This-Guide.asciidoc +++ b/docs/developer-guide/About-This-Guide.asciidoc @@ -7,10 +7,22 @@ This developer guide comes directly from the `docs` directory of the https://git === How to contribute updates -To contribute updates, clone the repository, create a feature branch, and edit the AsciiDoc files in `docs/developer-guide`. Follow the steps in the link:https://github.com/codenameone/CodenameOne/blob/master/CONTRIBUTING.md[CONTRIBUTING guidelines] to open a GitHub pull request (PR) instead of using the deprecated community wiki. Continuous integration lints and builds each PR automatically. A maintainer then reviews the PR before merging it. After the merge, the content appears in the web manual at https://www.codenameone.com/manual/ and in the downloadable PDF at https://www.codenameone.com/files/developer-guide.pdf[https://www.codenameone.com/files/developer-guide.pdf]. From time to time, major revisions are collected for print-on-demand distribution. +To contribute updates, clone the repository, create a feature branch, and edit the AsciiDoc files in `docs/developer-guide`. Follow the steps in the link:https://github.com/codenameone/CodenameOne/blob/master/CONTRIBUTING.md[CONTRIBUTING guidelines] to open a GitHub pull request (PR) instead of using the deprecated community wiki. Continuous integration lints and builds each PR automatically. A maintainer then reviews the PR before merging it. After the merge, the content appears in the web manual at https://www.codenameone.com/developer-guide/ and in the downloadable PDF at https://www.codenameone.com/files/developer-guide.pdf[https://www.codenameone.com/files/developer-guide.pdf]. From time to time, major revisions are collected for print-on-demand distribution. This guide focuses on tutorial and conceptual material. The complete API reference is available in the https://www.codenameone.com/javadoc/[Codename One Javadoc]. The framework source code and this manual live side by side in Git, so documentation and code improvements follow the same contribution workflow. +=== Where else to look + +This guide is the conceptual and tutorial reference. These resources cover the rest: + +* https://www.codenameone.com/javadoc/[Codename One Javadoc] -- the complete API reference. +* https://www.codenameone.com/developer-guide/[Web edition of this guide] and the + https://www.codenameone.com/files/developer-guide.pdf[downloadable PDF]. +* https://www.codenameone.com/how-do-i/["`How Do I?`" video tutorials] -- short screencasts for common tasks. +* https://www.codenameone.com/blog/[The Codename One blog] -- release notes and deep dives. +* https://www.codenameone.com/discussion-forum/[The community discussion forum] -- questions and answers. +* https://github.com/codenameone/CodenameOne/[The Codename One repository] -- source, issues, and pull requests. + <<< **Authors** diff --git a/docs/developer-guide/Accessibility-Semantics.asciidoc b/docs/developer-guide/Accessibility-Semantics.asciidoc index e61be68d285..739941cf655 100644 --- a/docs/developer-guide/Accessibility-Semantics.asciidoc +++ b/docs/developer-guide/Accessibility-Semantics.asciidoc @@ -1,10 +1,10 @@ -= Accessibility Semantics +== Accessibility Semantics Codename One components are lightweight: they're painted by Codename One instead of being represented by a native widget on every platform. The accessibility semantics API builds an explicit, immutable virtual tree from those components and presents it to VoiceOver, TalkBack, Windows UI Automation, AT-SPI, Java Access Bridge, and web accessibility APIs. The semantic tree is separate from the visual tree. This is important for renderer-backed controls, merged cards, decorative content, custom controls, and applications that need a reading order different from paint order. -== Built-in semantics +=== Built-in semantics Standard components work without extra configuration. `Button`, `CheckBox`, `RadioButton`, `Slider`, text fields, lists, tables, tabs, labels, dialogs, and containers infer appropriate roles, values, states, collection metadata, and standard actions. Renderer-backed `List` rows are exposed as stable virtual children even though no `Component` exists for each painted row. @@ -15,7 +15,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/A Changes to text, selection, focus, enabled state, bounds, scrolling, ranges, tabs, and the current `Form` automatically invalidate the native semantic tree. Custom components should call `accessibilityChanged()` after a semantic value changes outside a standard setter. -== Roles, names, values, and state +=== Roles, names, values, and state `AccessibilityRole` contains portable control roles including buttons, toggle buttons, switches, checkboxes, radio buttons, headings, links, images, text and search fields, sliders, progress indicators, lists, grids, rows, cells, headers, tabs, dialogs, alerts, menus, toolbars, combo boxes, trees, and separators. A port maps an unsupported role to the closest native role without discarding its label or state. @@ -26,7 +26,7 @@ Tri-state controls use `UNCHECKED`, `CHECKED`, or `MIXED`. Nullable Boolean prop Use `setRoleDescription()` only when the platform role isn't sufficiently specific. Localize labels, hints, descriptions, errors, pane titles, role descriptions, and custom action labels. -== Ranges and editable values +=== Ranges and editable values `AccessibilityRange` describes minimum, maximum, current value, increment, and an optional spoken value. The spoken value is useful for ranges such as ratings and durations where a raw number is ambiguous. @@ -35,7 +35,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/A Editable controls can provide `SET_VALUE` or `SET_TEXT` actions. Sliders and other adjustable controls should expose `INCREMENT` and `DECREMENT`. Standard components infer these actions. -== Custom accessibility actions +=== Custom accessibility actions An `AccessibilityAction` has a stable ID, a localized label, an enabled state, and a handler. Handlers always execute on the Codename One EDT, including when the action originates on a native accessibility thread. @@ -44,7 +44,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/A Use the standard IDs in `AccessibilityAction` for activation, long press, increment, decrement, set value, set text, selection, character or word cursor movement, focus, show-on-screen, dismiss, expand, collapse, scrolling, and clipboard operations. A custom ID must be stable and its label must not be null. -== Grouping and virtual descendants +=== Grouping and virtual descendants `AccessibilityGrouping` controls how a component participates in the semantic tree: @@ -65,7 +65,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/A Virtual bounds are relative to the owning component. Component nodes use their clipped absolute bounds. -== Traversal order +=== Traversal order Visual order is the default reading order. `setSortKey()` assigns an ordered semantic key among siblings. `setTraversalBefore()` and `setTraversalAfter()` express a direct relationship when a numeric order would be fragile. @@ -74,7 +74,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/A Keep traversal relationships within the same semantic parent. The tree builder resolves these constraints deterministically and preserves stable node IDs. -== Live regions and screen changes +=== Live regions and screen changes Set `AccessibilityLiveRegion.POLITE` for non-urgent updates and `ASSERTIVE` for errors or urgent status. `OFF` is the default. Live changes produce the corresponding platform notification or ARIA live update. @@ -83,7 +83,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/A Form changes and nodes with a pane title produce screen or pane transition notifications. `Display.announceForAccessibility()` remains available for an exceptional announcement that isn't represented by persistent UI. Prefer a live region for state that's visible on screen. -== User accessibility preferences +=== User accessibility preferences Semantics describe what a control means. Accessibility preferences describe how the user wants the interface presented. Read these preferences through `Display` or the matching static shortcuts in `CN`: @@ -130,7 +130,7 @@ IMPORTANT: Never use `getColorVisionDeficiency() == NONE` as permission to conve Preference detection uses each platform's public signals. iOS and macOS expose contrast, differentiate-without-color, motion, transparency, bold text, inversion, grayscale, switch labels, and VoiceOver; Apple doesn't expose the selected color-filter type, so the deficiency value is `UNKNOWN`. Android reads high-text contrast, display inversion and color-correction mode, animation scale, font-weight adjustment, and touch exploration. Windows reads High Contrast, client-area animation, and screen-reader settings. Linux detects the GTK HighContrast theme, disabled GTK animation, and ATK bridge activation. JavaScript uses `forced-colors`, `prefers-contrast`, `prefers-reduced-motion`, and `prefers-reduced-transparency`. The Java SE simulator provides deterministic controls for every portable signal. -== Collections +=== Collections `AccessibilityCollectionInfo` describes row and column counts, hierarchy, and selection mode. `AccessibilityCollectionItemInfo` describes row and column index, spans, position and size in a set, nesting level, and header status. @@ -141,7 +141,7 @@ Row and column indexes are zero-based. Position and set size are one-based; use Renderer-backed lists keep the full model size in their collection metadata but materialize only the visible rows, a small navigation buffer, and the selected row as virtual children. Forward and backward semantic scroll actions move the window. This keeps accessibility-tree work proportional to the viewport instead of invoking a cell renderer for every row whenever a large list scrolls. -== Inspecting and testing semantics +=== Inspecting and testing semantics `AccessibilityInspector` returns an immutable snapshot. A snapshot is safe to inspect without racing native accessibility callbacks and includes stable IDs, hierarchy, bounds, resolved state, actions, range and collection data. `toJson()` is suitable for diagnostic reports and tooling. @@ -154,7 +154,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/A The Java SE Component Inspector provides *Copy Accessibility Tree JSON* and *Audit Accessibility Tree* actions. Tests should assert resolved semantic properties instead of platform-specific spoken sentences. The `scripts/hellocodenameone` suite contains the cross-port conformance fixture used by native CI. -== Diagnosing and correcting an inaccessible screen +=== Diagnosing and correcting an inaccessible screen Accessibility review works best as a repeatable loop instead of a final checklist: @@ -181,7 +181,7 @@ Don't fix the warning by adding a label alone if the role, current state, range, .Auditing the semantic tree in the Component Inspector image::img/accessibility-component-inspector-audit.png[Component Inspector showing an accessibility audit,scaledwidth=80%] -=== Assertions that prevent regressions +==== Assertions that prevent regressions Snapshot the finished form and assert behavior that matters to a user. Prefer stable identifiers and resolved semantics over implementation class names or platform-specific speech: @@ -192,11 +192,11 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/A Keep targeted assertions for traversal order, validation errors, live-region behavior, ranges, and stable virtual keys. The `scripts/hellocodenameone` accessibility fixture invokes every portable preference API and exercises the resolved semantic tree on each native CI platform. -=== Native verification +==== Native verification The simulator finds portable problems, but the final pass must use VoiceOver, TalkBack, Narrator, Orca, or the browser accessibility tree. Navigate in both directions, activate every action, change adjustable values, enter and leave collections, trigger errors, and confirm that focus moves sensibly after a dialog, navigation, deletion, or live update. Record the platform, assistive-technology version, input method, and failed semantic node in the bug report; attach the inspector JSON when the native result differs from the portable snapshot. -== Platform mappings +=== Platform mappings [cols="1,2,2"] |=== diff --git a/docs/developer-guide/Advanced-Theming.asciidoc b/docs/developer-guide/Advanced-Theming.asciidoc index 0689bfe0111..2ebb5ab324a 100644 --- a/docs/developer-guide/Advanced-Theming.asciidoc +++ b/docs/developer-guide/Advanced-Theming.asciidoc @@ -770,13 +770,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/advancedth [[understanding-images-and-multi-images]] === Understanding images and Multi-Images -// HTML_ONLY_START -NOTE: This section provides a high-level overview of images. You dive deeper into the various types of images in the https://www.codenameone.com/manual/graphics.html#deep-into-images-section[graphics section]. -// HTML_ONLY_END -//// -//PDF_ONLY NOTE: This section provides a high-level overview of images. You dive deeper into the various types of images in the <>. -//// When working with a theme, you often use images for borders or backgrounds. You also use images within the GUI for various purposes and most such images will be extracted from the resource file. @@ -838,7 +832,7 @@ When configuring your styles, you should rarely use "Pixels" as the unit for pad and border thickness because the results will be inconsistent on different densities. Instead, you should use millimeters for all non-zero units of measurement. -As you now understand the <> it should be clear why this is important. +As you now understand the <> it should be clear why this is important. ==== Fractions of millimeters diff --git a/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc b/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc index 5aafd7cdaa5..373be8cb879 100644 --- a/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc +++ b/docs/developer-guide/Advanced-Topics-Under-The-Hood.asciidoc @@ -1530,13 +1530,7 @@ Another problem might be counterintuitive. iOS has screenshot images representin Its impractical to support something like HTML for the screenshot process since it would also look different from the web component running on the device. -// HTML_ONLY_START -TIP: You can read more about the screenshot process https://www.codenameone.com/manual/appendix-ios.html#section-ios-screenshots[here]. -// HTML_ONLY_END -//// -//PDF_ONLY -TIP: You can read more about the screenshot process <>. -//// +TIP: The launch storyboard replaced the build server's screenshot generator. See <>. === Integrating 3rd party native SDKs diff --git a/docs/developer-guide/Analytics.asciidoc b/docs/developer-guide/Analytics.asciidoc index d6c4fadcd08..4eacb306bcf 100644 --- a/docs/developer-guide/Analytics.asciidoc +++ b/docs/developer-guide/Analytics.asciidoc @@ -1,3 +1,4 @@ +[[analytics]] == Analytics The analytics API records how your application is used in the field. It's built around a small provider SPI: you register one or more providers with the `Analytics` entry point, then report screen views, events, user properties and crashes. The `Analytics` class fans every call out to all registered providers, but only after the matching consent has been granted. Consent is central to this API and is explained in <> below. diff --git a/docs/developer-guide/Animations.asciidoc b/docs/developer-guide/Animations.asciidoc index 6b7917e1c08..73f7c0e4054 100644 --- a/docs/developer-guide/Animations.asciidoc +++ b/docs/developer-guide/Animations.asciidoc @@ -116,17 +116,9 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/animations First the UI goes through an "unlayout" animation, once that completes the layout itself is performed. -// HTML_ONLY_START IMPORTANT: The `AndWait` calls needs to be invoked on the Event Dispatch Thread despite being "blocking." This is a common convention in Codename One powered by a unique capability of Codename One: `invokeAndBlock`. + You can learn more about `invokeAndBlock` in the -https://www.codenameone.com/manual/edt.html[EDT section]. -// HTML_ONLY_END -//// -//PDF_ONLY -IMPORTANT: The `AndWait` calls needs to be invoked on the Event Dispatch Thread despite being "blocking". This is a common convention in Codename One powered by a unique capability of Codename One: `invokeAndBlock`. + -You can learn more about `invokeAndBlock` in the <>. -//// The callback variant is like the `invokeAndBlock` variant but uses a more conventional callback semantic which is more familiar to some developers. It accepts a `Runnable` callback that will be invoked after the fact. For example, you can change the <> to use the callback semantics as such: @@ -220,13 +212,7 @@ Animations comprise two parts, the logic (deciding the position, etc.) and the p The separation of concerns allows you to avoid redundant painting for example, if animate didn't trigger a change return `false` to avoid the overhead related to animations. -// HTML_ONLY_START -You discuss low-level animations in more details within the https://www.codenameone.com/manual/graphics.html#clock-animation-section[animation section of the clock demo]. -// HTML_ONLY_END -//// -//PDF_ONLY You discuss low-level animations in more details within the <>. -//// === Transitions @@ -259,13 +245,7 @@ Themes define the default transitions used when showing a form, these differ bas TIP: `SlideFade` is problematic without a title area. If you have a `Form` that lacks a title area you would recommend to disable `SlideFade` at least for that `Form`. -// HTML_ONLY_START -Check out the full set of theme constants in the https://www.codenameone.com/manual/advanced-theming.html#theme-constants-section[Theme Constants Section]. -// HTML_ONLY_END -//// -//PDF_ONLY Check out the full set of theme constants in the <>. -//// ==== Replace diff --git a/docs/developer-guide/Authentication-And-Identity.asciidoc b/docs/developer-guide/Authentication-And-Identity.asciidoc index a52e7a8a63d..9e4f78804dc 100644 --- a/docs/developer-guide/Authentication-And-Identity.asciidoc +++ b/docs/developer-guide/Authentication-And-Identity.asciidoc @@ -1,3 +1,4 @@ +[[authentication-and-identity]] == Authentication and Identity This chapter covers Codename One's modern sign-in stack: OpenID Connect, Sign in with Apple, Google, Facebook, Microsoft Entra ID, Auth0 and Firebase Authentication. diff --git a/docs/developer-guide/Casual-Game-Programming.asciidoc b/docs/developer-guide/Casual-Game-Programming.asciidoc deleted file mode 100644 index c400b682a0d..00000000000 --- a/docs/developer-guide/Casual-Game-Programming.asciidoc +++ /dev/null @@ -1,80 +0,0 @@ -While game developers have traditionally used C/OpenGL to get every bit of performance out of a device, Java offers a unique opportunity for casual game developers. In this section you will build a simple card game in Java that can run unchanged on iOS, Android etc. - -Casual games are often the most influential games of all, they cross demographics such as the ubiquitous solitaire or even the chart topping Angry birds. Putting them in the same game category as 3D FPS games doesn't always make sense. - -Yes, framerates are important but ubiquity, social connectivity & gameplay are even more important for this sub genre of the game industry. The mobile aspect highlights this point further, the way app stores are built releasing often puts your game at an advantage over its competitor’s. Yet releasing to all platforms and all screen sizes becomes an issue soon enough. - -Typically a game comprises a game loop which updates UI status based on game time and renders the UI. But, with casual games constantly rendering is redundant and with mobile games it could put a major drain on the battery life. Instead you will use components to build the game elements and let Codename One do the rendering for you. - -=== The game - -You will create a poker game for 1 player that doesn't include the betting process or any of the complexities such as AI, card evaluation or validation. This allows you to fit the whole source code in 270 lines of code (more due to comments). The example also intentionally simplifies the UI for touch devices; technically it would be pretty easy to add keypad support but it would complicate the code and require more designs (for focus states). - -TIP: You can see the game running on the simulator at http://www.youtube.com/watch?v=4IQGBT3VsSQ[http://www.youtube.com/watch?v=4IQGBT3VsSQ] - -The game consists of two forms: Splash screen and the main game UI. - -==== Handling multiple device resolutions - -In mobile device programming every pixel is crucial because of the small size of the screen, but you can’t shrink down your graphics too much because it needs to be "finger friendly" (big enough for a finger) and readable. The device world has great disparity, even within the iOS family the retina iPad has more than twice the screen density of the iPad mini. This means that an image that looks good on the iPad mini will seem either small or pixelated on an iPad, but an image that looks good on the iPad would look huge (and take up too much RAM) on the iPad mini. The situation is even worse when dealing with phones and Android devices. - -Solutions exist, such as using multiple images for every density (DPI). But, this is tedious for developers who need to scale the image and copy it every time for every resolution. Codename One has a feature called `MultiImage` which implicitly scales the images to all the resolutions on the desktop and places them within the res file, in runtime you will get the image that matches your devices density. - -A catch exists though... `MultiImage` is designed for applications where you want the density to determine the size. An iPad will have the same density as an iPhone since both share the same amount of pixels per inch. This makes sense for an app since the images will be big enough to touch and clear. Furthermore, since the iPad screen is larger more data will fit on the screen! - -Game developers have a different constraint when it comes to game elements. For a game you want the images to match the device resolution and take up as much screen real estate as possible, otherwise your game would be constrained to a small portion of the tablet and look small. A solution exists though: you can determine your own DPI level when loading resources and effectively force a DPI based on screen resolution when working with game images! - -To work with such varied resolutions/DPI’s and potential screen orientation changes you need another tool in your arsenal: layout managers. - -If you're familiar with AWT/Swing this should be pretty easy, Codename One allows you to codify the logic that flows Components within the UI. You will use the layout managers to ease that logic and preserve the UI flow when the device is rotated. - -==== Resources - -To save some time/effort use the ready-made resource files linked in the On The Web section below. You can skip this section and move on to the code, but for completeness here is what was done to create these resources: - -You will need a gamedata.res file that contains all the 52 cards as multi images using the naming convention of ‘rank suite.png’ example: 10c.png (10 of clubs) or ad.png (Ace of diamonds). - -To do this you can create 52 images of 153×217 pixels for all the cards then use the designer tool and select "Quick Add MultiImages" from the menu. When prompted select HD resolution. This effectively created 52 multi-images for all relevant resolutions. - -You can also change the default theme that ships with the application in small ways to create the white over green color scheme. Open it in the designer tool by double clicking it and select the theme. - -Then press Add and select the `Form` entry with background `NONE`, background color `6600` and transparency `255`. - -Add a `Label` style with transparency `0` and foreground `255` and then copy the style to pressed/selected (since its applied to buttons too). - -Do the same for the `SplashTitle`/`SplashSubtitle` but there also set the alignment to `CENTER`, the `Font` to bold and for `SplashTitle` to `Large Font` as well. - -==== The splash screen - -The first step is creating the splash animation as you can see in the screenshots in <>. - -[[game-figure-2]] -.Animation stages for the splash screen opening animation -image::img/gaming-fig2.png[Figure 2,scaledwidth=40%] - -The animation in the splash screen and most of the following animations are achieved using the simple tool of layout animations. In Codename One components are automatically arranged into position using layout managers, but this isn't implicit unless the device is rotated. A layout animation relies on this fact, it allows you to place components in a position (whether by using a layout manager or by using `setX`/`setY`) then invoke the layout animation code so they will slide into their "proper" position based on the layout manager rules. - -You can see how you achieved the splash screen animation of the cards sliding into place in Listing 1 within the `showSplashScreen()` method. After you change the layout to a box X layout you invoke animateHierarchy to animate the cards into place. - -Notice that you use the `callSerially` method to start the actual animation. This call might not seem necessary at first until you try running the code on iOS. The first screen of the UI is important for the iOS port which uses a screenshot to speed startup. If you won’t have this callSerially invocation the screenshot rendering process won't succeed and the animation will stutter. - -You also have a cover transition defined here; it’s a simple overlay when moving from one form to another. - -==== The game UI - -Initially when entering the game form you've another animation where all the cards are laid out as you can see in <>. You then have a long sequence of animation where the cards unify into place to form a pile (with a cover background falling on top) after which dealing begins and cards animate to the rival (with back showing) or to you with the face showing. Then the instructions to swap cards fade into place. - -[[game-figure-3]] -.Game form startup animation and deal animation -image::img/game-figure-3.png[Game UI,scaledwidth=40%] - - -This animation is easy to do although it does have several stages. In the first stage you layout the cards within a grid layout (13×4), then when the animation starts (see the https://www.codenameone.com/javadoc/com/codename1/ui/util/UITimer.html[UITimer] code within `showGameUI()`) you change the layout to a layered layout, add the back card (so it will come out on top based on z-ordering) and invoke animate layout. - -Notice that here you use `animateLayoutAndWait`, which effectively blocks the calling thread until the animation is completed. This is a important and tricky subject! - -Codename One is a single threaded API, it supports working on other threads but it's your responsibility to invoke everything on the EDT (Event Dispatch Thread). Since the EDT does the entire rendering, events etc. If you block it you will effectively stop Codename One in its place! But, a trick exists: invokeAndBlock is a feature that allows you to stop the EDT and do stuff then restore the EDT without "" stopping it. Its tricky and out of scope for this article (this subject deserves an article of its own) but the gist of it's that you can’t invoke Thread.sleep() in a Codename One application (at least not on the EDT) but you can use clever methods such as `Dialog.show()`, `animateLayoutAndWait` etc. and they will block the EDT for you. This is convenient since you can write code serially without requiring event handling for every single feature. - -Now that you got that out of the way, the rest of the code is clearer. Now you understand that `animateLayoutAndWait` will wait for the animation to complete and the next lines can do the next animation. Indeed after that you invoke the `dealCard` method that hands the cards to the players. This method is also blocking (using and `wait` methods internally) it also marks the cards as draggable and adds that drag logic which you will later use to swap cards. - -In the animation department, you use a method called replace to fade in a component using a transition. diff --git a/docs/developer-guide/Events.asciidoc b/docs/developer-guide/Events.asciidoc index c45979b9be6..3f9ec37d048 100644 --- a/docs/developer-guide/Events.asciidoc +++ b/docs/developer-guide/Events.asciidoc @@ -136,13 +136,7 @@ in the Codename One AutoCompleteTextField. * https://www.codenameone.com/javadoc/com/codename1/ui/table/TableModel.html[TableModel] & https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] - the model for the https://www.codenameone.com/javadoc/com/codename1/ui/table/Table.html[Table] class notifies the view that its content has changed via this event, thus allowing the UI to refresh. -// HTML_ONLY_START -An exhaustive example of search implemented using the `DataChangedListener` appears in the https://www.codenameone.com/manual/components.html#Advanced-search-code[Toolbar section]. -// HTML_ONLY_END -//// -//PDF_ONLY -There is a exhaustive example of search that's implemented using the `DataChangedListener` in the <>. -//// +There is an exhaustive example of search that's implemented using the `DataChangedListener` in the <>. ==== FocusListener @@ -163,13 +157,7 @@ For example, in this code from the `Flickr` demo the https://www.codenameone.com include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/EventsJava009Snippet.java[tag=events-java-009,indent=0] ---- -// HTML_ONLY_START -NOTE: A better way of implementing this exact effect uses title animations https://www.codenameone.com/manual/components.html#title-animations-section[illustrated here]. -// HTML_ONLY_END -//// -//PDF_ONLY NOTE: There is a better way of implementing this exact effect using title animations <>. -//// ==== SelectionListener diff --git a/docs/developer-guide/Home.asciidoc b/docs/developer-guide/Home.asciidoc deleted file mode 100644 index d0cc1cadbe5..00000000000 --- a/docs/developer-guide/Home.asciidoc +++ /dev/null @@ -1,26 +0,0 @@ -= Codename One Developer Guide - -Welcome to the Codename One Developer Guide. This manual brings together tutorials, patterns, and reference material to help you design, build, and ship cross-platform apps with Codename One. Use the table of contents to explore the topics most relevant to your project, or follow the guide sequentially for a comprehensive tour of the toolkit. - -== Guide at a Glance - -* *Getting Started* – Installation, project creation, and first-run walkthroughs that help you configure your environment and build your first Codename One app. -* *UI & UX* – Layout managers, themes, components, and best practices for crafting responsive user interfaces across platforms. -* *Application Services* – Covers data storage, networking, push notifications, native interfaces, and other platform services you can integrate. -* *Deployment & Distribution* – Instructions for packaging, signing, and publishing to the major app stores alongside troubleshooting tips. -* *Appendices & Reference* – Supplemental material including glossary entries, configuration tables, and advanced topics for power users. - -Each section is organized to surface conceptual explanations first, followed by practical examples and deeper dives. Cross references within the guide link related topics so you can pivot between beginner-friendly introductions and advanced techniques. - -== Recommended Resources - -These resources complement the manual and are maintained by the Codename One team: - -* https://www.codenameone.com/manual/[Web edition of this guide] -* https://www.codenameone.com/files/developer-guide.pdf[Downloadable PDF release] -* https://www.codenameone.com/javadoc/index.html[Codename One JavaDoc] -* https://www.codenameone.com/how-do-i.html[How Do I? video tutorials] -* https://www.codenameone.com/blog.html[Codename One blog] -* https://www.codenameone.com/discussion-forum.html[Community discussion forum] - -For questions or improvements, open an issue or submit a pull request in the https://github.com/codenameone/CodenameOne/[Codename One GitHub repository]. diff --git a/docs/developer-guide/Index.asciidoc b/docs/developer-guide/Introduction.asciidoc similarity index 99% rename from docs/developer-guide/Index.asciidoc rename to docs/developer-guide/Introduction.asciidoc index 017953f6d2a..3ba32d08f6d 100644 --- a/docs/developer-guide/Index.asciidoc +++ b/docs/developer-guide/Introduction.asciidoc @@ -138,6 +138,7 @@ Before you proceed, this section explains some universal core concepts of mobile You can skip this section if you feel you're familiar enough with the core problems/issues in mobile app development. +[[density-and-dpi]] ==== Density Density is also known as DPI (Dots Per Inch) or PPI (pixels or points per inch). Density is confusing, unintuitive and might collide with common sense. For example, an iPhone 7 plus has a resolution of `1080x1920` pixels and a PPI of `401` for a 5-inch screen. But, an iPad 4 has `1536x2048` pixels with a PPI of `264` on a `9.7` inch screen... Smaller devices can have higher resolutions! @@ -317,6 +318,7 @@ image::img/codenameone-hello-world-title-label.png[Title and Label in the UI,sca Some complex ideas appear within this short snippet that this chapter addresses later when talking about layout. The gist of it's that you create and show a `Form`. `Form` is the top level UI element, it takes over the whole screen. You can add UI elements to that `Form` object, in this case the `Label`. You use the `BoxLayout` to arrange the elements within the `Form` from top to the bottom vertically. +[[ApplicationLifecycle]] .Application Lifecycle **** A few years ago Romain Guy (a senior Google Android engineer) was on stage at the Google IO conference. He asked for a show of hands of people who understand the `Activity` lifecycle (`Activity` is like a Codename One main class). He then proceeded to jokingly call the audience members who lifted their hands "`liars`" claiming that after all his years in Google he still doesn't understand it... @@ -478,7 +480,7 @@ image::img/build-server-results.png[Build Results,scaledwidth=80%] TIP: On iOS make sure you use Safari when installing, as 3rd party browsers might have issues -Once you go through those steps you should have the #HelloWorld# app running on your device. This process is non-trivial when starting so if you run into difficulties don't despair and seek help at the discussion forum (https://www.codenameone.com/discussion-forum.html) or stack overflow (https://stackoverflow/tags/codenameone/). Once you go through signing and installation, it becomes easier. +Once you go through those steps you should have the #HelloWorld# app running on your device. This process is non-trivial when starting so if you run into difficulties don't despair and seek help at the discussion forum (https://www.codenameone.com/discussion-forum/) or stack overflow (https://stackoverflow.com/questions/tagged/codenameone). Once you go through signing and installation, it becomes easier. TIP: You can also install the application either by emailing the installation link to your account (using the #e-mail Link# button) diff --git a/docs/developer-guide/MCP-Headless-API.asciidoc b/docs/developer-guide/MCP-Headless-API.asciidoc index fb4e6a1e0b7..5877340c591 100644 --- a/docs/developer-guide/MCP-Headless-API.asciidoc +++ b/docs/developer-guide/MCP-Headless-API.asciidoc @@ -1,4 +1,4 @@ -= MCP Headless API +== MCP Headless API The Model Context Protocol (MCP) headless API lets an application expose itself to a large language model agent. An agent such as Claude Desktop, Claude Code, Codex, or opencode can read the current screen, drive the user interface, and call tools the application publishes. @@ -6,7 +6,7 @@ The API reuses the accessibility semantics tree. The same immutable tree that de The socket transport runs anywhere the port can bind the loopback interface, which includes an application running on a device, so an agent can attach to a build on a phone as well as to the simulator. It's blocked on a release build; see <>. The stdio transport needs process standard input and so is supported by the JavaSE port, which powers the simulator and the desktop tooling. -== The MCP menu +=== The MCP menu Every desktop Codename One tool, including the simulator and Codename One Settings, gets a native `MCP` pull-down menu. The menu is added by the JavaSE port, so a tool gets it without any code of its own. It's the main way a user turns a tool into something an agent can drive. @@ -20,7 +20,7 @@ The menu has these items: Install writes a command that launches `MCPStdioLauncher`, a small bridge that relays the host's standard input and output to the running tool's socket. Hosts speak stdio and the tool serves a socket, so the bridge lets a stdio host such as Claude Desktop drive the already-running, human-visible tool. -== Starting the server +=== Starting the server Invoking the API is the switch. There is no build hint or property to set. The socket server lets an agent attach to a session a person is watching, which is the usual choice in the simulator: @@ -35,7 +35,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/M The stdio transport is the standard MCP local transport, exchanging newline delimited JSON-RPC messages. While it runs, application logging is redirected away from standard output so it can't corrupt the protocol stream. The stdio transport lives in the JavaSE port because it needs process standard input, which isn't available on every target. [[development-builds-only]] -== Development builds only +=== Development builds only The socket server refuses to bind on a release build and throws an `IllegalStateException` instead. @@ -59,7 +59,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/M Treat that call the way you'd treat any other decision to expose a control surface, because the device itself becomes the boundary. -== Built-in user interface tools +=== Built-in user interface tools Every server registers a small set of tools that read and drive the screen through the accessibility tree. An agent calls `ui_snapshot` to get the semantics tree of the current form as JSON, including the identifier, role, label, value, and state of each node, and the identifiers of the actions each node supports. @@ -67,7 +67,7 @@ The agent drives the screen with `ui_perform_action`, which performs an action s Actions run on the Codename One EDT, so an agent never touches the live component tree directly. Each action returns whether it succeeded together with a fresh snapshot. -== Publishing application tools +=== Publishing application tools An application publishes its own data and actions as MCP tools. A `Tool` has a name, a description, a JSON schema for its parameters, and a handler: @@ -76,13 +76,13 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/M The server merges these tools with the built-in tools when a host lists the available tools, and routes each call to the matching handler. The same `Tool` type is used by the Codename One AI client, so a tool defined once serves both a hosted agent and an in-application model. -== Screenshots and the simulator +=== Screenshots and the simulator The server exposes a screenshot of the current form as an MCP image resource, so a vision capable model can see the screen alongside the semantic tree. The resource is enabled by default and can be disabled per server. The JavaSE simulator drives the same MCP menu described above, so an application can be exposed and driven from the running simulator during development. -== Debug logging +=== Debug logging Because an agent drives the tool on its own, it helps to watch what it does. The server echoes the MCP conversation to the Codename One log at a level chosen through the menu's *Debug Logging* item or in code: @@ -91,7 +91,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/M The levels of `MCPVerbosity` are, from quietest to loudest: `OFF`, `ERRORS` (only failed calls), `SUMMARY` (one line per call), and `FULL` (every request and response). Each level includes the one below it. -== Registering with installed hosts +=== Registering with installed hosts The Install and Remove items in the MCP menu call `MCPClientRegistrar`, which detects the MCP hosts installed on the machine and writes a server entry into each host configuration, so an end user doesn't edit configuration by hand. Detection and registration are available to any Codename One tool, and they run inside the runtime because they use the portable `FileSystemStorage`. diff --git a/docs/developer-guide/Media-And-Audio.asciidoc b/docs/developer-guide/Media-And-Audio.asciidoc index 21bbec9c221..382a5c63c0b 100644 --- a/docs/developer-guide/Media-And-Audio.asciidoc +++ b/docs/developer-guide/Media-And-Audio.asciidoc @@ -1,16 +1,10 @@ [[media-and-audio-section]] -= Media and Audio +== Media and Audio [[media-audio-mediamanager-section]] -== MediaManager and MediaPlayer +=== MediaManager and MediaPlayer -// HTML_ONLY_START -IMPORTANT: `MediaPlayer` is a *peer component*, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues https://www.codenameone.com/manual/advanced-topics.html#native-peer-components[here]. -// HTML_ONLY_END -//// -//PDF_ONLY IMPORTANT: `MediaPlayer` is a *peer component*, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues <>. -//// The https://www.codenameone.com/javadoc/com/codename1/components/MediaPlayer.html[MediaPlayer] allows you to control video playback. To use the `MediaPlayer` you need to first load the `Media` object from the https://www.codenameone.com/javadoc/com/codename1/media/MediaManager.html[MediaManager]. @@ -34,7 +28,7 @@ image::img/components-mediaplayer-android.png[Video playback running on an Andro IMPORTANT: Video playback in the simulator will work with JavaFX enabled. This is the default for Java 8 or newer so recommend using that. [[pcm-audio-section]] -== PCM Audio Buffers, Mixing, and Effects +=== PCM Audio Buffers, Mixing, and Effects `AudioBuffer` stores raw interleaved float PCM samples in the `[-1, 1]` range, and `WAVWriter` writes those samples to a WAV file. diff --git a/docs/developer-guide/Miscellaneous-Features.asciidoc b/docs/developer-guide/Miscellaneous-Features.asciidoc index 70b5547b568..2da9ce7128b 100644 --- a/docs/developer-guide/Miscellaneous-Features.asciidoc +++ b/docs/developer-guide/Miscellaneous-Features.asciidoc @@ -450,13 +450,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/g .Captured photos previewed in the ImageViewer image::img/capture-photo.png[Captured photos previewed in the ImageViewer,scaledwidth=20%] -// HTML_ONLY_START -You show video capture in the https://www.codenameone.com/manual/components.html#mediamanager-section[MediaManager section]. -// HTML_ONLY_END -//// -//PDF_ONLY You show video capture in the <>. -//// The sample below captures audio recordings (using the 'Capture' API) and copies them locally under unique names. It also demonstrates the storage and organization of captured audio: diff --git a/docs/developer-guide/SVG-Transcoder.asciidoc b/docs/developer-guide/SVG-Transcoder.asciidoc index 4559d165ed0..9414f8fddfc 100644 --- a/docs/developer-guide/SVG-Transcoder.asciidoc +++ b/docs/developer-guide/SVG-Transcoder.asciidoc @@ -1,5 +1,4 @@ -= Build-Time Vector & Animation Images -:source-highlighter: highlight.js +== Build-Time Vector & Animation Images The build-time vector transcoder lets you author UI icons and illustrations as SVG or Lottie / Bodymovin JSON and have them rendered @@ -15,7 +14,7 @@ the SVG transcoder's model, the same `JavaCodeGenerator` emits a covers SVG in detail; Lottie gets its own section at the end that only calls out the parts that differ. -== Motivation +=== Motivation Codename One's older `Image.createSVG()` API depends on a per-platform native SVG renderer that ships only on a couple of backends (J2ME and @@ -34,7 +33,7 @@ emitted as a Java class that subclasses `com.codename1.ui.GeneratedSVGImage`, and rendered through the standard `Graphics` shape API. The same vector source produces pixel-perfect output at any size on every port. -== Quick start +=== Quick start . Drop your SVG next to `theme.css` in `src/main/css/`: + @@ -69,7 +68,7 @@ present, and the per-port build wiring (`IPhoneBuilder`, returns the transcoded image; CSS rules that reference the SVG by URL pick it up via the same registry. -== Why millimeters +=== Why millimeters `cn1-svg-width` / `cn1-svg-height` are the sizing knob you should reach for first. @@ -104,7 +103,7 @@ treated as design pixels at `DENSITY_MEDIUM`. If both keys appear on the same rule, `cn1-svg-width` / `cn1-svg-height` wins. -== How the build flow works +=== How the build flow works The `cn1app` archetype binds the `transcode-svg` goal to the `generate-sources` phase: @@ -139,7 +138,7 @@ in the user's compile output and weave `installGlobal()` into the generated `Stub` right before the first `init(Object)`; a project with no SVGs gets no weaving. -== Calling the registry yourself +=== Calling the registry yourself If you don't use `theme.css` but still want a transcoded SVG, construct the generated class directly: @@ -154,7 +153,7 @@ the one to prefer for the reasons in the previous section. requested dimensions from `getWidth()` / `getHeight()` and shares the animation clock with its source. -== SVG feature coverage +=== SVG feature coverage The transcoder targets the SVG 1.1 static-shape vocabulary plus the SMIL animation subset: @@ -187,7 +186,7 @@ with `Timeline`, you must register the image with a `Form`'s animation manager (or set it as a `Component.setIcon` with `isAnimation()` true) for the repaint loop to tick the SMIL clock. -== Lottie animations +=== Lottie animations Lottie / Bodymovin JSON files are picked up by the same `transcode-svg` goal. Drop them next to your CSS (or under `src/main/lottie/`) and the @@ -216,7 +215,7 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/g forward compatibility, but the archive container isn't yet extracted -- export your animation as a plain Bodymovin JSON for now. -=== Lottie feature coverage +==== Lottie feature coverage The parser targets the subset of Bodymovin a "spinner" or "pulse" animation typically uses. Anything outside the subset is dropped @@ -243,12 +242,12 @@ For animations that need higher fidelity than the subset above, export the relevant frame as an SVG and use the SVG transcoder path directly -- the runtime classes are identical. -== Troubleshooting +=== Troubleshooting `Resources.getImage("name.svg")` returns null or a 1×1 transparent PNG:: The transcoder didn't run, or it ran with zero source files. Confirm the asset lives under `src/main/css/` (or `src/main/svg/` / - `src/main/lottie/` for the dedicated dirs) and that the + `src/main/lottie/` for the dedicated directories) and that the `transcode-svg` goal is bound in the project POM. The same goal handles `.svg`, `.json`, and `.lottie` -- one goal, both formats. For arbitrary Resources bundles loaded outside the global slot, call diff --git a/docs/developer-guide/Testing-with-JUnit.adoc b/docs/developer-guide/Testing-with-JUnit.adoc index 4b8ea6e114f..0114f2b0d22 100644 --- a/docs/developer-guide/Testing-with-JUnit.adoc +++ b/docs/developer-guide/Testing-with-JUnit.adoc @@ -174,5 +174,5 @@ The UI driving (`TestUtils.*`) is identical -- `TestUtils` is independent of the === Cross-reference -* For the `AbstractTest` framework's full API surface (`TestUtils` helpers, `screenshotTest` tolerance algorithm, baseline management), see <> in the performance chapter. +* For the `AbstractTest` framework's full API surface -- the https://www.codenameone.com/javadoc/com/codename1/testing/TestUtils.html[TestUtils] helpers, the `screenshotTest` tolerance algorithm and baseline management -- see the https://www.codenameone.com/javadoc/com/codename1/testing/package-summary.html[com.codename1.testing Javadoc]. * For the `cn1:test` Maven goal, see <>. diff --git a/docs/developer-guide/The-Components-Of-Codename-One.asciidoc b/docs/developer-guide/The-Components-Of-Codename-One.asciidoc index 31565240a59..13c8c180b98 100644 --- a/docs/developer-guide/The-Components-Of-Codename-One.asciidoc +++ b/docs/developer-guide/The-Components-Of-Codename-One.asciidoc @@ -13,13 +13,7 @@ image::img/component-uml.png[Component-Container relationship expressed as UML,s Components are arranged in containers with layout managers. A layout manager decides how to arrange components inside the container. -// HTML_ONLY_START -You can read more about layout managers in the https://www.codenameone.com/manual/basics.html#component-container-hierarchy[basics section]. -// HTML_ONLY_END -//// -//PDF_ONLY -You can read more about layout managers in the <>. -//// +You can read more about layout managers in the <>. ==== Composite components @@ -44,13 +38,7 @@ This means a single `Component` can contain multiple nested `UIID`s. For example The lead component also handles events from a single source. Clicking another component in the hierarchy sends the event to the leading `Button`, which can make action events route to a different target than the apparent click target. That's why `getActualComponent()` exists. -// HTML_ONLY_START -You can learn more about lead components in https://www.codenameone.com/manual/misc-features.html#lead-component-section[here]. -// HTML_ONLY_END -//// -//PDF_ONLY You can learn more about lead components in <>. -//// **** === Form @@ -165,13 +153,7 @@ Notice that during the `show` call above the execution of the next line was "pau IMPORTANT: All usage of `Dialog` must be within the Event Dispatch Thread (the default thread of Codename One). This is true for modal dialogs. The `Dialog` class knows how to "block the EDT" without blocking it. -// HTML_ONLY_START -To learn more about `invokeAndBlock` which is the workhorse behind the modal dialog functionality check out https://www.codenameone.com/manual/edt.html[the EDT section]. -// HTML_ONLY_END -//// -//PDF_ONLY To learn more about `invokeAndBlock` which is the workhorse behind the modal dialog functionality check out <>. -//// The `Dialog` class contains many static helper methods to show user notifications, but also allows a developer to create a `Dialog` instance, add information to its content pane and show the dialog. @@ -1452,13 +1434,7 @@ indicator chains cleanly. [[mediamanager-section]] === MediaManager & MediaPlayer -// HTML_ONLY_START -IMPORTANT: `MediaPlayer` is a *peer component*, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues https://www.codenameone.com/manual/advanced-topics.html#native-peer-components[here]. -// HTML_ONLY_END -//// -//PDF_ONLY IMPORTANT: `MediaPlayer` is a *peer component*, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues <>. -//// The https://www.codenameone.com/javadoc/com/codename1/components/MediaPlayer.html[MediaPlayer] allows you to control video playback. To use the `MediaPlayer` you need to first load the `Media` object from the https://www.codenameone.com/javadoc/com/codename1/media/MediaManager.html[MediaManager]. @@ -1670,13 +1646,7 @@ In the first line you create a style animation that will translate the style fro === BrowserComponent & WebBrowser -// HTML_ONLY_START -IMPORTANT: `BrowserComponent` is a *peer component*, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues https://www.codenameone.com/manual/advanced-topics.html#native-peer-components[here]. -// HTML_ONLY_END -//// -//PDF_ONLY IMPORTANT: `BrowserComponent` is a *peer component*, understanding this is crucial if your application depends on such a component. You can learn about peer components and their issues <>. -//// The https://www.codenameone.com/javadoc/com/codename1/components/WebBrowser.html[WebBrowser] component shows the native device web browser when supported by the device and the https://www.codenameone.com/javadoc/com/codename1/ui/html/HTMLComponent.html[HTMLComponent] when the web browser isn't supported on the given device. If you intend to target smartphones diff --git a/docs/developer-guide/Video-Capture-Constraints.asciidoc b/docs/developer-guide/Video-Capture-Constraints.asciidoc index 9a5595d0459..725b6cf5843 100644 --- a/docs/developer-guide/Video-Capture-Constraints.asciidoc +++ b/docs/developer-guide/Video-Capture-Constraints.asciidoc @@ -20,12 +20,12 @@ This constraint can then be passed to `Capture.captureVideo()` to get the captur === Not all platforms support all constraints -how do you know if your constraints will be obeyed? If the platform doesn't support the max length, constraint, you may want to do something different. You can find out if a constraint is supported by asking out constraint object. +How do you know whether your constraints will be honored? When the platform doesn't support the maximum length, you may want to take a different path. Ask the constraint object whether it's supported. For example: -You can probe a constraint to see whether the entire constraint is supported (i.e will be obeyed), or whether any particular aspect of it will be supported using the following methods: +You can probe a constraint to see whether the entire constraint is supported (that is, whether it will be obeyed), or whether any particular aspect of it will be supported using the following methods: * `isSupported()` - True if all preferred constraints are supported. * `isQualitySupported()` - True if the preferred quality setting is supported. @@ -42,7 +42,7 @@ Suppose you want to capture a video with resolution 320×240. You would begin wi include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava004Snippet.java[tag=video-capture-constraints-java-004,indent=0] ---- -Explicit width and height constraints aren't well supported across platforms. Android doesn't support them at all. iOS supports 3 specific sizes. JavaScript supports it when running on a desktop browser or on Android - but not on iOS. Etc. +Explicit width and height constraints aren't well-supported across platforms. Android doesn't support them at all. iOS supports 3 specific sizes. JavaScript supports it when running on a desktop browser or on Android - but not on iOS. Etc. Find out if this constraint will be obeyed: diff --git a/docs/developer-guide/Wearables.asciidoc b/docs/developer-guide/Wearables.asciidoc index b320ecda4ce..06c33ff15e1 100644 --- a/docs/developer-guide/Wearables.asciidoc +++ b/docs/developer-guide/Wearables.asciidoc @@ -103,8 +103,8 @@ NOTE: `isWatch()` describes the device form factor, not the screen shape. A Wear OS device can be round or square; query the display safe-area insets (see <>) rather than assuming a rectangle. -=== Designing for the Watch [[designing-for-the-watch]] +=== Designing for the Watch A watch screen is small and is frequently round. A few practical guidelines: @@ -119,8 +119,8 @@ A watch screen is small and is frequently round. A few practical guidelines: without forking your code (the override layer activates on watch devices the same way platform overrides do elsewhere). -=== Sharing Data Between the Phone and the Watch [[wearable-data]] +=== Sharing Data Between the Phone and the Watch The two apps share no storage. `Storage`, `Preferences` and the SQLite database are per device, and there's no container that spans the pair, so a value written @@ -234,8 +234,8 @@ has no such gap. Gate a wearable feature on those rather than on `isSupported()` or you will offer it to someone holding a phone and nothing else. Add a `WearableStateListener` rather than polling. -=== Complications and Tiles [[watch-complications]] +=== Complications and Tiles A complication -- the small live readout on a watch face -- is the same idea as a home-screen widget: content-driven, rendered while your app isn't running, fed @@ -276,8 +276,8 @@ home here, because most complications are a gauge, a dial or a ring. Design for a glance. A complication is a few dozen pixels someone reads in under a second, so one number or one gauge beats any layout that has to be read. -==== What a Watch Face Actually Shows [[watch-complication-fidelity]] +==== What a Watch Face Actually Shows This is the part that surprises people, so it's worth stating plainly: **a complication isn't a small widget.** A watch face asks your data source for one @@ -348,7 +348,6 @@ image::img/wearables/apple-watch-simulator.png[Codename One running on the Apple [[watch-distribution]] ==== What the Watch App Runs Today -[[watch-entry-point]] `codename1.watchMain` is the watch app's entry point on both platforms. The watchOS build writes a stub of its own for that class and runs a second @@ -377,8 +376,8 @@ from the phone. None of this affects building, running or testing on the simulator or a device. -==== Native Code and the Watch Slice [[watch-native-code]] +==== Native Code and the Watch Slice Your Objective-C is compiled for the watch as well as the phone. The translator copies native sources through to every slice it produces, so a `.m` that imports @@ -400,8 +399,8 @@ no build hint for this and no attempt to infer it: which of your native sources can compile for watchOS is a question about that source, and the preprocessor is where it's answered. -==== Supported and Unsupported APIs on watchOS [[watch-supported-apis]] +==== Supported and Unsupported APIs on watchOS Because watchOS lacks UIKit views, GPU rendering and several iOS frameworks, the APIs that depend on them aren't available on the watch slice. They're guarded @@ -432,8 +431,8 @@ usual. Cloud builds generate the watch target through the same iOS build -- declare the watch main class and build for iOS -- and the same applies there, so a standalone cloud build returns the iOS archive. -=== Android (Wear OS) [[wear-os-android]] +=== Android (Wear OS) A Wear OS app is a regular Android app. The Codename One Android port renders the UI with the same pipeline it uses on phones, so no special rendering backend is @@ -499,8 +498,8 @@ dependency and the listener service automatically. The `android.playService.wearable` hint remains for apps that want to call the Data Layer APIs directly. -=== Feeding a Complication from the Phone [[watch-complication-mirror]] +=== Feeding a Complication from the Phone A watch app has its own storage. Nothing the phone writes is visible there, on either platform -- on Apple the App Group identifier is the same string but diff --git a/docs/developer-guide/Working-With-iOS.asciidoc b/docs/developer-guide/Working-With-iOS.asciidoc index ff327851b20..72f27e52fd4 100644 --- a/docs/developer-guide/Working-With-iOS.asciidoc +++ b/docs/developer-guide/Working-With-iOS.asciidoc @@ -20,6 +20,7 @@ If you've access to a Mac, connect the device, open Xcode, and use the device ex - Check that the `ios.includePush` build hint matches your iOS provisioning. It must be false if your provisioning profile doesn't include push. [[section-ios-launch-screen]] +[[ios-launch-storyboard]] === Launch screen storyboard best practices Launch screen storyboards are the default approach for Codename One iOS builds. Apple requires a storyboard-based launch experience for modern devices, so the legacy screenshot generator was removed in favor of a single adaptive layout. You can still opt back into the old behavior by setting the `ios.generateSplashScreens=true` build hint, but it's best to use a storyboard unless your use case can't be expressed with Auto Layout. diff --git a/docs/developer-guide/appendix_goal_test.adoc b/docs/developer-guide/appendix_goal_test.adoc index 653c7895e44..06c18b3f523 100644 --- a/docs/developer-guide/appendix_goal_test.adoc +++ b/docs/developer-guide/appendix_goal_test.adoc @@ -1,3 +1,4 @@ +[[appendix-goal-test]] === Run tests (`test`) Runs Codename One unit tests. diff --git a/docs/developer-guide/basics.asciidoc b/docs/developer-guide/basics.asciidoc index 74ebc1d0365..5365b9d1754 100644 --- a/docs/developer-guide/basics.asciidoc +++ b/docs/developer-guide/basics.asciidoc @@ -33,6 +33,7 @@ image::img/codenameone-form.png[Structure of a Form,scaledwidth=50%] Now that the structure is clear, open the Java file `TodoApp.java` in the project you created. You should see the lines that set up the UI in the `start()` method: +[[layout-managers]] ==== Layout managers A layout manager decides the size and location of components within a `Container`. Every `Container` has a layout manager. The default layout manager is `FlowLayout`. diff --git a/docs/developer-guide/cn1libs.asciidoc b/docs/developer-guide/cn1libs.asciidoc deleted file mode 100644 index 539b92ccc0c..00000000000 --- a/docs/developer-guide/cn1libs.asciidoc +++ /dev/null @@ -1,3 +0,0 @@ -=== cn1libs - -This section is under construction diff --git a/docs/developer-guide/component-selector.asciidoc b/docs/developer-guide/component-selector.asciidoc index fdbc62f8af0..98806005b37 100755 --- a/docs/developer-guide/component-selector.asciidoc +++ b/docs/developer-guide/component-selector.asciidoc @@ -2,13 +2,13 @@ The `ComponentSelector` class is a new class that brings the power of jQuery to Codename One. While it isn't *actually* jQuery, it's influenced by it. If you're not familiar with jQuery, here is the 10 second intro. -jQuery is a JavaScript library, created by John Resig in 2006, that has become a staple of browser-based UI development. As of March 2017, over 70% of web sites are using jQuery. The initial problem that jQuery solved was browser incompatibility issues. It provided a consistent API for most useful DOM methods so that the developer didn't have to spend all their days and nights fighting with browser compatibility issues. In a way, it did for JavaScript, what Codename One does for mobile apps. +jQuery is a JavaScript library, created by John Resig in 2006, that has become a staple of browser-based UI development. As of March 2017, over 70% of websites are using jQuery. The initial problem that jQuery solved was browser incompatibility issues. It provided a consistent API for most useful DOM methods so that the developer didn't have to spend all their days and nights fighting with browser compatibility issues. In a way, it did for JavaScript, what Codename One does for mobile apps. -The other thing that jQuery did, was provide an elegant way to select and manipulate DOM elements (that is, HTML tags in a web page). It enabled developers to form sets of elements using a CSS-like syntax, and to operate on all elements of those sets, as if they were single elements, and they provided a fluent API to enable developers to chain multiple calls together, and delay onset of carpel tunnel for at least a few extra years. +The other thing jQuery did was provide an elegant way to select and manipulate DOM elements (that is, HTML tags in a web page). It enabled developers to form sets of elements using a CSS-like syntax, and to operate on all elements of those sets, as if they were single elements, and they provided a fluent API to enable developers to chain multiple calls together, and delay onset of carpel tunnel for at least a few extra years. With the new `ComponentSelector` class, you've adopted the following aspects of jQuery: -1. **CSS-like Selection Sytax** - Support for a CSS-like syntax for "selecting" Components to be included in a set. +1. **CSS-like Selection Syntax** - Support for a CSS-like syntax for "selecting" Components to be included in a set. 2. **Fluent API** - The API is fluent, meaning you can chain multiple method calls together and reduce typing. 3. **Working on Sets** - Methods of this class operate on sets of components as if they're a single component. As you'll see, this can be incredibly powerful. 4. **Effects** - Support for common effects on components such as fadeIn(), fadeOut(), slideDown(), and slideUp(). @@ -59,7 +59,7 @@ Finally, ComponentSelector provides a `find()` method that implicitly uses the c ==== Selector syntax -far You've glossed over the selector syntax by saying it's `like CSS`. Though it's *like* CSS selectors, it's necessarily different to accommodate the differences between Codename One's component model and JavaScript's DOM. Rather than go into a technical language definition, You'll use some examples to illustrate the capabilities of your syntax. +Until now the selector syntax has only been described as `like CSS`. It's *like* CSS, but it differs where Codename One's component model differs from the JavaScript DOM. Rather than a formal grammar, the examples below show what the syntax can express. . `$("Label")` - The set of all components on the form with UIID "Label" . `$("#MyField")` - The set of all components on the form with Name `MyField`. @@ -103,7 +103,7 @@ Were you setting these values on the "selected" style, the "unselected" style, t include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava004Snippet.java[tag=component-selector-java-004,indent=0] ---- -If a component was in "selected" state, then this would changed the selected style. If it was in pressed state, then it would change the "pressed" style. Etc. What if you wanted to specifically change the styles in the "pressed" state. Then you would call `selectPressedStyle()` prior calling your style mutation methods. For example: +If a component was in "selected" state, then this would change the selected style. If it was in pressed state, then it would change the "pressed" style. Etc. What if you wanted to specifically change the styles in the "pressed" state. Then you would call `selectPressedStyle()` prior calling your style mutation methods. For example: [source,java] ---- @@ -142,6 +142,10 @@ Also to these basic effects, ComponentSelector wraps all the existing animation For example, Consider this example, that shows a button that replaces itself and all siblings with replacement labels, and then replaces them back: +[source,java] +---- +include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava007Snippet.java[tag=component-selector-java-007,indent=0] +---- <1> Finds all siblings of the source button <2> Call `replaceAndWait()` with a mapping function to define the component that should replace each component. This will replace each component in the set with a replacement component in its respective container. This will also return a new ComponentSelector with the set of replacement components. <3> In your "mapper" callback, you will return a new Label component to replace each existing component. @@ -165,9 +169,9 @@ This can get interesting when you start combining these methods. For example, `$ === More demos -you've posted a demo app that demonstrates a few of the things that you discuss https://github.com/shannah/cn1-component-selector-demo[here]. The file containing most relevant source code is https://github.com/shannah/cn1-component-selector-demo/blob/master/src/com/codename1/tests/stylebuilder/StyleBuilderDemo.java[here]. +A https://github.com/shannah/cn1-component-selector-demo[demo app] exercises several of the features covered here. Most of the interesting code is in https://github.com/shannah/cn1-component-selector-demo/blob/master/src/com/codename1/tests/stylebuilder/StyleBuilderDemo.java[StyleBuilderDemo.java]. -you've also posted a short screencast of the demo app on youtube: +A short screencast of the demo app is on YouTube: video::Cue0fnJdB4U[youtube] diff --git a/docs/developer-guide/developer-guide.asciidoc b/docs/developer-guide/developer-guide.asciidoc index f4097901b87..1b61a722511 100644 --- a/docs/developer-guide/developer-guide.asciidoc +++ b/docs/developer-guide/developer-guide.asciidoc @@ -43,34 +43,48 @@ toc::[] include::About-This-Guide.asciidoc[] += Getting started + +include::Introduction.asciidoc[] + include::Maven-Project-Workflow.asciidoc[] -include::Index.asciidoc[] += Core concepts -include::basics.asciidoc[] +include::The-EDT---Event-Dispatch-Thread.asciidoc[] -include::Advanced-Theming.asciidoc[] +include::Events.asciidoc[] -include::Native-Themes.asciidoc[] +include::Device-Input-And-Form-Factors.asciidoc[] -include::css.asciidoc[] += User interface + +include::basics.asciidoc[] include::The-Components-Of-Codename-One.asciidoc[] +include::component-selector.asciidoc[] + include::Rich-Text-And-Code-Editing.asciidoc[] include::Accessibility-Semantics.asciidoc[] -include::MCP-Headless-API.asciidoc[] +include::Animations.asciidoc[] -include::Media-And-Audio.asciidoc[] += Theming and styling -include::Animations.asciidoc[] +include::Advanced-Theming.asciidoc[] -include::The-EDT---Event-Dispatch-Thread.asciidoc[] +include::Native-Themes.asciidoc[] + +include::css.asciidoc[] + += Graphics and games include::graphics.asciidoc[] +include::SVG-Transcoder.asciidoc[] + include::3D-Graphics.asciidoc[] include::Augmented-Reality.asciidoc[] @@ -83,12 +97,26 @@ include::Game-Builder.asciidoc[] include::Game-Assets.asciidoc[] -include::Events.asciidoc[] - -include::Device-Input-And-Form-Factors.asciidoc[] += Data, media and networking include::io.asciidoc[] +include::Network-Connectivity.asciidoc[] + +include::Annotation-JSON-XML-Mapping.asciidoc[] + +include::Annotation-Component-Binding.asciidoc[] + +include::Annotation-SQLite-ORM.asciidoc[] + +include::Media-And-Audio.asciidoc[] + +include::Video-IO.asciidoc[] + +include::Video-Capture-Constraints.asciidoc[] + += Device and platform services + include::Push-Notifications.asciidoc[] include::Notifications-And-Background-Execution.asciidoc[] @@ -103,33 +131,39 @@ include::In-Car-Experiences.asciidoc[] include::Motion-Sensors.asciidoc[] -include::Miscellaneous-Features.asciidoc[] +include::Near-Field-Communication.asciidoc[] -include::Video-IO.asciidoc[] +include::Bluetooth.asciidoc[] -include::performance.asciidoc[] +include::Nearby-Devices.asciidoc[] -include::Testing-with-JUnit.adoc[] +include::Call-Management.asciidoc[] -include::Advertising.asciidoc[] +include::VPN.asciidoc[] -include::Crash-Protection.asciidoc[] +include::Health.asciidoc[] -include::Analytics.asciidoc[] +include::Smart-Home.asciidoc[] -include::Commerce.asciidoc[] +include::Printing.asciidoc[] +include::Deep-Links-Routing.asciidoc[] -include::Monetization.asciidoc[] +include::App-Intents.asciidoc[] -include::App-Review.asciidoc[] +include::Document-Provider.asciidoc[] include::Apple-Wallet-Extension.asciidoc[] -include::Document-Provider.asciidoc[] +include::Miscellaneous-Features.asciidoc[] -include::Advanced-Topics-Under-The-Hood.asciidoc[] += AI and agents + +include::Ai-And-Speech.asciidoc[] +include::MCP-Headless-API.asciidoc[] + += Security and identity include::security.asciidoc[] @@ -143,43 +177,39 @@ include::Authentication-And-Identity.asciidoc[] include::Phone-Number-Verification.asciidoc[] -include::Deep-Links-Routing.asciidoc[] - -include::App-Intents.asciidoc[] - -include::Annotation-JSON-XML-Mapping.asciidoc[] += Monetization and analytics -include::Annotation-Component-Binding.asciidoc[] +include::Commerce.asciidoc[] -include::Annotation-SQLite-ORM.asciidoc[] +include::Monetization.asciidoc[] -include::Ai-And-Speech.asciidoc[] +include::Advertising.asciidoc[] -include::Near-Field-Communication.asciidoc[] +include::Analytics.asciidoc[] -include::Bluetooth.asciidoc[] +include::App-Review.asciidoc[] -include::Nearby-Devices.asciidoc[] +include::Crash-Protection.asciidoc[] -include::Call-Management.asciidoc[] += Testing, performance and debugging -include::VPN.asciidoc[] +include::Testing-with-JUnit.adoc[] -include::Health.asciidoc[] +include::performance.asciidoc[] -include::Smart-Home.asciidoc[] +include::On-Device-Debugging.asciidoc[] -include::Printing.asciidoc[] +include::On-Device-Debugging-Android.asciidoc[] -include::Network-Connectivity.asciidoc[] += Build, sign and ship include::signing.asciidoc[] -include::Working-With-iOS.asciidoc[] +include::App-Store-Submission.asciidoc[] -include::On-Device-Debugging.asciidoc[] += Platform guides -include::On-Device-Debugging-Android.asciidoc[] +include::Working-With-iOS.asciidoc[] include::Working-With-Javascript.asciidoc[] @@ -187,23 +217,28 @@ include::Working-with-Mac-OS-X.asciidoc[] include::Working-with-Mac-Catalyst.asciidoc[] -include::App-Store-Submission.asciidoc[] +include::Working-With-Windows.asciidoc[] + +include::Working-With-Linux.asciidoc[] include::Desktop-Integration.asciidoc[] include::Desktop-Windows.asciidoc[] -include::Working-With-Windows.asciidoc[] -include::Working-With-Linux.asciidoc[] - include::Wearables.asciidoc[] include::TVPlatforms.asciidoc[] += Under the hood + +include::Advanced-Topics-Under-The-Hood.asciidoc[] + include::Working-With-CodenameOne-Sources.asciidoc[] include::Skin-Designer.asciidoc[] += Appendices + include::Maven-Appendix-Archetypes.adoc[] include::Maven-Appendix-Goals.adoc[] diff --git a/docs/developer-guide/img/game-figure-3.png b/docs/developer-guide/img/game-figure-3.png deleted file mode 100644 index 59c74d46d5992610b8aa572972580ceee13760fc..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 198269 zcmV)sK$yRYP)4Tx062|}Ro!pfR1`mnZ(O7nKcKOW4i$^9Ra0BJ8yc;~21%2p=|UR0&DbiW z$#rfTQ`a`O(`{9s_5yDV_yd5l2Of}kLK+Oj_Ok5(v`JGz71bo9J#^YYXp{DWs&KBa zQ@dTpxRI}aIp=pi@6k0t$5)!;m`NF6-tt{FpOKHBn3g+MAqmexC-gw4rh87hTrL7G z#)U`L!(So6-Zux@>;H3gR;i~0B%VTSS3P|m@o9jRsXML@Al^p#@G0Lx-0?i(9WEw_ zSYddU<1E8793KxjQ|c&UmW!mTC>k>?{om1c9S zUx<6_jj_!T&^M{wWM#>IBbOSf*xP<^F{$j$aOQ5Y{cT zROCL1M7^NKKL z&(yA}mSw#iM0^;IB{ZO5!wl{^Sg-*ysE~&Yz8!E;Qv(A`lu*=Clo*MpVGd>OdF6n^ zam1Jntk;<}MrqIC5$=Q>n{*R}?8oOIDUw5En2dl--Xw34!z7E+5pr-OgyQ-soSab)C%saskMla`aQLVzg0+MZf20tJU&K{hZoBrUc+U4e9&3o zw|KmGEe4#xz17wBu{f`SS_4i66?j31EjY7n{zGfhONK~c+td!TS#B}JoR}5UAd7p& z5phTyXSkK0xCeD3xaYP^o&J~#Xp9xFb0C;HHml5fA<%h1eR|qw7wxF+oNL9T1Aits?sKNIwvGaN)^WO$I^cUV)HzL_| z1K?{9p!>B*)`xfEv!4N6IG{J&h49W#Bz^(#YWw%`e_a{8n{G9m5AeR~_yl0%<7V@p zF?QOXePdZ7RogGT2 zGsl{O{*`FTZEqt8HNWc$h1a zjVYc=DHJYJB9&1(OdEo?r!wlv@S zxaLr(NYQ9i;c(c^W50VmUODf%K z&%v&?DctYE?*l)(_wbxuw~zC;=gpqG4Ch%?6mrku!2RquqmA*Ty|?QN74gI*6aCMg zJ)_xed>2n;mCo`k4&b|t&CyRQ4|iqYlQEbt27fM`OX2mO$#*&BkPpZS_)fVX&rlK? za<8%p6?w~*LWZd*sZ6?BnLwQT*&$Cltlrixee@&et6}Ray7iVjv|!-^wY9b?1D$LH z0`ML*Hu+}v$7olb% zKDDHgH{PsDeB6&5b0GtAj_*w7h4SC=&925s_|E1unb+6b=dRQE&dDj{S4nB9lSy_h zCi8-!AkR!P&zp`Gd6{SX*tN&t-C~|C<3}4NhjRiGlH42nJb-3d2xI%C(OA-EiY~^{eY{(LWSxlU@Y_kuj`&lVfcfiI>g!cN=T3?#J4X ze)OaI!WS+X!&+p}?qaRUfnYGSTUcwlj5`i^{?^3WBv@Ov3#=_xPiv>n18W<>+FM41 zwdiL3I$l8@560RISnEZf21u1i7+${}uois}&U!mztuK&M#+OzaJt~_EE1BXCd$Ov^ zt2_Sm8~NY+-qY35*`>15GL@8IlrT)8Yi;Xt#zh=Muaw_FHKw zWtFlhN~QpH^VtkSW4t(qhZh5+*pzRK9A7A+ZgfvySPIqT)ZUX)S8oFfX-;)@6BR}` zO2<&PQ@tn*C_$zgp&aCx%a_X=((c3t{bP#GP)fF)%fK<%2sXpIvb;nA4EUCgE@w~} z9X4;-qVn=`lrbNQY0_PDRdu!6+uEEV5{Z;Rz%n&8wc-%y)P(U9)C*lqn00q`sja;o z#V@L9(`UMQ)~#EI!BV7(iV7J$H`ljm+O%ohkb!5HSKJ1EGn`^JoRT|muGjK@FujJ{ z2gNmLw7zv?f#o!T*t3n`VmBsit?49paMP-FE zQmZOT@&*Mnb#(M7gCdBO8mqte#yR(P^o<0s8j8SIg^-{alz9F7b5-~}fmhvYk>%TRo0zYnJ6 zK;ARHg|#%kwhBi|LLFVbD&d*RWm9TyYF2ek4G(G3(ZC!hc5TK}Wf&hNr4bB}q?((X z;K6EV?4~hr&Dd{mZ{xY_QElybjh`?0FU~C6x$Tzs#nMAny!sH+6TbH`=D^adoN;mvBIjvhk>VS!5;u8Q(dvhrBZr zaaUs`d}njUka@MW)m&#rP0bzdI!nvSw6VV4@r+%E@t!&OjBlEn8XdnF@A)DjZQa`J z98WdXHR|c@buzN8tzDfR9X!KcO`kE_bPR2(-{53lF}!BasLeTP(xg}M+V1e4JyUy9 z<_&&EcSZBU_kKS>Wx~W{9_NxwbgH?1Gc+txDuL|iOE^O}weM)Ya9!ORP1SXBaz%wPRRY;5mCWtS~3p?qO{WSc@D)W*OF+J`+zQG={aihqd-B z4#e6F^3imW0@l`nwRJne+J;Rn4#%DI51phic1PaXK6_H$*?hYv^9*a*X#m#tfwi6e zSepiG{jb8>31DqRKEv9w@{wUJ`Z6561J)Wo8Q$&`YsU@7+VO7)tj&V8&0wtsEecpW zVd6xmGnmY`=hAG0-NxFPv&~r3=8a%&MLCYZFrT($opHC_`C{z#i@PO3>+Z06@zZPK6q@F+JqjL|%c`7P>p zqYl`6sos0iaVpNB;F$)%9@yc|E8%sY?UCI17)l8r{16Y`)(qGB`ptMVKaa5L)8$uu zPxt=yFS_i~OP%n#{JY=P0S6qUiQ_8u$Gh&)<(FQj4I9?$2S56;>NnKuXV?D3G3A99 z{;S@5#+j;hJ0Y?6c45qAy&m8*jKl%b$5h z)#E4aa8bjZ?+zRD!4&P?83T>0!TXuO9ZqQ;xR1w+vd{g?5wQst`}E`Mu2B8fmlY~O zVP``#IV`%71zf#>4)u0Kb?m|CVbo$Yz?5yxZ76wNJkXW8>E@gD#N&@^%9N=(?O*;y zgZTt~##>oN{1O3Iwl-O{ePJi!vb>>-T>1$v8svdmcZ#wq4D%z-9e?_x4m{{!ec}@r=x0B>R!=?oq)s{YRDJCH^R?GrGxg^`|5@Mq<~I~U zxN%$bvGdN;-~ax1J-~d|Uw?yMdif=N^^z~?>)-gc&i?R+iICeF=K*)9a~*tL_IXe8 zoy})p^_qu=NVMM~XGA)Qux@QyrAx2)vL?-}#(C14&osePj%9)D>4rCO+6AZUolDPE z6;7=7_ANL#lF-PbO`A69m)BkA`2MJ)-h~2qn2S8Zo`7i#qZK*DcIH?xz0yy(@eycR zQ(LRWix;?4?(Xixpbn}6!>^~iN6{PxPp(UzqO7`cD*W>1NA=XxTNR7eac+BYKKH4* zGNH3hKTQj!)L`_OJH>b||KCXDot@Bd_Yyy(mMwcipZ)A-v~b}fWZrl6-{1L;Zur&p znu@dS`d{6kva(X}z^ALPx)S>Qf@iQqKmFOyb^R}|)1UtMM@^nG8JYPdz4P$Hor3q} zFMUyeyZ=7zy>xH(3+SA4&e0dY@C7Ygv`9ZfR-S#<>FUC`UH14BO2BsmpPk{(cQ9WJ z-hS}!13wQX?`%8c2CVIUjaWMytR(}e&$h-v3 z(F_0eUy+w*s2Z%j=IX0-)s_F3$J$n~_J1$cC6{~|nc1r6o_}7S`}{@v^^G^`8F06% zZo&?iHr)B{urVL@q|DoGtVQ18(%FW!KLTqvHh{H}A+Yw4^Dt@+YyE1&0dIka*N3%h z!P+Ol+M_gSVl58QU1O~WtZe{mZ`p>m)4to`=4zUiJ@(}xXf=YX|$-0>%ewdRaQoa+3KeM}EM^w2h} zeeuN?_0_L_S>FU}KlqlwTFguMXiL*dU3SGK!(iiGYG~N%4BFq`bdxhAEK>38GtctyAFFO64$VzXD0czU zZ$nN{S{hpm>3ofiO%8bG&6}@%_T7h6zgt~o>7auSRUnKNovPI4#xC7{!wSuQ$I&|Z z`~~ulLp{Lb)Be&@J#p_{y8hQU>0|FZO^H|!s@`^hwe+^3#!xq4@Na{`HrkhhNcmLj zK2l{Zn)=-92GPhMD~aDYb=Y@4Wd7bjz)`>9mvHt=n$BS#`DJv~KMhjJHRLqHQ4(QjWJj22hi} zJ75fVz5_OOZwFt`;NJ&-9z6f<{yuPfOj@V(6RW~l#Xy~u(;+7xtcq!P&2lkPiv7yK ze8K?s`#P2C4QctE^?LgGR#MV2ATfxmG2E`huz2K=M|8po?*<$m)P48etI3lm>q!i^ zefQay=xdnt(FPakv2<@!<^m{&t5>hqVTT>2D=zyUX_FxxhcS59op-tD`auUDq{^C! zO2&&7Oqb}|EAAy?J6~sg;$W3c_bTp7DYtd09=Y>wUHSbV>*9YuU;Z>{y%@BGF%rJB zIR?{u;JvcN*UK6Hh!zGiT1ygAe{qhaP&E767)54UL+B=lJVi|CVmO`PVpT?r{bg+K`@m_Guh4 z8{Bj2$1rrM+!$c5*RUOUEd_cF^*WdqgSQ>-_rcry(s=9mFPB5Gg}SulusVJ0W5;Vn z>+>onlfv1_Squ$d59JUsZFwo8`)|HSn;Tl}#yGmHU%S>h2+VOZZ{9rJb=RGa-u8T( zW8{DX4!%{ferzicGAR~8XBp1kR%o3$Ck_=qEpB)g^6MLZWST&QWY=AZ-@E0itO z?{2tHTRMw%?5XE!!omurOUMf7+DFUpTdo^_d#lbl{XLpHxr!9Sm{XAK7^BH|Hh(G^ zcTr|@CfK!DG~&8n{7g3^ufA~c#k%&VKXuQhMOdH3`SzL5e$L52bDA7?{P8;W*yHr0 zAO1kU#K=AI_@lKE=Vgot#z`liqR)Qrv--%{AHc}H7sqFfwrt*r1LiS)@53QlrYO&@ zm6#M#yzP7j-}~WsYq;NS&cYZxcj5cO>u|ppwky2)h}0S~Wxe^x?D*M1edL0ph{ms0 zNhQ&GoGr?bmV{yJS10N3rTY7C?$^rIo1A2^QUiz4KUG zQlf(nK1BW^ur^((jRmaTe=yeW?XdQjVC{LkiM7MgYVhxa?~`Gz8MpSm$vcyKS6p$q zJ_pbJ@ehB{-FMx!ORU}hfCC)Xn)Ai5_GCQrmH}^0`0ZGG!Zxg3zizD_U-rm0tWCuG z-rQKrwVD<*5Ni)LtepbZdWOK-=fGMrG%{eo&an1=oKcoVu`{eSKDB72!`f9V9oAlc z`FC9j%&V}L5~E}ktWA~Z=a>IgrIU8V+U%CaVC`RY)%Sm>i~iYIi!6E&gLWeU8N*uB z--g24&s}t}9)0vtbdv{M;z$DDxa4)()261q|HP z>tOjmc)Q_#AH4lwter3mto^rlX;Wt&Yct89JicDAmQ-}If-}7n^s6f#)x*o4)U3&i z`HaxBrGy>!KQzvrT9-vxLqLi_Wd{Y+D*PSf(`PwA|)-cNMs4p&op z;DHBf4F<-|H~mJN5ENT6>Rws#vR1BKsRaPSZ$(gV^u@zm2by(UQXvqxD{Dt!ZLPc4;obOtHY0Nw(O@k23?{`!}{z%(%z zJ+f=I2w@`+mOV@*L@e&R_peSVsV0@Pjg-&#zwiBOBQ^P+yYJF}{O70Ke%&aFm6erx z>4oQY>@n|BJJGgJed?3?DazukS+jKOtr+|~-1(GR6y$RAHoQ59my}6QmbyTn)u&>( z=6bcdah-z6E<8n@1Z%Bsl+<)oK`-FziINKCTZGcpq-ZS1y-qvOHC{2=%$zwBBjE^F z+jq^?SG)St-~ax1E{glqQ%~sRlTX#tPe0{oe)Q2ttAl&+TljAMy7i<)_Naq&$W;Ka z(e~hj4<^mC14lzAj*E!?$Hh`01)8#TNh zeatcX!S}DxoVjx~bJk3?;3Y35%4UYvCqDi$YKRj$|NQf`fvf^kT%LYCqoFzatURHeH1RlDEYqh?mya6SM6v;cJD@Y(PsF`1q+5S~x30UYN4+uj zpVX}5-o3xhyYSyNzL@8ruZ45v-uK^!(d0WjL;~mC+`03#|NaL!`DSG!24dsJ)w&3^ z8M+_%zz4N@&1xbV50ZsaCtFWUkf_t;mu=5be)ESp#j&IG8hM?k=(RndMKD#qmBD z9Wo>10F1k}Yu4<5wJTnFsUK?>>Bu9GAVVdOwX^!McKNC|5Z0n_Eu0V5;)uH!EHwqq zoGT`i3^N-jo3IR=0@j)wFJSHa-v54=@nl&0$xnY~Cs=zlSlbBJehU8liNllIZoADz zKMZf3j>B{OW*xp94&p3wtRHLBVC@>PHn2;qrR2%CQ1ynjlz=7Td8~cql~-PcwO3zt zmBZTK{qA^J@W%# zZ8W4?erH(Q2i8vN$Jzt_saR`#GaS~g!uI+*5&ywh`{N(~Xa}sd-WSGOx8HWFo&_JQ z)N>%#-gD1Aj%SVM-&$DPjx)JW%hztl+BBtQA{CkXRsJtkFfki_(5GkDuG6@>{pB}r zfu-A7G;GzXRWeVoc||9Z18zhtqyEZL_N=*-c?u3jkXdx&Q$&znS@{ZzSyWp$ZB`BS zShHu(c4C4c#5B*UfVjRdSrcv&r(2|J6%Sd_vdjq9n2ZNM8G0Td@qoTTXp zG|P>*=$Uzm4Tv8ky7&!JN6#lU^f(L;2hvk3%1&pznDmdPob>t0OQnbi>^^v9$?aJuUC!bbaEuYOY_WvZDTIj5Ecp&e6DVbcXSrWkZ;-x3qpo3ye&A7FVKt zSj|N(>8|rIKuj5V0Y}NoS5|IQ1}`EqHW7pMf(tGrAe%#ob*dFlrqv#Q@SJmX))}WG z^uoIE!V9!`$=-OVALMljJZyDVDQARP=EcA|+tInw(U=ui3Actk02kd%MZMa%s#Pmi zKf`y_=u`3#GoH&@lCL!~@~9@1R$cfwm6TB*OM|mG%7`^^Gh@q23#|VAc~Tccp`d$j z87anlb`MT?_q$z9PdzoL?{hF7#)WxPUwq*O$4BPSn}L%lL_LrVa~7G=VYQ!Ek(&Iy?_Qy+uK7Mn-b|TC-LhOv zep>m;_kZvMRpMZI_~A#$#>r^z)ae*zf7UU`$F&%9J-yV)QzEcCB@cyjej~hE|9~w0 z15D-xsGsd=OX;^i{hQ)hdM|`%28VYdhttJn9C^~D`!;ItDN9sYJstqIMq+uaHGVU? z&YM3^H~;pgontL!1_i9O@&kvp7{)up+JC?BLS)ISu{PAZ9cx#vT%!$7HS7{=jlPzJ zGg`x1q*=2`EBPs4Z2*VJIx;8BA#TmfEK_INwCV2s=FOY7V=eOQ>tFebK90OQ_LyT| zg|+|no&VON`Ez%QwZ49=Eq@bZt&fN+o=(Si*5GbjJ0P|QeblO=_j7thP4Omx4(1v8PChzP!b7a4=Vui!PS~BVk zYgfFyLNC7fg2P&C>}cDb_mK}fd1%kzK&&kTOV0*t&wLfuKAz!Rk1U5lSc^k&zy02!Yxur^wJAytrhv77y5mmgJg^A) zTM29ZF@HbS-VfHc%SV}465Aq&1KDLAg1O~^E>-%M;7l3MwbL`l6zsKt%Q-xxKP1KM zfB)xyoYil}#2=}rvIaHgJ+($6y*<68mu+@N#|D~%SP{1wz~+VhEdXua(Y^QH$04I-U0Rbg7pJj6FEzD0F66d>jFG^_5q;h}r*Lb+z+6+V}50;|%@c z=Rb2GeCcIZP#^hQ6qYLe;rF*=e5}xae$t}0C^IN67J)SH-kOa7Hs1D<@+vLddta2` zYE79jOA#8-BuNFe22*d|VPI>YbOa$fhfK{Jl!_ zW_85U6I4ahu=_$c0WK+kWu;Dh&xyK$bf9bS99vD}xgYwFi;h{&zPm4Y^sOez8gQ9+ z-=bv3lVRw543Wz}kjA!l?Hav@c`WO|Jf~BqPE`ut@pzh5T$R(7whi15YBk<*jy`zq zC3LKvNGp5q8s-}@0khtB#na%U`?Sjp0RpF z%Zj+{(*Jd7oo2Y6ciu-Z7~Vnl!$SS)`s-Y}p+(y)@@$c|Z+zox`p}0y;v&@6b=D$^ zbBO@A(G0GYI?q5jqS^Bn=;c*wG;zu_bz@lMd?A+}q>B#9vxwLSL+FFJO zOpEc;OC3-~tPUED9$sIh3_m) z*NeQg>l>}nM|MLMB>)x$EHAG>@%8BgA3R(C`5*u0BDrS~CA{DhA9qeltKYFYTYKKV z_Kk1oTl`s_-WAk89(LF}bpgEv`cN1vna}1dW4;xu*D3}sjKjG%ci|E}^~`gc0Pk&S zXjUA)$Nl~$_ufuCcbZ zA8QW10su(tCbhqdTrNgDK6e;C7B^~Fm*qdAL4-Q?G!vmdR4U+9>>8Ghw3U zELg0k=_N1$9dc_^%UcR-J=?IB{xQW-hqXbva_7ikk<+1Ar65tQ!}ljEWa>f!q>b|2 z`{2WgeP%{bnbrUu2L)j`h1Qju-BnOuJ@l%=?u0%+H1hOij|whU65>$E7h z852<=*5L@tiJ*vBW27+Y)i%t79w_-%deD__!4fTb-l zWqg)Gp)mu`oF~?4*1XIHLWSjRj4xf?3I<6c2ERemr172G25&a}<(9y~31Q3N^!SQZ zgwmd2-r`_bZA8vW0kC#RUQ|)5aNA0ZZ5nD2sm)R2Xd}Pe59x;RsV&7aTEiX(4$8H$ zED_3tDXpf^6+CyAf@90d*tMBc#Ug6Pe^%3O0PW|xQt+)AXXcnPBe#g|$(C(kOS$CW zQ7?0tF_wWRi{Z~S5j6X33S}y3*Xthp(YnM@oEO6nqan|C6$HZIwNexp@jTHjIp8Cj zhQXV`01;lioR4nF*@^00@7Ki_U!cBphi?4cpJ=eq34MzkjV)!&vQw<9w$a?~ht-x@ z>W_7mwq?u;-H+`zINL=n%=u!^wcR(9eMxBBi`?=Nm4x6dClyc+jj94TwxWFJiC|F1 z8kOp7RtedKJ`_%iq|jTEWr>)>_T{_LjGURwOOc9aP9hJqvU*2z64`Z{T<_@W00+ul z6gD5#hZZLDOnyTnTgJz-Tk9Dx_y0bKHb2qJw<5&*n+> zyeXY2#?Cy0z7F`#QmN@GjZ?3v4F2Fd56cet1IYO}CP=278_V+*Bpn?-93MVFU;Nys zb>}_5(I4)60Dc8G#<2EPSlb0&S~H$8tQ{;mP4*-2EJLLqYlC2|24QU$tfhO|HmsfK zu=eM=8mztOtC#3V42wM0(t`kew63zIvlwoeLw-BfW}Oab-~0Sz8T4aqLB}&$XmZh3 zz%Y4fSljDEjuIKpfJqUswiOI789!0&X`X)^dwHzw0c*)+=*L>5iqrA(SX;StPQ;_u-1=^vy!tM zSSt@$>j7)m`E=367b;3A*p0XUaYw90UoqS?2Xr25!EA@MsU5J^^cBM@TS3Ec$8;cv zwe%%1ozV90wE%>}S^`S`Agd10CxxI6n!SuHZvbya24QnlZ*swO@u(pfN(M0xf_F6el z7Cs#cYrXwgYtL&RSQ{uRar17+T64I9wdi@KM|+sBFbY>#IRE_jg-?ZF3tR8}b>Pfl2IHBDz#ihNu-5oDZM^Kx)i}k4NHPJgj$>(!VXHR}7-lt+%3bqYt z7xro+27}pq`xwR$E(}|N(o8)yZ%{QamYziRo&ArHv>&i;-C)g8YLwgzq!o_V$XbV0 z{xjj7=s)K)^(gLo`agHG6pXtNyEAs3c3%DO3$H!Y2HS6TL*k?u+X(Q`DV}Gnq|M3b z>}z$e$u1(bqjy>E7^C}-5g@-b6pt(S3I;>2<*ydN;@1pP$&2CcV|k2ZV4ONVq!5%g zC>fz+C`M->!J3^347DUC2@S(+&{D+FCBZ}M@!%9OJ}eCAYwOaw!TV&)yfr)WT^s5ke(7o7 z{w$@bJ9eX=zmjwmEL_jF?+a(tf1UQxc-X+#{%lSUG&Lqo_48dX%aGWh+$ulb)Vzly zLX}*H8JM)4Msg-pf|{T_*lp)E`^m{UdozIU_R&VXpBL?Y2ml4S>@A8UPBWu5%V1&dNbPyP z1D|lH@#iF8p3f{A=!Eeo@|~SRmgmYxbhwu#rrh9M`dACluB*K-FaHa2KV?}9e4i*d zd3axtdA)JyWEL!!8tuu`D(GY$seWumlCyb=l$@^bwrs1dc?b61YSGA5R-XtYphL- zVXbl0j>EVGqm@y>S{l9gkL_4%@{aI?(*AMk^0v!cwp9tD8y+u9uh7&fmo6fT6L(9R zr7?g#DBJ;bD<4^7Nk4cT1rISMJfzU4sXt8+$gob{#Z24Bk{T)GXb-&rf;d*gJj_{^ zNAw_avp96BRIhd)?ZH7;e`ic}p8&_QX6fRoSkGIS`8UBp*3JS78*FZe4WFcRi_KT3Pt%AM#K9a4unc;1R) z<*Gt=h(@OF`Y3vEJ{5R|VV(?~QiZrw~=eylw zyXOhJ$MCD4AV8RAv$Vf;x|{J`xvvSQijSTe<5f(S!Z>eK&ABGD$e>EvICcU@F`1v; zI8)OBmgVEs4A2k<@d3cGKr!dxg~vG<>B?#N&T^#!{9`;G=XzMR&z)0&??V{XDIhz> z@k?`!YNugeE?KSGUIG=Nb~RM8e2=GCm0UX}JjRz#%{aGHJ`7%y^JdUzy(~isodRUE zwj1C3xmNg|ZFur%!e*y~UW~euLXVCT!6RRKqdEideGjR)V0d4o zL5-#8MaeRikvZhU`I=&hx-|2qpshKYgGPG@o3jitFSLj5fed{kpm7@FpcXsYqeAu5 zo@4gYzK9@6hHdQHi_qtz=JXNE7xwxHUUsUbyjNY^_mXHCvIE&&mvsphSg`A_mgXKFpj}!r$D63P1vm4aT@;BMNp$%o;8C3iK<^_F+P?P`mWeQ#WDf1@>qy!O#L=o@2R zFLd>}T1V1*d9dRCxT@Bb>%|Q(DCX(Y1MBWp{p0IgB(ZRdO{i=eJQO1u!_j`Y`G?zo zL?<^4-;Zc+b~dSGTEN?I*j2mgTZDR+=wdQJT_Z^pS%9x4x|wKYLf0<)F%ikUneRjl z`)rp2BfK}m(SA6YGpc;Yg$~E}BjuUVKL3%P-ze^HeCI=nx9D2R^%-E@&@~lW+xDW9 zdB1-0`uwBGIsD^g-Y|Ua9)wYp8N>1YNbk$rnD0Djj9#wagUrjINXBW_S-rkWFKu~2 zQJStSTX~n7f-9*@L-|G>GZAIC!^=Il*SKY3K>vVc-0@r7lu?v-6BPj*hqjF=_@?~Y zcOevSQ&Md?=cJE}t7xUVgBahLVpUm&3Xzr02wFHO>8_Gaw&z%*F@}@(qjAhP(41c5y^KR#93)_^_0Gf3*__-8*2Z=7 z->#udh!Qo(Z};ES5>xp-e38X5n#73`A`8GG?Mdc}V+7eSr;72I@nM=b_Pg;Ntl@Y# ziS=0&CzDiUAh2$3S&YoIY!}8I&B5>-m-j_5s3>b>B0IMG%OBqHot;ArzGwOo@qJf_ zsA)gn`@HadDUDJo8>oQqqv6rz`=VH$??e53pAEwIc%p*veFU#hG@K8(6|UbXYc%!l z0osS5eVlaQ6ppisKJEpy?}heBXkP~HvveySn)VpDFvG57PQ)DlcHgiac6{^EMlv1gDjTZvfdzyDTGy#w}) zG-Yu(+7CDXaQlyl{=@P8h$ioUW=8S38R6-T_U;yOwWejJ9a)5uAQ|5OfMRRO3BVH? zSzJT}WBYS&0K44}Fv5G|=F~gCR%m+#pCU6<0S(MwJNeahj6Sg-=jgCsc2Op6$J2PI; zzWK9PV_A;n!Qdrx+0AbuH7COC_)Kd%d64DSg`Qka`EXp4Bt|5eY0MD9f0>}Kgqw_i z_xKo2-tX}PzOg2>0EUN~mNTry;2@yJJ)k5CtR-hy#fz*ghkImrZ1>JF0@ncDEc%xt znr==R8zws4y*EZ+Tk44%pSt_ZN5^;eJ)7Hiz66m#4(JKM-;~$*I9`TZZ7gnlmn4-w z3;26%V!q6C8~eL^#%Y<+1Rt4^Yx~${yTkY1BzkQ>-+PI~TI#g5_ONp=vf~rcbeB6C ze9vKg9N$(SF}@E{5?~}war^*Aq5Z{4@y;LLXuuBk!3Ni#<-)Tgpgkd2PQY$92c?Y8 zZ%!#oeB@(@TkoRP$YhTRj-Aq;#-5z;u4r$(y65 zo&i~ohsd4*ucb0{^wYcoC9n=|<)eM$DAi9cdq6!rtRox4(SEr3hueQ-XP3X~kx17h%?33$(VYZ1Tw4)Aq91M=O7d2cw{zm@rJ z03q$|+ijRToUnxBG5+zJmKP-r(jrTS)3q>N|1d6aoYK!OBj-Dt_>Iha!|{DTVP5yk zaM%92Cw}AaX!34{P%OM=5l~W=9sbr}AWTC3MUjVCe8!(^m1Aft4*C+4$5;D8=drO=nG`ycPxN-9Wm1mS~%wvNj--Fqa*lXU4~VKGLAEGo0UtGj+l^-l0| z1D!+Jsw`YhGB8fdmp|_Lr20JF{9VBf;q{%^)!#whTbC}2vE(~Iaf z?6fD+>r4X*An>vX*{lI8uHPNR-Wcz_Cfbf|G&BW__i|Q`Y#Kj2W@V3l>b-I(ZdQbw zv305jjxoL)NR6@guag3%u)FF&6KWP2$0#tY%@WbcnKDcCCPx&7ay2cSR;Ovzm81)2 zspf-6BPhJR^ipZ1DQJ|MxlW8ySUA>!V%iy3i8*T6B#r_Z!-&XQL#b$*PV=N2V!U`Q zY7$`ivRM`CWyvc)7RXD@y?U{UhF2)PrcZkI&PUye&gO#d8QhgY3(y- zno8iA3|?i*U*Q36bW5lFT{$A0G+OFrGU_E0?R`Z3GSqRxYZ#x#cNwCxDSj)ZX<5*s zlp(wtfw+pNVMLC@h{W)z?}%$T_dbr}gOARrGi#ZQWG#U2r&m=f*w6Plx^t_K21O&_ zdmpQPl(K|hZ@v&hhE3 zq-wfz8|_P>eYQY*>n%bLrPo9I9JKe53Q;ndCa*?&^!QEfaXkU;qt-x#YsY(FLg{tW z9+J8m-#9WPXU)ctIW?sbSF`M6`NULFuZkDsX>Uu9zSPvK=bQ5~hh4#4#{s+>wPEPJ zt6|h1O5hlG+TUo6;f_Dj;|_K0Bc1z=zCWH6PSHH7ShFy{eda%y&>jSjd0ovNXAgmq ziC23Twhv>^k&NMJKivGo?LU%p8*RQD?dN7B*FL)YI%#5QES9fg9~@6{FI`#;fQaGY zF;}4RosC!V-bnAwaI_z8{?X(+yWrvY-nJRZ813^P$+?a0zP4VZ$9Kc=ZQ;aU@9S{Kf4%b-_8aQj3){RkUvbrOVcb8< z!L=v}TxJnXt70?#&QGM&+bAuqtO)o1X(C7t=Q;MEq+lU79>zetsn!zx=8(88*T%1sQZeRH3d z&zY-#y>*$|X*TJj(T(-K9lc?5kLLWNcjkLy<~;9h$68yx!T$5%Sjo}A(?|5SvaeX@ z9d)4o^T^|+wk}2LkcX29@h5wfdiW)6eQ34%TB6#rx=pbrRT|3x89|b^_d5r)1TKi5$&@^nmh^gLtHYU zPX7(x&kMk#tbOYEz6!qII5NHu%=BuXeJd5(r%o*|Hz<2jp6_4j#;F3|Wzlhs?@w4f zN0***&K~3YA}COXQ5oLbuO;xkXG%zon|rl8u!b7j|-^*1$^EH2jH@)J??WsKN+cxH{evp-oDK!fQJuN z4u*n>Ou(X=Wl6oU&I$HaKMUrGGRAvvGv*k@%XNH*l4pY`k;yb$%qeRTBlGVu#$;Ze z4rBlBK?)e(Z39$z%`nvDoD+0>W{Ht_UrY<<&C{|!KB8~kxLil>RjvyzIYT{t4T|0J zm|9-yP^{kiFMBkJ-D$sqap9#kPkwYWVQXg+vm&lJd^HZUgPyi(@95CWPvrFI6R&8> zgq1q!o%7Yu5Z9u}9Ib9xEpH7Wmk?`7(5!F2x$|}9H-D-p?p~{ZJ$<%L|KzD!_2?tY z-u5B}H4TPZMfZ4*tiQ6o)?%2u>_R@b5&>*)rE@Irt9Y2kq2TuNbTw@5&_kPZ`s>}x zwPay2YeFwZn-6J9$rLp;HL$d#rJ$OMyzR;3{n*%36v$bP0+ z9dK({_Vc0(E@LU67Jd2?hiK`$mulmk4=Vnbb!y#6eJokK)!ZZQ7QN>^y!T?-5>$o9 z+ImtfUqf^jSz1mHY~0i8WTlH$FJ$$^3!7AN{bn6|;ADjtr8O%(on~S&^#yUql+s8o z+N%X~=jre8{Wot|t|N$W{+sbVYdnvb@1M@;?@zy?$^Cra*ut|>HivA5)$)%cE5VoN z`+Yh7p62_ln>$>cRa9J2v#pT;!9#El!QI`0ySr;}cXuba1$TFMcXw+nKyYaY&I$Hddtq_f8Y8^ZfsCpJUTpzu}nycmfuI+5{)?FnA7{F|T? zcD*eULd!AhSht02-hLfE9@V|mIq${L8r<#?9<31?XFIW5hp+JVTeraN`r}8S92mEJ z>Bc^;=BP3TIDirZs;4#hbcco{F7}54_zDzJG>FSc3`?hV?mssCJgnq%_+=+-8ES+4c)ZQmzrShFJcB3f54`OMZf z2?`jkGd{GHZ{oR1P+VEhZH*OZy4kJRYq_UW1IE+h?(8N(B64MbR@IW>cJwj+BIb^P ziL*1@(a@HlgMyM?cKE%^%V@Bjp(a@S!SqE^`*K`=!(Uh4=FjxQgN-q9jXAOEn$64U zSGnIAKY2lnv_2!Oy1sT~U~FuHmR)-wW(m6S}QQC*sgMv%&<& zWh?Hb9TaF$ir~bVXNn$DB>&kFw1g(f!S(LD_=Csr_n-*arhqMpR=r`%p2Ox_|NU33 zVy)qAh)G{!9HX`0#I9SGZOm zznVVcWV!S&Rk0Pg(N2pLV~$y*e;jRrAEq*sc;Cenu6Dg`j zKFrUV2w#vV*W~}31;E8{srK7;tIzHtesXQ4tpm+=D`0Dq@Ek$k{lyr7drT_to4v&} zvAE*DFqWb%2dfmQsfiO(RnGoenSK5jug{7OA7dp$vThNp&73i>87>jzOUt!6dJ#Ds!)+&|PcGJG_;qpbdTkw#8dRJF@93+67Vp@V3+@;KEpz5uHa9{snPM?K7c3*MjE z&w5G@3#{pCkL;@oN%|Fl8-B@Dq+5?Y1H7T zPH$}_^!4FbyL$GfXX<$G;&=QGjHmC~hnan_9#ZWe10Jb;vK(7HYgn>}-tllvAZWj@ z@May`LAmBNF%V8N^u>ZH6&mGkwL-Stcn8mdFZ6=>vScH$3CZ?IVfWSv_8jj|pKWz^ zsfe_g{eY}}K-!yL&Q7*I$L(NX%8h@It+}5OLWg>zVcqR(al-vVG+*hd>Gx5*<@50C zd#u|q8I%*D76<7IX8Nwwt|uJ-%ST#S2Zf4+pUxlVMy~MnHq>el?y7nD?gMti8S7c# z-@W)|XeDf-4Om8%n0?7)pG6{(&Whk>qmw$8*E!I5mGlfQl> zyB&f8(Bm&@wTjH5?#v08yVedXKPUF9g;79W zN+jV(^S3kqZ_pOgH{s}6qXPf*JKB+>((W;nrSj=wZRt$4ixvzMp2))tv!sqGhz zuDtN1>>gpWHvF4RfXJ}<#N(kjZpG72r@HB*u@d_}RMM_cUEd;zNwF9nhYZc?&9Lx_ z2k_v=XjmkJS&=UBks7{2Bj&UqXjU{E61zzTyo7V=^5shM)wFh&HcHi^`rAl&Ga%fO zk4Vf6G;o;F*|W;_iqC2zr>9sS^2g>8`OolR_lK6Tn$S6(p@a+7bHw@5U5sIJUM-|M z^aHx0i4?wjr~35Q^kW@$>FtBkZ0su6*sIsh77okk8(GU0F0|j9XsG~FNj{fkJE<+! z@n#q%bYLD_^l@;vl|8?fswU3DAkbtwv&_gC$+CmV#tU1?dZZle^Za zTO0e-cH-*uL4&yqeX~~cf*c6yTzUns#~;e_146r6pACu>g*b^EFQnOK&uqe7z4FFn zyShPkw z&vs-NMIJxU({i#K`*x?tF?!jh6tq}?+Ad#~%lVaw@{U%34h|5n(kTRe`hMKQr4 zI;YFbnMl;}mn;idz4cZtVzYt9%i4#}&g)a`)))9VX#LfPUUHfJTa*fHf&EEZzn9V4 zLkq21EO+f&PINsu8*`CInz49<*R$Ep(&E||djk7|AzL7(RsOiL zCqGPn9sc8>t6AN4%h*n$`a_8+Z0IIQP|plO!|lcvE7C`H^b6*H|IZ)_IEKVTS!yhr zTbrwtHRE#0auU7j6HSMeVkPR` z&0BZ)8J5Ss^W32`YT%Gl zWt}E7D%r|7Vr&J?ME!6UBJRY$>)AkZUl#m?e=2)Dd2)6$yYYyiGSERKCTBckEDNS; zJ+8#bZSm^TOs)k_OlNb@3yC3DftI>%t5K@%pOgAp$MJV9V%R4CTp0N<8s#Lx@*(6GZFuZ}He! zxJLGU#u06=?LQbe8v<;-_NUg{SD~BNko7SzJ^bCP(LRK{?e10dF<_G97}8};#(DIe zuKKo$c6UD{dd*uv3bq}4I?HV-#M1IPI>Zf0ryKlXcluSx)pi;`M;d!mQTa@Q+R>(8rj`cK` z>6kdpckEY_1M3w50M6Hqx0{VuGJwg?aAI9=>3VLbtdZ3{JVmO2G(EeWtw2A1diEux zZUpZohqqNmzAM=B$y0Sz-J#*|7Gl$Fx5L@z0dF8Rc7+xOUtDR*Obmt%dgtPU#+yvtx8YFd6vR5DbT}eYe?G(^&445 zeq62Jt9Y)Ft7kJn-V#f41AB>kGF50Th zTw|}dzOE%Zb0XCS{#I1s{LRMm=ZSmBPP1E&h-T@Ml0r;M`t0(KPLc0qO}||hw`oM$ zUcU0vmYIBZj5%Te$vLN3Uq_kq9D4mJRnxDd;e@pjD=S93c@-bKvMa2H0J<6{`NWv} z6{l*8Wqq2Zb{X4scUy@bS%{=+m4blOeXBStpT|}&bD6`+{d5)`AMtzy=E9bY@w!D; zVA_0vg)2P9&X-_X6y52bc9u+GJLtUMWsR?NUG|=36)=&sf^Nc^U9!=66;(I`VArkI zx2FmcgF`iM>TThRCOY z=k2e8FS6XzWv&4N$Rukxa}=kf2B}Mzo+TQJf)K#Nj?KL0;s#@Br{MfGKA<^IEf3Ut zP7ni{e?7DUN*q7#b-!mmkM!Z%m|gg!Mat^XSCkoj(KhVPY~PBcY|ZKVgQ8HMT?v}B z-`)t4jX~rEx_L>XTCMX>BYu2GrVrin;A;0KnFX4a zwMUlGR|(p6V^y|6rQOo67`-y7~kdfL*@DnfU|3-ig`*UGDs zT|8&KRWkG2jq~vtsqPgPSs|pwI9VK6`-(L*r46H~TXcb-SEG)Mm^p&nKCrdTwUx4g zbuAwsKc|B}vDV1&GzjzuCt~r>wwO>=mU5iW@qGte`Kyp`3sMWKF^jXQCvCCf8p4)< zEBVD3alx22rmdHSx{fYwe<@t|JI!o+_)^j{r9`Ps-g1e%>)Kfm90JW~)#REML*)I9 z%MOSsVQ=I}&#Z|63FIr)JDcsk!Z{QS)*%*lfV8MY~=XB}*KVtP?<_kmRTfGC%>VO+!Lc*igk(c1Tmn3@Qr|C$=f=xw( z@T_%gT1l?k%U7M1ay+TWe>rqCy;+9QCWERZ(dQ*>ttHgW-&~ah;>kq0v=AB1r=}NS zVa_RXS#ulu;G8uQQE`z=H29mvUG)}l39=0mFqTRENA{YIQXaOtKVns&+{J+_3lB;4 zDzZ6pU&>5dC4!Q=CsmmOJ!aMqf}@tKC>x8gRRiplgbCZvz2vyll`(cr+A5;h8b=N? zznRi`WU_=rN_avDGKW9sDW0q%8sw;?{yqDOW_UB35LIBtk1iJ+QaJEBf&Mr*+{C`o zwzH$rwO?P$_w@I>|MND%&Oh^7%{Gvg3bsPj@meK&in^nit{QzTr#s9dh&Di?TD!Ov zPw2a`M)*!5y(Ds}CYP#8ZQmHv=wS&N<~Q2L3W{f&$*{!ZnY-!!w(#U1UpG|Z1b1<~ z7s^Qk7^)-pXI&G}t6}?$64WRG2di%gYeVc;^|3&~4?D&ycKeF(3s}7marS8(ZYJNW zj{wSK@8R!RdWHh2hrp9OeoEfm)KW0Q_ZR0OAaKiBi`ww>CGX*Vv*krgRk2p2s-CdP zzHkT#U(mEg7#fg38Uq@g$VoPs&OtI){Ko9+AptRmWkbLm5nHQ&FW^O(B*J|W!zBdo-mnb480gfgS#MaHkn1UQ(VEtbhB-zdBI`SW5TAl3r1HS*xBv^@uBXMMUQ8A)#AmlW zBbK_Z=TJbOA>?#g@fpWhRj?gqCv5#VjEI1-w_|U{=8ed3^|5$z?RrITztDTXLq(c4 zT63KjE?-(=6Ua8~y?7))6UEgu6HB@7I&?VRtODmg2q|Q-sbI5%)|z}XFrwocgoshW zw;pp@!r8u~0QYLHi~at0gH_JM`;}#-in6lw`;e4Y>np0AK;Y}=XxJ9xGe145m8%oP z&1df%T=1saf$O54OD=1=&Q@&Bl_+6;@H#gr;QuXOK?&&X9gFS79_k;w3zBX@KkCHe zlv=DdpHcbl@N%Blv}0QB!T3g%UsZYJthkmF?Nmx6vpQPj-`LW1qMKc=mJMv|$($NmZyXQZ=vG4KZ|~o@Duuds+LGt=OPpgrhZWSMxB0Ag_NZlZxm6>E zw&ldPlNmM2R$D3RNO1!cyDl^r3ZF6+e;(YcHolSE$2qU}l}qz-&0nhw5HZ~3MXn-# z#g=fJrqW&t3YY@4<~bX#Y1XRKLl@8@D5>(`f2qDRo>(&B7PlCvG1a&XCl<}}JwB>9 z=ayW(RZo4eeMQ0)r$yY~{*9yNNMuM8TTxC~>TuputCTo?iBsKc_IDx=WneFRzYyvP z3RS7?6qwC*`a*7{@+lAkt8wF>CcEG)w@iKr8Bz8lh?=%)6*JN=UJW+T(}Bp{Su-KW0`Hv>l{Qae3Wu4>Qwg9WI6~)3s*Stn6Fm zhk&vr#eEaFt`Gsj0xdH#_Y|K2w=>N%m_Na(cHhPM`1Rb^IxT7Se>q#)6V1l7q^e|a zO;ux;g5G~JpeSix8{UD6IDIYiylXD56yFZcDs%yD6Y-5MSM)YEnCA3cIt^gK`GS>T`sI$b|f}M`_YnB_>oxB@e<)}1|4c*mj-TpR4Ry(j&w}xldE3?~hRrj8> z)@E`x(GBaiA5*rrI8fQhC@Lo24ZtQ@1iQB9`Ud^7@XAnS?Hy|S7)H?Gu9OW7yeHd^ zuuIKq>fuYoX)5$uGSdOcWQZan+Nh*@<+bppy5a5976@u7)}sX z?#0zU02;heg;_1%^?3=*eqwxc&f;Jk-~Zsag7OdpU4K3tmlgzu?BnWmRA{d$v`Tjm9B|12-S&4(R{c^w(E zen-P@Pcpll{r`+?7ECIqj-|R=Bio+&>m#Ve!KXmRh)1BvHa<&o$}){$ksK%2?Kg=3 z&cLSX##V4vhajpT!j_htj_mmiXo4Td5e$D$)ZFrThG+u=FM=i(o~M9x#i=DUH7KBF zQCR%v)80lkV2bVM=}z)wfpRe{c6mkYFNNikz$Hn{Y>(H27Cr-)88>&$q+pzzvjOzn z8lCY=vBUD;T*(*759l(<5qgicP(Y@3d!C5c*!oUx0kx(4)-28lY%fn2kyad}TqT#D;M<*v` z_PuR4S`90QS1ixEnZ}>KkW5vcGT`f(PBeKo#d2blX5#jss;0%;CL`_6{Rei(Pg{JC z-yw_YTB8_Vb(456-^35Nl?2pb8h>3a{k^;gRS_WQXB?=7pZ-@Yz(ANvC9 z9)(zffVThq_Es(Yj1C@mT}&ENS$~HDnjCWaybRcFx5wOeXw~lb#i*+*E93{)e?XcF zt%MIz#Ual^8hW($3A|hr+*jlV1x01@)A@n&5Yonewy+m}iH=Ga+* zCrQP4zAjZ@l9n9EP}H79MHOY}lWk`B@aVsk>@P~7Mki8xO$VwbZ3 zWDQM?BGR`Tsppk)AwG3hka~w%*IgB8+colt2}pb zk&BdR*&24ZFHdtKwERg=V&OT_leorW?YHch%t-|W>0Z9_=lRN`llrZeEJ#8j3VPz* z4C|TfIKXYEgd;->UR~p8d90S6#jsfXDu1Qq3^V#bsEpUu(&~Df*E?mE&?%x#PZ4R~ z6udjEzeYa2hS{2BV>u`$)z0zdo}QvlPqcN>c#GxV!(AbJ6ffhQmiDelKy?UgNg(=# z=ooMJgy7PuGt0PV_l^4-&y!#~@&8-)e;CD~#a0;>D_9gS4$QN4m#u5JzN*Gt*QTAS zDzN4s&txi*R|IuMyUhw%_4(cZwc1Nno9qZmwkF^#R97#OODp7Aia^kaz#4B$1w=FqT>@AsJ5s=w$Dmekr<}{;JbHSugBirmnU{ zR%z)>DW@ltqm>+SzfJO$Q_u^qDpB@syl`oYm)~ttXU@nMEQvKGgemZTRj6fCN1^gvv+lqD|A~RMeV!|M$Ju6%5;+HEC zKJ#K;V^eVj?Ib){cb`!=>*5pi`ozxi1n)~H+<(zW{JM>3$&Wo6Wzfu9GR$vQWr2}7 z=4_s?nA@BSL^u4#OzA-k*GK(`NqGPa-K+6qn+LlgkwyS-N$n4P=Z*V}_@jW-eUg3U z`HI0nJ%oR+7#}*l6&L8ML;uJ30xq@fC~Zi<^K{IRFFCSN#jY37MnbAnMWP_zT3sA- z8Cj$|TNi-2n71?<+%iw*S~j0wy**Xy^Y4~xDfMh1o_&~burfGlpC2+z`uzNh*7vLm zXehBGUOBWLC<%Mz?b3NI#xAO*0D1D_+g42JQd(Eqo=R<9@a}`w z>sy;}MORZq*N*P@TIZe+9s2{XND>d8vmR`Yjqeh;!?e4&me;};;QoFwwHqAGeQ!I* zi(_}9u@-z45@L^=eSrm?Uz?^yeHRbIq%Y#x=iHsPoWuNU3Sq7MGJkmwkLSEYvbg-I zl9sG>Wo-Z1*1v{lWxWaeob9ZOI zc;4AHZO&d>sBukFZMK9rP*D634f^rTjb#T9cfi8b$F^OKzFQ?^)NmI07_DYzTA$0EcSRq~ zVz(qIApVYkg2n1K9!{deTUO0wPMw7O)WBdPm+3uYw8T8@sV7F$K~}-a zk6*Ac6WzwSu4W9zYkx^Ca^?9QD4jY|)6NoSWTV{>EdL0c-Sx=v&3C|DH zu%hQzaj*Rc_PkU@S=dg2P9&*Ny18mqO-)7$v5S4N!V(pB?~E(*k3FCYe-^{=f~ zrMFk|(rn(BBZ@z#yZ1gezGmnqqm3l&KfaI{ieZ}(8C9boTq;;iL${-iG`BXCsm9tK z(;N@didS@_CpQgsiFP8pq$L#Ff6r^wECWY2r;aB_U(yYdyy=mn8-h%R+v|w7V%ACWJCvbft0n0=@}KNl zZ2Ad7{}*SdV|{_yuodI*mut`d>i-~H(GG8l6NHMY>-&7pUvipTTQxM&G!`ylKe7m6 zGZx-UK&zZeJhzVDas3+-6wzYO)7>LO6)Xap)fHg%m$06Kg2M9vf^b26JX&O`wVRtd z>FcSK_aZBf@lShEkBhZ>%anL<7P_}aW|xkilbH|9-{DV9t+zwX+IR&g!e>&Qag~{hKhMp;5=+9iXl^h$ahhBKtaZ!ZD|LF+!%1mTt?#7O~DfbLU|e zOuT9w7#YD27%2KX?KQ2jsVq_FP=4M29?*OB0WV>0-$+V81~x)}hFxa1Y0EU5L9DJG zuSyxG$cU8!3~lZF#UhL?q*ts!bZJ&T=J_W08;)hPh!C~0!g-1&y%Q=hf$i2L#mx0o zgUikv4feaSPqJ@K#OF8{AA#WtA9&Mqyn{|xRG?cPkR#rE@rI4moFXylha?O>qB(_= zfb_sinhHA_CMxb=l!I|nT59WhDM6IGz~&0ps5I%f5T>z!()x0}a6E`V<&i8(^7LuQ zIyt}6elU$uhP8=eF5nO+uXX%O~-xy4!~d)zj9k$&1ImFa3q zhSxY7CGid9EBw+ISXZA}i{AJCng}IWfXvjPOnKA{6)7L2t=}`&nY)IvLGT%}F)jpe z+#~hTYDA*HufACftbGLs!XjTQ1^mN&o!xth^MI0M?tgJX$mxUZ06l`P+WTMa?Ex)_ z^R}U|7Pa#qNh9Jqu}ibW)|{BoEZE22d&%Y zwnzevg=4@rkeQx-*#{~=7tH^`UxtMtBqVILoG1D5H|!A6V1zaB9UNHypNAG_n8Jmk zK|KQLMv0@}>%}%R#|AQvBDAvTFI)!w3P6{x|G8SeOHh+{c?Ppl9shNQm=Of9juz?& zy94j>0bWEko^drd1A8ud<5u7pzuYGHsq^Or=(fs_P&Tmac|h#>>|ZOVm?z5rc=a)L zoqpf>VO<#8sYPFSf141PlbE$rRbTHJRA&I}xtnvn1}+qeJ)dj!S0RvYmm~x{_KF)Q z2jSz;!v<3Yax=RS6$lu|!ZA!J2{dkc!;cx-%})h+bN%!Yg<4ynd0O4snGs6S?Qz3E zSuExAs*nf-x92ba`BTt$bHx0^Z}dRa!P%`sr?@szxf4N#JwkOSqrT|*?jbGk3D^H= z`n{;CN;lVKiXczW4636`q+pCat`H}|UQ|mkXuPdm?wYJdy0?K*OPaDlE%0`T{XRU7 z@IGBl>4U&Aw_47uUwt=SeeW3x-7BnHfSPMq>l>h24ve|z>1FDDDv-i;^KLDquZ)Fn zx<9_w&wY{CKjHRvQ!z?sGXCmk+ayZbX_dF@3)0Wk%Pdd3lXKh46S;?WB(#vrcs zq)oV@AkkBT^R6q&$Zcv-u}lv9&CXM}f5QV0Bm*hQsp}3LV}qp&ey^}>kd8}|56Q$r z6BT>Qzzuwr5_5=qp}Z@^rd9t|FF82mA}UIjnk7xcvB`u^YotPiCk};)qhCqLm(8Ic zRY;srnbyI{m)4$A)`~*z>V|cV&Gk1lD6y4ZX|P)q3D1~9IZNVPOB$10+-<)x)+qpe zF;s=lz#Nxz%4k8lN+UQ#g7e?sbgL*Kvm_SpK7*3f#>`RW>q(@l{+W7+&mZx@=-6gE zI=+(*AAn;`w#<63+sCWz7he-Fhbf8l+Kcp_`%L5$Q-%3O-MEYHDQP#MpoEA~#U}3g zWr#xch<`Tw#4wSf&LHzwq(^qb?o038^HeI_%_LLZ&~QGgwYq9?il8_R+F{zbl^DfY zO_j?IH=yuUs0hi)l;U%^Vi=}we~Aj=tvdY8Ga9tT7Jo#G)%EuxRAi1p!<*F$K?*JA zLNLk+TraaI5OpORY!2T`>P6d2N;&IF)ywSHxEsj=lew!mS&O*K&+S9Xbbk>aa8Dvt zb2WH~*N7xt?YF+|vkH=@8R1`tC1<;~W7O-)vHU@B9BZDiA3a6QJQ0v5P%i;`BmWtA zenA155J!MM^18o*lllqcN`Jov>LqAwQF1lL|H4^+)xG<`^fSpVJ2qn}Yt#y%g4lu) zP80I{d^3f;&&s&k|GL$W;ei~ypfWqKIxr9f`!>UGRomCM3vC83a0NVG^!uQr-uxBa zlaD5B#?qzD{NQhE@tx$j=G^r?)H=Ec6`-R#-UYvU*|fs~$^`n?0?p0E+uyD=bKz*3 z`rzr->kKaRfph#ox3R=OqfG>L$CaGSHNi{Eiu|*;Om(`wvbLIa9?x5Q5xn`{K3jO% z!PD6eT{7jl&Rv@II<;Wf=XpV}KBL}PtaSI&arRlZ(}0VfUN(7(ZGEpM(C>sl_a8U> z^X#xLLPcD1WYcRUvADpe&x_Vt)-s4-zDfjAA7!8)xSMD*5Y(LS-j{8Ms!X8V#`?Q_ zRXc5V*amDq05E?XWe!k*gF4;#=G+P|fVkM0qvjjW8dS|HaPk#k_G71K=KZ<;{ha6| z_3z;6m&2b8hp|X^sRN5HtTzZBslCGk2aH%XsV-K~Xa}rI)O)b8PJ)xMC5D15ix0I8adz2PyqFIch`h3Yw7;t3?oP&fj&N$#F#H z6w({U;J=aL;Vo|LI&c#Iv)u&{{X;VaIK2@t)-o*>Ll7n*qajwjv+_Kd>Hlp;5|ajQ z>7ltcy&Sav-NtmSKxI2|C8B5I%n%Xv7$=WK}F#`4k_A z2PvX}a2yI{q9j;hc^ow^8d9gttUVxBar_;SNS+NCN-?S_ARIe-MZk_}ayYDvi!_m@ z`pzT*P-FECeR2v2``lSW7qS;7coWwyR0#J#$>$IHigJ}|O!2b06g^a88J9wjZ%Ll_ z%{@v(Z}H8PvSd#^ zpJM5t&Vqe;7&{T@j=i7hd176V@(QunYqhZsPv=^(o_Vy~7->SKtbz(r0VU#`orPmj z#owA@DRV(1+9vB(rXS;u!d{GPr${~LFqbD*^ogpXB2<>j{t`v2lziD4z2hF8r+R5g z^0P(vqGFEa70g3|A!JcG9a1=$^7bxkbpR z)T#epjdOn#G4(!)wq(6?dYbQ2JGDu59=kf%+5X&$j#BSC6U8vUlz;e$U~t0v|MJO&vGnZ|zYQHhzKy$GCRIfre$2;(G4gEWK# zM=2lJa^&ZE{XeWR9?NV3dhmR+ht|exqE53A%Wn>47Q4EzHSj*!+1bR(cCWK)**3>J z98EN^k(N2m@K1I2@!V@Y8Ss7?3_1sXrO#Wus5RKwL)X5~XMldv0%UCtV8sJBbFF`A z7PL+`HS*6kWqsI64+U5c4`xDc2e-cDl(dK^ETLoetesTnLw2PglFlAFF@m`LAXJT%N{`e~TT$T@)I)3x4{gf@pW-jE$?%}1Ily+JNix7cXl|NbBjJk6|N5J-Y%`9r zAX*<0qwfzZeMUIfm#-ljHaoQCGUYrwxA58HB&8-ih{=ZIo#3}T=$eX=-mO+fl4y1X z|D;2)y!s&~q&7L^ljOZPT;9dq+H#6TrC(C?@O)V+!vKQ~xnFq4 zEli(YI%^}0;u)XSCmO^r;gkxxP#1^6QeBPr1VmEu=?S))+b9hZ1plX0o%ChCwax&_ z#?tkAJ|e?BJPIt#byaM_7me1R;>w+F<9D-oQL!9>hLqXj+LpEY-w{SPjKzAR#o{#b zIRWDU!4-a+ye~a%c)vI88vLPujik=z71XqdM*2X=%J?eE!Pkdg>>bU0&Czay?52mG z!9Om;?u?o8N&%f3fKFfS1mRZ(W4HL@92UJkx;48hnGu#esZC~ipN4=0*c>y44IfTx<-+e@xKWS`eXj}C1{#Xm^MNPrKscr?5^^>SWVBO*&=Eh$uROl==9j}J)^?+mc{LJT^K-Ay((z1>p%;j+c8uqUg zuo>_is2dx5m(x9oI35ClZ+_#*fQ5wuWagd)Xghe6tLYzk53OD5;S%BZx#g6x89c^K zeSLW$!-0jz3q!RY^z6ZcoC@J( zBn3`ruNN2Rh^2y~_(0R3hjQUp+8-vA`<)VzKz8>4ej|7H*5$qHMS;%$gbr6h`qN3R zjoJF9JtYBnQv|1GhuWPkrR;BNh+=(znVEtg5s~~+pelO|*hzk`ul3a6B-?-6{cE{v z3cCmYs6aK>Ihl_dPSv37)d`P7a2>99GU#HTbAQXTPa=PLe5)8Y9^aaOC3HzIofBZ7 zKs=Dx02hCUL69@hg>@Kij8w?{S5sb*in*>};s(k%O#;2zPC$E{!X2i!X0u=NYTi6V z-^|NHOncp-`;KJC`8Cex&!FDD__*Rw93Bvg>^f1ZGp0#h1%KhE*J3RBf*ZREHts`Z z5YfSe;t&~Ns6jdC$YDVcdnuX&5Z0D;7O0PUt|VN+r8R~mHC{Fq1`p^mZ>5< z4!bM6=*D-F5g-{QYANu^F*U{h4;~E92SB3y>+h_z563Qy8RM5Dbj-bNUwV;?%7hv7 z65%U8eiRhH#y|5Yn+(FF#M(UfqN?;z87r&rqps5BdKCAz!AqVa`d%-K)Z{iRn2?;e zQr_hE3uKZI0~3ySL~A15QG}mC?*9@h6($?0NGV#~m-x-GDUlAn(Tgd_MLR+8ts>yQ z8N%WfiIs<|j=<~a;9|Y`dDBqst{4h%?F^FfIxq&|n+zLTtP(#0%iHZti6Ue=0w#(N zjL8n88(1x0hA#y-zIB_0Nty{z|B)quN{JOzhK7H+#SlJ%*K=F92cNZj3RM0G=TJXd zhiY{-@S4XPaDJNEqv1}g^QFkgNEebI!M02|tB>&eb;9X6Q=7&5uSqT%k7C>ZyFhR# zgA|0H-yL#H%~>5J4FgKswMuX5-Bgz=v_VpVU4#}*8x|+~ciD@B@mWB|_;RAv>c=*o z;A!*iwf^mNw-b{4#Fn(Vxn}As!;;B+)~9*(FCc+(`l>0S&kLUuNJW5m%{SlFg8wG0 zLNu2l<0PvIfkLI=q>Kd!!F}IB!>$_I#phnnUTL8Hr4U{LdFVd=gAgf6t}>0BZx-5_ zr{`|DUch?^(iDw~E;6khR&Qk3>URcMbvPZ4m`kw~IwmpDWx5#^B-!q2ING9#d3?b< z2`8}Q68Hm@%Rbfjb(6aft!Z7n4$U3S_KrN^-z5;XUAeG~wE~3r1=etDc;G{HN$@OY z3D|lG-FCB`2hv;j-azd>A7tqVtwfU2b~j^N;jN5A)DbnS5;+_jyZu>ErZ+XJex_w} zVWCL1X01y=T}_RfKm|^K?VmX^@1p!9tS-{uL{ zNoPY_cpKCPd~J>f9SsQqp%ea#nr5N~Ta!NcFIUev0Mo+Rlx5>3*-xwUb=2?NMR&|R zMh~bI+F}56v-Z2P{QfW}Q$k+@gWQyH+rkAd3tM0c+IV1^hrq~&hKQSVTo#KvQ=gk6YUrsl7@T5ZK7kSg z->iq1Jb%D=1_JWtWrW1PlXVOa@i_(P!9kC2DLOE);;91J!NCEayL~3``wTsIT>Iy? zyxP#(xU6k)!wV<3J@FgD*>+-KecnR!(=ae@nlJJe?q+LsyAugI!oG9c$0b6LdEi@A z2j{MT%b-Bb_e)BNSU}xh7`opigT9=w1^$db&c{`sLrQo3VXq?gt=1#Bk_Yy4W!> z-r}!haIs4Xw>vk%0g*BZtt|27ciF2UYP)Wdm0WUov{t7U`Qy5f|$!|CF)l+-x-T zqsz)NcLiZ!d+u{FbkuBx$PBB>Kl@NbP+1*OPd{|g`w-8c7bMt6P_bvO_!qx-%YL=g zrnFc(KR`o^KJn8bAX;Qp)`dW%=FITAV}22`ij1T#uq7)IUKx$zYz^&hc*hFr(d-3^ z1Hr}MUNxWoii5NVA;MmxcY=E%U&&rlUQuAXK(CZ=|Gr_ZQDS*`>alJ^hd8>@hr^F2 zLH>LH^-5Wq^K;5;G&Q!WAqPV>l$v7P`K5jlSCYRcb5p;d0;s2YadG{-@4MWT&`XPueI#7rsJwL1d(*8O6QmyOl2jg^$}RDm-kl9RmqJp92M z(sqUB+RRG8Z+NIaeg>rCC-nT)gH0dT?0TXYX*I2#97z_+*985gvXWn9Lvj1>+`I$$ zbEz+=0{k?)a46;)Kv(K{`3(};6RvC&OA_!Xa<#i&6)=QZM5GtwemHCCsIFq2WK(&I z9QZxY&@LzE4(z!j6L8IqJvtUzPb-wNu;n>xEw#Z_YL0VzT`#T?3h{$Ig=k8IEp?={s4L-ux8@8Oh*80j<> zU(D>ylrhmEXN2+8wLQ(`I^L0R)>h=IvTk)Ya8sNYyZxLXw#9o~r1$lbz2$6@xXXN) z&%k#}%xkrYqq|4l({8siRx!I6nII~Wt`P0|z-IIYW*jZf#GD(J*SjeQ1>Lt4WFerD z=A})0!flCPidjn{!&cB^+TZli7CB8+pMc{kqOclB-wq@-G{x$4uvSQT*qL~3b4hkV zsdGZs;Q;~DuT_@y2g4Y591-By^|gy3%9VlI(Sq3k$CQjH&PU-Kwvc$*7cc(~$4dyC zC33vj(53;8$<4Ldh%{CbiKI?`k*UCq`Y_F?0&tFb>2UsVEXu+*(f|7WtWELCe{>v#;80Sz-w>_9_wsv-TQiEyLL|JD^TR!)QVMp3qzWh8oRK+P? z<*c}!v*;4$JMJR*pEul!Kpee*Jb=LPfL+Ze;iNUVsf4=_{I*^I#_dDubrb{2srm#C zeHeV6&|TajFvM^H5_kH_Sq45j6`hV-O0FpZR{GvJ9fg)N#Qy`PjAAg6c;^qj5O}ni!U}0Y>^U>@f;f=t`o1g)^z*6;VN4^xCSBYqG?M>Y4d@ltZ-B# z&mDI(D-LBYH92TGNH**?6s{(Ky0W%JBkeWCoJqd1?5jHdDP71kYmbLRve06&Yb$`Q#Lg? za_`;I;5F7ZhsB1zRL#f;c+cY(FlNkY*Bj>LWvm%<5KV+gyfWBbr+QGSpexX=zx%(K zddIlR`>0~{eHRrzqP(( z6AA{1Wua^R-0>yp`~V1CcWcsTZVjNyj1N@Q%8*jwbAS!-9pP-^1 zPfnm$l+MVl9WYh4Vu*NN9o5y zwlUCu?CGHkJjI5>hsJfgiC1R|ZGRgdX48$>(L13foqi-mrt4;tlaoDdq*;;U#-^lr z#KVS-C}3fvbg=#a(4CR=vf1HS6#Qy6=Dc_IG8&?DNu=_pjWK=xMGxBV#n_>dDrG5i zmPPPn(P`BZ-}S;b&+mjFpsX%XO!H^O8kQ!f42*<{rbOfhb6$kKMRKIX_@dhOM-`cc z+qPITMy~hP^1Gd((I#;bkj~+6kMPt|`tQt@7|Q-oYtd1Y2bJSddVk5-TGWGI0JiAG zL~fQ~ljk3kkbHei`nAz+%XoiR?j$2Da1D9C1?#>vZ$0og{HxR=Hy5`DG4|L({FaB1APHHeEL66ff?-AYOJpiqZH>0R>Yo&QalQH0%?{rdog9am z>Jh{$y2+hX$RuIHhV;aOFJ3`~70Rb<>jm!Ug=&#}#vMB7m^ueSb*T2mZ&p+g*{Rk? zq-JCZ*-o%}E9GI0WxGn!yB1g?O6eG1sAi^Q`2Pft9l$pFiB&CTsa9HKpXU~;3A@N4 zD$_7|v^HrnibJ4X*eGvQk}GE>LJ?lhsPqBw_9CsnkapB&_}Utj7KGLj!ilm~KMkWe#;A)`V(Hqde z7Q`fcsXGl0KK6Y;0VtXtZ!!v~TzYrNU;3_g*vVdcU|Ss@3B(~3Khi9~xT-wlA|1TU z3(WR|(@Tu*{A`gXwyVrbqTUlEE6QzEV42zfDTd+y$Z20hMp=m+O~ntqW6V{JdwU@; z6=F>|qlu0>6Z@R@zjz0k{qKN)pJB7_ zTZuD`*L4M3qBx3d%VduR(JJKqM@J!8)8kCzql)Wv`re~Fm$z7~p@M^~k7i6}>#YZX z*rYO58qRm^YaXYh4{_>d9|&E3IB7-I>7CslYFTJxzV%002tS<#ew7B|4Yiia0`DLo zmy%GBJwF-Jc!i0rA0juwpEBU_b=~L2J(MExtEQJoK|2`Wwl-Bg_oe$Ou$^8KL9VXQ z5BV2$9vM_b-b^c6e7noeY{24ON_pBV2rTB^)Q-ypMw9@UTcA_G;#5(0mgWvr{9Yyg zVw_au7!yvXCXn&s-Bz>*uavMPMzXd&B8ZB;YJ1o+?8t`CkZJJ4;IK6mZgoF92{nV# zYgv*wN&5^#^nc%9;wh{iv~g!Vm}%Ld0-1ZGBR55?zhNuh#EzK%uvNro?IPU-$6xTX zh!;AT_>iGnbSZlNwR5}?QoD`7bB$&?<4en&Ym4txr@*@Fo7L%DGkNBd6Osi$)$&cU z=sO#3r=~lj)`AFE_IfamRfA$B9+W*2GrKTT^-T0xZG%=u`_drtSe8nBC={Z*Uwi(E zXZ6-W_D&75ffqn4ie-4BQ8vH#q(dn>f+5q+HK;Ty^*mut$6+p_M#Y|39skCf4fcPi zPQQElwL7ePvvq_c(}-oFS~5*_IZ&0=iL{ujgE`2iJc+8@gU9duyPiM4$3m- z2*I1ksnMszso)XJV)KP2O$ah0_ngSoF8!-J2#1~T#dRYKjUaC-`$gN&ZB`y1yh6A@ zS5~AdiAp|sWC<0+0#NK~L$IQDNpZbS{TdDGSMdo3Ii!kQBF}gW(*e3+fF9kFz{{LP zu-{#mA2rY-=jAJqjBtj>;=ZB^8aUMO=@4fUxpWH#4NZGbHG=qhi-YwCS&(?@$qMjM26cO$&JsP;op7gbA%sOxQERX1)lFt7mE)v)6D$;SX`P+IzKkZT2h%l z9sUXY@#c%)IZCW#gEBL9zFM@o{zRnbx}vM^dcbl6sOdbBE$NQ>ro7B_>)UgZ$ouL_Iwfje2#V_${ZlYi{z#Y4Q5(XXozgemhc>BD5I zmEpfnwbX}b1{=GJ)T3?>cfLr_7i&UM#*E%>Z%1O>5S=XjUR`iL#Ckv`=w;~=+}xE+1N`rN%BhGX+n|M0C#f_=! zhiX#EX_!SGPY<)5xCzaLtiLYWmqGI<{X{8lSzTxiB<9 zg2P;LXvwNF~xwg;#>Js*uP*fswV$t&M zz~R%HZv7a+mr}u}UUc5b0X7oDCHCnxvB|S3zX!JSfVgwA_iZxQ1$9jx2wo0BzQvxq zWvxG^mdk5b`WhPQypc0MOig$?rMRGHF=hAg$w?{sOHq_EzTeKx%xt@IdFf1Exm(^y z(L@+A6EE}z#aPZMI?U?y=<6#W8u7xG(+Q^S2dJqWxHcGykI0bFTyhHX<~fo*m+k?d z=uU_U$wGcwYeC0yu85mXBtwqbxHll^NMF;H)M(nXv_aMmKbK+ixzbkMkJUbLE<80-8>uX(`5nH_s{qXG-o#Z3-VeM#B8>MhK0lHJz8BVooYAij6JK zE@6}@-xq(v$ZC(~9>iQ5jVXi6-b|a{xwDHg=IKnaNN4;Z%Bx%|J&`Eg_B?agRKo>x z#T+2QgFA`p0~76j0`s+4&{OtXoH7M!It?dTu1FWgn#DUMKaN;~@^{_eng}M1S|2Oa z5ROlRqzgw|0rd!!10fM5i(&)`Q2Et=Aa;4h#2W7A0}HR>mhE?S) zqBGHOiF0kL6UqERI@|BK-F|(A3$qO{`ru(tjk=Y5d0V7Dw>+N1nDX_+ueAVMOh){b zY&r|utn0*y4HC0JsZBT86FD<_UPq9^hS;E*33q>Ux)W3=LhZ(Lrh=e^;UI)AfPwzh zQ4}OHoXf=fwGh4ey8+1j5rHj_j*);`!i+`lF+TqZ0QxK1Zmmck~coxfL72uqY=NvQ>r<3&ufrU-}UZIzn7}xWrCElSA)VzP6 zCBpwAI{R2YW%IB6z~fh!Ch+qH+RQxuHg0l1UjoZ0^Kpjv9_0tMC$^HqI<8JG2K7la`o*bJ}=R|x;*einT3}(5zr$@x7 z{>nfHB!Je7v@j_O*Lk?~a1N)LxPq{PeMqDW<>lj<(069iS6#AfaTmxa8uER989wvH8~1Wr(jc~?nDUf=ggJ(sP6F}~)gaMD(Yd-<~_ zBu?>^&hhPg(Gj>%NE4nHt^7~DBq^?=N;aZM@0HPF=g+bKb5{K~O(5RM_9)YoZD2`ul?tKSne-<9_M;EB$aZ*FF71>TfU2CDbM- z-(LX3XWr}9Tb#x;7UO0VTs@LCZG~K+7Js-T{aCwRh$A8w@i-vN(Suds#?Mpild3EC z;xmtpYFU{TjvdVLC?8!Gd$s?B0O#S?>`HAS*sLnP9C^7uS0b+%bNV>NUtvF6laF7# zE1ulu*zwHWltT>KFCwkdWkohnBMyTg<;(BPqGbxq*{16 z3BPyPPMBq&O8%3EC?Q8lI2KXxS)aowXNr8b7;!MDfC=#-0rCzQd#Z`|gmeB^I9Za8 z!Fsw-eGAb8HGsb<%pW57lANLFN)IcqsPG!9eoiew@dG zp;W^vS}}<8keQ$@7MO=D$1GUPA=j-L8{&m)f$gZ#d*I+ud;Osm$I!esB zLtoRWLiX9@mZ{UZxXE7xM|=KyfXGR18!#st(PFn&{GYf|fsTrPGr<}Wdi&>v#q0vFLLJtvjLjT_Ik7uC`U~ zPwj@gT@$XCDB;>oCtQo2IyCN_g0UZ*h=X!vwzs=r&D`1b%~vh+im=Y4zbkHpdMY(z zf>)&HRP0{2E@24hCqpA6dr-`rW5q!9Um>q+zZkXa)E+_pIi&u3n9kbB94eYc#> z(e8n-Ez@|jJF0|xjHwm9j(;q67yDEd)e1Yb#NH$zY;OX86@*7i;UEVOPvt&nlYYw} zPq#ezx(@ZbPG(SGQ3A2(MFsWz{A&&Zh6@!)c-SsPl*sV z%o*!D=}=jEYNh`ibt6?oOPFhdS%qQK@&5&&JPp(p5cgd4kT-3MBr7(e9EE~&W>iT` zJjIs{R`3it;zHu^b8R~gv6iEmv{~dFbUCFqbpGB@;%Z9Y=b87f1OD&H#KP1Lj|Ti9LDy{yT>3%sq`h+ zj4uu9kA+2+du#?H9pLp0@C|~ZHmFIUwXe3e>Nq||$(yV$L94|XWNR^sxX|zh2?yLF zmyrtWe@j}Jx0VPpcQ+~szYycAa0yQ-KGpLaWgXh{X+Mn0xK=e%OF4|9TOxvHw-;G5 zIX*!~usXs7)R|n}d1p0X(~;p0gd}wv-Ps5di$%_rI|f2uAaXoFx@(ey8@zu$Bw+MF zTH@hg7P1s0@~_v(KODeBekj*Zd6-^GJm3?*Bdm0Lc@ba|UOn}MlWzHvSwuToo3kCZ zrnyAZ)p977c?mPZtn}mu4w|f>ENiDa;e~`$-NiyK-6fCSc3yUYGrP65Dg)JT=LKJ{ z`W5W7=%-93Ao8=O((haRIQ^h&LPA2?ZzuWOKHUyv+uHJ39}jB|9)m`QEuFuR@tS$N z;ezJ`>ioL*Yokq2n_@pb2Gxfp4;Nw?r1K!4{AAPI2TQK4S>zcpCoXgl9eSe|098$w zeh)w+Hr@Eq3wAr#LuaE?s^rG}ZN-edQ~F&!nX@$E>*8cTM+KdRd^cBadBfzqY>x$j zo9h(SwfoSV&^&gGKzY+xiZ%=rE-A>dT42OYFA;LpaT|8E&MmC^c{7K=dBJ{|2;?!S zt>ZsC^8KHoFc&qZ#g9C}AIaoBF5bEw=@0A_iby}Bofx&V0QcLXYge^N5ZWBW>_rH6g z3!>Ja63mkUx*(PeEZ#4l>|j0iq9hxJ>Ld**nJL-NBjzoS-=#E)x#ExZr!!2KYcXNp!E%PDYg}zQ%r*o>vvUtl1kaOS zd%`-^7q2Vx{Tmx4G}&l%2a49M^|2T;oQtOXRcFXwBdt7HHM#ixlsx$7kQ4%k=RVKa zODspiS@QgzKkW3TQeA&a>mIAB7ia68LOOJD48@2n6c4_&FlUqBj7@rF3axS7oNM1l zP(81G&EZV_%nUM!dk~l_114@-eOaz8jNV?hwHwDh$J;hCm}R4(uB~P2(tKm!ou{PBmn3GfCYe1CYRSSVrZZ;lk_*VYh;jwR8 z!P2q`0+-ID%28}1q*PbDBiXTc1ZJD#nY;e~cn%XoxOZRDTy|7l6mVw>Wg_il`ZfaH zhj5n(KWX$7*l*{y_`V$-6Ch{Xj6lJT=n&ks$Gc>xB)FfbYHRqP2_EMG3g8V0%h3wk zp`sT}4GrG6s^aR9+4PnbVN+gauH3YC{V#Xxa68FA^iDk0{68PTvi9~DiM(N_#K9o` zGv1|-9lTiSx0VCvD3?hdDb#z^V=)iEY^aOcVW)MDV2O{tQTq8$B~XpeKD|z@Vej^7 zM#)P2wnMv3N%eZY9P%hK;(c!fvdgv7F@t&)j||^M8&MMb9uF)iin$C!;~*(TKR0WN zqs(edbw1L@-TL}Kbg$7Cu@Q|{rth;)RA_{%P%RPu>1^{gX%+2aNmaM`X#Gn?Q@%ag z9>zRgha+@^Ar8+|hc-CG32VxSE+JO)`FqA( zh4#C1(k+%EbKLU{#CoiXd~in{0=2rfN>Q(bOJXj+NFP2U^A9c8M0#j@15e_i7aH$* zPF=+oJU?0A_~-2Z5x-u&$OWw6VWgS@U9i&$2-4J~G_}?qBvbQ|n~eGaZW0;u>v#Nd zk%IRjzX9f&-POLe6Wkhs*qo^%xPMd+W7N0t2Y_KY>PNM*sJ zFyZoWc6x{CU)cSV90Hy*XRGEg7IJxUe@k_AXc`N6%AXU@wQh@}RvW0RJNifIl8!(@E=+izM%Hma)0f|!v7!YGUbA27_P?({f~Y3*H)cB< z?ti&6c;7cN%!-e|w*KikQPr~kb?h2dG^ooL?5<5)AE)}oFt5=m)q;!oZ#g_?TlGMx zY;7&)5B-X77C;$0Z;xY8hksT3FTM)x4$p=WscmDbXqrTUIJN7_fQyYL?U-|)MMQYo zAO)XeL^u3@qLq3KO_S0_^o4J|l?yPRX4avbT{06#~^)@~t-RlR#blSsYs4A+8KX|;0cU*&rGbQQ7jZ)XYS(Bx3$O|5>)>vcg>F=jf$+G=RS&*cxj_Qv*AXjNbRxH@BGaEoB9Z&ANb+$KF{!<- z{Msw25TYZ=dACpxkIM9dUZeaHJ61b@2~af>bAX)t^85FncYZT+M?kaO7zu;QM;@tT zUx!ZZPV5Yxj z0&DvzTB$br=8ns#vlom(G~HmHJ5`<2-W>9UjN4K8v+~_zur`8~ugpAPA-dsHnY!iv zgYTj3Fnew=iiiYaeR1(17-r3@s@p3cb^V8K^ElnL$&k*+7*qE;qHpu&Tt*H?HtpL- z5V0;_3ky}s)c2)bW)A;Pd#&6h;OTIBxU}oxzJ^D5deFhp9T_wWPwFY0qUXM1v~>p8 zXx_0#A?jRF?o>Vc9j`nwGifG}hNHDXh73ILjRB!diP$3C$@r?F*7FO%{5ZG``CjYJ zXd(Hjv39aZPnYYsaZu4w@MwCZkL!04Z2(I&?B2v-w>rHGlH22>U#_7oO#2nZ`Dm2o z?q;>epdH03bIriyY!)I9q4B!1 z2lr*?D|)}2Uu;P&skO%wVV$HvlXv%^k?K#V-y5FMCO`kAJ@Cr*tVkCLoU&iG`kn+M z0;5x=GI{@FwEtUJdwRd9_NqiLxyQHXLL(V6R2wpqUS7@?f6^glW>G(0*8i;%Tg}yY zY>r5VTO*9GXz)&14}wdnYfCGIQyA&yS9!ZnMj?mQD3;gOYd*OXnPGtO9+M;jC2D=> zqY|M1tCHFHz|%T&95gHK3N~zM^do)Fn=qPg`~}dvF{7r4KE-BZQ?H!2O_iqi0VYR@52kQw@7XW>$!yVj_zAY4yS|~=-{EhhEUWF4CqG+6L1 zn~|;UH`}Q%Bp@e3n0Mw`&kFE?JqLbcI}+Glp;_t#0Lg?e_2_^WOlMRDL|%9>%A~22 za%f}kf2~$(RCoUkzvB%lJtPf-f_irrhTm`TPFZF#{QhO`rdv&lJfu*9Iha@ue=1Dz zFb!vWXD7e0z&6A2tCG(Nr+G1;;*eG(Lp8w_p1me}Sc8_;8kj9lP4e0$O-=I#;IyKS zJriGQ;S}aF0_FJz2h&cveV(05a#+5v)uE!IPEycK+fod}88Wsk@;B0arnb0DYkNm(|4WURJXnkNuH~g{8F`kl15|)#1d;I}Kh%Ydt#c5rx>GZS~|`U&EHi4Tkgd zyQ1$Nb~x$LT;3bHr9DtL42|=cogp)wyr!2gTtA*DGj)FtON!QEI5tCHEr^!KJh#7W|H3!@R#A zU`KZsCoA%4hZ|Iz6it%Ls@Jt&sRTufWH~;_hBfsmK4Gih6W7{uME2FPD+Zfjn5+oM zhvxcm6Y7DQ~nOx-_`?BIVo6xRh0G8#;?P4|kPK z$hN=Ugxf*UyG7pX>F5$pE0KL6m(DpkXb|kQ&~1J=dG{?SPi0d~5;x^&Iaek#8YCTU z`NiEK>>wET7Um($1FfTZBy%X8EHjp?tBvN=&gguRe^?Vxsfk^4A^S z=_Ui{!Zt?wvBi$$Bx|>C6vKf=`LITc(XsZ)2;~6+n}39=8Jyc2o`E%}Q#6swAgDe^ zI|r*sC7Inq77MuT+gPRrHE^&_!6WHiw&jo&@Z)^!=#6ht&GLsr=4aLf=N)OZn$)Ep zzUu5fh_C9=W*WRraOanB{I)Knf2ns2xa=J{&`POH#Hug(EemDbO52OP9HFoO5G>C` zIS*F+3Z96<}x9@x`n|;p$#KFvNz01nxc{`lvxd(I}+ z4sHEC7lu`#nx=tolOT9yid2x=&A`2q#Db@b6Ro2gvd;w-!N9Q>HVmxWO|?jQtHA`# z;34K|maPvx>mHRrVaVIQa*qr#X0up1BWzHEJGa?ahU%)@F7lRDPlo3Z zqG-*y?AE;O%+(QV=p!`kwQ{|YQ>nFhU`}@$ zrbwp6JT}Y9Um)7rDHxfUG%&Z9H|AMfbfr0eY9sd5$1HLtBo;v_Rutbp_Cw6^>Rv?8 z86mZhyc7A?3-NhIqn~@8Ci4asEIzc&S)G`?pETaN6bd$6PF|OxS0<2TSi4sr=aeY& z+^=DF7xH#rZ45fym7Udag8EH-^1v1EBRrg+nw*t+Pp|Px+oSort+XqOS~06xUJJkR z|3P0p^{5PQeIwOKE8q+TLrs0+rDbt3sMBdua}a3@(N$GdzA}28N@*{wvF1HYz?HVMF&!i|9eSd{Cvs)-9=ga=F0q;<}n9fkpMDR}7y&-_th;a#16E&V)c5Im_D+MirU7 zA+W3Hqmb*F@mFj&Q3#p~`frC`ygv9)MddCj_61?uy8qdE!!~Um`8ZAdZG4orPn|u6fP7uT_WT%OJD|KW#P0I9?3};Y~-y);qR)6JY2U_zkBgsO# zI{p25l!J=mVG9SnbXvABn%Scv#W(+{gGa$0QSG>#t4)S;K$oswP_wF`K+sVCKQBZD z_hf7?n;b@inNm4mA=!Qmgx_D>(9v+WjP&MUUi3QV0q%>?&3DPbcvcB#=lf<=jZ^xH z&x`YIQ{8Nap)d0zg>Y87@*y?97G+e9D?Mz77ip-BBmkCPEVohAV z;vq6RNbHW|jmC==lUh3oeBV=&V5V>5o1uRb#raA%R8~ukpLfYiE8LI=)Wb^}@toQieKNMM_ zl884`4zpe6@8~>$DFa5>g&FU@m6ChVllIR~;A%M#6!Ylxu+{KLe*e zp<)sL>vYF=yi?yPxnlvQLsL_o%ZRZN0s+~}h#Bv(G+%W<3`X%iC~2L-tTHrCcogZWaDMVAELZVIDi z0@rb_8Zk`4tT9y~s3lydHUUcz9Z?I>b>8fk757~EWobRN_YEefTj>@{lMbe;lKgIW z0&AB}wCzL(?B&&d&8s>(Bcr46zBHh!%TeB^Mu6s;Z8BRaW&xxj`?V~0=r#8#| zF_Mic9sl%MQOA6ey_z&xuJP5KHzn4frAoF<*T6;g%u>ST7~uR;laUsiV`_$8F*Z>J zj>B37!r?W}Kes;KVbZUj&n66+wUC}rq`=cz$yBro*SE`NE6>C$3xtY`)m0cWiL47h z&z4Eu2I;9wtH3<3UpBbx7~%6p_X}RU{@QWh=_2+1H2oN7p_!kz*66jppg~4vau$A# zwzTEt-*k9aa#3lB~B^#YRFQaaO@=z@ zIZU$>9QjS^?k?E^=dqz-wDJFbDVEfsI5|45gQ>5b<*^3aBAm$zCY&J*q4nv%*V6tO zYO11)ghz&$p3BofnQMMF&~kT`f7OUF1^q2oIKp13~QO>Ae&V zO(c9Ud$OJvC?VqIqRcB+Q#iG9FAqtLh`#(66vLje-CqUg-bx2_khoLdIff zjrwT=V~Fz}13$f^pZ-zrk4Dgtegh~Wiz^5OZtdb58+CUQxmc69@cu0wj)tP3$gUcl zfMlQ{XUe$^KCUO@TdarYE-@t&kgRWsFDv62`j^RewLVinC1^b9C1FTZV4fP-J%K^G z;l5_aglucamUZK-^HdqQct@uy@u9VQSmMf_E*-S$okDbVOPwH0;_AC2BMo zLIXYmFBiDFpXCo<%evQ|t5#Ca1Sg=5S=QPa{;R&fDh|uM zJ={M$X7j(-{9eE?^>YFift6sxdnl{@`C*M1-Ah8^A7)Y588;L5U>}nHdU)F_r{yd`0bjFQjjw`$%l4(Msa) z@`pBWYh6%VNR9?@8)ZQu6O+?aYWU&Coqwa*uH&JNPsw8=JBrc8bs$p=D1(fsxAZ6M zw|0V~pHQD%Y+-gqaQ$=k1PBTg?O7str!ce*|G-*9`whkibNRqfN@Kz$!+i2AO^f4j zh@G3_WcaBe`Jk7fSUW+DQMw)EjLa$u5v8UCA>%eNVyhep#95>C(5r|{Y|)l@t%}e}r|$G6S^1S{UJ}ll>S!o45ohFRj{;9xz{!0}B?~a%lUB{z>v%u*&@_fZdZu zEA_9TZAmF{`fR~S!K0P_7J|5hkUJRJolF`k7wIyn#U@aQ;)bqtLxU)VSXXA6C{mhG zB*LZNBCNDh8MG2ffwn!7ClEvT@oE2Z_rfuE*c!$>)U81>0?0pjHTf^a_GJ~y zn(d2WBr2)*Gc>7*+|W(4d6RH?GxaZ}J(Q$C@CmF*fAx3A(|^$03m`@6t?0`R?~!PO zC}}1mG7{p5M#E~AQX^5$+H-$wc2IZsRbmmf=s!B~G}0P@O8LE~a@g;<-eK``ql4y| zhbRbPpxX07?m%NSX`{F6uO7TXGkR}O&wKbAW;@4t_O;Si*I*DjQ3C5s%#iG5fxil{ zBwj8ZL?%1ujrW+f=i8K3-nlXiDiAB&;i(UMI(JJF=;+d<1m|hefsZOuU7*H!l$UP9 z4l~)8Z;k(P)l9&MOGyr!oOQE#G0(-wLP|k9>+L1#N2OdN8$5{u@Zehs9oL%eGP-0D zGmEMFq(>+@Af7+tj;{Z7Jw)6$-D>|iwoIo+HYA*CG)7+f=( z!z_<%vFW{6=O?mBG~hiI`NGL0AVG}q?o6}PBSsWHOg*xs(eWbeNPc|E)ul6P*lw}X zILq&Z*V!TsLQ9+)0n>SV;SLgogmx!52Fe}srxUKk>Egyh`F6Gfqn-z0Q(ZjaP{(=6 zuuJ*Mt+YT&e7y0O6oxQQD0(K1ujq^F&`N0BY^D7$?z8!99E->5WTYo~EP_NW5-E-@ z$Rrdrv_!^#sBZ8HiImtcio`MZeeAx1IIJU5h~RL1ki zwO&2VqTaZfd*wJ|r@Lr$I2_!=(nu0NOR@anjI?oU=L<^P&x3s7ETR#szDyf-h*???Kt!QFz~M;SP=TPd5^U}UannnlW?rwefSIH zW7VjKq&aT3VQ&dw$n}En(|`3Ko(%r@?s1&6DI^ueqnCaZpq|X(#Yz^7G*Xb$#5S|9 z31$mQU&m8buGE6AT##`4SBNPBBfcNZf;`knTjhi+W4mc+bz=0Qrpyt+Jj`-#A9aLr zYX7YW0CzPw0P*z)%5fb2H<>@?YqiW$A9pj)H+SbrH7P#Bwkp~;*Y9y;#N;vVJ`O79 zYoU95dn)=G)skhQO6mzE{0~%$5@5(hd`(7%#5WvUG5^>-R`@q3Eaoy@$J!X2T8J{^ z1{)+9c_Nil;clIoI(~>H=8RcxI1J8$vo#83<5S1ybZ6fq;|Cf>z3^y57wHhfhc zZ@r~l1A(bvj(V40&w2cfD87|S9k%tQ+Qrg93-;>%W91ADAAyuHWZ7y~If_ zqRQY!8zDd{F1zEEpE=H8v^&tlZjJtDXYjnYF^oYHNNO4k1IPiN+-J7~3s#N+n_(c` zMCxdqSs0YlCnr}J=#_LmDV%Z}h?U1OlAkGJZQ~ye^$y09GCwDH{*CiIoW*3jAmDf% zn-lrmUTE{(&#PvwRx@o7Nz{8{r~7t)s>)n1fsedYie)A!uUbUJ#Pdx4&vNG=;Ah%M z=WaA#rpat?-aQwy9cbyaEU7(b`WG@CC%JX*O`H@d50tI0|81X6^jVe%=$M&8_fIXP zZbx>=U2#B_S0jozp-cUJ_x+-7)~${|{jG`cQjZtXc*zSV!)&+7e3zxB3ew0# zR>6UV8Vtdc^-Qvuh5jw)XO3`Nr zTj6p3X~O!%xZaTl!crl>JfjZE7}0G4ZILx)>DGSj*3cQ=@P_IcyGsB!<}(ERVBnjM z8x%y8AnDJ-T`fz2gj9!wHU@hwjbpsV{%dy05h**nb2xUhvOFU+H>+!i`ko@LgpMf2 zZ%lqr7zO$YTw;>1XUuyFd4&*-ugcV!7@!G|9|YaUEnz+R4HIswB|5_*MPEYgg93&Q zCZZT#41YqfM&$Ojfb{UqL$%Dt0(-f^CCNfkht!>_s9v=o{?dJwpFnE|o)Stf^@l}m z$f`R|Q4TSeY%AjOU1_|e{94`>MuLG#%cgHfGyg63c0Fd4s^G978#w{BD`#W~6CyKH z1=GR}hg=*F8#Uu!UGMnL8AMKaOzKe}V~1<)ZUDfj=>QkU;+qAZuvKAgduaRJz}!a; z@G&6a^!w#|&N1IwM4_HlQ@n;=efR=H+sGg+S1`*UjtMc;ARHE8I1C*FEA#3-5D$T> zF$DqBnTP*@yA+y__wio+l7T{4IMiswg$G_kN3%}j&!{*M3g^8FXAphe2>|; znJ(Eb7vs-aB?5h1wu2XbM;_5#*<*i~AoNLZ&q6)!npZZ^K~@{Dv_nqGSEqb_=d08G zc=ycTx2@-r&vnm|`RwZ^1v8yz{I1=(LnBk%*KaCxms63~G9}o~lh+)Y8P~Gi_Q||Y zSUTlfR?t=-fMh;X7rsw^YcuuOO||+@IZh|WGgJ{|NB29u9jNdILLUoL=Sou)C8UL% z;dtFsc~%L3zlKl`1Yv(vlu08Gv>AC46b3*;!b73K5I<2m2^-YCqhVL^%@vaZ{8fNcZ8nH5(XT?=RL(p?k4mT` z_(eoztie%z@Iay8Ly8iUjQZ>Sw@L%7zs~q!N@mxojN0p#IbHVY92z9`Zm00CQT2l}Rxi;I>+_q)8LFLh`=N>d|l$ysrGEv?uX{KKrFFHOZp8`y=KanKCc zK#uQhG0$Ok8OS4S0UFEiR0@J=Hc|3soCC`qkkWi|f865w6h|<0tUXu#&Iwe{Yc^Ry z)}!rJ4g6+e;{|?;f0cipjXuOQ2%zuVZoF*_k_wS0PrO@wNfu1w>CGfG*5g7!kR(2Y zu#5I8*_WeDYDjkMzE473jXV~guoV6+Lz||zyEByfScw@&9i8>~SNT@Zd7zVw|D+Rs zmF@DeYp+vrS>@nuQ;+N3Y!Xa+OG;$+lAWcm^Z5_GF(ag04X9{-(Z2)auO{+qI*T?* z*yFozh0doGSTYufQ@$Wg;7ex=={?_}*%fJ+k(AFNm6ZQsq$G`0+6U%c-5kpJ2u+Ud z=F=co=D;jGlhnusRZV<6(((pf;-V-;Ung@z06W)r z=P%8A(@C;*V^0Z0abcQZ?gMhUeHl{4j~U#f_QPy%+vC|Hv|RABPc9<{!@*262sP5cnzn;<Yt(%Ji&vfsO)Zjgjrxvdh&#e6d%zO@l^)&!5p)a!Djo=b=>S-S-c zntVGK`STmb`2sM|7W+{#eA_qp+mmj{sG)IR0xM?EYGU)wk;{KdD2_R>^;v|F1|#jH zVVe|!mjY_wlD4Ng!cnlFn+X4VZv-1>3&%4d1RtLoclL6dZ}&zmaD#Mn);g>`w0Gb& z_ZphexaB*_FhjwnLupwptJ=?1G&gN@VQzgDdqtu9-|{(L;cJGm6E;W-ySCbkQ5yyy z_XZsk3IBbz?SFA-q}!w;r{mooPIwTZ&@c(E+#9&ah2@mAmk(Y5E;s>p8@fydNvSzFd% zHAnr}_2kln$%pCB4*ZSdNWLZ96Bqzpg9cw1j>2pUZCSm`RiCk0WlE6#Ghi5U=l`SY zEx6+Bwq;=&2^J){JHa6d?iM__yVJP46WkqwySuvtO>mdS-5nZe?BhM>?!E6AcYiN{$4J)&mtvK-dt?~6Xu)bWNg87mv(dpVrAq9sYAzI-w0p1Qw=nazBQ zwuIU|RRpnryu$V3GQ^W2JP@tQdi7Go?tBX+;V=2~WL=GSbgv*GORO6);90F7QLZ;n zB$UsRVQ@mOL++z?wv_u!+Lw9c8B<&n>(?sH$5*h_J?Icsa^wfpvP3IT8GC8Yn`tfvm5Ijlvt8VO#m)KrEQ>|} zYijzS6rQYMx!_J{ePO)b(CnFZL|BN6&1|{odHq z(by9Zb@B<)*D2q1>JiJtGYj0)xZMuq!CsK5kJK2NcH&Ux?3Srl48;+d)PZ=1!KDw8qb!QNtk?cZ7Pce^pNAN2Lm z%g8v4_Rk8;Yj2`TAzZDA8OLY0N7x*!vsN%%#k7OWYI*K6FA?$?ErDGxYSFjX256HC z@8K*nKs0`kksFM{MZc?B4eLm&q`>2Rp$_bkDT?Jf1H%4m>L?AmM9ZRQ8mScLMtWub zhfu9&aB%Ity^^N1)x@8pWT?%0)6q=Jwo=Pg>hRR>dP?2^iE%&Tgvyh)Aw}2S`hbNG zLwl+E=Nx=0>jzmJUsYa-pf2ouZwNDZaaGOtvxc;8^vfU8-O^XmNj*8))+^L&TpMp~ z%>yOboU7X5RM=bQJ`ksuG$*@`d0}d`-*b zom-$QZz<`k@toj!V!V&Bpp@LHggm>+_#i9&Q`-ML^Ur8oP%_y>J+Z{p+}e@5)-oSu ziK_zhH5rD#%Hw2oywF@}3&u4%60xkOt^n`Xy5e(G8e{B>@t~K?9bto_WssD8@wJd}JjqE#HXjrt7LR2i~rRlv9N*c?hB`=qb zbjUO;V|5RE%>o^k`zggr4&-+VGC7iU#2C?n!an{o5nAt0D8}#y%9{7`r?SJhO={u> zSi+9KmP9ZpXSGDHx($e%A2O-|J1PRD|_{ zL2k1Xt!M5XXtrHnB+b6p+<)mIwOThu{zWc+vE-qzG`l0Bzpr6zxlTIQ3CtpjD1WO& z`9y$1KJO!t&>XX+w@$;7@PkNzT1j+40Nbf+Mo{2_H1s}0-*ml)s`l=8CeBX6XJWg6 z3ZPal!_W!5L!9$*SxLKCd#`_jxdr+9>Z*0l)9OK{Wt~s})lYRjkD@GBkcC`2-IPWq zRaxgNd$7u(MDYaMVNw@LlwTDbf;-dX;3_f$GpbjBKBey9BSLZy{|wLIy&P^KIi z;vZSq_5S`d(-#$V^_R2kIMCk>vUpExX@B|YW;rO8s=wv@-0Xhqanr!1fbLl|5@s|N za=b)^tf@{amr3wBF(;GHx2%wqo12f|?^w~rm4;2SbhCdGq(mUxZa4#;MKve8WLY*s zMf@yE6-5m!A~^G%s)DOFV)1h=_qZn9h9$uQn_%CAL?enec%|li+3v*E*^p3pm8YEr zS3$GvOC9AH3z_MMGj`b`@VO}$M%aNhu5lt2!JQClC|uW>>oOehhLTPdxU9uz_m8;I z39Hy%`Yqh^K@*s@$DshLl;Z=|zqRcrUn4%3uT|MRIgl*aqF@f+z(D7o(fIRpc)T|ad%D(c z7s(^#Os~9qVc4%>DUjUT`h&jFU!ic=?1{dVZobNhXqkYt5h;gx_%M#&x1Wy z;vyfr>Yy6F`xHgE1NA%LM~Th){K&RO4-*z0v#5qzrH`#x`MWTF0w4z?Q-sgYDDNWw`askg0 zq)p>yJWFvPx!2<~-eiYo5Up}e@$bzD{XXh0P|NAXycFv7;k*&zz96R1_? z=AYfx5mLxAge8NuJ($e7=oR|v!<3#(6`x?DK53H5kgUP<3`f%V2b^fmAVsW9Kb~Ol zw;NnP%Ivi63C9y40hn!ikJnVZmr6l>YBz}NRM8EogeAP-$5Vbj zU0(~L?`7hf`0Tj^CpS7v2o@p_rlkO~yXFIBtLr!?qP1PzLQQM6RfD>(9bGd#mhD#B zr)_&)aGUr07!J)q9$gC|e^k|V_vSkHY_yGOFKMP8;f&C%n9a|TGDjTJI1Ju9Wg<>e z(~XzA8oxCgM{~x-8vTntL&RNaWg!sKbrMr;rEJu@R=PLf;60=yF=ps}tO0I77Ft$6 zL=5ScuR|B7N7B=%ngrMc?4bD^ipGq0OSkSPuCq`#X}&Dg8HZMgUIYqW-0DoSxlu)@ zFts%zzrM2OG?|X3{)Trw-%?b+7tY|apm*Io1Go7>1cQK`S(;FYyehY8nU6 zxHOgui`|GWvK@R+M{?s;5r;@JK}C5S_e`APtRh3aKmm$f7crZo} zi+q}4VIXrM@w!WYC6mfPjdan^+hd2`@h^pq^^T)oSwu?7ttKq~R+l0h4%8X$XWtpt zmxdPxGJ4>434=mcg+Pa~9RijKdu>$h)#oti1-cVh|$3 zlt9f5E@~GT-${-y_zbX}_T~NyU)EhHTQhN$FsCrDPD}}PXAWl~D5a#IJb-z(mYUF( z0hutXE>?oEZr$WNoPf8v0Ugvf3@Q-$^Iz-+>H2npTUijK)~^?0t9@{@k1bDzxMv_c zRIR(y5A2+ryiKZp*07sy=c`9T%aQ>bHzI;O-$;OwOU?X?Cq`-1h;Zn!lTBBxW_GdL z+x#)SZ{W%1&3GbJ*jQz;KcWp^Qy6?_Ryx(ZR%w(ickeRi?48S3a1uRIjH8A;PE zn5D(^&nh=?!APEd6C8GH)>h2zs~mh?3%1>lW>2F%&oYaaQX)l;jQ-_2zu`%pIkrJ6 zQ~TkNDgi4PaUuO92|-A_GW7z{Z0lj$-11uL$M^hLnjB|mDyezzgk9}P_H!k}6x|jM zA1o571Oq80)t1vr7bYPFUTc6Ae=64UL612$&HRyFd8XqCo!pbqJ=>xWp`ni&_jIbw z*LKs1SFx+156UmzTc*~F)oW^;hD3ES4yVDl8vfdQ&a{sgF%ux?Pj-b>n=lrFIo$?< z{~j>?EKx7lbmKCE<1+V}w&Ac4?61eqae60Q!#7BnT$5D|7U6ry=ftU#@6ahsACKmh z6$64`2zWXe`KEoS#XiUF{3V*9=Z?c@bZJ}@oo7f_P%;enP&9PS6(bYd47IVChcVhZ z%|#>?U%U{CtN#P@Ez+YX9-@)wIu9RRwjYt*S_ZHHa9jv@P+JTRNS`>UbCzmO>b{Kew#cam zyTiI+ycJA%?LA%aU(Ca$*cA48(Hzz9F!*4HEP92ig^V859}zG9I)n*tqK%ZhznYA` zH@E9B1oJ??y^b7BjLO<-wXys)PV#?Or}o2r^p=nrhUwGd;uVs~CUsUreF#}2i16_DYAGxUqH#WhZ`)U#<^PZyD zTns{|VLCA@3V>*LTjy*_n=hTy`GdS&$i3#)W<7|fB8_WpnabKmADD%&S!Ovx${$V@ z<@U_LG`QCtG=BaW81EW;Be$jnXh_!KRzqlFb{w|7bbt(X4!)RJ(%pC{V#-=GFRD0b7{#q4eOuuN8!>PIN=YkB!mg%K9i8Zu{tVG;b94>J zrQ^3qp-7!rp4Bqz&AH=g>vo=^21-H-n@W0Za9@f*%zw)Y7q$a>vnCvgJ}0~CR%AB9eb;tUfx3br zeemZUpyL)$<_2hvdqr1p7+~RQ+9<6-=ZzG!-*vu6#{>^6J)URVs)vFYFP1!MV82jr z-L0cNd|#{f+g0a3UX(m!LP^JAw545=3e8Ek!qs;K446HbQmP0DtgrR29>StY*W4Y2~)y+_yFu>4A8UQ=@0|kt;!ysFTb7 zXzu$X$>PYbgBh#alp(D^gu<|(H{!{mR&CV!6h$?uu?NN@Ye~YAFu!o3Kik?foB%jg zRN@k8EsY?``Ely6{5zacq=W~?lov#D@z~?^->i;AdFp+CW50+tkETO`$TBj0=jLo* zN__3Ib`W6w^h{BAH8XaJ?B}UIK}@1n#o=zkzK#?$rS=DCu8+eH#krX3&1a8J8phP~m$WN^_MSH~ zROnwK-iRj4NA^-tmx*}WG}XLe3|i`n!&!ZS+XuegecZvcer}7t6`x(xZ;$VS4{&c9 zMozw^c%Lt$k6aV$61$&@z9+>KG5Kkb3`<&8=!Bh&R`Nv1{hV29{;~|$=gmV56Kc<_ z0>u8r@+RrmHB%k^^=v!t)>>b`LsctFReS```y$&*;{nr1o2*YTx5(=&m}CiPnv6Z? z5!IcKnoEkTW7^R z%;6(twY*H3g!odFZM@LlyA|7$XVWf;#_sW_8H0%X2s76UXhZlu2^ICGDE%4_d{#(J zHhP-+P8$FM5Q*)ET5FF2RwAM=t<>}^tdzZMDD7-_9>P-u;_3q90Vn%1=C<_<%o(4W zu^UFJEcgg)QkgH{ zL&i8dg~x@K?s;y*^lzpS%Dk>;!Sc{e(BTQEGQpa_Wn*5bzqsN1>DkDCq_bWUWSAYw zcj;>iy`du)erN*W1!JCXVQhXD zm(9p7R7$@!8Py+T!vzA#nyseo^ddk5gnWv(V1qEKHE;AGb@4K-bHZ?v&DB>UCbX{k9W8RH&2v4PIgGPaAT>*Lyd=ADQk^wS|Vohw{W;s@Fq z+JC?hMeexNxuKWN!`PNmpx9>h-43%s-d&G>pznSci;V#^+4mkc&+XR;Nq~uc;;zqo zM%qsN4yz#cxqu)8ByD;okOKV<=Tl5VL2*=j2`PfFTc&QIbiPRW2FIcf4U-zvc_NJs zfpSlxBqDmnnq};)#h#M0i@R7G!c4_DK#lQI|0JpZZ-x{Bu+)E%+dkBQspzK@{5jkL$r*m2Q=?G9>ETKwZql-rpN>*OgA0eG!p{=5$+V1%4h2KN;iJF4wa6^<*n& zITPQXd2pN}^wi0DL0%aT4HdL8j;SZ~RTdslu@&_&bU5oT-0XO@W%if$ctCk++NQ*Z zm@Xq$aCca7zUyKmyYYhkv|(iM4h(_ZSfAD8G@}s9YcQ49+gbfGqmi|(gJ=!K={E)v zb8lyp3m03{IZWoy`!{9dk@-!z+EF6-cvMk`O=m2k(Y|e4zZ;{33Of5>Xxyq=z!45blm|IPIg07c&kS45!y7GnHL7p5ZC-}^YoNA`3 z`F)cde#Fo1{^InXU`KZO3|l+z9n)q6?Gxd`<&|cE1MB$%wn;-#m?v|N(hyA|vE?-E zrG-_5|CH<}?#;K}jwbw#U9PB^cDd#f4_}FEXg{spM0q54kL>{lhhdp|rdyUZO?j1+ z4O>5lJ)ph(!y3NR#xz9-<}d6j@*BpXHP)u-YqI|IPeegZoh=ANUCb_wPi(ZbZ9-CVv)!Bqi;&<7LkVf2}6fvcdEIC`zE}s z34&cBN$V%3`L1W+M}@;<&idQ)5afFNo73o+f#0tbM!qYd&AR zd_8EQKANrD@ZjfPcayt)z3XXOcxxfFz&*}(R6p4N2^AiOOBLHrh0@TwezQ%|VfWNE zkrz{#%&Zf2?|!S_E%XErUumkJVPoMlQXjY!j!#5hVTb7hNNSmmXro%qguZ<$w-Kr&+GU9$rZB!rxB}t&d|m&wRI`-*-9>vkHXeg< z40;ZGIutA*1yD-O1tV$F=PEJmslD;lMA=eJ!^&m^!obQ=W0KwVG_%*7YZExHhPri0 zYshlGDdEm#=+pI;NH*BoDC<v{S)fV9TB*?q5Tb$ey=xb%Au4EJ^14JUDM5*53(_<0xxNk zORZNsnVkO+4;kQeRbT;*zRiKo1A*3|o;>?D~IC@Y~}k=P&){EDyIEkvo7T#J&yug7!i%m{+RhzEt) z@Zekws}x*?_l-)BwoQtDu^iQah@D}+QIz7=_?!Ze5?T)Nb2@i#WI9dcDHFmd;w40+ zbeV~(qDW{3LEF;2V0&3!@duW4o;i-JD?uys|USw(eV zHt!qnHC9lmTHT|EMi!(M8lv+ADniVJ*M~FI^a8tj5^^C4pzSA`ZLM6@iM-vihj)4O z5^Vx>eZs^!sA<&WYsyQN2=I{(rJ*7BR_g=DanvsZbHmC;#P#lTM6U{ni*zIX$C=d1j+Wtd=f{lGL0r9Bg6yuXdQJ^q)H+}AO~z@~qVCzL|0q4;oy+qJ zOpo?|4$41@5d<#uciOm@VPA+1?Mv7K<^0U{EIg$E*_PULQ3X|Ow6SwSl3DydPg8tq zIic!zqZIB-N+aV!{{)H{X(e4o7(~U*Uo!?hP>qG2UcXx3dIe*PtK7{ESI51&W>}>p zVpHQnsD+^|8#Vwv{sWC-U>GCa827SiL=gwbcP7d8n4DVEOfuLDN2mS#ux{lSI*K^a z2%Id*jCnh%c@{Cj*m6s(FMhv@e6Bl+wk}OlYReO&^u? z{@NdqbUc=;g$^&vpn*tNc3WT^PbroZpWJD_&|98J7NF^u3PO7gCoD;qA0Ke*9A749BU2q$_N%jk zovfE?e+LsKu&Hyj4N7_J`Dm7pwXA!dNNP|I8=DvBraC%_bp@Q@jY-34B941o`FRH3 z6`&|YoZYrMBrh#lPIx^iDK|9PxGO>R9+59+8?RN7(G@o|0?&vd9#!@nt&V?irkQ2p{`33) zM>X(2)Z!;@bd)0gP;oJyLR5I}Kk&6&Gv8up=m3GKAK=%uw38kV78YXYb4wL;{1q9l z6vO%C`feN#6Bnfd_*=h4Lv=r$Mmw0Hm z4guCwuS%qnsI9{4?u&Z>;RjbZ=6=Oj_uDR4Nf~Vmtd~#IW%obMpl0I+3U+RW@3dd$ z1@C{CD0(ydZt6Zccj;jZvM`reH3|ZvvitYcwKne_&xa^e6N7zHS8jA zq`F(ZnOyFu_DJ=42qbkaA34Z!VpduuUijicq2%1>o)`S--_o)Fy$Js2B>4S6B|H8- z9=|GLj9G44qgL@#^c!08T6QKv*NkY`NZa;@`F&y^f3motY0>1FmnYFfnDorGiV2=v zu}C!1Z{@cq_6DQYZa68P^C9EwlD2#z-8JQ35mBNNQl)9 zI;z*M_G9U-e)+RQHt%|cA5YBhoSnNDQ+|d^Z)?3PDMEzp#GN!=7{|4sZ2%nut_1$6 zKYaKPWT7lBx6Hwlk;;};YZ{(&It3?GFAWMR&6|fPpIwyMvwT)78(IsK5Gd!ZZ{g*dgV1_S{Z|&rr06B}Ja-tA>qtl)f2uY+wT}2#6Hv%z#=^okT3R?K{en5P5923=aNUAPjK{9pSl#X zsg(7eHR4q=43~h)C3J|_qwhvfx5`x0vMO7Mh%mLri3g3t9^){VWeR9}H_VrE7;9eQ zx3Z%n!gWQhJN4A04`k;-trfgBUQq+B7xOB^73=sr36Skb{9gRTob;Rk%9b&PXbjA6_#^tb(Cru})+UYGGN z;8oALUK#1CP>5Nokuq=BZzHY*o-hD-fG)b(eD53QMW5p?)ym~GsHY&E`3s%Pir@3F zpYwXW1V5uUy1~mXji80o{#f13q@bS)&Nu+D_?|Q0zxEGfCvecGL%lvNMaqk;7 z(i>V-3efl1OPMxsxaa%Xuv9w|e$Kvbr+WQD=&?bTbBD+bB zh8LrE{<_*)bcPxC?KzS=yb8o@QFXC80?_Jh6`__I;X_@~ma^`74~AYsUeULoF{aTh z_FMjmr9~5M$KKr7;d;?L`TPPrHNJQ$l}+cG9^_Yh$Wyc!8&;hBiT1zB&;P4@0q}a{ z@cG=h@XC?OO2SoYc<1(x@~D2#{MAJEj=4w#Q&`>%bGy(Ur0~k!m0IG5xHzK{jY+Rj ztI8zs14#E8w@)_QYNj5c=$+nj!ONzMNsBh}EK32rxKLg11*sn^uU~cfu(QXI@7H=a zOyMy0RJ{>%Z<1F<>CI&pzHt#yKtn>r&~{bK5_1sz1Nv$r5gSlfmnJKxX+A3O%6+mD z>t=QO#TaVC(>dm3isfwv8$NCy->$>L2P$DpR%|I@N4`*XSLb9!0TEINM0F<`hU8V3St$G)#V; zldnZ7l@SC#>`d%0*nr|B&i;Jl^@GS!RCvO*S5#EU9?uhIubD7R@(#AQ^H!A?E{qGF zO}jhI|QQqejPTNdW5N zJBBM~cE;l$ht?f(B&Kox*0se{npHZ{3J%Mp-a3)IJv+KPc_Y#6mu-+PWAR#Wn)Gw^ zjSP)cy6y#Y$mJlb(y@F~?J)7!5})=#)I^^6wYCQ6`#6Z$z~;7f*!fz*Hn1(Wvwh4J z(pDm*t*@VhXg<>H!Z|anRhTze-YcNH_cUMQyJu}xWXs1v7d5FIG_owNV=#@E0jmUO zi_M}gmoGl&kBmQ$vzFLm1h8Fx9QmwvWe1$hw9sX-uJc;7sa5O5;8a?S8z}adp(7p@ z1~o3~OY?0uj%zm!&>Krx8%+O=Rn zNb>Z8o#=)4xCkOaS#B>w%4y6|3Nh{V{!87yxokcxVkq1(uo|f2o%M!KYin*RE~MUQ zlL^*B_N3%pL%m_7_!P+ZMS{ET2Ch zzGVsB-C*{`g>63Q2I)z`w@#mT4!Y5c9o(& z2?x8UUs7hgCkmZ&@CI+CI+r_L8j5B`!5O^g^>%M3i@HB_4!U%*e%AcAx5+-A+y3L@ ze0cTeTwME2b0YLH=nQE3U?&OT>?!l)tvdHV$Z$p1!AcXGT60KJuzr&&Av6X}d;eh4 z{u(*4DfIO8r9-Ep*{&a9 zgkm6EBeZS(Jy?g0l6-Y?bV^bW^n_=Eu^<0oS8qPg75OumawJd%CucmRv<%uEb;*vz zqxbWqhPjhEvr8t1dg!u3+-!Zt+jf>~>6yQ*n4^xercJm>II*Rm0q2HeLl)hg3Dik% zFBD%qiNnAQ$X*DN9!6umu=`ii*;p=Fb4Hs*k<%OZ@=P3#PmX9+f8DX7>nvJb%R-m> z+T@bmF!eve-Tz$Wo8O&!MR91-ErnP%du1F9U}N*M`EZ(Il2nV0iCe*ro2tr7@U5XY z5ye8s8tfaNW{jD=!%HWWNPQ1@H!R*=b?_Z3RWfx^ljHHB*@(r62zL(MgBYA$IG$UP=f|;srw9k8_bEDR&J)Dsk zf4F7cT!g7x2JIcyLiJ-KUFdg?*d#Tf77wk4EH=OHo!IOHgo47tPjQW&c*#5S!0w?L z|5bOsqvQFT=h{^H#s7YKxX__iFbDtoPcZEFGGqh^gsC>QzxF4fRlhWpBE~@s92s5s zZva*f_<}jv#Nz_$lU`n4#P-Q5U=55PgTL0=oJtaBAkNR8CN$cii(B|kW-9}b4^*Y0 zlsDqSl>?XcmZ#>F1F+p-pT*^$gOQoIJKnz!wWel-Z3=6e5(%uY5{lHs{}eI$H&^D5 zy=!G{-$yxQj^P(qY?3Tf(UoNngu87v@{|uyjWlh1%IU>R*L$OYKCWw#uC~zETZ4PA z!98`T2bvYX^-EFsXBh_mCYw9{$VM*7|NQ*_BjbM8g0aEt;TLmFS0Nn25pn&55qQRc zTEx4J%#UHP;ux=fEnRwvh?`109t_b(oZjvt6;sv)LR- zBSVMVhThXusDQ~qj$5{BlKN7_9)mTv<*d5y9qQ9Nk7Rhi{=)mTG%R?YsSw@x7Rzfw zIS-5b|9tve2baBfAm>)N;C=An#jM00zZ%5pV73nOf^^Zt)&XlBThN@|@8*a~bpXWg z5WAVd?(9p6Wp-s#;NcmL&w;gm-%ud=5~QC?uV8!}(R6J9-+6{##MTU7On zbJ!@<(|S>v(0VgzmXXsC`a@-@UO^1w#lYijEeiNfi!Gt{+Pw&+pS14#FINN!h28J6 z8J(B%-{$xXxJRQue#~NZi}BeEB&~fn25EYG`0dAqcvCnOzA&X=2h8*hVd6Dydrv4| zGlxeba!$}5JYpIo;ZBrVd3`zK>Vy=J>wg;-xZOfqp<__HC4`O=PEi(#i8ks^-XwFh(h~bF-N`$FPe^?u(p{?nD%2Hf=ZBoy; zS08}QInH|^VOfN+?%{%jc2whr1{!>g-<_nX z^u@M^{>fopI<{p=9Not+A?f$bfzu?L%3O0yYvOzb6w`hj-c`j(tx;$Zh?S<_*+<%u zu-1(J^wkLAu3rP&JlOn;TE;3R3uMx;hMVwZ4Z5Emd6i7SfO zD!&kf+Bs!pMd~-8R?Ra`a7@F)yNLd6jkcEqx^EtjYn|1hP>%7O=9hwpwF8`O3C4vh z0-+{%GLO}rrPY9;ijPm1)lfhTs?|!*hNlBjllLASM^oD}530)&amxm3RhaH;F()Nm zwVC;C{?>Hclz@l+|4%y+U`{ItOW4>ybIUa}y87VIe=8(LTe853nzO!0ucBmL$_pnUi6h%|Ju0m$@`KzdirnDy4rasx*#$wJ%k2 zzYrJq!Jj3q*{Sv~0MGc5kriVpas6!k#48^lyZjSLgFdCQH?Q|dLtB&o;`Frf`1m*; z2%QfdKfvUzjsF$3aSax@^$cxTMqvDIvI``G%n2nGCuilYJAee2~P+xD8WPd21 zT1{0IxRTv5Y&M=5$#njmvnfgHhU`1Zm6*@p%`>iJB58}nuxPTuj-oe2kk4R7O>HQt z?-CA^-s$le&U)RfoZvVQR11=uik_bBH5PxH#W#$Vm*&%5Q?BB2a5v4IIVc%!vwfN= z>HY~L^S?CsnFy%05vIbGaQw(SQ}$boamZ$ff5|IqVUP`r_z*NA_xYfv-*81zuiQgx zRZ)dl!8zyQt;Y!DhDcjB>`4VmOjB^^+#O#iZLzD9Tivi0sdINh7X(tiO|mZzK=UcY zl*8PQDlFWY>(s~T7^Hi~nV%N-5xtBwV~7$s!>i+&kL(vUtGPv>efmGa1}VA)#Wl8z zo+YKK$MAI~&LOky#Kd(D;FuGiWCprzm#t`S5{bYPDjj4|eLHLafkJgRy5kemePmBj zi|3>4w^`>&S!Sn~0qZ~gB@`+N#)?3nvw1F$L%B%O-;!tv{TF`TmgNrGUtukcTp$ly z3dMzul8~~G>8i84u_XSqTchewQv~u|cw$_%pS=e~B2+pWp4dHR&O^|e^Q`Q7cCV>} zQujXzFWql17rao%g19KufbM^WdV#w#dG0bh2hBIgRGui9)9;28{D(4@%l>fVeiFKf zbKSk*9~YF@s`!TE@`1oTH?RBUX6Zo`J(=%P zf@D$!!)QD~zfSsjG_vx~`r|;T$C)CTFduF1@=mmCmxcG~<-GsOm7H3BtewT4X{z0F zo?b%KfD=uZI+y}-@Au{gZ+oX{I(M!t&p$sDYiBRC91ZWx21eydWTDbMB!l}uK>fd` zJy;^H-$Y^i^zmUx-;qLvR{l#%%9GcuF0kAcq*@X$SYF!{`8; z#J=%5i1g*Y_-jcJ^R1BM>3vZ>GL1>+R$tG>zBzK(V7We7z37q>lCiYiKkMZ_;Kb zDsGz@H2mNT-Zvx+`Dzp=Y1B1R5o_52qM>VsNn;MZm&jF}oL3JfE-x!{=)~ljC-*Il zs;;=KJx#rTbO$d3GeE@}J%i-oNt}uMT_!uU5m;UDkp35LhQX71EnJ>TBZay2&2RtG z82<;D?h~+wjnK3%#;(+5{GvDpbM^! zfr3pf$x$gdx=_>m#=jZCoVP6b+960mIi|2kWYp)iun;`P-4`mE9_;0~1Y^eLY)D!!B zo9khOB?`n+ckE*~LU>eU1y``h_7*be;{{=XClg*qjC^VyR%`di*{87_b$lsEZ7-2W*og9q(U50BN>FW_%5S z`W!oa<)73TN~)4B$rSkbp-Ad#6ypa4m20&2Tk4#MH~J1$(jdf4x})rCNrDOmo^GT% zG|Q)_C*szB1b~)IOiUbr&>x;09EeM=;Fca23|Z1k?1Omv+jf@|>){w;IkA?9v}){g z2?Hfy869a=OqF&^dar}1Xdq%OE=?UvA8%1wbkl{TU~;IUA&2|%9E5SxqjC3s*)G(* z$ZIvJCC}L|wdkyick(Z``6?WE+Snl1VkPL2 zrHm`6<@M9+|LKwe@UQ5o-!mB0K@uBpzLBH}iNC(_#!YGv5O!dwILXX-$ka)W|HN~G zTJQcX@N_q6+wAr-$Qw8FQ429!#R+PlKkUL@Sp$CMcZ{5u<(gTC_4rBE$rPZoAq7uk zETHZqTa!>Mg%(kY1{X=wK}(PMN}P)B>yQjnyBA6D#}O6LI@CzN;7fiJ~i5zHng%@wu;?&3+g&Kc3ZJ0kTRn+ z3xu&4!q8SRM?wc?4J^Yn*gS!kBUkhqktcXJ-famg+0ttV6!0LWU48wAQMLVKV7!mC zgTm7grpYA*3Txzp$re6;?c=eCK%TMB{a<=%F)tbs=s~8BusdI@^&ht0nwqniYK5ah zV0h&0F>&sM+F?Pk3j@Xh+F3qQ1v&bU&Vp$_+B=~w>WPUnya~$XXQt|;CimQ> z4+^+LW>|aVmPbb9T&$G{lO$})D=G-@G-_Z5juKBzmQQ;H7A$D7>Nv)3jH#PsoacMS zGdMeHMU>PRfP!?fIk4t0yBnz{VDrCw^Go?m@zN;2tOM`6r!0|i84D+jhv5VWsy6lh zwR8A?0bQx10f+M?J^j^s4;(9g8=o0uRJ0uxF|Cwv-WcTyetjZX^xf>u_ek|=)ns}fR22RPVgfek;lvXn-e0xzDwA}Cj2D6n=+rzz^({JgHY z?aAi%b6SeU5gaT`c8SXM=_SRJy{h3!_HyiQ{lExAyfn!V7;8X|DSAp$5=CHr72G+Z zXHctVDk;zjIPqbQV*Z$f60!ye6)R$>@&K_#ex$G=5+cyn4Y~dF zMP8ulDJz||F$=_Ua6C$XmcIvO_>e+@C1WI&92_4Oijly_E9utys54%cX4Q~kriIwc zgQ|5-{6kYywW^4ME4i=0$s;5sQl6;ae~5jSx%qD*0b@%J*aV(wMrCVAe20?p6STWH z;P@JLL9nXdbxzkj(u}DtOWwLUp&qG1&>|vOdeHl|pqP=HY;99?orjQ@sppGvbfRS$ z@+NOw6V>Jd)t8c<7y<7@ONVAoo6!Op#yJ%r=IHx!@$>DI0vrQQY*w1T zR(oV{M02CM&pR}}k|a)jCHaVg!W*jg_B_wBOc$xPw4wio20r2SGcDPdb1`_s8~nNQ zUoJeoukiV-2cNnNdaySJ*OsW)&8*BiM;-T6d+Ad~o?}b68qeXWQeYJ1Y-}5+^7vEM z+t+McpGZ0&4R5k-EJj{Nr$!Igd_MIWCP6fLCs|i#8QxP=!=9R zQIS)q&L;x0&jMW6-UV;7i7C-E)|)sJd7heu;B+2 z*%t8e-pyz0R`B5SmSkX-VbMG`Y7(%tASj!+L-AV1tj*pbu?VtaeUoU7G1ViwhTUxP zTghKyAzzM0Q|Emtrba%ke$17xYdw3TbDY4^vZ^wF1eHqLdZ@f8MIxFj@zlWwA4gik zQH!%J=xMFl>0Zss&ZbUP_CT3ohf>vSE&~3l-s_%;Q`>J;u4D7L!Fu8d2r|B3H)4-h zYE?6IdxaQ!t!b~DyxcOS!~!zIwU7+X&yJdXhl=a+rWanffol5Np|I_9pDT6D9HjJ* zbTty}PR)%aO&0k*TdFoX|E}M(kk)0xc%5}`tXm<+{gKt@%K7qb{j((ZAMTl9JgdX< zZ5)_FWM2HhRF4A*Yu{apa(iN^o<&-#{v1^byzuQGy$h6*x>ia3;6Q|2@)bC5@x!ut~u)^wwC)KrY_)v((QR^RXqb07{LFQ0 zEIo4AL-lvmn;^OM|5_mUH#ukQM5gm$uE9P!8RkcQscr}cc5f`LKROp;D?!xVwRf+Q zq_(Jk-XZ#NT@f}1TX)~LQoN9F_4^-J(2I=lV|)t*bkMHVk1MEOmLkC<_3u%_O&aPs zF2My0k$C8Ng~7G_#OE<9GS3S#5d*UrT!HN(xRFRST{E}YvQ z1Ch17>sMY)NY2URm1sAD8cYW|L-$*eKdozY194fDE@~f;H-;*vA6GBA$T|rcZ9OnS z;_)U&6+oB>ZrbX3!;vEoQT>$e=P6ZA>67$M@eaG~cCfBZ5w$4YhCm6u^`vHqfW2>c z2#qf(-=BsnMd-FR4kbCD9mp~4{rN`>FQtR--gpJxdQ0 zYGXGRfZcpgy0|T8Fw;aMw;Hs9mLGOqw%46uR!U_p)&A?bUN ze+gY}D<;wsh6Ec4RPTf+Y^{`#2Rqc7Uf*Xsxvus2pF5P!SocWK4P%8q@0;6QVn-)N znTC1`5x?ov)_uU`R#V(dSx*(w86EORE9N>~=O+qA-0RfYB(7cm-w+LyqnuI9ttCVz zsT2WOuim^--n4u|p}CC4djppOJMS}X-KPVMEM7ClKFp;v^pT@F_xX`j)dr)t$WwIV zCgKSc%S4e+eJ?FQ1}g15{K0wN7B{~({i|GzB{pB|3H>wLl0=o>3#*PtnL#XR6( z`PK-qB(;BEnLo5xA+|`X9l42jq0kV%Cy<=8{b9jYcF_O+5uokUXjw@p~Es%H)PUb^tzDw7Xl;PXteeg*Yif-+9bj73&AYa%?f7 zADF{OY<6aV81h}ma0wq7uLz2SA2awuYiDO=I@s}=iHchI7zP#C#{lRAJ$zU?KlAY{ zoG~pgg%%2!szjo1<*IGAX7}{bHKZo6(4>GfdV{bqG!0GOe#UH%Oo7iW!_{#)EYjOx zLB+zmatm5}TNq(%@Hufn(MNgE$mB$Vb0e&9w-x42_v+w;wkj!6o1pliS#@pIf+J>t zDpch(xVy&#>PVhle-V89w|Ahio;@2}oaBQLkS$p? zk<+XP|8d2qQp0-$PddqEW%XOj1;>$^`!sPuzD;7=Kitte0F6OB5kq)|C@HFO|@ad}`=Mzh@h3S)xiEafu&Y{btct^1b+9ae7j;mtD zMz;!dEgco!;ndLsRS-uYV5}RsgT+~gWf|Ajt$eQaE%tYOBRJS+S`*C#0_F_x)&87V zT2$Hf9BNE1rz>Qw*v<;JuPN#@u1_fGLY+ivE6svzPVL)hrjr~K)og ze6~+_S|WlEkpF1>7Mt#ETi)}HIKz}AQ@G~U%jfgBZFOoP%WkJ2$lX!5j=}GI2{X$8 zS(@Cialo8j3mk^noH}X`3*?h`sq@sH({Uv4^SIrjp~JRT$=Ex^n^+>PPp90KZ9U%6 zz3jT~6};IGwk&pTdrr`{alrv!ZT2L1zQ$%*Rok`fo#2a1bke%^z-Ig7gWTuJD60Fh z5Z>;X>AL^KiJZ6%XPgYGE#n_U+eK2mhEdr)UG#rK;e?tYGO9?Puge#4tYCIQq*>cf5l7wkJ|!0X=q@{@Q_j{68|w%P+vzg4V6?~(Xv@wi6NLSb!fx5gypK*l{| z+bE&$VrYCWnf$i{GGp^balqfi=7|w*axqg9$O72U<328x#gu)kK5$|FgJNxTRO+M? z&O_6<0KhcwmV5kwzK=iPTnylII?mUyo-lj(O*fJCH$SV0X1dlmSo-fn3=1B(xs_hj z!49;2L0Z@@fWgGPF&&O*W3BfHC8uWLpbRH;tnZNtN|I3NYP~JEgc*?_ZN*MwI6_ z9y)P#k`6>H87pJy;0}hkYw+UQmc+Bhjv_=XKlgmC!y!gvBgDuod48rpI{STTh0poq z8IIu!g56aiy4yj!4*&gY+y}ca%nlVU++$Lf(+GYnR3c4@(IW;>N0r0eraxg(b&Dv$ zvFT1uf#59t1F+cpNhx|%?`Xk-4J0XpymOp~%xnOOrtsel=B?^(e|l$e8aEy)#%K#! z4+k|wKU}Wb;`QENYlJCW22!d$x@QS=l}syYcMS3N!=$tva{4@ySJmPzC9qPA!v1A*kq^w@WY-#W~B`Rb- zLse1gP5fMQQ6`wtDumh#X7WCf5~_b5v2))c>usDzYQj=c$!@hQcu=Bu@DroVp$f`UEv|md^% z1&gaF>4z6p;N}*H$GD=r5NpZq2H$3AIVhL`sw#t1kWzIYjly!gCn_*=<`J}nU#I9{ zNk}|Ae@|!sRHgYtSaacZ)3=Yjq06UJ9pRLKzM=FvAO!Ip`8O8qY;4MRV!oSvPJ&Y9 zWZgcoZ*gLShm8E1%P72^QhZ|EWaml+sKU4pn_GX76_ef$Lv7><$h_cpM`vsA$Jo9o zy?)^E#}bfryaP|Vs*~@t17v2?i=z+lPJ_C%)poFPL45B`0h@F(w1(O5uZQglS&U0) zRRliEmtGUOAak;@U))bsJ`C}vzw+)sD8E<19(-D5C&#bTE~d1Xj>cvP_B|w2`b^}B zIyYpDL-@L#Z6clg&=_9BbIKxofw@M0$D;nvJ!N0Su`ql8Px_U-RwBRs4VWBM_e-G< zfhaHgJ!whNa{kJCKb1wNgx3o%+aLvP<_%{bor{rwxDC#>#^2k|IZO+*HSf)=rZjRH zFf+g(+}o$PXTyTY8?!<-y)Oq?HBP_ve5SDkt)at^br_e-_7hsox_q?$rfTb2=anRC z{T`96AJup8_&i^+-A<%%U$+pn%jn@G&tlL(j>kC@UJqx#R^Ab@{mvJDsAK=M^%v-4 z@`2`lDz#(dqNHz5gO9s}MBH*Af|wC6^0u%#BFRUungC8+a(Pt;TF}V@4KThumsMX( zQ7kV#7ETX?mg})<+MJemX5BQi9>~TC!-9{uzn=6r8a&Fn_w(x7)+5}OeH`m@*0LHQ zy7e`54S*Xuj(5rDtgz+>F(dqBByiBi z|K$I>#-xpD^fu!E*{RDvNzNh4mn0qb2HmyAZF)ilw-l_iX4U5RXOa`P%n++_oH?AD zmJ$N!8uAvgI!`pK1Nmc6Z6|+eh_=RW66tNpqsKyZmUl_R|3XYUcO8%$Y!C@<#E}YE z=D#mMXBv|!9`x7(W!ZUJ)^y))T{rQ-D~^EcY>@EGtmZb(#SNfo2Ny=jXdnEfzeoZs zBUT_jhBE1!!-_)7AgaxD6~mq_x>Asctyc=Z(EgIL9KWNFD!Y`%&nIOrYsH`-N#Nz^G>ittotyGT7*kdN2b^^$4M z>5?Rm>;k4^bKBw$#XD1shQg(pOAwy7pA5( zLodB9PsU8=Y>YdKnNNt`dcLZkgEB}9kx3_5RBMpO>11%z7Z))Z5tg@w<~w#_)=zwI z+k5YIM{dDRQl}zl(|(#2JK&{^v21w0Uc+-E?m7a5uo>deOZ(qrxVu-t_jB0q-{$4xDw5B*ir z{a+13lklX}f?^BzuimAew-Bb)2cc(8xzv>PJDeuliFTQ7aJ?$`zr=&9d3$YG$8tDw zih1@)SW_@l-f8woVi#Vn;P5vzVaiSc{=}wR>`!1W1^W0_2jo}ri zxrB4@wZK{tdK^vV+O;}YJlN|vEK$Jn1&bBPP|Y^r4#0$HVhjGs#KOjwU2%ELN zXsIOFJcv8GLSi-CgFbG9GgFso{x4}1C%1*=V=mr_36Wk}#NAr>l@Um2v(+Mg$-(oW%@*mP26QH$#SWkKClXfL!zqCy*pDtZ8_!~<5#MBgVmzrE zlCPbU@dlwD))<!E;?a>6?9ycWqyMu^zbGriTo* z$jUH4cDVS8<}|OK@KBPX0I}Eiw&m({PJlr6)8hBdId!sIe$CD1+AFWl=B|1mVsnbk z<&iQb$@8Dqx4$9xDT`FUY333MTILRoKF{5y*aCS=dE%*&&4)iP#(qY;BXxiv)f zZ9L1~0gO@|w@jpbj!Bi!vjN@tFv!8Vh~aG!nm!DA7ed;?CJSq|`X2&N$^Wnllj_rU z5aW3DS6laRhzX4`iXN{=D#Pcqp*D?s{w^C`2Jfi(uVekkRj$t^1Md1+Mbe9z>Ze~x z!{jpwfDU-3o>k2HawN?a>_7T;SGwCK(DA-kufyG#1VY=GJ?GX{*=)1C$fOYx16+rZ zQ8!n2(BVI$@y~o?EM`%N_y* z9Ipw27jYb_gzmUfA?c-Vdn((zEPfs1wy_x9DZ=yeo~}QwZU;fzZVl=(wEEWOxSk%m z?^As()pk4x(X$rJVEVuMrIM4!snHZgt0DpCzs2@s;-1+-l#2;#a)Q>=%ZX&Z-Lkgdl)>{&-SGBH9C#u_t`Tf|3;+;_k>Qxe^U~S51gkN#ngpxgr9g6e$+BBV!Zg8 zG@!9mGn6L|w!@yM#ncYej-36U)@8>%?tpu0VMr$qAU9YfibHdnTgHK>x-hyU<9z!! z664V7O7F%RQuc6hCV?MIS=c1A4p@j zQcWIW^+}UDa!sIHU00vAlit$feVFYy^bQcZvVjBAGUcyWaO0m_RXtB486{F~Q(C@# z`YfmN**#<-%X>z@Bt)gW5Ha#5Cwh7pG|D~LzoU%RVnhFCLkECujV= zQZz6FJvMw)x>66q9m!Qype-&H>dAsV0FnI4n~~GR{eU6nLFjWO4u07aLHm8Pn}@G^7~!yyMo65>`npz( zZ(Wf%inv$O$!;L&WlL_{sg}1NpvFY$0JE`5(w8sVO?)|OrKxVklPGI|ka$J}zLmVI z!V7)k@)xX+Bv7xcaoR9mF*POvd!M(uL1aS&^%9kncJbAi>TW2MK=3axj4wb#7{f05 zmhkKaaG8W$%zb^6`gSWnJ+g41PQ}uSYUYD4Z2{{Cxtkos81l_IwgE8w;|mkzMNQ*< z$CTd6N9RfFBiLJQNT41$0USd=!lijUBJW{MjxcSjzMXRzu-^g0{)f~td(-KB#CYSb z1OD>2&sE$1EH(f%omb9$?rfa@HDLE!?6TEvB$snef^S!~#ot(6m4PW(2@@tZyi*={ zivLCxC@jis6zRmwd9^hnkw~$2=BHNr1iGHG``Y2vbPgs774kI-ziO>JN>zQ-_Q~^G z?%Gz*z4qJ>s3F=VGB)5$u)Und$}KTDGWoV48t!#D`c`y3<^&n$lY%{c15!)E-Y%Uo z2}+oaCfO`Z_A=SpEtHau9TuhzaH5sGaeq z-!WNQ$)_8Ic#G*5Wt7zj%PGn zmq=N=kxojozH|=H&8ymbeuxDiaP{@?MMX)S!dGu{N0i;sjqmaw(`(&=_do#lc{2mo zBKkQ?*S0N=2S0wB9h$*GR90w(Pdx$(Ut z7nf1Gc&SF$vWayPHo-tZ)j<&-d+SSh;fi6EmA~2xv+DLZ$w(wBOS@Z_QDazq91~}a zukg>2jh2rtkIM9^iF;1H5$xfmU(M=Y4c9Dge;+0Vz?0ng3nrPol9qEjL{&}4Kxd;* z6_Hy1s+A6ATC{W2nS^JgjB0IM<#h=?#hLiu!_Sz_m19>k;5E89ud>vTORPoz`Pw`0 zNkW{!u0{nTbbw?rJtgc??INxz~ahBXLTD6z+%kr7W;<38mu zELyCionW*PwU73Q_gTk}VH(z%{64bQK`Z_hoy}?km;aPi?EZjV3KdQM<+|EYP^q|X z*Q?1OZAB$Zr6LRR?F@5e&tq|DtDWX(&++1tg5))h3*gXkMMhZQ<0Mnw=50UD<|0C> z9vtzkME^}qO=O&wjTJ#k|S}R$Az$NfE*hHUTz0D@eq(3;au;eb0VN>Fl8yvAku3%jBwy2Dkx~4GaHn2uc>nVv>YfQzSuq<1wlFg@)Bwc!2RhYo9(V}l;QYuqr^9H>-q5PR!`x^bfi~rG1`Mxic zDyfA8b!W1XRi>4<`FRpU-P=bCM7$ccPVc|8d987*RUQtMt=mO&uYK_U(o3zFkVP?L z`t+dL-fZ}ShRx=^wncM*+NZc@WXV*^r|`kzDfsqSWM?}9qhmkT=sfoEmL;>>a*w1V znnA+<6Kd{UK4+YdW$j`%qRqYZ0@S<4c=^}n&`T!L>r=;EW#jl@arqQr&CM*6Wa z$_Cb;yF|?hxp24fqiCYoQqs4HbKh%-v2~h@cEt#c$4P8D7&*%`KZA36$RG;d&jH4c zIAq$$jY_u8eDQq<0Kr;(VUMl{ z=rbyFwL=P?XPKvkFeN08k9ZCm0Po^AlBK{VSTGPTn@KyqZxc<968a(4a({JgefJDP ziq!kwj`hN`p~|(Y$9_M4WxKBr<9O-&SK{MYNhn{yV_QO+J(2_BJ5(?f%QuAXMtu*z zQ}WE+Z@L%u zw>`wnZhH)tIe=~~kFmV9?fmH^_X;Z{+B3}V@9W|40q$59jiSFm^j25w)Q1p;E_TG{ z>vP;i>Dh1>qT%ucKEkN}bSWcmx#s;jl5mkYT6pV+DZjYyRaZZ*homu2*hp1jERwMu ze}s?`V1oi4=K6xy;{fGv&=F0s*0-Ve&Axe5AbQn5+<5}Pjt#r59eKfeBttYxhv1GBLs7=b1pM=+-MWmr$Mgfr{@t%vibi! z2jY|ZtZ2XnV|v{uM?OCGjzTRI!YB%7M9~P`mesVggPVlj_ZYu~dK!W@Sa@1#{&h2g z^KXs>+$^x~F7#pOk?HnzuXD~YYK^}P3%6s1E-zZub=={`Ons$xQ3;otEL`6jANp(> zIZblv@AN4{J*|ARt-)e8UsGa0`4{!bPNf67#3~w5IOWoC)bUFxErH-yfMn~R0uCMZT>j&-@gSzz!+SO#g#et{nb zgbxQpHbzhECwEwtZ7Z5f7EFL$h#?D=F!(=swK!}Pk^%~@nNUdEX@aQ<{SQb8ydSkI zYJ!Rl)z_Z}iL9Y7OiVnrKI&t}J3+z)X1i`sQY?FhTSHGhcNbA>un_6Fp(={~iW~97 zBGs@XC0)`ZOOoN;J5v=47}fAr!9UWG)36_T(t;@TDsqYZ125WmwOP=e|k&YR5?S?D+#%zeh zGxn)nlU9JP8A9t|Fg8S#W#QCXh#8oAsDQN;`|nI&f^8Yf$5#6o^#-KJl6FLG_=saa zzwr-VlOjV;WAd9k_mC5U!=aPtCh9esc4KD-s=(}T&ZuKD#Q*!i%|LOZTa}N4I-Y)a zEW)FNeR9zKfhRoKm`IDGC+DAIp8s+j*kom6X`PA#?UtD5lqJz2jxq|dLK1fQOtT6n zB-9D`rnpSr+9Ys_C`&He{)ZZveZkl2WL@ekPqzl9^WE6-0w5%_b4$*S4Phjk-wLkJ z1L|9yJf$xW(STxJdq7DWWeKeao1~HHWj_mL6M}Lt!n4J8gGVfCH+kjS@$t^t}A6yTtTItcoT!=}rRRp?v?>3c z(Mw-bY`tGVWhD;cgl=GB%x%8chwSCoY+V(Avfrxf z`7f74PZ=Nw?25DASP`l-lUu3UJELn?)Y~>@q+`Hb-+X{(;pBfMv-A23ssE~Ztx?<{ zddpv1?-=MtoYl@}8@EQWt)F5tAnwbZyP zpX2p{IMg0%WA6uX+b*(MpsK@dQ`-8k1P7N8cUhusOO)OLM)R1FBRO@PXlBNS$%r@Z z`2o<#@9^PGu>G&boszQI=tG;uTJ5FVIj^7nK|k(4FftFj zSm&JD;XHzX98=RiQF&>MM-#h>>|_tZzQMIHfL?Sll81Ijsyy1u@LqYqvP=xN#tgd> z`)=>D_JydM*}*d5_cAt!Op}|QpF9_l^;Uf2Ke&J7KKWlm_kX7EbCu%eykO?*mKMyv z9upKMz#+Jxh3Eng1sAPk9cii%drk@WJR7uZ$e%xa)RW1Pns=$C-NK6a$$o?21I-&$ z<+>TGT2#-t=;M@@?~Lmy7C8EbW;6tH>LYP82_kouLBbQJu(2~^IirDQM%++lbsPp* z|C|-H-Yb=%R#lcxBsM`T8#Q*G6eEuXhjB5G%Co>ae*Q}=;g#*_&|8RMW-0^a3Vwt; zE1(Mf+Sv}$D@LTR-}?TBReay_UI_2je(z%~9l{PC4^MCVL!nq_8ynVJnilxkJEE)z zC=60})9En|TEFs_zb73MOiXAoS>}h#I%AK-hwqRC{-IQM3=5RRG%)j2aci z#fEAxorJxmAB0G0AUR5|-K`vpGUdws-Htm@at87WyLubpIw)$j2itgd+Td8)0@4$$ zL`pRcBF);?jM#ZALxY^_knDEArJXBwobp1=T3H&~f#U1j9;H7HL)Kf~h&_{7fC!P# zf^nXItgMfCum0F(Ih&+{VRL>1>_MUWm&hPtGS&*4M<>0AZlp7>LIL1{09>dkF91kP zADfNUbP?=9#zdJ6>{U_gu-snjkO2&^#4Y#JT@MiPbpweVeU$a|FXYM;pnRJ8c*-o# zR?_nA!Qk+txl*3RL}!o!n=riN>T}$W_kCBa)ACBx>XavP zFL8TQ8SMK5Pz(F_?=Ygp8)QO~84ReK2^)475i&E&@KJ_ZHEsPGZ;*T4eau;75{{bpFk* zQqSBBdg^mW%?VRCI8j|mfHm1>*^5W0;Zl@z>AJ?T)4*ZvNYa|>D64pzBCzjUjsYt^ zy70wd6ej#lgP_ztgXaBxm}1LRUGr2!(?PnWq(;Z>&-7hFz5SSa--mGeK!nqa8{-Ry z$-$HrKP07_uVI+MS{sHJ8&dX4=%Ds8C@1Ksb^O0iE0*kT8+dCe6YkULN&AULd_@4h zOIy~mvQ!yi_r8z!#=mr-1AXhoZHz=oLA#kY@1~S-A(EY!3bahA%k94W&@D?~dv3jnJpCCcR%1+J1To6GLSR zBgr7ewO8EL<;CI^kWY=dN~B8gE=@L+Y%DWqpfUseoJ-RJ;7Md_&+Z`nl02!+k|kf= zO?~v1Xbp?WCr`#b2%~k}DPY?35HmVd>E-E32DlNKxR9y}ryR=yAsNRnKJ&C($3jBaxTxJtLzQxi)`$;>C8CteE-bGql8 zy@r7;pC586O~`8qgYveMlB>ly`1Q2;6o7QSUX!)`WFSkT&g=Md5VaSsF*IWn($V)M z*Jnw~u#Rrqk1iA*fG3ReO29LO-e2-^r38}r9ox?M>jQYV0zeO=S_(%Ymf+QGqpA=< z7N}KV;xFr*oSie9MwIRU>0Lo}0p6VPb|kr{-R7`Ux!hK}5`wit!@*g}I)NxM;FN1M zJUZffT=u0wfw2tj_Lq#;_BlHPt(v)WM7e`gyLln^y#Wegyp!F+1x8z-YYa)gB4VX8_ z{Q-wGtQIn9^_`e~yxp$%3FL-=zx+Deql$7vNY?dqBJ}xU=(b!XT#)9B9x=f(+w$}TDtfb-Bt)ku~M6xdJ?vp6NYOB<&-|SsnWQsMt>yIxI8oTmlFf`v#oXw7nxaVm79K<^oKMcj(n0dP?X3 z|B-AL_7AnJ2{_D$KWz#Vw#;|)$N9~U?wPc2sM-3let zf4ADfo`}{0rJ0xqPH%7>d@qhLXZxzd7q!TeH!{}#eFRuH=hu2QSWb){Cukb{9x4C^^agaIk$LkZEc@~?CuD&MGcZH)P?f)R>FQp+#=?zv>Vc{a-vEqT zLqMb-gPh_QCHh1y*67gUuhPw?X0~t^T(knLiikU?4-n3MzsNIw*6hz$;}qEknO!Wi zo7ra?d5eh%(Ua8v`isJ(PxYA*v>`QpwElFtUU4{?po@)3@SRKHqRQ+nCb~a53~rI# zC(!yBeImyYj`+#y(aWagejA!SZJy7{l*MA}g?E0ViXuwGQJm`(U!!bRjlP!M{>ZO%Ih#1iDIiBo_)80hyOhx z1JW@a`2OZ*qp;kYl-i0sEyJWc#M3p5DE-g@q6~Sf&1o% zgcxhG<+dR-jpk;ZD2?oby^N+DCma2C9bj7RQosHNN76-`fnJym7NfdrwQ>rE`V>=u z05FU6(;FA~LtU&2cU)Xw)YpYLj>n#HE1F`AFbF|jK=S9rvNuP+0hXFXi`PBG{LFME zVE1U&u*T#-v&%OE^O`(xJyRV{^#`>GEHyj@vxn1tGP5uid(iiLvrH|kDx&DEfp%bh zx5Vmx&CS_QqoR+n6+L-jy0~O)F~UQ%%(Zl73(bgENtSf^$Es!wrpOvAf{=_jX9rAl zNHw)94eb&z?^+x&$xNU8@}Fmu4^tf`;I*UaZ=RyhW0vNjJ=ns%#u^ zRm8Cyp7C9)-`q{6Ut5ZU+70bm`P0uKr$+{^5)eZ1*rFTA=RoI&n@EZOq3aNpd;^tG zntCt6%J1pz`uEO$nw-@xJX_`uPkg0colDU$f|I{qEi54i&uC132#Ig zx9I>2AND|w2IQ{a$CQSfJ^mbX^aISrHnS`b%CI)`H=_(vPEXhO_R=?AGnhF)Bk|!w zJuN5k29(X52r*YnTJE z*<1%FHiu3p_uMRO{!}&{%+8WwrXh4o_thsXRVxg@Nmj|Zz3@4xg*&U7lc)bIUv@*fXZ1k2&-0Sp>9$!u$)4FiqnuP{aO!Y_pH&8`x9F9mF#kjc za@(Ys_q>^3g3Rhhs5{VdRLUr>-_abYae9IBS$8;<|61kFdZHQ?#eS4f6{pHAvmvMH z&QL2DbF&!XQQ^6PJA^o@G}QLoYHS)Adl*wNplJ75mkqSXijkF4tF{Du1SPf4W9_~o zs=g1PZ5&im_usIJHIzzB)Xl71Y}A@=uQ)PoXZ$4~9LRPdjw~2zhAn zKJCdH*p0KkG;yWMJYlNc~G7IK* z0*$w|PE(@4c)5mle&u@1^1cVu4O9=TEJx;F=Bns))oNLiNFEOeVa)hpoFer?9^Tlo zvV@_^br=;Y6`CwHEZe6s)5GM7Lr>$TwThaXW<) zSYIw><`D7WvAEfD!O&^k3>Ot9xk)P)H&~r|XX%tR&neBSn{RsaC_i1TmmGB*+p3q9 zIIbQgXh^rA^kE+5Tv}*kv2au{@ZNXx-p%VRFf$$5LiWLEKmC!CJhiNN-F_5@<^sKq z8@NR`tiSrs9o5=qL&PC#(PNf@bg@yh#{kNC>W@QBikKu@InEJ%3&C;O^mj_b_r3LM z87ytd_Q+i)@{v#3wuY?JDniJp-3!R{4N$@rJ~MQyUFOuUrTRP{CHKPBsP@w#NFMc~ zi7(%A5b$n>=bK{O$3yMv2wo|1awT7M?cT^+p%DP2SmwYp&72po|UU$bl!Jp zG|HD%04H-n)+<+hb21;B0iAm!58_}=F2#4HG!)!D9d^85gEv2vHDyd8TZ84$Ri6?>wW9T0j^3SC`QpN2gDWHO=6O}{JQ z-j*}?NJhXtFWZB?@8X$4BV`e=I0YXYKe`_jbRKf!toKV9X1D~kK81M#e%?0m7_}sb zov&DenR&7XKo~A6X;aShZ6kQ5 z=P{2=Bp25aeMRKWDP&n~rzMd#rz3|4$x2)K+Lj-AG-%bC($c(X4 zfQF5J`KeRf4I)s=Nzmr8%F5>=T>bX4sQn$?0j=hD1xJy} z@G~Wjdq0D|Y{mh(nzZ&Zf^`Z3+k*3BVMR@nVMl?F>(nY>J5#7vF=)q2T43}oSApfl%`N`QNH|gUJ!&Hq0f=r#a{7hGKY>V$_6{riY-2c*|hOWAAxDz%& z7So34N!41&W|T!Q#fb_z{u1vz=EpP&0#On+j_BRB)BeNnlCefXNng}`tQ|H?jn%0R zFhxDD%v*PhQ;kxhYOc_P6+2sp`|R%LH26kXg{Et6&)UQP$9nObhd_s@isXyfkP>ua>phGU`n282Y!W zKRS-vRnw2G`NkL7_X4sx6jKd7YKa`J!Si%9ec@q(YxaV|sm~((Hz?^~&j1Q{l z{^Gc&_%}46=J7pZCNBO;?FZ2NoZzdPxWP+<@msdY9gZ9qB+?b$BC^nv1mHw);+Vf? ze8!1l4@|$YRpzij2lZZSfLPQ6k!ax84CqbW`;!W}!ZkpA5-{!D=gmXh5k)_F6q;?9 zO~q328SAsyeQKjO8_Y{>vB2D>cE~1s$dplnEt)jm%Ld| z_=t2jo|pz(WaT22%Ucbw3M3-M+kv7i}R z`ryE}z}ARiPO8VYOSbxGuyi$-68B7(%!`ibMGp*gnquvg}c?0JD+-j?$V%0{Y*tFOj z1l#kz_S&_lr-5bYdPL3XYLG5Dgd9TpcRMfE6CyD-jjd`7gX938d#_CYVG8fhvf0x$ z2au-CwqKAe93TK2$yRVJ-9_z4!M%G|%|ZG;Ll13#T^j5qPatn@RG04Iv?^goIxCU< zH!Zn}t`%9sBcHpCrDgV;pH?nRF2024ccbbFBX(+r2b=1cr}oCQBP?#S%2*E*m`s(z z$}?QA`?1J(Q7*HNJ=#eo;QP(DF{9P|qel2A-z*WTteQfrqkPq@<&o>;9Yy>5$3s;Qpr>yg$1Y$0y%h2{)+|hf@R&@tGm>6M9p0UOAQcjm|`rya(CRN!tss> zhY&9ohL^C&DSM@ZR*QZ!J}rcNokjN7Io|S)I>Th3@c*tng6?w8JgSr+Z84+1VtFB5 zHc_|T_oDy$2`yN$=Kpp49KAfjb{Mh%c9YQZ1jB#QQ=8+B$KilX$X~1-a_F65`+Fnw z$>z`%9>+p2vL7bK*%Dg$fk6H4#3=`BO4DNlrRFBtW^$<9L`*7$kumr>Pzn+whGY>Y z-v(i2*JLwQUq2Z&lNg@mhpjj)kr?bOC$=TPDwC~!VKn8MB#Uj$v1}6Xpgaky*Bk|B zau*xpXj^W+Nm*c%GGyjqWb`9v!HGh)5Eq$zQC_KzFhY z$XXeZXS_?=$vc;RMeY@F{rx#frvv6lkhy&56F9bR4g+EDpWQF?dw6F!k9Y(_Yn@!a zAgfs5h}me`yn0&50t7})|M+y^5bvef2e{T$)%{!6%KXXXLi=w%z$ zsd>E?e{6v0e~@)f-IZ`nxZQEbPIheDX2-V8j%{~rTOB*uv2ELS#~nL4@3}ZP-}wt` ztg&iTt*7R!_;&;v?}sD;w zkmG3~IdIy%{(xmXFf;vHyhx>vU{-G!onLdBK=jkx?LE}SOlkUij_jLZQ*+F};x{wo z%dvXNmWG~;H|}wb+UYBFzob}Nhp@*70f7fO=iRR8XO&^o*GnYg$&3N!x7_IyzCq-x zam6H9WMkhgk$e^Ix;I|N;c2FHc<*jA|NbQwdt9<{|4BsS`OL<9f|Fj?BVx&@Uom?9 z2_x|M%2ahcpl+?_C+JkNlvwIO>A^=Re+~f`LxCfANr8G9-|Td-u&bm{jEG1Uu1%II$@K&q3%@qPqU`xqvgHJ$hD|7}tKizN1D zLL!`X+Xojz^%W-HvN~EZ*&qemWSzq@CoDe)?8D{+4a#!{04L5T^GAS1J}#! zMoY2aR`nGl*qPZ?pORR#Z^6;U=1wJSjddL=G%^9eUQP+;5+H@mS|nRzzSo9x3^_}z z;FjgIo!ffN&AghqTCR|fOy=h(CNaFj02I^6@LN(_xU(FQ(;s+7gCc#qUGdB(D_3wS z-cCKX8Epw6`m4B1Sh=h~=^4!O+7(|kL^r#wLJ6(;`L$weU5j#dtw3i46c`fWF&gml ziHw*s2-bd`rFwG|uvg+QulmvF0Fd&ffDf{=LFkkQ2)3+SNQqn^| zG8>C{XoTg=din2xPPzif~g(z-x|NE zGks2&+r#m+_qEM#p|6q}CaAJx(ka#Acjwup+OCLsTzB~f@km5p@s=6gjH3#A4Gh-_ z0{xpS{eQw$LrRYyO48sQh24o@)7M~0UXRVl)Cm8cB?|*xP6%(0Pu_0&=}1=mwK}hR z42Q$L-Hgj+da$3*_D_kDZHU|=`J}5KmXyU=%H?KjR_X&@6d888DvY(ZseHbr&ziBT zFcp7u#;_;~JWf5DtrbQh3RL&daHF<+>g6j)$`5=m!)Il?f{ zDp=geUPgF%kQ5j7D3ComsHt}a0e|f_*2!fJVKc9WfhV4#!YS^8I?DYrnGfd&+X>bQ z{OTHp{qIPSxhQlsZ}yyxaBk2~8)*CK#AVa*SbANWi8Cpb1o&)K6)lM_MwZx__O*;f zt@64@I&=FnLI5I;x#XCM-pW>?lmYo>z(uq^X8;6fo1=}cw(C_ts?M-lM0k({aMYk% ztFGRZ=yF`IuT&tW6$+UB;rm~TrI$_c%ioRBR)g%jb zFXe=0R3bIhY&6e5I?Yk#+gTBGvE0T*bjN2nZSK+(0z8P^)~~$7`o&j21_7PN?F;V8 zWtYM;wF{mkEPh_)#FwW*!I)VynO14_iZg_bhHY(|%7ItA`?Jxu2YOEh2v2|CG`*TU zeKrtzz2?D*rve0A`c&m!fC*z6wT-ozMD?>~2MufuBhHzS5szcymx>ds0`&#B_Dcm;MANPH4m ztO{VjVcH~b)Rl9f;Ez2HU4-$HF-pvfxvsE4i&nHEd=4(lt@U+}tXoC!j&NpHxr18U zv$iBpNPypK?1dQPirzujCq|>Q08ob+iaK6yZ+}I{O;b|6-JRvQxbU3E zRS$}}gNBvdi%xapVNZWUTc36C(wy z$lA*dxfTbt##`~z9`x*YA{}XE#YywnT3_wRO96f+AYI-BU1scIi@-(gG^!VS&5-$kIUCosyx1!RX1}giepVL2z#9#nauG2 zGWk`J&1Lv#Nw0>D&JDTOIR^|Gq2N4>qH-{Hs%(td%6Gdo&= zeB2PnoFw9I1u;wG@DTj!VLglDYpM$-jZIQ#xm?lMm8W66D*EbSn|ne?)M1MXa$8qJ zj@Q_WX*1~YwE5~M(Obg*h4@$2Ck`27)qPfFnbN!RaRjp|*bdcgkUabRPfc?4mg;-> z{;tm?mC4hExftT(edrGOw8kRd+sNbunfVkpZn&>_UIglxrusq~AiDAz(R9X|e{2BZxS13jrBhsH+=UVDjVNa&fVC7)-+(N}rF1;E^xdcjKqz{Yr-|vo^_@m-3 za3?Jw*F25hXFqGd(M=`qd*j~#lsFR^NifBeyUX%sGi3!Z)_XjQiv>krwb)ZV;sEhb z(YCIG)oT7V4b?n-_n;K038olpg-^YaV7Vnr#%0X>j|AUnT@H_F88gY=b`6&y`8IgK zUZWbXPpQh13W0KD$42s!*|h56N2B_%&2NWk_2pk<=V*019^0Ybldm@HtaG(MVe3C0 z!qJv1)`Q|U93J~7quZOsmbI&dzj&A~o8eohXy^e}Xl(MVAe=W2%Ndj7!8|^Ux@p+t zT>02tPFVS;yPa^DxRCC*7x=J5PXJvLyRWl7cXb2Z!Ez( z{9UVxlKEhNIw?m*ImPqSq?I;pb{V7y_INF7#91i5)JCQqxRMN$zG#MMHr>V;a*qWpk)9M++*9@&T^E z`EIM>DA%>xWKz5J%^6g=H@nTT^PRqVP|z?L?>Wa9zFeUbTxie7Cy~v(Hr=wwK!tnY z__}3XGS#lkE{Gy<&_o*j0g*#r-{U~|-IsR&CYm#xfZGJ3=2riD5^n@Lb4F-7SXWshs zeTkFn_Nm)+8f591h;*UP)r$`0C2CMU$UGjdl0tJ1rnK5tc z2iqI3(yGpfluG#nR{o#Oa%YbxDvfAWjHteLNw$VQFu%y%%D6bxI~Oyij0by>ijGgz z_N426xA2WZ)B507uXigy)AQbc=d(hy#;;myV!}klLJl+$&57IZJBSC{OUt2#6?TRx zkB0wo2PNeliq|r@l7)HrT(IPXo86(%sOmhEOk@|9smp`?VST?wJeZeNb-t1>Q1;g2 z6nni!rum)l8{|AQGNsRjF0jm1+3m4YC!r@_hD?}l6ANZ=V6_gH9Nd+h0`NBO{E+Na zw~*s759J5=*>cBm1qPY2Wu@r5)oV=KE5q$!K%np#<(S577XJxezL;6qKxeKtI?JIrYMvxF zZ{YmA^pEwpR=v%0KXL6Dh(!Gw0c)AU6HeC7)~MC58hr|ooZqk>E)t1#swH+IV>(-O zbP|RLW&XD%n|yK})9Jw^nx|=47OY>S;|H5LjN@{@XC%SX;Bl>NS+Ez)RgTlfBpp+k z%|R_p|F!keM7vP5ftJ5yEDGLo8SzVK2R2<9x*`L(L7thfYJgxU_fPtqUALmKf&>BH zYJ!cWG&3ZVO$%0=4Bn{Y(@gA|=wdnk>lJDE+4M!HCvLZ$idkbF*Tm)D(wW2F!&0$w zFIsh0eU#f`i%YJV?NuNZ6_`gugg^zIs;mri10DHpvAhzBYpK~$(`ijQg*&CzYqYW!Euxui|%PO0M`~9I) zRm!ApH^GgkGT(qZE34hyx90oLaVEKn0-pEWt(h5@JCD=VEupeRNCWJ(jV0z~SKMy_ z5A)y&3kH+BBc2HdNJ@490Vf;=wUiSu1M!`0c#!9kZ1N^;+WUOBry$J7zYjIy{wGfM z!0-aZ25_@Ox>M*?nFHbkiNSi|UmRoepd6Op?SKFO_4KFxM8qu#1Uz|5^={#Bg8#I= zOa#2Gv?C>uWB>c{6|VVyll;;qjS|rq=dFHi@i#NjH_yhXR zL9j?#3qwd4G-th!YZFT6h`-nMExVicy&F)jiRGXE!dK_r^6=}NEkEtM23xgZ7)=)E zZ%AlJ(Ct82_rK8I|5V`VO?LO-6z=HJToU+40XvN9%6Fv^xb+aIk&=|1lY0^Mg+QSKH^ z*&&=(FSSaRR8Yzl?sg7I4rU36LI@pMp_>T79KMmp;3GaITTjqlOsG3M4GjaJcId0XJn}lJLt{FOUmW@}!%EpIuW?rY?gw`nS@orJ zXJ&}10lQ0PFdTu&l{<8WtHxnooyID$)8KppW&d$<5)&>uBAfZ=%QVZU>!)PrI7n}| z63Zh|LLd}iaY41LQ89Z_rl!80`IuE)YIlqS&;IW~mbuBjN>w(4{YZq>Jf4{FTTSb> zr7a8Z)Y{sZ!lg#VpOun|Iudi1sfm1AVJ%Rcm+!GUx5^9ROT?ch1YK6MymPdM1dWS> zL-Us=%*9sBA4aK!cHc?VD>w_atM#I+4vW-ftV3r#@E`rhzDe!ZxaK{oy3y;gk&*SV zMngsRPRc!RFz-!nczL+V5WFw0R8CzasZ%l3x$$H?r<{tYUtPS&iZXlT8PjPWJg-+w z-`ONXeYh=bNJFPCJ~M4!?CbC%SAs{wfhV0n_Y zu>3>=o{-F8ShRbkL)qDoj#iV7VVdDQw6E=nXw_^Me}5{ ze5R3+4aeOzT#hBr5Px2rHXL%jxba*3l$mLa@WqF|{S97-Iad5`yO^;q-yX^ zafzKXW4rZMd7G%RteZO(E@!S+Q;LtX)Xl`bQ{KoDjjn5Fg?Y zV*epjHzBUSiSZqO((LAaJRZ?|J>C-y;Enc4TIPoHetMvKoQnhScAR7LHs3;!P)Bov znmt&Ya&4PetZIeCIQLU;y5(B~UaN48Zdgyk+A}v;tqK2QRkErZx<^(%5iA>QscVwD zkjKdwK|^0tDe$|4bx*m>g^N?d;ZitI+g3Y@hh4Dc7?vy4W8qbd^#eh99xdiuXpQ~;! zFd>u1RR3?y_oUm~oVnI0QukEJlp_KYmQ+RgAw>;E^KKkW=e*sj`*aj+#T>7n`mv-1 z)ayM0v{OY9$TmuN&n`c3`30{R?)5wISO39qwN^Bcm(XEDt81u~qY^gxuh0^?Mv60| z#{>cV*9q=MCMZ0fcDo_Z`587q)^RftSmpJChChqphyZOXduT+vK3eKpTF}ye=A6u8 zP61)71{gvo*+D9@qMS2JEI-RbqhsPr)bb?#Y!Ldt4oERMQ>%_xP3qd`(fphQTwT>X zY;6W3LT@R8``JRYrvqhbbKTLTsLFlv1^^TPVCLJWS}q3y?Meu@0=1^56(Ch`!3VKV z!1}gi=hi*TwXbfD*u9|tg|Yp+fT?cNo|O}76w3;oBVtiZk`W|q2}_I2 z&@gF>>m)0}iG3V4jM_Sqyc)y%pJwaBCR&|c;|0awBMI7Ay7cRda{qobHWsw2>%(WW zd;a}%;tAJ;{U_a?QPbZ~ATx`H2_2{UJY+9QYv#P&#np3`k->-{Z-jwO&9GF-lmxzU zSs%)?Az@UzxO<#Ms|=J@ZCCbxqlFk&K@%6wLT%(^}ZYA9#g=xTwXv@ z{La!~ESa?+-^-9%IPXttl!9ZdWMPghZ3qs#U(5eAbhX!B`;KN5)}*8$T$f`*txUsiWo zUP)`Yq0wc#FH(v{XX7mD#l%RnJTLIhA#IfetRLPdt!{MR6S}iV@1y<&_xuL9UPWjt zRHx8+9gnwKy)qu>7*qZkF0_RH?Hs<+_3F!!AB@)t0X}V)Zr2t*b+~`l z+fIO&q*+tLrru`XeRxf9D3b77I=|b$JI`}ZnN`yirQg;{wp}~BZIRvk$SL-!%aCkz z>|9o0E1@_3Mx;st=hmL9`Us}y0itBoM|y&@X5Fc`)F^h~gb)ngW_))6u#)e8iqwVu zG&Trf3dP~%@UkZP5Ip;ltMh)F(Wk$TmiKD>@$4G^w}Z)7t`mZW;Nz?j*Xsp`11s%3 zN!I}q!6e3bC$pY|4}G^!da%MyUiJ>ugr>o0va(%3!@(5BlCNHEGzEhmyC3e;1$Lnd zaa; z$|R(58p(Y-3oFA472C|*n8?sx*Liv8Q~Oz$&=*;8{nxIVC|yIZP*JZ-2Cw%^(fhSW zfnzqmsH#?*X*KbRoabh7?SxStw1e>sh8w0|xtBMh8OXP>l8!JA$&z0{-C^uO3uCvHHDs9aJS);CtGWV~*Ufup|)P=6#@Tv&+8n~K*qedx>GIrS4^>2Y6y&Gsa zrc+3AVkx)-Mv?^GQBgxEOl8~EAvXRNNU06+j5PWfkhfmtgr*BUr;g^yw*HoHQv6O!r$oZxW9YIC=54R@C{vm2+eyp#02%SN1Q4pK zwHW-9rRHVr-A&Q3Gpv=Me|xb6UeGzYOJ2UsoEU+60O6#Z+#ZurOIN9w@#SUoaj#6M zBJSEWi$2c!Grvdm+EGx`(kcd|vyVnF5M!vXCznHa+co5*v8r+VF@_`@g|OljSi?nx zjfk5r%;v#ats6v5?;3(o%kTI>y-f}V2T83M{9gR?s-Z+%yQV1y&tlTlJo(a>g5vJb z-s|5BONnnAQNLb3F5WN87gM{qCBQ$H7+OIrH6qw}(iF$`y;6_cf0QH{?PHE*WnocOCQ^AE{7?C3y5It{%1} z=m>~gxPSbF9(&w)!A>pjDi)>yHT`7XVAEYAPQfUM)!i?HrW@X)wYSgO=gOazc2z5!)5HUB(UQTV5o+2Ybv{=;Rwb1kxHdLr&fONkWoZ>2UpDNxpH|qUw=3#gRDTuo1nq4nckp+lX5IwXUuylg+>3T*$d$t!@uu?cV%lMdZHTAB0jy7lLz5 zU{yf~wu=$O;gmsp^1-o!RpW?DIjStLT~jR<^?A+&19d)~#;rm$x${{zSI2k*4%?gDE%v={_=`i#>;R?7lDYBi+X!LiuNNeaN z#A22PSgCUJjFw}s(EQjU);P;i^wm=&`~YRqO{n=G2A2HE@)_iRvi3j5j0$_yFX3jve4sY*F=LN0bC27^# zwNcT#)dq@UuYgFDT7D-83|oivyy6p^knp)sBHvd3mrSO=U3j&r-@jcF32aGO0#DK` zM50d!@xqLAly?ElCv1PRi+kd-tD?EJu&-BfOq|a+nfw6=1>L5wlfi9O%$kTlyYOjFzIuCDA6I{f*}N4KnfyRG@C;v z2h!&FmJ{3Pl`}O)i5z)lCfy9BKLprDidkUJ1&8A#euM37;%}7eYJlNpNc(`Rz2`b|fhf$3ZGiPBKCw9Q_pocf;E ztj$}#p?R-g0x~AlI{x##$GMH7qeNaBHD4+O{H|K?SvC(Jbh>Vg#^4H#7jF>1P9z-Y zHppuSM>{Fme+1!@;ZNy~=s@_$YLQAX^$AJOE-)jdy$UFa{caKcjm7+nVWKXD3rk7g zs&|CNy%@L%DFKzgtqV&yyyZb?GEWU%YMjz%#m_}aO_CxL9*NVy?adTbh*$CL(YUc) z#z`f3mcrzJ>k-TY+|1~m>Ebuv+}adkhW+@MtMs;jlJO7E8b=HGDp!OU7esV#t|I;g zyB^^{b>Fod(`p&ZvGB=aNZ&Mi(9Y8KFE_TQ2J-|lf!v;eB;Z%NVN0CyW)EO(T^>mE~afVi9a2d-yW+Ts@RF(zokRIFC_*IgN2-+wE>=2V(~F3_>>y-jAZG-+>7sGZU4P8=q~ zjyI-!)3eo@qq(JsWBpvR>KT4S4&BA@NNoFcCeKxA?iTNRpUyk(HDG4Ov1SCBk}ZbK zm@-NpMVU9c_(#|+ zwN%#TB^D2k`4@{+So1VnLi|i{{(BU#*f-bdf?*=Q?-y8u>QEu@rv+_Tkb+MpeIJIx zr4>uF4%62Q6*at=c05Qt6SCaO`pNZe$cbbeu+`3Yl8ZH9@g0b0FuG_;GTI9xK8c?I15<-$u7{;@YT2)AkLgxzo*r{I z=Ka;+S)zb@ELJ*q)SHTprxp7x9!6vBy83|9%o!Dj^BVLXv4cvQ*YnEdCi@!Fwz}-( zDlqNNZxt_!J$fNIX2xsgNSbopoKai&nSMQHN|@F>>id&Rb6F|=P-F8Bq6=>d7i412 zJQXr~8@3MjWNzBpmG(&vI0(P~P|LGEbfLw{?48;qO9PF7Qr*PoL$BLA|L(`a*7Fy~ z))P*yvtP*TbLsT+9kNVr!cFqZ+4+=-P17}8G>lkbsT^NU^` z`SBtD4S%*@*!oFWlWSP^6R&frouNd;nN*JZg5y4nuCY@9?>yKk#pe`I2e#D3Z2&cy z)(|!Y_OxOY2EK-6gfdA)#WnQg)EzN@GIx?Oa=M?HqPX&59QRwC;jmwwY*m2`7NoG3 z%Na(c^N7>@pMB>-4eVkk(Ui8#S6dLe-E*skJ2J1_3(QlB@A_)pva@DgBP>A))8df1 zDnGv1T*MhYMQNcr0VUU0quC0fw3%br`zcZ(wsntTWk@x`ZRep&HLYb)?z(-lnI}cK zozu^4`TQ6~=r!x_@Unn|FzSh1Af%TDmp@u?)Y*W=0Gq%_3c1`(7nNCp1A|CS=6kgk zgx>Eg-15V{QCILo73r_&K_6;yuVQ@&mx3zCcCK8`Nl|W?$xoO6fTRz5t9Dde%pB*% zGUwE1zJOq!`%%i0&ucRQj#Zo*&T-$N*I1kw83VmRc zwj%h)(>9NRP<`|i12}DaUBG&4P`5lR4ZZ+M(nS^Y6=rB@{G~yAfP_`3cNIE`%dorz zic$-G%boIzRD-|bQ_V;VY?V8Tz4oXM=3=mM6J2>b626_=CWFZoZ$F8|m9Wzk0D=UXM zDA}L=Myx*+?`xNv&lrH>B}(_e6IjwZZG}}}MMY9N1WK%HM##obE8T-Yo$DBfL}RP|;`w=qRFl7&O*d=)PX?v)9C<$n&-cKTS^O#4UuKD*iD;(K(*r%&2l{j{gbMAJ?E zG^fC9KA{eNRkJ!k54ax0U4(W^1DW$uFl7BwZ-I$PGjI%tgUAF3dD|(?f^kUU~8oTVu<3I*fooJZ6Ec3%m_o=I zIZL>fTaqw!K?prM8+F&pK%He$`UFjJTdd#j z^?wt^nGNV&%%~VUtX_u?;AWBJ1wpV2RZ@ENFkxreC4^p9Z2_;v$K7s8)xISU;z}y? zGSPpAj)w%^lzs>E+@&wr7e0bC25hVBlT48CSpqfk1MLl4Dr8u^$Wcnqe&(CN{OYPL z_$r>K?&L>i;YwH$LFn-3mA9?snfVD-;O`xgJ@PH%b6k>^BpXnq1!HXXgf@snPO0_8 zB#-Ki#L9=^8v()&%dRmU%&w+%S&G9yZ`9(4#cxhEeQ-nx*>dno4$`cQ^|WDI4A$UT zcBqo}F_D2v#&vugZy8}9z-juNQfd3QwKCvO!z1)K;HFl0z4k~3{kvLfECPxVq3czX zo}D(^2kDXW_^U=aiLu%&5TnxZ6_8B%?n?Bphn?Lex5Rb7p5@KBii$C z$NkFSC4vt-%5v!bVdO|P zBl+}DqFhjab}cB5KBhO=lUkF(?NlrXyktNpVJIikk&Knmci!90>?Wy0h%5K6yZ=+@ zX|V@|DAHf`t5i<|ZJD!+OyiR*tgbfT#BchjuZJcds(b1;*dIlelh$@1?9%mck996- z`4+r^8ulA&i6dKL8At=EMeJtglHG>V#T70SUtT0D>>KMqyr(}}KbTPG7e~>!(isM~ zdx;>@;MB{5a|X^AEYBRGFt%TZOCIL=%Hoa!D!Tl=`z|Pm+)#=vw;^3dQv9y7JFom! z3-=m&5PwIG8vJUk&Y!~1@?vpkG3*NNYWwW)=PCieAfDY~8AMGF})5)j@n(atQ zXeC=}X;x0Mkh7dF^_xX!uBlQShxwbt(I7OfD(0u^yiIruA846_ro5l<8zU!pt7@M& zH&zs~ajrauJQ)yE-H$*E>*iV{W-n=pY6hF+ccCjO{1f8P#lkDJx*scEWM`Nc%Hlu7 zv&?H_*a(|<)X|XJ-Q`Q<6-d$h^+}5(VO|;Xm+-Y!Yz@~{ef{!krpU44O)t5BVQYGj zG^(vv+K+7JB{Tm@AKX^9=tfCpv2h9}9ZU8VgGTvEE0WC9Nm60W_p12}qlK>-N`=U> zPHT=QSD)Eefok-{zTY>hh1-c?CuIv0#D_t8q6w1Qsa4@Q_3jh8&Nn`b-bWPG77dz5 zNp^u#_OE4&LKE1`=u;_1L=CpZ5|?i`g}1~&5=Rot%;1YfhHa20P839RWLWmZW7*Hq z(Hp+Jn1y#0pHU43g#BAPC@7~$U{~CZWFFK_%4t0Nw-0w4t(IP4G%wMvgc5G_n-KQ& z#}VV7pI5?(_g;|*iAf^acl6ij)Pw3;1cT0+=FOE-#A z)MKGYix;QM;7X38?PTbw;9&KU?~#c^?Bd@>jR!;%Oq-Fe{i;%zjgYo)8)WclO_T~! zs~Wt)RtfLZ&_}2ee@xQh&Lh2w@aUaIf}7p(TfPd=IT0Z_6n(m=wOMM*A}OK@uL;w> z?6oc3(!aChq4GiN$y5xBJ0cSl7yz2NJ2_tX%&I)YhkUBvcXYpMH^!Gr!bgIoc&-$i zXJV@kXoPO5+6~V9j`>E?jGXd@rDxf3)Xp}kS~lv@JMA739ZXd&^P&Y-IF2nXStLjZ zzDAy>RCO&0G!3u95YoV}gT1>NQMeG3VP5%Gn!S_POLTKE>%C(>U0tF8D9E<><7At* zFFOJ&ueE$rs|LXbnsf20QV`ng=?}Lecs_g5Rc*GxCB%s;iLD%gvLJjW?=FX>uz-h* z(DWtUmp2>zoegLHh<*emBWIZ3rp^17gpKn<#WfwbyeTzshb%haX?-~SPs_20SklYF zQy8vwIZOEipfaIKrqHYO7rXC85a&%ZC9ZfeLkk9>L`(k$Xs7o*_=eNxyVG`a#q#Qx zV*G1mT7s@BLI{r}{s71%@aGTF9qZ*wR}%z@4ynC6@5z8Cy0nY@<_W`q@K}w47g6s+ z7#(&T8dKjp*ZJW^HPSI5z>jP*B$PG6(SJ4?5102bBF7CoZ9K-YqbxGRN@P!x$_hci zK@-8`{>~sQ)Cie5SwJ$}`Zbx;`^?YqOPmE?n(kx^L@Gd`QiO-_y)>T798;Zb>t@=| z7ExLT*c8^S|5DkLXI=;E2|{9d9A2O5?1tbEjAt@^&kV_$wYkfv}>YoH)bec`BTfpE8Jp_i=CxbD7`n@kb%?AYUU&^801)lD!(G|U)xx+=J7o}lTy4h(k@*LCUMFvj&7p?0=kShqlvd+Q%i%#p0(Eay^10H|Z z>@_|F=VLTcbEb0wm|PMn|Mc-!1VPsg%lqB;GOl~99 z_Y?fe_j*7>3j_9AP4-vowAi1;@`Pv2?Ey1B=3j>24&SW=-ft0pZ6zZ*JNJx0&~1t7 zT!P_NDV6OzcCVS?`+Goh{MRs*-LTh%o+O+}oB%46xf_7G(}XFn{$>ljxN*%$}o3=7V{Rih_wIR zd-#2aqli9i-0XOcip?3D*=kfgeivV|~S?=4l? z^V%2apYzOb5ztzN*b^H4B*W|~TKl`M)YE`jdjf-|B1%3WM(g)@g|&dMe_njhw^0pq zNcVg+=9msG`NVI&w8n_Qy&X6IDm!a1S&{2&oI5d?2~VfKI9XH%;4H8v>n2LvFq50z zqpfGIv-Wz`vZ9N`$Ed?P`Sb7V?4|??*na@vM!$Ed_U-Q_C!a+uqO<;yY6mnGqLBsb zJEB$1J^{SpHXX%@{8VK;7(`UU$hE)YEPT#MZ640Fr(SiX%tM};KBV+wVD#y1%!?Wv zD_WKaKi9Y|fTy4Yktn9RaoOiE-;}yxBzuDY#{$UCUT{UTw2COBwM7WN&ccMfz^`i# zs&syzUsAAfc+ge#sJRkg4$O=O znqt;VF%8x>H!o>YX5wB1H`lZ+{RSJ|`FTv2fhy;f!jc@JHU6;1Ldr)j!#kvSFc*=h z_I1RZ_=ERL)K)C8bYk48x%#Z?d=>nD!DHf!rfK2Es?=$PU@w@vVimtv&5>We5Br(c zyfmv@Q5UwCG=&El?1D%ya#uveKQJcgqx!MGKH;BWdLQ3cggE{h<;4A{^z7?Wq%a~3 zO89$5)$4H6e$~y%yon5n6+O{qdPl!vz^~S!0}#1?74WfpZ`{007yOXr*5nue4VW9&bMY-hD+_PLb5OR+V7Mh61kn@>?%zyZh7JJ@#=%d`4sF zC+B2SSp(L-UUvm89lqN=v@)GzND(I4_W=95+VWteYFB1h4m=AH>b&s~RpoctU^pJVW& zSH>|?=&JNSV$jGoz~f2;^S@fNES)#~R97DUkFB= z-x&h5{CJR$_YBy6(wN+O>w9cGJqDzis7_o;Y1&Xku_@v-i}fG5-k#sJ)9N6>+PLXF zCaxII?**kuQDL)XWBf*OhZ;DG7ciEc;%|ifIfeFxJ1gGdPxk2`+!zjU3e$ehCAJga zry1#p!s8jcBv5JTgU2wOkk*D#CsMH(IrYjjQQPtG6Sk5GcPSP=@VdbK#QdCv{A&v6 zeAwc9d+a(Gq$fwm$cxiso~0OOKpRLU(>swFo3!a|`|YhuR=S+D0?-J+?T`c7kAj>z zdEAzeR;p1L<~;Sl4VY&Pi3VX5kKV}f1GXORZ%YUU;^Z_|Mwz#Pm@;+*0~;9Wv1-!G zT(+Uh%4cT56rd2}?Uo@WF$RFH^!CGFE`1@EIDf~pi&^^sB1V{xp!sCph^ zOcR5){;AAB-f$8Rd^AdQAN46UO(8waX*Hhqhd^q#zyHIQI6i7s%^g67^ZtNc*ia6ERLE*b6*W{(@&XoLkqt=2RsYMrC&P@4afPRVr-Zga^6q+)uch(^?17UN z&)Cw{Ywf|m&s($+*7|s#6VBSstFvPS$ewrW7OP;seeTJf%m8@0d^6Sw*;KeYOehXM zjhZTpz&<!RkZ zIW6}M*t4ZuTC4}WRX*!G3aMC|&pv)b#GdRev%7Z;+LF~NyS}^2c594e$h}=%X>BN^ zz6dCmTFiLMS+%N2FbJ_!q9Pf^*uqx3S^A{M&mM&W*)5Y;mUw5X$Go1 zI*M|k*^V_wo;AXwO645u0VW(#dL-Z}*}+_tztnGcT<^0_{8PfdJUeKAbt!XMzSa(s z;-?ARn!5)UP%Uw#NR>@#6;slB3PkF zF-ik}`pR#9(FyT_CKnZz;xY3-6>potL-W5L{0BF_3UOg>iJd!4o<6Bfh0&KosLwFT z{?qoqLm0%r=PWPtYn=C;Q$Pt*fE-JqrM#$qtkjM`!|j!hleRDI`8TdvxW~d`KezMD z59-eXi~ho{e{}6A?4I9tS}mPLP3K4Pnn{F?M?Ud@yBm41ls9&B&GA@Y{1 zKj)Xz6j3gnB4+b5Km2n9Zd<>?f~g~~Ik5aKWX#=lUd+O4*{ z(0+Fg%AEm0=ScdJbD0eO>Vdz&$O?&-loZ8z(zUvVyBDgE@~xWGH>%Hv@>Xc)3w5)@ z#XJX+P~D(3q*0ueSja0yT0%a>^_%LCjUtSnK2jFB&`*6A?c`a9@IoXpK8*H~Ymu>zXl8r9x7%iVNPSgXZJwq|dvf$GR{d;Y|%eS2rv zF1@tE{^vU@EOf$WlNb_1-4_r~-)$2;CoIz)0SN#k6dYa!Ub1uwu4Lv=Pk00drmO}-X_WbBd-6 zk+t%r#Mxpv9yug~dD5cz3j)x7fiarfmDpA@ZwZ%ua%)lOw|o zx{_l1V?3re)*)KNc>TjuENPEe`7$IG4E=G8B4f`dthYC56T@C$?#yKvjT;H!noW^f3K?&-CucjKJ8n6Y zVm~rJ`67^skjmtxQ|Mc%oO3Ym?z>PHrT zq0GDV3mfNsLmv@o$0=QZ-Y)l5=d<5Aa&@ot&8*^5U#{L_J;ZAgB~|?E#h+*1yXx0b zx|WLeyL&F)I2-2r(Ct6-e%;2oU+Oo3WMD zdAHq1jO<0hI=m(LJ7>jUEf2O?w7J>3e)rdG&&c1{5g^&?72FMMCX2AZp_ar`S9|l# zw&moA9i>B;?vLj#uiArb=)+;Y`*cHVpYVc9)y%Y8~oj)8za^ucJXZfzh>^W?{W z!H(Q@w@qS*tV;xmZK7^Ez7n=xhi2e@^m(R2iXR87dlUU%0T&s5atABmV7+VdrgE+M zu;V35mbf~kAfH(m2fwHUQCkVc7Wp^|8IT|yq3)eiY&9;WK#Gv+#E~Q1cgA`S9k6C# z?2aJqMK%8HHt0E&A?$l*r>qhqPz2;c9OZVjXV`Z4OxZQ>xXePZo$n&XdUqVMlt8&9 zF`{IGK*C{GU(TZZo-Nh3zA0m^6|>|FP7;$1B0=vD@23}!uAi}xBvhlXZ_={M<;iCU z3T=+Q(qU3;r7rt0Vq`(V$jIy?mqDH}n&a4cGuqFtV@g+g)q9|^ceSm;Q|lv-*!Lbk zW*5U2)fsEGrnlkc>d4t(Y}9JnYHUl>`8M2p3?&1_q8b7-#;Hdv`(Yq^_2t&jv?^Q4 z!Q&9A5L$u71czIjrIGp$?A~t&j>DKvK!2tVBNE0>uVBb$%+Ig-&6X~3R`h^Y=m2YR zexk>IxGQJp5rcT=`89U=+E!r4J;2=?$R4p4;|T+5u)%VmoidXNt0b?Am$04?b6_$$ zV^vLrVW8OhSZv$t>TMX5Q7^IKEf+Le!%_s9>Uujd^t9DRLl}caiYYmc7|7COc20&h$P`RwT6q*W{{x4I>$$&m`{f9@cl6f#So4|s*fV$O5Y z9!`eE6^PfOS4!9XS@ZzhO^8}f2t^TLAc}-3kLQND&=q;J7Atw@iCW+YEG}N>kzHUv z_`AIDcZ-z8>h~~vdbDUSQ1Z&HQoNCuIA*cBb+|_`(#491n&>AnAUo}LuCJ6nMX@D$ z#WOBaUao$@xfd;5M!raC6uM2`=R!ew(c?Q4+B~34PedP=h-{{`zP&S|H;A= zkn%_u@*CxodRjYe^9O&)PEPmOB>h<}g@v7N!KXWC8(XdABk#4;&fS(q@vcNZ<<)a- z5HoQGi(}=r7u%Y;uq8zjMg7WStj7={rhLVU6;25i!r@G1R6gH8d837DTX?=9R46r! z_nPX}LbY*zd9lZL{@xdVS55K)Y2>L+y}_*?P(9XO4YP&R8YrPXB zW2b}b$nB-;7uv#?pK;MU;mZofpR;z@ksB*Rz_}roV;&pAO!D-@TW!zulsyA{|AK2S zv6=zDJ@(voiviW!*i>sDT32QpTSlybP=)LSV=0Dp7}$PU1e%oqiU(HcGMLPIk4@P4 zIBm_57GY$c(2N> zl`1`(djM;A8*#JsgEe;GNZRf_HVhZVkVP(RwX&rvh`lAagp3XAufE8p`;OY^z;VVP z!zLHA*aS`wh%|!{)}vaCfPH5Bjzc)guo`C$j1#XL>1?pk!-IC{C^>G22+L_C#7s^H zR_M$rq2z4tpiZr8gJ9UD-9hS?ZlR?Q>T#C)%?*vO0>It;nq z;4I!<5K*;AA|gy>oi7BAy()KBs`M=H0l$|#t4>;v|mnbjiVVOIuaPXPG zrBE!QSZbdr;TJ0}TfbZ|3lxvMlJh;fXB@ZinR5rKZx$<3rjQq$Q12?^#nSOkh7cV1 zWv=jY^)Gg9U50xvfItN-<~5e$=!yhszB~8o+@>==q=2Y-I^RKwr9_{47LtVcLFp^l zzJQE7<&;V|$JsK_D&5$btM|-16^#6prcJ#{LnDV_0C*~T-YYL?Q_fpU3-ii1oJgS@ z`duzZ9BkII;X=E*p&KS^;%5oO4Jw~6*N~{>>yI|#Jx2`UBQYfThiDK zz=j)^*N%4OusD`!OycDmf=zjO*VUx7(%*CoA;KgPVm~4QifjaHFBT0D6oB0uYy6 zcu(}YtKeV)hE`@@zWW=tdF2{A|4rTY*!|zOJ2tNbDx|wm8@P|3anVAjbXloPE&axm z#2{7GVe??!0iBbcIariZQC_$)1#z|BE5@;RASBwpq}TYEB3yip&Aqa`Op7q zPyFa9>sZ=tYr0z4r2Z+DH)0P=)=t`{jz(KuE-!4zMiK#g+_%>@+6~q`&fGOPMx0{A zCI==!M8I;)d+(2SSxs%&rY6RL=`~w<6g0@nVQVJ7zh(xKGPomB!AhGr*vGugpk(st zT-IjQ6EHBt5-+o#(=C9)mtc8s#8LHf_XLuNF)}kn*v-t8?Kw$)bqvnGx2e-wm#(%6 zJhGMK(gs9e+3G4Q?{2fojqMg2J!w;LK~$iij>eB!O|;r3^CQ;SR!>TwG>USgwbwS; z;1pz&`^iN*Q)w5k-)#GOwpu(k=J*0+s1@&tn3hZB-0K1Hza;qaj>jxGl(q+kXRM{A z%5HhnYMYI=T5LRS5!l^x80v$qRqR`Z{6i?uNFTCu1?%*vg@H0B;j;+Uu`WW`SXUmk zB~7h-t+w8Sg#Q7-Tf1bF_3qsU#{l`~E3~e1O`rk}ihS;Md+Cnp0e_CXdXljuXF4UV zD0vlRRp-oItf$;BPfJ`=oMRnDFo*})8wO_U3^P${1=g3)>WZh;`I*ti)O^wev%&A2xJd{Gl+r-N zOv+WP9I}Gwe;mJ*WPG9{K8kQJr=c1ovT^0d^Uy!R?U2^itGZj z*@#Wi|HCk_x5LCvSQ$z|z-m|wy;+_whM1wDA^Xh#`dbT6`)v907W?prZn294C?1D~ zF<6w_$mF=CSVZA{Hf1cUDH9ud(2gA>&B|!XIrxKXvGnIz-2*2{mA7VngS~Z4*d96%uoGko_{Q;m zi;l!?o$_MA0T-#PvMJbW;~1=xcyE&!!O9|)tQEx9SFRv;HW1PJ6V?wYpNwP)(t>$3 zW+4+S_r)-%5trK>v2`0eEq?$siE^fsr_9S3baT&H-9x3u{j_^PQ+)=gP+fC{ZSDo$ zIZMnop)5Ol@xnr`Sw%L4%+v&MPg1pw#Vw5?xWZd*vk~%wHdKLVN)QG^3O6!Iv~<;3 zQ@GVy8Bb*XW`C#c=^-W;mj4`~I;+}NT4G5*M#Bko3%97TCb0Is?1ZmYa?sqz3v=N7 zZ9navDb@FC_kc6wMuN7!4Fi0C&bH=j>_psW&-cw(`v!76%n%Y3!_dN5nqqzQRfBF) zc8BIB5Gs(7O;v%m3tL0Std%uXkkO)w`H(=UnpW!_%MhY93WrbHmehAyxD!wB$Ry$o zm^@?;^0ySm)T@1yrTWjx9`F-hc3P3L=E@n{Pdn(x{zFQQfSHSw;`J6eWZ_*R#8{;M zS5&uqKNi09+@4eYrFr}(ET_(0=)51N&IRvrPLN)x?&9{-p0g?|RH)xMX(`20{OWcT zH!l3C!yHr`X+DpzC7C^g!jKi77RA^H1YI6q5mss8RV>aaVO@tvOPcdQD>c)Z#mhuk z3)xtK5}Eq()@of0hy+Xb02EgGcNuAba9*;7Ye{+IAVI}dqi(NKL9=3?;u`a>5~Q8U zD9SzxTnG}E6tUvc-UKTZ*>;5ztW^UG&X7`Q&Q)1FU;1nv8hlo{nMt!yfiRjUepgWB zGN7q)jN@dn_B8loZXr*7YFAiFx0sz>p*=uu{P_^V2OHEMY9Rzt(vu5bc3Q+HpOhv+ zt@%_%5ui9?iOzf1Sx09V%z-FZ5M`j zqy^Z1sLY-?jA8)6L=C2g)*5`zJcpU7wC%$aMz$Xe6j~l3j_Mt<6JPwMOxJ!HOKlYFrnO*fCNA&5U`;r5v>Z1UqkYwUt+i?Hs}%o=FJ9ieIE}xi0L26b3GkuPDZ;g}y^N<&h zMwawyo`$3~WUy3NNvWJ0JutUaI8TKZmSoIoTAFS3W(;@Z zy|#ZQW!r`;t$QE%mDS91amb>)7;0gc^}?*3oAi1)OLhU|3FM^=YnoguWf+M=pxsiwcFPiak*gcBxN=K@a!E|j+C=XW?j)QnbVi*(cCI!K|g|AE6s|O3g@_FYf+JwQgJ3{ zsK~EGj+J9omB%edW%I9rK0}=xgF@_;Hg`sGG0LjQhdkE-x3WEqS;T*40%B^e#wR zG*UT1tdhfP8wyqcfoJT(di~UM<2F9XNCO_8A>~hV3*LTUdok=3319+VY5QD1NDmc8 zc%c`Hsg=%zlP}aqbHf1-3j?tD$3h2}wx3l!5Y0AN`uJXA`sKxdbvBA&3&?r``ugnn zl>O(|p0>B%c!@Qw?Y0#0|H_9PWGqdx-wS(vJa@vTvndONLoo0Iq4$O?jmJXcv;}DB zY_`>&dvuo_+;#-!P#8jiGp7l?(A3}zO7rHJv#MiCjd}HYfM?E>52^&BJfKTth9W$s ziS;02_kaIkTQjo6F1_wTtAr4-Z@S;Yku0!e3?+=K%oOt(=p!E%%aMU27JvvbHa-k8 zMe`!@v}}i>%qB@gl^3$8t<$#O{WTz=j6Dp6IgBpKDVhmt1zxyl)sOubxczPZhMh9~UHU-^bzbK?cp+Sd#icbS!2D#=hhvl8rhOueOO@pjs+$np99}7wb19h1<3Q{U$)s0K-(8EvK*dX#t zBZG+jd2z-#exK4Jc{UG`SF3H3wo0Yc13%jyK(bGa;9(%XCzLNo!NOZMk7!FFsB?N4 zUlvxfoR^XjypU3$IA6G+9NV#Okgu$o{78GF%vWp+Tju>18I(Ms^7w{n(0pw~hsjk+ zDa19?#)4kywz=*4#dUnZiRIl?nwL{$?)v(B(KfjKbH8*STKUf?z67{muI=SUi+fKG zD@SNhd>wmT8}(KFtE1=IQ3*6RPmVYbFkuIio!7oIF1O&H^OuiW+L?!aGYi{#=kklK zv*QYwM#z6$g<{zwf9Gpcl$uP?xRiIyQu%4CsPNeaa(5plA8)U`v^=O;5z;AAnm+SF zp3+p8viD!tWFNY&!X}>YvpV`l2?Z)3P7ynFe8y)xpBl6n_f?+(QWf!7$BITPhm>kp z8VJ=4IiB$fdx4$GoS+=S^UF&;)A{?){4N8G>hbDf79aST2bKOlD|;XZu~>S_Z(Dax z*zf*)w><|`xEv(N?Q5ECSAV}f+MlrM&~sM4qtDi@U12RNmXY>n1jh%&DEYX>@j!%N zR>{P@Rsos=hc0sqFj3Nr-GBFE)_a_s%%oX4+B;(1-3?X=RAmS+vCHCh=KOV5_E4!& zKkXjSysSAU59BjBm9v$tewT-KXaY8N=C^XT0Z^vBQo(w#&Zr`ES`o4Q5u97~u}2TNGBB z#f_Ol4MD-|)-ft(?T~>`2m=|X{|k$eE?X+EeGjlck-f*#q`#{G!L+dpNK}Xvc*E16 zNxU|2@VFi7J#M$W^=hl?Y)5$=gn*Hga3&~6vRJia(}Z#f4ORyG2~Pbq>u84kRn@G& z6`gkUM4x@{um2Gy@0j^nd%etg0$EuyN-WGtAbEZSLmxj*Q7dX!stzS4 zk&y2Y_-{zirnza(?bBaal9?n`{9?{1Zr3Lz50phEnIIKVJS_d%#jR=^OD|9>K>;D= zKxvU9)rWHKiY8UsCQq*qPs(f}hQ$(LB7{&-)FgEb|8z~YAxDhs{Pa7z7yXkxJckhB zyu~c|0ia?;0w}g{J3r2EcQ3S0_n{I-!HfIJW5{BbB#lIv{H_5Ormfqaw(a}2 znUNjhH7R3oN^whlgO!sDH-Vt$<@t2Cv{?jLe3?ko zRHR@OJKt?6{F0!k{wsQL=j~eHE90kBUROQf4@YdziQ^U^457TiV;416+p~`hqAXjO@ulDJ?9>4`I-X=cE4RVvNo#Jdw3pFtVz=`y>O94+s%W3K zqo1J=7V6Y?)uZFwcNR(=qd&D>Mf>Eb)b?UsE^JoYIjEpSasc}sV;1MR3{B8TC~*$PAMw7uCihU+U?p%8{7>z_1*0)Ud8QJ2hwTU zHdozg)fDfoSgkup*W^d_>(hC4I6(d8yteM3;u$~Lr_ZrCqi_SGbPWMQr1;$&uxU>- z7sJ!^niMY0I%`v*Mr*$4Iy-!D$a;Yx>zP!tFt#tNvChYL+lrl2K;(mV-BoMto$n>j zUhFwbv*6AUUsgt3bj5TZx%Mh;S5L2TB%U>5(c%GeI6^6gQW2{l1`SyE$&()Y8v2QtJX zlGfWMd4%x_()v_q?26m#5ZdspGLd*;o(mE>@V@vGTiV`YU%hXeJ^c8v^&D-o2Od3S zcV0eY-+p4d-G0Rd_Sh58+U}6wx`DeN+~04j>e6<@`4MZaorRs7F~;!F%j^-HUeHlz zrziXZ29_58=IW4w%U4oYWGg7CzN9Xgv)(9pSF4krk>>Z zn)7Rc(QmNOx)%GvK5~?TbV`XjpIo@voS6Avy0~Vt7|xK@z>zd)oU)cVBk8>L@ukl$ zZ9VIH0NJLnX7Oa1)8wn~oru|m(V*3IMy#tUYx@q6Lmp$v4XavVeGl4+;jDdsTMug* zd?tH`tb0kPg+<1Q%-~14jgWz2_~Zy<6EaPV$&dH$vq}(AJ%`7vx;8Hc-) zut2h3lb_blg3NT*^-!rnKZ71%#VeIk5B%(VAdK6tv!hd~Zh@E-g!nuYq4PGfp!u=9 zN&Ax$oWol8g5_3Ap=5e#hipdp*B1u~)_E{DImJMV9&Sse81hRqmL8xu9%6xHMHG_L zZPW)+fM8XwP=`nD;2d2;9!{O_p$$&i5HpmvGvUf3r*kDgWMETfW`@;I=M~~^Wq}q9 zSaOD%>tVY`vL;Cl2pGL5bM;?=H7Xyb%kc@J^0S+aR$+?aS-!*ohssbSe2A zdrw$j|BUs6EC~33j?&+aEYb`>i^t=zRl`uKST>YfDY|;58oMD72I^C#a{BQ-xUWHn zmM-}d^*{JuHLyI1Uqf5;R3qeX9kTA8vo=Mo6N(cGI=pwyFi? zV&6%ty|&B3jKf-D;b$fe*_M@+%u9I-l@Z6jC2Y~kwAB&&Jv}`R8Ufw{V52#C!my?k z=9Qo1jsA<90;L-2=?+CL-rqE{SQk8aP5W|r-DE+E0@aay$3LA zAnZ&bJ3)qQIT*LQJE9m-%5VT+?Dd2#1d644EN#{GOO55zzW4Yu)^+%}#b)zXjW>3B zf;F|K4pPUM9YeX@u&l+l4^G?pT}^h`GNQ)Ya@JCjx2iB)1E2{MLIf!a9aoy~Uhkvl zm9)R4*uIk9E1mn3_ke_VCZF@`FS5`4-d|f|RV{I1UIaSwmEp-!Mw=`XQy$Bk@{pBb zZO%bj;bEJH{Zb-TvM2x&{DTODS$W;i8gh=7SNa-C8?fBu46LFkZ4p@aBT38exe&L>O!Q3^|Pl*u;?E2)%OXAR^ESp-m(lp}VbcIHvV zJ*IL=+6463&8>psruZ8`4cEv52g_1#pbSRb!s#shTt|$6Vln2QbW-iov@9rb28BBV zt7(+{ZGkesQ(9G*`mjJ~RfxHiLNCa+>TobFwN-^W5-3rAlvcklfH53JSq#ebr}{z0 z1VOgd+#q>XgLFhrE^oH{mqiQm7G)uf@IU|&z+2=2!sd;5ZP)NVyYpiow7%d$wd^%1 z@_0rVSNI25Xv=}%1tGiA-7_Y*;~e|Ec)}QTup-k3!7d&~mxFsJ15(HX*4BO%o>z#w z1ceWJA^)L2Nte;sQIBv&jBU&HcK3Z>yWgzh2ip5M_k*9O$=E9H&!beyeZSoRqRGgCh%UKkKizaY7%G zGlR?l0lVn@Cdbm&P^+DUY`%l~Os!dVqD4d8Mtj*-{5bZEQko5=KX8-}8_}<(Q{uNe5R~ z)nISFv5{C@NTDK~7J^{vc)G(b{m}nQ4$-JJZoa{iC=PdA`4&bL>jvxi1wa2$mY1Au zhT*+HiV?SZOovO|n9~|U1^|9U^s`tQN}sf?ySLkpy-&NeRjh(;ot0JEEu)K_T-tZ` z_5dLFTQ0kTlmtyoMe?eltU->FN#n^v>D~IwQ`XkpVl7KMP!I#eNurdY{iT?^!lb`R zCui;3-@VsveCw@j_gFL-avsWD1UL&9%_A&6@q-^(-I6w2wPCgM3QK7exx9Rox#@%* zJ_hR}kkRh$ZYD-~j#;Y_2GcP6`bg39@b|t)&b6dnb@>%m2BU64!NpSxS%G{aM-Lvc z{Rj5h!xgic)A)OVdcQVbe!OZKJGaVt12l|EsYQpLf|c{fDnrF zCD;b&gW0hun~aTG1SMUDfXaH3+(aOvK|m=lgXoD|m{ACko_qFbV36Hb+1SAO^c_EX zl*J*zS0^CrS?cn|eOf~Fw@M*RloUnI6$YCHD269#ee-hic&@U(1FxN88^B0V7oUWT zg50v%DNAS6PblI^Jk~6dA)Zx+_NLqSsagS6lC)Y5UUOeTlq~Blg)p|Ez7ka0`Qh9HkgTreOT}!4H05 z`}Xd&_rCXicJSZ+0%qao4AN!wdae`u+Osfe=C$ z!vxHL+1QDsty;~yhzctcl81bvL&K!S!t3fm;h0R#S{lNiC|Ci z55{GP6n9HIm)W+bpE6(F{1t_xi;K#OPAqLW`+Iz%U38NSDafMR_Gslt@x z=A}Tn-==0J;4+!E5b{@fInq>i#@=*MttBUdR$q=$1hj!CL57LA8~~*A0dbcXH;Esz z0+iXj_F>e+X3TbE9#AR;R%AAm$~oBsemB6h=po_HpF&`>v7gi*#b;IAw$QyV+IO7n;JC~ide&NioU4%SP65@YG_M6paeQdE^FHeM zGme!?gwYj9YOzjWpMs}yY$SwGH=DH1nihNJ@DFWt>44Q>CC(yIWqf+;ir=|!CU zjy}An6Fc`<-?r`6v8s(0l8S&d28k-DY?7FiYv1`n zJ5ftga=bBBvoXup_^kiP^S0+lTkXp0Zn11-B{?kd{z6!kB_+*{yS`?1>z3JxS1cug~hPuiX*pS0yGRzZ*7Y`Gd14wTw52_-y|F+Tz`UdX+VKVjL> ze%o-{TPh(Y%B^6$Vh}W{@xR+Tx^5wK=BA#aPOjrbB>ElO_S}R0PX%u{UNe>)2VQp>gETjm8 zgG4G>i9rCes1e4);hd#bGj>r-uDJLTn*c^t(O7AD z;)#R=pabpe|N1q1;GqX?XlT$be$$)mum19{Z0D|>wsHM=_Py`k@4W6$Jn@8m@{^ym z<;$15>y?z-*K)-C^sYV2oRO=XHC}O)j2YNI^EOhA;Ven~*$6~F{=-%|76iI!Mw(G8 z3)fn6rp}T>9_t~6%6Il=?7nCEZIBpsOj9&WS!yAZVkW9C^OE|j!fG07E!IoAj4Cct z%a{veCX~mFKTGA@>j8PKg849WbD703+|7(*z>9foratfT980MTkM(2ZWDd_)v4;6A z&bSO}WM!w#3(PYyGs9XvX*uRQ7f!<(TE;rlK`o@6_dyQTjzvg=#L9v%d#~BooRlVpACRCo#Ck zN01#b98WQJNH}X%Ny!P!gN&b9j4a92jFlt1NdS#wfQ}YGY{OXHl30oZFw)|dvJD$H zSY1P-8-KbjkD$S@{Kk?A81!0f8nS@pE0$ZR94jzC66eA*X$#|(-1W$VRx?gr!!s6T zK`bMYw+j_zK}};Zjx)I?f?=y|TWZ^O9qJ6D}_cLLmBm<++BIz)iyBnq~#lz*a$G^CKTs@@TK&jl%={*vpxFA zckPCo?@;1W1Ov`zVy;4g_i()mgoc?RV25e;a9tngMznAXnjEUN3y3i}y5~7-#sioi zvC-A(Yo3WK5n+Iqbq!R`01HE)T_$`Z0bw7^*Ku6@gmcGqV= zXJyNlLiSOEPAPjmOu*GOw(rnkt3jddxN5V_RQhOU%Ifh>`r=uLk814Xo?UiD^D4XR z-mhEr1)Hq3Vi}#pb95FE_fXw0Jwi3!fzZ63)HtjI^ zKr1SuPT*R-hH(KL*eRg2x1xd^wG1S&RSS04gwaKUnSQiQaj|PSNBbrxC*ARvUV4c; zzow?z{^*bX$lbLBx!KuSEYan7XAjzUzxQ4H_6yv-qjHU#`r*{>vS!{lz+q zpD%Y#vHq7kj{UCh)&IpG&Q-q`e{}8Q^Evf7w~e*K!OIr=JXP;Y)PJgZs*iql*PQ!g zQ=#a~LO*kdoNDVr&uZ>CX*z<5c%&6k{3xCQ7RX%>KWvkO!$4(6fk**0;YZ{}DFnT8 zQVxs{51|}}ZIVe4bX<$&2~+Ny?i_5?83G2qAexQR1I++`U#STiS2qJsko#KJJp zH)Nw&8$DIPYUz}$U{+aTWl>?{X!K(!OIKd?X4}4Fw`K75awyLS6&&)6P;;ZLfn+03 ztYxHgiKRjT$YK-^OMmc?X7Q*@aJ@)5bh-$naTMt?Vxn}19z4V`_GbiRCEj;Rf@-wf zc}hjBB{O%lqSDIn;KpFN>_2!2&v&Cm$uH>5CJ#WIPvJkCbNEGICHVWijNb-&za z@xZh2QwA2txJlb2kSU(ED3~ekRf?*9B)n;RQJK}JVqo${$^zkhg-4Ka_TLzT=M5JV9RpJp6_$b%w^d8fO|e(m}-RtNMV2&AtP6i1LB36}27 zWlov@{=OkPvvdeSbK&N=8xf7O$8llpn|n{hPl6@lGyC@ zl!g6x{U@ue?a!TwXampl^%eqky!4PU12-FJLJ%3slKFBV7=tA4Kfyd@Kj_K zYK2S@K=vyG!rpTWKyoNbg<4mks3!h)m2|<@4cqOHZHBi zSs=ni;<<4+G=gbeuNv4Q03Jag(U%~7XL z(uS8P#p5L$tKOG5d{n41YT> z>|*^-1#XR6;21gi1O@RRV2O>mP*5u7+FZ!rszlqhmLU1N4`EG%O=LWZv-L+vp~5v3 zPa#sKtFFC*+gW&Ea}`_ieX2sHZI0utM2LX3_#xjdsn?-)bOU?6y1KZWCO+yb>?!XpbHH&O^5L zEjL;m@{CY>tu>(_D{7+&MR(c{&=v@A8n1b-+HdV!RukKXEwL_Sqtp?>ol?d^pG(69 z%)MBBnQgvevsHGsSucV{g!_#mAf%INpogRZ>8iJCV1$Wm(pqX(nztolso0S9Kfe{l z)@#k(m)lr=*6LTcS^1J`6nH6?0#*~lRMTi1FT2EcKmCLqOitNl*WYRlot-G0DEm@A zQEn6T>6-J-v&Jyc>-~pGU(;e0%a@u@lRt{~2m(xaS-Z7wY_Rq}Sl`o9Z zxksoEz&;jaF%d~s0ytU{%YP*TjjX6s{8XGVjM;QnmAN}|LYU?Q{S?WZq=-uK)u)OS zS?!YtqUgPq@TOymo`+%`GS*0Nlec$%DUw7@;Mr|z#`BA}_|t#!mv;BvcRS&)4A1cx z*6i=zf4}|1KmLQQU%%d6v)G3Db(OxqetRGcBs4BaCtk25akD?%F>Y(fn!M_YmA3!r z0c#~z>=@w@e#pi8fgk_#{ZHA6IB8S7Vc2UG5;1}2mA4GkM~HFRSVnHhB@z3;1yzHQQ{CJvd#u$7EFe$uP0^y=bpzbvUpclMjFR!>5dX5#Xiw+;flp;UE5?n>%&i*jwK6 zmY@74N@u(-dq7|=gAse}x;nc70c+}DKL*B_w->Y3)gJrM`E9nj1DNwTIq6|fjzhkgWnNVd zWH;C8;sqbd>$2}kjecc4;1}!@DcK2kPT-ppU{6)6nQ^YTU3k1aq%I9z002M$NklTa0%uh8}JePw-_W!^*b*S61CV#<;{}NxKNQ0DR`}8GM&?cb-jg( zs`8*v%ER9cZ9U!mhwGe2y=-Kg|0ig8)2$g}E| zcI8U(vv>w|>H5^+m1k6aqDhMHayLqCNMoT}zG8*7Hnv%mcu+SMX#;>y6f)pU^m|)V zE2%GfEZkUYvz*71a2N2R^#&Nb=m<8xYqtd<^$|lOAU;bBjoOjB@3QihVq6}xHS4-< z=J*7$Y_s@R1(QPAe0GN&i49oSEkB2Mv(jezM{G$`Gb$&rDX8VgAAZ0_AX3WTaV^)2 zS!6n8F$kKW(%r3$ZV9j;5yt z3ajNQ%sz9Fc&%xxyJ9`4wJ7YNp8?D#hdU zUGLSI%C5E-OHn}$V^vnn+5Wxz?4buALP!i-dmC}Q%tuj(y!!k4>`PzzlJkNTJ;}v7 zN}sR)9tcAOWiW&QRzRu06UJ@NJwF0Oo$(j1v6_w7m~VtQ`k5gci-fHY`0=+MIABu% z0CK#0)6B0);_Q8KZ5)Ni}r>qS1|_rEiyhsd^ST7*(H_=**72B zX+P{4wQE*wgv2pw`}Xg(PkiDNPAUDE#s?h5@gfOcP$kWf1Nmq1TH;Mbn+yP%7tM5BP)e3KAff6K{yc4h`xB zMT^dGGU^Mfq4XS)tAxlu#T3bIR?HVH1yGaZp>+3f#=YwFqHgK>o<0eYaMP)Cnx%B-A$L;OmDyKx#tn9T-t8SZ@bBoCyv|5Hy*a~-3P4`Pph8^{Mh3=?ZnfM z+r_`|HuEp3!;?cF@PqWhGlO7(`)mdFT#Z*yfbPWX2yH%$65M9>wcR#IjOURDzhUd% z^&ZPDskejE6BhImyGx3YN}!P**a;u`$A7n$D=)A$H(p2F3qzS_q)Vu1$mrRWLHzLb zKl!vB*}2z#^{bz=+Dq4&M~UjllUPyZ9`9m9Q&pW)MM|-Rzqa;vJJ!=<$B!K&?Z%i@ zkv~$Mt9e%}-BM=v!j}B_M_-$+WdHH&5(8ZjXQF z+g90uf=c{*KOWo9KfKMw-9lGK96WO^%xz%?N(-#BtMO8tzb0%K5Ubl=S!N>#3By3f z3wf%@VT%!qT-I^I3dWe%lqSr=Xt-LORP^yOYmz&z^y>}P19^r2APWgLV|gZLRKzgvbT(U@-PR_OXxId8|WHQuh%$ z@ys*NSZiym3zv}LPoCRved}BHE5Gt9PVv?EW6X24wYAPDD2{=hJ9paFty>+e`3=>< zrJGrx2ar=?dkLc3wRk#gyLQgleNXk-C`NeY zke`8Q&j^e1;oOJ>(^eA>S|=!jw{K~+j#S)6`p9e$2HgU3ud}(@#uek87{-BuF^_pp zlmbp}U!Y~B&2Q`;@ULmwXzzaiyNPiehg={^+5w5K3_9bY)I@LRb@`}+DJ6e_b?;H+sD@>wLM1>xt%o1@fvw-P-m0sr zSo9UxKBv{a#7R2-k%u3%3okyO*9--V{_qLbwLnoG(R;ppkKK01Z7%nz1Xlkd$E%uu z|JN_rH8)>}0s_SmLaoomOwN-$P+rwPVlbb@x4H%2|3wo1`R< zjgQ%mr*_yax4(sWzggYEDK%ABkgTJ-*8(P@rrzFO>pgMOZoc_OOH9L5r(C8*t@@1m zl@oP8N|Mg&UfRXNg*P6Lw&2SusvF-H^^3L$m~Av}e|OjCU7Tuw3&us&&T~~h*1n+u z$N^4Rg8M1BybA-)PnsB|>PaK49Y4}zRVaMTf`cOBxfPdqiZu6$*k-luZC2IZ;PN`= zxcF#%3a=~dP$?{ZJcB79etVCcu&xX6uI7L^Lr^e|SJsc!n&=GcB|hlj;HYhwn6?g% z7xZ@=a;hjD6!2A(3c>71@38fc0R`lWWuyQSG1c_sERdSCHF2*4#8Vy_8iomyNt`w& zm|#bUiLLM}sZhBM(w?4yQCpt@g3Mi{Va}a~);>W@lNgpKrpIjiwr8x9iN4!zf}J^Q zN00VdC8>y#z-q!6dj(YT<$rP!+9b3nN7fAD^5YpJ!Hw_HqLAP{rz z^vt+L%Nl@q`$$tWZa*S*Qo|KDTKyX0t6;A@a^#rV=#;HEe+e6IW8z=bajHbAO(LAA&JgS64gYsP*gC z+qb^?EeqqR4HG|};5i0j1#fTy!$c0@Q-Y&}#+sTM8yp;jnYry0%lS70gcpF)-m~R!$@7_yI{H1cv z^?(-vf|Q{)a%{k|$I{k@v3}-6#vcF6uiA!{po7*lllrIG{`t$>Y)>z9Ipk3(xGWrp z1MATF8#mb|((@G&Xfo6(+(I$5f**hNt6z0|0@_wI#%ca4hH8l6{m=jW&)qpi^vXH+ z5YELLMwEl+4$tb04Ia%R_ke!l-|p}3v8lrct$lT!1(tT&_Pqo4UCcV$D=wk)f(jVsD+#Zpr9&BSeVKinaHy#6W3_7uy_961B(G=`%C>kVnnMDi%p z{;8nDbFuf&>3jBn;N}n72QPa+R=Z)C)-zVy(9B9Gl}E9TFZ@<7Wf0MC-uM<uk7OX%p0L!MzG?SL?Lx<_m7Kh6?h9qUfa{hKZ6O+6w{DOBbK5 zjIOqeFL)F5_~aBN=U`P`J>8-EQ+-k_b(a+9+05G|D=)Q` zu#Hcd8m3VuzTWT^`^DStw8^yC`R=!bPgCnJk@)Ju;+5XE9+osO*Tv{9K zW{$T7u(5IEJh+1o$%J|Njf#mb6fY;t8Mv~l~%E$8RP^| z8$9vymFu1Jk~-*@Kk`vvioCb5?WTC9713HdZ~cWZ2KrH=lD2NsCR_i>|8DuV)xZ?< zR)eRfB2s0wP4)C82}D{O>|MYA5v%K3Z61Uaz>(C=j~=-mEU$;0|Ho$r?9Cr~hxt!V z*-Uw^u%IwZf_Ty8<<-ogQ7P1b#;#rgjd49-0U6GgQWU~o&Gr00cKZu-uu40P4B&F zH0m8ms30K(sL_kT*kFo@?btDPVmmoGPR_Z0?sM+FPy9IT+?_~C*89EdTkrp^PCyj>6F1;TIowp= zA0Y)IK0+a|rWR!Q?UGtNAx={#n|owD>%*=+NA1DC{#Sc+xYJgxx!R@$itL$3A4g#8 zwYu^myMvgrd4(-j=<6aJVUXwQF!i$2jsG@SEK4x=I|+XpNPu^$fl!6oOu_@W4|#d; zveF}6_hPDDTolmr)Q8gDhtQvo?1FKXgi*v_qmVpw?1;s?Gi}A1HTH5>x^1MSN*;hT zgi?g)78JZ3jZ#%^#Ofy*x*^N0+=}x^Sa{&T0q2I?+}vz8-bfibo=XD2x7>1zv(%QC zmtzEJwZHw_zjf~`SFXIcCg38zS{bVZQ`6AEm*OSaiM%MgSrh!Qt+m?X~_QGN6B2**MEY^S1l>4R(0%Uc3Jf|G=!6x{PpygxLUK zRF<=ZH7F#;QpI6@*uT#2Cz%;m4Q0y~#E| zj_W5Narqa%=;~ckbJ+>NK4K9i_==J9JlU2LSoGOj077V@#i1PI zp+t$P7AjyiBPtL}(fY0HDR|#t6E0hB`E`@9$T?Y@QY6Ge;(in18noZk>WS2^jM~vR zHrVbLUa+~>-fWYXFJm7B3@o%EUk;}r2@V-~1JMDJ2$h*XIAHsK@k={%tlqBt`qwO{ zZejwf;VMh$yBoLcxDi}lXHA@Kv4J7m`si~OZ|<}Ociv(}03E@t7}n>oml7g8ECNG= z?mA}E*8$wlih~)JhXB$O>mk+>_t={8)`M$M5tkf66P3oIkjp*0sB#Jy)@MQ7Qx6|G zWLMqs8OBUubwS`*w|NKJ0glwuBvtz0K*Z+P)LF~=eb!D4DM;JOS_iFQlXSdT_1qk zH({#Homy#w^z5Zps-H20(}R#9i1G!E_|jEYdCzrfBS44wtNDn3`%k5wyISpn|Jk396S1Gb;}%xB2*h~61#ASw?Nc~PX= zCzb+wY-JKs;KP%{L%emQB9l-Al%OmZ>BNieKe*4DxXUI^DYmppMHZ+o!Mavt-&!%l zs(bfZCF^ks?#G8uaQ{*g$fyIEmF=;KIjr~O0O-KYblZVWj>)Hl2J0A#U&WloSua(B z3;kJ2(EG%4&-)v=o>vi`_dKpxKYWD6_^hfC&nIf9UrI>N_Vyv$_)mxI=akXQnmo-` zjw`fd>o?n0j25T!^LQSSVH;1o^II|rl*#}pYjQI8z}Wq?eyJ%NynwC%uYm8^`~+k7 zPksJ$Q9w%Ufi#ldA!`;Ed2MDz(7Y|Zwt&pV4S32Oe(H=JZ%VgqIUP1**(~ zZ@rz$9JCdMmG-&G0pypUWumn2K(3G+oQW_QzziHK@F6pMtg9ztM^5x2ZQ?i6vgT#P^&=oFp`F! zT3QUu0fc%=y$q7*D9%0{Ch`!|#FzlY!w4#AXq!C5Hf5s0Y&dWPK<%}n%v?Kgtidj; ztHYHQaEetgtT<-=g@}_4U^(4j7J$Un-fS}Y~If(dNOzYV$+qQYH%~^GYb<`8vxu?mdO`m1)G5|3EK894J zDfT++UEGczI$}rakK3*HPPQRjdJpW{V_q`5R*>Atxr1w8cxSAuG(Ryh?yo+bYto)b zDZ*8Jm{Qj`ES@D5C0Ot^U+@5P!1OvU)|t|zO;n0_=G({T^P6hye*W&ex#kg`)}%X* zCGq#`F@!T{+|AAv?2w4=&vtamAGuuBvq4SKw3koqPkb zC|Sd;BLwHp?cBe$-4P{bwNoZz38rQoa~0-Zq`(b4iDDufmssZ2gS$|eBvbvw{zW>- znoJzat{0xSs?KKXdu5BY?mT3jgfAR?{O5N0yrixJ*}Iwa!S(;Py^h z@)?ZAa}hNOd<_i^CU?9SUwn~=eAotXTc169mYatFb1^`YpOTVN7Y?v#(`F0eqTEjy z#}kwT`Y(UANg^09y{ z{{8(}^QCA(YyGn$d+bHL$S%G9MjJO{xiynGsus(`4VRMXpY{8l+I_4|NlWhA?e~Q* ze8GWQDflh~fZy)8)bF2g3Ltm*F)js=ETRZBWyE%c5q<_*ky(%fj>a48;T>n}EIC20 z`QqhPmPagYGdW3WD(rU`R9awg2Zo_c>t>zoBB4k+AkeJE^|<0jecg;&_Ft=nzfHLINKtS}uY(!y9! z{Rr3*x$M$^GoW+ollV#m#IjigHEWbZw>mGf=E+Jku%#9dN%X8N6JT&M-2(UMqk+^R}2Kv#q1BkK8;!Fl0=E_hAc(H_!FC#7{F%-s5j1`)zwNGxb z*I#@U3tAfGVJ4!ZAnp+}YjIvCuE{>mRcXCvTdipFI6x;cltkBiDBIH8(oGo($VvQZ zrZ3An$PnBPIL@6|K-?CkcK|(cCN@TC8Z8(h9*#rAeMR`*+|XpzRW)|z@Nt_k`y}9Y z3P6gaUL#mq(0IhC*y~b169W17_RL@ zSRTCy1x%M~;w=o_C`zRG;W+oZR4iIa|8q?+Q}uRLt2YKnP0J=WY2;7=Qn`(bhQpCQ z%D^S#v0 z8Z3O`kd>ZlunLqT^;@@CcBa=({`8mD0i~54;i-bF1Vly*;>{;-Z{S z!m&ZtlKT1+_SgUOukGp;D{NeK&8ULn(@CXI|5ar~YRCkdX7$H?R>{4O4~G?E4fC_+ zmy<`Vi8Y+1mHUxr4G)-bN0V**>0jE+RoOOcHkN;^_pu-`+bGpC5cIqpm)1|RzZS1G z?j`#=>wGS*dYLFu%kUH$_8sMZQ?_{4Dc140%ABRzMMVKUSMT7crC^38@DaB&(BBki2~j|oW?xf^z77gQy^8f zx)ob{{_}Gzk+qMKMxbXYw8t!`lhC9#6zJfHtSmT_tnd-S zZ~B@C?XUj)hsZsHcG-mK_9wS~&gN$BV>^;cdKfugUIR*M3cn4JTo$2g5T0s26vi7= zVNV_l_ABfOPDfEFS;mF?!KbNqaZS>TGDXNz_~P;U1usPoNe_-?=o(^d$RYG(K$oMVYqp?ptl;=Rae0OBYx# zyLTeDhe*-geQduSfBZizt+g4KPyr6=Q6cDc^y2Qw!~OErt8lHwZpt~+iTmu?zuWdd z`IrTX4e_J>lM$|bT#c2KRbl~|Z)L=%4WoIK)sC4GeB`BR<60LF1PUr7rG`z17uTnC30w=?RoeiTs>PL86X;f z-iMX8azcf9N(hWE&bM*1WWyxpO2xC*ScmKw%KHP}3=E+PV=+0Q0gK@h7AD&^Ns8^v>7%yu=sufWTWRy^#u4AF9!DCk-yXE0@gUv= z#H&QnBB`vBN>!b6z2ziAv@lgnFD%Qt`{WufzewrmckbU|1(cwfgR62L!gMVm2+cGtU_2JAxXx;W7e>93+D}z9XV(v0JAG*&9sIS$LXKzf@F}=-8=4~ zlN{^E^saeVdnaM+!yKBjfl9EG0;LC6?tYS$DI;<^_rKni4LJ)}^s;hX#rfCGJ_G#& z4g`0Qbgzw=q(q{=4|7zhzJAjbAR~pvvMQ~vj`u*Wono$!utar}*}Vss{a_5wA3_W= z!g$C;GOaQ{YIn}dw8hmv>uTsHAs`AvwwLGUSV?BSmHUZ@Y$ucU7Cblx2_GVHN-h~f z$4{%Z9%5JeGP(CNcy?rPf1}$=wTqMjJpAH>e4T0z+qP3fwg&}Dj1Zg<2>@$qeRe!V zmIuNe(iDiDo=c!g++KU-c{}j(lQ?V*T=2OjkScJiINNtyM+JPf_Q?%d$E4cDK>Q1;8>m^F}m(}_(0*N8vc>YEPQQ$PQocD8;C`EM}v zNbW@VOv5`ehs>h|C{yA+9y=4g z1+Y2E%IdK{_|CU+Ys6Xvu#^OfhopPgGUU85c{a*DIfmPE1+jW%Xn~cq#wABmhk#lE zO=le` zEP$14>X*J^{pAQ3fMqY|D4j||9TstoUv}H!ZY(TMJ!?nTuD9`(lWiPUl{BovA*^F* zrQB%C=VRSg)S@E_ajPe+=lQ2>A8GJw%X6*lrdzESi(qbTnH5)-STAu0`I*I5J&u^% zu6An$L~Q)0pV{(zd#w2Gdx&l2!2p@#Bp#W$c&^owya=_cb5S(hSmoY&2Dk5iz(|<# zEnX#k!ae)aYp$}5zj(~*iS4`dyMN-`p*kmxO`g=R{kt20d@{^|Gd<6(r%hE)e2^fCl*>)hWjBSaMy?hwwDBE@=|C%c9J-h zZkt7BORw%xuA8+VWt9LY_b+qT)7D`5mBcw`Ff!HxKMIbr8I!DS&LlgFr$R1#KRnQ5 zi)KzR&nr7EqY3_)kcBthbbs&;_#pbrRR*! zi~WedA5W8AU7$7z@ZUP`AI-Q_>rrladg+`}4uz#`Vcl7@U;*>f;{b0fS+xah(@|ha zAt9IKbnYoBUz9=IIczXDsrJdFfH%-Vu7NJ=&KR!?VfhLdsx45rYXwHM#)=> zcMwYBp)QnRoUpx`KG4VPWpIvAX3}}O1(sp9$kz6KjPPL1d0^W4cs3#t_Q#-fY z-j`lL0@Xs6NUo)7{Xt!L1 zgTW(*?bvfKSZ&vUb#L8kho5`ShI>1Ztevxhb&>Gk^m8v-u&o6FKZ5I4nze4*VW-I4 zy8!Ffp2wcD;{ete1aAP{L?>K7d1}( zxMeHy_w0#dY=0KF>Onw#2*6?1_{QsYcHKI=3_?Ehk3X_j#vG3M0nX#Cql@gPSm~;3 z$}B?Gp4L6vZQnCb*nBMMuRipUwH(;*+%KY7ofXd#0ED(4+|P3e+ZufcIHc#NS>2pc zz(tFN3OlVQtwGa50;aglo;%%EUwMTcK7G;wmoRZ!O|3_5ptq4RppnJ8Im|SAu}GJf zmEnHNLW2c+!i;ISV@D~$bdorSHrtK6cm2s@SQP^nM0p_4($(B#$B41*qg<5BNGI28 z&iiqT-vW4gi6`~a=l9eGdhN{a%@zpaIu0sMx*`jpiRG0HDS#p%jQg!zlXVVVck<=3 zLo!zaB}ofm^8B(|4wBWhNuaZ>hl5Ap9v#I2%yJd(B<}ej@!Hl;5-v)?;A_CYgtj|| zhI0GaPW$&=d+h*T5gy9!2zt9PI(M-EZE2Y}|DXTh2V@=Gj5|5^u;K)CBfEy6i>0>h z&>`FON=t2s~Ci3YoA(PG~mH$V}>obU>m5IVs9CZ0;QPc8+@ zyHDfkFlag4=YwQxsGVJgz?X+&GY=)G&vHlrn3*31J)S{8>sXoF!|gB)$mGAU`g8dFefN zIq_oOzJC@;6^97ZQpPbo)lw}*f%irMDUz|*SRV$YSV6!lClzD#@Y@+UuAMBKJ*;W+ z?Iaa|Wo2Oes)*VbuP?V1R}(IX3>rq*3K15Wor3`=D~IPMnY(wA_5C=x1O{`+8xXMk z+9HfY`PRm|N?v4i^<*qTB$n|a)h=ENV3H#75SD~j0D_PH@=>Huo`h(8V*ps$q?{<7 zz^(u_Y^rFDgZ&-$?4u9a^SJu**pwHeV3M&9yJTg3A>#900s6fWxC_ zPJqCO5o|kcM>f6&=nZ0->aY_C*GB+-8GzypII_3twEg<0KeLS_TZzKFy=14Y-?Y;_ zja`{OGa&>g$ zSl+x?6ZdfJGY>y*9R!?s$b<~`wvDfEv@l7os)$8tIod$BV%%_h`=~|hvyB_q+kgDx z5yp*MFyJ84*=Ac`dByzH1@$4I9-x%RnJrk7DVId59NUbg_^F>fWZ`~*Es1j!!}!W` zYb~3)r=`?KJw|DV-LGx|AVX?m2A+NRQG4>=|J{Z=$OZ!N-}A`NEx)~uIN?5PJ9FAL z{QRd*a}_|ajrI-Li$D7hTlb4!fvkYh%#GY+p{Egz8B!06kK!vcSxAP?w%GcgJ!-E#@)$AA{D%fTSjd}j z$rdik$tGTye*K&{z-!<3J@$)-91cfh#LJPIT~CSgiIbnHvCNH&4fcWQC0 zWrL@V+vZ>Y+I++wX?JDqR#UPq=>uc9(Db)-661mUH2@^p1U>Xk38{v$xCzXhuc>yb zqv{%)ed`@oGif$+PEshR5+U`A5_O?nFPS>WCSAS4PNG=p#2ioMQ_DfC=^EL?J!$#2 z^<<+xjZ&ctw`263(4O(}kYIF{TU=sK{QNO{ZS7hcAf{6mKPAHQ07CJ90BJ_8_skjF z`O+&^i{(8JfE7XT86<0Ql$cMzkzy0Z- z5*ts6H2P7AtW^8tQlP2JXBA7XvD!6PS!Cf->tD3Y3a?#dQ|`Rerr&(MT}|ffOO`FM zJMX*J{_J~qnLoYEI*YF|-<<2rKXI}3&A!TpXRoogx)rw0Gs%2cEwkd4D{OfA6;^!L zE#|**nFUwOw}B1w6^jb(`+rzL+K>*Em#9-IfA{gLsV~NP`xIO=d=-UQg|))Fr$R3ityu zks|P3y7V%eJ_jLNX@wC$CCw)+L=v>+QW>(w`Xg3F#xfrP-&e0*h1-DpZ?beGxRy@AK?)k5OU|F>lg!K(k7nGRTYggFTAO8f@8?@?6XWEkM*ANp) zCRTzBi*t(Ynk!dYPFcAP(O*eLrOjQk#P;ntWM@0ut!~~7yXNy>KtF?PWhi8`=Fi84 zx5jE@*#IKUTe`&dlI8dKi46$q8MgBJ8?9o(WUI&O5dpAIrw-dB5I|W?Ev}|vKw!Ww zU%AG*4gqG~*k%Qlm3H-Y*SRdmvR=ADzuBSF?KXSbOuOcW+bsx4 zlHf6A`gF@IEW_6ra%N#2!p+o20wMOx zU{2GBrOm{Z`LavqTOU@rI5C|*j#tT{KH`Woh{-7~$a6^`vZ$3B#hP0;d9vA+E3BOB z4KY_S5R9yuP6mgS1o9N~tb}6;Yu&q`N)7r$Xi~DgsWFx$pcTEuOkXn|cVLrW5N=85aJRe)JP7CYx(+MVg)R4_bL= z*osI<)`aHYPXXKIR&38b{f3p?ztxIwy2bMKaN*&kl25uGF4;A?-~P6=^DXHg#kgd7 z9l+B4z3+X`ZoTzZ+qnaU92VQ#Zokc*Bzfbsjshh^=y|e zz1;FBxzWeHUPRKnn38}o)y~0Q{G1%{=!fJFsrTPD3TW;5AFn=b-~G-tR`aKSWZqDr z4F@QvlG$nX!z3NNsl@)azs#EOD$e8?c4~Ml_3!3lZJWwEtN8d<8^{}G?%82D%a-3& zz}=e3n#y|4q7n^v6HnVrmRstl^Gtl|=^v^iw0k@?{HPj}3*K)VVN&Dy$SDxV`>LDu z{}?gYy?P3fuOz(?hL}`h13a_6DBQzoQL8E|u&ZvVw&R1RZ0E_il}?FUHOGxvX+H9? zrcUr_qJSjSA!{H%#~!QHZ0Jmr9m(Bp*L>k#^Uj`$Z~)LF zex&k>rB=ZZWEl#=|1dq9<++f=b2qTAIwTJ z4$eYiK&x1N4T+-8+Ut$2cI_AMvh3?{4Gd7%mOEt<+tkM1D3|@-H*MYNR%`9|+I@fY zhZdMvWi5T(3Tw3rfnvmtR%@dMDo6F1P5r_bZGB6FZRs1d2ma{07MVz0RSHq7Y|-+s zeuLf-K8COc0#Y(=zu$UKQCX~(-yS}Zrn3m|;&=a@5U`%?Ed8duzJ z%+7{;?eyRg8_&b4Jg1OSNmJaIGfO7g;y?cHZmyy{;35Fyfs6vm5n(OBJ(bkF01z^8 zW)tg$YpvCk*tjp>15P4jAJ^0%*OOITW)sMCd-}*;l8aG+tO%EB=%QJ#TsYrq=R!-S zwLNWsAt8Dl+mn`F)pAx?$ zUV>KHXvv{bMnC;fuBh_*m6U$LtAe{z<3}d3NwX%}lBM&lyQj?xbE|OQCjf=!<2c33 z_cS)!KmV^ET5DsAWx#%3T;!Pm+>@1ITlcBonxB2yq7Of1g%g#`VHqCn*_4=~)R3=( zheLqx)LVv7>430dYGY+lgL)WujKQ9MYs>7liVnXO59eBA4e`KfBwlMCpk!5%^< z1kXMHoXwau%bs}R3F4u7xRVuq`>q`}ZrnJ#93`0&t_%(b?fL(B-dTD-_qoqG0RZno zw+=B#bVIqDD)Cn=O<01G+J(t}ynDk=^Hb(0K8QNggXa*NQ|%(5 zfcVa_M?$#wqw(y@@Y*lpJV!>He)KSjcN}y3d3k*2qBQG^wAlak65&}WM51Tnc6F@7 zmV|T2w~^q*i-dZq%lse|U>$PLRTeQlj-r5qtvNBYd7Bi1v@>x%4)7$2KIv;IY$`b93vUlKfpACTRNpe@RGH3 zN|P+U^LCp&b+%1ewwzKZ)Ug6ZD&ZsE3=U6w0TezQ?4!iZ&RDS6eD~aCWfLb^HgTiE z)1DD9CJkl`cTb96oH*)~rTm#N`&DUf@U9s@PTu#ky&e$rF~} zeWztlpJGL1hnBf4KvqjM1z70-C){8KUQlQx&4%=#fYO(K`Bw8%o3`}&71jbl3PZ%# z@t{#`qe>}((Gh4o=%;*k$M4;5Z}^7n(l6a(L)}5!`}|h25l^B2Ok6h-pwSOXNh9_( z(%x>vhjv?Ne}fG-bF-J8wWZgt5y2t3vfW~N)!4ZPS-6LZD zg@^ruG0Oy~AK1L!`rCS)wJ|DC@fLHc8|laPhY>Na;vKnV0q6<}>^SbWavPnTMkxcW zlA2H$#jTa>Gvvv!4t{UuhdRn@D6&}F)EDUx*geAra23Y~0Uk=+0Ri^W-Jt!VfJ7e} z$gFtK1|e^d0NG6K3t&-k@esmkG}vRMcnIXc=fFq!6<2x=uB7(zZMWTSFW{=*)`}pa zlO`Ko?EA?h*8K9bRs~NraI-NJ?g8VYUT^?S)i4>2A9?&oRvz!N9O@`{ZF|-(pS#>V z*|l5|mR2nEQ7rpu+>4$QT?TvDlKbzqr;Z=AVw5>0t#NDPfi;v%J-Ti$#$S(ZBn#`z zyKb=R>Y3IDn9C<&n~$DOBdfmS%P+s|cw+hT<+f$Z7TdOM8=faMZu?}sAvSH=lY0 z)Q^W|sk6R+3bb@LS>rDo-2KXO!#X&oFMZa+@IjjT(2Lv6OV&{zCBNb*)ZDW-@c{lH zy{C@-1X4ikKrS-(?D8N;>3dQd6QAfi%RKIz$awIiPYP+){umbAG_3vd@=3K61>O$@ zx|nc3$b5uLTI=90e^XuCRp*rDb(`A;h1AW^rJ;v zv6NbW75AwKPXS>MlEc*7ZgMz1gfJh`b-24sV?l7&qK=U5gl>070ON48o&US{xI`N< zEN_^1-FPnaNA9&@venL`6i^&@>VC!^Cl1leT^tu6rvEhJB?TUb0Ia|gZp4VyO`B-( z{%#v8%QPh%1Z&{hn#T1)T9J!=y|@Pj(6CNhwGco|L?oGJcfYa27AhW-ap{^dAexs% zS`AM>X02;qv&}p9*<_GcI@Z`_Qz&ILaB6he4v1?dV9WtPQb_@zLqZhpwtM{Uj>STTpXDQgt7U^{k7>M^FHY;a_jOE1X;GzY&84XS9W94|zf3>m`a zplB)Rywe`}*;BY^<%rwe3hhXyaYO%->Zx|im%dC{7Em8_CoeUfNcka6Wz0&e$J@PM z|GG602P-B}IUl!9>wW-0`&iLKlyVwsYqHFRmylqAb5qMlJWP&!EZy{bxZX}Z_O$tM zv(KA5mpP9o%3@(zzV`a-?1md|B&k=I4dUwR8hwkZzI{9GCKzcOLT#J)6gOklKvuCT z@?%;jciURBF)s;(Y-u%$l7VIvKWzZ0asV^}v9wjpSIfSmcIaU;ixVq8>zgww3fd?LNC4la}`)*scYL&Cle*W{H zw}S@{+6ynd;DD{(Wx@UCH@|5mB_$}pP#Q^zmujC#3it|87L^m?aUO!zE$>>=U;lkw z9iYXnE!>y#!U3&B<=&BM7d-_e_PnD$k>y^GI35*F`K-?@+N{-5N2Xee0`H#!^7wQH zl(*?ZtoL@!+0fo$2bZoxLWTB`;KRG#TpRt6{2}#zF;U=Mfvr|Hm)2cnCJ+cCSb&sJ zNxURM{k=3DBR&if=jvRsIC}^IdKj=Hf!+_0@=LwL3M;TGP#w?$tCiM+8zq9B9@;8N zl!opV_9cV+MEdT&O07*naRMjzPPau(@C9<#r5Wd5p^fE%#$wqc=bDZ?^WOm+ig>zHa4L-Qecb+15b5A%->#aj24O zInX}s_!-;w%*!@(02l9@*I4=&f7e-$bq+r+=Nle-(oPX`JpRhdt)AMeom;4P3jND* zul19;Q4S)%EvG4N7Et(_yh$NY`EIPKdw$6`^bG)_e=<0O0TT8i3KGtIhzNtNPh7IeMy?&BL?@(=AG7b z_<&t8caBqN2yUe#O{6nV!@ccx^2OI|XxkxMFmW13!c8a~vUKi|C&hQZ^Bp^I;DBYw z-*=>OYOCWuZEfqbtbF_jm}}K;4s#z#q{b`{mt~WP=AG=pr?dMPuq#-`Fg>f`L$vo?%Nc@br{RDMY% zi{4RK{irS|)$jYJKon(cxR>?i?H-(3J3dHr3mF~gJ^7}`%gADxH16QSexcAPk$>wBEwr$&}dzrg;N|+578rAH$7Clr4l4a2@KP+PSkT19*by9tbt6U{WAacd8o)L3IvZN!1l#% z5ry|y?RPD!tAjtA8tH+=hjtx_{Q{1DzX8_IIlK0$OLUXe!E@F9LU5)CM37zGYvxjjc>Mt%`Oj+E#ets2;_;N4D%`DNf9d78Wx^H z@F!<)U0OBpT`^ItX{6BqqE8%) z&k5-Dm5=NvnYp#JmfwA{LOO+AE&LA(h9ga|}tqNjK* z>9gbJ%}z;%mb;iq3P+Cilo7LIxOT&)cp-WS)RZhAD*AniRODRIkZ%*t8ygC z2z3>?f4U4h;P=U87%5ilZdmeI?Gf|HWUw&6Tjr^zAegLqU3N~2*QdMsL7;hQ`euR& z-0~4xulRFmlS|*-Tu8CSL^0` z4z0{Pk&`WY^8mpT%2fpNFxYmbx4#>|-QtSKjI+=$VcQ!Nm%8C_)rT4`A7~9D?7^Ihs;SC(A zAvo{b8=#{rBE;KU4VOrPObM?C{yfd+I{!P?@CGa4_UQ43U^xBp5(-yBs1`6E?etj3 znOg6X+jC%!dJjFxeBlel#WZ8KfxyP0l~;x;Yn3}mo*$2*_RYzI88+18;TyEkcA5=7 zpy{RwneLEMucv3p4kt z&Fhhf*GfD&8TUBPHKEYp*AUk)QQ~80*a-KhH8{QO>6}ZF3TG^L$3P%4by#jBdY`x_ z{R^u@H%O;mv?(Wqiwc>bfoAbQAP}kI=LBmci8h4qXN6bk7yZ1qwXuOcz zI4yO3*?{wrVy@UHV-HjJ4lbf@($A>xcd}H=A>)koO<8g_K~#Zr1aKc*oq7Xov_UQu zgz!r3D^N1C?bQeLH^-7utmkZ(Z&6m${W@7t{eM##9;)f1J*@-0IYuYnn;@XXQKXCc zS{Qw{4N=B$MHcPG-o-&W$)=?`r=wzVnJiAj=$e(amu*fOuKn<53=9n#JwBg(+E}{I zWs3%_L$`EPTBbh((bOm1JF3DIWvy*Ea$|&JSPCt`!-$VX1sY+C} zn9_Ww`)Z(H_{xv-kSeBUV|?HFTBH7v7?-QtInV499bK zfvxOeA5-mHhTNq07mlmlvsvtZX`X%sA)rpgElP!3D80zL;3-`D;NZ-)G2TUu4)lz3;Y;j;3%V-eX)|tvPv0MsG zX;3Ql8)h5DW<&HXJxp%EtVO^h7Q%#PBt~nV%(axB=6dTtxGdv`-l)0HA$*Ok<(~*w zZ4iJd$M^m~8-HJO0QWdP}{{o7;q;~q?a^<*%rO)0TRDnm4j;EopAUG>Ha80Ba$5b z=pbM; z=#`SWjQiw#C8!R)C4IzqZ{4jnCsdXJP1+azRtwG|1^UG>22soZQyvNBRc( zcf+6g!>=4FG8nr#O3HhQ{S(9{zYLWL?Uf&7aNK;y`#gQ_a+zM6Y_B!zW=QUJu&yD| ztAjqk@Pfbf942eRS=|7S0!VaLCQgqnY^P*8SZ6BCR3Y}4r6w7?IDRLmqIYs>X%uy; zBQB!Z*rMJ#HRfG?g0=Lky%ZdoQ5p7+!T8;>ZQvbP7JDteE4y0*GVfeZOx#ER;BYTR z@J-#dtE3u(a(9AnFA5(EZ7TJDb5-eyTp8hw5II`K+4NZ=DEMKRh&k=%c$t($k;of! z^0ZD=rie`kwTZb*Il!`$Y9N@X_6!Rr17o$gHSfJHE?1I&LjlvWZ9h?J2Jl`gX67fdTnO(%OmYt zUDtn&aDi3duD4ovoLnFkzWhE7uzy1C25)AOxJ|v5!|l3ip>eQwdWN-{5yVN&gzn_B zD>b5uG%T*E*-%wK_<6DAZ(CaT`3$@(LjqY7243B++nR8hmln?k;K-Um+UQ+Bi>pfR{p~p&o&rF>8Vr>>#@_KgJP0DH8yGKjlN!n#9S@yTkyST z`&*u4VEop4f36=!JK^?gAw)2TTFf4!3o(r6kfWsg$6c_A1_W*V0Eq~2Y`(~5)(^G6 zRO_awjel-|X5u87>U^&{b8904ST}ExHUmW0L`w}t=tkXu#Vu^a^uJ*Hl7Jvg~4eGv0S{+ZiG5zNytK1x${@II?x(%v}5#7bYW=s!?z zI$y=75e?;`fzWYrc)ewl&mGHqH@Gd)Xx$L+3S#L-RK|u9%dz?ugIKV8!)o67`31fDY zAY}VScV-7#ykowM6z19*k>4h|eFdXXDgQ`S5~64C6R;PPOMIhY){ew?K38hI@#7?% zpj+|Sm8)(hr|Q0{R)z5g(){St^uFgYn0Z1i!C7JR_AA_4xeOdVPHpyA^1BobN51LE zH40tWQwVdXrpa{%9P6)a#S?IwsFo_Y!QC#%I_t8W?^f}b`oFL#k+fpO-exmeBYuGv z(Yv=UO(ZjJ`gl7|v~KR&&jWT0RJ#4~Xnv4kb@msJ>cdaDa;f@!Z;4iJQ_IY$=D zh*-p3>lbkZ81H3m zWu`+TV`cG4bt>%|s|f7ptk|S7zx=I?Ef206LUrq=#phI7_JKn>VuL}=M6HloWvH<= znYuz%ee1jhDz;qpT8zotXb@{toLo9)61<&A-7ZTb?-GnWOuoL4WHi5|NI4-srw%k+ z@VoL5>_*{h7Tj|W0c=mOeFv6~eLfD=(*)T+9KE!FRK7vEZSsV0;slF&nM%YitU zLJ>#3=a~~zck5x~QieG2+Ffe$k%EKDV&}OylSYDpHbR)5F{Ofjwv}Hr28ZKwow_qaAMDl`h zueror*=HOfJ!l4V&3aRQjYJ*XdMb|Y31ITiVt^J>r=ch#BD-!+#(9-5+ixhaf{Jc$ z9JokWuJ&7(?WPc<+T0*Vkh_g)U&?IG?f&<&RlKfBX_sx7n}W#1oI3C#Ua|Kue4(i* zXdGL~%NtpbvJ3so0d}JA{Mhr+Nd}K5L*=459Q^Wd*0IIuN*>*5-M-;;ISOf1mYJJ{cnsST6=x@xfj82c zK4J&lU_Xw0k53=_BTcy=8+mgtciB^WQ(^cqc2VdNr1w#s_BbnxwWaoUuOig(l-fu& z>eF_fP<;LC>PiCZ#_E>6uja~K%KB|kI0!`@%~=Blt~ni5tG?1DPq0vrsTd8hZ?oZ$v6(Gve<4!0Y$ang~Vd=A{ewfXQLcgBxa40 zitJ9h#zaOF?TFmf>hx;OwPr=Na`erqN)pHj32UUbAb(9p|hv@XG z(OphU{zBBfAOW0~u>YJg?~-bqbQ?((g&a8qY8(By#YAS}nD11z?h zdbB*mwnZX$#sL3}!V7R8n3|Alv82i(PN6eGXx6{*gRt)po#)QdXJFNdD&tpw{`%!; zzf_385i4Av=0DRU_Ipb$Nntk={vd-sw4LkU>{WgnO&=eMRjE$nw`chvTbe-b8m`Gx z?rA_>(@G2@1Q>sI)Kd1^)9R~bP9dy&^8@jkF~eC-lUw)mKii|QqssOd3`s}*`>M1w z%n5Abp{PyK4*N{=iNAFP<}Mc)8D8;75WMEJuCTWv@iL5O{G8(QR2uA6R<9*|3kF1O zq=`iAosWr)7ppQ}$$#l$F~F|}|DI7(h*PG5t|OB`?mq=VT0;y$N?1SE*RvPhV8#1$ zvfXcAH0k4uG{IlN8EexOAbW~jk?+s#xMF`DB&!aLlvjjxCSvOem7F>n!ocTnNR%W* zwWBi(`G)$&H0DF4ZT1J+1;TGHUx1_Zm^-58ReUD1^WDKSc2gark{|%(^%`*o&mdtI zB~oTZ~;f;jl+j;~Nb|r}V;{rdZu|opbB17GKnxE?YG(jFWjY z@|?Qa^qorq$)R9Z0*5;G=O+{51k+JN{lg4|2g*bk6BftB9;Ok0BuXpMu8t2r$AHru zjHdSo94FHw4uxBig0-hk@e~G;dlH-MAo_05 zt+Lv)QiJFInLwWg^0#{XqOaRK4SW&xwX*7Vahd^8@ZX(a^TXNF+SdEz)^ae4z;R$X zLKzNNO}yY|%bI0>7}Dj(NolTok7lY~kWP>(n675YX(5qg`9BOO6^xdj)YrX13_x{o z1M;7gvuhBwbo7k%HVz#WG#;G}zj*4HNLhKE6fegr<2dZ!xpZsIm?Myk<8sxKG*WYTS3ki6HR_v$72z9y6*Fnsk$m*2Pzq2m~XIBi(Fu&%=D(AFJ2rDi|`2QYODBx zpcBoi23gH&iF7rBOd%8voOM}qB>VvpEs-r&jhecl20316DIK@9qe{;?#8*`-)jcD*1R@MF|(GHkI;| zUoYd?TJ6T6JIcypYcfUO>32F^B*vl_iWPS*oZeHMo>SWKrt{p4riTSEpX!rZ%tZ^G z$_Ge_h@2)TJE{xo5fxc9+Q?b>niTvNm8cgVK=K>%vPESF44&e$?Yag8IllFv4C>zN zJAjCSQspi0S)%&p8;>w&GWyuU#jtakcb%liMeQC_x7nhW6@LN#9{vDJEFtDMgqgE5 zpuXbA>gprWw_dLnlYig~)SC$?ASd98g1~2kc|(&9>7Bw2AqF0>(Mkr48PMwKPIu!AwU zsZ9x*t?pSVAd}GW4Vnn%tuj%;JV_(WJbp-25~65=Sll+SIH9)O_9|U4Syyp>-kz7@l9EKV;c+8i3JSMt zX-9wpq*||EZSkyfK71zlqh1ufC&G_OYW-p{@xg6?Y4b5Qt0E=(5V)PgBNY!@OWSJL zT27a6t&omF5;z9of{PPUW%>4XbptYrCDNSa34z}R?4vO8s&yT|CxYt+jxAyKg-n2?! zK|!Av_Q!$YXR$PD#nJR^q2$HGk|F0quQ2f|jw)dZ_3wh7L0PbYe3VivD-~}XZIr!0 zMB+N*7xEtEi7gmwwB;dJl5LTvREdm~n1WxBlmn23MXKPA#yzS-$)g-QApD7L+n~v} z(~S~xewj=1eBEoYAEbo1O8Jo?dcVwfIlLBAG<+uU==7Qfsy|nk7);Ty6#XU5%&1<8 z5=ah=%RNWOuiECx(kAO8o2%=*x?OT#lCxw)7$tvd5(mTWWzv>TWL@VWb-ad!(mb&i z=Fc~M$^L3J>?}vL)7;;L)8j8ui2 z7w>!G9@@JhuaB4O_M+K@T8MQ~ZRwgT&XQAb%Tdriwt|eO%$1QUi33nP8Kyz3xT!v) zQhwmF)_;;I0s2m{uPI4od*beRrzeg2fMeK5zXh(jPEokcy3~TK0WKolFAh7p4um&<07Z*me zxn|8L7ZXf%F(dP%M&kIein<7YGX#1Ri!XI~Q#|3XZ7omtui(0`d32j^eO6rQUdBip zIR)`Tqc}n0Uhdxm`AiP( zgTIN=b}PWaAgwP7zOV8N@N3O#Z97~2hlJ+vxeX)e2;d}TKUq?fRVMi_$ppBdrp&Xw za{Z)p#P{7b$cg)Mux*NAmUJU`6;$4*b%G?Oaxu!y`4cHd-KMz7Y0E_!e!-20m`H-* z!zM-zLzPoZNmdr_8Vxh(+B${wbe!TLprTM$m_TasQykuyU?vQzGEOpcYUstC6k1HG zyH6I4rm~v?A4q2pO=yw^C4@8{IVeJJ9b;riAS1Fqi`y%smN&SVV@Ux?r&;AZ34pAD zCz1!}lDg?f&YV_dqyftPq2&XtBQ!42<8SLj|G-~IvO65ePNfx0To9IwsW zI1U}S8z56sQTg>!ca!;+e_#S^4p`~|R^#%TxcMs89WF}{YWqrdxZRy$((G($b58T(x`hBbZhX{Moa&0i_@0h7WTz)WMR`= zWLhOj>BN_U5J|gl}e|(nwCCfd(TZBTmyC^ zQC~8RUnZ9k6NxIBk&T9zqu^8p@p4t=FlYVXf^M4>pnwx_pl~jbCJ50&qIooZm$7wo~<<+N_dR9gZ4r-~{4SXZ{X zlC`;Nsn+mLJt$b5J8i`yPx^i@S%!mr+dIF+M>4OusR@KkJhX_*6lj)|mw$L!wIAOR z57lET7{6kEzenP?pL~?rT{!}6gaPJ1f&#uDs@Qb?(beFJ@YuMLRg3_5F{Y#JUH$Q- z+;?WwP_Z=v6E6Jny?9OO=ZJgms?}f$6o|0okaZIYwN-h3_gWfo_WGlTnc4hWKHYYf zT1qSl_G@%A`wrru!F#{%@{e{jgrY7aC%Uz=!`-PL7_>4#XH)bQ{O7Q{K5Dq}`sKX1 z10!+}+uTFp0_-$_ZP=#+A`iHT^s zVk!zjS|1$#@17tD5S;;VPUhrKTAHQDrdKKdOLP50MB2Oi~KJgKWQG`AHzWe${dAICKJ=(Ppu*(Nzlui`|i1*1x#g7>l znb2dpU>iC1qvHG^fk+f8C23X^l1RltR>}o#%P2k6ErZk+xvNaKjHtE|Sk3ru_q)1(zbZCV$K z^AbhP^q7B9oY$?@wD;=gSwmTp1?HCg+w&FJA#`pi2YY@4eo16^ zjaF;1<%lWz-D^*JerN2R1&H`E+5hHMQ^fB1(w5Vb(@VX>b;$uFu6vM@;A}PCSL)g1 z+dJ9_juxb+@0KM+iazQ%t$^PVt{B&S<`p)sadT%xif-L)-JzsKJ0j*KfN^Cs{YG5Q zZQW3ChhaE@6#m&2V?1SYPFX6ntNrv z6?>ZWzneO?6qQ-7_VBjzErXW?!Y^%kebMaHQ9dydtw&s6$oMcV5#nyV-P08Q0=39{R-{el`XvrQ|0ivRZLx_A1!XJid41jZc<&1D8nG}N@{kbmVp2$ z=G`Br+5>e73ewC;Xujry$r0sFviGqf(Oi*XzZ^;>rj+idKzTi{&PFg2Rtzdmj9R@O4X&hH9&*&S`=FZDw#Rz+ zU3DV7C4HOms~19E*L`Zgy+={#FBCkk=7#dFgr6}r!441ZU4c)R8UIh2Ct_}C>C~mk ze_Q_+z-&-@Xp_X$yUNjcQ_}2*1Bo-tM2HMijjLcES_g2L%4tFbHl|j%`V5J2Ryl2S}Ud8qkCytCZ$aQ_E zJ3-Pk#o#1%(54y$b!;@?>=0VVw~^nOLMfNe(SuEiwLjVE!StClZUF#IU39+WZ9-z{ z0E|<7nu*5y+mo2swQsVyHavoOX|OU06E|)gch$O%m{bY4Ml7+JG8%)_?cMXSbQP6} z$PDc!8H7BZQljol_f1dqyd9Y-9Ly~PhUT)5Nq+KeNRD~!n*+;khIwLe%plipw=Ao= zoH%riHsHh*S~FiZzdFYW=Z1uZq*$$y?+l38ZxM81cy?*3-K*ber#D52Qd_@!z37_j z(=xJuBSREE9@aLi;0v0PYkO6~veCy|+i>tDu(R(!ZsrUSiezq*mKUNb6r=zR%+{>G zQ7gl0Un^624=9RHi0^gm77h@4T?Q_c{DM+#j!$oKZ=BiO?{qLi&e6{r3ZJ~ zPS#K6eDeJyQjOi?;cixkq^*z~-TsdRfk`UPb$^&wPqD)T1! z_)^?Z2l~ze5ap&vDdCZh@T~-j z*XR7M!vG2uGGVTT^7u$v6$;;y8L1G{F25r#T&Ey7zg|O6O>Wq=!sZ}euyDj$ zqZ(Kq{Lp0e!BbJi*u&2@i3c`jQ>|mw3U$zul8CCU0-O+==q+xXzj&e}w~SefvWLWuqNd39<_@4CNoC8+}aM2=>>6X;stc@ za03=Gqs#>tH)4OlIFl|uo2$!jqr7gX!-Ad)xa(;)3Ypu$`fUi1ldJ;>%>ib^^%I4j zk~!db9f@$G-iJE`{k_In?dB))F*$`KHJ7M8bk&=GJSbS5#gYy~-r&+Np4oS&n|#qy z%mKD5+tr#!^Wr=`g_#OlNg>;Xe|Gi11FCKs_v{W9E5LH8t&P%KUXO4n`eZo3R%evH zAvm|4$!t)hQ??H4Zb97==6eli@;mu^l zxhb7?*6jY4@JVJo#vaR=8HBF*Lk23Auq{G--8@DwI+Ko*IZ_loV9=cH19wFcCVCoe zOVVf2;JZBf1IMJByFGrE*X1~rz@V04M`bv33YzM?Z=2IhLc4??ca3VZ>8axXUD7n* zV;J>ycoN59#&J*;=_#mZ)}5X0<(+!misa&j?9eJtwRb=3hUl^+wl%)Yi0IDh_knB} z*WT{x>T0baDy`bweE2E8iqmrZGUUW@+7L;5UT3wc-T&=VU+oXO=s%<$12a+L@wThw z!xtP^rn!y9>7uyeF0@`)*_X%S#?(U!0I!Mmn`J4y`y;1CRwwt>z0M6?8GuKE* z7KEz%FR!&sq zynyX6b;5dznY3`cc-Bct=lXeqK%r+E<6h^?&G+!|yqJKKN6Qk{CzU@xKUb#4QJN(j(XlT+ z;--9$D%Sqm?62PQ!^6jn@KprOtUt41cuJ4qZAk9=P=LGOH|8QA2`E94a65zxU4UVT zI!ifXmpQfE-08!_&Zuqa1&0^WE2SQdM^M;b*)IL`a_WqV4hu+<|0PAu(en{1L<(KR zeVe;R(R!A-h3>yJ2j+x78g(3I$30>GBip8QLDS#74s$_Gi+`o~XhIv-QEWwcw|>j> z?ca(I%8&Ny+sh|@m#KT&SP9ls)>Hjdv+*XqglIh{2vbx1UqJx45GJ9nZ#h1sietlf zp+{lAsan_Z%S|Cl@YeS?56DJOJ8c$T>}1jWE#h+Gc?aNka>UBN3;xPCuoe?s;Z>M zoA0D8u(Bb%kFP6|Haw=t*H;dscGOT`)ijoCA~`f(Fw6^OP%HXnsA_db@j;wsSLna5 zU!_2F4GEfC5Yd9`IIA=`AFOd$4c((#EI~}tCAlIvVw;vK#ETEc9+VoY<75#Kx!>g2 zpV=*2hUa`{FLrf;q7vdT4IS$m!34bPCLwHqfocCttRaOC?|#TVg0%Z(Lo~e}+37l* z>Qc5?(0W9A@NT885wi9wfF#gZsLwvS~&Hjar*)YBNkb8?ey=Ak<8IgW-jet4>&PZ z-n*FipDWY4_M?bBB%}&JS+Ab)afZ{1nfai83h*&4r(x1F-xIG2pLcB=iO@#JUf(m3 z>=wYNL$2&QId_RaSXng>Z^N$J+Dio$19tlo5Jk^CO?2M)w*mzKh&=`SZPZ=dbUt#x zZaPQDh7|A`gUf2mVytz~a(ZN!kT+fMruD^~z-g~r2Y*%g=i_WUUgF2m5Od+Ab3td*y}h&_fZa+(fd;w;x~Z&bZ#v z!ym;HbK=C`&Svsb9?$C+oPlfjTIGalMVS#Z;(Y)e#&$*5D@5($b$YwE58Jbv& z-<(KxdjcGgkAu)UVm(#!UNGywS85}>6F1mC2|B&Moyd6>d(20`scW#Cgs{6cFXvzO zyXTdGOgh3|S9*}P{CZ{Y&KrFw*(A-sBa0C{@9hW$$|wjk*|--;oApaLnLTiUW>jPk z38HVm!5_DSu0OTRZW!2SFQ{U>^^LC}2mJ`e}D+w`y2PTXJ#QtYQ8(&Wk#|5_POv}Sv*MlX#(A&7asBkF(+ z-IyjHDv)*9;P!Z15UdzdSN1g%mX8^804)%46|;PY({6iTc>|3gxHA-Zn=czgLl$X> z%ZGk0_G-+OOg%eX?6#@7=Iz%p&7_H_^G7p>llEraJNrIEs$=_tJ87Gy3sjBGZNq&`X=ky4N!= z$g--+y>AO5dnjWnn7lDmT;`#SVTVwee1}o@QJiS~5s6X$fC01a(cq@#0LQ9=9TLbI zSjF<-V%p|HaoG0C{kPQra6{nNCUkCM4;`Pzcin#<#i1Hrn{2#ubHp((HZ@Cv0*y8 z!Bt{g*7)$nZ}E-bc*zfaY{W`b64ov5)jhCnBw9rOpHy)}&hj11zHBA3gm*%hOnCy@ z)Bp{39W0K}?l6QfMR(IO%!qf;R_#rIXahb_MBWbB278GlY3J7=GNd29;GVv>)UNmhN z?oTRb%hVJFu+G4zR-~9wDz&ZLzQFWiyN#C{J|i6b55HnNuHnZCS$2rjYEWIzlGBZXRi8~q=#_U_hC}VMs z@?_Xr8?Z>QVQdLKcg)Y0)oomYY6Nu~K~WJwKAQ_xY9XgA@vj7dHsB|^f|*d>!awQv zIRd}|S~t6iQcHwCi@{>LTLyf(Ffg!Je&3S*8cYtA2viomuwR>9Hm2|}=;HvFl6h@f z$m}EHj&tPW8UJA0JxY`J;oTZ=aP7i*k~qH{%?IX4``5^)lKU#l{Gf7G znA3k4?RGK9Hze0irP2eFqX;PTe$(PrF|cj|BHVHc+$(YY3egQ*z359}Svyc`ca$Q0gS;?@?cSiTzM8j2?_7{|XFJO;N!(#B-Sg~cv z=b3a(L26mJ!KAbly|7+e1!6wDJgIsmbN=rHfeJH)3ByO(yh(D2PgYgcPgjG0{`cSe z!&4OoYr%}#apg=;wDeUK*BQX>ZUIXiC@ArM$rF4)D&+0EN@X-EO$k^25qtH7{r20fVG&Oz%2sSp>6}FTM};+_ z&BLO4dSf0FO*81+cWr*%T>ipOgSi2`0L(;+7#(mA*)uPCs6?#`NbB}=4{`$#co6Z! z5`{|bHjN3WFIG5@vWTnZ`ba?Gr6lWo@9|vAFH9xtGGj7#fpZ}vVZ_%9khlw(0*x6M z!AlA~a9ytiG|VQ3dT|Ym)!TBSspH`8H)6QG88O4TS%la&btb@ckxvA6`v)dZ{P5Z` zg81IHtbY(Ll(IWa6RC;pn%5kQ6hXESo9(Y}Mp8VbgS@e5T2Td7Go+F}1y$Khv}swv zVcT$a)%82(v{NK*ACN`vgL0j$rXzd%yUl|`XKjZcBoCtx{! zU2n3u8R@j$iOfx=P{i&wG z=_Yaes2&>bnM+9P1HV>rh^DBfrX+H0lRT8(jB9}ph`M>B1?*Yr1 z|3tLp9%;|dl~=GL#ZA>>xR?YjXb=0`JbMgfs1kOR-mgZEhDYjMB;9YOLMRy!y;}u8 z*fz_iC`Wyj@9l0jnEQJ!a}&aI>3HlM?k{cVej8!roXAf{vx}JInj+wOb%#ImG$q!> zUu_vbjdn+VzEOBd^);{NO>^0%i^i$P;HK>+l$8M+#zw|K8$@1}r-m5QA^~Y68ciJ) zW7XO_y(XI)Lc0s#>6w6$OLceeD;(W!Qw@FI0RdI-JQo7fAUd{zot@9)+AE=(hWn;i z`}5{orRV$43LE~@>8lno3Fi(-m1KQmj?K{N_D_zrrsi3L^}BR+L$Bh=&XeGf1!_og z*f9u3?tM}_`A2u{j;x-2&JA1MCWj_<-lw%#55IvSJ=e$!$xhB5NENX#UQaY=KWzhr zI)>+&jiy7#Xa@fGHinKj#>~cJ+G=TV1P85?@4S?VNX56&c4J7*tDx-m^9xajUMg<`+M59W{uIF8qp5Heoh39rDkX)F83R8hUEWv(CzYcpr|BVSeVU2WnC7UzV zq=RvzQLxJ@Xw8pXaY0@G;sG17Ljtu}RurG2wAEL#!QY=<9TlSFXxwYArlc7d z4|+OTR0MT_Ej&|YTCIP3^XR-s`&)w04M%R-wF^mJ>`Yi1T%i0|wEB_4P(vOLUV*1u z%tg(z?xaDq_?_rsTThw>rmUtX?^J11fDLNbR_!>eXlvysC3Qp6bH2+QHS9N*bOr{n zRJ9gEp7of7KA4B-NP1{;_SBE+yilYXv{;VCH->SZFhf$OgiVmE3fFL#9=s)xv zImuU@lx;40W5R=mq*mT9i1wJ~H?S(RXbf}`RI>+z-e66S2KbgzP{wvJ?hzMrT zLtGPwLEDWC-J;ic$q4C1;RzurI<&Ie)YRlZGF3wczMO~Q2etOC^K&45AIae8Gq~0c zw;tel8?0j|TaWAkOhiUr5tO_9u-?CM!6EMl&Y%e_P*u%F22M0wjtD_PcBgVe_V9rv zEt@L*ms6?gjEVZvUSM<2>-D37{UwDL99DB`_$)SIZUBrnpc7i?264PKY)Iv~J=Xg2 z;hcfNBssp{Qg{{4w)f|Thc3}$mmSUveE-i;Cad!D%V~*4?D+A*=}hyB<}KU#n;gEg z&$p`fz#qJ27EeHWCDJHoAfQ*u!h9(+V3RM?aciRq8FMpQ@E`7(xH%Vc`rUHIOlk;WnWri~uD^ZhKW`P2gt`fD~=uKJE9PxQ@>E0MNJ z2)-KOennXDoql4xnVZ#eZOx$);5j2O)#lC1OK+go*OF(VK)Ndvr~f((URWwRt4!s zC7nyLiSbRA#UT$I6U29RdraG}?QW$ZLB&Eln{g7I%2-5^7$>8lk z5?6OHG)gOvjZRIq7$2R^=O@4UL|I`|7)o|N_JHBb%fmdx@WlT!`G;mK346M^gmM?} zt|r+l4QeI@*xI&`bZUWESA2Qi;C@V?qZOtR_!teDtbAm$?hJP}ADwM0t686di5O(@ zJRuR&0PwEUzbYZtUM((h*x;fXhsMFi?7`OgZCZkXfYdk}XRYPq6OP2BDfn=zNj}f! zc{Fr3DA1nSn!If>Vd)=Q0-i1XTsnzFs`UJw{r&trkAHD)b7RcwhG*+>5u{@-o`Sg_ zt$4+NWbpWN-M28Pog^-_mAr|+4A~}TX{Bk2V-2t9^M8u-|IF5c0OadDl-!+FPkAds z(sstgT+71y=jOMS;uT#qr1J_{7g@Gz_#4pHo$T>>BE}-te-kt@GYlFtipJq@47nnz zXmZ8g0t0IraJM!*zU5z`P2=;wB}kKQ&U!_uN512W9H7<814)*4*NCI}S|Fu>0PWkE zKPf2=s@2^fO2!s|x&a^DceE5@5-{S%H1Zmv3a3-$7!7WCy!vZ0{5B&jg#?-3W?)Ev zC0ER-XlNU=uwXR=Zza{xARbi}BvBL+faDCO!Zk3B^u%qxU{SbD~AR2^qLTyzk`OpHp=|{v2aT6)HdDHfMM?b-hER zt^JYLrAN^WVOSRBNK4Uft^klCg;>y&_hve)q{OSRJh(6D-1cO0RGNk#pfWXIAOCdV zhN^*@f}p98px;g+X;CwPDp+VU1Ry?28xjbg$nDl^6;gx*v#O2 zj_@TX*SA-!x!bP96-IV89YaL(QQClKF4<11f(aZB?gWQl)JYQ6DgHajCDW!*QxVod z1slx===q5jGI-s>WeNs#$&2b_4;Zk637P^7f=+aL)^qrMYYvP~6sZA9y8oV5LqutF;}%k%^HzR22V+c_h?+e~C3_-I(7n z&V)@_9J3IGvVL0CGLo1JBcE6S4L&TM)ty-woP3`2+Ix`V0f1{MolQl9D=}*z{Wse) zP*v0OpTX2@6hqVO3GP5Hd@>{?N!zz61`Xy)G)WW5Dhs{XSUl~}`;44#vQIO!h3?6< zyrSVq#Ro01c#KSLOqDL~H zhiKtYt`8|F8)$OgKD-dq$d0c`l*|8$`M-@zlK6NADf$o0@DnH zf5g(7i@}~(!d0Xt_+ld4?Isd%lnRL#q4Gc1M|G~Aa`m2ILHA?5(4_E)-GmeEL=m~N z3Ym|@FmPX$2o0DV*0J#I;X6l_Kk4A&VdA3pi>-m;oC+^^SdJ{>XZ zRG|WR4KXtW;*pwH@qnx@>4L0ZaRhpxCsBNvag8XLjTSXYFeV=6Lq!vp)qDmaYAOvG*JMdQAX}f!vikqA^_Ed>wOhMz2o41*xYHuV zX({ed3W4GTx8f4qT?++@6ff@X5?qQFcMa|Yg1de^`#pP~_l$4<%NXlV)>ya9dCf~A zsDuX`ikZq9u{fewIXMQfj&D;XeI&D|QvsS;mrIbFqIcZf?So$e5`tF|>Sv!VWf_ak zJKt~NJY5tzw`Rmibr3QdzQ~+Mw~ceC`X)b~;g#xWjovV0^PfK9RP||HICqt^fkD zHE2CMlIBx}7utO4WDv5bAQUBXnM5(yz5?He3kLmCt>HTK_S@z)dRNi2u*Cb5Xa~RL zWM15TYf=@y2d|bR<5@_lXtybole4&O$n-?>oE(ALh%;H&ybFOtwoa$dL5}Z|w2Mok z`0K3~Y-IJ_yKoPL%$e0o9TaA>mB`S6bCy!+<#^&|tG!aw`ni2z@zdqhjU$))nft2Ll}7cJN-FwlP!HH@$O~k7}{P zHfPs7{Yk;6r9gQ|bMeRQm+6G(-M(4ryn{jTN2|KK5qf!cZ^~;qx&P-vsmv(H`Rn6F zeV+4S2fHzv<$Heg`Krz%=HK8@$z1c7tS!85M-F3y?Ek0JaEl_BKL0z8y(;VSy^#-q zMM1 zdO}Rh+C2|71$PtAA^0XMj$Y9l;f*H{{4xKzih^uJvJ2H5QHHy$LU=T=E^Dp|5-(0YPXyPlQ3^*Cax$LCO z#2vXHVU)pH<}?3zCu%zyBDeKQbJpdZ1>!SK1I`!&Uhf+DJ`m#Kq25 zmedih4X}<=xamcEOUoi`U_?xgX57L#M7=6&!iW#GilCLp~68@hS_WuBgKal_FY461xsHB8AiG~K zrar_!bsLD7E4k3@yIR*Cd?f#hWO;^Z`-s}4jh>~>le{kpm% zL{k7>nR01U+s{ewx3nf_+d;-fLy{dikP>a|?v_5>b_>+5^NyD*`aPh5WJl+YKewIn z1g|-C7?NHth{n$6d|V2Vplx$<<}B@q`iNqP`Xk=uEmDHViTh5P znM8DezC%Bq3x2$QkpMP1Hb+UCWH{k`9HWXNkxgZCGO4Y8=4InfdY4Om|6$WU+pN5z zJ_@f?B>7%(XVc>l=jdEUwqY3*Y`~ZOqW+}Kna$*kQ55*irdad z^Yq-Z)b_eC6t%Zn9Ve-?L(<-Qq5X@?nXtoqc3D@gxn|~;xoRA7P(7g&VNI0T{BqA7 zH?k}fl^@K;qh_iNg@`w-_y^enQGnuEVwSt|0@L+@@D0Cp!N(Z))}SxqOI%_U)$^Oa zCmwt(^K~w1ve5T?f4Wv$9%aSAFwz7PocvK z-v=w&Ss|Ad@2j;ACMP~w8LSe`r{h!0| zA9(mbE7i;&IkdjM)#gG(c=zOUo+-IB4k~Mh+>cMc=5ao~vxVM0*XAQo1eS`1>uJ>bckB$PX8>uEk?DMwF>8&=2=(>ED_8g9M%%ecn=zw z{ekkeC}^&Y0Or?3PF-nqtheT^O>R-14^HVs$yJ!7Ly*6@1tn8a?Kc zuBuC{;I9&y6(E#<+3RfBN7aYV;WagwWWE4i?^kWE0fqdQ{Yi&2eerG)-M0M!I!Rk} zNM_rzwyNf*sjTHMshc>ZzB6S83PR16EbuFUC&sxO4%1qdnZ2VH{war6^d$!^z3a>C z59nuD)uook->0)3?$dm4B4JeejtDMp=K)&uQ*MyNRJexa#s0UFI);^4(uv=UZA(~t0x`rk|L ze?VqlPFz|KhXu%4Zyl~Vg;lOtR7b+g7+hq`z!43fgfiXs04uNA&TIVDu~&pKjo^~# z8Kk()pdHoy1)tOIzwItUNPi?+SB5UpG1kI6aNJvOknahT1vHnP8cXkkxD3OI1^!~b zdTmg0J}rh0SJ&5e;X$!3w0TI&M*fic4NRSn?8)zr+l5AkqIq&UuR3=&jOygWWKV22y*atP8PRvPl+H zRvAGZ5^6LKwO*=38`1qI(Gz`Uu6J?ftU%xrnFJ;(~7bXcX!EE)bqr0+HE^ZZ5l3R`#5Jr6ur?$QI*LH^T;`uFQZ zm&CZaD074?8U|0d6;JR?6>9lU7#JegtAV)ZHZ3P#)T18jeeZZi-*FkwUke|1AVXac zTFvJZvTOZ5wh{&kqTpG&Gy?0GLzz}L%!h;^p12X2+q(^_r@1`>z|)~?`~HN={9)La z`32#NDHZM(hX(KCUhUG7G)2kAh3heZOy|-ODH@C$@vL+yfFYf>L(jQrYtqzSC|Y;4 zT}JTe*Q0Sqvb7$3M2FJM#eLQ`&|1}Yfhtcwj)e$s?x0x4=Pt0`qC(prFDhVH?N8qI zB!m^8>&;tM1F)a%Hf^bMm479M{@0?fjdwLTKhyzaDaK^+;*AXE!{^)7H{Gu89da+E zc9hZ$-vB=qP{%)Kj}tH_pqf?!M1I37W;@pfr$d@pXw+3J22%OktCd71z=^`eWBt2X zH~2AwD649jf(N6ZvskfOS}ZzmEYSqyrp!$N5wOTe{yOSddii5qhvr4j5E=Vx_EyZa zV|<{rv#9|K0O~D`Sa<*VhPwn_2~wWx-g>@%%0 z`Y~VV6bXc*#m9E5ZRM7i@aN$^$MYz~$RTOh(M+4i&pPv&H*F_d>9A_O{sxMZe448= z(j{dMtre{}EfVsV^VcfkF2PMBnqzsd>P990k}id~O^x;@ZA|pVy52R15%>BtQKK63 zmv(g2c`ZsAT8mswC^g1{Fm7TCzSKtT8Tfxig4e8*5xuLz97RdRlehX^ zmf)JI{P0l(f7PR&c0p3sbb06~aM05SE0syXlmzg5k}X+vduaf3W9@Uld+EnO_p(!q z^kddloQ=&3AePwI)XmYJs`QPy5IFMKwdjo|6R?{?)({1B!F=E6y~nQFIw>aBrepQY~^VZl^2wBW~H6 zOkm7o%+tnccV{9`j_eh;w$A|-=#kH9AH~%%2VPsXAZoP^4cr`*5!CYOS^JINc?RJV z%+Zq2iG~tP20hl9^yb+ZaL-)M&OF7l2%vrmMc-iqYTZXjTVAbwSWmgYQdU}Y1mW(FUO>v)=LXDmEGlMLN);GH!JE70hzcGoQ1^a+K6-rq^k|{VW2#LHnegoM$-1Hp~WM1 zEgitb=k)>0j@p`g{3}uLYw4k{K&@|BdD$^gnd;krLd?^+G2^hLgH0qV`eK%4zBe>A zEzq@DzAIIbLXrk4Y<&e;c2|=Pe~lUOL0IWKPU|AcBZ(eKkwUN!;7tQe6}wm#ib6b# zc7e0(U=vWJWWfShByJPw_AKM61y&}LOv>ML8vf`{FD5&2$+q%L(&%Gi?$yFQ@!OE@ z&XZq`_|)oh#~<#@t<5NwO3+k<$LLEa%AF3U{$lTc;o_uOTr$3ZcUXm4$V0S6q!smv zl-RH+l=*uKhH_QgZg#jAOD)aukct9q+1r!iZ-oYQk+Ax=-BZC~fj&?6%!A`COiBlC zw6PH{N%em><-dj$|CFpFH0Wr42tLTWV*d3G5FhoPsR1*Ky7AHH-6poxD;v=2M<;H2 zGB6#IY`l;Xg~M&GQ??a9d!4rb`pd;V9n;s+Y68HrtaIM8CRG=cdL~kFQ3^ex9|RhN z263*YMdW7mJD3l+X*&Hk(k(F==sJYG^;AgTyUGEcB_Oun4;JceC zhXTPn0%=kD+E4YK%toM}yTHzROVse*4`-!?c7GaS|J&=yMTQwO;i=oF6{qFIl>)$0 zKRHMbt3`p(!;)`(DbI}dE2dStSv^A)TfG-7 zY&3#z03_)_-h{Ts+KY>ekmlzwMQa%Jm1W^ju)%XXFw8m(O><;#y?>2cBx8*MpI(pX zu*dSd(~Cn15SGmNFCSe#19`mZ?A)Cw{vLT<6vNVaNR@Tw=uf7a@gO}9N?KqAHf;$m zQoRb@beUkFf7QUg!o>i$#!XzDdSn)grFyBx8fCCafdFs6eS8b;phOkG4WnAZgZ32# zf9^p3*Hp{aZh(!gH7NjD|8r_YFAW;W934Y&Vr|Nx& z#ay?BuwjpELE!HgC>B5l!OOXqoSOhFtImm+sqQEm0lofL6{hEX0lr!lobyq?z!>wn zgRQtQ7Kz$ZBjIQ%F10HAvQ<vtcpwpc0nBntLrlh60Z= zycAQu+!>q%dzfH*FkIT=msjmXBWTCBLWdhcuBbk{J>FJUcw{+>bnR##g zw3E~gwxsO}qHVW-{)U=fprvq|$pEeuT6r?RAMo@(=;zNHRIl9 zecJ_V;-V>Vu(AT^5+=cX)F0?y;Uk-UvzrsOFp($w&;%%=Z3cG2s+5+VfxTH>FxZ*g z4*UQdk~-mFDXJ3u7}BA+1$MP^jdCVRnE%tO2CK5laCQP+5Pin_5%(Q}M;Y3D6C=4y zZro_!`Gld5RRa$mMGT|u(wE|}FSF9QiJ+Y`wb{8;bef_nn}bp$Yg#^r>w%IaV+BFj z$1+@v-$lNY@Y^1en-5pjxhd#D4_L`Q{!|o;i*VN{+n8>#5dLqUbc7!rKa~6V$@7oL zBvj{THYhHHz?NH8opOkp?|Tf2ioGK`1JXBty`rN5tLhj)k>ERK;nMYStTCAwW`)1N zKfhzNLDK&UM!}$z^U`=Guz}=a0UH-&+}hby^XZ=JjE0|g7J9s!!sF2mn6wwVU+%M0 zTDW7xI98`%o=7Co0<73BXaS8VW>A~l(mR>z(pKtk(~WKs@NjKh-_zpK>%Z`Na&6W> zBUfA zdpwAD)S`qgc|{Y?##V6Hp#~|;MKzhlw4ES8c?)d`-nu$E)1qz5ZP?b9*?hb2l|C!8 z*eX?bcMO(Pig_~T{~d+=`y7An1Ajmuqp0dR{7<{QFJ&aJ4wtM%?&e2zC>oy8@IT4r zn5w~`k+jFXjK_YuvRPf%v<$*RurShMJhS@3GbS&K?wMs&G<52q!{v2t_m5LQV_c+< z8T~XJfQ7v~hBRa?T(y15k69|B_M+KUlXD~;E97m08wRu%QmT3Ka*)}& zUP&cb+lC~|>-cA?td8OMFR-#PkN3gGc_AC{rdkKXc|HKENG?&QkCfTcePuMQoK^7{2_~}{*LP#$dZU%?|`WhSnyjJT(mc! zQaKfYDjJUh7cVM<7@DLMdp!pW`FJ0Pv|z}7WMKfiE;-Mx4lcKeKL|dYPr5wb?07a% zO&=*6``Nnoa4=^Nm)aYZ5@jv~HdO^YX41VFv6e7gFme{S(o%6PAy|7gUk%6%0#<)c zr@Ong9=UIH*=McSaP&^AGpK>hu9(@2Khai&$7?_PpGo%_1}Tcilt5LmP(R1WT+CH% zRAzArIA9QL@!IjSp5Xd>@hC3|kMx`J{MVC!3;k^+Y!3$o#i_1ouZJHb)sdZvAaQ=IQL$x0xlDT_Sb&bA-!m<=ayWwOWZGDQi} z9X%sLgR(#YUQE?Nmn#gHGKV=@Gi4d3lBo!y&Bt}+7Md$hXSGIO-7jaYUMCsif9{o#7KiFac&_=5W6gS=aDC+WbN|u;+3wpOPC*E)+(d?u zy*|c)Xd&+?4J3Xq{3MWWBxOINI=e`ZtTy#i>AO5lja*h7IeWDWz~T^grQ~U*(E3$*$Rb#EA>|V z6yd5!Ejj4&Vhs8Rzu;2YZ5rizrP-B=sSjWPFZ(=vdnj;(@}4LL-5})+5^U%sqV4|& zc%^#d8l(~B{j)K;{r>=3!z7-6*DQp1d5OX@{UmNI6-^)!2j&4IZ}A$Zo+S;N~k zXPBWyQLlQ@&~Meps?JB$HUJ>{p%FClwiIvG{r(yxon=qFWfb2XYv0LTx&mQd@qy9H zCz<=6GEmh_yBV-Cvbku%qVdf(Xl05gIyZp3SIdqWTs&O!=X;Z7%|)@*gJn+UMY?DH zJxJE$X8#a9{`VeurVl#)_5WPH1^Y4Zs*uA_Ju$wL7XDMF^R1SK)k#U<$lpI-C5+M{ z3dMmzyHYV}!nvXYcahy7z6}d&+q$r;K(2?Q@W+X za5!aw^(5I2#I()ZdG7 zXIq3JDX+7814Vl#$wh%2 zwO;APT9@b6o_Yxq0~FE)=i4spDjK~jrn10noH%64rr0AHfxFy93`){oCy}KzaI=xT zB&iT>{(~(XtFp@MCDZtq=sfJf@aiuTG2afH??SCg^dXiwU(ttA2RNIi2pmJJ$<@J& z>fe?GJRoz?Sx}cQ5A+JxY}5Ni?9n^&hUiMM!9?~N#M}#gr!^p~&NDHBls{~)z5+J( zO#(8$rLMxBc}sg`Z`z)H^zU_Hm%tl+1E0K0u|(Gk@z5{)U2`m>#(AW%P{cZ&@k@Rx zE`gVUDMjf(w>`ZSw?H;eVd}VfhSY?Y75Bxqv&Zty>yM?!EW@oLo9;v|TxYwb{hOn! zWVD=To;SjU7{GQt&r|$X>>4peOi+6so~vyfru4j zF3VOB#$Ja~1m!1lrneS7^mvNd{7&UKt2gII>Cii2mndf^s0MCjgS!0}wDg2N>DSj5 z=8$SGW_4J^?1}-79}CD8B^d2BK~1fTzYR;4z%kB5 zWmeU0wPQ2b7i%^B<5du+SZmBZaQ7S5;GQb?jqk%>TkFS`8^8Xw$lc8cF2FX+&R*r? zcSe7ljU2bU)@Ljl^Lv2z+YXLM53_{mj!fD$b?($` z-7h>958gkE?uoG#_t@#p1SX*LoA!A#cC@CGE7W zx%{Ai+s{Jh*I@gjqejns5K-TL|0fZ0RgS17Iv*DJqHD&C>4!xf4A7hSJYO^5Y2SeP z&W|MED~MzZ1C&Azj2w8E9^_uf_%41mOIlDNE>~@FAq*wXC9k^FgY#K7*g!Vl$Anwv z6V4qU`&n(x2bWTCcgH)2E~nCGc4}(w@!_rv3U#4gR7f&`3~Mw$J>DT+S`NcQ;NUQO z+pJtjN-`N_^iaa(;;)4cnf208cq+^M93jUHuz$!aUW`cdO!!yyB;$Oa!_zK(o&u4c zQ&#KAHreI6Ec*>wEVt$6-pwtUZDlg%24V(g;~a_z3f-`lzImyAyI^$*bSKz{7G@5S z!n6>9^(zM{)J=bkCOqj;ITzZ${0H>0gYsocm9<9(xGIZ;zg;ojHao`09l2v*Fcc3q ziwlsusl2R&x(e@eE?iu8u8@R7C$lA(>no?D!;JbSi$5|sd5|C9bT?E}PW(8p!JV4; z+wK`bT5^7V-qPgIw0G@ZpcUYPHcK*6Q<-*r)tKh`mpCBKhTfbjca5B~%cYCH_7ST+ zW8Y}b#ZA={{&W-`?9?2pUEZ`*p5c;)SZV5dEu_i)?eSCAYholiY0NqUBHfscOKRsd zC2rXE*K`YX+{3C%s7p#PGR5i(^(X~HV$C_5)b3{-P0) zT=QJ45b86YsV%N0So~*vZFcROl33obwrSJ3Z`(^b9&-4sRKnO4t z=mM;ZiDObjh}?IHxIO;q0B2rvb)*UsuGsfc9PRpvR&-tzwncd#82M0{$oPVvqI}NZ zT}6}gmtZj~QK1a@UNx_{Q}{kz`BLef8X;K@-uP{WVdWMLJs5THx>?}kZ!YxgjMnwP zD`6oLg-xyuw01A<&bB`9mQAM^LVuqh>}wFyuE0wM`BZcL>rNuU++VwDWWY`33_|vF z4>%+G@;}IIgn`kXmiRk4@rlxg7Tq*Z^JTu3^=&QYqCgp>7Es)kvQ7TedoE>{_}}14 zpCff|unEg|N3ry?HKRHGP7lKUqq+VK?6w{KH5ukY<~PRa4OpZ@UU)C4{od^O%+|DI zpuJ`%?IyT?A<$!a{L}wV6fvH7^#@5mf>tvHOUgL%7n;6Ae!E@LvQ%qdb+KI&P$c!U zr&A{2_s4u0>OGQ%>&JHE%RP#-NI2~$Wm0pMb}p){g+!e{9#dzjBj^F#k}BtW2UTc*7eqp3T~H(tm+wY=QwX^mK@ z>lOjXqHP zk{4I5K3i)}ioz{9MMBp)7Jy%?;M>V>Q6WMZG>Conxo-sz9luLB{b$ZKBY^N{qiy*$ zImS$&+3Ou);tPXAa*?B{b|c45q~0UX<Vmu0sqiYCH1KDPOj9crm)nj-B&sv+r}L~0oo1ualN<1Wg%X38 zhP684*#O}9WlR=f3DS=Qc`U+pruWm|D*ToQ&MvndP0wY zUlmLpMNNorD`N-B(AjykK{mh2G@%P(pQX&|%Z_wxYx0Mov=32%NsfvYksko};>M9J ztW=EeA9nZQYrW=G!A%iUDh6H51HT1ha3v(DGAF1G9-w%BOMV0JhF>`dQG~E|onfPH z)$waslY6-Le#(=v)w9ubs-SouxuBuDv_TZPuyKjLmd%;jkdaFE0Jy@WJtZT}3?W@M zTdHTYugIRk>6)%462Mp9r^q|Y%G5nx9zTr z>TxUz=|Z54sr9m3XkKa(>q6_;Mu?~thd`n#-` zZR1vg|6rS4rqKBDY9QW%3pX#lT(Z-m^Y{(7{BKH+T^eru%SIF@YKhzM0q-Nvm6~Vd zkY5P+>o*7yl=wj@lNLhMbbpjr?AI~+eHB27~<<$EFv z+LN02hWYX1f3Npn+H%jw< zHffyQyiK_R18g|U)9Nm>!&Sx zC8xV4yCdO{%#Q5tS2a$Lwm7!L%?rPlqa)hfD>=F@82mxiCVI(o^dxa{BlH4#+%zwm znb8(=5y7-DKHSSS8$JE7jX;-kC&B64qxX$e%&Q-MwQObm-dCh)$C+RHe6oGF@J>(U z^I#RZSliW2C_4?9uy$0*XtcnKplBtu)Fzi7kj!euvrua>S;{;gzHT7TAuUghax9{8 zt3sSl*L+vnkQ*m?XN-rmz9zj$obXc>E`Ut41jq<(`tod-@q-|H86Nt0F)`wgd{>68 z-SXPdNG*cc26NlZ)^;FF(ISn#8OvW%#^6+40WAT zoC?LG;)OU%!J|LPt35>rxXis8xfqk*`XNIb&(eVu?jH2T0%Bfw`g$rMJZx91Bb8Fs z6YoI8&)PNxr=(aE)t%TS-z`yc$>TQ8!Y|2$GFGJnJYsiA#cvQ15~Z~ktxU#c8HfD+ zFu|pSQfLNC@kyugz*^gD=|XtS!LE+%Zfz#tl#I{i1-y^$E70)Qx+b>>171H1d0&1# zw!m6U%iDdw;Jk{~$ZkQCGyA_PT54FlXoZRjc!1`Dn!bTB*$E?qqieIMh{*< zr*!KOOF1x-Fevy5Y#m^3R^L*JaZ(34?<=@3i*`QE8AZ9LWix**R*2DwCup#ye=OOK z+-`v5!~!Zh>fx~uhXy-Jzuh_8L$*+r=9fjg0?5R8*qzZ~qJI?TWvyamyklkR%wN#8 zXNzn~YUppHO7D-zTPHL+uj$H8x>(v0RDajR zx6m-Dq?KBGAq^K)AUeN{s%a|;9sNcIRz6_1BvqqLCSf?57e*p;nSB)2oBiN$t0_`) zXvbfPkF=Y+L$Tt#3=>h?9BTWH6B_x`ooI|&Cu;=hI+L0HiT|xRb!d`5)zMS<0Ojm& zQ57%;i|m9wACj8m@{6m0RC=6c3EtSt2l^3O?PHQnsg%epzT#$5#{#hx^y)JN#TGhS zTRr=pTp%@Pyj2$pxZ8>n^wl<<)RPDTbXpT;P$vaD6g0|?Xp)WS7W+<>EGHx(3+ z5%eRwtbHfC4VYstph+}VPHZXbtxEJT~#~1TTHx z&`-_H*-r!feAG9}$c2H&p~hT1Fg4_7CSpgR-?aB-(Wuu<+Ojl1%l0Kw`tx61q_1V~ z2U7Kc?~ouXkC!4j!cBjKQC1sw+U_72YcjOX$^6cwwHqD`^&QXlhjB`tkAGaBy3}%H zv_3$R9-h0DwD>nJdFW#TFBB=!MfLCsDHO4i@_kF(i^wtM80wP`oj9xN$7nQvs z3YY@6%YyZu!-Li}XQTQCR%&H+PY-xcyS0xuM8dYNUYiny6>n?u^g#N}0!@fcJe}Up z!&PCg2u!8if1O`+xL&e4tj~4OTlWrGCf-I?P}9!hPITZd^gNdwH z-(rK>DD3D%6e$YD`!?oX<_XjdRi14!sHa<=J9_stdp}!m?;OwOc*l}}PQ&I)k6YgS z1sT(k0D9WL9)%o6eO`m)n8RUe{-ZxMZ==ob8V?%lDeD?gVo|wO9|&G z4v~EMG&pX49M4x^O|=<#8C$Ue?+#xNFUnH(fizX8OT5KQ@ccNoR=>hso`ZjL)=**; z7}HUfYFvafPVKYD&S~;Ea!SzzZ=f-}bMxokBy-p^RM81lJj}U}vosr^R!1i&cI#bMhqBZ|6Ohgj(*G$e# zpP23B=Zt>uf-tlWiVIK2+9-p;lIaN6&1*PG*^3Z z^o3tzdnx+7j*zU&*=j~}Xd}viS z?%xj#>2<_e>y{T<@2t;etf9_#YMsfD+LB0el#Xp0R@Qf$3Msm{f7w2FmVg{Fl2^3a z7X9JeQ(y3E%-MT5OWoqu=?_ux<b?oQ zQHVo|)ZW#i6y=UC4A01;^DoPx9mQtXGiPtV{wT=$C%AFW=kj>;KogSgNNn4~f`B6& z7)0D5^g~I)FiW1J%HOdC{wYY3wZ8+yoC)D&e1HtX+z~8HXAC=?az$w;j|*6d^+Y$w ze}OfYA-w;9oqbzT%9RMWwvqrhKO^d?@%p9Wv!igW`8Yr^^;z4CtmgkU?`4Dc&7gko z#!(8l@1tOE>@c9?**QcHn(ceW?Uq#;$W9G)JojG|gPkjmlFv^;O-2GlN6R_^mXbrXWFK668t z$3VhPCC!$>ht}!z?`b9KDcNo#J|_(4=N{U|KtfH9n2*M@6`ay7WUBwfMqxetGmYFR z?wzko)Uk?M{czKza`bi5E}!-D)<33Y|ICszwFB`Cx2ytZIz#7z-56FgHJnFDvq0rXsp--=0Wn8S zjDcEtCkSTrtgwO&R)iGh%#^JS@5vXpwc0ns!95apTgw`F59%w>t^ zaFR^N+Ty`KF_*Q{c79AigWOLUdFxC4BAEM5vo#&Y!!OXp-tjK?HFVvvq>I37!_q*o zw{s^YZH3-oS>q^tT$kTA_g_Sr7sA0)GK6~jO*}%Pcp-4A487>!wW_=}AUd2#-y}-tkvI#Se2_!cO18=|Md^7C@_e}9 zK~Td-wL01#Zlf+$($P;!6shTC;{Z63T_t}-G8XXe-e|rJ=9#F!nyhDT@a@V#rh}9@Yd^l$IH5> zGpo73<#k6sSIuftg6lCK=LHK`=KWbu#%KUzNbcaB%Dr zNlj~Bah3IZ7?j?O&~mBq^4^hsPQm9iTDR%=V+ybPTSsXoq(#xM#~!M6`rfDD6ppfq zygYxs0MqzUDesR(0f8epv>7+TYrJ_>eYk(3&UZ4aV+duo?w@(W&VJ7*hh>jAZmeH{ z%{9rSnTE47mnem{{cVAwB!9&|un1Tile=x#hHb#MR~iGK&G2FG#jXStv|SOL+h8uT(8HnjI{79#OZ|61YFB^M7l3@)~9i4 zFEkm4->vvf=$60niS(Sw__P-iB7djy`HJ(k_Td_SyJX2NOD5|rK980_rVV=e?TRhd zUN$+)>2TDUX@=P-tNR~Qh1C74P)hNIssB}78{r0x8bfYgU$5+1??d-6IP&Gvkvh7o z-W?&`>`UjR3BZ>KbI&`IRe*lzxF2M!b;fN%t4?q zm9%mMp;TZl|M!O|#;=z{V^+7g< zfiHaY{x2{AQkQkN1)GjhG&tN_4{@UcRHnl`^jXC-4Iy&oIB;df-gK<8ypnl6{iZ2W zZTN3&p&fT5(&MDmK8KJ(XmNXo%e#Zy#?b%~Xp6IgpQN zq?P5iCnl5-t#w8bg0O%s6@XdN3qcIPX$-qicH8b)6KHjyy zO`yeSymX(X=0$1H%#FaMb$z{_d&E(x)T;%)ANd(MeLtat?7GJ8n%C{$ZU{oqEI3z{ zzWgL2dMFH>2_k*9pEXB%KI*A|RoL!l}m;aNSM?s-VUq4cgJJ-LV>IIf|+ zY<64M{g<5oRj4p||AZ@>(zm~oZ5dxC?q9VsrVDr*OhK2`JmDwLRMr{-8uBRoq8FEy z4ZfaRSp0A5L%B8%wqdq(HkOH7_^2m5x;?~b#(mXg@0&ZgZD8hPGq#colQy%@`EQ~; zGjw@uh*w?FqSh-dzxE@RubVFRA&4e$f_{=wczN3dA1yim|n17Ts zVHWDo)E00$j zFY4!hluJx31V311Q3yATx*+CEub%q*tX;Kp%Cws&)C@B`chc(TYeq$|E4_!nMa;#) zH^VpN-u=U}|6+X{zBJ$1c7|G5C3x?a3mo`UJ$AtNBR@tS=-45D;+dLPpt`S;OXK|` z>~$4__---?AyPKKc#6R$T41hdJD^b+U+m;Ks4lobY zMZkFGtwXh0>+@Es_rprdKAKe6dW@S!QgwEd3{-wem-Xm`d4|I_SX1PTHZs$8Xs3?AqO#OVlKacpBqQ*E4V|7W9`C167!-p)0$(atDSq* zWleb3a^!a5C^~NXeZE6cHl-#1#Ynx1i1vtB11T6pu{kLh8}k1V^-kfDwNbZr%#TC+ncreC9Lf826~G&C}>*g|({7 zE5=#SbLhal`h!7LIdG0zu765WC_ITtlq+8!m}F2CCg-Ha1_uT+!dXeDG`-lh0|SC| zg|$7-n(t&QyGwu@JHg>An4&$DMj~qY(>{EA^0Hn0Ag&U7GuP*;xRbjBr|Y6-p!tW} zA!?h>Q=qG7Y>aAUb!@{g1~w}t5i;)|1po`Oux#g@Q=WaEHvw1$2rn7y=_xL=QJ}?j zePVy{?R!H!ckl7&U$kAd*fWzez`|z<`xf)@aT@RRzZ#|94ksdy%p_T4lg*#B?a4O% zn5M|USNj*`4wpAm+Ga!#cZ233k_0TM(*fGBd%%%-%uvrnhX-+M(xsu|6#Ng!YqugW_EXmB5}&D$*HvJ=s6DU>UE8 z-1|_2U#orIzTsBq$*`UFsM!l6C3ceI+`DAJh*xU;z6`q^MR99;F$nz-i~OE@Amvr| zJQOD4Mh~LGB>qaX>V9nx5yO+l$xnue)he=>aIyV=FzhBpj8>{)IIEG&{6< zm!11LPT+st5>hEjhhdX!vYGlv)^jU1zuvm4wznQ+7@&?^>OMe+*e4!OO(Iz(>l z_Wl6m+^c-m$Atns_w~9I5j(oLcXc(LL4yvkiS9w&Za&}`s>>9B_f}oz4=3v?Yl*AS zW_$2#L;!Y%*@q>gP^(C%`X}_hcJ+!|?V9QMQcom8dJS2|ty$P$HHLw;%+IqFg4R>j5Vn{Y&u! zPxoG>w5zMuwhw4RHy0}}8~borZ*?sHZtnt|O#CJO^qn{|lT`90S(H|QP>9>AFDZly zDsr9zgPJVgL14_8@S*PJf|@Erg#!@*(ZB#OiNi8}PF7+?B_W%{IA(T}oZq>XrJZ#$ z4rNDfV{R#u)T|i-Qk0SV_se@*&nA*WCc_~Ab^Rz~pQ=TKRxDS&vPjAMzlgG9>Z)4rJfeFpmnf3{TDEc(A>U;PBfWqfXYj}Q3CIAlFm5A?{p~l91xbwE8nd!? zBL;4p74gIk_1f6J>pZ% z8)BVwjEdc!-Kq)B+PHnU6-|1#9hr^I-L*qOg1m$zJww->5+UJASQPB;NfD>K)kx{1 z@t2D|^=@uhiQb0YyK}nY0sBd_2LVD`qLN9%T-#4iSs?sbM1m@#8?SG#0 zhWNPNVq=(xJN18L#(qKE@wKLW0Z)ZuK8#s%ruv7B|KjdYxWWa%Y-^_nM|Jug$`G>N zo%U_Jgq@dJ&QH==p41z?xcS_;Jq45r{YoKjql0G%M$d+>VEqv^;_IMm0#$Q8Q*drG z_V|h@#E@_$l~86i@spr|76FgwwqfSpSTnBdNVF$s*dVXmfPjHqQ|aJlw-sMD#J2-U zgYB^$^Vdp@_5TH*{%7VJ0JoICcfDV&`**&b5w-Yc~o);IURTP`am^As;D_UJr zfe{`y$a}=6809PlPwfTX!zVg53M0DT=r$$-L9UN6%R5U>RXYOK35Rsd*Xvz0My)9_ zQIBE0)i8*%sYqM*x~j|$84EZOG1GD~FS7MI=fdT#TtC!p4{)O#)4$R}AQex78QN!4 zb1gKZeEqHGuGga{%U$Cd`9%bH`|GwO?^JKAk{pY#Z~MAs5U4*4Pn<^izR9)So8sQx zbXaP*s%BQw?0e3B*ymD3(J=Ugom>#<3Ogl*i<_lkA1rp0=nzEJG<+u3Ut1!}Z$O!S z?;kaZU8B2u++hV6&2AacmHj4*Fw0x$$@_kaHu0v_`K8)2{YCM;+h;bT~2rw5n*?$%?cs|Sr8+V1yi|`9~6nAeut%Rx}7k~R1cy~K@TV6C3Q7KcuAw} z{jjNBTp62IRJUv%_YirCOx;_zk9N0L<^mAE9%SFDSaa=84*$fRE0n|%9{Jwv_U=`;1=X?L@3}ZX z8+KW=u^6IXbUVc}T`8{G=CbIXHM@_n-4Pl5lyWhQE*#SR@GxF|v_raDSaHE$g{N*h zCo$brP2RXcz{qp?hHcQ&>ppq%z<&aRJmRrsd0>Ge-_oK+FP6RF%Lbn~ayCX& zYqEz#Iw^2sc>Cr=MeFSw(CPPjBJXci+x3ve_jWa~Jg&BUHMS#{e1^6GlUSx~5dG7A zgv)*1G2GjC;r{g0s;Z*m`VGSBanrjNQe}k&p;#~e2g$(?L4@0?n4431FeSIxf)^7H zyD!JTUAD(xvh;8BHQWNXnbWQBMPTIt)x13j4%9saix5)~>)oK<+}pqMezPWsRAoYi zNB{9fxaZBNnyaHR>27eP&S^TMZP=j;58l-tlB@(=R7f4lwg8I=4)0YA{zH0RCuAKk ze?H$YKYK5FnHD}rHoXH@TvH!6FLRi?+$Up7rHVE?zA_nYZ1(JWe?pc*LBi|X5haAY zl5&J*XYU8@lVi=<5%r-E4F}lTWZ^d3j5p9$Y3brDV9PF9c=4E{WbAZyl9`0fJDh1fTO8xM_#p+N%dLG`ovqh4~?>VJb0xz%| z^ndvwB-q%nKlQ)ulH(c8W!5=-C#&aC|3?YIISZ;_q3_9aR&|PJ#^;n#Y*hkbF*k$9aAXg;x8M#(WPF9tKA|DgKtSNRz2Sau9mp z)>*UAM*9~BK3douEfx~sjU_v?_%okqTP;YttRNCmY{c;A-6mE(cO3b&m@nqiq`fAt zsnnNg4>a}I-iZsO;>{ub(Rv(VmW@{7-@Y+ z2@Xj+z7zuqx!c0QBcU3%vD)wDn+~EjUc6G&jUJdKoaDva?$jSz z(vIKq%x>D#y>Y*B-j@>%r+G^w2wqp|mdQa7wfr*okNp<#|UU&l|?S-yF%ExE-ikSA#`Q`NF!b zu*mbte#?bGcQ%QjqKdHoyC7L@!Cx5mgMa9Ck5{a25pjo%1Az?WPH0 zI$fQn@!a(jMy%-M7GMxSfR%0@atOFS&Qmur`LYbiqU85CCs&W^D0jE{RUo(q< ziuiy$!MuPHu)L_=3ibJBumZu#X88hBHPq$QL-jwZ_t4!gI)t{4!a(#m>3I7 z%TUy+>NrM$ZorD`$xN0Ja8p}r8>_i0nprs!1f{qz)$MDkrkcjYl!+X^@#n_V(83qJ z*Hh^L&3DT5iXfCoIVt%fbjJV6DsJ*Izj!X(QRyA(Z&2G&%^^;4K{~Nb?*|dL!yt)o zE+(yRAI?(f;X%JF&+3|CE&p?QECIiXY5}X@H6^(uadzK*Ib|&oZjv46DI(FeC|r>v zK1IZ$#+IaZRNCYIWQ#_Mi>Pry-DIF1PTBAU#%0TKbz8bqEYc;PfqwgCt!E^Dd?wk+ zP9K%_qkTiocMwykDuK!dwpO?~fb2&k2X2q`ZqL+9(vLtZy!%E;N4NOuJrSMw z(tm6*_)tV?EG5|_789-Yn=;nJ4@;G-O!1g!yq$RBj%FxnzlZlOBH(S^Xh&ufkE~LQmITNOlGsM z6&^&l`h5$Qswjr0rKJw-*|O+xzc8u+HatWr=3!_`VD+(BTleCGHJqlfp_x)(%3)WOth;z9meIw*rM0}zzoI2;w8L8K4=te=u8j^vVC^>YcV4h7cB%X_gPf#qURcgzv&J)!K9ix~gb(CNcGhUs^R7Gs z$3Iv9$5h+hZK`($E{gwy$R)TKFr|q9zSt6nXj9p}6n9$Xao$1rb}d*Tlg#NfFaIf8~9-z6Uw`HY2!WuTn&kBQaAgQWPM@QFeJc5Mf@cX{A-RKmfL{lj90~tL3 zE11I^TgtfUka{B2m`kaP=@>6o)%UOd6T{~X1O7~!_|p)UzRP7;4BzMGRrD8b{hN2>X;tVbA`$w}ro1#=_SSdk>}Y!@HD(4H5|;E7 zthIwBfOvGaaqxvD*oNmd*^bXlyJfho`TAD|yA4!wkb`N3Ds8s8s%D8=SSAp&nm|qO z-;*jU?6WUw2%)c4>?b zy-fhimR92te`(hghYnlQ@^ns0-N8AZJFeZewN%dMD%z#CyexiPZ3WA{kuBl~C_!7o z94T)FO=y15bL6+`0+%)13;5(y8CPZ5?fs6`sSgH+xH`|nYjw}2-{os{aQZ{AA1eVC z9C!d z2n&Vw$b9*21u^$B|0(Mo7KwXu86|7=iWkC;Ue|5|3NlwwJzD!mzq+19#bCGs9{Py? zwT=K*@TCWlrMyQ^d}P_an5c~8(D$I845FDy$0{uqhJ!S+M7F|{gL6z;(U6>EGvC*2 zT8wnss;nId_0x9_gmV04|4|rZLSR!H8~ZX-NQKqR&9(&j=%#~P$Q;|<;(n;~3@?O9D+mW-4%dRt zsee=y;fV*2MYm^{I07WAMljnJ&f`Gw(bpt{64+Qr;;rw7|=Kw@&WlNALHJP${y@$9*sEEjs$cuj#h` z+E?nXew)R>YHhn#MpSF|#CMn0ojSgU+x!j{t|0;6`LtS{onlU@@uK!qku98Bl5>R7 z3(}0#2iwig72OF)Sfm_4*?T*Gk_Jq08zw%|S^<7XO{Zx*zW23|_2!#(6`YUiQFrow7x=7IFo2l5EG8;+GuIz}M@baGq+!qe9jGKD9e9S75AlD_T8{xL zBEPxo7e*JUb9G&1+k+L1Vox5UNye8s>-z5gJ7#QI3kmb^^L1eMcKo?o%$3)0jON2& zE#WTZhC+1O;S-<07v*LUc}L@lTtk z{v>)89Y`E#4oCk}0v+lGa=jc(0(&eW&`BTUC}fiX50Cg%7u`a{yksPY>DPOyaK*v@ zDTJs4_8AS%6sS!8MRKM1IQ2IwL8BHc8BGFr9sj&waNG%2EZFKiOE`buF?M@` zMbvL7T7mdyC0+a!?9$m(07T$94*gI@gc> z_v-#)r!zku{sVeqbzedM2PYo@-@Ofm%_D`J5O|*x&}8uY`$dtg@A&}rxLrpEtm|Q! z`;T0UzBLDP#A}&qk{zz!KMcSU)}5ZDx`8HJhM<+b1R;dTKW++N8nNf*0Raq_O%c6ov~N)3<?4anj*rgU?PmF$7m*q=Gi z{42-(*8F~Q@HroH|Bshm#YHFS7hQSuf+`PzLt13>5u~@;Ytvi+)ZG`|)D%A%GNakl z{BvS%dKHc#!Wjd6Uw!mz{UpZG1L0=b=Y8vVJ04C0kbl#Xa-m)_>G|)n`rkR$n=J~SH?sR! z+y|fJWc+Za`H(E(XF~?ntRqPt##%kPe^i--C)YQNAXNNQ+7A}~kLOV30lW9ZH-dvJ z+yDeI$uz>^pPNe!>440}&p};ZbV%yVz$UvDYuw+D%dQ^zB}m8`w-2#tPOblxaFJG_ zAYN9R`FYyR8E)kJ)wz)gnM06Vw5QYX)&4D84rn)PiSH@S+lvP9)#39SHJqNFUA*ht ze}`^jA4Di0^{S@~eqsOvn&9kSPCf%ocjVTZxoeI2aNYr@Ze*W(b5yV*T=B@A5OYxW8(L;_8E-VPW# zdb@V(p&@!6Qfs+#oc=v~8*hIoqqMq$@*0m8Yn;v2Z(7pd>sZVFKFu5z%_~`r<3VX; zQtmm`Z1RstM$0iX9E+=aMTt5NSr~al+U%V22kr~gk#WA!@z^v- zS`TRMdN}y{iks?QAI_Z>GcUggz~%u_f1XEoemy*Yew01D zHBOVMxvsUh>c6fot?#DJ_d`Z*yQGeSXu2Tva_mDZ@<}RTIdwkE=FJ)D;p=*Xw*Y|Zc9}S_^YGo zWr}#v$V&eX19uuuZ;J>Ud1Zg7vGwJ=@c*!$6^vRXb?T&Y!%DM<(vEmnaapl#*tUG#0w>hyc4OD)YczURT@FQ_-e>Pxyl$ug3fzJ)-FCrOaP%jiGjILl#) zNpZ%|loDB3OIUYfq0&}@UH`b=fU+H9N;-}8)7yUI1WD%0Kt_f;up@IYBb*2PI+McE zerApPeyfagTCm@PP7#OPLcN)@*uX(6q5$$8CK!dUl!*hXd_@uey)+T6Brr=~smkbT z7c(xSYsA}PEP@>WyV&AhDEY9~i}JqtyZ%iu~4Eihm>i2X=?Dx|2Vzx^$r8bat{=UA%R zV1~i>rWF0Or{uF}-})h*&1FpfI9ooq$p86vdrdiGe9^5OhLMRMosOyJu^UMN*585m zfQG3odZYh#iWShgCukg*H>I8vo6~Wb3R1n(=kyQUh&-s74MWI|0av@nKlGCs@hSv! z7xnxe0)LYRQv>BR0DcgZ7H#4KD5MgG0a#TVKYGvp+zs#S>eo<;0koZ3u|7!W5Y-eF z%}vS%A^ECE5PhWgyp`NSV}7i3dAC4zZ#rfYAx^`8L;NXEAGr}$rxYs4>&|M&j|On` z)xDuJ{V!b2M9C>3>fH@%A(#&@4gdJ0B}lX|rNa zftg#|zS-H7@Pz~9FUA4oTvn^>EBR7j-q(bZDQinAnqVfRr<-rR9dhJuba41z;-U7$ zXDfq$MQ@oe8`9?p>hV~8J5M0sfRllTGyfY4$i2eLFP#-NK61#FbYethJuUgYqCLh_XH+4yVW`&iU5quk%|a z{;<9<$8A%)l{L=!ju=_UfToWQE1Tujy1@CL+NZI*MO~xUj|YEzgqTE;DP0*rnOe;a zW&2k9u;2Cn-mf@9A-sBEeiQH~@){+gxM}|*?(o@h`nhOBldUXU=l4@*B01TRx1~Um zJKzt0kiRXt;LTGcrnN}I`v$DP9mkeiGW3ov1XQ-w=8{3v zRa6}nCoK6!ck)jT>0j^DoT~-ZVed&67F0V{iTLV6>uxp5QBc*j}C*Bp9?V$lSF1N|9M=eE5`ZhVmi}?xQi@;NW?MKTen2cDB ztx$>XwKpN~&Sp4F)DJ=|p0(zluxlhwSd*pOKHfD+xa<^=UG4G=gyMHTgo-Oa>i@-b zRd$a^zPr^4eOp}$qknJXG&&0i;;NvuNP$=$n^s#E|0$v&NVtG%U-Jj3uIrlm-!dj{ znn0WiT5wi=9z8wLian(~z2_++PU zbEKrpmI-}d;(xE9H-EF(TKRVXRs&^wlbaf(z$s~<$0_Wr#*X_>)x~4ldvi~wVeP73 z%}U+$`YLLm>f7E#p0p26d9pNBWtotLLj&yP$%*6Pc)HU?$vkS{ouUBqJ4TW`R6cj< zhiM00O;yC7dJAo{`Q)RWcg)_@zn41$;Tx?7GqkbQN>E%92T$Ac-EcL=NrjY3>dwxM zl5=l<(LXU^QNo9_ZI+|23w%MARjm zO8zY7Xrh6IKJ5e+OP8qhFN?0ztBh@-Yx-LDkgzzmI7w;!Nd0=h9JA7u2VR6*4Io;1 zX(6!L)Aa1sTtY(ojLAQXOT@fADoh=IeMgO7vJ ze4;hmys@bJoDq0mYd@~7r;gX$Eb`P{C=tm!&iZ+FC<@x-9-UKuF!Jzh*|i^t30xLXGWDk#4Q{T+yK$#lgjwg^bty1M7RS&UH*$qfZ? zYjEC_UFCIhva+#MKLHS(lAffzV63KQyDQVWc<-)T!RhE-oRB&JcRK`u&?^upP@YnICKkQ2_Q7y4i?+))ETyU|omwPWpiLFnbcMThMbd|$ zehn1H144Ql8fQy=KDs6g;@Fx)n)UMEe<3;vKd!>@0Sb#aRH&infOf0z5GWBIfj8|@ zZLHuJnMG1XRDa4lTWZx&QBuW~lYe;3hNiPG(vW;b7;M1`QcKliMq&oZc#@&EzFu_L zUaV{U0hgyHVk~vxRtlski6cp{e7mtsoqVKqvhciB+G1Ay_*$s$o^?XoM8orZVEJET zT?DPW%*i5Ed)?pr-m_VsN71Mow`^Hk*S0Ml)K892Hu^sw`8PB8-}BoaUuf^C<3{B^6gTWi zLZ@+@^RaXAt|`gk?+)WMUyXO1Ye*3m+`|L6{LmZ_pV$KE5e2vy=At~|R!YA&UkCHI>hvX+cl#uhe8Oc?m*r}cZU$g4TK5FMVAsc?Uo z@S&Dr>ZYhC5ZBE4-?yq!{MQ#3-KzS#;KKIZf6{K_1mKAy((t8mMidxCnWitUCV2~p z0&a!It81E39VY`#7{L;tz>)?tet{Lc!Ub zwNW^tf}4V&0gNMB>4;}#^LU%K$XB(_UP_{EwKNk;OiLlPAk_FuMlox^dQJBGM;;9@ zv!6S(1Nw0hTO-955eOtnB2Ym-FCBKkpw#FZgDD8}8~!*gLn zgqzXB%tK1lr`6%kLsXyQp{|!_4P->ZEPT+tZ%6(gM*w(D|1n?r05!7?>V8Uyx-b0I z8}i62O~UN`lKv&5I6`-bDA3V8p~@#tAnxN0o|2X|bQ8Z9+3+8BZ%Y^K+~r|+#P_`; zJm^-rztX_7Y8jCIr9?uC)kz<#lXlB>H5}R#4PjM0@fy%SUY2-S#nCX%`j+FR2%HhU zVy5`+b&!I8=a6L1nhGZLNE8-&3=QUo-s^lB{{iDxRc*JGy1p+EBv%oblv8*1*3|>( zT?C0MAqC1mw{>rd5y?mB;V1`=4U%lrxHiL)*`BAW>zx$iL+C(cYN10I;N+irf!&p) z>p7iE-qS%yFVMZ(1+EVkI+LoRT$|Dp)l>?*TGO(zv6W$MoH!pVw*^SXnQnc?U$!}0 zds+y5Ap*`bU8i#Zhqe|vIQcJ6Q|{e+n9Z!SN!xG}l@=3)Ld?`}Ib&8ls~h%17^#c0 zzc0G}`GN3`8@=pi=R*<8Yu2!yzagiS+70X*B!)VBT7DyB9LEK|;T?yj(8g@puDGC? zCF91S@J_GH%=$0i2a)>k;&HfOpHzh2TMkY7QU;rSK`di zFc`@DUYKiRO$H;;Yj%L4^{_~1sux@W*TQ;=Fop{RmG1tm@+rB85cSb!koIkHrA2sSIYFs=WylE)v` zAZbYhLfny($IB%j5vtyUD-S;KpO*Uvk?0YyD9Sb%x*vYDU~V6GM~iKsfO}&C{Rs9( zlM6JO@_c)tSxM+^rboiTb`<`O{9Z$A`kr-?tK`5ZEfK?G!KB4j?FlU1pxwY{*!)W6 zp^o<7f8T!B^pA8HMa35J5e#jiYeOMInyGRGh4yp47Fu}UVltS>?)Hzq;JwB!zO_*S z9*l|T{$4-L4kD~a_F&~R(EhPsP*fDPT!_SweR_kyhqY2od~WmB%f3(}zLjz>W7}cSO)9JdFlsF>W>3yLRWZa6Y#hvEQeLsJq?IS?pb949-XG+LVlIhXWJ1+N!wA#{7Kl|`=Tdz| z$po+yST}tMtXBiizT`S!k2tJjwRshoTeEGm2#7?~%ng1t>HyZ3P3VPI)nROqqMqoF zp^`NCy3}WJbNi~)$z1z_O3mg2jk2zEuOGkv?q5wc4l3=}CSf;xT>F7=h*LNY`=rpZ z#ErF?j4Sa6V1$%Z_#gs4ImgRZF)lT%iw5@G%H8K-^lZOkWD{~ZnN1MK``IO&Wi`gR zM5~k|$FOt!nRO9vz#>xl73v9 zmy_x0z4{SU@5X$*^wt`_j zp8FIdIDx-YK|#k``V}-d3KFVv#4~ZJH(qS@6NQY*4#r>t?DeFIU_MbV2i#VXpy&YF zM_H*NEI6-^ct$IspC_)O;8|^&NRB*4M55qO=*kS^-pK0L0?7Q5g3?O5j4WB2OPDt8 zUpJ|sT75}+La|%x+G@7;)h)kaEJd8gc>P{leE#vgHNg4LZ~nq4!abb37nN5mN~zi+ zAfdqq4IFlqvH5q#u=^k<;-V%|%K;6;Xy8SJb?D@;zT{T(upPr-1IW-^Y}1WU;cEYs z=^kmtZq-1G4HkmYx#q`zU8x=dp04v{qcpdth!|8>Vg-K+>nb^kXF~4L7Cc{z;x(n! ztAM5$8q$;-VQrtntv|oz^o9f7M**br_Iqx-`!=3^O6Ta0MhzIMlfeBkwDBMZNw_XS z8o;~8UOm&0mx})8rn5h2_OGZ`hO}6#1ZuLWCQS|SknVAY@NF9wse=lT_)y_TB0IIw z4p;-}@H$X#Q=w1_9)AD0KbA63iJMnat~wul0Di%YP4nOODew!5T~&269}yy=h$??c zMySu{$@u@rLoJ3`Yq58aTj$m*#HB5;D3Ej_GN3~#DX2Be}Dejx8SVal-^o7xMS|!zdWkset z;s1#HYHi56Fb+~QHc~7XR|S9j_>D+Sz!1VsOpdR`WOEi7!K~^{L_OLA1?6U> z#z%M9mMwqm*D3uArW}mt#sjUzHyv2H$a0W@L&l?68>9Q*-8n?7BV0W~M zF0C}0^AOQfJSo0W?C)fUOv`A=A;|4@_TMqyM9cqA75~5LC2~cghs{)MGleXckAa6) zG%{ClE$bGHVhCnPMbPiby1Y_xQ2#e3F4IAT>VY^y+s3^!W%}7z3$LV|j1oo^gf4XQ zTc6~RQ1>@`oJ}Qq0PMj0@1YcGn&26C)`&EuNd<(r2v#}f?wA$-9r}{?2Ijdi{Y^LY z8K4Uq!iU#kotE3GKwJe?7Qr}Ks0of@q<8%(NZPi3bKe+KTqN6+0=#+TeEHf83sQWn z$RyxsOe}iiIqu#?8%s#xj;-zZTP!hK_XQru_vxzKs203lR`(b@q1j{Va|Jj9&M~iV zC!6442zgmcw|9#JD3TCoZAQW<&W{1HY0^HqrCk!|k@w!`r5yrBGKCGaG|p36c>ERaeEEWyyN7bVUFlNJbXs1?%+16}p+= z9~+n?R)b>`=C&ta^vjGyV6Ms(L=udf4)Y3KWP2XvY zj}vqdv>)5U;Df8qxw^V~#~#EbMBAx^Q1X23Qt3;&*vB*G_~1?zt9q*OtHw zu_E+h`odgN=FYQHG&TRl7^;Ui$w-DeR(1P{6!`LkUkl&y**SG)Dppf|E03~lqiF5oQ6%T#bD{%pztT=+oSf2s6gom{$i7T3mw2n zwons4Y(zGwR{ia7-X{H8t7RjdgtfK;AI6Ymr{mI%{fET%6DNNWRdI3_1fl=_HZcuN zMO}3gFRu8XLf0JTJR?aFR36bTOV^T^c(&zv!&`Iu7N+{;o;4J|TOs{ZY#enTo4_yE zfR*5rKgsd7ALqY?STMt0;b*8~>KvnYxH!4M_!r4nRW&^`K3yKVWVl1Fr$zp*T-Neh zyrzeLJ~XuXVsQCHf#%oHY0wtJT(9c=fs)yURZT!92V56Gt`m4Fat&jAkT^VObXx7A zxZeb#i&--5P^~qF%(2f&(E!()6chg}h;9g33W7KxnKClNCohfyLi`eM;Rj=mPG@^1 zh0$@#sb|RbsdbNeI-nh*{ACfZ=~Ks+!ph#XlyWY4#}UoXupRBwj=M9I`FTi}V>(yY=WhvO zmxgFXN|dLqk7$lEQJd7@zxsI8)|QAqC$*R#vqYp#Sg}WL;)SyG`K*=II z!^l`;l+6I3C@m+|xCH9KQiP~`&mwrTCLeklWpE2)<{Kz&*T+b)x#65vz|wv~3`r^@ zJ%i{8`XeH-Ax{L>W=&wj{64x=3scP)R?nu6QIqu4u6wHgy{_EimZS*M?Ti9>Wz&{n zb##HH=Jt1$Ym2zes=m)=(p9_+?RDA){T4JwBD4L6n@r=FS42@YQKtu*HB=h9AFBZ> zEp&>x8Ul_{&aV8L)xT0-HFXucRA9+q5>=foPka(Br3_#XLO3sO8DJc4gS}>nT^z5} z@fO|{s&5)=EqM)Jw=;1;A)jfi<&Ab7;lBCq-xI8aXu#JohQ!;lC~D#?I|o528a)%)EMt?dxrQVU4<5bCb>Q1xyyucg{z1GLw0 zJ!l_96oM@R`bH{v5|Y;BJ{X2iIL%wXe|AC8(0qkLK4RE$8dWtZM_*_ADz|*G$5TV- zK6swpvieY?ZL@%Z=%_FAYc$8h)9Bctoj3iLQVwxof&kFYkz)8tWrTq2XY{R8+Cz4Y zdA?`ZcK7EFt)Y4{lQ?Gmq%1{sd-E{=W7{+rXyxnIMS`atNVlN__2ux3HrVv(4BU{V z{kCof1RFkuvSetzDQKi0xDSG-p{Je(lh(=sa!0vU<^BuuHuA4fYCj%5ZBH^DKzP6k zTjX`}siCT)+@n}YLDg0DgQl9=TK8UdN1e(tZiv3w%}U>c3EdvQF5j=zGRf-G7vD== z_qGSSj(he(w$Jby9nVbJZ2k>Q2F;hOYw3aaK4iZc=#3=(8b4{oYC%)&retZ96mOfcwK5M!V9vhRcIUR9gWZs~@^z2z{|Ku8uhxnq0s;cf3nadcjgGD)p!Bab zz@~?0bb-i=r!S@o4ayFCPuZUMmvF8({ zUEa=YzB~~TLu#d4O;I$S#GJwHpO&xz{ z9o(gPFdO>U!8?F}^&4-6RdiY4Nh*-Nq_W=)f5qJL#IxCE2X8eH%iK3q!yTg8fHsKl zAH(=7a7o`CJX-D%4Akj5Z3pt+^`x1I(^z{5P-Tr@OZ+0CzT|F~#wl9^k~KH)-mJsWTZUg(d%d{?#Kc$WEXVtqx@v{THbu_o)TENY=m zy8@@r>q0bSaHO3MxU5Etp9sN-h2eEwvf+@iQMb8 zci&qHcD&zyI#5;naF1%R;8Fat_^`hVEU;g|+D@_3p$bID_PqI$2@+|kCtXkoFl?uZJLyDI^xB+Cl!p*aXq1*;Hy`=k<1nj+`K!#be6M`n zZ*?ts>Lucxu5tfDyOVJhn9*if2h*}EO;dlzh+eC{HtFDdK1*))>NE2Damw2>MV*ck zYZq}mUBSh^E&}16Hu2N&L8Pg+EaWB0#~P}ARNr&;4tsI9bD-jHEh;_bR@yB3_wV0U zC7NP@%Q|d( zf|0(~!fsc$_@L~zc80CSRWz)X^iosJIV3e>;_6ONza6MDPKq(=3RF$w=~_HgnO|k} ztI>b{Kt9`I!)Ye-Cu)g0#Q z%{tC}FYAvU&l<|~{X(;CM-kxYaczG#*~aonhNF`YsZ)TtjxswT*qj5cZTkvK76Yek zEez%xs>+J4lyWR~_$2O@p||5YXZWb@Gkxwp-?u)$faj{)oMMuGR}umZ3mF6qBk>$i z0p}K%bzhOTxNb4LBT6yQtw%k>m#Q=jEhmlEoJVhiJ(P9$BHm@;@j=w^P3ytbPc3v1 zUN3MSV}4rXol@h!CXZFsbq^jWf<$ToV5e7Cb%>S40SX5P2hC=aIA!=Uc+sbbcvass zFYMM^QMVTPmzwdCD6s5#R~Zs2M~9-qs(%j>K95SsRJfr>B(NlXOxf0asGGU@h_u6=Z>Q`mWe>4uF zodg0@xfu#v?82a>%~oQ>T+`l2?S(O~*4_=b$@f10chUUSm3=ZO^j?#CMOPiiBI);f z^FWNZpAO@mRq%P+-m-wI@9?c{K6bg-P3snvq$H`9Tc?$@m>_IHjon|oC&qO{vT_rr z>{UscWz!$8?JeWzCqI`#(v&$&hc@Laa#F0F_? z%yiqqy$D}Sno~$E&c>&@$hc@xej;=6he$JcOCJb9z{t?3Bgd@6^kvMyzRvu`@4SgA z2Z#p6wcxIAJsHny$2C}BNh8yY&C&B?>xLH8d&C>#>LbCKJ!>-H<*D7^LWT3px+dY4(dF&_1J8ZEGP%x!_`%gbx0(|~TLBhd(_xwO;sw53!tgS4=P{o%$`!C#NPKG)@ zF`yEH&r?e+x9DZ_l77PazW{64u&S!6n$ptY6@Bk_&wJ=fD8_^Q%Bz20t}bT-MN7~5 zJawo+R`IWRsm3CScy48_T=%0~_x8bF5)TqERbSuvsmJa|K3E%9fM7WZ!?+G-eRm;T zn8FJ3-Mz8Ihwl7a#{TS9zZewnPsbZsd(I2(BZOsYjrp%ENM#Q9%wM&7U8|E6^$jw- z5LCqDzr~{9`Do0i>hxp~nhCy&cppDc49f@X=WgvCLU2O8j*%<$P@4C>D?7Ah`69RY z!qe%lbOP(xt_c$)FOX=MqK?j?>c@FaFJ&s($v=qXeY$w4$pRAy$sE6|FB|#)*VS7^ z)e&voy0Cx*cL`2#clY2fLBqn`H9&9;8r(=UmJ4Fpid{_285o5^8Xs1+}qrfiq`jj1f)n1Mp&)#iL*=dVKJj-ekkz zMDWvcWo|>M(6Cf1^rtIv)m@;3!2r4HJ{Rf_dc#v?9c`uCLpX}ltMJ%0gQl)wt>BaQ z3i!hZPJzeNN$8$&l!RMp)QRaAx~;l2^P)UqQTgV;C@3&K$fM&1VHC(bYr6Ky5i&JJ zrJOZmTyL7cajz$6m0*>P%tb@ex8(J>n{U{ki_TbX?@g7;Vv`VD;q(N4n#ed!mbDX6 zz&?-47yMvjgJ?@rNv8eF4E31>qmnh{l}~QhQtnA*4ZJT1xQ(brmm!X`vU$QG-ivxM zS?xgjiM89zXb6p0PirKovT)V5`>Rn|GXe6D@<^~s$?xU&ehl0j&oZs|dHlim-2Za* zgtWBkv0;ZXk5zsVh>YH-H+uxEg2Mab`WG0!;i)g&Im z`bfI4UxsS`kZBBUgZE(!O&>xV0b!UiF3m{RA(Y>77uLOUO`WHH)xyeI*TI2>7>^PN9W|J=bi!q_KNND1cYDtkd%h##U)ffr%KxNMe}GG^ba7E0H4hT) zdglv?_@h*DirOvIpOtg|o+l8h*yu~;Ui#5rp*~hqiJWq-it_U@nz;jZXRG&$aDcGP z{Jg7XMayaQ9O;l%3A%|&r|B$(8eiUES0e#K^GO5jUyvjoGvJ%9e#(6l(t_ z8{VberJk(5Wqrh29m3=wZVmaWF=M`1NUv>4=DKS6cDBZOw>%HNtWC0BX!JH`O9F45 zNN(b3m+!wmNrK+*fu~ZEb0dl4^*zV9zTYvC%ob6zCZ))4f$e8q>nZg`Thu+(iK5U* z7J?!P_!Z3q4Lt#Z&!-6Bb%pfg&zPb7yQR%GEg-)~4AS4!Gvh%xx`}ZzcdiO&9Vm6l z{9V6HG%LlUDr!&GW9-D=*79Gkn)%37Q{;RfTY9-v#~%7N1NYSDmaOR+6*1c^oi94( zTUHd7Wgp(s17i z>bF$mo>b&_-P5hcp_{H{<~o7sJzZtNq>u>FDWd3<1>%>XT#vBdYi#{03BM zic2JvT^7G^6UW{NPmgNpE0li{wVXPnN5fETzt1uzeA77sYq(F}VChu8`iuPMI5`sC zrpLmtvdOT%9_{1c8GqeEK0=;aMbdg;Eam=d)^ogvM7{yVOAg8g)c!F5g`uA0dSnjdDp<0`^#ak<_~ z@g?S%puXT)QSd2W;C4#1B9py_S5Z0Kxxx|bE`A47SU`yNh^dq?SuR`)Q7vbJ6)%1F z`3WX2)%cb!<5bQ6p{1jgYF~U$$nFIdIzw&`NN-s8fltY zYdwDTcOuybxyd{N9Ix~jvKJS~=hzu^A_=$GFN+ee__hZMyI4@oVp2vBW-7}_!yh~U zpDO7;YKt~pOw{PTPWUIZKQwfgh0qh|=U-vTISe$&l70Ssc&{|M@nL%(aS|Xc-FN^& zfBCC({uY3<`ES)qIOFv!1!A!2NMU3+J-ZKws_u-YlLDPnp+>gp%(x9d%+W+Zj zlZ(ozQ!$i9skF0GIBOy5IB#Fx^bqdvKpOYo_%Le)kTNNWTv{D z@jqu|DzRtl?FcG6M~NvJCatb#4a5SEq9^_HS6;528@8QqoJCVBofp2N@MH^%r-*>r z0d|*zv(ug`F7MQ?R7Y4E$&#_yNeyTS7~d~b*cG3<|CBj}3c4Z#3vL*4nAn6)Dc|c) zg1f8!-0bTSzs;cUkvgfljeUtdX0f!^&v^~NMqjQ&Lt`VKlL?H|Pl2al_|x}OGojl3 zeba?P*T`iY-#JYVq7lBd5oy4fIX6(XQTyqZR;IumN*LI*Y& zVY0KU8V3Y>q{=u$WMPx_bjM~Mjgs+YOxJ5#J=MfZ9n>kj>pLkcDNj&N2+KrMG@ zv4stL2v3a2*mM@cyYQO5Obx!`Qar0`T440{>p(xlz@q#;e%49W|1%i-e<#YX5}23k zP0ua_fxBR9zI~K9djAykJhCNq9j^qF0<%T#wxFMHNSiy$zQpVMi7Zx=#Z`5ecU)%n;rgqR(S%RQT9d^;Qb8Y4eL*$N$Vs@d!ExZ*YCa?G_B zgdM2xLax9~u`=dD2|r>mVD5kvLHOYZdBXi~N#g@xQ)!U5G>xjd^^*8-A{M2v<$g2_ zTOKDUe<~KzUmBt-W`-0WmnyXuYKEP)L_2>)x_)CQYx~SXFNq`ve_DrKL0+b|Tt9Pu z{FnK~%jQ~x%P>J}DS9}#5DM&KNY`DHI*g`cEPIf&kUWA>i7toLEJEfYG zX%T4F6=D^CAgx^-y{IR$U}h5urIgS>^_LTeLHnPyqbE- z#Ttt5mn4Sdi?O7F=K0zq!@q3{M&(e${N#F}SSeY@eiTY%M`<#?w@gbsOXg@SlFIUt zB?_cV+6ep%Y^<|q$MsfcsPCDyrBv*df-`J0u=~F$hU;G-G71tyg{<+F{jqRQmQN}p zo__yS^s-W2T1s*Gq#ZT4Rh9z3C?%v+)K$}IfVe+16_HRDmzVKcv5&&)KFLxCw2!e} zw(zluC2A^=>#Km4(PZ#9E(v(wGg^VEqoP=;euG?xdW;C!)NOvp(te4k5-2Ju6&jFr zZ_bym$#ih5?CsvXrU)w5XB<`zb(BAuZ$hTUM;;woch+3F87?9oHwA+VH7G~1fQ8H3 z1qg&l7%MoJ?FXWZ*G4j4xLurLhLsl1vu?hUpF%<&DXwkT@&Uya8LiB|bR8wiicx40 zsP^*PpE2z8>iM(t&ZJZfB%CgU+<%{}zPHS}DHjN@Y(YM~iAax${n>50qWV4^vFSRy zzSM6>6%CD^PEN$gio2kuHcFeh!%fwk8yuJ4;9g;&A0p{CDT$zB#jc}&6$A(2Us>5to5P~5q&${avgA~|6 zo%W<+WTYq>{FSaJCx?YjcnD2~k=)S5Z+;isFmhFdq#Du6Pcb6|v0DULi|X|XwapTK zbMY69xqy{H_~1X3El&(VuWwcSBiylFT*Kx8MHSkH5w6-+5mEeGa#<0F-Og6Z`Z;U- ze;ZhRA*h(g1Oo0^Q4OXIg35q0R{Udl3O_FMI;i58uYXe=;EP*#V*3BAa+49Dsk&W3 z7(34JYN#*ATL^sOU7JiHMh-s`3;OO@cBmkiC*$iOD6CMCEjx-)e^TKSJU$k#wUKjh zHq;&9a^?&@$~$8=EHJFDu4>9N-1I&A;(m^#B>EFrY^&nNiHWKoc1n{QTxUFCrk4zv znT1R!=Fpybf%3q!DEqKSjh#nRLUTXVi?WnB4(v+aM7-=T>%((Dt(H7ftX4O{9kq)}uvUTF`Zk7|#p{Wr7lbeQeKrM+QrFjnL6a5tXMmXaknFnl zK@}BW;+DS4`CeI37-~p6(BMs3cM+joK`2)v@c5m%2)ad{yX@E7*f7{u8=IK4{^oPk zj3oJsm@9!}0DXPC*?LmJXTxD|f%R#Vn)?3pN>xij>kG;=I(1iXT&MpdV7ZJD=vPM? z@zp!w?U4$_@!aEd0Kd}JbUEN(6sl}R$Ey_uYtzX&pUrfvxR`h5K=PN<_KVihispOE zv@ax))3bSW`Yb%KRc{c>zdYkV)8jIx@lOj~*NwfP+Rf!+7$M7@70``bHgTtDXf%9r zq)pXp7?Qb08%~!*rzo)rVv(?nWwk{WTk%@qI9LT#e*d~%=UHDgY<)O)DD~EU)QP4} zM8$%w9_POeJEVkA=K{cp(YGl{Os}H+D)r@$ppH&h#jg9=1|uNRE0b05HAVg3^%4xT z9i9`O5wfjb zsDf}*@x-10g13h>UW&}*+pJ-o{b(XXbzQEA$58@9B9OU%1Ytz#imjIwA#7?tVQDyB z4xe;Qv5E%ayKLh;gO2I1Jq~0!T8$c!!H`bX>6S2u{DY2nyuyK$9xv1%RdInc)ZAg$ zRD6@iLazoZuD9`bn|y)~nNga5D3^_|=Vf5yBlRr4VeESgJMa72!*^Fr5tXrk+DJsO zG|XrBx+C6r5A;x`+$$mhwSfBPp=-4l7}LA6UsmqwIk`r$+KB0~QsaXMaN`_>v!7Iy z$M0&2(q=}d;)yTkMtYi1_Zx%Mb(>o${d^p9fSn+NN$vlH^OG;W?jik zmN|qRzNxp*u2i_ZqGVY4K>B^IJ4wmyX{SoJ$0Ys~n_@)Mgu|5Wu0ENIBZLZ;JLMa? z;7rf&b8YYnVIW=B9nI2*by}lr%5eRl7oJYJYN|F@hR7Qk9pPphE=1Lo5#}U}+pWsQ zK#8tQ2)0H?0lJ=*kRjP-0*F`&VZ*7Z_;5OxtcxgJ!{JE#hzeJu|9Pdu@O?eAaHPg! zZd?9DwXVJTYo~wr1xaj9Qw;U0pE2mRP3bhP*tRBk*Vvr5xs5?8jpQm_Eic%?l%XTV z!i|x`mDWudkc;CLEmU;C*iaPzuG{;b1z7BO|2(HQcW3a+XJufc+BYgoWidZ);eC^| zcPGd$mjE)Cc_-eA-yDq3n{)rg+NxmE@I6V9m@1i>p_y=KC6<4o7DOIQja(ufM{pms zYaNWEc9)jo~FC_;*1PJxy24zS@XH$D6nnD^g}nE^Qw>nEPawF0B*DE zOsFjGC9UiyHEK#-yG8!S)`Z&C{QiA^umFC6E*>ZHtZmLg+dBRIY9Ru!P65Z=)%|nY z|GTgz&wIngp9>G`{Z$xq*;VwGSq$24fB_{3h)Nc%!awLcdOW82dW}I8#&-)LgJpp(U_Z$nZ z5*dIt-)Yp7nDn-M&%*XbGjf#}bHZ^L21+^Sb-kZVd%wN$(IMM|;^M3j2s+=a3JM{EOFo$-hAm=~&B?b~PI? zs>X+>glW0S{AejFlm0^CVgQDeGwoKZa84P8H$Kv%-DIhUAVyurhK+Zt8pw@W1`o5W zJS<+F-db}U93PvbJE?t0mrM9b*W<=7fvkuzEYUfOjD#4O8R7`dnnrnSl_EJ=qNz>( zpfmaLuSa*C^s+0C-2SCH(&i3`{cT9E@@@2XZ$`1Cr;Q zlcaMG9jrO?FOhRxvAoj74sNxLxMDm#9ReglSr%m#+E#2y^o_mHF=4`bTxJ@2s@$!` zc3;I<*cgAHYq8aX1?0Z*I2p-J>G*20&pgb{$b=(a#p3=zA^g70g1qSOP}KA{-QXh*fzzw1kp7uz07HCXw)JeG=Y z+tTZAxcBpRo}&tPNU=zeSz3W-Ngyp-F7IpI@?O++Mbrf-j`T4s6=jiEN^y%XC}^Vj zSHvCGDhF7LQ*+uUUM0i+=20gfhenA$^I8B&gY&VA_KlC2JO0LX(u-sZ7sPA`2Z;QHhXqI%hwrCnpV>tMDH8Cppqvj-gE`9^W zw`VvH=Z)`OWXVt_ldH|OHpAl=b=N7g^oW!6XF3#anBQ~wfS`_;`+i$ugT}i;yUZcy?{#gB~7;= zEM0Ggoxz3}XDTpPPavrrnP5qHq*#+EV-))}~}<`vM3{^fE*i z)s!sq$&5+T@p(gHQIbOGoQDrSmzMsH>D>&m3&%GzXP&fVEjyZNX@T2yyG38u^rX*a zVk%vIT4zmHTDeD*L`c;!o}Ooc9I!!JM^<{m)nfHtYEx0N!uyd0fuoz)tjPb=^mge- z+(Oc;5d;3+-2(%jUE9%?K?n~ctn&I&uo8eGXl_;6vAlkF?)nRP*-*-vXD=KB)qL>B{*P(5ZSJBtpYP829D}K(T6AMqT|@I9R7w3mr2aV| z_O?hl762RxFRO(%p1vnyk4X!f_Vgd^m1a(aQITDl4S}(cndb#l7NTu4O&NSq69Lre^5%-IsG{=yO{v0mz3jd7<{|5d$rg z9t&9xDM`2EmXCjFz&<2I;Zg2^J^c&3uDLH5`l#i6dN=Y0Z9g~xQu3^yPi1~nY`{6?S;ic>?6mWj+PNRwf9Bk^804S=7f1@nXrl?Fs*q4Ot!r=PG8k8v=H(uki{qzs#4hbF#-C4g$L#hLbO4 zWm)hDXmO5w9gGcb6VeL$q2QZiN?E=s{dt-<@RaA!vBwPPCgCg*87fBXxQF}|Qq*g( z+;!A6ZQgt4Icr0o0^=8W9i#po$^)sB@s-F=(9m?&Fj6DZOr>+%ZnFD5-HG{qf-3=_ z<)TFuHpZat?e9!g1?63n0p$Ee4db*I3ai!Tattjk$^m}~>ary~8QHbQG=TQj>0IZu z*)T!CW~!MlOKg@U1WvF!GIl)NA_jnGn)3J{ z!XuByTJAY2Y601K)vE2{EWwq-e9>JpZ7@W7gKXLghSFs5P0%S%fukv69N8YgR2w16 zL!_S>qHO}jyNs;o3fEjyseSoo;`a0(-ySAV20t}1)zqp|2VuMmN^7OF|2dI_I*Pv| zd|#n9u5U7?9xC&sZx*-80N`5iFM_$2{Dk;hS8nb2bixR{IQ^1L45xI}Evtg?1 znbA5cOlQ~Id5m7u@zX(OW98}tmj4Uq(!GZ3A_Lc&wa+z`kEIS3-&M1GEro2-a^;ZP zYkj3ROe2_crO`F37F&&yfM#{IZ<6g{>b{o`cE4`m+21RXh$Pab-G%4 zrN$~;r*<3fB=fKVGuEN($q5TwjI_PG_C>i+V}6Asn-y^jwJf8g!tC(qs@m6H!M1FL!399~C~LTz$D2aqN7%0T8Gq5t(GEg#O=FQ+XI+IvS_kR1Qswu%fkqlsj6@T& zGEH;>jMQ88@rq(9*|But5vu>)R?Rb}ni3A;mxqj@^pfO^EtArLK#;ysi^^lTC~@iz z;|v#dm{%fI_<(N{@K+*Gmr?D`n9-YnWcHw8mhg!gRyZ&Dm~CTAw@~>koddZQF1saZ zVVW7)Ekjoht@hGwM_EGgMMx4alND@_&0rLszoS@XiM-ZWD!kKEFyPGJs6HyR#r=&;w*s->&}2x%)IHD2PCi2DY2^gt6q+1=@qg-`*nUI}Ku*{KVrs$p#q z&qkmmjIozVz(*Y`4F3+H6|ri5|0(Rxz7ce2`)A^N%1ZQ2f!g!b#b;D#Ln=?=mI4ie zyonfEal#X5#s~iOIK5yrX*>5@l^36D>nXzx_L%eX+Ra^3lb$PiF7)GVh+5L#qN?DJ z$7hI<28j0wjP^AGn716B^CJP1!RbSnf0E_i-K3*(y`)eS3363wz-8-$vK3H=F}yO! zYyg4@byMU9%ncpPk!0udvXPOfMWS1g6h%q^Y`wQ^CBRXnk>Z1Fdhj_|0k2OV`F5D! zzg_rD&Zh_#CH}6F1R3}Q%ukj#`XBrE(P$mULcggK+Xi) zo#ZcHpzgXQUz#4wpL^c|Oo4}06h?-nA9BzN1XiSwkNhX$(NrACJ^7QF4PWb_TbEmS z%m4CMjv_NQ`jO{Tn)6W!eG{;!%m%yEj6<%hzG~aYvZ-_9FJ|KMRoTUGoqATlIub+ZZ2TvR;o{GD;7 z-KV^sZzMK1MMFDdrQnCj)TayZhs3K9}k}#mu)8PCL{y)O#f@t z#lfnwNWDf2K67@$L_psY0-L|``6a3{x(e(!jtQvT`o_PPuBy}aM;GZ@#>h|o|D_Li zqK+m<;242j&c$^)06MxC?i9sNCw81f0zo82bESo(Ei2v>0r=!9`VAq-J8UsfsRi&itLQC@e8y6iRRG|HuegQOyOe+^9gCWhR4q6^somf%Lcc2JlK{M7n20Zmq4}1usqYkSC$xS$D~{b zA>l*VOKP`eq^9DnC{`RLakX0yMiDobmO8%lPx{ybIc@4YUp78Q0VcW!IL&={NqIt- z3HTg^etWCbe?Ikl%G-zJ-`jp#6U&Q&v)0Z)>dOl@JWbz+m}@(WQ0SY6JDx5!YFxJ% ztMpwWS}!P+S~O#r^J;77IooT7_l6i~Xk2L2SC&_-*IEv&`@_@{WfD`{J?K0raF|h5s?o*H`-@E<)8cmU5Djq>9iA+yj05-eKUV>OoMobfTYNGW3}+^ zc5M01+Kk(;y4^+AYG}$WMZhRUFcIqL)bacDY1SwcaeMy!Xxm)KwAH63B1r-9+ens} z6pKqlcE~=t0W`#~)D!9sno4$?5RRPcL5l+y2x}_LLg3akZmFjZba^9Z=UCTVVk%g= zlEG4fCPbCyGE4#6c}>x!nZaL7+6ZBl_(v{2K|fJemrlx1=+vSljQbjHbqnwOfA_C6 zSwGlKwuA=}!590EGOHc#l;zAokp11~j9|L>p5n%36*wh`^u@mgE8o;+*dt2=NvVb6d zLfO`)dvn$crxr7x9ZE_YQz_NqW>uguLA zakyYjCdX4{mo*A}sQ}v6#_8xhnr1p`3Ll*8fY;rqU@v%Mt^2vw4GPZEU&=JBk#S{X zAmsY~Z|+^ekmp|u-s9aKlKop)v1tlpmvC^A zfUXM=kV9>4cX5IF$iD0#~s=Rywq)WMC`pQk+SvFBI9HJOY`IU6f zNK%6&fXMvZoIhVKbflKCWF(P;g(`<4T&p>Bm7Zb!m>1$0&CTf`k;j<0BqI}@E4W#0 z%nJ*ERA8ZIo!5tshY-<%$!B@^VRy0Wv(r?;=Y8Mp6*HPd6PrsBu1?SDXdRlE+GKgU z7&ALSP0%GAll@*m{cPlm2^&p<`rLV&^gcxl&n{0~vX(8J#;JfO*+9UTV?CWq9u_YZ zcCqKw-1_D@c^Yj6mgR}x&7NpScQrFRUmZ+gV>|Gel--%KuUoRtK@!ib%q$)mq~+Hh zF~@IL_EWWMdrEQtc9e6>-Ylzwq>gyXn1u~Wcb$-EnU#o(}#iwSyz~(rd zbMM%l{Yyq=$2}bjgPLpbf|;4h~BK5j=#~*CETv6E>oRrl`Y(^JW%- zVy0Pm(7&^~){V#c&gYPm;H0k!f@8vp0>AcNtf8WVkyvX1?6Y1Cug+^G7;rG`jZKRr* zZ5%w*hc?;uqqkLLc=G0ZyIufsHjC}9OrpgHNev~_lyyLe!pqx6yhpyjJJBZ|fNt0V z%ugW%UejwXqL2j`28VDuxj6jA%&-!}rR2uNP5~wp9>Ahf;BtjpWsm*h zDlNMw7J72^*zU7v734H{3vS(HU~H=3c`dI~I#wr<`)Kot9PY0e>r8~k(;Yikj6OE4 z-C;Qqa-FcuN0%qN9)%g6uhTN)>-zDu zUp<`tt;}r1s@tFRKEi#qGAU_|0PaS69kg!js}}IC)d_us3po+5)FvAwjf^JdtkY@7 zomYrPa$sI^(&{5Ut#Bo_^ne@|f*T&dA5+~?&uG(JyfaO2%FauTQ5@%znbmBWQPcMd zP{c(Q$LwIM)pd(vgMb}AqiNfA$#ZqQlU0yc{k&$;mG%V{?_uQ0bNfLHr|Omb$KoE8 zU2FBznv8sds#_<un-v>0?H!OL zkCBe#)iUc9lEM{-7v6kQhEL>bl{I|zuR`nZvl=xZTQV_5C6rH>N>icUMn^Fnbj55a;b%~2LF!jQ;!mv7^h=*vwX zM29IG;cTBlCndaTA_nKLv>m+U`u;2Moq9mZ-w8vrgF$YxmKNpm$Kh9~oZ&JAsbju) z#YjL)w!+P0?nxMe#W52gxguc89;sD_k>f`$YjOwWG-}0DNkKhpSHCrZ(|rGNkbv9$ zKL$WK5h1_rT0f)fO59ynXk0HRU!#6y=e>$-Yyf9KnUQ!~L4~S|uMIo-fk#A?LP0@% zCdK5%Y4s1_#aXSGRoPb`)-AKroJ1D-&5R57s&%u@O+P~{dUY(eIw2>I32-83h>GYl z{vQFla1D$7VqzQ+LkRx+uNdHyMoMzoEZfT~-!! zpYgg2z-MoC8mDt5D(BcGKv`@IRux!Kiu zQ@i(PJCNNa^8?{SzWvuF?bja8sV*AY7H0laM~C_H<)ld=#ris|*6SwE%-d|u8St}F zKsC+wErQDH3vQ`G@!w z7iu9SJ6vvA;gJyV$4MBRG1e9kZ;`$`kJeh29R&V+K5&L-WCiK#8-yB_g5=zwu4R3` zA6sw6yGQ*`Uyc|(mm`QbS)VR`0|4_I_ctM__ht*mg1Bc=iSf{JOt;oth}&%V@DcM$ z%lSS(7mt9M#x!aq>}{FMUy**07=1eF-*VSjBm9quo(5BUSa$Un;hMB4(S-(cy(-qn z5`5|rYq&6VTA(OfdYT;SfQ!YdtzzdWN0Yx*7j^0;d?uWj%zApm!^R5_0wRV&N(w$( z;`+!hy-DBkjbB}=Dd`JW@|6=}#94lV9B5q?Ww)gF8!p3kx3*}?O}#oq6gz8upEpPf31KKb8X0VCX#wYoQTqY@yRtap zNe(4gE8AAc?<~LO3TCs9yQ(drEzU>T>A}yAVtZN_T=IME{{x{)6~Z8?L7v4RbF|sfA*F3c$A^Cvi;|eZMy3=1nsj@C%!T^Ku_t%S?ECA%SZgMP)XWH&x zar7bjto&YH1DQe(>Fus}?L#vVwbEa?Z}C$KpC$Spwqe5TGa)z^EzMN7`GRVM%9IA5 zBAflneu;JSXC97mB^8$6z;;(MBVa4s_p}!X8dobhBe76+5!~D?GBFpz5TJ|)*ZPfPqNg6g%~cISGHit6L6Q> zjQ{#7hV%NDjREUgsYo+>S=j0&xd2`=#D5iFzy4*_R?})lnIEm?@f$;W=3qt2+a z#gYu#`b5Zaab$soX`x@;eu*I-zI8z(7~m1qQwigSD6aD9^|4#K5W+i_5K;*N$N-Y?3}&EWgaQeM`AXTeOmHq zEIbgdS|x;&0tH^BqM)YmaO5ZTp(`Y35Uut@{MHqUDqWA{(D5pnT`Tz*9V6xT>hKl%HOz!C_k%K!NgcY8)j zTzUfgNv#u3KO0&yG7FXpZR*x+u(q&SN>oo;>*Ru0!LMJ5v<8QZ6_y(a)(uo`ppG3u3PSxy&0^owvG9@Sf|PKFOlAWwP9_58+mru* z=iuhzL9pO+p5KMG=ulHs6k^U=daag6L|v*CXDxHY1|wFAN3O(y@4-?tE|8NHi7f{4 zy*ox%OaI^dJmpQ|#%1E+tR@~u_A&}{f@0e&#K(;sa6%yqzuM5O)kb9S>AL)dE^q4J zDrt%)jNbgyJj_7D-lXp>@}Kx1_06BD3SYi!Wi!}P{rp{BhJdka1P+%_RiA!}gq*R= zsP6ql_lRgv<;gALWO@Z1?$$m++buT>xi9m2tk_t3$6CxrUbfX8I4@7yFbU&cUtfy^6~8ISboT@N5Kh@tg%0th<@Uj?NEqgez`gHQNY|JKX&KJ8u`eCekqB0fAnvweW z94EWm70^41^$wtB>=k_k*+wOt4cfhOS>%G(%rx*ijKAA77EzwnbLrOqeWH-FWic_) z*P5N~kwdgXVTFb1K$ohBxw#KGvf>nCvBP1~sr!vKtNEw^89lSx_qMlUx4hB$?#iij zdebZ!G_mSDjun5m72-cGi(m`gVr4ZN7|>E}@WFPklGfz177K#-ZNc@U06*>IUQg&l z(WBfagv7?pabBWM5kMOkh(|Ss&QPU@r?OAVPxGuA=c~$|?d_v1DtMz|w^p?bI+Tvt z_nwt!l6SRfu$09JzYlj^&t4CDIC<~{hOcF|SqQx@b)s6hLDpI{3k*RKkF>ftcRroD zB5um;THfiH#26VGu9~iARqMrOa;O=fn|EP=HWm>gaP2?;{SSTscavk&P@4<3M<~?_ zb2cAC+?q08r)vLjc7oGXHl?bg@{6*UV_ctehZe~ziR+E-cX)`|Av$|aMGi&OSfZ` z{I&x0>*$*IiATE9f!uEXXaDo_*YctO>fB`Lkpndf+?P#4drY?Ll{CH-2&9MCT0`SpZ$3hlLw z6yu|Qgci+Ps7g#%n7KxTq>-ZVTAI&&x7Zn*xA@yt=>Q4a>04MO4@a4Br!-zPBAHwZ zl2q2OvBdL{);fIRKBaMkRcfAzgvJG;KPoF1XTXA4tZ-k zxlseGjbj{z*O6)6>NyjbQ0S?V?fRKFw2Em1`U=`A+BHoHE(FeUy1cP+JW=7|d8rux zJQYIb1bj!3#Hl+%=eF^y#emtTx2Z-ryZf6TaECg!^|IL+G9zrQtF8DQ2h|t)Kdp94cIUjxXM;}DockNn zuK{%VA2aX2OHe^Hhva<$Rr2diJ?$lIY%CZCN3>=sek z?0wv`<(~!GK{}>oJo+NfYQDDU$V!h4$@7(|KGnJs(|#F`#S;JhMF&i@h<^80Pn?^7 z_iM5xuFLs8ZUZfA?TGkoJ{@XY>m5lIt`lmymbSNl5IZ`5bR@#xeB3VBx8)FK;!QKv z7sUO}w3BRoW5Y>{zL-HZx)iwkEtjf?H)MW&*)#FYWzx$0j)*hmW8mEL9p~cir#2CD RG7#ePk``AGs}Rxm|6i%GhcEyD diff --git a/docs/developer-guide/img/gaming-fig2.png b/docs/developer-guide/img/gaming-fig2.png deleted file mode 100644 index fb53fd69f7b52ea54c5315eec7bb7d7b4d7dc2b6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 121397 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

C=;T6L`zCUei0A*4wT-B1*+*IiDI26ta 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. -//// WARNING: A common Android hack is to write files to the SDCard storage to share them among apps. Android 4.x disabled the ability to write to arbitrary directories on the SDCard even when the appropriate permission was requested. @@ -775,14 +769,7 @@ The simplest usage of `XMLParser` looks a bit like this: The https://www.codenameone.com/javadoc/com/codename1/xml/Element.html[Element] contains children and attributes. It represents a tag within the XML document and even the root document itself. You can iterate over the XML tree to extract the data from within the XML file. -// HTML_ONLY_START -You've had a great sample of working with `XMLParser` in the -https://www.codenameone.com/manual/components.html#tree-section[Tree Section] of this guide. -// HTML_ONLY_END -//// -//PDF_ONLY You've had a great sample of working with `XMLParser` in the <> of this guide. -//// `XMLParser` has the complimentary https://www.codenameone.com/javadoc/com/codename1/xml/XMLWriter.html[XMLWriter] class which can generate XML from the `Element` hierarchy. This allows developers to mutate (change) the elements and save them to a writer stream. diff --git a/docs/developer-guide/languagetool-accept.txt b/docs/developer-guide/languagetool-accept.txt index 3431b61c540..5b9d2aeaa75 100644 --- a/docs/developer-guide/languagetool-accept.txt +++ b/docs/developer-guide/languagetool-accept.txt @@ -722,3 +722,24 @@ dp # One of the SMS services an application's own server might send the code # through, named alongside Twilio and AWS SNS. Vonage + +# ----------------------------------------------------------------------------- +# ComponentSelector (component-selector.asciidoc). +# ----------------------------------------------------------------------------- +# The author of jQuery, named where the chapter explains where the API's ideas +# came from. +Resig +# A method that changes an object's state, as opposed to an accessor. Standard +# API-design vocabulary, and the word the chapter needs when it contrasts the +# two. +mutator + +# ----------------------------------------------------------------------------- +# Vector and animation images (SVG-Transcoder.asciidoc). +# ----------------------------------------------------------------------------- +# The After Effects plugin whose JSON export the Lottie format is defined by; +# the transcoder reads files it produces. +Bodymovin +# A point in an animation where a property's value is pinned, with the values in +# between interpolated. The universal term across every animation toolchain. +keyframes? diff --git a/docs/developer-guide/security.asciidoc b/docs/developer-guide/security.asciidoc index 043250ecb88..2fc728c5cbd 100644 --- a/docs/developer-guide/security.asciidoc +++ b/docs/developer-guide/security.asciidoc @@ -61,7 +61,6 @@ include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/g This allows you to type in the first text field and the second text area shows the encoded result. A text area is used so copy/paste is easy. -For your convenience this app can be accessed here: https://www.codenameone.com/demos/StringEncoder/index.html === Storage encryption @@ -193,7 +192,7 @@ The trustworthy way to verify device and app integrity is hardware-backed attest Enable it with a build hint, which bundles the SDK (Android) or links the entitlement (iOS): -* `android.playIntegrity=true` -- bundles the Play Integrity SDK and enables the runtime token API. Optionally add `android.playIntegrity.verifyUrl=https://your-backend/integrity` to *also* attest at launch on a background thread and exit if your backend rejects the token. `android.playIntegrityVersion` overrides the bundled SDK version. +* `android.playIntegrity=true` -- bundles the Play Integrity SDK and enables the runtime token API. Optionally add `android.playIntegrity.verifyUrl=https://backend.example.com/integrity` to *also* attest at launch on a background thread and exit if your backend rejects the token. `android.playIntegrityVersion` overrides the bundled SDK version. * `ios.appAttest=true` -- enables App Attest (DeviceCheck.framework) and the runtime token API. `ios.appAttest.environment` is `development` (debug builds) or `production` (release) by default. The runtime API is identical on both platforms -- request a token bound to a fresh server nonce, then POST it to your backend for verification: diff --git a/scripts/developer-guide/check-guide-links.py b/scripts/developer-guide/check-guide-links.py new file mode 100755 index 00000000000..6c56e0c5abc --- /dev/null +++ b/scripts/developer-guide/check-guide-links.py @@ -0,0 +1,161 @@ +#!/usr/bin/env python3 +"""Check the developer guide's links against the site that has to serve them. + +Three failures the guide shipped, none of which any existing gate could see: + +* Fifteen ``codenameone.com/manual/.html`` deep links. ``_redirects`` + covers ``/manual`` and ``/manual/`` exactly and has no splat, so every one of + them 404s -- and every one points at a section inside this same book, which an + internal cross-reference would have reached. +* ``https://stackoverflow/tags/codenameone/`` -- a host with no dot in it, which + resolves nowhere. +* Plain ``http://`` links to hosts that have not answered on port 80 for years. + +Rather than curate a list of good links, this derives the set of paths the site +actually serves -- the redirect table plus the Hugo content tree -- and reports +any codenameone.com link that lands outside it. The baseline is a ratchet: it may +shrink, never grow. +""" +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path +from urllib.parse import urlsplit + +ASCIIDOC_EXTENSIONS = {".adoc", ".asciidoc"} +URL_RE = re.compile(r"\bhttps?://[^\s\[\]<>\"'`)]+") +# Served by the site outside the content tree. +STATIC_PREFIXES = ("/javadoc/", "/files/", "/developer-guide/", "/images/", "/img/", "/blog/") +# http:// is correct for these: RFC 3161 timestamping servers reject TLS, and +# example.com URLs are illustrative rather than fetched. +TLS_EXEMPT_HOSTS = {"timestamp.digicert.com", "example.com", "www.example.com"} +# Hosts with no dot that are still real destinations. +LOCAL_HOSTS = {"localhost", "127.0.0.1", "0.0.0.0"} +# Only the marketing site is served from the redirect table and the Hugo content +# tree. cloud.codenameone.com and friends are separate services. +SITE_HOSTS = {"codenameone.com", "www.codenameone.com"} + + +def site_paths(repo_root: Path) -> set[str]: + """Every path the website is known to answer on.""" + paths: set[str] = set() + redirects = repo_root / "docs/website/static/_redirects" + if redirects.exists(): + for line in redirects.read_text(encoding="utf-8").split("\n"): + parts = line.split() + if not parts or parts[0].startswith("#"): + continue + paths.add(parts[0].rstrip("/") or "/") + if len(parts) > 1 and parts[1].startswith("/"): + paths.add(parts[1].rstrip("/") or "/") + content = repo_root / "docs/website/content" + if content.exists(): + for page in content.rglob("*.md"): + stem = page.relative_to(content).with_suffix("").as_posix() + stem = stem[: -len("/_index")] if stem.endswith("/_index") else stem + if stem in {"_index", "index"}: + paths.add("/") + continue + paths.add("/" + stem) + paths.add("/" + stem + ".html") + # front matter may override the permalink + head = page.read_text(encoding="utf-8", errors="ignore")[:600] + match = re.search(r'^url:\s*"?([^"\n]+)"?', head, re.M) + if match: + paths.add(match.group(1).strip().rstrip("/") or "/") + return paths + + +def findings_for(path: Path, known: set[str]) -> list[tuple[str, str]]: + out: list[tuple[str, str]] = [] + for number, line in enumerate(path.read_text(encoding="utf-8").split("\n"), 1): + for url in URL_RE.findall(line): + url = url.rstrip(".,;:") + split = urlsplit(url) + host = split.hostname or "" + if "." not in host and host not in LOCAL_HOSTS: + out.append((url, f"host '{host}' has no dot in it and resolves nowhere")) + continue + if split.scheme == "http" and host not in TLS_EXEMPT_HOSTS: + out.append((url, "plain http, not https")) + if host in SITE_HOSTS: + target = split.path.rstrip("/") or "/" + if target.startswith(STATIC_PREFIXES) or target + "/" in STATIC_PREFIXES: + continue + if target not in known: + out.append((url, "the website serves no such path (checked _redirects and the content tree)")) + return out + + +def load_baseline(path: Path) -> set[str]: + if not path.exists(): + return set() + return { + line.rstrip("\n") + for line in path.read_text(encoding="utf-8").split("\n") + if line.strip() and not line.startswith("#") + } + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--guide-dir", default="docs/developer-guide", type=Path) + parser.add_argument("--repo-root", default=".", type=Path) + parser.add_argument( + "--baseline", default="scripts/developer-guide/guide-links-baseline.txt", type=Path + ) + parser.add_argument("--write-baseline", action="store_true") + args = parser.parse_args() + + guide_dir = args.guide_dir.resolve() + known = site_paths(args.repo_root.resolve()) + if not known: + raise SystemExit("could not derive any site paths; is --repo-root correct?") + + current: dict[str, str] = {} + for path in sorted(guide_dir.rglob("*")): + if path.suffix not in ASCIIDOC_EXTENSIONS or not path.is_file(): + continue + name = path.relative_to(guide_dir).as_posix() + for url, reason in findings_for(path, known): + current[f"{name}\t{url}"] = reason + + if args.write_baseline: + args.baseline.write_text( + "\n".join( + [ + "# Developer guide links that do not resolve, or are not TLS.", + "# A ratchet: entries may be removed as links are fixed, never added.", + "# Regenerate with check-guide-links.py --write-baseline.", + ] + + sorted(current) + ) + + "\n", + encoding="utf-8", + ) + print(f"Wrote baseline with {len(current)} entr(ies).") + return 0 + + baseline = load_baseline(args.baseline) + new = sorted(set(current) - baseline) + if new: + for entry in new: + name, _, url = entry.partition("\t") + print(f"{name}: {url} -- {current[entry]}", file=sys.stderr) + print(f"\n{len(new)} new broken or insecure link(s).", file=sys.stderr) + return 1 + + fixed = len(baseline) - len(current) + print( + f"Links: {len(current)} known bad link(s) against {len(known)} known site paths, none new" + + (f", {fixed} fixed since the baseline was written." if fixed > 0 else ".") + ) + if fixed > 0: + print("Run --write-baseline to bank the progress.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/developer-guide/check-guide-structure.py b/scripts/developer-guide/check-guide-structure.py new file mode 100755 index 00000000000..2c1913dd3e7 --- /dev/null +++ b/scripts/developer-guide/check-guide-structure.py @@ -0,0 +1,231 @@ +#!/usr/bin/env python3 +"""Verify that the developer guide's book structure is what the manifest says. + +Three defects motivated this check, all of which shipped in a green build: + +* ``Working-With-Linux.asciidoc`` was included on the line directly after + ``Working-With-Windows.asciidoc``, whose last line is a paragraph. After + include expansion the Linux chapter's ``== `` title became a continuation of + that paragraph, so the whole chapter rendered as subsections of the Windows + chapter and its title vanished. Asciidoctor reports nothing. +* Three chapters opened with a level-0 ``= `` heading. Under ``doctype: book`` + that turns each into a *part*, promotes its own sections to chapters and drops + its title from the numbered sequence. Also silent. +* Six complete chapters sat in the tree while being included by nothing, so they + never reached a reader at all. + +The first two are caught by rendering the book and checking that every included +chapter's title survives into the output; the third by walking the include graph. +""" +from __future__ import annotations + +import argparse +import html +import re +import subprocess +import sys +import tempfile +from pathlib import Path + +ASCIIDOC_EXTENSIONS = {".adoc", ".asciidoc"} +INCLUDE_RE = re.compile(r"^include::([^\[]+)\[([^\]]*)\]\s*$") +HEADING_RE = re.compile(r"^(=+) +(\S.*)$") +FENCE_RE = re.compile(r"^(----|\.\.\.\.|````|\*\*\*\*|\+\+\+\+)\s*$") +# Inline AsciiDoc markup that never survives into the rendered heading text. +INLINE_MARKUP_RE = re.compile(r"[`*_#]|\[\[[^\]]*\]\]|\[[^\]]*\]") + + +def parse_lines(path: Path) -> list[str]: + return path.read_text(encoding="utf-8").split("\n") + + +def first_heading(path: Path) -> tuple[int, str] | None: + """Return (level, title) of the file's first heading outside a fenced block.""" + in_fence = False + for line in parse_lines(path): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + match = HEADING_RE.match(line) + if match: + return len(match.group(1)), match.group(2).strip() + return None + + +def normalize(title: str) -> str: + """Reduce a heading to something comparable across AsciiDoc and HTML.""" + text = html.unescape(title) + text = INLINE_MARKUP_RE.sub("", text) + return re.sub(r"\s+", " ", text).strip().lower() + + +class Walker: + """Expands the include graph, recording every edge for the adjacency check.""" + + def __init__(self, root: Path, guide_dir: Path) -> None: + self.root = root + self.guide_dir = guide_dir + self.reachable: dict[Path, str] = {} + self.errors: list[str] = [] + self._visit(root, "") + + def _visit(self, path: Path, attrs_from_parent: str) -> None: + if path in self.reachable: + return + self.reachable[path] = attrs_from_parent + lines = parse_lines(path) + in_fence = False + for index, line in enumerate(lines): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + match = INCLUDE_RE.match(line) + if not match: + continue + target_raw, attrs = match.group(1), match.group(2) + target = (path.parent / target_raw).resolve() + if target.suffix not in ASCIIDOC_EXTENSIONS: + continue # a snippet include, validated by validate-guide-snippets.py + if not target.exists(): + self.errors.append( + f"{path.name}:{index + 1}: include target does not exist: {target_raw}" + ) + continue + if path == self.root: + self._check_manifest_spacing(index, lines, target_raw) + self._visit(target, attrs) + + def _check_manifest_spacing(self, index: int, lines: list[str], target_raw: str) -> None: + """Require a blank line after every include in the top-level manifest. + + Two adjacent include lines put the first target's last line next to the + second target's first line. When the first ends on a paragraph, the second + chapter's title becomes a continuation of it and the chapter disappears + without a single warning -- which is exactly how the native Linux chapter + spent its life rendered as subsections of the Windows one. + """ + following = lines[index + 1] if index + 1 < len(lines) else "" + if following.strip(): + self.errors.append( + f"developer-guide.asciidoc:{index + 1}: include of {target_raw} is not " + f"followed by a blank line. Two adjacent includes let the first " + f"chapter's trailing paragraph swallow the second chapter's title." + ) + + +def render(root: Path) -> str: + with tempfile.TemporaryDirectory() as tmp: + out = Path(tmp) / "guide.html" + result = subprocess.run( + ["asciidoctor", "--require", "rouge", "-o", str(out), str(root)], + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(result.stderr, file=sys.stderr) + raise SystemExit("asciidoctor failed to render the guide") + return out.read_text(encoding="utf-8") + + +def rendered_titles(markup: str) -> dict[str, int]: + """Count every rendered heading, normalized and stripped of its number.""" + body = markup.split('id="content"', 1)[-1] + counts: dict[str, int] = {} + for match in re.finditer(r"]*>(.*?)", body, re.S): + text = re.sub(r"<[^>]+>", "", match.group(2)) + text = re.sub(r"^(Appendix [A-Z]:|(\d+|[A-Z])(\.\d+)*\.)\s*", "", html.unescape(text).strip()) + key = normalize(text) + counts[key] = counts.get(key, 0) + 1 + return counts + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--guide-dir", default="docs/developer-guide", type=Path) + args = parser.parse_args() + + guide_dir = args.guide_dir.resolve() + root = guide_dir / "developer-guide.asciidoc" + if not root.exists(): + raise SystemExit(f"guide root not found: {root}") + + walker = Walker(root, guide_dir) + errors = list(walker.errors) + + # 1. Every chapter in the tree is either in the book or declared out of it. + declared_path = guide_dir / "not-in-book.txt" + declared = set() + if declared_path.exists(): + for line in declared_path.read_text(encoding="utf-8").split("\n"): + line = line.split("#", 1)[0].strip() + if line: + declared.add(line) + + on_disk = { + path.resolve() + for path in guide_dir.rglob("*") + if path.suffix in ASCIIDOC_EXTENSIONS and path.is_file() + } + unreachable = sorted(on_disk - set(walker.reachable)) + for path in unreachable: + name = path.relative_to(guide_dir).as_posix() + if name not in declared: + errors.append( + f"{name}: present in the guide directory but included by nothing, so it " + f"never reaches a reader. Include it, delete it, or list it in " + f"not-in-book.txt with a reason." + ) + reachable_names = {p.relative_to(guide_dir).as_posix() for p in walker.reachable} + for name in sorted(declared & reachable_names): + errors.append(f"not-in-book.txt lists {name}, but it is included. Remove the entry.") + + # 2. A level-0 heading in an included file silently becomes a book part. + for path, attrs in sorted(walker.reachable.items()): + if path == root: + continue + heading = first_heading(path) + if heading and heading[0] == 1 and "leveloffset" not in attrs: + errors.append( + f"{path.name}: opens with a level-0 '= {heading[1]}' heading. Under " + f"doctype:book that renders as a PART and promotes its own sections to " + f"chapters. Use '== ' or include it with leveloffset=+1." + ) + + # 3. Outcome check: every included chapter's title survives into the book. + rendered = rendered_titles(render(root)) + expected: dict[str, list[str]] = {} + for path in sorted(walker.reachable): + if path == root: + continue + heading = first_heading(path) + if heading: + expected.setdefault(normalize(heading[1]), []).append(path.name) + for title, sources in sorted(expected.items()): + # Compare counts, not membership: a chapter sharing its title with another + # heading would otherwise hide the fact that it was swallowed. + if rendered.get(title, 0) < len(sources): + errors.append( + f"{', '.join(sources)}: the title '{title}' appears " + f"{rendered.get(title, 0)} time(s) in the rendered book but " + f"{len(sources)} document(s) declare it. A chapter was swallowed by " + f"whatever precedes it." + ) + + if errors: + for error in errors: + print(f"::error::{error}" if sys.stdout.isatty() is False else error, file=sys.stderr) + print(f"\n{len(errors)} guide structure problem(s).", file=sys.stderr) + return 1 + print( + f"Guide structure OK: {len(walker.reachable) - 1} included documents, " + f"{len(declared)} declared out of book." + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/developer-guide/check-guide-xrefs.py b/scripts/developer-guide/check-guide-xrefs.py new file mode 100755 index 00000000000..c93383b0bd5 --- /dev/null +++ b/scripts/developer-guide/check-guide-xrefs.py @@ -0,0 +1,113 @@ +#!/usr/bin/env python3 +"""Report developer guide cross-references that point at nothing. + +Asciidoctor does not fail, or even warn, on a dangling internal reference. A +bare ``<>`` at least renders as the literal text ``[missing]``, which a +reader might notice; but ``<>`` renders as an ordinary link +with the right words and a href to an id that does not exist, so it looks +perfectly fine and silently goes nowhere. The introduction shipped two of those +pointing at an "Application Lifecycle" sidebar nobody ever wrote. + +Working from the rendered HTML rather than the AsciiDoc source means both forms +are caught, along with anchors declared in any of AsciiDoc's several syntaxes. +""" +from __future__ import annotations + +import argparse +import re +import subprocess +import sys +import tempfile +from collections import defaultdict +from pathlib import Path + +ID_RE = re.compile(r'\bid="([^"]+)"') +HREF_RE = re.compile(r'href="#([^"]+)"') +# Asciidoctor falls back to printing the raw id in brackets when a reference +# resolves to an anchor that carries no title -- an anchor on an image or a +# paragraph rather than on a section. The link works; the sentence reads +# "see [watch-complications]". +RAW_ID_LINK_RE = re.compile(r'\[\1\]') +# Anchor syntaxes, used only to point the reader at the offending source file. +ANCHOR_SOURCE_RE = re.compile(r"<<([^>,]+)") + + +def render(root: Path) -> str: + with tempfile.TemporaryDirectory() as tmp: + out = Path(tmp) / "guide.html" + result = subprocess.run( + ["asciidoctor", "--require", "rouge", "-o", str(out), str(root)], + capture_output=True, + text=True, + ) + if result.returncode != 0: + print(result.stderr, file=sys.stderr) + raise SystemExit("asciidoctor failed to render the guide") + return out.read_text(encoding="utf-8") + + +def source_locations(guide_dir: Path, target: str) -> list[str]: + """Find where a dangling target is referenced, so the error is actionable.""" + hits = [] + for path in sorted(guide_dir.rglob("*")): + if path.suffix not in {".adoc", ".asciidoc"} or not path.is_file(): + continue + for number, line in enumerate(path.read_text(encoding="utf-8").split("\n"), 1): + for match in ANCHOR_SOURCE_RE.finditer(line): + if match.group(1).strip() == target: + hits.append(f"{path.name}:{number}") + return hits + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--guide-dir", default="docs/developer-guide", type=Path) + args = parser.parse_args() + + guide_dir = args.guide_dir.resolve() + root = guide_dir / "developer-guide.asciidoc" + markup = render(root) + + ids = set(ID_RE.findall(markup)) + dangling: dict[str, int] = defaultdict(int) + for target in HREF_RE.findall(markup): + if target not in ids: + dangling[target] += 1 + + raw_id_links: dict[str, int] = defaultdict(int) + for target in RAW_ID_LINK_RE.findall(markup): + raw_id_links[target] += 1 + + if not dangling and not raw_id_links: + print(f"Cross-references OK: {len(ids)} anchors, every internal link resolves.") + return 0 + + for target in sorted(dangling): + where = source_locations(guide_dir, target) + location = ", ".join(where) if where else "not found in source (generated content?)" + print( + f"{location}: <<{target}>> points at an id that does not exist in the " + f"rendered book ({dangling[target]} reference(s)).", + file=sys.stderr, + ) + for target in sorted(raw_id_links): + where = source_locations(guide_dir, target) + location = ", ".join(where) if where else "unknown" + print( + f"{location}: <<{target}>> resolves to an anchor with no title, so the " + f"sentence renders the raw id as \"[{target}]\" " + f"({raw_id_links[target]} reference(s)). Give the reference link text " + f"(<<{target},some words>>) or move the anchor onto the section.", + file=sys.stderr, + ) + total = len(dangling) + len(raw_id_links) + print( + f"\n{len(dangling)} dangling and {len(raw_id_links)} untitled " + f"cross-reference target(s).", + file=sys.stderr, + ) + return 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/developer-guide/check-missing-code-blocks.py b/scripts/developer-guide/check-missing-code-blocks.py new file mode 100755 index 00000000000..378d9af07da --- /dev/null +++ b/scripts/developer-guide/check-missing-code-blocks.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +"""Find prose that promises a code block where no block follows. + +Commit bbdc6058f0 ("Extract developer guide snippets into demos") converted +inline listings into ``include::`` directives and dropped roughly 415 of them on +the floor, leaving the introducing sentence, its colon, and two blank lines where +the code used to be. The Commerce chapter documents a paid service and, after +that pass, contained no code at all. + +``validate-guide-snippets.py`` cannot see this: it validates the blocks that are +present, not the ones that should be. The signature here is the hole itself -- +a sentence ending in a colon followed by two blank lines, which is what a removed +block leaves behind (a real block is separated from its introduction by exactly +one blank line). + +The baseline is a ratchet: it may shrink, never grow. Entries are keyed by the +text of the promising sentence rather than by line number, so ordinary editing +elsewhere in a chapter does not churn the file. +""" +from __future__ import annotations + +import argparse +import re +import sys +from pathlib import Path + +ASCIIDOC_EXTENSIONS = {".adoc", ".asciidoc"} +FENCE_RE = re.compile(r"^(----|\.\.\.\.|````|\*\*\*\*|\+\+\+\+|====|____)\s*$") +# Lines that end in a colon without promising a listing: headings, attributes, +# comments, block titles, list markers, table cells and block delimiters. +NON_PROSE_PREFIX = ("//", "|", "=", ".", ":", "*", "-", "+", "[", "<") + + +def normalize(line: str) -> str: + return re.sub(r"\s+", " ", line.strip()) + + +def scan(path: Path) -> list[tuple[int, str]]: + lines = path.read_text(encoding="utf-8").split("\n") + in_fence = False + findings = [] + for index, line in enumerate(lines): + if FENCE_RE.match(line): + in_fence = not in_fence + continue + if in_fence: + continue + stripped = line.rstrip() + if not stripped.endswith(":"): + continue + if stripped.lstrip().startswith(NON_PROSE_PREFIX): + continue + if index + 2 >= len(lines): + continue + if lines[index + 1].strip() or lines[index + 2].strip(): + continue + findings.append((index + 1, normalize(stripped))) + return findings + + +def load_baseline(path: Path) -> dict[str, set[str]]: + baseline: dict[str, set[str]] = {} + if not path.exists(): + return baseline + for raw in path.read_text(encoding="utf-8").split("\n"): + line = raw.split("#", 1)[0].rstrip() if raw.startswith("#") else raw.rstrip() + if not line.strip(): + continue + name, _, text = line.partition("\t") + if text: + baseline.setdefault(name, set()).add(text) + return baseline + + +def main() -> int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument("--guide-dir", default="docs/developer-guide", type=Path) + parser.add_argument( + "--baseline", + default="scripts/developer-guide/missing-code-blocks-baseline.txt", + type=Path, + ) + parser.add_argument( + "--write-baseline", + action="store_true", + help="rewrite the baseline from the current tree (only ever to shrink it)", + ) + args = parser.parse_args() + + guide_dir = args.guide_dir.resolve() + current: dict[str, set[str]] = {} + located: dict[tuple[str, str], int] = {} + for path in sorted(guide_dir.rglob("*")): + if path.suffix not in ASCIIDOC_EXTENSIONS or not path.is_file(): + continue + name = path.relative_to(guide_dir).as_posix() + for number, text in scan(path): + current.setdefault(name, set()).add(text) + located[(name, text)] = number + + if args.write_baseline: + lines = [ + "# Prose that promises a code block where none follows.", + "# A ratchet: entries may be removed as holes are filled, never added.", + "# Regenerate with check-missing-code-blocks.py --write-baseline.", + ] + for name in sorted(current): + for text in sorted(current[name]): + lines.append(f"{name}\t{text}") + args.baseline.write_text("\n".join(lines) + "\n", encoding="utf-8") + total = sum(len(v) for v in current.values()) + print(f"Wrote baseline with {total} entr(ies) across {len(current)} file(s).") + return 0 + + baseline = load_baseline(args.baseline) + new: list[str] = [] + for name in sorted(current): + for text in sorted(current[name] - baseline.get(name, set())): + number = located[(name, text)] + new.append(f"{name}:{number}: promises a code block that is not there: {text[:100]}") + + total = sum(len(v) for v in current.values()) + baseline_total = sum(len(v) for v in baseline.values()) + if new: + for entry in new: + print(entry, file=sys.stderr) + print( + f"\n{len(new)} new hole(s). Restore the block (the originals are recoverable " + f"from bbdc6058f0~1) or rewrite the sentence so it stops promising one.", + file=sys.stderr, + ) + return 1 + + fixed = baseline_total - total + print( + f"Missing code blocks: {total} known hole(s), none new" + + (f", {fixed} fixed since the baseline was written." if fixed > 0 else ".") + ) + if fixed > 0: + print("Run --write-baseline to bank the progress.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/scripts/developer-guide/guide-links-baseline.txt b/scripts/developer-guide/guide-links-baseline.txt new file mode 100644 index 00000000000..c57b4ae2d19 --- /dev/null +++ b/scripts/developer-guide/guide-links-baseline.txt @@ -0,0 +1,41 @@ +# Developer guide links that do not resolve, or are not TLS. +# A ratchet: entries may be removed as links are fixed, never added. +# Regenerate with check-guide-links.py --write-baseline. +About-This-Guide.asciidoc http://github.com/chen-fishbein/ +About-This-Guide.asciidoc http://github.com/shannah/ +Advanced-Theming.asciidoc http://freebiesbug.com/psd-freebies/iphone-6-ui-kit/ +Advanced-Topics-Under-The-Hood.asciidoc http://developer.android.com/reference/android/content/Context.html +Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/Mobihelp.html +Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/package-summary.html +Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/integration_guide/ +Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/ios/api/ +Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/ios/integration_guide/ +Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/ios/integration_guide/#getting-started +Advanced-Topics-Under-The-Hood.asciidoc http://groovy-lang.org/ +Advanced-Topics-Under-The-Hood.asciidoc http://jruby.org/ +Advanced-Topics-Under-The-Hood.asciidoc http://shannah.github.io/cn1-freshdesk/ +Advanced-Topics-Under-The-Hood.asciidoc http://stackoverflow.com/questions/11421048/android-ios-custom-uri-protocol-handling +Advanced-Topics-Under-The-Hood.asciidoc http://www.mirah.org/ +Ai-And-Speech.asciidoc http://localhost:11434/v1 +Introduction.asciidoc http://teavm.org +Introduction.asciidoc http://www.xmlvm.org/ +Miscellaneous-Features.asciidoc http://wiki.akosma.com/IPhone_URL_Schemes +Monetization.asciidoc http://localhost +The-Components-Of-Codename-One.asciidoc http://awoiaf.westeros.org/index.php/A_Game_of_Thrones +The-Components-Of-Codename-One.asciidoc http://awoiaf.westeros.org/index.php/Portal:Books +The-Components-Of-Codename-One.asciidoc http://www.georgerrmartin.com/ +The-EDT---Event-Dispatch-Thread.asciidoc http://foxtrot.sourceforge.net/ +basics.asciidoc http://docs.oracle.com/Java +basics.asciidoc http://www.miglayout.com/QuickStart.pdf +graphics.asciidoc http://fontello.com/ +io.asciidoc http://twillo.com/ +io.asciidoc http://www.anapioficeandfire.com/api/characters?page=5&pageSize=3 +io.asciidoc http://www.codenameone.com/javadoc/com/codename1/io/ConnectionRequest.html +io.asciidoc http://www.codenameone.com/javadoc/com/codename1/io/NetworkManager.html +performance.asciidoc http://github.com/codenameone/CodenameOne +performance.asciidoc http://github.com/codenameone/codenameone-skins +performance.asciidoc http://optipng.sourceforge.net/ +signing.asciidoc http://developer.android.com/guide/publishing/app-signing.html +signing.asciidoc http://developer.apple.com/ +signing.asciidoc http://get.udid.io/ +signing.asciidoc http://stackoverflow.com/questions/6320255/if-i-revoke-an-existing-distribution-certificate-will-it-mess-up-anything-with diff --git a/scripts/developer-guide/missing-code-blocks-baseline.txt b/scripts/developer-guide/missing-code-blocks-baseline.txt new file mode 100644 index 00000000000..046df94ef0b --- /dev/null +++ b/scripts/developer-guide/missing-code-blocks-baseline.txt @@ -0,0 +1,412 @@ +# Prose that promises a code block where none follows. +# A ratchet: entries may be removed as holes are filled, never added. +# Regenerate with check-missing-code-blocks.py --write-baseline. +3D-Graphics.asciidoc ARGB pixels: +3D-Graphics.asciidoc Querying capabilities at runtime: +3D-Graphics.asciidoc own material, so a textured model renders with no extra setup: +Advanced-Topics-Under-The-Hood.asciidoc For the release build, you will also need to inject some proguard configuration so that important classes don't get stripped out at build time. The FreshDesk SDK instructions state: +Advanced-Topics-Under-The-Hood.asciidoc The default generated stubs for the JavaScript build look like this `com_mycompany_myapp_MyNative`: +Advanced-Topics-Under-The-Hood.asciidoc You can override these methods in the draggable components: +Advertising.asciidoc (for example "Sponsored"): +Advertising.asciidoc anchored at the top or bottom) and call `load()`: +Advertising.asciidoc identifier. The recommended order is to initialize, gather consent, then load: +Advertising.asciidoc manager and provider handle the foreground hook and freshness window for you: +Advertising.asciidoc method that registers its provider with `AdManager`: +Advertising.asciidoc next one when the current ad is dismissed: +Advertising.asciidoc rewards, verify server side rather than trusting the client: +Advertising.asciidoc transitions, no more often than a given interval: +Ai-And-Speech.asciidoc `ChatMessage` values to JSON under a named key: +Ai-And-Speech.asciidoc `SecureStorage` overloads: +Ai-And-Speech.asciidoc `chatStream(...)` can mutate the view directly: +Ai-And-Speech.asciidoc `cn1-ai-stablediffusion` cn1lib (when present in the consumer project): +Ai-And-Speech.asciidoc accepts either inline bytes plus a MIME type or a remote HTTPS URL: +Ai-And-Speech.asciidoc and streams the same text into a `ChatView`: +Ai-And-Speech.asciidoc calling `cancel()` on the resource closes the underlying socket: +Ai-And-Speech.asciidoc clustering: +Ai-And-Speech.asciidoc conversation: +Ai-And-Speech.asciidoc one user message: +Ai-And-Speech.asciidoc returns a string the runtime can hand to `JSONParser`: +Ai-And-Speech.asciidoc safe to log: +Ai-And-Speech.asciidoc you need a custom send pipeline: +Analytics.asciidoc Consent is broken down by category, so you can honor granular choices -- for example allowing crash reporting while declining behavioral analytics: +Analytics.asciidoc Each device is identified by a pseudonymous client id. It's generated on first use and stored locally -- it isn't derived from any hardware identifier. To honor an erasure request ("right to be forgotten"), reset it: +Analytics.asciidoc If you would rather not gate at all, switch the default so collection is active unless the user withdraws it. This places the compliance responsibility on you as the integrator: +Analytics.asciidoc If your application has already obtained consent through its own mechanism -- a custom prompt, a third-party consent-management platform, an enterprise device-management policy, or a jurisdiction where you have determined consent isn't required -- record it once at startup so reporting flows without a second prompt: +Analytics.asciidoc Once at least one provider is registered, report usage from anywhere in your app: +Analytics.asciidoc You can also seed every category and then revoke just one with the builder, for example to allow analytics but not ad storage: +Analytics.asciidoc `FirebaseAnalyticsProvider` forwards to the native Firebase Analytics SDK on Android and iOS. Register it like any other provider: +Analytics.asciidoc `GoogleAnalyticsProvider` uses the GA4 Measurement Protocol. Create it with a measurement id (`G-XXXXXXXX`) and a Measurement Protocol API secret from the GA4 admin console: +Analytics.asciidoc `MatomoAnalyticsProvider` targets Matomo (formerly Piwik) through its HTTP tracking API. Matomo can be self-hosted and supports IP anonymization, which makes it a good fit for privacy-sensitive deployments: +Animations.asciidoc That one command will enable swiping back from `currentForm`. https://www.codenameone.com/javadoc/com/codename1/util/LazyValue.html[LazyValue] allows you to pass a value lazily: +Animations.asciidoc builder: +Annotation-Component-Binding.asciidoc ASM and inserts the equivalent of: +Annotation-Component-Binding.asciidoc After binding, drive validation through the returned handle: +Annotation-JSON-XML-Mapping.asciidoc Hand-write a `Mapper` and register it at startup: +Annotation-JSON-XML-Mapping.asciidoc mutation: +Annotation-SQLite-ORM.asciidoc dao surface isn't enough. Transactions: +App-Review.asciidoc By default the low-rating feedback is collected through an e-mail to the address passed to `setSupportEmail(String)`. To deliver feedback through your own backend, register a `FeedbackListener`: +App-Review.asciidoc Rather than picking the moment yourself, you can let `AppReview` decide based on simple engagement heuristics: how many times the app was launched, how long ago it was installed, and how long since it last asked. Configure it once (for example in your app's `init` or `start` method) and call `registerSession()` on every launch: +App-Review.asciidoc The simplest usage is a single call at a moment that makes sense in your app -- typically right after the user completed something rewarding (finished a level, saved a document, completed an order): +Apple-Wallet-Extension.asciidoc In Java, publish the user's cards whenever they change (typically after login) and keep a fresh token published so Wallet can skip the login screen: +Authentication-And-Identity.asciidoc Firebase Auth isn't an OIDC provider -- it issues Google-Identity-Toolkit-style tokens via REST endpoints. `com.codename1.social.FirebaseAuth` wraps those endpoints: +Authentication-And-Identity.asciidoc For federated sign-in (Google / Apple / Microsoft as Firebase providers), first obtain an ID token via the matching `*Connect` class, then swap it for a Firebase session: +Authentication-And-Identity.asciidoc Or, if the provider exposes a discovery document: +Authentication-And-Identity.asciidoc Pseudo-code (the actual HTTP calls depend on your server library): +Authentication-And-Identity.asciidoc Refresh the Firebase session at app launch: +Authentication-And-Identity.asciidoc `OidcClient` saves the response under a per-issuer + per-client-ID key using `com.codename1.io.oidc.TokenStore.DefaultStorageTokenStore` (which serializes to `com.codename1.io.Storage`). To restore on next launch: +Authentication-And-Identity.asciidoc `com.codename1.social.FacebookConnect` exposes both the old SDK-based `doLogin()` and a new SDK-free `signIn(...)` that uses the system browser via `OidcClient`. Use the new method for the simulator, the web port, and for apps that don't want to bundle the Facebook SDK at all: +Authentication-And-Identity.asciidoc `com.codename1.social.GoogleConnect` now offers a modern `signIn(...)` method that runs entirely through `OidcClient`: +Authentication-And-Identity.asciidoc becomes: +Commerce.asciidoc After a purchase, or on app start, validate the device's receipts with the cloud and refresh the entitlement cache. `refresh()` makes a blocking network call, so run it off the EDT: +Commerce.asciidoc Drive purchases through the manager (these delegate to the `Purchase` API): +Commerce.asciidoc In your `Lifecycle.init` (or wherever you set up purchases): +Commerce.asciidoc The core idea is the *entitlement*: an abstract access right such as `pro` or `remove_ads`. You map one or more store products to an entitlement, and your code only ever checks the entitlement: +Crash-Protection.asciidoc In your `Lifecycle.init`: +Deep-Links-Routing.asciidoc Or annotate a static factory method: +Deep-Links-Routing.asciidoc `AssetLinksBuilder` produces the payload: +Deep-Links-Routing.asciidoc attempts so they can confirm before discarding unsaved work: +Deep-Links-Routing.asciidoc without redirects. The plugin's `AasaBuilder` produces the payload: +Desktop-Integration.asciidoc same scheduling and handling code is shared across phone and desktop: +Device-Input-And-Form-Factors.asciidoc (or `Display`), mirroring the existing `CN.isShiftKeyDown()` family: +Device-Input-And-Form-Factors.asciidoc Any pointer listener receives an `ActionEvent`; call `getPointerEvent()` on it: +Device-Input-And-Form-Factors.asciidoc React to fold changes with a posture listener: +Device-Input-And-Form-Factors.asciidoc handles desktop and touch: +Device-Input-And-Form-Factors.asciidoc https://www.codenameone.com/javadoc/com/codename1/ui/DevicePosture.html[`DevicePosture`]: +Device-Input-And-Form-Factors.asciidoc pointer is a stylus or eraser: +Events.asciidoc The pointer events (touch events) can be intercepted by overriding one or more of these methods in `Component` or `Form`. Notice that unless you want to block functionality you should probably invoke `super` when overriding: +Events.asciidoc Then when you need to broadcast the event use: +Events.asciidoc This will work great but you will still get the default error message from the EDT over that exception. To prevent the event from proceeding to the default error handling you can do this: +Events.asciidoc https://www.codenameone.com/javadoc/com/codename1/ui/events/StyleListener.html[StyleListener] allows components to track changes to the style objects. For example, if the developer does something like: +Game-Assets.asciidoc is registered as solid. Applications add their own without changing the level format: +Game-Builder.asciidoc Loading and playing a level at runtime is three lines: +Game-Builder.asciidoc `start()` it: +Game-Builder.asciidoc the editor: +Game-Development.asciidoc as 2:1 diamonds -- columns step right-and-down, rows step left-and-down: +Game-Development.asciidoc build the frames yourself, which is what `ScrollerGameSample`'s runner does: +Game-Development.asciidoc come from one piece of geometry: +Game-Development.asciidoc each card's bounding box: +Game-Development.asciidoc engine can report completion, so guard with `isVoiceCompletionSupported()`: +Game-Development.asciidoc handle: +Game-Development.asciidoc layers the sprites within a cell: +Game-Development.asciidoc never recompute coordinates: +Game-Development.asciidoc objects, and step it once per frame from your `update`: +Game-Development.asciidoc rendered positions between physics states: +Game-Development.asciidoc required: +Game-Development.asciidoc rotation automatically (in pixels, screen space) -- and the scene draws it there: +Game-Development.asciidoc runs once before the first frame: +Game-Development.asciidoc that always faces the camera, so your existing 2D art keeps working in 3D: +Game-Development.asciidoc the thin point reads exactly the same to the eye: +Game-Development.asciidoc uploading it with `com.codename1.gpu.GraphicsDevice#createTexture(com.codename1.ui.Image)`: +In-Car-Experiences.asciidoc Head units enforce a hard cap on the number of rows/items they display (driver-distraction rules). Query it and trim accordingly: +In-Car-Experiences.asciidoc Register a single `CarApplication` from your app's `init()` -- before a head unit connects -- and return a root `CarScreen` that builds a template: +In-Car-Experiences.asciidoc `CarContext` manages a back stack, mirroring `androidx.car.app`'s `ScreenManager` and CarPlay's `CPInterfaceController`: +In-Car-Experiences.asciidoc `CarScreen` exposes optional lifecycle hooks -- `onCreate()`, `onResume()`, `onPause()`, `onDestroy()` -- and `CarApplication` is notified of connection changes via `onCarConnected(CarContext)` / `onCarDisconnected()`. You can also observe connection globally: +Introduction.asciidoc In a cold start `init(Object)` is invoked followed by the `start()` method. For example, `start()` can be invoked more than once if an app is minimized and restored, see the sidebar <>: +Introduction.asciidoc Next consider the first lifecycle method `init(Object)`. The <> discusses the lifecycle in depth: +Introduction.asciidoc Now that you have a general sense of the lifecycle lets look at the last two lifecycle methods: +Introduction.asciidoc The hello world Java source file looks like this (removed some comments and whitespace): +Maps.asciidoc A native provider only renders on the platforms that ship its SDK. To show a provider's map *everywhere* -- including platforms with no native SDK for it -- register a `WebMapProvider`, which hosts the provider's JavaScript SDK inside a `BrowserComponent`: +Maps.asciidoc Because it needs only a web view, the web provider (id `web`) is the natural last step before the pure-vector fallback in a provider chain. The order is fully customizable from code, so you can express per-platform preferences -- for example "try the native Google SDK, then the web map, then the vector `MapView`": +Maps.asciidoc Every map -- vector or native -- exposes the same operations through `MapSurface`: +Maps.asciidoc For a real app you point `MapView` at a hosted tile service. The simplest keyless option is OpenFreeMap (OpenStreetMap-based vector tiles); for branded styles or higher quotas use a keyed provider such as MapTiler or your own self-hosted Protomaps/TileServer GL endpoint: +Maps.asciidoc When a provider is selected the build server injects that provider's implementation into your app and wires it in; with no provider selected (or when the provider is unavailable at runtime, for example when Google Play Services is missing) `NativeMap` simply renders the vector `MapView` fallback. You can configure the fallback basemap explicitly: +Maven-Creating-CN1Libs.adoc Now try it out. Try adding the following code to your application project's main class (or anywhere in the application project, for that matter): +Maven-Creating-CN1Libs.adoc The simulator dispatches every action on the Codename One EDT through `Display.callSerially`, so your method can call `Display.getInstance()`, `Form.show()`, `Dialog.show()`, `ToastBar.showInfoMessage()` and any other CN1 API. Reflection uses the same classloader that loaded `Display`, so cn1lib internals (including package-private classes) resolve normally: +Media-And-Audio.asciidoc `AudioEffects` provides small platform-neutral PCM transforms that compose with the mixer: +Miscellaneous-Features.asciidoc A typical use of this API would be something like this: +Miscellaneous-Features.asciidoc Easy thread can be created like this: +Miscellaneous-Features.asciidoc For Android 11+ (API 30+), Codename One detects if background location is needed and presents a dialog explaining the need before redirecting the user to the app settings. You can customize the permission prompt message using the localization key `android.permission.ACCESS_BACKGROUND_LOCATION`: +Miscellaneous-Features.asciidoc For example: you can copy the `Image` to `Storage` using: +Miscellaneous-Features.asciidoc However, it gets better, say you want to return a value: +Miscellaneous-Features.asciidoc If you need to apply the scale manually for custom fonts or layout calculations, read the values directly: +Miscellaneous-Features.asciidoc It allows formatting numbers/dates & time based on platform locale. It also provides a great deal of the information you need such as the language/locale information you need to pick the proper resource bundle: +Miscellaneous-Features.asciidoc Or, you can use the `Media`, `MediaManager` and `MediaRecorderBuilder` APIs to capture audio, as a more customizable approach than using the Capture API: +Miscellaneous-Features.asciidoc The `Capture` API also includes a callback based API that uses the `ActionListener` interface to implement capture. For example: you can adapt the previous sample to use this API as such: +Miscellaneous-Features.asciidoc The following code demonstrates usage of the GeoFence API: +Miscellaneous-Features.asciidoc The list of available languages in the resource bundle could be retrieved like this. Notice that this a list that was set by you and doesn't need to confirm to the ISO language code standards: +Miscellaneous-Features.asciidoc These asynchronous calls make things a bit painful to wade through, so the API wraps them in a simplified synchronous version: +Miscellaneous-Features.asciidoc To solve this sort of used case you have two APIs in `Display`: +Miscellaneous-Features.asciidoc You can add more than one attachment by putting them directly into the attachment map for example: +Miscellaneous-Features.asciidoc You can install the bundle using code like this: +Miscellaneous-Features.asciidoc You can send a task to the thread using: +Monetization.asciidoc And you also provide a button to allow the user to manually synchronize the receipts: +Monetization.asciidoc And your `itemPurchased()` callback will need to add a world: +Monetization.asciidoc At the end of the `start()` method: +Monetization.asciidoc At this point, the app can track the sale of the world. To make it more useful, add `ToastBar` feedback for purchase completion: +Monetization.asciidoc In the hello world app you'll use this information in a few different places. On your main form you'll include a label to show the current expiry date, and you allow the user to press a button to synchronize receipts manually if they think the value is out of date: +Monetization.asciidoc In the main form, you want two buttons to subscribe to the `World`, for one month and one year respectively. They look like: +Monetization.asciidoc Now in the `start()` method, add a button that lets the user buy the world: +Monetization.asciidoc Now you'll change your buy code as follows: +Monetization.asciidoc On the server-side, your REST controller is a standard JAX-RS REST interface. The Netbeans web service wizard generated it and then it was modified to suit the purposes here. The methods of the `ReceiptsFacadeREST` class for the REST API are shown here: +Monetization.asciidoc Once implemented, your `fetchReceipts()` method will look like: +Monetization.asciidoc The `createRESTClient()` method shown there creates a `RESTfulWebServiceClient` and configuring it to use basic authentication with a username and password. The idea is that your user would have logged into your app at some point, and you would have a username and password on hand to pass back to the web service with the receipt data so that you can connect the subscription to a user account. The source of that method is listed here: +Monetization.asciidoc The `submitReceipt()` method is a little more complex, as it needs to calculate the new expiry date for your subscription: +Monetization.asciidoc The buy callbacks are like the ones implemented in the regular in-app purchase examples: +Monetization.asciidoc The general usage is as follows: +Monetization.asciidoc The source for your ReceiptStore is as follows: +Monetization.asciidoc To build the signed discount payload required by Apple you can use the `ApplePromotionalOffer` helper: +Monetization.asciidoc You are now ready to see the full magic of the `validateAndSaveReceipt()` method in all its glory: +Monetization.asciidoc You'll register it in your app's init() method so that it's always available: +Monetization.asciidoc You'll set these in your constants: +Motion-Sensors.asciidoc Callbacks arrive on the EDT, so the listener can update the UI directly. Sampling is reference counted: the hardware sensor draws power only while at least one listener is registered. Remove the listener once the screen is no longer visible, typically from the form's `removeNotify`, so the sensor powers down: +Motion-Sensors.asciidoc Register a `GestureListener` for one of the `GestureEvent.TYPE_*` gestures. The accelerometer powers on for the duration that a gesture listener stays registered: +Motion-Sensors.asciidoc The detection thresholds suit most apps as shipped, and you can tune them when a gesture needs to be more or less sensitive: +Motion-Sensors.asciidoc `MotionSensorManager.getInstance()` always returns a manager. On a device without motion hardware every sensor reports as unsupported, so application code can call the API without a null check on the manager itself. `getSensor(int)` returns `null` when the requested sensor isn't available on the current device: +Native-Themes.asciidoc `UIManager.addThemeProps` after the theme has been installed: +Near-Field-Communication.asciidoc Beyond NDEF, the discovered `Tag` exposes accessors for the underlying technologies: +Near-Field-Communication.asciidoc For FeliCa on iOS, set the system codes in `NfcReadOptions`: +Near-Field-Communication.asciidoc Host Card Emulation (HCE) lets the device pretend to be a contactless smart card. A nearby reader (Android phone, payment terminal, access control gate) sends ISO 7816 APDUs and your app responds. Subclass `HostCardEmulationService` and register the instance: +Network-Connectivity.asciidoc To advertise an HTTP server you wrote on port 8080: +Network-Connectivity.asciidoc To associate the device with a specific SSID: +Notifications-And-Background-Execution.asciidoc `ForegroundService` runs a long task while a persistent notification is shown: +Notifications-And-Background-Execution.asciidoc before; the bean has been extended with fluent, backward-compatible setters: +Notifications-And-Background-Execution.asciidoc reconstructed after a cold launch -- pass state through the input data, not fields): +Notifications-And-Background-Execution.asciidoc text, URLs, files or images shared into your app from other apps: +Notifications-And-Background-Execution.asciidoc then reference it from a notification with `setChannelId(...)`: +Printing.asciidoc `Printer.printImage(image, listener)` prints a `com.codename1.ui.Image` directly. The image is encoded to a temporary PNG file behind the scenes and the file is deleted once the print flow finishes: +SVG-Transcoder.asciidoc the generated class directly: +The-Components-Of-Codename-One.asciidoc A common source of confusion in the `MultiButton` is the difference between the icon and the emblem, since both may have an icon image associated with them. The icon is an image representing the entry while the emblem is an optional visual representation of the action that will be undertaken when the element is pressed. Both may be used simultaneously or individually of one another: +The-Components-Of-Codename-One.asciidoc A form is a container with a title, a content area, and an optional menu or menu-bar area. When you call methods such as `add` or `remove` on a form, you're invoking something that maps to this: +The-Components-Of-Codename-One.asciidoc A more practical "real world" example would be working with XML data. You can use something like this to show an XML `Tree`: +The-Components-Of-Codename-One.asciidoc A picker can work with your existing sample using code like this: +The-Components-Of-Codename-One.asciidoc A popup dialog is a common mobile paradigm showing a `Dialog` that points at a specific component. It's a standard `Dialog` that's shown in a unique way: +The-Components-Of-Codename-One.asciidoc And call its methods: +The-Components-Of-Codename-One.asciidoc Call the builder from a Maven plugin, an Ant task or a one-shot `main`: +The-Components-Of-Codename-One.asciidoc For example, if you wish to query a database or a web service you will need to derive the class and perform more advanced filtering by overriding the `filter` method: +The-Components-Of-Codename-One.asciidoc For example, suppose you want to pass a string with text to set in a textarea within the webpage. You can do something like: +The-Components-Of-Codename-One.asciidoc For example, this behavior might not be desired so to block that you can do: +The-Components-Of-Codename-One.asciidoc For example, to enclose the `CheckBox` components in a vertical `ComponentGroup` and the `RadioButton's` in a horizontal group, change the last line of the code above as such: +The-Components-Of-Codename-One.asciidoc For example, you can change the focus `Component` to have an icon: +The-Components-Of-Codename-One.asciidoc For example: +The-Components-Of-Codename-One.asciidoc For example: You might want to create a proxy for the https://developer.mozilla.org/en-You/docs/Web/API/Window/location[window.location] object so that you can access its properties more from Java: +The-Components-Of-Codename-One.asciidoc For example: the demo below uses the GRRM demo data from above to build a `ComboBox`: +The-Components-Of-Codename-One.asciidoc Here is a simple example of a `MultiList` containing a highly popular subject matter: +The-Components-Of-Codename-One.asciidoc Here is a simple example that also shows the difference between the `scale to fill` and `scale to fit` modes: +The-Components-Of-Codename-One.asciidoc If you add to the sample above a `Validator`: +The-Components-Of-Codename-One.asciidoc If you call `Display` directly instead of using `ShareButton`, pass the listener as the final argument to the new overload: +The-Components-Of-Codename-One.asciidoc In the sample below you fetch all the contacts from the device and enable search through them, notice it expects and image called `duke.png` which is the default Codename One icon renamed and placed in the src folder: +The-Components-Of-Codename-One.asciidoc It's also recommended to place the list in the `CENTER` location of a https://www.codenameone.com/javadoc/com/codename1/ui/layouts/BorderLayout.html[BorderLayout] to produce the most effective results. For example: +The-Components-Of-Codename-One.asciidoc Let start by exploring how you can achieve this UI that fetches data from a webservice: +The-Components-Of-Codename-One.asciidoc Masking allows you to accept partial input in one field and implicitly move to the next, this can be used to all types of complex input thanks to the text component API. E.g with the code above you can mask the credit card input so the cursor jumps to the next field implicitly using this code: +The-Components-Of-Codename-One.asciidoc NOTE: In all the sample code below, you can assume that variables named `bc` represent an instance of https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html[BrowserComponent]: +The-Components-Of-Codename-One.asciidoc Normally, to build the model for a renderer of this type, you use something like: +The-Components-Of-Codename-One.asciidoc Notice in the sample below that you associate all the radio buttons with a group but don't do anything with the group as the radio buttons keep the reference internally. You also show the opposite side functionality and icon behavior: +The-Components-Of-Codename-One.asciidoc Notice that this feature works with the on-top and permanent versions of the side menu and not with the legacy versions: +The-Components-Of-Codename-One.asciidoc Notice that when you work with numeric values or anything related to the types mentioned above your code must be aware of the typing. For example: in this case the type is `Double` and not `String`: +The-Components-Of-Codename-One.asciidoc Now look at a more advanced Renderer: +The-Components-Of-Codename-One.asciidoc Now make it a bit more useful: +The-Components-Of-Codename-One.asciidoc Now that you have a webservice lets proceed to create the UI. Check out the code annotations below: +The-Components-Of-Codename-One.asciidoc One of the features of label that moved into `SpanLabel` to some extent is the ability to position the icon. For example, unlike a `Label` the icon position is determined by the layout manager of the composite so `setIconPosition` accepts a `BorderLayout` constraint: +The-Components-Of-Codename-One.asciidoc Or synchronously: +The-Components-Of-Codename-One.asciidoc Simple usage of the `Calendar` class looks something like this: +The-Components-Of-Codename-One.asciidoc Simple usage of the `ToastBar` class looks something like this: +The-Components-Of-Codename-One.asciidoc The Renderer is a simple interface with 2 methods: +The-Components-Of-Codename-One.asciidoc The callbacks you pass to `execute()` and `executeAndWait()` are single-use callbacks. You can’t, for example, store the `callback` variable on the JavaScript side for later use (for example: to respond to a button click event). If you need a "multi-use" callback, you should use the `addJSCallback()` method instead. Its usage looks identical to `execute()`, the difference is that the callback will live on after its first use. For example: Consider the following code: +The-Components-Of-Codename-One.asciidoc The code below provides a brief overview of these options: +The-Components-Of-Codename-One.asciidoc The data of the `Tree` arrives from a model for example: this: +The-Components-Of-Codename-One.asciidoc The first step is creating the webservice call, you won't go into too much detail here as webservices & IO are discussed later in the guide: +The-Components-Of-Codename-One.asciidoc The https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] interface can be implemented by anyone in this case you did a stupid simple implementation: +The-Components-Of-Codename-One.asciidoc The model for the XML hierarchy is implemented as such: +The-Components-Of-Codename-One.asciidoc The most simple/naive implementation may choose to implement the renderer as follows: +The-Components-Of-Codename-One.asciidoc The old API provided a synchronous wrapper around an inherently asynchronous process, and made extensive use of `invokeAndBlock()` underneath the covers. This resulted in a nice API with high-level abstractions that played with a synchronous programming model, but it came with a price-tag in performance, complexity, and predictability. Let’s take a simple example, getting a reference to the "window" object: +The-Components-Of-Codename-One.asciidoc The screenshot above was produced using the following code: +The-Components-Of-Codename-One.asciidoc The split pane component is a bit desktop specific but works reasonably well on devices. To get the image below you changed `SalesDemo.java` in the kitchen sink by changing this: +The-Components-Of-Codename-One.asciidoc Then implement the method `automoveToNext` as: +The-Components-Of-Codename-One.asciidoc Then you can retrieve its properties using the `get()` method: +The-Components-Of-Codename-One.asciidoc This code should output "The result was 7" to the console. It's fully asynchronous, so you can include this code anywhere without worrying about it "bogging down" your code. The full signature of this form of the https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html#execute-java.lang.String-com.codename1.util.SuccessCallback-[execute()] method is: +The-Components-Of-Codename-One.asciidoc This sample below continues from the place where the <> stopped by adding validation to that code: +The-Components-Of-Codename-One.asciidoc This swipe gesture is commonly used in touch interfaces to expose features such as delete, edit etc. It's trivial to use this component by determining the components placed on top and bottom (the revealed component): +The-Components-Of-Codename-One.asciidoc To make the code tighter, keep a reference to the `Component` or extend it as https://www.codenameone.com/javadoc/com/codename1/ui/list/DefaultListCellRenderer.html[DefaultListCellRenderer] does: +The-Components-Of-Codename-One.asciidoc Unlike the `InfiniteScrollAdapter` you can't use the `ContentPane` directly so you've to use a `BorderLayout` and place the `InfiniteContainer` there: +The-Components-Of-Codename-One.asciidoc Unlike the `MultiButton` it uses the `TextArea` internally to break lines seamlessly. The `SpanButton` is far simpler than the `MultiButton` and as a result isn't as configurable: +The-Components-Of-Codename-One.asciidoc What if you want componentName to be red? Just use: +The-Components-Of-Codename-One.asciidoc When using lightweight picker mode (`setUseLightweightPopup(true)`), you can add custom quick-action buttons to the popup. This is useful for actions like setting the date to "Today" or "+7 Days" without scrolling the wheels manually: +The-Components-Of-Codename-One.asciidoc With this: +The-Components-Of-Codename-One.asciidoc You can access JavaScript variables from the context by using code like this: +The-Components-Of-Codename-One.asciidoc You can also delay the showing of the status message using `showDelayed` as such: +The-Components-Of-Codename-One.asciidoc You can also query the context for objects and change their value for example: +The-Components-Of-Codename-One.asciidoc You can also set its properties: +The-Components-Of-Codename-One.asciidoc You can automatically clear a status message/progress after a timeout using the `setExpires` method as such: +The-Components-Of-Codename-One.asciidoc You can combine some demos above including the <> to rank GRRM's books in an interactive way: +The-Components-Of-Codename-One.asciidoc You can convert the sample above to use toggle buttons as such: +The-Components-Of-Codename-One.asciidoc You can dynamically download images directly into the `ImageViewer` with a custom list model like this: +The-Components-Of-Codename-One.asciidoc You can now replace the existing model by removing all the model related logic and changing the constructor call as such: +The-Components-Of-Codename-One.asciidoc You can show a progress indicator in the ToastBar like this: +The-Components-Of-Codename-One.asciidoc You can use the `ImageViewer` as a tool to view a single image which allows you to zoom in/out to that image as such: +The-Components-Of-Codename-One.asciidoc You can wrap a `TextField` with a clearable wrapper to get this effect on all platforms. For example: replace this: +The-Components-Of-Codename-One.asciidoc You need to change this code to use the `addJSCallback()` method as follows: +The-Components-Of-Codename-One.asciidoc You will need to define the following theme constants for the arrow to work: +The-Components-Of-Codename-One.asciidoc `ImageViewer` also supports optional side arrows (material font icons) and an optional thumbnail strip for direct image navigation: +The-Components-Of-Codename-One.asciidoc `InfiniteProgress` can be used in one of two ways either by embedding the component into the UI through something like this: +The-Components-Of-Codename-One.asciidoc `Label` text can be positioned in one of 4 locations as such: +The-Components-Of-Codename-One.asciidoc `Slider` is highly customizable for example: a slider can be used to replicate a 5-star rating widget as such. Notice that this slider will work when its given its preferred size otherwise more stars will appear. That's why you place it within a `FlowLayout`: +The-Components-Of-Codename-One.asciidoc `setRenderingPrototype` accepts a "fake" value that represents a reasonably large amount of data and it will be used to calculate the preferred size. For example: for a multiList that should render 2 lines of text with 20 characters and a 5mm square icon you can do something like this: +The-Components-Of-Codename-One.asciidoc app for specific examples, but here is a high-level view of some code that creates a Pie Chart: +The-Components-Of-Codename-One.asciidoc doing this, you need to define this entry for all entries, for example: +The-Components-Of-Codename-One.asciidoc or a go icon using a hint such as this: +The-EDT---Event-Dispatch-Thread.asciidoc A simplistic approach is to do something like this: +The-EDT---Event-Dispatch-Thread.asciidoc For example, instead of using operation names lets use a more "real world" example: +The-EDT---Event-Dispatch-Thread.asciidoc However, `updateUIWithContentOfFile` should be executed on the EDT and not on a random thread. The right way to do this would therefore be something like this: +The-EDT---Event-Dispatch-Thread.asciidoc IMPORTANT: The Runnable passed to the `callSerially` and `callSeriallyAndWait` methods isn't a `Thread`. Use the `Runnable` interface as a convenient callback interface: +The-EDT---Event-Dispatch-Thread.asciidoc Invoke and block solves this in a unique way you can get almost the exact same behavior by using this: +The-EDT---Event-Dispatch-Thread.asciidoc Or this with Java 8 syntax: +The-EDT---Event-Dispatch-Thread.asciidoc TIP: You can write this code more concisely using Java 8 lambda code as such: +The-EDT---Event-Dispatch-Thread.asciidoc The `callSeriallyAndWait(Runnable)` method blocks the current thread until the method completes, this is useful for cases such as user notification e.g.: +The-EDT---Event-Dispatch-Thread.asciidoc This is best explained by an example. Typical Java code reads as a single sequence as such: +The-EDT---Event-Dispatch-Thread.asciidoc To explain how invokeAndBlock works you can return to the sample above of how the EDT works: +The-EDT---Event-Dispatch-Thread.asciidoc You can visualize the EDT as a loop such as this: +The-EDT---Event-Dispatch-Thread.asciidoc `invokeAndBlock()` works in a similar way to this pseudo-code: +Video-Capture-Constraints.asciidoc Find out if this constraint will be obeyed: +Video-Capture-Constraints.asciidoc For example: +Video-Capture-Constraints.asciidoc Suppose you want to allow the user to capture a short (5 second) clip, in a low resolution, appropriate for sharing on a social media platform. You create your `VideoCaptureConstraints` object as follows: +Video-Capture-Constraints.asciidoc This constraint can then be passed to `Capture.captureVideo()` to get the captured file: +Working-With-iOS.asciidoc Repeating notifications will continue until they're canceled by the app. You can cancel a single notification by calling: +appendix_goal_generate_graphql.adoc The `@GraphQLClient` interface looks like: +appendix_goal_generate_graphql.adoc an `OnComplete>`: +appendix_goal_generate_graphql.adoc ends the stream: +appendix_goal_generate_grpc.adoc Call sites use the static factory: +appendix_goal_generate_grpc.adoc The `@GrpcClient` interface looks like: +appendix_goal_generate_openapi.adoc Call sites use the static factory: +basics.asciidoc For example: see this code where: +basics.asciidoc In the race to make code "`tighter`" you can make this even shorter. Most layout managers have their own custom terse syntax style for example: +basics.asciidoc The UIID's translate the theme elements into a set of `Style` objects. These `Style` objects get their initial values from the theme but can be further manipulated after the fact. To make the text field's foreground color red you could use this code: +basics.asciidoc This works great for regular layouts but might not for constraint based layouts. A constraint based layout accepts another argument. For example: `BorderLayout` needs a location for the `Component`: +graphics.asciidoc (that's: where the numbers appear), and the remaining marks (corresponding with seconds) will be short: +graphics.asciidoc 3. Invert the translation performed in step 1: +graphics.asciidoc A `URLImage` can be created with a mask adapter to apply an effect to an image. This allows you to round downloaded images or apply any sort of masking for example: you can adapt the round mask code above as such: +graphics.asciidoc And you will translate it down slightly so that it overlaps the center. This translation will be performed on the `GeneralPath` object directly rather than through the `Graphics` context: +graphics.asciidoc Center: +graphics.asciidoc For example: a `pointerPressed()` callback method can look like this: +graphics.asciidoc The `animate()` method in the `AnalogClock` class: +graphics.asciidoc The code to instantiate the clock, and start the animation would be something like: +graphics.asciidoc The remaining drawing code is as follows: +graphics.asciidoc The simple use case is pretty trivial: +graphics.asciidoc To do this you can override: +graphics.asciidoc Top Left Corner: +graphics.asciidoc `ConicGradient` - following the same pattern as the `Shape` hierarchy: +graphics.asciidoc can override or augment whatever the default set: +graphics.asciidoc class is the most common option and accepts a list of color stops along a line: +graphics.asciidoc coordinates of the component. You therefore need to get the absolute clock center position to perform the rotation: +graphics.asciidoc for the images. The default is a scale adapter although you might change that to scale fill in the future: +graphics.asciidoc hook. Two ways to install one: +graphics.asciidoc https://www.codenameone.com/javadoc/com/codename1/ui/Graphics.html#isShapeSupported()[`Graphics.isShapeSupported()`] method. For example: +graphics.asciidoc it can be added to a form: +graphics.asciidoc mark at the 12 o'clock position: +graphics.asciidoc method to append curves to the drawing as follows: +graphics.asciidoc methods in the component as follows: +graphics.asciidoc name of icon to `icon_URLImage` then using this in the data: +graphics.asciidoc show this, try to place five of these components on a form inside a https://www.codenameone.com/javadoc/com/codename1/ui/layouts/BorderLayout.html[BorderLayout] and see how it looks: +graphics.asciidoc straight lines rather than curves might look like this: +graphics.asciidoc to draw each individual tick: +io.asciidoc A more advanced usage of the `FileSystemStorage` API can be a `FileSystemStorage` `Tree`: +io.asciidoc A simpler implementation could do something like this: +io.asciidoc Above, if you want to select the IDs of all players that are ranked in the top 2, you can use an expression like: +io.asciidoc Above, you globally find a lastname element with a value of ‘Hewitt’, then grab the parent node of lastname which happens to be the player node, then grab the ID attribute from the player node. Or, you could get the same result from the following simpler statement: +io.asciidoc Above, you selected the IDs of all ranked players. Conversely, you can select the non-ranked players like this: +io.asciidoc After you do that once you can write/read contacts from storage if you so want: +io.asciidoc All you need to do is fill in the code, for this example you will implement the first method for simplicity: +io.asciidoc An `Externalizable` object *must* have a *default public constructor* and must implement the following 4 methods: +io.asciidoc And delete an entry using: +io.asciidoc And vice versa: +io.asciidoc Another approach is to use the `setFailSilently(true)` method on the `ConnectionRequest`. This will prevent the `ConnectionRequest` from displaying any errors to the user. It's a powerful strategy if you use the synchronous version of the APIs for example: +io.asciidoc As part of the premium cloud features it's possible to invoke Log.sendLog() to email a log directly to the developer account. Codename One can do that seamlessly based on changes printed into the log or based on exceptions that are uncaught or logged for example: +io.asciidoc Assuming you added a new date field to the object you can do the following. Notice that a `Date` is a `long` value in Java that can be null. For completeness the full class is presented below: +io.asciidoc Binding makes this all seamless. For example: the code above can be written as: +io.asciidoc By default `GZConnectionRequest` doesn't request gzipped data ( unzips it when its received) but its pretty easy to do so add the HTTP header `Accept-Encoding: gzip` for example: +io.asciidoc Codename One provides many tools to simplify the path between networking/IO & GUI. A common task of showing a wait dialog or progress sign while fetching network data can be simplified by using the https://www.codenameone.com/javadoc/com/codename1/components/InfiniteProgress.html[InfiniteProgress] class for example: +io.asciidoc Developers need to write the data of the object in the externalize method using the methods in the data output stream and read the data of the object in the internalize method for example: +io.asciidoc For a lot of REST requests this will fail because you need to add an HTTP header indicating that you accept JSON results. You have a special case support for that: +io.asciidoc For endpoints that return a list of DTOs, use `fetchAsMappedList`: +io.asciidoc For example, if you wish to have finer grained control over the submission process for example: for making a `HEAD` request you can do this with code like: +io.asciidoc For example: to block all network errors from showing anything to the user you could do something like this: +io.asciidoc For example: you can do something like this in your `init(Object)` method: +io.asciidoc For starters all the common methods of `Object` can be implemented with almost no code: +io.asciidoc If a document is ordered, you might want to select nodes by their position, for example: +io.asciidoc If you continue the example from above to show persistence to the SQL database you can do something like this: +io.asciidoc If you continue your example from above you can do something like this: +io.asciidoc Implementing the `Externalizable` interface is important when you want to store a proprietary object. In this case you must register the object with the `com.codename1.io.Util` class so the externalization algorithm will be able to recognize it by name by invoking: +io.asciidoc In the above code you do the following: +io.asciidoc It's also possible to nest expressions, for example: +io.asciidoc It's also possible to select parent nodes, by using the `..` expression. For example: +io.asciidoc Listing the entries is more interesting: +io.asciidoc Moving on, to select a node based on the existence of an attribute: +io.asciidoc Notice that you can also implement the same thing and much more by avoiding the response listener code and instead overriding the methods of the `ConnectionRequest` class which offers many points to override for example: +io.asciidoc One of the bigger features of properties are their ability to bind UI to a property. For example: if you continue the sample above with the `Contact` class, say you have a text field on the form and you want the property (which you mapped to the database) to have the value of the text field. You could do something like this: +io.asciidoc POST requests use the same builder pattern: +io.asciidoc Server returned headers are a bit trickier to read. You need to subclass the connection request and override the `readHeaders` method for example: +io.asciidoc Since strings might be null sometimes you also included convenience methods to implement such externalization. This effectively writes a boolean before writing the UTF to show whether the string is null: +io.asciidoc Since you assume most developers reading this will be familiar with Java here is the way to implement the multipart upload in the servlet API: +io.asciidoc Some headers are built-in as direct APIs for example: content type is directly exposed within the API since it's a pretty common use case. You can set the content kind of post request using: +io.asciidoc Some objects make sense as global objects, you can use the `Preferences` API to store that data directly but then you don't have the type safety that property objects bring to the table. That's where the binding of property objects to preferences makes sense. For example: say you have a global `Settings` property object you can bind it to preferences using: +io.asciidoc That's what the new method of `URLImage` does: +io.asciidoc The JSON ("JavaScript Object Notation") format is popular on the web for passing values to/from webservices since it works so well with JavaScript. Parsing JSON is as easy but has two different variations. You can use the https://www.codenameone.com/javadoc/com/codename1/io/JSONParser.html[JSONParser] class to build a tree of the JSON data as such: +io.asciidoc The XML processor handles global selections by using a double slash anywhere within the expression, for example: +io.asciidoc The `CachedDataService` will fetch data if it isn't cached locally and cache it. When you "refresh" it will send a special HTTP request that will send back the data if it has been updated since the last refresh: +io.asciidoc The `Rest` API makes it easy to invoke a RESTful webservice without many of the nuances of `ConnectionRequest`. You can use it to define the HTTP method and start building based on that. To get a parsed JSON result from a URL you could do: +io.asciidoc The cool thing is that this works with many component types and property types almost magically. Binding works by using an adapter class to convert the data to/from the component. The adapter itself works with a generic converter for example: this code: +io.asciidoc The sample code below demonstrates listing storage content, adding and viewing entries, and deleting entries: +io.asciidoc The simplest usage of `XMLParser` looks a bit like this: +io.asciidoc There are many methods of interest to keep an eye for: +io.asciidoc This can be tedious to do if you want all requests from your app to use this header. For this use case you can use: +io.asciidoc This means that this code won't compile: +io.asciidoc This should display an error message to the user if there was a problem sending the SMS: +io.asciidoc This simple example allows you to create a server and a client assuming the device supports both: +io.asciidoc This still carries most of the flexibilities of the regular binding for example: you can still get a binding object using: +io.asciidoc To parse a CSV use the https://www.codenameone.com/javadoc/com/codename1/io/CSVParser.html[CSVParser] class as such: +io.asciidoc To use the expression processor when calling a webservice, you could use something like the following to parse JSON (notice this is interchangeable between JSON and XML): +io.asciidoc Up until now this was pretty cool but if you looked at the UI construction code above you would see that it's pretty full of boilerplate code. The thing about boilerplate is that it shows where automation can be applied, that's the exact idea behind the magical "InstantUI" class. This means that the UI above can be generated using this code: +io.asciidoc When you used a POJO you did this: +io.asciidoc With properties you do this: +io.asciidoc Without this feature the code would look like this: +io.asciidoc Working with a database is pretty trivial, the application logic below can send arbitrary queries to the database and present the results in a `Table`. You can probably integrate this code into your app as a debugging tool: +io.asciidoc You added a new syntax: +io.asciidoc You can also add any arbitrary header type you want, for example: a common use case is basic authorization where the authorization header includes the Base64 encoded user/password combination as such: +io.asciidoc You can also override the error callbacks of the various types in the request for example: for a server error code you can do: +io.asciidoc You can always submit data in the `buildRequestBody` but this is flaky and has some limitations in devices/size allowed. HTTP standardized file upload capabilities through the multipart request protocol, this is implemented by countless servers and is well documented. Codename One supports this out of the box: +io.asciidoc You can build a UI that would allow you to edit the `Contact` property in memory: +io.asciidoc You can do some more elaborate bindings such as: +io.asciidoc You can now send hello world as an SMS to the end user. Once this is in place sending an SMS through REST is a matter of using the `Rest` API: +io.asciidoc You can pick either one of these approaches based on your personal preferences. Here you show both uses with the server API: +io.asciidoc You can send a get request to a URL using something like: +io.asciidoc You can then add entries to the contact table using: +io.asciidoc You can update an entry using: +io.asciidoc You might not have noticed this but in the previous verbose code you had lines like: +io.asciidoc You want to extract some data above into simpler string results. You can do this using: +io.asciidoc You've some special case defaults for some common property names, so if your property is named email it will use an email constraint by default. If it's named url or password etc. It will do the "right thing" unless you explicitly state otherwise. You can customize the constraint for a specific property using something like: +io.asciidoc `NetworkManager` also supports synchronous requests which work in a similar way to `Dialog` through the `invokeAndBlock` call and thus don't block the EDT footnote:[Event Dispatch Thread] illegally. For example: you can do something like this: +io.asciidoc `Storage` also offers a simple API in the form of the https://www.codenameone.com/javadoc/com/codename1/io/Preferences.html[Preferences] class. The `Preferences` class lets developers store simple variables, strings, numbers, booleans, and similar values without writing storage code. This is a common use case in applications. For example, you might need to store a server token: +io.asciidoc `com.codename1.io.JSONWriter` class is the complement of `JSONParser`: +io.asciidoc database by name: +io.asciidoc directly: +io.asciidoc download a `ConnectionRequest` to `Storage` using code like this: +io.asciidoc out any change made to the property: +io.asciidoc than a `Map` literal: +io.asciidoc that prevents setting the property to null and defaults it to an empty string: +io.asciidoc the array directly: +io.asciidoc values from parsed JSON: +io.asciidoc you will notice the `final` keyword: +performance.asciidoc All you need to do now is update the `lastScroll` variable whenever user interaction is in place. This works for user touches: +performance.asciidoc For example, Codename One annotates: +performance.asciidoc In the new Contacts demo you have a share button for each contact, the code for constructing a `ShareButton` looks like this: +performance.asciidoc Then you did this within the background loading thread: +performance.asciidoc These icons are in a shared resource file that you load and don't cache. The initial workaround was to cache this resource but a better solution was to convert this code: +performance.asciidoc This works for general scrolling: +performance.asciidoc `Simd` is accessed as a singleton: +performance.asciidoc method returns. This is what the `alloca*` family exposes: +performance.asciidoc the allocation helpers on `Simd`: +security.asciidoc Baking an API key (for example a Google Maps key) into your app is unsafe: anyone can extract it from the binary, and rotating it means shipping a new build. `com.codename1.security.Secrets` solves this. You define the secret once in the Codename One Cloud vault, and the app fetches it at runtime and caches it in `SecureStorage`: +security.asciidoc For HOTP, supply the counter explicitly and increment it after every successful authentication: +security.asciidoc For RSA-signed tokens (RS256/384/512) and ECDSA-signed tokens (ES256/384/512), pass the key to the dynamic overloads: +security.asciidoc Shared secrets are commonly distributed as Base32 strings (the format embedded in QR codes by authenticator apps). `com.codename1.security.Base32` handles the encoding: +security.asciidoc The runtime API is identical on both platforms -- request a token bound to a fresh server nonce, then POST it to your backend for verification: +security.asciidoc There are two simple methods in the `Util` class: +security.asciidoc This works through a new mechanism in storage where you can replace the storage instance with another instance using: +security.asciidoc To block copy & paste on a specific field do: +security.asciidoc To use a key that was generated outside the app, feed the DER bytes to the static factory methods: +security.asciidoc You can leverage that knowledge to change the encryption password on the encryption storage using pseudo-code like this: +security.asciidoc `com.codename1.components.OtpField` is a segmented input -- one box per character, that advances to the next box as the user types and steps back on backspace. This is the standard pattern for SMS confirmation and authenticator-app entry screens: +security.asciidoc `com.codename1.security.Cipher` also covers RSA encryption, and `com.codename1.security.Signature` covers digital signatures (RSA and ECDSA). Keys are represented by `com.codename1.security.PublicKey` and `com.codename1.security.PrivateKey`, which wrap X.509 SubjectPublicKeyInfo and PKCS#8 DER blobs respectively -- the same encodings used by OpenSSL: +security.asciidoc `com.codename1.security.Cipher` exposes AES through the platform's native AES implementation. AES-GCM is the recommended default because it's authenticated -- a single tag-mismatch failure detects any tampering of the ciphertext or the associated data: +security.asciidoc `com.codename1.security.Hash` implements MD5, SHA-1, SHA-224, SHA-256, SHA-384, and SHA-512. SHA-256 is the recommended default for new code; MD5 and SHA-1 are exposed only for compatibility with older protocols since both are broken for collision resistance: +security.asciidoc `com.codename1.security.Hmac` implements HMAC (RFC 2104) on top of any of the hash algorithms above. Use HMAC whenever a message authentication code is needed -- API request signing, session cookie tamper detection, JWT signing with the `HS` family, or TOTP token generation: +security.asciidoc `com.codename1.security.SecureStorage` keeps small string values in the operating system's secure keychain (iOS Keychain Services and the Android Keystore) so they're protected by the platform's secure enclave instead of being written to app storage. The quiet, no-prompt API is three calls: From 40f7eb02df25132dcad6aa5e6b23af1a6fa72145 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 09:04:57 +0300 Subject: [PATCH 02/21] Derive the site's routes instead of guessing them from filenames Review found the link checker was reading Hugo's routes wrong, and it was right: 1055 of the 1262 content pages override their published route with a `slug`, so deriving the path from the filename both invented routes that are never generated and rejected real ones. `content/demo-poker-demo.md` declares `slug: "poker-demo"`, and the checker accepted `/demo-poker-demo.html` while rejecting `/poker-demo/`. Front matter is now parsed for `url`, `slug` and `aliases`, which is what actually decides the route. The same pass removes the prefix allow-list. `/blog/` on it meant any `/blog/anything` link passed unchecked, even though the 922 blog pages are right there in the content tree; static assets are now enumerated from `docs/website/static/` rather than waved through by prefix. Only `/javadoc/` and `/developer-guide/` remain exempt, because both are produced by a build and cannot be enumerated from this repository. Deploy-time redirects are read out of the script that emits them, so `/files/developer-guide.pdf` -- written into `_redirects` at deploy time, not committed -- resolves. Known paths go from 2625 to 4353, and the baseline gains one entry: a genuinely dead `/files/iOS_UI-Kit.psd` that nothing serves. The review also asked for `docs/website/**` in this workflow's trigger paths, so that a route change here re-runs the link check. The gap is real but the fix would run the entire guide build -- maven install, demo build, screenshot generation -- for every blog post the daily publisher pushes, to execute one Python script. The check instead runs from website-docs.yml, which already triggers on `docs/website/**` and needs nothing but a checkout. Both workflows carry a comment saying so, because the reasoning belongs where the next reader looks rather than in a review thread. Also: the two snippet files touched here get the Codename One header the copyright gate requires of modified source, and check-guide-xrefs.py drops a computed-but-unread local. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/developer-guide-docs.yml | 5 + .github/workflows/website-docs.yml | 11 ++ .../ComponentSelectorJava001Snippet.java | 22 ++++ .../ComponentSelectorJava007Snippet.java | 22 ++++ scripts/developer-guide/check-guide-links.py | 111 +++++++++++++++--- scripts/developer-guide/check-guide-xrefs.py | 1 - .../developer-guide/guide-links-baseline.txt | 1 + 7 files changed, 155 insertions(+), 18 deletions(-) diff --git a/.github/workflows/developer-guide-docs.yml b/.github/workflows/developer-guide-docs.yml index f893155d096..7393eaee936 100644 --- a/.github/workflows/developer-guide-docs.yml +++ b/.github/workflows/developer-guide-docs.yml @@ -283,6 +283,11 @@ jobs: python3 scripts/developer-guide/check-guide-structure.py python3 scripts/developer-guide/check-guide-xrefs.py python3 scripts/developer-guide/check-missing-code-blocks.py + # Note this only catches a link the GUIDE breaks. When the website + # moves the route instead -- a page renamed, deleted or re-slugged -- + # the same script runs from website-docs.yml, which triggers on + # docs/website/**. Putting those paths here would run this whole job + # (maven install, demo build, screenshots) for every blog post. python3 scripts/developer-guide/check-guide-links.py - name: Build Developer Guide HTML and PDF diff --git a/.github/workflows/website-docs.yml b/.github/workflows/website-docs.yml index 8fc0a7b1a02..fae57010a9f 100644 --- a/.github/workflows/website-docs.yml +++ b/.github/workflows/website-docs.yml @@ -277,6 +277,17 @@ jobs: --root-dir public public/**/*.html + # The developer guide links into this site, so a page renamed, deleted or + # re-slugged here breaks the guide. The check lives in THIS workflow rather + # than in developer-guide-docs.yml because it is the route data that + # changed: widening that workflow's paths to docs/website/** would run the + # full guide build -- maven install, demo build, screenshot generation -- + # on every blog post the daily publisher pushes, to run one Python script. + # This job already triggers on docs/website/**, and the check needs nothing + # but a checkout. + - name: Check developer guide links against the routes this site serves + run: python3 scripts/developer-guide/check-guide-links.py + - name: Reject absolute codenameone.com links run: | set -euo pipefail diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava001Snippet.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava001Snippet.java index 8c4252dc331..58b44c1b024 100644 --- a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava001Snippet.java +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava001Snippet.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.codenameone.developerguide.snippets.generated; import com.codename1.gpu.*; diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava007Snippet.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava007Snippet.java index d0a80b4d414..e37ee3ced65 100644 --- a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava007Snippet.java +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/ComponentSelectorJava007Snippet.java @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ package com.codenameone.developerguide.snippets.generated; import com.codename1.gpu.*; diff --git a/scripts/developer-guide/check-guide-links.py b/scripts/developer-guide/check-guide-links.py index 6c56e0c5abc..6ad0a610583 100755 --- a/scripts/developer-guide/check-guide-links.py +++ b/scripts/developer-guide/check-guide-links.py @@ -26,8 +26,11 @@ ASCIIDOC_EXTENSIONS = {".adoc", ".asciidoc"} URL_RE = re.compile(r"\bhttps?://[^\s\[\]<>\"'`)]+") -# Served by the site outside the content tree. -STATIC_PREFIXES = ("/javadoc/", "/files/", "/developer-guide/", "/images/", "/img/", "/blog/") +# Trees the site serves that are produced by a build rather than by a file in +# this repository, so nothing here can be enumerated. Everything else -- +# including /blog/ and every static asset -- is derived, because whitelisting a +# prefix silently exempts every path under it from the check. +GENERATED_PREFIXES = ("/javadoc/", "/developer-guide/") # http:// is correct for these: RFC 3161 timestamping servers reject TLS, and # example.com URLs are illustrative rather than fetched. TLS_EXEMPT_HOSTS = {"timestamp.digicert.com", "example.com", "www.example.com"} @@ -38,33 +41,107 @@ SITE_HOSTS = {"codenameone.com", "www.codenameone.com"} +def front_matter(page: Path) -> dict[str, object]: + """Pull the few front-matter keys that decide a page's published route. + + Deliberately not a YAML parse: the tree mixes YAML and TOML front matter and + only three keys matter here. + """ + text = page.read_text(encoding="utf-8", errors="ignore") + lines = text.split("\n") + if not lines or lines[0].strip() not in {"---", "+++"}: + return {} + fence = lines[0].strip() + out: dict[str, object] = {} + aliases: list[str] = [] + in_aliases = False + for line in lines[1:]: + if line.strip() == fence: + break + if in_aliases: + stripped = line.strip() + if stripped.startswith("-"): + aliases.append(stripped.lstrip("- ").strip().strip("\"'")) + continue + in_aliases = False + match = re.match(r'^(url|slug|aliases)\s*[:=]\s*(.*)$', line) + if not match: + continue + key, raw = match.group(1), match.group(2).strip() + if key == "aliases": + if raw in {"", "["}: + in_aliases = True + else: + aliases.extend(v.strip().strip("\"'") for v in raw.strip("[]").split(",") if v.strip()) + continue + out[key] = raw.strip("\"'") + if aliases: + out["aliases"] = aliases + return out + + +def normalize_path(value: str) -> str: + value = value.strip() + if not value: + return "" + if not value.startswith("/"): + value = "/" + value + return value.rstrip("/") or "/" + + def site_paths(repo_root: Path) -> set[str]: - """Every path the website is known to answer on.""" + """Every path the website is known to answer on, derived rather than listed.""" paths: set[str] = set() + redirects = repo_root / "docs/website/static/_redirects" if redirects.exists(): for line in redirects.read_text(encoding="utf-8").split("\n"): parts = line.split() if not parts or parts[0].startswith("#"): continue - paths.add(parts[0].rstrip("/") or "/") + paths.add(normalize_path(parts[0])) if len(parts) > 1 and parts[1].startswith("/"): - paths.add(parts[1].rstrip("/") or "/") + paths.add(normalize_path(parts[1])) + + # Hugo's published route is the section path plus the page's slug, which + # 1055 of the content pages override; deriving it from the filename instead + # both invents routes that are never generated and rejects real ones. content = repo_root / "docs/website/content" if content.exists(): for page in content.rglob("*.md"): - stem = page.relative_to(content).with_suffix("").as_posix() - stem = stem[: -len("/_index")] if stem.endswith("/_index") else stem - if stem in {"_index", "index"}: - paths.add("/") + relative = page.relative_to(content).with_suffix("") + meta = front_matter(page) + for alias in meta.get("aliases", []) or []: + if isinstance(alias, str): + paths.add(normalize_path(alias)) + if meta.get("url"): + paths.add(normalize_path(str(meta["url"]))) continue - paths.add("/" + stem) - paths.add("/" + stem + ".html") - # front matter may override the permalink - head = page.read_text(encoding="utf-8", errors="ignore")[:600] - match = re.search(r'^url:\s*"?([^"\n]+)"?', head, re.M) - if match: - paths.add(match.group(1).strip().rstrip("/") or "/") + parts = list(relative.parts) + if parts and parts[-1] in {"_index", "index"}: + parts.pop() + if meta.get("slug"): + parts = parts[:-1] + [str(meta["slug"])] if parts else [str(meta["slug"])] + paths.add(normalize_path("/".join(parts)) if parts else "/") + + # Some redirects are written into _redirects at deploy time rather than + # committed, so the file in the tree does not list them. Read the paths out + # of the script that emits them instead of assuming a prefix is safe. + for emitter in sorted((repo_root / "scripts/website").glob("*redirect*.sh")): + for match in re.finditer( + r"printf\s+'(/[^\s']+)\s+%s[^']*'", emitter.read_text(encoding="utf-8") + ): + paths.add(normalize_path(match.group(1))) + + # Anything committed under static/ is served at its own path. + static = repo_root / "docs/website/static" + if static.exists(): + for asset in static.rglob("*"): + relative = asset.relative_to(static).as_posix() + paths.add(normalize_path(relative)) + if asset.is_file() and asset.name == "index.html": + paths.add(normalize_path(asset.parent.relative_to(static).as_posix())) + return paths @@ -82,7 +159,7 @@ def findings_for(path: Path, known: set[str]) -> list[tuple[str, str]]: out.append((url, "plain http, not https")) if host in SITE_HOSTS: target = split.path.rstrip("/") or "/" - if target.startswith(STATIC_PREFIXES) or target + "/" in STATIC_PREFIXES: + if target.startswith(GENERATED_PREFIXES) or target + "/" in GENERATED_PREFIXES: continue if target not in known: out.append((url, "the website serves no such path (checked _redirects and the content tree)")) diff --git a/scripts/developer-guide/check-guide-xrefs.py b/scripts/developer-guide/check-guide-xrefs.py index c93383b0bd5..e1fe0a5049d 100755 --- a/scripts/developer-guide/check-guide-xrefs.py +++ b/scripts/developer-guide/check-guide-xrefs.py @@ -100,7 +100,6 @@ def main() -> int: f"(<<{target},some words>>) or move the anchor onto the section.", file=sys.stderr, ) - total = len(dangling) + len(raw_id_links) print( f"\n{len(dangling)} dangling and {len(raw_id_links)} untitled " f"cross-reference target(s).", diff --git a/scripts/developer-guide/guide-links-baseline.txt b/scripts/developer-guide/guide-links-baseline.txt index c57b4ae2d19..683e6809107 100644 --- a/scripts/developer-guide/guide-links-baseline.txt +++ b/scripts/developer-guide/guide-links-baseline.txt @@ -4,6 +4,7 @@ About-This-Guide.asciidoc http://github.com/chen-fishbein/ About-This-Guide.asciidoc http://github.com/shannah/ Advanced-Theming.asciidoc http://freebiesbug.com/psd-freebies/iphone-6-ui-kit/ +Advanced-Theming.asciidoc https://www.codenameone.com/files/iOS_UI-Kit.psd Advanced-Topics-Under-The-Hood.asciidoc http://developer.android.com/reference/android/content/Context.html Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/Mobihelp.html Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/package-summary.html From 7b98f520907d8572fac726c651887f87e14ab526 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 09:32:57 +0300 Subject: [PATCH 03/21] Stop inviting contributions the project no longer accepts Removes "How to contribute updates" from the preface. Nothing in it is lost: where the guide is published and what it covers are both already stated under "Where else to look". The same fact makes four other passages wrong, so they go with it rather than leaving the guide asking for patches on one page and not the next: - The preface described the docs as "reviewed through pull requests", which read as the way in for a reader. - The author list asked readers to add their own name. - The repository bullet advertised pull requests alongside source and issues. - "Add your template to Codename One intializr" solicited third-party project templates through the issue tracker. - "A path to contribute improvements back to the Codename One core" was one of three reasons the sources chapter gave for building the SDK. The other two stand on their own. - The SQLMap limitation note in io.asciidoc asked for pull requests on the parts it does not map. The "Debugging Codename One sources" section in performance.asciidoc was a fork-and-pull-request walkthrough and also pre-Maven -- it drives the NetBeans "Team > Git > Clone" menu and the Ant "build & run libraries" section, neither of which exists in a Maven project. It is replaced by a pointer to the sources chapter, which documents the current flow. Left alone: three "file an issue" mentions in Working with iOS, ComponentSelector and CSS. Those report a rendering regression, a missing wrapper method and a missing PPI table entry -- bug and feature reports, not code contributions. Dropping the walkthrough took two plain-http github.com links with it, so the link baseline shrinks from 39 to 37. Co-Authored-By: Claude Opus 5 (1M context) --- docs/developer-guide/About-This-Guide.asciidoc | 12 +++--------- .../developer-guide/Maven-Project-Templates.adoc | 3 --- .../Working-With-CodenameOne-Sources.asciidoc | 1 - docs/developer-guide/io.asciidoc | 2 +- docs/developer-guide/performance.asciidoc | 16 ++-------------- scripts/developer-guide/guide-links-baseline.txt | 2 -- 6 files changed, 6 insertions(+), 30 deletions(-) diff --git a/docs/developer-guide/About-This-Guide.asciidoc b/docs/developer-guide/About-This-Guide.asciidoc index e6e54b3b600..4810d3e981e 100644 --- a/docs/developer-guide/About-This-Guide.asciidoc +++ b/docs/developer-guide/About-This-Guide.asciidoc @@ -3,13 +3,7 @@ toc::[] [preface] == Preface -This developer guide comes directly from the `docs` directory of the https://github.com/codenameone/CodenameOne/[Codename One Git repository]. The documentation is written in AsciiDoc, reviewed through pull requests, and published from the main branch to several targets. - -=== How to contribute updates - -To contribute updates, clone the repository, create a feature branch, and edit the AsciiDoc files in `docs/developer-guide`. Follow the steps in the link:https://github.com/codenameone/CodenameOne/blob/master/CONTRIBUTING.md[CONTRIBUTING guidelines] to open a GitHub pull request (PR) instead of using the deprecated community wiki. Continuous integration lints and builds each PR automatically. A maintainer then reviews the PR before merging it. After the merge, the content appears in the web manual at https://www.codenameone.com/developer-guide/ and in the downloadable PDF at https://www.codenameone.com/files/developer-guide.pdf[https://www.codenameone.com/files/developer-guide.pdf]. From time to time, major revisions are collected for print-on-demand distribution. - -This guide focuses on tutorial and conceptual material. The complete API reference is available in the https://www.codenameone.com/javadoc/[Codename One Javadoc]. The framework source code and this manual live side by side in Git, so documentation and code improvements follow the same contribution workflow. +This developer guide comes directly from the `docs` directory of the https://github.com/codenameone/CodenameOne/[Codename One Git repository]. The documentation is written in AsciiDoc and published from the main branch to several targets. === Where else to look @@ -21,13 +15,13 @@ This guide is the conceptual and tutorial reference. These resources cover the r * https://www.codenameone.com/how-do-i/["`How Do I?`" video tutorials] -- short screencasts for common tasks. * https://www.codenameone.com/blog/[The Codename One blog] -- release notes and deep dives. * https://www.codenameone.com/discussion-forum/[The community discussion forum] -- questions and answers. -* https://github.com/codenameone/CodenameOne/[The Codename One repository] -- source, issues, and pull requests. +* https://github.com/codenameone/CodenameOne/[The Codename One repository] -- source and issue tracker. <<< **Authors** -This document includes content from contributors and community wiki edits. If you edit pages in the guide, add your name here in alphabetical order by surname: +This document includes content from the following contributors and from earlier community wiki edits: - https://github.com/shai-almog[Shai Almog] - https://github.com/Isborg[Ismael Baum] diff --git a/docs/developer-guide/Maven-Project-Templates.adoc b/docs/developer-guide/Maven-Project-Templates.adoc index 6b9f80ade3d..f047013695c 100644 --- a/docs/developer-guide/Maven-Project-Templates.adoc +++ b/docs/developer-guide/Maven-Project-Templates.adoc @@ -36,7 +36,4 @@ See <> for a more concrete example of the `gen You can test your project template by using it as the `sourceProject` parameter for the `generate-app-project` goal. See <>. -=== Add your template to Codename One intializr - -If you have a project template that you want to share with the community, please file an issue in the https://github.com/codenameone/CodenameOne/issues[Codename One issue tracker] with a link to a GitHub Repository of your project template, and request to have it added https://www.codenameone.com/initializr[Codename One initializr]. diff --git a/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc b/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc index 319d955ac1b..6242e2df1a7 100644 --- a/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc +++ b/docs/developer-guide/Working-With-CodenameOne-Sources.asciidoc @@ -91,7 +91,6 @@ Building the SDK yourself gives you: * Immediate access to fixes and features before they reach a release. * The ability to inspect, debug, and change the framework when you need custom behavior. -* A path to contribute improvements back to the Codename One core. Once you're comfortable with the baseline build, continue with the scripts in `scripts/` or the `BUILDING.md` guide to compile specific ports (Android or diff --git a/docs/developer-guide/io.asciidoc b/docs/developer-guide/io.asciidoc index 49576b198ec..43a688bb488 100644 --- a/docs/developer-guide/io.asciidoc +++ b/docs/developer-guide/io.asciidoc @@ -1466,7 +1466,7 @@ You ignored functions, joins, transactions and a lot of other SQL capabilities. You can use SQL directly to use all these capabilities for example: if you begin a transaction before inserting/updating or deleting this will work as advertised but if a rollback occurs your mapping will be unaware of that so you will need to re-fetch the data. -You will notice you mapped autoincrement so you will try to map things that make sense for various use cases, if you've such a use case you'd appreciate pull requests and feedback on the implementation. +The mapping covers autoincrement and the cases that map cleanly onto a table; a use case that needs more than that subset is better served by dropping to SQL directly. ====== Caching/Collision diff --git a/docs/developer-guide/performance.asciidoc b/docs/developer-guide/performance.asciidoc index 7d118c6f1d0..fa258c13afc 100644 --- a/docs/developer-guide/performance.asciidoc +++ b/docs/developer-guide/performance.asciidoc @@ -449,20 +449,8 @@ For this purpose the Codename One simulator allows you to slow down networking o When you debug your app with your source code you can place breakpoints deep within Codename One and gain unique insight. You can also use the profilers and profile into Codename One to gain similar performance specific insight. -When you run into a bug or a missing feature you can push that feature/fix back to https://www.codenameone.com/[Codename One] using a pull request. GitHub makes that process trivial and in this new video and slides below you show you how. -The steps to use the code are: - -. Signup for GitHub -. Fork http://github.com/codenameone/CodenameOne and - http://github.com/codenameone/codenameone-skins - (also star and watch the projects for good measure). -. Clone the git URLs from the projects into the IDE using the #Team# -> #Git# -> #Clone# menu option. Notice that you must deselect projects in the IDE for the menu to appear. - -. Download the cn1-binaries project from GitHub https://github.com/codenameone/cn1-binaries/archive/master.zip[here]. -. Unzip the cn1-binaries project and make sure the directory has the name cn1-binaries. Verify that cn1-binaries, CodenameOne and codenameone-skins are within the same parent directory. -.In your own project remove the jars both in the build & run libraries section. Replace the build libraries with the `CodenameOne/CodenameOne` project. Replace the runtime libraries with the `CodenameOne/Ports/JavaSEPort` project. - -This allows you to run the existing Codename One project with the Codename One source code and debug into Codename One. You can now also commit, push and send a pull request with the changes. +Running against the framework sources rather than the released jars is covered in +<>. === Device testing Framework/Unit testing diff --git a/scripts/developer-guide/guide-links-baseline.txt b/scripts/developer-guide/guide-links-baseline.txt index 683e6809107..74b964ab4c9 100644 --- a/scripts/developer-guide/guide-links-baseline.txt +++ b/scripts/developer-guide/guide-links-baseline.txt @@ -33,8 +33,6 @@ io.asciidoc http://twillo.com/ io.asciidoc http://www.anapioficeandfire.com/api/characters?page=5&pageSize=3 io.asciidoc http://www.codenameone.com/javadoc/com/codename1/io/ConnectionRequest.html io.asciidoc http://www.codenameone.com/javadoc/com/codename1/io/NetworkManager.html -performance.asciidoc http://github.com/codenameone/CodenameOne -performance.asciidoc http://github.com/codenameone/codenameone-skins performance.asciidoc http://optipng.sourceforge.net/ signing.asciidoc http://developer.android.com/guide/publishing/app-signing.html signing.asciidoc http://developer.apple.com/ From 5f3841da8c79ecc3817d3b0d934725c1f0431c72 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 09:44:12 +0300 Subject: [PATCH 04/21] Make the ratchets actually ratchet, and honour wildcard redirects Two review findings, both real. A baselined entry that no longer reproduces was only a printed reminder, so it stayed in the file and kept a slot open: a later change could restore that exact file and URL, `current - baseline` would still be empty, and the regression would pass. A ratchet that never banks its fixes is not a ratchet. Both checkers now fail on a stale entry and name the command that banks it. Verified by filling one of Commerce's holes and by repointing the dead PSD link -- each fails until the baseline is rewritten. `docs/website/static/_redirects` carries 21 wildcard rules, and recording `/files/cn1libs/*` as a literal string meant a real link to `/files/cn1libs/SomeLib.cn1lib` matched nothing and would have been reported broken. Splat and `:placeholder` sources now compile to patterns. No guide link hits one today, so this was latent rather than failing, but both workflows would have rejected the first one written. Verified in both directions: a link under a wildcard is accepted, and `/files/not-a-real-prefix/Thing.zip` is still rejected. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/developer-guide/check-guide-links.py | 72 ++++++++++++++----- .../check-missing-code-blocks.py | 29 ++++---- 2 files changed, 73 insertions(+), 28 deletions(-) diff --git a/scripts/developer-guide/check-guide-links.py b/scripts/developer-guide/check-guide-links.py index 6ad0a610583..d793aff247b 100755 --- a/scripts/developer-guide/check-guide-links.py +++ b/scripts/developer-guide/check-guide-links.py @@ -89,9 +89,31 @@ def normalize_path(value: str) -> str: return value.rstrip("/") or "/" -def site_paths(repo_root: Path) -> set[str]: - """Every path the website is known to answer on, derived rather than listed.""" +def redirect_pattern(source: str) -> re.Pattern[str] | None: + """Compile a _redirects source that is not a literal path. + + Netlify sources may end in a `*` splat or contain `:placeholder` segments, and + 21 of the rules in this file do. Recording `/files/cn1libs/*` as a literal + string means a real link to `/files/cn1libs/foo.cn1lib` matches nothing and is + reported as broken. + """ + if "*" not in source and ":" not in source: + return None + pattern = "".join( + ".*" if part == "*" else (r"[^/]+" if part.startswith(":") else re.escape(part)) + for part in re.split(r"(\*|:[A-Za-z_][A-Za-z0-9_]*)", source) + if part + ) + return re.compile("^" + pattern + "/?$") + + +def site_paths(repo_root: Path) -> tuple[set[str], list[re.Pattern[str]]]: + """Every path the website is known to answer on, derived rather than listed. + + Returns the literal paths and the patterns for the wildcard redirect rules. + """ paths: set[str] = set() + patterns: list[re.Pattern[str]] = [] redirects = repo_root / "docs/website/static/_redirects" if redirects.exists(): @@ -99,9 +121,15 @@ def site_paths(repo_root: Path) -> set[str]: parts = line.split() if not parts or parts[0].startswith("#"): continue - paths.add(normalize_path(parts[0])) + compiled = redirect_pattern(parts[0]) + if compiled is not None: + patterns.append(compiled) + else: + paths.add(normalize_path(parts[0])) if len(parts) > 1 and parts[1].startswith("/"): - paths.add(normalize_path(parts[1])) + target = redirect_pattern(parts[1]) + if target is None: + paths.add(normalize_path(parts[1])) # Hugo's published route is the section path plus the page's slug, which # 1055 of the content pages override; deriving it from the filename instead @@ -142,10 +170,10 @@ def site_paths(repo_root: Path) -> set[str]: if asset.is_file() and asset.name == "index.html": paths.add(normalize_path(asset.parent.relative_to(static).as_posix())) - return paths + return paths, patterns -def findings_for(path: Path, known: set[str]) -> list[tuple[str, str]]: +def findings_for(path: Path, known: set[str], patterns: list[re.Pattern[str]]) -> list[tuple[str, str]]: out: list[tuple[str, str]] = [] for number, line in enumerate(path.read_text(encoding="utf-8").split("\n"), 1): for url in URL_RE.findall(line): @@ -161,7 +189,7 @@ def findings_for(path: Path, known: set[str]) -> list[tuple[str, str]]: target = split.path.rstrip("/") or "/" if target.startswith(GENERATED_PREFIXES) or target + "/" in GENERATED_PREFIXES: continue - if target not in known: + if target not in known and not any(p.match(target) for p in patterns): out.append((url, "the website serves no such path (checked _redirects and the content tree)")) return out @@ -187,7 +215,7 @@ def main() -> int: args = parser.parse_args() guide_dir = args.guide_dir.resolve() - known = site_paths(args.repo_root.resolve()) + known, patterns = site_paths(args.repo_root.resolve()) if not known: raise SystemExit("could not derive any site paths; is --repo-root correct?") @@ -196,7 +224,7 @@ def main() -> int: if path.suffix not in ASCIIDOC_EXTENSIONS or not path.is_file(): continue name = path.relative_to(guide_dir).as_posix() - for url, reason in findings_for(path, known): + for url, reason in findings_for(path, known, patterns): current[f"{name}\t{url}"] = reason if args.write_baseline: @@ -217,20 +245,32 @@ def main() -> int: baseline = load_baseline(args.baseline) new = sorted(set(current) - baseline) - if new: + # A baselined entry that no longer reproduces has to leave the file. Leaving it + # keeps a slot open: a later change can restore that exact file+URL and + # `current - baseline` stays empty, so the regression sails through. The + # ratchet only ratchets if fixes are banked. + stale = sorted(baseline - set(current)) + if new or stale: for entry in new: name, _, url = entry.partition("\t") print(f"{name}: {url} -- {current[entry]}", file=sys.stderr) - print(f"\n{len(new)} new broken or insecure link(s).", file=sys.stderr) + for entry in stale: + name, _, url = entry.partition("\t") + print( + f"{name}: {url} -- no longer broken, but still in the baseline. Run " + f"check-guide-links.py --write-baseline to bank the fix.", + file=sys.stderr, + ) + print( + f"\n{len(new)} new broken or insecure link(s), {len(stale)} stale baseline entr(ies).", + file=sys.stderr, + ) return 1 - fixed = len(baseline) - len(current) print( - f"Links: {len(current)} known bad link(s) against {len(known)} known site paths, none new" - + (f", {fixed} fixed since the baseline was written." if fixed > 0 else ".") + f"Links: {len(current)} known bad link(s) against {len(known)} known site paths " + f"and {len(patterns)} wildcard rule(s); none new, none stale." ) - if fixed > 0: - print("Run --write-baseline to bank the progress.") return 0 diff --git a/scripts/developer-guide/check-missing-code-blocks.py b/scripts/developer-guide/check-missing-code-blocks.py index 378d9af07da..b050f130a43 100755 --- a/scripts/developer-guide/check-missing-code-blocks.py +++ b/scripts/developer-guide/check-missing-code-blocks.py @@ -119,25 +119,30 @@ def main() -> int: number = located[(name, text)] new.append(f"{name}:{number}: promises a code block that is not there: {text[:100]}") + # A filled hole has to leave the baseline. Leaving it keeps a slot open: a + # later change can empty that exact block again and `current - baseline` stays + # empty, so the regression passes. The ratchet only ratchets if fixes are banked. + stale: list[str] = [] + for name in sorted(baseline): + for text in sorted(baseline[name] - current.get(name, set())): + stale.append( + f"{name}: filled, but still in the baseline: {text[:80]}. Run " + f"check-missing-code-blocks.py --write-baseline to bank the fix." + ) + total = sum(len(v) for v in current.values()) - baseline_total = sum(len(v) for v in baseline.values()) - if new: - for entry in new: + if new or stale: + for entry in new + stale: print(entry, file=sys.stderr) print( - f"\n{len(new)} new hole(s). Restore the block (the originals are recoverable " - f"from bbdc6058f0~1) or rewrite the sentence so it stops promising one.", + f"\n{len(new)} new hole(s) and {len(stale)} stale baseline entr(ies). Restore " + f"the block (the originals are recoverable from bbdc6058f0~1) or rewrite the " + f"sentence so it stops promising one.", file=sys.stderr, ) return 1 - fixed = baseline_total - total - print( - f"Missing code blocks: {total} known hole(s), none new" - + (f", {fixed} fixed since the baseline was written." if fixed > 0 else ".") - ) - if fixed > 0: - print("Run --write-baseline to bank the progress.") + print(f"Missing code blocks: {total} known hole(s), none new, none stale.") return 0 From 5dbf82604544e5086330744bb4389c41ced73056 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 10:00:27 +0300 Subject: [PATCH 05/21] A redirect destination is not proof that a page exists Two more review findings on the link checker, both real. Redirect destinations were being recorded as served routes. A rule whose target was deleted still parses fine, so `/old -> /gone` was making a guide link to `/gone` pass. Only the source counts now. Of the 923 destinations that were not also sources, exactly one route depended on being trusted -- `/` -- and Hugo renders the home page whether or not `content/_index.md` exists, so that is added explicitly rather than inferred from a redirect. Unpublished pages were also being treated as routes. Hugo defaults buildDrafts and buildFuture to false, so a `draft: true` or future-dated page is not served and a link to it is broken. Both are now excluded. The date test does make the result depend on the day it runs, which is noted in the code because it is a real drawback -- it is kept because it mirrors what the site actually serves. Neither case exists in the tree today (0 drafts, 0 future-dated), so this closes a latent false pass rather than fixing a live one. Verified in both directions: a link to a redirect destination that nothing serves is now rejected, and a link to a draft page is rejected until the front matter says `draft: false`, at which point it passes. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/developer-guide/check-guide-links.py | 28 ++++++++++++++++---- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/scripts/developer-guide/check-guide-links.py b/scripts/developer-guide/check-guide-links.py index d793aff247b..a478933185e 100755 --- a/scripts/developer-guide/check-guide-links.py +++ b/scripts/developer-guide/check-guide-links.py @@ -19,6 +19,7 @@ from __future__ import annotations import argparse +import datetime import re import sys from pathlib import Path @@ -64,7 +65,7 @@ def front_matter(page: Path) -> dict[str, object]: aliases.append(stripped.lstrip("- ").strip().strip("\"'")) continue in_aliases = False - match = re.match(r'^(url|slug|aliases)\s*[:=]\s*(.*)$', line) + match = re.match(r'^(url|slug|aliases|draft|date)\s*[:=]\s*(.*)$', line) if not match: continue key, raw = match.group(1), match.group(2).strip() @@ -80,6 +81,19 @@ def front_matter(page: Path) -> dict[str, object]: return out +def is_published(meta: dict[str, object], today: str) -> bool: + """Hugo defaults buildDrafts and buildFuture to false, so neither reaches the site. + + The date test makes the result depend on the day it runs, which is not ideal in + a gate. It is kept because it mirrors what the site actually serves: a link to a + post that has not been published yet is genuinely broken until it is. + """ + if str(meta.get("draft", "")).strip().strip("\"'").lower() in {"true", "yes"}: + return False + date = str(meta.get("date", "")).strip().strip("\"'") + return not (re.match(r"^\d{4}-\d{2}-\d{2}", date) and date[:10] > today) + + def normalize_path(value: str) -> str: value = value.strip() if not value: @@ -126,19 +140,23 @@ def site_paths(repo_root: Path) -> tuple[set[str], list[re.Pattern[str]]]: patterns.append(compiled) else: paths.add(normalize_path(parts[0])) - if len(parts) > 1 and parts[1].startswith("/"): - target = redirect_pattern(parts[1]) - if target is None: - paths.add(normalize_path(parts[1])) + # Only the SOURCE counts. A rule whose destination was deleted still + # sits in this file, so trusting destinations would accept a guide + # link to a page that no longer exists. # Hugo's published route is the section path plus the page's slug, which # 1055 of the content pages override; deriving it from the filename instead # both invents routes that are never generated and rejects real ones. + paths.add("/") # Hugo always renders the home page, _index.md or not + + today = datetime.date.today().isoformat() content = repo_root / "docs/website/content" if content.exists(): for page in content.rglob("*.md"): relative = page.relative_to(content).with_suffix("") meta = front_matter(page) + if not is_published(meta, today): + continue for alias in meta.get("aliases", []) or []: if isinstance(alias, str): paths.add(normalize_path(alias)) From 9275dec727a9da8936baec507c383c875f8c1ead Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 10:14:01 +0300 Subject: [PATCH 06/21] A directory of assets is not a route, and localhost does not do TLS Two more review findings, both producing real false results. `docs/website/static` was walked with `rglob("*")` and every entry recorded, directories included. Copying `static/uploads` to the site does not create a page at `/uploads`, and seven of the top-level static directories have no index page at all, so a link to any of them passed. Only files are recorded now, plus the parent of an `index.html`, which is what actually produces a directory route. `http://localhost:11434/v1` -- the Ollama endpoint the AI chapter documents -- was being reported as insecure. `LOCAL_HOSTS` was consulted for the has-no-dot check but not for the TLS check, so every localhost, 127.0.0.1 and 0.0.0.0 example failed. A local development service is not expected to terminate TLS. The stale-entry rule added in the previous commit caught this on its own: as soon as the exemption landed, both localhost entries were reported as fixed but still baselined. The baseline shrinks from 37 to 35. Verified in both directions: a link to `/uploads/` is now rejected, and `/files/javadocs.zip`, a real file under static, still passes. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/developer-guide/check-guide-links.py | 12 ++++++++---- scripts/developer-guide/guide-links-baseline.txt | 2 -- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/scripts/developer-guide/check-guide-links.py b/scripts/developer-guide/check-guide-links.py index a478933185e..a743d088512 100755 --- a/scripts/developer-guide/check-guide-links.py +++ b/scripts/developer-guide/check-guide-links.py @@ -183,9 +183,13 @@ def site_paths(repo_root: Path) -> tuple[set[str], list[re.Pattern[str]]]: static = repo_root / "docs/website/static" if static.exists(): for asset in static.rglob("*"): - relative = asset.relative_to(static).as_posix() - paths.add(normalize_path(relative)) - if asset.is_file() and asset.name == "index.html": + if not asset.is_file(): + # A directory is not a route. static/uploads holds assets and no + # index page, so recording the directory itself would accept a + # link to /uploads that resolves to nothing. + continue + paths.add(normalize_path(asset.relative_to(static).as_posix())) + if asset.name == "index.html": paths.add(normalize_path(asset.parent.relative_to(static).as_posix())) return paths, patterns @@ -201,7 +205,7 @@ def findings_for(path: Path, known: set[str], patterns: list[re.Pattern[str]]) - if "." not in host and host not in LOCAL_HOSTS: out.append((url, f"host '{host}' has no dot in it and resolves nowhere")) continue - if split.scheme == "http" and host not in TLS_EXEMPT_HOSTS: + if split.scheme == "http" and host not in TLS_EXEMPT_HOSTS | LOCAL_HOSTS: out.append((url, "plain http, not https")) if host in SITE_HOSTS: target = split.path.rstrip("/") or "/" diff --git a/scripts/developer-guide/guide-links-baseline.txt b/scripts/developer-guide/guide-links-baseline.txt index 74b964ab4c9..97edd4149fa 100644 --- a/scripts/developer-guide/guide-links-baseline.txt +++ b/scripts/developer-guide/guide-links-baseline.txt @@ -17,11 +17,9 @@ Advanced-Topics-Under-The-Hood.asciidoc http://jruby.org/ Advanced-Topics-Under-The-Hood.asciidoc http://shannah.github.io/cn1-freshdesk/ Advanced-Topics-Under-The-Hood.asciidoc http://stackoverflow.com/questions/11421048/android-ios-custom-uri-protocol-handling Advanced-Topics-Under-The-Hood.asciidoc http://www.mirah.org/ -Ai-And-Speech.asciidoc http://localhost:11434/v1 Introduction.asciidoc http://teavm.org Introduction.asciidoc http://www.xmlvm.org/ Miscellaneous-Features.asciidoc http://wiki.akosma.com/IPhone_URL_Schemes -Monetization.asciidoc http://localhost The-Components-Of-Codename-One.asciidoc http://awoiaf.westeros.org/index.php/A_Game_of_Thrones The-Components-Of-Codename-One.asciidoc http://awoiaf.westeros.org/index.php/Portal:Books The-Components-Of-Codename-One.asciidoc http://www.georgerrmartin.com/ From 43ebb5a10c056eff0d361d004f7004acbf7d0a3f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 10:23:56 +0300 Subject: [PATCH 07/21] Follow a wildcard redirect to its destination instead of trusting the match `_redirects` ends with `/*.html /:splat/ 301`, which matches every root-level .html path there is. Treating a wildcard match as proof the link works meant `/this-page-does-not-exist.html` passed -- the same mistake as whitelisting a prefix, arriving through a different door. The captures are now substituted into the destination and that is checked instead, recursively, with a depth limit so a loop in the table cannot hang the run. A rule pointing off-site is still accepted because nothing here can verify it. The real .html links keep working: /developer-guide.html, /blog.html, /how-do-i.html and /discussion-forum.html all resolve, and /files/cn1libs/a.cn1lib resolves through its wildcard to an external host, while /this-page-does-not-exist.html and /nonsense do not. Also documented a gap this exposed: Hugo generates taxonomy term pages from front-matter tags rather than from files, so /tags// is not derived. No guide link targets one, and if one is added it will be reported as broken -- the safe direction for a gate to be wrong in. The second finding is that `--write-baseline` can bury a new failure as easily as it banks a fix. That is true of the repository's existing ratchets too -- check-cast-semantics.sh rewrites its baseline unconditionally -- and the real control is that the baseline diff is read in review. Still, the accidental case is worth closing: both checkers now refuse to ADD entries unless `--allow-new` is passed, so shrinking stays frictionless and recording new debt is a deliberate act visible in the command as well as the diff. Verified that a new broken link cannot be baselined away without the flag, that it can with it, and that banking a genuine fix needs no flag at all. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/developer-guide/check-guide-links.py | 95 ++++++++++++++++--- .../check-missing-code-blocks.py | 24 ++++- 2 files changed, 103 insertions(+), 16 deletions(-) diff --git a/scripts/developer-guide/check-guide-links.py b/scripts/developer-guide/check-guide-links.py index a743d088512..1a26e3401ef 100755 --- a/scripts/developer-guide/check-guide-links.py +++ b/scripts/developer-guide/check-guide-links.py @@ -103,31 +103,70 @@ def normalize_path(value: str) -> str: return value.rstrip("/") or "/" -def redirect_pattern(source: str) -> re.Pattern[str] | None: +def redirect_pattern(source: str) -> tuple[re.Pattern[str], list[str]] | None: """Compile a _redirects source that is not a literal path. Netlify sources may end in a `*` splat or contain `:placeholder` segments, and 21 of the rules in this file do. Recording `/files/cn1libs/*` as a literal string means a real link to `/files/cn1libs/foo.cn1lib` matches nothing and is - reported as broken. + reported as broken. Returns the pattern and the capture names in order, so the + destination can be reconstructed from a match. """ if "*" not in source and ":" not in source: return None - pattern = "".join( - ".*" if part == "*" else (r"[^/]+" if part.startswith(":") else re.escape(part)) - for part in re.split(r"(\*|:[A-Za-z_][A-Za-z0-9_]*)", source) - if part - ) - return re.compile("^" + pattern + "/?$") - - -def site_paths(repo_root: Path) -> tuple[set[str], list[re.Pattern[str]]]: + names: list[str] = [] + pattern = "" + for part in re.split(r"(\*|:[A-Za-z_][A-Za-z0-9_]*)", source): + if not part: + continue + if part == "*": + names.append("splat") + pattern += "(.*)" + elif part.startswith(":"): + names.append(part[1:]) + pattern += "([^/]+)" + else: + pattern += re.escape(part) + return re.compile("^" + pattern + "/?$"), names + + +def resolves(target: str, known: set[str], rules: list, depth: int = 0) -> bool: + """Whether a path is served, following wildcard redirects to their destination. + + Accepting every path that merely *matches* a wildcard source is the same + mistake as whitelisting a prefix. `/*.html -> /:splat/ 301` matches any + root-level .html path at all, so `/does-not-exist.html` would pass while + redirecting to a page that is not there. Substitute the captures into the + destination and check that instead. + """ + if target in known: + return True + if depth > 4: # a redirect loop in the table should not hang the check + return False + for compiled, names, destination in rules: + match = compiled.match(target) + if not match: + continue + if not destination or not destination.startswith("/"): + return True # redirects off-site; nothing here can verify it + resolved = destination + for name, value in zip(names, match.groups()): + resolved = resolved.replace(":" + name, value or "") + resolved = normalize_path(resolved) + if resolved == target: + return False + if resolves(resolved, known, rules, depth + 1): + return True + return False + + +def site_paths(repo_root: Path) -> tuple[set[str], list]: """Every path the website is known to answer on, derived rather than listed. Returns the literal paths and the patterns for the wildcard redirect rules. """ paths: set[str] = set() - patterns: list[re.Pattern[str]] = [] + patterns: list = [] redirects = repo_root / "docs/website/static/_redirects" if redirects.exists(): @@ -137,7 +176,8 @@ def site_paths(repo_root: Path) -> tuple[set[str], list[re.Pattern[str]]]: continue compiled = redirect_pattern(parts[0]) if compiled is not None: - patterns.append(compiled) + destination = parts[1] if len(parts) > 1 else "" + patterns.append((compiled[0], compiled[1], destination)) else: paths.add(normalize_path(parts[0])) # Only the SOURCE counts. A rule whose destination was deleted still @@ -147,6 +187,11 @@ def site_paths(repo_root: Path) -> tuple[set[str], list[re.Pattern[str]]]: # Hugo's published route is the section path plus the page's slug, which # 1055 of the content pages override; deriving it from the filename instead # both invents routes that are never generated and rejects real ones. + # + # Known gap: taxonomy term pages (/tags//) are generated by Hugo from + # front-matter tags rather than from a file, so they are not derived here. No + # guide link targets one today. If one is ever added it will be reported as + # broken, which is the safe direction for a gate to be wrong in. paths.add("/") # Hugo always renders the home page, _index.md or not today = datetime.date.today().isoformat() @@ -195,7 +240,7 @@ def site_paths(repo_root: Path) -> tuple[set[str], list[re.Pattern[str]]]: return paths, patterns -def findings_for(path: Path, known: set[str], patterns: list[re.Pattern[str]]) -> list[tuple[str, str]]: +def findings_for(path: Path, known: set[str], patterns: list) -> list[tuple[str, str]]: out: list[tuple[str, str]] = [] for number, line in enumerate(path.read_text(encoding="utf-8").split("\n"), 1): for url in URL_RE.findall(line): @@ -211,7 +256,7 @@ def findings_for(path: Path, known: set[str], patterns: list[re.Pattern[str]]) - target = split.path.rstrip("/") or "/" if target.startswith(GENERATED_PREFIXES) or target + "/" in GENERATED_PREFIXES: continue - if target not in known and not any(p.match(target) for p in patterns): + if not resolves(target, known, patterns): out.append((url, "the website serves no such path (checked _redirects and the content tree)")) return out @@ -234,6 +279,11 @@ def main() -> int: "--baseline", default="scripts/developer-guide/guide-links-baseline.txt", type=Path ) parser.add_argument("--write-baseline", action="store_true") + parser.add_argument( + "--allow-new", + action="store_true", + help="permit --write-baseline to ADD entries; without it the baseline may only shrink", + ) args = parser.parse_args() guide_dir = args.guide_dir.resolve() @@ -250,6 +300,21 @@ def main() -> int: current[f"{name}\t{url}"] = reason if args.write_baseline: + # The command that banks a fix is the same command that could bury a new + # failure. Shrinking is free; growing needs --allow-new, so recording new + # debt is a deliberate act that shows up in the command as well as in the + # baseline diff a reviewer reads. + added = sorted(set(current) - load_baseline(args.baseline)) + if added and not args.allow_new: + for entry in added: + name, _, url = entry.partition("\t") + print(f"{name}: {url}", file=sys.stderr) + print( + f"\nRefusing to add {len(added)} entr(ies) to the baseline. Fix the " + f"link, or pass --allow-new if this is debt you mean to record.", + file=sys.stderr, + ) + return 1 args.baseline.write_text( "\n".join( [ diff --git a/scripts/developer-guide/check-missing-code-blocks.py b/scripts/developer-guide/check-missing-code-blocks.py index b050f130a43..4ca3334cef1 100755 --- a/scripts/developer-guide/check-missing-code-blocks.py +++ b/scripts/developer-guide/check-missing-code-blocks.py @@ -83,7 +83,12 @@ def main() -> int: parser.add_argument( "--write-baseline", action="store_true", - help="rewrite the baseline from the current tree (only ever to shrink it)", + help="rewrite the baseline from the current tree", + ) + parser.add_argument( + "--allow-new", + action="store_true", + help="permit --write-baseline to ADD entries; without it the baseline may only shrink", ) args = parser.parse_args() @@ -99,6 +104,23 @@ def main() -> int: located[(name, text)] = number if args.write_baseline: + # Same reasoning as check-guide-links.py: the command that banks a fix + # must not silently bury a new hole. + previous = load_baseline(args.baseline) + added = sorted( + f"{name}\t{text}" + for name in current + for text in current[name] - previous.get(name, set()) + ) + if added and not args.allow_new: + for entry in added: + print(entry.replace("\t", ": "), file=sys.stderr) + print( + f"\nRefusing to add {len(added)} entr(ies) to the baseline. Restore the " + f"block, or pass --allow-new if this is debt you mean to record.", + file=sys.stderr, + ) + return 1 lines = [ "# Prose that promises a code block where none follows.", "# A ratchet: entries may be removed as holes are filled, never added.", From 0f9a4492b921acc31ff8af9f7c827e0498aae72f Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 10:36:09 +0300 Subject: [PATCH 08/21] Follow literal redirects too, not just wildcard ones The previous commit stopped trusting a wildcard match and followed it to its destination. Review pointed out the literal rules deserve the same treatment, which is right: a literal source is a rule, not a route, and a rule pointing at a page that was later deleted sends the reader to a 404. Every rule is now followed, so `known` holds only paths something actually produces. Measured before changing anything: no guide link resolves solely because its literal redirect source exists while the destination is gone, so this closes a latent hole rather than fixing a live break. The count of bad links is unchanged at 35. Probing the change in the direction it was meant to still work caught a bug in it: following a redirect into `/developer-guide/` failed, because the exemption for build-produced trees lived at the call site and `resolves()` never saw it. A generated tree is reachable both directly and through a redirect, so the test belongs inside `resolves()`. The call site no longer duplicates it. Verified in both directions: `/manual/` and a `/javadoc/...` URL resolve through their redirects, and a rule pointing at a destination nothing serves is still rejected. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/developer-guide/check-guide-links.py | 22 ++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/scripts/developer-guide/check-guide-links.py b/scripts/developer-guide/check-guide-links.py index 1a26e3401ef..ea58d718540 100755 --- a/scripts/developer-guide/check-guide-links.py +++ b/scripts/developer-guide/check-guide-links.py @@ -141,6 +141,10 @@ def resolves(target: str, known: set[str], rules: list, depth: int = 0) -> bool: """ if target in known: return True + if target.startswith(GENERATED_PREFIXES) or target + "/" in GENERATED_PREFIXES: + # Reachable both directly and by following a redirect into it, so the + # test belongs here rather than only at the call site. + return True if depth > 4: # a redirect loop in the table should not hang the check return False for compiled, names, destination in rules: @@ -163,7 +167,9 @@ def resolves(target: str, known: set[str], rules: list, depth: int = 0) -> bool: def site_paths(repo_root: Path) -> tuple[set[str], list]: """Every path the website is known to answer on, derived rather than listed. - Returns the literal paths and the patterns for the wildcard redirect rules. + Returns the literal paths and every redirect rule, each as a matcher, its + capture names and its destination, so a link can be followed rather than + accepted for merely matching. """ paths: set[str] = set() patterns: list = [] @@ -174,12 +180,18 @@ def site_paths(repo_root: Path) -> tuple[set[str], list]: parts = line.split() if not parts or parts[0].startswith("#"): continue + destination = parts[1] if len(parts) > 1 else "" compiled = redirect_pattern(parts[0]) if compiled is not None: - destination = parts[1] if len(parts) > 1 else "" patterns.append((compiled[0], compiled[1], destination)) else: - paths.add(normalize_path(parts[0])) + # A literal source is not a served route either -- it is a rule, + # and a rule pointing at a page that was deleted redirects the + # reader to a 404. Follow it like any other, rather than treating + # the fact that a rule exists as proof the link works. + patterns.append( + (re.compile("^" + re.escape(normalize_path(parts[0])) + "/?$"), [], destination) + ) # Only the SOURCE counts. A rule whose destination was deleted still # sits in this file, so trusting destinations would accept a guide # link to a page that no longer exists. @@ -254,8 +266,6 @@ def findings_for(path: Path, known: set[str], patterns: list) -> list[tuple[str, out.append((url, "plain http, not https")) if host in SITE_HOSTS: target = split.path.rstrip("/") or "/" - if target.startswith(GENERATED_PREFIXES) or target + "/" in GENERATED_PREFIXES: - continue if not resolves(target, known, patterns): out.append((url, "the website serves no such path (checked _redirects and the content tree)")) return out @@ -356,7 +366,7 @@ def main() -> int: print( f"Links: {len(current)} known bad link(s) against {len(known)} known site paths " - f"and {len(patterns)} wildcard rule(s); none new, none stale." + f"and {len(patterns)} redirect rule(s); none new, none stale." ) return 0 From 5025f92e0017944b2d47d99c03f53bc5b4be3a72 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 10:56:28 +0300 Subject: [PATCH 09/21] Hold manifest entries to chapter level, not just away from part level The structure gate rejected a level-0 heading, because that turns a chapter into a book part. Review pointed out the mirror case was still open: a file the manifest includes directly that opens at level 3, or carries no heading at all, renders as a section of whatever chapter precedes it. That is the same silent nesting defect the gate exists for, arriving from the other side. The rendered-title check could not catch it either. It accepts a title at any depth, so a chapter demoted to a subsection still has its title present in the output, and a headingless file is skipped entirely. The rule applies only to entries the manifest includes DIRECTLY. Nested fragments legitimately start deeper -- the twenty appendix_goal_* files open at level 3 under their parent -- and an include carrying leveloffset is shifted on purpose. All 90 direct entries already satisfy this. Verified three ways: a direct include demoted to level 3 fails, one stripped of every heading fails with the headingless message, and the level-3 appendix fragments still pass. Co-Authored-By: Claude Opus 5 (1M context) --- .../developer-guide/check-guide-structure.py | 23 ++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/scripts/developer-guide/check-guide-structure.py b/scripts/developer-guide/check-guide-structure.py index 2c1913dd3e7..3dd908d59ad 100755 --- a/scripts/developer-guide/check-guide-structure.py +++ b/scripts/developer-guide/check-guide-structure.py @@ -68,6 +68,7 @@ def __init__(self, root: Path, guide_dir: Path) -> None: self.root = root self.guide_dir = guide_dir self.reachable: dict[Path, str] = {} + self.direct: set[Path] = set() self.errors: list[str] = [] self._visit(root, "") @@ -96,6 +97,7 @@ def _visit(self, path: Path, attrs_from_parent: str) -> None: ) continue if path == self.root: + self.direct.add(target) self._check_manifest_spacing(index, lines, target_raw) self._visit(target, attrs) @@ -183,7 +185,13 @@ def main() -> int: for name in sorted(declared & reachable_names): errors.append(f"not-in-book.txt lists {name}, but it is included. Remove the entry.") - # 2. A level-0 heading in an included file silently becomes a book part. + # 2. A chapter's own heading level decides whether it is a chapter at all. + # A level-0 heading turns it into a book PART; a level-3 heading (or none) + # makes it a subsection of whatever chapter precedes it. Both nest silently, + # and the rendered-title check below cannot see either, because it accepts a + # title at any depth. Only entries the manifest includes DIRECTLY are held + # to this: nested fragments such as the appendix_goal_* files legitimately + # start at level 3 under their parent. for path, attrs in sorted(walker.reachable.items()): if path == root: continue @@ -194,6 +202,19 @@ def main() -> int: f"doctype:book that renders as a PART and promotes its own sections to " f"chapters. Use '== ' or include it with leveloffset=+1." ) + if path not in walker.direct or "leveloffset" in attrs: + continue + if heading is None: + errors.append( + f"{path.name}: is included directly by developer-guide.asciidoc but has " + f"no heading, so its content is absorbed into the chapter before it." + ) + elif heading[0] > 2: + errors.append( + f"{path.name}: is included directly by developer-guide.asciidoc but opens " + f"at level {heading[0]} ('{'=' * heading[0]} {heading[1]}'), so it renders " + f"as a section of the preceding chapter rather than as a chapter. Use '== '." + ) # 3. Outcome check: every included chapter's title survives into the book. rendered = rendered_titles(render(root)) From 2135841cc9626aa27f6136bd2e3e1fc0bd8eef07 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:08:24 +0300 Subject: [PATCH 10/21] Compute a manifest entry's effective level instead of exempting offsets The chapter-level rule added last commit skipped any direct include carrying a leveloffset, which left the same nesting bug one step along: '== Chapter' at leveloffset=+1 renders as a subsection, and the outcome check cannot see it because it accepts a title at any depth. A leveloffset shifts every heading in the included file, so what decides whether an entry renders as a chapter is the declared level plus the offset. That sum is now what the rule checks, and a headingless direct include fails whatever its offset. No direct entry carries an offset today, so this is closing the bypass rather than fixing a live break. Verified both ways: '==' at leveloffset=+1 fails because it lands at level 3, and '=' at leveloffset=+1 passes because it lands at level 2. Co-Authored-By: Claude Opus 5 (1M context) --- .../developer-guide/check-guide-structure.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/scripts/developer-guide/check-guide-structure.py b/scripts/developer-guide/check-guide-structure.py index 3dd908d59ad..f4ec2e20390 100755 --- a/scripts/developer-guide/check-guide-structure.py +++ b/scripts/developer-guide/check-guide-structure.py @@ -202,18 +202,35 @@ def main() -> int: f"doctype:book that renders as a PART and promotes its own sections to " f"chapters. Use '== ' or include it with leveloffset=+1." ) - if path not in walker.direct or "leveloffset" in attrs: + if path not in walker.direct: continue if heading is None: errors.append( f"{path.name}: is included directly by developer-guide.asciidoc but has " f"no heading, so its content is absorbed into the chapter before it." ) - elif heading[0] > 2: + continue + # A leveloffset shifts every heading in the included file, so what decides + # whether this renders as a chapter is the declared level PLUS the offset. + # Exempting offset includes entirely would leave the same nesting bug one + # step further along: '== Chapter' at leveloffset=+1 renders as a + # subsection, and the outcome check cannot see it because it accepts a + # title at any depth. + offset = 0 + match = re.search(r"leveloffset=([+-]?\d+)", attrs) + if match: + offset = int(match.group(1)) + effective = heading[0] + offset + if effective != 2: + detail = ( + f"level {heading[0]} with leveloffset={offset:+d}" + if offset + else f"level {heading[0]}" + ) errors.append( - f"{path.name}: is included directly by developer-guide.asciidoc but opens " - f"at level {heading[0]} ('{'=' * heading[0]} {heading[1]}'), so it renders " - f"as a section of the preceding chapter rather than as a chapter. Use '== '." + f"{path.name}: is included directly by developer-guide.asciidoc at " + f"{detail}, so it renders at level {effective} rather than as a chapter. " + f"A direct manifest entry must come out at level 2." ) # 3. Outcome check: every included chapter's title survives into the book. From b1ec17586c1c811035f8abbff583f2845f3d113c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:21:30 +0300 Subject: [PATCH 11/21] Count occurrences, because a file can be wrong about the same thing twice Both baselines were keyed by (file, finding) in a set, so repeats of the same finding in one file collapsed into a single entry. Two identical introducing sentences, or the same broken URL named twice, are two things to fix, and the second was free: it could appear later without the check noticing. Not hypothetical on the link side. The checker found 38 occurrences while the baseline recorded 35, so three Freshdesk URLs in Advanced topics were already being hidden by their own duplicates. They are now recorded, which is why the baseline grows by three -- banked with --allow-new, which is exactly the deliberate act that flag exists for. Both baselines are now multisets: one line per occurrence, compared with Counter arithmetic, so an entry appearing twice needs two lines. The missing-code-block report also names every line a repeated sentence sits on rather than the first. Verified by adding a second occurrence of an already-baselined URL to the SAME file -- the file boundary was doing the work in an earlier probe, which is why that probe proved nothing -- and by duplicating one of Commerce's promising sentences, which is now reported at both line numbers. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/developer-guide/check-guide-links.py | 39 ++++++++---- .../check-missing-code-blocks.py | 63 +++++++++---------- .../developer-guide/guide-links-baseline.txt | 5 ++ 3 files changed, 62 insertions(+), 45 deletions(-) diff --git a/scripts/developer-guide/check-guide-links.py b/scripts/developer-guide/check-guide-links.py index ea58d718540..cc058c38478 100755 --- a/scripts/developer-guide/check-guide-links.py +++ b/scripts/developer-guide/check-guide-links.py @@ -19,6 +19,7 @@ from __future__ import annotations import argparse +import collections import datetime import re import sys @@ -271,14 +272,21 @@ def findings_for(path: Path, known: set[str], patterns: list) -> list[tuple[str, return out -def load_baseline(path: Path) -> set[str]: +def load_baseline(path: Path) -> collections.Counter: + """Read the baseline as a multiset: one line per occurrence. + + A file that mentions the same broken URL twice has two problems, not one. + Collapsing them into a set understated the real count -- 35 recorded against + 38 occurrences -- and left a second occurrence of an already-baselined link + free to appear without the check noticing. + """ if not path.exists(): - return set() - return { + return collections.Counter() + return collections.Counter( line.rstrip("\n") for line in path.read_text(encoding="utf-8").split("\n") if line.strip() and not line.startswith("#") - } + ) def main() -> int: @@ -301,20 +309,23 @@ def main() -> int: if not known: raise SystemExit("could not derive any site paths; is --repo-root correct?") - current: dict[str, str] = {} + current: collections.Counter = collections.Counter() + reasons: dict[str, str] = {} for path in sorted(guide_dir.rglob("*")): if path.suffix not in ASCIIDOC_EXTENSIONS or not path.is_file(): continue name = path.relative_to(guide_dir).as_posix() for url, reason in findings_for(path, known, patterns): - current[f"{name}\t{url}"] = reason + entry = f"{name}\t{url}" + current[entry] += 1 + reasons[entry] = reason if args.write_baseline: # The command that banks a fix is the same command that could bury a new # failure. Shrinking is free; growing needs --allow-new, so recording new # debt is a deliberate act that shows up in the command as well as in the # baseline diff a reviewer reads. - added = sorted(set(current) - load_baseline(args.baseline)) + added = sorted((current - load_baseline(args.baseline)).elements()) if added and not args.allow_new: for entry in added: name, _, url = entry.partition("\t") @@ -329,28 +340,30 @@ def main() -> int: "\n".join( [ "# Developer guide links that do not resolve, or are not TLS.", + "# One line per occurrence: a file naming the same bad URL twice gets", + "# two lines, because that is two things to fix.", "# A ratchet: entries may be removed as links are fixed, never added.", "# Regenerate with check-guide-links.py --write-baseline.", ] - + sorted(current) + + sorted(current.elements()) ) + "\n", encoding="utf-8", ) - print(f"Wrote baseline with {len(current)} entr(ies).") + print(f"Wrote baseline with {sum(current.values())} entr(ies).") return 0 baseline = load_baseline(args.baseline) - new = sorted(set(current) - baseline) + new = sorted((current - baseline).elements()) # A baselined entry that no longer reproduces has to leave the file. Leaving it # keeps a slot open: a later change can restore that exact file+URL and # `current - baseline` stays empty, so the regression sails through. The # ratchet only ratchets if fixes are banked. - stale = sorted(baseline - set(current)) + stale = sorted((baseline - current).elements()) if new or stale: for entry in new: name, _, url = entry.partition("\t") - print(f"{name}: {url} -- {current[entry]}", file=sys.stderr) + print(f"{name}: {url} -- {reasons[entry]}", file=sys.stderr) for entry in stale: name, _, url = entry.partition("\t") print( @@ -365,7 +378,7 @@ def main() -> int: return 1 print( - f"Links: {len(current)} known bad link(s) against {len(known)} known site paths " + f"Links: {sum(current.values())} known bad link(s) against {len(known)} known site paths " f"and {len(patterns)} redirect rule(s); none new, none stale." ) return 0 diff --git a/scripts/developer-guide/check-missing-code-blocks.py b/scripts/developer-guide/check-missing-code-blocks.py index 4ca3334cef1..57497869b55 100755 --- a/scripts/developer-guide/check-missing-code-blocks.py +++ b/scripts/developer-guide/check-missing-code-blocks.py @@ -20,6 +20,7 @@ from __future__ import annotations import argparse +import collections import re import sys from pathlib import Path @@ -58,17 +59,21 @@ def scan(path: Path) -> list[tuple[int, str]]: return findings -def load_baseline(path: Path) -> dict[str, set[str]]: - baseline: dict[str, set[str]] = {} +def load_baseline(path: Path) -> collections.Counter: + """Read the baseline as a multiset: one line per occurrence. + + Two identical introducing sentences in one chapter are two holes to fill, and + keying by text alone would let the second appear for free. + """ + baseline: collections.Counter = collections.Counter() if not path.exists(): return baseline for raw in path.read_text(encoding="utf-8").split("\n"): - line = raw.split("#", 1)[0].rstrip() if raw.startswith("#") else raw.rstrip() - if not line.strip(): + if raw.startswith("#") or not raw.strip(): continue - name, _, text = line.partition("\t") + name, _, text = raw.rstrip("\n").partition("\t") if text: - baseline.setdefault(name, set()).add(text) + baseline[f"{name}\t{text}"] += 1 return baseline @@ -93,25 +98,21 @@ def main() -> int: args = parser.parse_args() guide_dir = args.guide_dir.resolve() - current: dict[str, set[str]] = {} - located: dict[tuple[str, str], int] = {} + current: collections.Counter = collections.Counter() + located: dict[str, list[int]] = {} for path in sorted(guide_dir.rglob("*")): if path.suffix not in ASCIIDOC_EXTENSIONS or not path.is_file(): continue name = path.relative_to(guide_dir).as_posix() for number, text in scan(path): - current.setdefault(name, set()).add(text) - located[(name, text)] = number + entry = f"{name}\t{text}" + current[entry] += 1 + located.setdefault(entry, []).append(number) if args.write_baseline: # Same reasoning as check-guide-links.py: the command that banks a fix # must not silently bury a new hole. - previous = load_baseline(args.baseline) - added = sorted( - f"{name}\t{text}" - for name in current - for text in current[name] - previous.get(name, set()) - ) + added = sorted((current - load_baseline(args.baseline)).elements()) if added and not args.allow_new: for entry in added: print(entry.replace("\t", ": "), file=sys.stderr) @@ -126,33 +127,31 @@ def main() -> int: "# A ratchet: entries may be removed as holes are filled, never added.", "# Regenerate with check-missing-code-blocks.py --write-baseline.", ] - for name in sorted(current): - for text in sorted(current[name]): - lines.append(f"{name}\t{text}") + lines.extend(sorted(current.elements())) args.baseline.write_text("\n".join(lines) + "\n", encoding="utf-8") - total = sum(len(v) for v in current.values()) - print(f"Wrote baseline with {total} entr(ies) across {len(current)} file(s).") + total = sum(current.values()) + print(f"Wrote baseline with {total} entr(ies).") return 0 baseline = load_baseline(args.baseline) new: list[str] = [] - for name in sorted(current): - for text in sorted(current[name] - baseline.get(name, set())): - number = located[(name, text)] - new.append(f"{name}:{number}: promises a code block that is not there: {text[:100]}") + for entry in sorted((current - baseline).elements()): + name, _, text = entry.partition("\t") + where = ", ".join(str(n) for n in located.get(entry, [])) + new.append(f"{name}:{where}: promises a code block that is not there: {text[:100]}") # A filled hole has to leave the baseline. Leaving it keeps a slot open: a # later change can empty that exact block again and `current - baseline` stays # empty, so the regression passes. The ratchet only ratchets if fixes are banked. stale: list[str] = [] - for name in sorted(baseline): - for text in sorted(baseline[name] - current.get(name, set())): - stale.append( - f"{name}: filled, but still in the baseline: {text[:80]}. Run " - f"check-missing-code-blocks.py --write-baseline to bank the fix." - ) + for entry in sorted((baseline - current).elements()): + name, _, text = entry.partition("\t") + stale.append( + f"{name}: filled, but still in the baseline: {text[:80]}. Run " + f"check-missing-code-blocks.py --write-baseline to bank the fix." + ) - total = sum(len(v) for v in current.values()) + total = sum(current.values()) if new or stale: for entry in new + stale: print(entry, file=sys.stderr) diff --git a/scripts/developer-guide/guide-links-baseline.txt b/scripts/developer-guide/guide-links-baseline.txt index 97edd4149fa..c2d739300eb 100644 --- a/scripts/developer-guide/guide-links-baseline.txt +++ b/scripts/developer-guide/guide-links-baseline.txt @@ -1,4 +1,6 @@ # Developer guide links that do not resolve, or are not TLS. +# One line per occurrence: a file naming the same bad URL twice gets +# two lines, because that is two things to fix. # A ratchet: entries may be removed as links are fixed, never added. # Regenerate with check-guide-links.py --write-baseline. About-This-Guide.asciidoc http://github.com/chen-fishbein/ @@ -7,9 +9,12 @@ Advanced-Theming.asciidoc http://freebiesbug.com/psd-freebies/iphone-6-ui-kit/ Advanced-Theming.asciidoc https://www.codenameone.com/files/iOS_UI-Kit.psd Advanced-Topics-Under-The-Hood.asciidoc http://developer.android.com/reference/android/content/Context.html Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/Mobihelp.html +Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/Mobihelp.html +Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/package-summary.html Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/package-summary.html Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/integration_guide/ Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/ios/api/ +Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/ios/api/ Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/ios/integration_guide/ Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/ios/integration_guide/#getting-started Advanced-Topics-Under-The-Hood.asciidoc http://groovy-lang.org/ From 0e5eda978b17096c92d8df61ce44abd175eb3dfc Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:30:20 +0300 Subject: [PATCH 12/21] Scan container blocks; only literal content is not prose The fence list treated ==== example, **** sidebar and ____ quote blocks the same as listings, so every promising sentence inside one was skipped. Those delimiters hold ordinary AsciiDoc, and six real holes were hiding behind them in basics.asciidoc -- including the setSameWidth/setSameHeight example this PR's own description cites as the canonical case. It sits in a sidebar, so the check written to find it could not see it. That also explains a number that had been quietly wrong: the first measurement of this defect, before the fence handling existed, found 415. Adding container delimiters to the fence list dropped it to 409, and the drop looked like the fence handling working rather than a blind spot opening. The count is 415 again, and the baseline grows by six, banked with --allow-new. The structure checker carried the same fence list. Narrowing it there changes nothing today -- the twelve includes that sit inside container blocks are all snippet includes, which that walk skips anyway -- but a chapter include or a heading inside a sidebar would have been invisible to it, so both scripts now agree on what a fence is. Verified both directions: a hole inside a sidebar is now reported, and prose inside a listing block is still ignored. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/developer-guide/check-guide-structure.py | 5 ++++- scripts/developer-guide/check-missing-code-blocks.py | 6 +++++- scripts/developer-guide/missing-code-blocks-baseline.txt | 6 ++++++ 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/scripts/developer-guide/check-guide-structure.py b/scripts/developer-guide/check-guide-structure.py index f4ec2e20390..c7a1cb4716f 100755 --- a/scripts/developer-guide/check-guide-structure.py +++ b/scripts/developer-guide/check-guide-structure.py @@ -30,7 +30,10 @@ ASCIIDOC_EXTENSIONS = {".adoc", ".asciidoc"} INCLUDE_RE = re.compile(r"^include::([^\[]+)\[([^\]]*)\]\s*$") HEADING_RE = re.compile(r"^(=+) +(\S.*)$") -FENCE_RE = re.compile(r"^(----|\.\.\.\.|````|\*\*\*\*|\+\+\+\+)\s*$") +# Literal-content blocks only. The container delimiters -- ==== example, +# **** sidebar, ____ quote -- hold ordinary AsciiDoc, so an include or a heading +# inside one is real and must not be skipped. +FENCE_RE = re.compile(r"^(----|\.\.\.\.|````|\+\+\+\+)\s*$") # Inline AsciiDoc markup that never survives into the rendered heading text. INLINE_MARKUP_RE = re.compile(r"[`*_#]|\[\[[^\]]*\]\]|\[[^\]]*\]") diff --git a/scripts/developer-guide/check-missing-code-blocks.py b/scripts/developer-guide/check-missing-code-blocks.py index 57497869b55..ba03af6adf7 100755 --- a/scripts/developer-guide/check-missing-code-blocks.py +++ b/scripts/developer-guide/check-missing-code-blocks.py @@ -26,7 +26,11 @@ from pathlib import Path ASCIIDOC_EXTENSIONS = {".adoc", ".asciidoc"} -FENCE_RE = re.compile(r"^(----|\.\.\.\.|````|\*\*\*\*|\+\+\+\+|====|____)\s*$") +# Only blocks whose CONTENT is literal: listing, literal, fenced code and +# passthrough. The container delimiters -- ==== example, **** sidebar, ____ quote +# -- hold ordinary prose, and skipping them hid six real holes in basics.asciidoc +# alone, among them the setSameWidth example this check was written to find. +FENCE_RE = re.compile(r"^(----|\.\.\.\.|````|\+\+\+\+)\s*$") # Lines that end in a colon without promising a listing: headings, attributes, # comments, block titles, list markers, table cells and block delimiters. NON_PROSE_PREFIX = ("//", "|", "=", ".", ":", "*", "-", "+", "[", "<") diff --git a/scripts/developer-guide/missing-code-blocks-baseline.txt b/scripts/developer-guide/missing-code-blocks-baseline.txt index 046df94ef0b..6b3df360c9b 100644 --- a/scripts/developer-guide/missing-code-blocks-baseline.txt +++ b/scripts/developer-guide/missing-code-blocks-baseline.txt @@ -268,9 +268,15 @@ appendix_goal_generate_grpc.adoc Call sites use the static factory: appendix_goal_generate_grpc.adoc The `@GrpcClient` interface looks like: appendix_goal_generate_openapi.adoc Call sites use the static factory: basics.asciidoc For example: see this code where: +basics.asciidoc From that point on you can write code that looks like this: basics.asciidoc In the race to make code "`tighter`" you can make this even shorter. Most layout managers have their own custom terse syntax style for example: +basics.asciidoc Instead of: +basics.asciidoc Instead of: +basics.asciidoc Some things were changed so you won't have too many conflicts for example, `Log.p` or `Log.e` would have been problematic so you now have: basics.asciidoc The UIID's translate the theme elements into a set of `Style` objects. These `Style` objects get their initial values from the theme but can be further manipulated after the fact. To make the text field's foreground color red you could use this code: +basics.asciidoc The same applies for most network manager calls e.g.: basics.asciidoc This works great for regular layouts but might not for constraint based layouts. A constraint based layout accepts another argument. For example: `BorderLayout` needs a location for the `Component`: +basics.asciidoc and `setSameHeight` methods, for example: graphics.asciidoc (that's: where the numbers appear), and the remaining marks (corresponding with seconds) will be short: graphics.asciidoc 3. Invert the translation performed in step 1: graphics.asciidoc A `URLImage` can be created with a mask adapter to apply an effect to an image. This allows you to round downloaded images or apply any sort of masking for example: you can adapt the round mask code above as such: From 1f7e69d6ef55edef7fb570df920b0340e0f0d68d Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:40:58 +0300 Subject: [PATCH 13/21] Nine of the holes were not holes, and the guide's own routes are enumerable Two review findings. The missing-block detector treated "sentence, two blank lines" as proof the listing was gone, without looking past the blank run. An introduction separated from its block by an extra blank line is untidy, not a hole, and nine of the baselined entries were exactly that -- the block is right there. Reporting them would also have made the gate reject valid AsciiDoc spacing for anyone adding one. The detector now skips the blank run and checks whether a block actually starts, conservatively: only unambiguous starts count, so a real hole is never explained away. The count drops from 415 to 406, and the stale-entry rule identified all nine on its own. That is the second correction to this number today, both in the same direction as the evidence rather than my confidence: 415 measured, 409 after a blind spot was introduced, 415 when it was removed, 406 now that false positives are gone. The second finding: `/developer-guide/` was still blanket-exempt while `/blog/` had been made route-specific, so `/developer-guide/does-not-exist` passed. `scripts/website/build.sh` rsyncs this directory to the site excluding `sketch/` and the AsciiDoc sources, which makes every served path under the guide enumerable. It is derived now, and only `/javadoc/` remains exempt -- genuinely unenumerable, since it is produced from the framework sources at build time. Verified: the guide root and a real image under it resolve, while a fabricated path, a Sketch source and an AsciiDoc file do not. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/developer-guide/check-guide-links.py | 25 +++++++++++++++---- .../check-missing-code-blocks.py | 12 +++++++++ .../missing-code-blocks-baseline.txt | 9 ------- 3 files changed, 32 insertions(+), 14 deletions(-) diff --git a/scripts/developer-guide/check-guide-links.py b/scripts/developer-guide/check-guide-links.py index cc058c38478..a8c6672722a 100755 --- a/scripts/developer-guide/check-guide-links.py +++ b/scripts/developer-guide/check-guide-links.py @@ -28,11 +28,11 @@ ASCIIDOC_EXTENSIONS = {".adoc", ".asciidoc"} URL_RE = re.compile(r"\bhttps?://[^\s\[\]<>\"'`)]+") -# Trees the site serves that are produced by a build rather than by a file in -# this repository, so nothing here can be enumerated. Everything else -- -# including /blog/ and every static asset -- is derived, because whitelisting a -# prefix silently exempts every path under it from the check. -GENERATED_PREFIXES = ("/javadoc/", "/developer-guide/") +# The one tree that genuinely cannot be enumerated from this repository: the +# Javadoc is produced from the framework sources at build time. Everything else, +# /developer-guide/ included, is derived below -- whitelisting a prefix silently +# exempts every path under it from the check. +GENERATED_PREFIXES = ("/javadoc/",) # http:// is correct for these: RFC 3161 timestamping servers reject TLS, and # example.com URLs are illustrative rather than fetched. TLS_EXEMPT_HOSTS = {"timestamp.digicert.com", "example.com", "www.example.com"} @@ -207,6 +207,21 @@ def site_paths(repo_root: Path) -> tuple[set[str], list]: # broken, which is the safe direction for a gate to be wrong in. paths.add("/") # Hugo always renders the home page, _index.md or not + # scripts/website/build.sh renders the guide to /developer-guide/ and rsyncs + # this directory alongside it so relative image links resolve, excluding the + # Sketch sources and the AsciiDoc itself. That makes every served path under + # the guide enumerable, so it does not need a blanket exemption. + guide = repo_root / "docs/developer-guide" + if guide.exists(): + paths.add("/developer-guide") + for asset in guide.rglob("*"): + if not asset.is_file(): + continue + relative = asset.relative_to(guide) + if relative.parts[0] == "sketch" or relative.suffix in {".asciidoc", ".adoc"}: + continue + paths.add(normalize_path("developer-guide/" + relative.as_posix())) + today = datetime.date.today().isoformat() content = repo_root / "docs/website/content" if content.exists(): diff --git a/scripts/developer-guide/check-missing-code-blocks.py b/scripts/developer-guide/check-missing-code-blocks.py index ba03af6adf7..802fb1403ed 100755 --- a/scripts/developer-guide/check-missing-code-blocks.py +++ b/scripts/developer-guide/check-missing-code-blocks.py @@ -34,6 +34,13 @@ # Lines that end in a colon without promising a listing: headings, attributes, # comments, block titles, list markers, table cells and block delimiters. NON_PROSE_PREFIX = ("//", "|", "=", ".", ":", "*", "-", "+", "[", "<") +# What a real block looks like when it starts. An introduction separated from its +# listing by more than one blank line is untidy, not a hole, and reporting it +# would make the gate reject valid AsciiDoc spacing. Deliberately conservative: +# only unambiguous starts, so a genuine hole is never explained away. +BLOCK_START_RE = re.compile( + r'^(\[[a-zA-Z%.#"]|image::|include::|\|===|(----|\.\.\.\.|````|\+\+\+\+|====|\*\*\*\*|____)\s*$)' +) def normalize(line: str) -> str: @@ -59,6 +66,11 @@ def scan(path: Path) -> list[tuple[int, str]]: continue if lines[index + 1].strip() or lines[index + 2].strip(): continue + following = index + 1 + while following < len(lines) and not lines[following].strip(): + following += 1 + if following < len(lines) and BLOCK_START_RE.match(lines[following].strip()): + continue findings.append((index + 1, normalize(stripped))) return findings diff --git a/scripts/developer-guide/missing-code-blocks-baseline.txt b/scripts/developer-guide/missing-code-blocks-baseline.txt index 6b3df360c9b..d5a5f1f6104 100644 --- a/scripts/developer-guide/missing-code-blocks-baseline.txt +++ b/scripts/developer-guide/missing-code-blocks-baseline.txt @@ -5,7 +5,6 @@ 3D-Graphics.asciidoc Querying capabilities at runtime: 3D-Graphics.asciidoc own material, so a textured model renders with no extra setup: Advanced-Topics-Under-The-Hood.asciidoc For the release build, you will also need to inject some proguard configuration so that important classes don't get stripped out at build time. The FreshDesk SDK instructions state: -Advanced-Topics-Under-The-Hood.asciidoc The default generated stubs for the JavaScript build look like this `com_mycompany_myapp_MyNative`: Advanced-Topics-Under-The-Hood.asciidoc You can override these methods in the draggable components: Advertising.asciidoc (for example "Sponsored"): Advertising.asciidoc anchored at the top or bottom) and call `load()`: @@ -75,7 +74,6 @@ Device-Input-And-Form-Factors.asciidoc https://www.codenameone.com/javadoc/com/c Device-Input-And-Form-Factors.asciidoc pointer is a stylus or eraser: Events.asciidoc The pointer events (touch events) can be intercepted by overriding one or more of these methods in `Component` or `Form`. Notice that unless you want to block functionality you should probably invoke `super` when overriding: Events.asciidoc Then when you need to broadcast the event use: -Events.asciidoc This will work great but you will still get the default error message from the EDT over that exception. To prevent the event from proceeding to the default error handling you can do this: Events.asciidoc https://www.codenameone.com/javadoc/com/codename1/ui/events/StyleListener.html[StyleListener] allows components to track changes to the style objects. For example, if the developer does something like: Game-Assets.asciidoc is registered as solid. Applications add their own without changing the level format: Game-Builder.asciidoc Loading and playing a level at runtime is three lines: @@ -106,7 +104,6 @@ Introduction.asciidoc Next consider the first lifecycle method `init(Object)`. T Introduction.asciidoc Now that you have a general sense of the lifecycle lets look at the last two lifecycle methods: Introduction.asciidoc The hello world Java source file looks like this (removed some comments and whitespace): Maps.asciidoc A native provider only renders on the platforms that ship its SDK. To show a provider's map *everywhere* -- including platforms with no native SDK for it -- register a `WebMapProvider`, which hosts the provider's JavaScript SDK inside a `BrowserComponent`: -Maps.asciidoc Because it needs only a web view, the web provider (id `web`) is the natural last step before the pure-vector fallback in a provider chain. The order is fully customizable from code, so you can express per-platform preferences -- for example "try the native Google SDK, then the web map, then the vector `MapView`": Maps.asciidoc Every map -- vector or native -- exposes the same operations through `MapSurface`: Maps.asciidoc For a real app you point `MapView` at a hosted tile service. The simplest keyless option is OpenFreeMap (OpenStreetMap-based vector tiles); for branded styles or higher quotas use a keyed provider such as MapTiler or your own self-hosted Protomaps/TileServer GL endpoint: Maps.asciidoc When a provider is selected the build server injects that provider's implementation into your app and wires it in; with no provider selected (or when the provider is unavailable at runtime, for example when Google Play Services is missing) `NativeMap` simply renders the vector `MapView` fallback. You can configure the fallback basemap explicitly: @@ -120,7 +117,6 @@ Miscellaneous-Features.asciidoc For example: you can copy the `Image` to `Storag Miscellaneous-Features.asciidoc However, it gets better, say you want to return a value: Miscellaneous-Features.asciidoc If you need to apply the scale manually for custom fonts or layout calculations, read the values directly: Miscellaneous-Features.asciidoc It allows formatting numbers/dates & time based on platform locale. It also provides a great deal of the information you need such as the language/locale information you need to pick the proper resource bundle: -Miscellaneous-Features.asciidoc Or, you can use the `Media`, `MediaManager` and `MediaRecorderBuilder` APIs to capture audio, as a more customizable approach than using the Capture API: Miscellaneous-Features.asciidoc The `Capture` API also includes a callback based API that uses the `ActionListener` interface to implement capture. For example: you can adapt the previous sample to use this API as such: Miscellaneous-Features.asciidoc The following code demonstrates usage of the GeoFence API: Miscellaneous-Features.asciidoc The list of available languages in the resource bundle could be retrieved like this. Notice that this a list that was set by you and doesn't need to confirm to the ISO language code standards: @@ -158,10 +154,7 @@ Near-Field-Communication.asciidoc For FeliCa on iOS, set the system codes in `Nf Near-Field-Communication.asciidoc Host Card Emulation (HCE) lets the device pretend to be a contactless smart card. A nearby reader (Android phone, payment terminal, access control gate) sends ISO 7816 APDUs and your app responds. Subclass `HostCardEmulationService` and register the instance: Network-Connectivity.asciidoc To advertise an HTTP server you wrote on port 8080: Network-Connectivity.asciidoc To associate the device with a specific SSID: -Notifications-And-Background-Execution.asciidoc `ForegroundService` runs a long task while a persistent notification is shown: Notifications-And-Background-Execution.asciidoc before; the bean has been extended with fluent, backward-compatible setters: -Notifications-And-Background-Execution.asciidoc reconstructed after a cold launch -- pass state through the input data, not fields): -Notifications-And-Background-Execution.asciidoc text, URLs, files or images shared into your app from other apps: Notifications-And-Background-Execution.asciidoc then reference it from a notification with `setChannelId(...)`: Printing.asciidoc `Printer.printImage(image, listener)` prints a `com.codename1.ui.Image` directly. The image is encoded to a temporary PNG file behind the scenes and the file is deleted once the print flow finishes: SVG-Transcoder.asciidoc the generated class directly: @@ -310,7 +303,6 @@ io.asciidoc Above, if you want to select the IDs of all players that are ranked io.asciidoc Above, you globally find a lastname element with a value of ‘Hewitt’, then grab the parent node of lastname which happens to be the player node, then grab the ID attribute from the player node. Or, you could get the same result from the following simpler statement: io.asciidoc Above, you selected the IDs of all ranked players. Conversely, you can select the non-ranked players like this: io.asciidoc After you do that once you can write/read contacts from storage if you so want: -io.asciidoc All you need to do is fill in the code, for this example you will implement the first method for simplicity: io.asciidoc An `Externalizable` object *must* have a *default public constructor* and must implement the following 4 methods: io.asciidoc And delete an entry using: io.asciidoc And vice versa: @@ -359,7 +351,6 @@ io.asciidoc This should display an error message to the user if there was a prob io.asciidoc This simple example allows you to create a server and a client assuming the device supports both: io.asciidoc This still carries most of the flexibilities of the regular binding for example: you can still get a binding object using: io.asciidoc To parse a CSV use the https://www.codenameone.com/javadoc/com/codename1/io/CSVParser.html[CSVParser] class as such: -io.asciidoc To use the expression processor when calling a webservice, you could use something like the following to parse JSON (notice this is interchangeable between JSON and XML): io.asciidoc Up until now this was pretty cool but if you looked at the UI construction code above you would see that it's pretty full of boilerplate code. The thing about boilerplate is that it shows where automation can be applied, that's the exact idea behind the magical "InstantUI" class. This means that the UI above can be generated using this code: io.asciidoc When you used a POJO you did this: io.asciidoc With properties you do this: From 89904a21b2e23607b71eb5ebce87bde1d89c61e6 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:50:20 +0300 Subject: [PATCH 14/21] Reject a document included twice `_visit` returns early on a revisit, so a second include of the same file left no trace in the reachability map, and the title check could not see it either -- it asks whether a title appears AT LEAST as often as declared, which two renderings of one chapter satisfy. Including a chapter twice therefore duplicated it in the manual while the structure gate reported success. Confirmed before fixing: with Printing included twice, the gate passed and the rendered book contained the chapter twice. Includes are now counted at the edge rather than inferred from visited state, and any document included more than once is rejected. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/developer-guide/check-guide-structure.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/scripts/developer-guide/check-guide-structure.py b/scripts/developer-guide/check-guide-structure.py index c7a1cb4716f..4d6b1e85f1b 100755 --- a/scripts/developer-guide/check-guide-structure.py +++ b/scripts/developer-guide/check-guide-structure.py @@ -20,6 +20,7 @@ from __future__ import annotations import argparse +import collections import html import re import subprocess @@ -72,6 +73,9 @@ def __init__(self, root: Path, guide_dir: Path) -> None: self.guide_dir = guide_dir self.reachable: dict[Path, str] = {} self.direct: set[Path] = set() + # Counted at the EDGE, not per visited file: _visit returns early on a + # revisit, so a document included twice would otherwise leave no trace. + self.include_count: collections.Counter = collections.Counter() self.errors: list[str] = [] self._visit(root, "") @@ -99,6 +103,7 @@ def _visit(self, path: Path, attrs_from_parent: str) -> None: f"{path.name}:{index + 1}: include target does not exist: {target_raw}" ) continue + self.include_count[target] += 1 if path == self.root: self.direct.add(target) self._check_manifest_spacing(index, lines, target_raw) @@ -161,6 +166,16 @@ def main() -> int: walker = Walker(root, guide_dir) errors = list(walker.errors) + # A document included twice is rendered twice. The title check below cannot + # see it, because it only asks whether a title appears AT LEAST as often as + # it is declared. + for path, count in sorted(walker.include_count.items()): + if count > 1: + errors.append( + f"{path.name}: is included {count} times, so the book renders it " + f"{count} times. Remove the duplicate include." + ) + # 1. Every chapter in the tree is either in the book or declared out of it. declared_path = guide_dir / "not-in-book.txt" declared = set() From 5dfb50ef21e6c13931c99955e149476228eea18c Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 11:57:37 +0300 Subject: [PATCH 15/21] Scope the duplicate-include rule to unconditional manifest entries The rule added last commit counted every textual include, which would have rejected two legitimate shapes: a nested fragment reused from more than one parent, and a chapter included once per branch of an ifdef/ifndef pair, which appears twice in the source and once in the output. Neither is a duplicated chapter, and this guide already uses the conditional pattern elsewhere for the PDF backend. Counting is now limited to direct manifest entries that are not inside a conditional region. The defect the rule was written for -- a chapter listed twice in developer-guide.asciidoc and rendered twice in the book -- is still rejected. Verified all three: the duplicate manifest entry fails, a nested fragment included from two parents passes, and the same chapter under mutually exclusive ifdef/ifndef branches passes. Co-Authored-By: Claude Opus 5 (1M context) --- .../developer-guide/check-guide-structure.py | 34 +++++++++++++++---- 1 file changed, 27 insertions(+), 7 deletions(-) diff --git a/scripts/developer-guide/check-guide-structure.py b/scripts/developer-guide/check-guide-structure.py index 4d6b1e85f1b..eb47408548f 100755 --- a/scripts/developer-guide/check-guide-structure.py +++ b/scripts/developer-guide/check-guide-structure.py @@ -75,7 +75,11 @@ def __init__(self, root: Path, guide_dir: Path) -> None: self.direct: set[Path] = set() # Counted at the EDGE, not per visited file: _visit returns early on a # revisit, so a document included twice would otherwise leave no trace. - self.include_count: collections.Counter = collections.Counter() + # Only direct manifest entries are counted. A nested fragment may be + # reused from two parents on purpose, and a file included under + # mutually exclusive ifdef/ifndef branches appears twice in the source + # while rendering once -- neither is a duplicated chapter. + self.direct_include_count: collections.Counter = collections.Counter() self.errors: list[str] = [] self._visit(root, "") @@ -103,12 +107,28 @@ def _visit(self, path: Path, attrs_from_parent: str) -> None: f"{path.name}:{index + 1}: include target does not exist: {target_raw}" ) continue - self.include_count[target] += 1 - if path == self.root: + if path == self.root and not self._inside_conditional(lines, index): + self.direct_include_count[target] += 1 self.direct.add(target) self._check_manifest_spacing(index, lines, target_raw) self._visit(target, attrs) + @staticmethod + def _inside_conditional(lines: list[str], index: int) -> bool: + """Whether this line sits inside an ifdef/ifndef/ifeval region. + + A chapter included once per branch of a conditional appears twice in the + source and once in the output, so counting it as a duplicate would reject + valid markup. + """ + depth = 0 + for line in lines[:index]: + if re.match(r"^(ifdef|ifndef|ifeval)::.*\[\s*\]\s*$", line): + depth += 1 + elif re.match(r"^endif::", line): + depth = max(0, depth - 1) + return depth > 0 + def _check_manifest_spacing(self, index: int, lines: list[str], target_raw: str) -> None: """Require a blank line after every include in the top-level manifest. @@ -166,10 +186,10 @@ def main() -> int: walker = Walker(root, guide_dir) errors = list(walker.errors) - # A document included twice is rendered twice. The title check below cannot - # see it, because it only asks whether a title appears AT LEAST as often as - # it is declared. - for path, count in sorted(walker.include_count.items()): + # A chapter included twice by the manifest is rendered twice. The title check + # below cannot see it, because it only asks whether a title appears AT LEAST + # as often as it is declared. + for path, count in sorted(walker.direct_include_count.items()): if count > 1: errors.append( f"{path.name}: is included {count} times, so the book renders it " From 7b2b61857801c48e9f0e8e4ef031896ec21bb2c6 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:07:27 +0300 Subject: [PATCH 16/21] A list item is prose, and a conditional entry is still a chapter Two review findings, both on fixes from the previous two commits. The missing-block detector classified any line beginning with *, -, + or . as markup, so an introduction written as a bulleted list item was skipped. Stripping a valid list marker -- one followed by whitespace, which distinguishes '* Item' from '*bold*' and '. Item' from '.BlockTitle' -- lets the prose test see the sentence. Worth being precise about the effect: numbered items were already scanned, because digits were never in that prefix list, so this closes the bulleted case and finds nothing new in the guide today. The count stays at 406. The conditional guard added last commit skipped too much. It excluded a conditional manifest entry from `direct` entirely, so the chapter-level and spacing checks stopped applying to it and a conditionally included file opening at level 3 would have rendered as a subsection unnoticed. Only the duplicate count skips a conditional now; everything else treats it as the chapter it is. That required one more correction in turn: an include closed by endif:: on the next line is correctly spaced, because a preprocessor directive is not content and cannot absorb a paragraph. Verified: a bulleted introduction with a missing block is reported, a block title is still markup, a conditional chapter at level 3 is rejected, and the same chapter under two exclusive branches still passes. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/developer-guide/check-guide-structure.py | 13 +++++++++++-- .../developer-guide/check-missing-code-blocks.py | 10 +++++++++- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/scripts/developer-guide/check-guide-structure.py b/scripts/developer-guide/check-guide-structure.py index eb47408548f..5cd7cc1f90f 100755 --- a/scripts/developer-guide/check-guide-structure.py +++ b/scripts/developer-guide/check-guide-structure.py @@ -107,10 +107,15 @@ def _visit(self, path: Path, attrs_from_parent: str) -> None: f"{path.name}:{index + 1}: include target does not exist: {target_raw}" ) continue - if path == self.root and not self._inside_conditional(lines, index): - self.direct_include_count[target] += 1 + if path == self.root: + # A conditional entry is still a chapter: it must open at chapter + # level and be spaced correctly. Only the DUPLICATE count skips + # it, because the same chapter under two exclusive branches + # appears twice in the source and once in the output. self.direct.add(target) self._check_manifest_spacing(index, lines, target_raw) + if not self._inside_conditional(lines, index): + self.direct_include_count[target] += 1 self._visit(target, attrs) @staticmethod @@ -139,6 +144,10 @@ def _check_manifest_spacing(self, index: int, lines: list[str], target_raw: str) spent its life rendered as subsections of the Windows one. """ following = lines[index + 1] if index + 1 < len(lines) else "" + # A preprocessor directive is not content and cannot absorb a paragraph, + # so an include closed by endif:: on the next line is correctly spaced. + if re.match(r"^(ifdef|ifndef|ifeval|endif)::", following.strip()): + return if following.strip(): self.errors.append( f"developer-guide.asciidoc:{index + 1}: include of {target_raw} is not " diff --git a/scripts/developer-guide/check-missing-code-blocks.py b/scripts/developer-guide/check-missing-code-blocks.py index 802fb1403ed..c486f434842 100755 --- a/scripts/developer-guide/check-missing-code-blocks.py +++ b/scripts/developer-guide/check-missing-code-blocks.py @@ -34,6 +34,10 @@ # Lines that end in a colon without promising a listing: headings, attributes, # comments, block titles, list markers, table cells and block delimiters. NON_PROSE_PREFIX = ("//", "|", "=", ".", ":", "*", "-", "+", "[", "<") +# A list marker is followed by whitespace; a block title (.Title) and bold text +# (*text*) are not. Stripping the marker lets the prose test see the sentence, +# so an introduction written as a list item is not mistaken for markup. +LIST_MARKER_RE = re.compile(r"^([*\-]+|\.{1,5}|[0-9]+\.)\s+") # What a real block looks like when it starts. An introduction separated from its # listing by more than one blank line is untidy, not a hole, and reporting it # would make the gate reject valid AsciiDoc spacing. Deliberately conservative: @@ -60,7 +64,11 @@ def scan(path: Path) -> list[tuple[int, str]]: stripped = line.rstrip() if not stripped.endswith(":"): continue - if stripped.lstrip().startswith(NON_PROSE_PREFIX): + body = stripped.lstrip() + marker = LIST_MARKER_RE.match(body) + if marker: + body = body[marker.end():] + if not body or body.startswith(NON_PROSE_PREFIX): continue if index + 2 >= len(lines): continue From e7f885e42184ba540bb094b5975098537b7b29fe Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:15:53 +0300 Subject: [PATCH 17/21] Accept TOML alias arrays, and do not demand both branches of a conditional Two review findings, both latent: nothing in the tree exercises either path today. Closing them anyway, since both are cheap and both are the kind of gap that only shows up once someone writes the markup. The front-matter reader understood a YAML block sequence under `aliases:` and an inline array, but not a multiline array. Three pages use TOML front matter and none of them declares aliases, so nothing changes today; the reader now accepts either continuation shape and closes the array on its bracket. The title-survival check required every direct entry's title to appear in the render, which is wrong for a chapter inside an ifdef/ifndef branch: only one branch renders, so demanding both reports a chapter that is deliberately absent. Conditional entries are excluded from that check and still have their heading level and spacing validated -- the checks that can be evaluated without knowing which branch is active. Verified: a multiline TOML alias array is parsed and both aliases resolve, and the same chapter under two exclusive branches no longer trips the title check. Co-Authored-By: Claude Opus 5 (1M context) --- scripts/developer-guide/check-guide-links.py | 13 +++++++++---- scripts/developer-guide/check-guide-structure.py | 12 ++++++++++-- 2 files changed, 19 insertions(+), 6 deletions(-) diff --git a/scripts/developer-guide/check-guide-links.py b/scripts/developer-guide/check-guide-links.py index a8c6672722a..55f111a0747 100755 --- a/scripts/developer-guide/check-guide-links.py +++ b/scripts/developer-guide/check-guide-links.py @@ -62,8 +62,13 @@ def front_matter(page: Path) -> dict[str, object]: break if in_aliases: stripped = line.strip() - if stripped.startswith("-"): - aliases.append(stripped.lstrip("- ").strip().strip("\"'")) + if stripped in {"]", "],"}: + in_aliases = False + continue + # YAML block sequence ("- /x") and TOML/flow array ("\"/x\",") both + # appear in this tree, so accept either continuation shape. + if stripped.startswith("-") or stripped.startswith(("\"", "'")): + aliases.append(stripped.lstrip("- ").strip().rstrip(",").strip("\"'")) continue in_aliases = False match = re.match(r'^(url|slug|aliases|draft|date)\s*[:=]\s*(.*)$', line) @@ -71,8 +76,8 @@ def front_matter(page: Path) -> dict[str, object]: continue key, raw = match.group(1), match.group(2).strip() if key == "aliases": - if raw in {"", "["}: - in_aliases = True + if raw in {"", "[", "[]"}: + in_aliases = raw != "[]" else: aliases.extend(v.strip().strip("\"'") for v in raw.strip("[]").split(",") if v.strip()) continue diff --git a/scripts/developer-guide/check-guide-structure.py b/scripts/developer-guide/check-guide-structure.py index 5cd7cc1f90f..94eabec1adc 100755 --- a/scripts/developer-guide/check-guide-structure.py +++ b/scripts/developer-guide/check-guide-structure.py @@ -73,6 +73,9 @@ def __init__(self, root: Path, guide_dir: Path) -> None: self.guide_dir = guide_dir self.reachable: dict[Path, str] = {} self.direct: set[Path] = set() + # Direct entries that sit inside an ifdef/ifndef region. Only one branch + # renders, so the outcome check cannot demand every branch's title. + self.conditional: set[Path] = set() # Counted at the EDGE, not per visited file: _visit returns early on a # revisit, so a document included twice would otherwise leave no trace. # Only direct manifest entries are counted. A nested fragment may be @@ -114,7 +117,9 @@ def _visit(self, path: Path, attrs_from_parent: str) -> None: # appears twice in the source and once in the output. self.direct.add(target) self._check_manifest_spacing(index, lines, target_raw) - if not self._inside_conditional(lines, index): + if self._inside_conditional(lines, index): + self.conditional.add(target) + else: self.direct_include_count[target] += 1 self._visit(target, attrs) @@ -284,7 +289,10 @@ def main() -> int: rendered = rendered_titles(render(root)) expected: dict[str, list[str]] = {} for path in sorted(walker.reachable): - if path == root: + if path == root or path in walker.conditional: + # A conditional entry renders in one branch only, so requiring its + # title in this render would report a chapter that is deliberately + # absent. Its level and spacing are still checked above. continue heading = first_heading(path) if heading: From 6364bb4d71beaa82e111b431ca1a69df91b72216 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:21:20 +0300 Subject: [PATCH 18/21] Move the guard scripts to their own change The structural repairs here -- the chapter that was missing from the book, the three that rendered as parts, the six documents included by nothing, the dead cross-references and the contribution material -- have been stable through nineteen rounds of review without drawing a single finding. The four guard scripts written alongside them drew twenty-six, and are eight times the size of the change they were meant to protect. They are genuinely better for that scrutiny, and one of them found a bug in the instrument this PR used to measure its own headline number. But they are tooling, they iterate on a different clock from the prose, and keeping them here means the documentation fixes wait on them. They move to a PR stacked on this one. Co-Authored-By: Claude Opus 5 (1M context) --- .github/workflows/developer-guide-docs.yml | 19 - .github/workflows/website-docs.yml | 11 - scripts/developer-guide/check-guide-links.py | 408 ----------------- .../developer-guide/check-guide-structure.py | 324 -------------- scripts/developer-guide/check-guide-xrefs.py | 112 ----- .../check-missing-code-blocks.py | 195 --------- .../developer-guide/guide-links-baseline.txt | 43 -- .../missing-code-blocks-baseline.txt | 409 ------------------ 8 files changed, 1521 deletions(-) delete mode 100755 scripts/developer-guide/check-guide-links.py delete mode 100755 scripts/developer-guide/check-guide-structure.py delete mode 100755 scripts/developer-guide/check-guide-xrefs.py delete mode 100755 scripts/developer-guide/check-missing-code-blocks.py delete mode 100644 scripts/developer-guide/guide-links-baseline.txt delete mode 100644 scripts/developer-guide/missing-code-blocks-baseline.txt diff --git a/.github/workflows/developer-guide-docs.yml b/.github/workflows/developer-guide-docs.yml index 7393eaee936..2d0df8a5a1b 100644 --- a/.github/workflows/developer-guide-docs.yml +++ b/.github/workflows/developer-guide-docs.yml @@ -271,25 +271,6 @@ jobs: echo "Asciidoctor exited with status $STATUS — the final quality-gate step will fail the build." >&2 fi - # These four cover defects the gates above are structurally blind to: a - # chapter swallowed by the one before it, a cross-reference that resolves - # to nothing, prose that promises a code block which is not there, and a - # link the website does not serve. Asciidoctor reports none of them, and - # every one of them shipped. The structure and cross-reference checks - # render the book, so they must run after the Asciidoctor install. - - name: Check developer guide structure, cross-references, code blocks and links - run: | - set -euo pipefail - python3 scripts/developer-guide/check-guide-structure.py - python3 scripts/developer-guide/check-guide-xrefs.py - python3 scripts/developer-guide/check-missing-code-blocks.py - # Note this only catches a link the GUIDE breaks. When the website - # moves the route instead -- a page renamed, deleted or re-slugged -- - # the same script runs from website-docs.yml, which triggers on - # docs/website/**. Putting those paths here would run this whole job - # (maven install, demo build, screenshots) for every blog post. - python3 scripts/developer-guide/check-guide-links.py - - name: Build Developer Guide HTML and PDF if: github.event_name != 'pull_request' || steps.changes.outputs.docs == 'true' || steps.changes.outputs.demos == 'true' || steps.changes.outputs.workflow == 'true' run: | diff --git a/.github/workflows/website-docs.yml b/.github/workflows/website-docs.yml index fae57010a9f..8fc0a7b1a02 100644 --- a/.github/workflows/website-docs.yml +++ b/.github/workflows/website-docs.yml @@ -277,17 +277,6 @@ jobs: --root-dir public public/**/*.html - # The developer guide links into this site, so a page renamed, deleted or - # re-slugged here breaks the guide. The check lives in THIS workflow rather - # than in developer-guide-docs.yml because it is the route data that - # changed: widening that workflow's paths to docs/website/** would run the - # full guide build -- maven install, demo build, screenshot generation -- - # on every blog post the daily publisher pushes, to run one Python script. - # This job already triggers on docs/website/**, and the check needs nothing - # but a checkout. - - name: Check developer guide links against the routes this site serves - run: python3 scripts/developer-guide/check-guide-links.py - - name: Reject absolute codenameone.com links run: | set -euo pipefail diff --git a/scripts/developer-guide/check-guide-links.py b/scripts/developer-guide/check-guide-links.py deleted file mode 100755 index 55f111a0747..00000000000 --- a/scripts/developer-guide/check-guide-links.py +++ /dev/null @@ -1,408 +0,0 @@ -#!/usr/bin/env python3 -"""Check the developer guide's links against the site that has to serve them. - -Three failures the guide shipped, none of which any existing gate could see: - -* Fifteen ``codenameone.com/manual/.html`` deep links. ``_redirects`` - covers ``/manual`` and ``/manual/`` exactly and has no splat, so every one of - them 404s -- and every one points at a section inside this same book, which an - internal cross-reference would have reached. -* ``https://stackoverflow/tags/codenameone/`` -- a host with no dot in it, which - resolves nowhere. -* Plain ``http://`` links to hosts that have not answered on port 80 for years. - -Rather than curate a list of good links, this derives the set of paths the site -actually serves -- the redirect table plus the Hugo content tree -- and reports -any codenameone.com link that lands outside it. The baseline is a ratchet: it may -shrink, never grow. -""" -from __future__ import annotations - -import argparse -import collections -import datetime -import re -import sys -from pathlib import Path -from urllib.parse import urlsplit - -ASCIIDOC_EXTENSIONS = {".adoc", ".asciidoc"} -URL_RE = re.compile(r"\bhttps?://[^\s\[\]<>\"'`)]+") -# The one tree that genuinely cannot be enumerated from this repository: the -# Javadoc is produced from the framework sources at build time. Everything else, -# /developer-guide/ included, is derived below -- whitelisting a prefix silently -# exempts every path under it from the check. -GENERATED_PREFIXES = ("/javadoc/",) -# http:// is correct for these: RFC 3161 timestamping servers reject TLS, and -# example.com URLs are illustrative rather than fetched. -TLS_EXEMPT_HOSTS = {"timestamp.digicert.com", "example.com", "www.example.com"} -# Hosts with no dot that are still real destinations. -LOCAL_HOSTS = {"localhost", "127.0.0.1", "0.0.0.0"} -# Only the marketing site is served from the redirect table and the Hugo content -# tree. cloud.codenameone.com and friends are separate services. -SITE_HOSTS = {"codenameone.com", "www.codenameone.com"} - - -def front_matter(page: Path) -> dict[str, object]: - """Pull the few front-matter keys that decide a page's published route. - - Deliberately not a YAML parse: the tree mixes YAML and TOML front matter and - only three keys matter here. - """ - text = page.read_text(encoding="utf-8", errors="ignore") - lines = text.split("\n") - if not lines or lines[0].strip() not in {"---", "+++"}: - return {} - fence = lines[0].strip() - out: dict[str, object] = {} - aliases: list[str] = [] - in_aliases = False - for line in lines[1:]: - if line.strip() == fence: - break - if in_aliases: - stripped = line.strip() - if stripped in {"]", "],"}: - in_aliases = False - continue - # YAML block sequence ("- /x") and TOML/flow array ("\"/x\",") both - # appear in this tree, so accept either continuation shape. - if stripped.startswith("-") or stripped.startswith(("\"", "'")): - aliases.append(stripped.lstrip("- ").strip().rstrip(",").strip("\"'")) - continue - in_aliases = False - match = re.match(r'^(url|slug|aliases|draft|date)\s*[:=]\s*(.*)$', line) - if not match: - continue - key, raw = match.group(1), match.group(2).strip() - if key == "aliases": - if raw in {"", "[", "[]"}: - in_aliases = raw != "[]" - else: - aliases.extend(v.strip().strip("\"'") for v in raw.strip("[]").split(",") if v.strip()) - continue - out[key] = raw.strip("\"'") - if aliases: - out["aliases"] = aliases - return out - - -def is_published(meta: dict[str, object], today: str) -> bool: - """Hugo defaults buildDrafts and buildFuture to false, so neither reaches the site. - - The date test makes the result depend on the day it runs, which is not ideal in - a gate. It is kept because it mirrors what the site actually serves: a link to a - post that has not been published yet is genuinely broken until it is. - """ - if str(meta.get("draft", "")).strip().strip("\"'").lower() in {"true", "yes"}: - return False - date = str(meta.get("date", "")).strip().strip("\"'") - return not (re.match(r"^\d{4}-\d{2}-\d{2}", date) and date[:10] > today) - - -def normalize_path(value: str) -> str: - value = value.strip() - if not value: - return "" - if not value.startswith("/"): - value = "/" + value - return value.rstrip("/") or "/" - - -def redirect_pattern(source: str) -> tuple[re.Pattern[str], list[str]] | None: - """Compile a _redirects source that is not a literal path. - - Netlify sources may end in a `*` splat or contain `:placeholder` segments, and - 21 of the rules in this file do. Recording `/files/cn1libs/*` as a literal - string means a real link to `/files/cn1libs/foo.cn1lib` matches nothing and is - reported as broken. Returns the pattern and the capture names in order, so the - destination can be reconstructed from a match. - """ - if "*" not in source and ":" not in source: - return None - names: list[str] = [] - pattern = "" - for part in re.split(r"(\*|:[A-Za-z_][A-Za-z0-9_]*)", source): - if not part: - continue - if part == "*": - names.append("splat") - pattern += "(.*)" - elif part.startswith(":"): - names.append(part[1:]) - pattern += "([^/]+)" - else: - pattern += re.escape(part) - return re.compile("^" + pattern + "/?$"), names - - -def resolves(target: str, known: set[str], rules: list, depth: int = 0) -> bool: - """Whether a path is served, following wildcard redirects to their destination. - - Accepting every path that merely *matches* a wildcard source is the same - mistake as whitelisting a prefix. `/*.html -> /:splat/ 301` matches any - root-level .html path at all, so `/does-not-exist.html` would pass while - redirecting to a page that is not there. Substitute the captures into the - destination and check that instead. - """ - if target in known: - return True - if target.startswith(GENERATED_PREFIXES) or target + "/" in GENERATED_PREFIXES: - # Reachable both directly and by following a redirect into it, so the - # test belongs here rather than only at the call site. - return True - if depth > 4: # a redirect loop in the table should not hang the check - return False - for compiled, names, destination in rules: - match = compiled.match(target) - if not match: - continue - if not destination or not destination.startswith("/"): - return True # redirects off-site; nothing here can verify it - resolved = destination - for name, value in zip(names, match.groups()): - resolved = resolved.replace(":" + name, value or "") - resolved = normalize_path(resolved) - if resolved == target: - return False - if resolves(resolved, known, rules, depth + 1): - return True - return False - - -def site_paths(repo_root: Path) -> tuple[set[str], list]: - """Every path the website is known to answer on, derived rather than listed. - - Returns the literal paths and every redirect rule, each as a matcher, its - capture names and its destination, so a link can be followed rather than - accepted for merely matching. - """ - paths: set[str] = set() - patterns: list = [] - - redirects = repo_root / "docs/website/static/_redirects" - if redirects.exists(): - for line in redirects.read_text(encoding="utf-8").split("\n"): - parts = line.split() - if not parts or parts[0].startswith("#"): - continue - destination = parts[1] if len(parts) > 1 else "" - compiled = redirect_pattern(parts[0]) - if compiled is not None: - patterns.append((compiled[0], compiled[1], destination)) - else: - # A literal source is not a served route either -- it is a rule, - # and a rule pointing at a page that was deleted redirects the - # reader to a 404. Follow it like any other, rather than treating - # the fact that a rule exists as proof the link works. - patterns.append( - (re.compile("^" + re.escape(normalize_path(parts[0])) + "/?$"), [], destination) - ) - # Only the SOURCE counts. A rule whose destination was deleted still - # sits in this file, so trusting destinations would accept a guide - # link to a page that no longer exists. - - # Hugo's published route is the section path plus the page's slug, which - # 1055 of the content pages override; deriving it from the filename instead - # both invents routes that are never generated and rejects real ones. - # - # Known gap: taxonomy term pages (/tags//) are generated by Hugo from - # front-matter tags rather than from a file, so they are not derived here. No - # guide link targets one today. If one is ever added it will be reported as - # broken, which is the safe direction for a gate to be wrong in. - paths.add("/") # Hugo always renders the home page, _index.md or not - - # scripts/website/build.sh renders the guide to /developer-guide/ and rsyncs - # this directory alongside it so relative image links resolve, excluding the - # Sketch sources and the AsciiDoc itself. That makes every served path under - # the guide enumerable, so it does not need a blanket exemption. - guide = repo_root / "docs/developer-guide" - if guide.exists(): - paths.add("/developer-guide") - for asset in guide.rglob("*"): - if not asset.is_file(): - continue - relative = asset.relative_to(guide) - if relative.parts[0] == "sketch" or relative.suffix in {".asciidoc", ".adoc"}: - continue - paths.add(normalize_path("developer-guide/" + relative.as_posix())) - - today = datetime.date.today().isoformat() - content = repo_root / "docs/website/content" - if content.exists(): - for page in content.rglob("*.md"): - relative = page.relative_to(content).with_suffix("") - meta = front_matter(page) - if not is_published(meta, today): - continue - for alias in meta.get("aliases", []) or []: - if isinstance(alias, str): - paths.add(normalize_path(alias)) - if meta.get("url"): - paths.add(normalize_path(str(meta["url"]))) - continue - parts = list(relative.parts) - if parts and parts[-1] in {"_index", "index"}: - parts.pop() - if meta.get("slug"): - parts = parts[:-1] + [str(meta["slug"])] if parts else [str(meta["slug"])] - paths.add(normalize_path("/".join(parts)) if parts else "/") - - # Some redirects are written into _redirects at deploy time rather than - # committed, so the file in the tree does not list them. Read the paths out - # of the script that emits them instead of assuming a prefix is safe. - for emitter in sorted((repo_root / "scripts/website").glob("*redirect*.sh")): - for match in re.finditer( - r"printf\s+'(/[^\s']+)\s+%s[^']*'", emitter.read_text(encoding="utf-8") - ): - paths.add(normalize_path(match.group(1))) - - # Anything committed under static/ is served at its own path. - static = repo_root / "docs/website/static" - if static.exists(): - for asset in static.rglob("*"): - if not asset.is_file(): - # A directory is not a route. static/uploads holds assets and no - # index page, so recording the directory itself would accept a - # link to /uploads that resolves to nothing. - continue - paths.add(normalize_path(asset.relative_to(static).as_posix())) - if asset.name == "index.html": - paths.add(normalize_path(asset.parent.relative_to(static).as_posix())) - - return paths, patterns - - -def findings_for(path: Path, known: set[str], patterns: list) -> list[tuple[str, str]]: - out: list[tuple[str, str]] = [] - for number, line in enumerate(path.read_text(encoding="utf-8").split("\n"), 1): - for url in URL_RE.findall(line): - url = url.rstrip(".,;:") - split = urlsplit(url) - host = split.hostname or "" - if "." not in host and host not in LOCAL_HOSTS: - out.append((url, f"host '{host}' has no dot in it and resolves nowhere")) - continue - if split.scheme == "http" and host not in TLS_EXEMPT_HOSTS | LOCAL_HOSTS: - out.append((url, "plain http, not https")) - if host in SITE_HOSTS: - target = split.path.rstrip("/") or "/" - if not resolves(target, known, patterns): - out.append((url, "the website serves no such path (checked _redirects and the content tree)")) - return out - - -def load_baseline(path: Path) -> collections.Counter: - """Read the baseline as a multiset: one line per occurrence. - - A file that mentions the same broken URL twice has two problems, not one. - Collapsing them into a set understated the real count -- 35 recorded against - 38 occurrences -- and left a second occurrence of an already-baselined link - free to appear without the check noticing. - """ - if not path.exists(): - return collections.Counter() - return collections.Counter( - line.rstrip("\n") - for line in path.read_text(encoding="utf-8").split("\n") - if line.strip() and not line.startswith("#") - ) - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--guide-dir", default="docs/developer-guide", type=Path) - parser.add_argument("--repo-root", default=".", type=Path) - parser.add_argument( - "--baseline", default="scripts/developer-guide/guide-links-baseline.txt", type=Path - ) - parser.add_argument("--write-baseline", action="store_true") - parser.add_argument( - "--allow-new", - action="store_true", - help="permit --write-baseline to ADD entries; without it the baseline may only shrink", - ) - args = parser.parse_args() - - guide_dir = args.guide_dir.resolve() - known, patterns = site_paths(args.repo_root.resolve()) - if not known: - raise SystemExit("could not derive any site paths; is --repo-root correct?") - - current: collections.Counter = collections.Counter() - reasons: dict[str, str] = {} - for path in sorted(guide_dir.rglob("*")): - if path.suffix not in ASCIIDOC_EXTENSIONS or not path.is_file(): - continue - name = path.relative_to(guide_dir).as_posix() - for url, reason in findings_for(path, known, patterns): - entry = f"{name}\t{url}" - current[entry] += 1 - reasons[entry] = reason - - if args.write_baseline: - # The command that banks a fix is the same command that could bury a new - # failure. Shrinking is free; growing needs --allow-new, so recording new - # debt is a deliberate act that shows up in the command as well as in the - # baseline diff a reviewer reads. - added = sorted((current - load_baseline(args.baseline)).elements()) - if added and not args.allow_new: - for entry in added: - name, _, url = entry.partition("\t") - print(f"{name}: {url}", file=sys.stderr) - print( - f"\nRefusing to add {len(added)} entr(ies) to the baseline. Fix the " - f"link, or pass --allow-new if this is debt you mean to record.", - file=sys.stderr, - ) - return 1 - args.baseline.write_text( - "\n".join( - [ - "# Developer guide links that do not resolve, or are not TLS.", - "# One line per occurrence: a file naming the same bad URL twice gets", - "# two lines, because that is two things to fix.", - "# A ratchet: entries may be removed as links are fixed, never added.", - "# Regenerate with check-guide-links.py --write-baseline.", - ] - + sorted(current.elements()) - ) - + "\n", - encoding="utf-8", - ) - print(f"Wrote baseline with {sum(current.values())} entr(ies).") - return 0 - - baseline = load_baseline(args.baseline) - new = sorted((current - baseline).elements()) - # A baselined entry that no longer reproduces has to leave the file. Leaving it - # keeps a slot open: a later change can restore that exact file+URL and - # `current - baseline` stays empty, so the regression sails through. The - # ratchet only ratchets if fixes are banked. - stale = sorted((baseline - current).elements()) - if new or stale: - for entry in new: - name, _, url = entry.partition("\t") - print(f"{name}: {url} -- {reasons[entry]}", file=sys.stderr) - for entry in stale: - name, _, url = entry.partition("\t") - print( - f"{name}: {url} -- no longer broken, but still in the baseline. Run " - f"check-guide-links.py --write-baseline to bank the fix.", - file=sys.stderr, - ) - print( - f"\n{len(new)} new broken or insecure link(s), {len(stale)} stale baseline entr(ies).", - file=sys.stderr, - ) - return 1 - - print( - f"Links: {sum(current.values())} known bad link(s) against {len(known)} known site paths " - f"and {len(patterns)} redirect rule(s); none new, none stale." - ) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/developer-guide/check-guide-structure.py b/scripts/developer-guide/check-guide-structure.py deleted file mode 100755 index 94eabec1adc..00000000000 --- a/scripts/developer-guide/check-guide-structure.py +++ /dev/null @@ -1,324 +0,0 @@ -#!/usr/bin/env python3 -"""Verify that the developer guide's book structure is what the manifest says. - -Three defects motivated this check, all of which shipped in a green build: - -* ``Working-With-Linux.asciidoc`` was included on the line directly after - ``Working-With-Windows.asciidoc``, whose last line is a paragraph. After - include expansion the Linux chapter's ``== `` title became a continuation of - that paragraph, so the whole chapter rendered as subsections of the Windows - chapter and its title vanished. Asciidoctor reports nothing. -* Three chapters opened with a level-0 ``= `` heading. Under ``doctype: book`` - that turns each into a *part*, promotes its own sections to chapters and drops - its title from the numbered sequence. Also silent. -* Six complete chapters sat in the tree while being included by nothing, so they - never reached a reader at all. - -The first two are caught by rendering the book and checking that every included -chapter's title survives into the output; the third by walking the include graph. -""" -from __future__ import annotations - -import argparse -import collections -import html -import re -import subprocess -import sys -import tempfile -from pathlib import Path - -ASCIIDOC_EXTENSIONS = {".adoc", ".asciidoc"} -INCLUDE_RE = re.compile(r"^include::([^\[]+)\[([^\]]*)\]\s*$") -HEADING_RE = re.compile(r"^(=+) +(\S.*)$") -# Literal-content blocks only. The container delimiters -- ==== example, -# **** sidebar, ____ quote -- hold ordinary AsciiDoc, so an include or a heading -# inside one is real and must not be skipped. -FENCE_RE = re.compile(r"^(----|\.\.\.\.|````|\+\+\+\+)\s*$") -# Inline AsciiDoc markup that never survives into the rendered heading text. -INLINE_MARKUP_RE = re.compile(r"[`*_#]|\[\[[^\]]*\]\]|\[[^\]]*\]") - - -def parse_lines(path: Path) -> list[str]: - return path.read_text(encoding="utf-8").split("\n") - - -def first_heading(path: Path) -> tuple[int, str] | None: - """Return (level, title) of the file's first heading outside a fenced block.""" - in_fence = False - for line in parse_lines(path): - if FENCE_RE.match(line): - in_fence = not in_fence - continue - if in_fence: - continue - match = HEADING_RE.match(line) - if match: - return len(match.group(1)), match.group(2).strip() - return None - - -def normalize(title: str) -> str: - """Reduce a heading to something comparable across AsciiDoc and HTML.""" - text = html.unescape(title) - text = INLINE_MARKUP_RE.sub("", text) - return re.sub(r"\s+", " ", text).strip().lower() - - -class Walker: - """Expands the include graph, recording every edge for the adjacency check.""" - - def __init__(self, root: Path, guide_dir: Path) -> None: - self.root = root - self.guide_dir = guide_dir - self.reachable: dict[Path, str] = {} - self.direct: set[Path] = set() - # Direct entries that sit inside an ifdef/ifndef region. Only one branch - # renders, so the outcome check cannot demand every branch's title. - self.conditional: set[Path] = set() - # Counted at the EDGE, not per visited file: _visit returns early on a - # revisit, so a document included twice would otherwise leave no trace. - # Only direct manifest entries are counted. A nested fragment may be - # reused from two parents on purpose, and a file included under - # mutually exclusive ifdef/ifndef branches appears twice in the source - # while rendering once -- neither is a duplicated chapter. - self.direct_include_count: collections.Counter = collections.Counter() - self.errors: list[str] = [] - self._visit(root, "") - - def _visit(self, path: Path, attrs_from_parent: str) -> None: - if path in self.reachable: - return - self.reachable[path] = attrs_from_parent - lines = parse_lines(path) - in_fence = False - for index, line in enumerate(lines): - if FENCE_RE.match(line): - in_fence = not in_fence - continue - if in_fence: - continue - match = INCLUDE_RE.match(line) - if not match: - continue - target_raw, attrs = match.group(1), match.group(2) - target = (path.parent / target_raw).resolve() - if target.suffix not in ASCIIDOC_EXTENSIONS: - continue # a snippet include, validated by validate-guide-snippets.py - if not target.exists(): - self.errors.append( - f"{path.name}:{index + 1}: include target does not exist: {target_raw}" - ) - continue - if path == self.root: - # A conditional entry is still a chapter: it must open at chapter - # level and be spaced correctly. Only the DUPLICATE count skips - # it, because the same chapter under two exclusive branches - # appears twice in the source and once in the output. - self.direct.add(target) - self._check_manifest_spacing(index, lines, target_raw) - if self._inside_conditional(lines, index): - self.conditional.add(target) - else: - self.direct_include_count[target] += 1 - self._visit(target, attrs) - - @staticmethod - def _inside_conditional(lines: list[str], index: int) -> bool: - """Whether this line sits inside an ifdef/ifndef/ifeval region. - - A chapter included once per branch of a conditional appears twice in the - source and once in the output, so counting it as a duplicate would reject - valid markup. - """ - depth = 0 - for line in lines[:index]: - if re.match(r"^(ifdef|ifndef|ifeval)::.*\[\s*\]\s*$", line): - depth += 1 - elif re.match(r"^endif::", line): - depth = max(0, depth - 1) - return depth > 0 - - def _check_manifest_spacing(self, index: int, lines: list[str], target_raw: str) -> None: - """Require a blank line after every include in the top-level manifest. - - Two adjacent include lines put the first target's last line next to the - second target's first line. When the first ends on a paragraph, the second - chapter's title becomes a continuation of it and the chapter disappears - without a single warning -- which is exactly how the native Linux chapter - spent its life rendered as subsections of the Windows one. - """ - following = lines[index + 1] if index + 1 < len(lines) else "" - # A preprocessor directive is not content and cannot absorb a paragraph, - # so an include closed by endif:: on the next line is correctly spaced. - if re.match(r"^(ifdef|ifndef|ifeval|endif)::", following.strip()): - return - if following.strip(): - self.errors.append( - f"developer-guide.asciidoc:{index + 1}: include of {target_raw} is not " - f"followed by a blank line. Two adjacent includes let the first " - f"chapter's trailing paragraph swallow the second chapter's title." - ) - - -def render(root: Path) -> str: - with tempfile.TemporaryDirectory() as tmp: - out = Path(tmp) / "guide.html" - result = subprocess.run( - ["asciidoctor", "--require", "rouge", "-o", str(out), str(root)], - capture_output=True, - text=True, - ) - if result.returncode != 0: - print(result.stderr, file=sys.stderr) - raise SystemExit("asciidoctor failed to render the guide") - return out.read_text(encoding="utf-8") - - -def rendered_titles(markup: str) -> dict[str, int]: - """Count every rendered heading, normalized and stripped of its number.""" - body = markup.split('id="content"', 1)[-1] - counts: dict[str, int] = {} - for match in re.finditer(r"]*>(.*?)", body, re.S): - text = re.sub(r"<[^>]+>", "", match.group(2)) - text = re.sub(r"^(Appendix [A-Z]:|(\d+|[A-Z])(\.\d+)*\.)\s*", "", html.unescape(text).strip()) - key = normalize(text) - counts[key] = counts.get(key, 0) + 1 - return counts - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--guide-dir", default="docs/developer-guide", type=Path) - args = parser.parse_args() - - guide_dir = args.guide_dir.resolve() - root = guide_dir / "developer-guide.asciidoc" - if not root.exists(): - raise SystemExit(f"guide root not found: {root}") - - walker = Walker(root, guide_dir) - errors = list(walker.errors) - - # A chapter included twice by the manifest is rendered twice. The title check - # below cannot see it, because it only asks whether a title appears AT LEAST - # as often as it is declared. - for path, count in sorted(walker.direct_include_count.items()): - if count > 1: - errors.append( - f"{path.name}: is included {count} times, so the book renders it " - f"{count} times. Remove the duplicate include." - ) - - # 1. Every chapter in the tree is either in the book or declared out of it. - declared_path = guide_dir / "not-in-book.txt" - declared = set() - if declared_path.exists(): - for line in declared_path.read_text(encoding="utf-8").split("\n"): - line = line.split("#", 1)[0].strip() - if line: - declared.add(line) - - on_disk = { - path.resolve() - for path in guide_dir.rglob("*") - if path.suffix in ASCIIDOC_EXTENSIONS and path.is_file() - } - unreachable = sorted(on_disk - set(walker.reachable)) - for path in unreachable: - name = path.relative_to(guide_dir).as_posix() - if name not in declared: - errors.append( - f"{name}: present in the guide directory but included by nothing, so it " - f"never reaches a reader. Include it, delete it, or list it in " - f"not-in-book.txt with a reason." - ) - reachable_names = {p.relative_to(guide_dir).as_posix() for p in walker.reachable} - for name in sorted(declared & reachable_names): - errors.append(f"not-in-book.txt lists {name}, but it is included. Remove the entry.") - - # 2. A chapter's own heading level decides whether it is a chapter at all. - # A level-0 heading turns it into a book PART; a level-3 heading (or none) - # makes it a subsection of whatever chapter precedes it. Both nest silently, - # and the rendered-title check below cannot see either, because it accepts a - # title at any depth. Only entries the manifest includes DIRECTLY are held - # to this: nested fragments such as the appendix_goal_* files legitimately - # start at level 3 under their parent. - for path, attrs in sorted(walker.reachable.items()): - if path == root: - continue - heading = first_heading(path) - if heading and heading[0] == 1 and "leveloffset" not in attrs: - errors.append( - f"{path.name}: opens with a level-0 '= {heading[1]}' heading. Under " - f"doctype:book that renders as a PART and promotes its own sections to " - f"chapters. Use '== ' or include it with leveloffset=+1." - ) - if path not in walker.direct: - continue - if heading is None: - errors.append( - f"{path.name}: is included directly by developer-guide.asciidoc but has " - f"no heading, so its content is absorbed into the chapter before it." - ) - continue - # A leveloffset shifts every heading in the included file, so what decides - # whether this renders as a chapter is the declared level PLUS the offset. - # Exempting offset includes entirely would leave the same nesting bug one - # step further along: '== Chapter' at leveloffset=+1 renders as a - # subsection, and the outcome check cannot see it because it accepts a - # title at any depth. - offset = 0 - match = re.search(r"leveloffset=([+-]?\d+)", attrs) - if match: - offset = int(match.group(1)) - effective = heading[0] + offset - if effective != 2: - detail = ( - f"level {heading[0]} with leveloffset={offset:+d}" - if offset - else f"level {heading[0]}" - ) - errors.append( - f"{path.name}: is included directly by developer-guide.asciidoc at " - f"{detail}, so it renders at level {effective} rather than as a chapter. " - f"A direct manifest entry must come out at level 2." - ) - - # 3. Outcome check: every included chapter's title survives into the book. - rendered = rendered_titles(render(root)) - expected: dict[str, list[str]] = {} - for path in sorted(walker.reachable): - if path == root or path in walker.conditional: - # A conditional entry renders in one branch only, so requiring its - # title in this render would report a chapter that is deliberately - # absent. Its level and spacing are still checked above. - continue - heading = first_heading(path) - if heading: - expected.setdefault(normalize(heading[1]), []).append(path.name) - for title, sources in sorted(expected.items()): - # Compare counts, not membership: a chapter sharing its title with another - # heading would otherwise hide the fact that it was swallowed. - if rendered.get(title, 0) < len(sources): - errors.append( - f"{', '.join(sources)}: the title '{title}' appears " - f"{rendered.get(title, 0)} time(s) in the rendered book but " - f"{len(sources)} document(s) declare it. A chapter was swallowed by " - f"whatever precedes it." - ) - - if errors: - for error in errors: - print(f"::error::{error}" if sys.stdout.isatty() is False else error, file=sys.stderr) - print(f"\n{len(errors)} guide structure problem(s).", file=sys.stderr) - return 1 - print( - f"Guide structure OK: {len(walker.reachable) - 1} included documents, " - f"{len(declared)} declared out of book." - ) - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/developer-guide/check-guide-xrefs.py b/scripts/developer-guide/check-guide-xrefs.py deleted file mode 100755 index e1fe0a5049d..00000000000 --- a/scripts/developer-guide/check-guide-xrefs.py +++ /dev/null @@ -1,112 +0,0 @@ -#!/usr/bin/env python3 -"""Report developer guide cross-references that point at nothing. - -Asciidoctor does not fail, or even warn, on a dangling internal reference. A -bare ``<>`` at least renders as the literal text ``[missing]``, which a -reader might notice; but ``<>`` renders as an ordinary link -with the right words and a href to an id that does not exist, so it looks -perfectly fine and silently goes nowhere. The introduction shipped two of those -pointing at an "Application Lifecycle" sidebar nobody ever wrote. - -Working from the rendered HTML rather than the AsciiDoc source means both forms -are caught, along with anchors declared in any of AsciiDoc's several syntaxes. -""" -from __future__ import annotations - -import argparse -import re -import subprocess -import sys -import tempfile -from collections import defaultdict -from pathlib import Path - -ID_RE = re.compile(r'\bid="([^"]+)"') -HREF_RE = re.compile(r'href="#([^"]+)"') -# Asciidoctor falls back to printing the raw id in brackets when a reference -# resolves to an anchor that carries no title -- an anchor on an image or a -# paragraph rather than on a section. The link works; the sentence reads -# "see [watch-complications]". -RAW_ID_LINK_RE = re.compile(r'\[\1\]') -# Anchor syntaxes, used only to point the reader at the offending source file. -ANCHOR_SOURCE_RE = re.compile(r"<<([^>,]+)") - - -def render(root: Path) -> str: - with tempfile.TemporaryDirectory() as tmp: - out = Path(tmp) / "guide.html" - result = subprocess.run( - ["asciidoctor", "--require", "rouge", "-o", str(out), str(root)], - capture_output=True, - text=True, - ) - if result.returncode != 0: - print(result.stderr, file=sys.stderr) - raise SystemExit("asciidoctor failed to render the guide") - return out.read_text(encoding="utf-8") - - -def source_locations(guide_dir: Path, target: str) -> list[str]: - """Find where a dangling target is referenced, so the error is actionable.""" - hits = [] - for path in sorted(guide_dir.rglob("*")): - if path.suffix not in {".adoc", ".asciidoc"} or not path.is_file(): - continue - for number, line in enumerate(path.read_text(encoding="utf-8").split("\n"), 1): - for match in ANCHOR_SOURCE_RE.finditer(line): - if match.group(1).strip() == target: - hits.append(f"{path.name}:{number}") - return hits - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--guide-dir", default="docs/developer-guide", type=Path) - args = parser.parse_args() - - guide_dir = args.guide_dir.resolve() - root = guide_dir / "developer-guide.asciidoc" - markup = render(root) - - ids = set(ID_RE.findall(markup)) - dangling: dict[str, int] = defaultdict(int) - for target in HREF_RE.findall(markup): - if target not in ids: - dangling[target] += 1 - - raw_id_links: dict[str, int] = defaultdict(int) - for target in RAW_ID_LINK_RE.findall(markup): - raw_id_links[target] += 1 - - if not dangling and not raw_id_links: - print(f"Cross-references OK: {len(ids)} anchors, every internal link resolves.") - return 0 - - for target in sorted(dangling): - where = source_locations(guide_dir, target) - location = ", ".join(where) if where else "not found in source (generated content?)" - print( - f"{location}: <<{target}>> points at an id that does not exist in the " - f"rendered book ({dangling[target]} reference(s)).", - file=sys.stderr, - ) - for target in sorted(raw_id_links): - where = source_locations(guide_dir, target) - location = ", ".join(where) if where else "unknown" - print( - f"{location}: <<{target}>> resolves to an anchor with no title, so the " - f"sentence renders the raw id as \"[{target}]\" " - f"({raw_id_links[target]} reference(s)). Give the reference link text " - f"(<<{target},some words>>) or move the anchor onto the section.", - file=sys.stderr, - ) - print( - f"\n{len(dangling)} dangling and {len(raw_id_links)} untitled " - f"cross-reference target(s).", - file=sys.stderr, - ) - return 1 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/developer-guide/check-missing-code-blocks.py b/scripts/developer-guide/check-missing-code-blocks.py deleted file mode 100755 index c486f434842..00000000000 --- a/scripts/developer-guide/check-missing-code-blocks.py +++ /dev/null @@ -1,195 +0,0 @@ -#!/usr/bin/env python3 -"""Find prose that promises a code block where no block follows. - -Commit bbdc6058f0 ("Extract developer guide snippets into demos") converted -inline listings into ``include::`` directives and dropped roughly 415 of them on -the floor, leaving the introducing sentence, its colon, and two blank lines where -the code used to be. The Commerce chapter documents a paid service and, after -that pass, contained no code at all. - -``validate-guide-snippets.py`` cannot see this: it validates the blocks that are -present, not the ones that should be. The signature here is the hole itself -- -a sentence ending in a colon followed by two blank lines, which is what a removed -block leaves behind (a real block is separated from its introduction by exactly -one blank line). - -The baseline is a ratchet: it may shrink, never grow. Entries are keyed by the -text of the promising sentence rather than by line number, so ordinary editing -elsewhere in a chapter does not churn the file. -""" -from __future__ import annotations - -import argparse -import collections -import re -import sys -from pathlib import Path - -ASCIIDOC_EXTENSIONS = {".adoc", ".asciidoc"} -# Only blocks whose CONTENT is literal: listing, literal, fenced code and -# passthrough. The container delimiters -- ==== example, **** sidebar, ____ quote -# -- hold ordinary prose, and skipping them hid six real holes in basics.asciidoc -# alone, among them the setSameWidth example this check was written to find. -FENCE_RE = re.compile(r"^(----|\.\.\.\.|````|\+\+\+\+)\s*$") -# Lines that end in a colon without promising a listing: headings, attributes, -# comments, block titles, list markers, table cells and block delimiters. -NON_PROSE_PREFIX = ("//", "|", "=", ".", ":", "*", "-", "+", "[", "<") -# A list marker is followed by whitespace; a block title (.Title) and bold text -# (*text*) are not. Stripping the marker lets the prose test see the sentence, -# so an introduction written as a list item is not mistaken for markup. -LIST_MARKER_RE = re.compile(r"^([*\-]+|\.{1,5}|[0-9]+\.)\s+") -# What a real block looks like when it starts. An introduction separated from its -# listing by more than one blank line is untidy, not a hole, and reporting it -# would make the gate reject valid AsciiDoc spacing. Deliberately conservative: -# only unambiguous starts, so a genuine hole is never explained away. -BLOCK_START_RE = re.compile( - r'^(\[[a-zA-Z%.#"]|image::|include::|\|===|(----|\.\.\.\.|````|\+\+\+\+|====|\*\*\*\*|____)\s*$)' -) - - -def normalize(line: str) -> str: - return re.sub(r"\s+", " ", line.strip()) - - -def scan(path: Path) -> list[tuple[int, str]]: - lines = path.read_text(encoding="utf-8").split("\n") - in_fence = False - findings = [] - for index, line in enumerate(lines): - if FENCE_RE.match(line): - in_fence = not in_fence - continue - if in_fence: - continue - stripped = line.rstrip() - if not stripped.endswith(":"): - continue - body = stripped.lstrip() - marker = LIST_MARKER_RE.match(body) - if marker: - body = body[marker.end():] - if not body or body.startswith(NON_PROSE_PREFIX): - continue - if index + 2 >= len(lines): - continue - if lines[index + 1].strip() or lines[index + 2].strip(): - continue - following = index + 1 - while following < len(lines) and not lines[following].strip(): - following += 1 - if following < len(lines) and BLOCK_START_RE.match(lines[following].strip()): - continue - findings.append((index + 1, normalize(stripped))) - return findings - - -def load_baseline(path: Path) -> collections.Counter: - """Read the baseline as a multiset: one line per occurrence. - - Two identical introducing sentences in one chapter are two holes to fill, and - keying by text alone would let the second appear for free. - """ - baseline: collections.Counter = collections.Counter() - if not path.exists(): - return baseline - for raw in path.read_text(encoding="utf-8").split("\n"): - if raw.startswith("#") or not raw.strip(): - continue - name, _, text = raw.rstrip("\n").partition("\t") - if text: - baseline[f"{name}\t{text}"] += 1 - return baseline - - -def main() -> int: - parser = argparse.ArgumentParser(description=__doc__) - parser.add_argument("--guide-dir", default="docs/developer-guide", type=Path) - parser.add_argument( - "--baseline", - default="scripts/developer-guide/missing-code-blocks-baseline.txt", - type=Path, - ) - parser.add_argument( - "--write-baseline", - action="store_true", - help="rewrite the baseline from the current tree", - ) - parser.add_argument( - "--allow-new", - action="store_true", - help="permit --write-baseline to ADD entries; without it the baseline may only shrink", - ) - args = parser.parse_args() - - guide_dir = args.guide_dir.resolve() - current: collections.Counter = collections.Counter() - located: dict[str, list[int]] = {} - for path in sorted(guide_dir.rglob("*")): - if path.suffix not in ASCIIDOC_EXTENSIONS or not path.is_file(): - continue - name = path.relative_to(guide_dir).as_posix() - for number, text in scan(path): - entry = f"{name}\t{text}" - current[entry] += 1 - located.setdefault(entry, []).append(number) - - if args.write_baseline: - # Same reasoning as check-guide-links.py: the command that banks a fix - # must not silently bury a new hole. - added = sorted((current - load_baseline(args.baseline)).elements()) - if added and not args.allow_new: - for entry in added: - print(entry.replace("\t", ": "), file=sys.stderr) - print( - f"\nRefusing to add {len(added)} entr(ies) to the baseline. Restore the " - f"block, or pass --allow-new if this is debt you mean to record.", - file=sys.stderr, - ) - return 1 - lines = [ - "# Prose that promises a code block where none follows.", - "# A ratchet: entries may be removed as holes are filled, never added.", - "# Regenerate with check-missing-code-blocks.py --write-baseline.", - ] - lines.extend(sorted(current.elements())) - args.baseline.write_text("\n".join(lines) + "\n", encoding="utf-8") - total = sum(current.values()) - print(f"Wrote baseline with {total} entr(ies).") - return 0 - - baseline = load_baseline(args.baseline) - new: list[str] = [] - for entry in sorted((current - baseline).elements()): - name, _, text = entry.partition("\t") - where = ", ".join(str(n) for n in located.get(entry, [])) - new.append(f"{name}:{where}: promises a code block that is not there: {text[:100]}") - - # A filled hole has to leave the baseline. Leaving it keeps a slot open: a - # later change can empty that exact block again and `current - baseline` stays - # empty, so the regression passes. The ratchet only ratchets if fixes are banked. - stale: list[str] = [] - for entry in sorted((baseline - current).elements()): - name, _, text = entry.partition("\t") - stale.append( - f"{name}: filled, but still in the baseline: {text[:80]}. Run " - f"check-missing-code-blocks.py --write-baseline to bank the fix." - ) - - total = sum(current.values()) - if new or stale: - for entry in new + stale: - print(entry, file=sys.stderr) - print( - f"\n{len(new)} new hole(s) and {len(stale)} stale baseline entr(ies). Restore " - f"the block (the originals are recoverable from bbdc6058f0~1) or rewrite the " - f"sentence so it stops promising one.", - file=sys.stderr, - ) - return 1 - - print(f"Missing code blocks: {total} known hole(s), none new, none stale.") - return 0 - - -if __name__ == "__main__": - raise SystemExit(main()) diff --git a/scripts/developer-guide/guide-links-baseline.txt b/scripts/developer-guide/guide-links-baseline.txt deleted file mode 100644 index c2d739300eb..00000000000 --- a/scripts/developer-guide/guide-links-baseline.txt +++ /dev/null @@ -1,43 +0,0 @@ -# Developer guide links that do not resolve, or are not TLS. -# One line per occurrence: a file naming the same bad URL twice gets -# two lines, because that is two things to fix. -# A ratchet: entries may be removed as links are fixed, never added. -# Regenerate with check-guide-links.py --write-baseline. -About-This-Guide.asciidoc http://github.com/chen-fishbein/ -About-This-Guide.asciidoc http://github.com/shannah/ -Advanced-Theming.asciidoc http://freebiesbug.com/psd-freebies/iphone-6-ui-kit/ -Advanced-Theming.asciidoc https://www.codenameone.com/files/iOS_UI-Kit.psd -Advanced-Topics-Under-The-Hood.asciidoc http://developer.android.com/reference/android/content/Context.html -Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/Mobihelp.html -Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/Mobihelp.html -Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/package-summary.html -Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/api/reference/com/freshdesk/mobihelp/package-summary.html -Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/android/integration_guide/ -Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/ios/api/ -Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/ios/api/ -Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/ios/integration_guide/ -Advanced-Topics-Under-The-Hood.asciidoc http://developer.freshdesk.com/mobihelp/ios/integration_guide/#getting-started -Advanced-Topics-Under-The-Hood.asciidoc http://groovy-lang.org/ -Advanced-Topics-Under-The-Hood.asciidoc http://jruby.org/ -Advanced-Topics-Under-The-Hood.asciidoc http://shannah.github.io/cn1-freshdesk/ -Advanced-Topics-Under-The-Hood.asciidoc http://stackoverflow.com/questions/11421048/android-ios-custom-uri-protocol-handling -Advanced-Topics-Under-The-Hood.asciidoc http://www.mirah.org/ -Introduction.asciidoc http://teavm.org -Introduction.asciidoc http://www.xmlvm.org/ -Miscellaneous-Features.asciidoc http://wiki.akosma.com/IPhone_URL_Schemes -The-Components-Of-Codename-One.asciidoc http://awoiaf.westeros.org/index.php/A_Game_of_Thrones -The-Components-Of-Codename-One.asciidoc http://awoiaf.westeros.org/index.php/Portal:Books -The-Components-Of-Codename-One.asciidoc http://www.georgerrmartin.com/ -The-EDT---Event-Dispatch-Thread.asciidoc http://foxtrot.sourceforge.net/ -basics.asciidoc http://docs.oracle.com/Java -basics.asciidoc http://www.miglayout.com/QuickStart.pdf -graphics.asciidoc http://fontello.com/ -io.asciidoc http://twillo.com/ -io.asciidoc http://www.anapioficeandfire.com/api/characters?page=5&pageSize=3 -io.asciidoc http://www.codenameone.com/javadoc/com/codename1/io/ConnectionRequest.html -io.asciidoc http://www.codenameone.com/javadoc/com/codename1/io/NetworkManager.html -performance.asciidoc http://optipng.sourceforge.net/ -signing.asciidoc http://developer.android.com/guide/publishing/app-signing.html -signing.asciidoc http://developer.apple.com/ -signing.asciidoc http://get.udid.io/ -signing.asciidoc http://stackoverflow.com/questions/6320255/if-i-revoke-an-existing-distribution-certificate-will-it-mess-up-anything-with diff --git a/scripts/developer-guide/missing-code-blocks-baseline.txt b/scripts/developer-guide/missing-code-blocks-baseline.txt deleted file mode 100644 index d5a5f1f6104..00000000000 --- a/scripts/developer-guide/missing-code-blocks-baseline.txt +++ /dev/null @@ -1,409 +0,0 @@ -# Prose that promises a code block where none follows. -# A ratchet: entries may be removed as holes are filled, never added. -# Regenerate with check-missing-code-blocks.py --write-baseline. -3D-Graphics.asciidoc ARGB pixels: -3D-Graphics.asciidoc Querying capabilities at runtime: -3D-Graphics.asciidoc own material, so a textured model renders with no extra setup: -Advanced-Topics-Under-The-Hood.asciidoc For the release build, you will also need to inject some proguard configuration so that important classes don't get stripped out at build time. The FreshDesk SDK instructions state: -Advanced-Topics-Under-The-Hood.asciidoc You can override these methods in the draggable components: -Advertising.asciidoc (for example "Sponsored"): -Advertising.asciidoc anchored at the top or bottom) and call `load()`: -Advertising.asciidoc identifier. The recommended order is to initialize, gather consent, then load: -Advertising.asciidoc manager and provider handle the foreground hook and freshness window for you: -Advertising.asciidoc method that registers its provider with `AdManager`: -Advertising.asciidoc next one when the current ad is dismissed: -Advertising.asciidoc rewards, verify server side rather than trusting the client: -Advertising.asciidoc transitions, no more often than a given interval: -Ai-And-Speech.asciidoc `ChatMessage` values to JSON under a named key: -Ai-And-Speech.asciidoc `SecureStorage` overloads: -Ai-And-Speech.asciidoc `chatStream(...)` can mutate the view directly: -Ai-And-Speech.asciidoc `cn1-ai-stablediffusion` cn1lib (when present in the consumer project): -Ai-And-Speech.asciidoc accepts either inline bytes plus a MIME type or a remote HTTPS URL: -Ai-And-Speech.asciidoc and streams the same text into a `ChatView`: -Ai-And-Speech.asciidoc calling `cancel()` on the resource closes the underlying socket: -Ai-And-Speech.asciidoc clustering: -Ai-And-Speech.asciidoc conversation: -Ai-And-Speech.asciidoc one user message: -Ai-And-Speech.asciidoc returns a string the runtime can hand to `JSONParser`: -Ai-And-Speech.asciidoc safe to log: -Ai-And-Speech.asciidoc you need a custom send pipeline: -Analytics.asciidoc Consent is broken down by category, so you can honor granular choices -- for example allowing crash reporting while declining behavioral analytics: -Analytics.asciidoc Each device is identified by a pseudonymous client id. It's generated on first use and stored locally -- it isn't derived from any hardware identifier. To honor an erasure request ("right to be forgotten"), reset it: -Analytics.asciidoc If you would rather not gate at all, switch the default so collection is active unless the user withdraws it. This places the compliance responsibility on you as the integrator: -Analytics.asciidoc If your application has already obtained consent through its own mechanism -- a custom prompt, a third-party consent-management platform, an enterprise device-management policy, or a jurisdiction where you have determined consent isn't required -- record it once at startup so reporting flows without a second prompt: -Analytics.asciidoc Once at least one provider is registered, report usage from anywhere in your app: -Analytics.asciidoc You can also seed every category and then revoke just one with the builder, for example to allow analytics but not ad storage: -Analytics.asciidoc `FirebaseAnalyticsProvider` forwards to the native Firebase Analytics SDK on Android and iOS. Register it like any other provider: -Analytics.asciidoc `GoogleAnalyticsProvider` uses the GA4 Measurement Protocol. Create it with a measurement id (`G-XXXXXXXX`) and a Measurement Protocol API secret from the GA4 admin console: -Analytics.asciidoc `MatomoAnalyticsProvider` targets Matomo (formerly Piwik) through its HTTP tracking API. Matomo can be self-hosted and supports IP anonymization, which makes it a good fit for privacy-sensitive deployments: -Animations.asciidoc That one command will enable swiping back from `currentForm`. https://www.codenameone.com/javadoc/com/codename1/util/LazyValue.html[LazyValue] allows you to pass a value lazily: -Animations.asciidoc builder: -Annotation-Component-Binding.asciidoc ASM and inserts the equivalent of: -Annotation-Component-Binding.asciidoc After binding, drive validation through the returned handle: -Annotation-JSON-XML-Mapping.asciidoc Hand-write a `Mapper` and register it at startup: -Annotation-JSON-XML-Mapping.asciidoc mutation: -Annotation-SQLite-ORM.asciidoc dao surface isn't enough. Transactions: -App-Review.asciidoc By default the low-rating feedback is collected through an e-mail to the address passed to `setSupportEmail(String)`. To deliver feedback through your own backend, register a `FeedbackListener`: -App-Review.asciidoc Rather than picking the moment yourself, you can let `AppReview` decide based on simple engagement heuristics: how many times the app was launched, how long ago it was installed, and how long since it last asked. Configure it once (for example in your app's `init` or `start` method) and call `registerSession()` on every launch: -App-Review.asciidoc The simplest usage is a single call at a moment that makes sense in your app -- typically right after the user completed something rewarding (finished a level, saved a document, completed an order): -Apple-Wallet-Extension.asciidoc In Java, publish the user's cards whenever they change (typically after login) and keep a fresh token published so Wallet can skip the login screen: -Authentication-And-Identity.asciidoc Firebase Auth isn't an OIDC provider -- it issues Google-Identity-Toolkit-style tokens via REST endpoints. `com.codename1.social.FirebaseAuth` wraps those endpoints: -Authentication-And-Identity.asciidoc For federated sign-in (Google / Apple / Microsoft as Firebase providers), first obtain an ID token via the matching `*Connect` class, then swap it for a Firebase session: -Authentication-And-Identity.asciidoc Or, if the provider exposes a discovery document: -Authentication-And-Identity.asciidoc Pseudo-code (the actual HTTP calls depend on your server library): -Authentication-And-Identity.asciidoc Refresh the Firebase session at app launch: -Authentication-And-Identity.asciidoc `OidcClient` saves the response under a per-issuer + per-client-ID key using `com.codename1.io.oidc.TokenStore.DefaultStorageTokenStore` (which serializes to `com.codename1.io.Storage`). To restore on next launch: -Authentication-And-Identity.asciidoc `com.codename1.social.FacebookConnect` exposes both the old SDK-based `doLogin()` and a new SDK-free `signIn(...)` that uses the system browser via `OidcClient`. Use the new method for the simulator, the web port, and for apps that don't want to bundle the Facebook SDK at all: -Authentication-And-Identity.asciidoc `com.codename1.social.GoogleConnect` now offers a modern `signIn(...)` method that runs entirely through `OidcClient`: -Authentication-And-Identity.asciidoc becomes: -Commerce.asciidoc After a purchase, or on app start, validate the device's receipts with the cloud and refresh the entitlement cache. `refresh()` makes a blocking network call, so run it off the EDT: -Commerce.asciidoc Drive purchases through the manager (these delegate to the `Purchase` API): -Commerce.asciidoc In your `Lifecycle.init` (or wherever you set up purchases): -Commerce.asciidoc The core idea is the *entitlement*: an abstract access right such as `pro` or `remove_ads`. You map one or more store products to an entitlement, and your code only ever checks the entitlement: -Crash-Protection.asciidoc In your `Lifecycle.init`: -Deep-Links-Routing.asciidoc Or annotate a static factory method: -Deep-Links-Routing.asciidoc `AssetLinksBuilder` produces the payload: -Deep-Links-Routing.asciidoc attempts so they can confirm before discarding unsaved work: -Deep-Links-Routing.asciidoc without redirects. The plugin's `AasaBuilder` produces the payload: -Desktop-Integration.asciidoc same scheduling and handling code is shared across phone and desktop: -Device-Input-And-Form-Factors.asciidoc (or `Display`), mirroring the existing `CN.isShiftKeyDown()` family: -Device-Input-And-Form-Factors.asciidoc Any pointer listener receives an `ActionEvent`; call `getPointerEvent()` on it: -Device-Input-And-Form-Factors.asciidoc React to fold changes with a posture listener: -Device-Input-And-Form-Factors.asciidoc handles desktop and touch: -Device-Input-And-Form-Factors.asciidoc https://www.codenameone.com/javadoc/com/codename1/ui/DevicePosture.html[`DevicePosture`]: -Device-Input-And-Form-Factors.asciidoc pointer is a stylus or eraser: -Events.asciidoc The pointer events (touch events) can be intercepted by overriding one or more of these methods in `Component` or `Form`. Notice that unless you want to block functionality you should probably invoke `super` when overriding: -Events.asciidoc Then when you need to broadcast the event use: -Events.asciidoc https://www.codenameone.com/javadoc/com/codename1/ui/events/StyleListener.html[StyleListener] allows components to track changes to the style objects. For example, if the developer does something like: -Game-Assets.asciidoc is registered as solid. Applications add their own without changing the level format: -Game-Builder.asciidoc Loading and playing a level at runtime is three lines: -Game-Builder.asciidoc `start()` it: -Game-Builder.asciidoc the editor: -Game-Development.asciidoc as 2:1 diamonds -- columns step right-and-down, rows step left-and-down: -Game-Development.asciidoc build the frames yourself, which is what `ScrollerGameSample`'s runner does: -Game-Development.asciidoc come from one piece of geometry: -Game-Development.asciidoc each card's bounding box: -Game-Development.asciidoc engine can report completion, so guard with `isVoiceCompletionSupported()`: -Game-Development.asciidoc handle: -Game-Development.asciidoc layers the sprites within a cell: -Game-Development.asciidoc never recompute coordinates: -Game-Development.asciidoc objects, and step it once per frame from your `update`: -Game-Development.asciidoc rendered positions between physics states: -Game-Development.asciidoc required: -Game-Development.asciidoc rotation automatically (in pixels, screen space) -- and the scene draws it there: -Game-Development.asciidoc runs once before the first frame: -Game-Development.asciidoc that always faces the camera, so your existing 2D art keeps working in 3D: -Game-Development.asciidoc the thin point reads exactly the same to the eye: -Game-Development.asciidoc uploading it with `com.codename1.gpu.GraphicsDevice#createTexture(com.codename1.ui.Image)`: -In-Car-Experiences.asciidoc Head units enforce a hard cap on the number of rows/items they display (driver-distraction rules). Query it and trim accordingly: -In-Car-Experiences.asciidoc Register a single `CarApplication` from your app's `init()` -- before a head unit connects -- and return a root `CarScreen` that builds a template: -In-Car-Experiences.asciidoc `CarContext` manages a back stack, mirroring `androidx.car.app`'s `ScreenManager` and CarPlay's `CPInterfaceController`: -In-Car-Experiences.asciidoc `CarScreen` exposes optional lifecycle hooks -- `onCreate()`, `onResume()`, `onPause()`, `onDestroy()` -- and `CarApplication` is notified of connection changes via `onCarConnected(CarContext)` / `onCarDisconnected()`. You can also observe connection globally: -Introduction.asciidoc In a cold start `init(Object)` is invoked followed by the `start()` method. For example, `start()` can be invoked more than once if an app is minimized and restored, see the sidebar <>: -Introduction.asciidoc Next consider the first lifecycle method `init(Object)`. The <> discusses the lifecycle in depth: -Introduction.asciidoc Now that you have a general sense of the lifecycle lets look at the last two lifecycle methods: -Introduction.asciidoc The hello world Java source file looks like this (removed some comments and whitespace): -Maps.asciidoc A native provider only renders on the platforms that ship its SDK. To show a provider's map *everywhere* -- including platforms with no native SDK for it -- register a `WebMapProvider`, which hosts the provider's JavaScript SDK inside a `BrowserComponent`: -Maps.asciidoc Every map -- vector or native -- exposes the same operations through `MapSurface`: -Maps.asciidoc For a real app you point `MapView` at a hosted tile service. The simplest keyless option is OpenFreeMap (OpenStreetMap-based vector tiles); for branded styles or higher quotas use a keyed provider such as MapTiler or your own self-hosted Protomaps/TileServer GL endpoint: -Maps.asciidoc When a provider is selected the build server injects that provider's implementation into your app and wires it in; with no provider selected (or when the provider is unavailable at runtime, for example when Google Play Services is missing) `NativeMap` simply renders the vector `MapView` fallback. You can configure the fallback basemap explicitly: -Maven-Creating-CN1Libs.adoc Now try it out. Try adding the following code to your application project's main class (or anywhere in the application project, for that matter): -Maven-Creating-CN1Libs.adoc The simulator dispatches every action on the Codename One EDT through `Display.callSerially`, so your method can call `Display.getInstance()`, `Form.show()`, `Dialog.show()`, `ToastBar.showInfoMessage()` and any other CN1 API. Reflection uses the same classloader that loaded `Display`, so cn1lib internals (including package-private classes) resolve normally: -Media-And-Audio.asciidoc `AudioEffects` provides small platform-neutral PCM transforms that compose with the mixer: -Miscellaneous-Features.asciidoc A typical use of this API would be something like this: -Miscellaneous-Features.asciidoc Easy thread can be created like this: -Miscellaneous-Features.asciidoc For Android 11+ (API 30+), Codename One detects if background location is needed and presents a dialog explaining the need before redirecting the user to the app settings. You can customize the permission prompt message using the localization key `android.permission.ACCESS_BACKGROUND_LOCATION`: -Miscellaneous-Features.asciidoc For example: you can copy the `Image` to `Storage` using: -Miscellaneous-Features.asciidoc However, it gets better, say you want to return a value: -Miscellaneous-Features.asciidoc If you need to apply the scale manually for custom fonts or layout calculations, read the values directly: -Miscellaneous-Features.asciidoc It allows formatting numbers/dates & time based on platform locale. It also provides a great deal of the information you need such as the language/locale information you need to pick the proper resource bundle: -Miscellaneous-Features.asciidoc The `Capture` API also includes a callback based API that uses the `ActionListener` interface to implement capture. For example: you can adapt the previous sample to use this API as such: -Miscellaneous-Features.asciidoc The following code demonstrates usage of the GeoFence API: -Miscellaneous-Features.asciidoc The list of available languages in the resource bundle could be retrieved like this. Notice that this a list that was set by you and doesn't need to confirm to the ISO language code standards: -Miscellaneous-Features.asciidoc These asynchronous calls make things a bit painful to wade through, so the API wraps them in a simplified synchronous version: -Miscellaneous-Features.asciidoc To solve this sort of used case you have two APIs in `Display`: -Miscellaneous-Features.asciidoc You can add more than one attachment by putting them directly into the attachment map for example: -Miscellaneous-Features.asciidoc You can install the bundle using code like this: -Miscellaneous-Features.asciidoc You can send a task to the thread using: -Monetization.asciidoc And you also provide a button to allow the user to manually synchronize the receipts: -Monetization.asciidoc And your `itemPurchased()` callback will need to add a world: -Monetization.asciidoc At the end of the `start()` method: -Monetization.asciidoc At this point, the app can track the sale of the world. To make it more useful, add `ToastBar` feedback for purchase completion: -Monetization.asciidoc In the hello world app you'll use this information in a few different places. On your main form you'll include a label to show the current expiry date, and you allow the user to press a button to synchronize receipts manually if they think the value is out of date: -Monetization.asciidoc In the main form, you want two buttons to subscribe to the `World`, for one month and one year respectively. They look like: -Monetization.asciidoc Now in the `start()` method, add a button that lets the user buy the world: -Monetization.asciidoc Now you'll change your buy code as follows: -Monetization.asciidoc On the server-side, your REST controller is a standard JAX-RS REST interface. The Netbeans web service wizard generated it and then it was modified to suit the purposes here. The methods of the `ReceiptsFacadeREST` class for the REST API are shown here: -Monetization.asciidoc Once implemented, your `fetchReceipts()` method will look like: -Monetization.asciidoc The `createRESTClient()` method shown there creates a `RESTfulWebServiceClient` and configuring it to use basic authentication with a username and password. The idea is that your user would have logged into your app at some point, and you would have a username and password on hand to pass back to the web service with the receipt data so that you can connect the subscription to a user account. The source of that method is listed here: -Monetization.asciidoc The `submitReceipt()` method is a little more complex, as it needs to calculate the new expiry date for your subscription: -Monetization.asciidoc The buy callbacks are like the ones implemented in the regular in-app purchase examples: -Monetization.asciidoc The general usage is as follows: -Monetization.asciidoc The source for your ReceiptStore is as follows: -Monetization.asciidoc To build the signed discount payload required by Apple you can use the `ApplePromotionalOffer` helper: -Monetization.asciidoc You are now ready to see the full magic of the `validateAndSaveReceipt()` method in all its glory: -Monetization.asciidoc You'll register it in your app's init() method so that it's always available: -Monetization.asciidoc You'll set these in your constants: -Motion-Sensors.asciidoc Callbacks arrive on the EDT, so the listener can update the UI directly. Sampling is reference counted: the hardware sensor draws power only while at least one listener is registered. Remove the listener once the screen is no longer visible, typically from the form's `removeNotify`, so the sensor powers down: -Motion-Sensors.asciidoc Register a `GestureListener` for one of the `GestureEvent.TYPE_*` gestures. The accelerometer powers on for the duration that a gesture listener stays registered: -Motion-Sensors.asciidoc The detection thresholds suit most apps as shipped, and you can tune them when a gesture needs to be more or less sensitive: -Motion-Sensors.asciidoc `MotionSensorManager.getInstance()` always returns a manager. On a device without motion hardware every sensor reports as unsupported, so application code can call the API without a null check on the manager itself. `getSensor(int)` returns `null` when the requested sensor isn't available on the current device: -Native-Themes.asciidoc `UIManager.addThemeProps` after the theme has been installed: -Near-Field-Communication.asciidoc Beyond NDEF, the discovered `Tag` exposes accessors for the underlying technologies: -Near-Field-Communication.asciidoc For FeliCa on iOS, set the system codes in `NfcReadOptions`: -Near-Field-Communication.asciidoc Host Card Emulation (HCE) lets the device pretend to be a contactless smart card. A nearby reader (Android phone, payment terminal, access control gate) sends ISO 7816 APDUs and your app responds. Subclass `HostCardEmulationService` and register the instance: -Network-Connectivity.asciidoc To advertise an HTTP server you wrote on port 8080: -Network-Connectivity.asciidoc To associate the device with a specific SSID: -Notifications-And-Background-Execution.asciidoc before; the bean has been extended with fluent, backward-compatible setters: -Notifications-And-Background-Execution.asciidoc then reference it from a notification with `setChannelId(...)`: -Printing.asciidoc `Printer.printImage(image, listener)` prints a `com.codename1.ui.Image` directly. The image is encoded to a temporary PNG file behind the scenes and the file is deleted once the print flow finishes: -SVG-Transcoder.asciidoc the generated class directly: -The-Components-Of-Codename-One.asciidoc A common source of confusion in the `MultiButton` is the difference between the icon and the emblem, since both may have an icon image associated with them. The icon is an image representing the entry while the emblem is an optional visual representation of the action that will be undertaken when the element is pressed. Both may be used simultaneously or individually of one another: -The-Components-Of-Codename-One.asciidoc A form is a container with a title, a content area, and an optional menu or menu-bar area. When you call methods such as `add` or `remove` on a form, you're invoking something that maps to this: -The-Components-Of-Codename-One.asciidoc A more practical "real world" example would be working with XML data. You can use something like this to show an XML `Tree`: -The-Components-Of-Codename-One.asciidoc A picker can work with your existing sample using code like this: -The-Components-Of-Codename-One.asciidoc A popup dialog is a common mobile paradigm showing a `Dialog` that points at a specific component. It's a standard `Dialog` that's shown in a unique way: -The-Components-Of-Codename-One.asciidoc And call its methods: -The-Components-Of-Codename-One.asciidoc Call the builder from a Maven plugin, an Ant task or a one-shot `main`: -The-Components-Of-Codename-One.asciidoc For example, if you wish to query a database or a web service you will need to derive the class and perform more advanced filtering by overriding the `filter` method: -The-Components-Of-Codename-One.asciidoc For example, suppose you want to pass a string with text to set in a textarea within the webpage. You can do something like: -The-Components-Of-Codename-One.asciidoc For example, this behavior might not be desired so to block that you can do: -The-Components-Of-Codename-One.asciidoc For example, to enclose the `CheckBox` components in a vertical `ComponentGroup` and the `RadioButton's` in a horizontal group, change the last line of the code above as such: -The-Components-Of-Codename-One.asciidoc For example, you can change the focus `Component` to have an icon: -The-Components-Of-Codename-One.asciidoc For example: -The-Components-Of-Codename-One.asciidoc For example: You might want to create a proxy for the https://developer.mozilla.org/en-You/docs/Web/API/Window/location[window.location] object so that you can access its properties more from Java: -The-Components-Of-Codename-One.asciidoc For example: the demo below uses the GRRM demo data from above to build a `ComboBox`: -The-Components-Of-Codename-One.asciidoc Here is a simple example of a `MultiList` containing a highly popular subject matter: -The-Components-Of-Codename-One.asciidoc Here is a simple example that also shows the difference between the `scale to fill` and `scale to fit` modes: -The-Components-Of-Codename-One.asciidoc If you add to the sample above a `Validator`: -The-Components-Of-Codename-One.asciidoc If you call `Display` directly instead of using `ShareButton`, pass the listener as the final argument to the new overload: -The-Components-Of-Codename-One.asciidoc In the sample below you fetch all the contacts from the device and enable search through them, notice it expects and image called `duke.png` which is the default Codename One icon renamed and placed in the src folder: -The-Components-Of-Codename-One.asciidoc It's also recommended to place the list in the `CENTER` location of a https://www.codenameone.com/javadoc/com/codename1/ui/layouts/BorderLayout.html[BorderLayout] to produce the most effective results. For example: -The-Components-Of-Codename-One.asciidoc Let start by exploring how you can achieve this UI that fetches data from a webservice: -The-Components-Of-Codename-One.asciidoc Masking allows you to accept partial input in one field and implicitly move to the next, this can be used to all types of complex input thanks to the text component API. E.g with the code above you can mask the credit card input so the cursor jumps to the next field implicitly using this code: -The-Components-Of-Codename-One.asciidoc NOTE: In all the sample code below, you can assume that variables named `bc` represent an instance of https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html[BrowserComponent]: -The-Components-Of-Codename-One.asciidoc Normally, to build the model for a renderer of this type, you use something like: -The-Components-Of-Codename-One.asciidoc Notice in the sample below that you associate all the radio buttons with a group but don't do anything with the group as the radio buttons keep the reference internally. You also show the opposite side functionality and icon behavior: -The-Components-Of-Codename-One.asciidoc Notice that this feature works with the on-top and permanent versions of the side menu and not with the legacy versions: -The-Components-Of-Codename-One.asciidoc Notice that when you work with numeric values or anything related to the types mentioned above your code must be aware of the typing. For example: in this case the type is `Double` and not `String`: -The-Components-Of-Codename-One.asciidoc Now look at a more advanced Renderer: -The-Components-Of-Codename-One.asciidoc Now make it a bit more useful: -The-Components-Of-Codename-One.asciidoc Now that you have a webservice lets proceed to create the UI. Check out the code annotations below: -The-Components-Of-Codename-One.asciidoc One of the features of label that moved into `SpanLabel` to some extent is the ability to position the icon. For example, unlike a `Label` the icon position is determined by the layout manager of the composite so `setIconPosition` accepts a `BorderLayout` constraint: -The-Components-Of-Codename-One.asciidoc Or synchronously: -The-Components-Of-Codename-One.asciidoc Simple usage of the `Calendar` class looks something like this: -The-Components-Of-Codename-One.asciidoc Simple usage of the `ToastBar` class looks something like this: -The-Components-Of-Codename-One.asciidoc The Renderer is a simple interface with 2 methods: -The-Components-Of-Codename-One.asciidoc The callbacks you pass to `execute()` and `executeAndWait()` are single-use callbacks. You can’t, for example, store the `callback` variable on the JavaScript side for later use (for example: to respond to a button click event). If you need a "multi-use" callback, you should use the `addJSCallback()` method instead. Its usage looks identical to `execute()`, the difference is that the callback will live on after its first use. For example: Consider the following code: -The-Components-Of-Codename-One.asciidoc The code below provides a brief overview of these options: -The-Components-Of-Codename-One.asciidoc The data of the `Tree` arrives from a model for example: this: -The-Components-Of-Codename-One.asciidoc The first step is creating the webservice call, you won't go into too much detail here as webservices & IO are discussed later in the guide: -The-Components-Of-Codename-One.asciidoc The https://www.codenameone.com/javadoc/com/codename1/ui/list/ListModel.html[ListModel] interface can be implemented by anyone in this case you did a stupid simple implementation: -The-Components-Of-Codename-One.asciidoc The model for the XML hierarchy is implemented as such: -The-Components-Of-Codename-One.asciidoc The most simple/naive implementation may choose to implement the renderer as follows: -The-Components-Of-Codename-One.asciidoc The old API provided a synchronous wrapper around an inherently asynchronous process, and made extensive use of `invokeAndBlock()` underneath the covers. This resulted in a nice API with high-level abstractions that played with a synchronous programming model, but it came with a price-tag in performance, complexity, and predictability. Let’s take a simple example, getting a reference to the "window" object: -The-Components-Of-Codename-One.asciidoc The screenshot above was produced using the following code: -The-Components-Of-Codename-One.asciidoc The split pane component is a bit desktop specific but works reasonably well on devices. To get the image below you changed `SalesDemo.java` in the kitchen sink by changing this: -The-Components-Of-Codename-One.asciidoc Then implement the method `automoveToNext` as: -The-Components-Of-Codename-One.asciidoc Then you can retrieve its properties using the `get()` method: -The-Components-Of-Codename-One.asciidoc This code should output "The result was 7" to the console. It's fully asynchronous, so you can include this code anywhere without worrying about it "bogging down" your code. The full signature of this form of the https://www.codenameone.com/javadoc/com/codename1/ui/BrowserComponent.html#execute-java.lang.String-com.codename1.util.SuccessCallback-[execute()] method is: -The-Components-Of-Codename-One.asciidoc This sample below continues from the place where the <> stopped by adding validation to that code: -The-Components-Of-Codename-One.asciidoc This swipe gesture is commonly used in touch interfaces to expose features such as delete, edit etc. It's trivial to use this component by determining the components placed on top and bottom (the revealed component): -The-Components-Of-Codename-One.asciidoc To make the code tighter, keep a reference to the `Component` or extend it as https://www.codenameone.com/javadoc/com/codename1/ui/list/DefaultListCellRenderer.html[DefaultListCellRenderer] does: -The-Components-Of-Codename-One.asciidoc Unlike the `InfiniteScrollAdapter` you can't use the `ContentPane` directly so you've to use a `BorderLayout` and place the `InfiniteContainer` there: -The-Components-Of-Codename-One.asciidoc Unlike the `MultiButton` it uses the `TextArea` internally to break lines seamlessly. The `SpanButton` is far simpler than the `MultiButton` and as a result isn't as configurable: -The-Components-Of-Codename-One.asciidoc What if you want componentName to be red? Just use: -The-Components-Of-Codename-One.asciidoc When using lightweight picker mode (`setUseLightweightPopup(true)`), you can add custom quick-action buttons to the popup. This is useful for actions like setting the date to "Today" or "+7 Days" without scrolling the wheels manually: -The-Components-Of-Codename-One.asciidoc With this: -The-Components-Of-Codename-One.asciidoc You can access JavaScript variables from the context by using code like this: -The-Components-Of-Codename-One.asciidoc You can also delay the showing of the status message using `showDelayed` as such: -The-Components-Of-Codename-One.asciidoc You can also query the context for objects and change their value for example: -The-Components-Of-Codename-One.asciidoc You can also set its properties: -The-Components-Of-Codename-One.asciidoc You can automatically clear a status message/progress after a timeout using the `setExpires` method as such: -The-Components-Of-Codename-One.asciidoc You can combine some demos above including the <> to rank GRRM's books in an interactive way: -The-Components-Of-Codename-One.asciidoc You can convert the sample above to use toggle buttons as such: -The-Components-Of-Codename-One.asciidoc You can dynamically download images directly into the `ImageViewer` with a custom list model like this: -The-Components-Of-Codename-One.asciidoc You can now replace the existing model by removing all the model related logic and changing the constructor call as such: -The-Components-Of-Codename-One.asciidoc You can show a progress indicator in the ToastBar like this: -The-Components-Of-Codename-One.asciidoc You can use the `ImageViewer` as a tool to view a single image which allows you to zoom in/out to that image as such: -The-Components-Of-Codename-One.asciidoc You can wrap a `TextField` with a clearable wrapper to get this effect on all platforms. For example: replace this: -The-Components-Of-Codename-One.asciidoc You need to change this code to use the `addJSCallback()` method as follows: -The-Components-Of-Codename-One.asciidoc You will need to define the following theme constants for the arrow to work: -The-Components-Of-Codename-One.asciidoc `ImageViewer` also supports optional side arrows (material font icons) and an optional thumbnail strip for direct image navigation: -The-Components-Of-Codename-One.asciidoc `InfiniteProgress` can be used in one of two ways either by embedding the component into the UI through something like this: -The-Components-Of-Codename-One.asciidoc `Label` text can be positioned in one of 4 locations as such: -The-Components-Of-Codename-One.asciidoc `Slider` is highly customizable for example: a slider can be used to replicate a 5-star rating widget as such. Notice that this slider will work when its given its preferred size otherwise more stars will appear. That's why you place it within a `FlowLayout`: -The-Components-Of-Codename-One.asciidoc `setRenderingPrototype` accepts a "fake" value that represents a reasonably large amount of data and it will be used to calculate the preferred size. For example: for a multiList that should render 2 lines of text with 20 characters and a 5mm square icon you can do something like this: -The-Components-Of-Codename-One.asciidoc app for specific examples, but here is a high-level view of some code that creates a Pie Chart: -The-Components-Of-Codename-One.asciidoc doing this, you need to define this entry for all entries, for example: -The-Components-Of-Codename-One.asciidoc or a go icon using a hint such as this: -The-EDT---Event-Dispatch-Thread.asciidoc A simplistic approach is to do something like this: -The-EDT---Event-Dispatch-Thread.asciidoc For example, instead of using operation names lets use a more "real world" example: -The-EDT---Event-Dispatch-Thread.asciidoc However, `updateUIWithContentOfFile` should be executed on the EDT and not on a random thread. The right way to do this would therefore be something like this: -The-EDT---Event-Dispatch-Thread.asciidoc IMPORTANT: The Runnable passed to the `callSerially` and `callSeriallyAndWait` methods isn't a `Thread`. Use the `Runnable` interface as a convenient callback interface: -The-EDT---Event-Dispatch-Thread.asciidoc Invoke and block solves this in a unique way you can get almost the exact same behavior by using this: -The-EDT---Event-Dispatch-Thread.asciidoc Or this with Java 8 syntax: -The-EDT---Event-Dispatch-Thread.asciidoc TIP: You can write this code more concisely using Java 8 lambda code as such: -The-EDT---Event-Dispatch-Thread.asciidoc The `callSeriallyAndWait(Runnable)` method blocks the current thread until the method completes, this is useful for cases such as user notification e.g.: -The-EDT---Event-Dispatch-Thread.asciidoc This is best explained by an example. Typical Java code reads as a single sequence as such: -The-EDT---Event-Dispatch-Thread.asciidoc To explain how invokeAndBlock works you can return to the sample above of how the EDT works: -The-EDT---Event-Dispatch-Thread.asciidoc You can visualize the EDT as a loop such as this: -The-EDT---Event-Dispatch-Thread.asciidoc `invokeAndBlock()` works in a similar way to this pseudo-code: -Video-Capture-Constraints.asciidoc Find out if this constraint will be obeyed: -Video-Capture-Constraints.asciidoc For example: -Video-Capture-Constraints.asciidoc Suppose you want to allow the user to capture a short (5 second) clip, in a low resolution, appropriate for sharing on a social media platform. You create your `VideoCaptureConstraints` object as follows: -Video-Capture-Constraints.asciidoc This constraint can then be passed to `Capture.captureVideo()` to get the captured file: -Working-With-iOS.asciidoc Repeating notifications will continue until they're canceled by the app. You can cancel a single notification by calling: -appendix_goal_generate_graphql.adoc The `@GraphQLClient` interface looks like: -appendix_goal_generate_graphql.adoc an `OnComplete>`: -appendix_goal_generate_graphql.adoc ends the stream: -appendix_goal_generate_grpc.adoc Call sites use the static factory: -appendix_goal_generate_grpc.adoc The `@GrpcClient` interface looks like: -appendix_goal_generate_openapi.adoc Call sites use the static factory: -basics.asciidoc For example: see this code where: -basics.asciidoc From that point on you can write code that looks like this: -basics.asciidoc In the race to make code "`tighter`" you can make this even shorter. Most layout managers have their own custom terse syntax style for example: -basics.asciidoc Instead of: -basics.asciidoc Instead of: -basics.asciidoc Some things were changed so you won't have too many conflicts for example, `Log.p` or `Log.e` would have been problematic so you now have: -basics.asciidoc The UIID's translate the theme elements into a set of `Style` objects. These `Style` objects get their initial values from the theme but can be further manipulated after the fact. To make the text field's foreground color red you could use this code: -basics.asciidoc The same applies for most network manager calls e.g.: -basics.asciidoc This works great for regular layouts but might not for constraint based layouts. A constraint based layout accepts another argument. For example: `BorderLayout` needs a location for the `Component`: -basics.asciidoc and `setSameHeight` methods, for example: -graphics.asciidoc (that's: where the numbers appear), and the remaining marks (corresponding with seconds) will be short: -graphics.asciidoc 3. Invert the translation performed in step 1: -graphics.asciidoc A `URLImage` can be created with a mask adapter to apply an effect to an image. This allows you to round downloaded images or apply any sort of masking for example: you can adapt the round mask code above as such: -graphics.asciidoc And you will translate it down slightly so that it overlaps the center. This translation will be performed on the `GeneralPath` object directly rather than through the `Graphics` context: -graphics.asciidoc Center: -graphics.asciidoc For example: a `pointerPressed()` callback method can look like this: -graphics.asciidoc The `animate()` method in the `AnalogClock` class: -graphics.asciidoc The code to instantiate the clock, and start the animation would be something like: -graphics.asciidoc The remaining drawing code is as follows: -graphics.asciidoc The simple use case is pretty trivial: -graphics.asciidoc To do this you can override: -graphics.asciidoc Top Left Corner: -graphics.asciidoc `ConicGradient` - following the same pattern as the `Shape` hierarchy: -graphics.asciidoc can override or augment whatever the default set: -graphics.asciidoc class is the most common option and accepts a list of color stops along a line: -graphics.asciidoc coordinates of the component. You therefore need to get the absolute clock center position to perform the rotation: -graphics.asciidoc for the images. The default is a scale adapter although you might change that to scale fill in the future: -graphics.asciidoc hook. Two ways to install one: -graphics.asciidoc https://www.codenameone.com/javadoc/com/codename1/ui/Graphics.html#isShapeSupported()[`Graphics.isShapeSupported()`] method. For example: -graphics.asciidoc it can be added to a form: -graphics.asciidoc mark at the 12 o'clock position: -graphics.asciidoc method to append curves to the drawing as follows: -graphics.asciidoc methods in the component as follows: -graphics.asciidoc name of icon to `icon_URLImage` then using this in the data: -graphics.asciidoc show this, try to place five of these components on a form inside a https://www.codenameone.com/javadoc/com/codename1/ui/layouts/BorderLayout.html[BorderLayout] and see how it looks: -graphics.asciidoc straight lines rather than curves might look like this: -graphics.asciidoc to draw each individual tick: -io.asciidoc A more advanced usage of the `FileSystemStorage` API can be a `FileSystemStorage` `Tree`: -io.asciidoc A simpler implementation could do something like this: -io.asciidoc Above, if you want to select the IDs of all players that are ranked in the top 2, you can use an expression like: -io.asciidoc Above, you globally find a lastname element with a value of ‘Hewitt’, then grab the parent node of lastname which happens to be the player node, then grab the ID attribute from the player node. Or, you could get the same result from the following simpler statement: -io.asciidoc Above, you selected the IDs of all ranked players. Conversely, you can select the non-ranked players like this: -io.asciidoc After you do that once you can write/read contacts from storage if you so want: -io.asciidoc An `Externalizable` object *must* have a *default public constructor* and must implement the following 4 methods: -io.asciidoc And delete an entry using: -io.asciidoc And vice versa: -io.asciidoc Another approach is to use the `setFailSilently(true)` method on the `ConnectionRequest`. This will prevent the `ConnectionRequest` from displaying any errors to the user. It's a powerful strategy if you use the synchronous version of the APIs for example: -io.asciidoc As part of the premium cloud features it's possible to invoke Log.sendLog() to email a log directly to the developer account. Codename One can do that seamlessly based on changes printed into the log or based on exceptions that are uncaught or logged for example: -io.asciidoc Assuming you added a new date field to the object you can do the following. Notice that a `Date` is a `long` value in Java that can be null. For completeness the full class is presented below: -io.asciidoc Binding makes this all seamless. For example: the code above can be written as: -io.asciidoc By default `GZConnectionRequest` doesn't request gzipped data ( unzips it when its received) but its pretty easy to do so add the HTTP header `Accept-Encoding: gzip` for example: -io.asciidoc Codename One provides many tools to simplify the path between networking/IO & GUI. A common task of showing a wait dialog or progress sign while fetching network data can be simplified by using the https://www.codenameone.com/javadoc/com/codename1/components/InfiniteProgress.html[InfiniteProgress] class for example: -io.asciidoc Developers need to write the data of the object in the externalize method using the methods in the data output stream and read the data of the object in the internalize method for example: -io.asciidoc For a lot of REST requests this will fail because you need to add an HTTP header indicating that you accept JSON results. You have a special case support for that: -io.asciidoc For endpoints that return a list of DTOs, use `fetchAsMappedList`: -io.asciidoc For example, if you wish to have finer grained control over the submission process for example: for making a `HEAD` request you can do this with code like: -io.asciidoc For example: to block all network errors from showing anything to the user you could do something like this: -io.asciidoc For example: you can do something like this in your `init(Object)` method: -io.asciidoc For starters all the common methods of `Object` can be implemented with almost no code: -io.asciidoc If a document is ordered, you might want to select nodes by their position, for example: -io.asciidoc If you continue the example from above to show persistence to the SQL database you can do something like this: -io.asciidoc If you continue your example from above you can do something like this: -io.asciidoc Implementing the `Externalizable` interface is important when you want to store a proprietary object. In this case you must register the object with the `com.codename1.io.Util` class so the externalization algorithm will be able to recognize it by name by invoking: -io.asciidoc In the above code you do the following: -io.asciidoc It's also possible to nest expressions, for example: -io.asciidoc It's also possible to select parent nodes, by using the `..` expression. For example: -io.asciidoc Listing the entries is more interesting: -io.asciidoc Moving on, to select a node based on the existence of an attribute: -io.asciidoc Notice that you can also implement the same thing and much more by avoiding the response listener code and instead overriding the methods of the `ConnectionRequest` class which offers many points to override for example: -io.asciidoc One of the bigger features of properties are their ability to bind UI to a property. For example: if you continue the sample above with the `Contact` class, say you have a text field on the form and you want the property (which you mapped to the database) to have the value of the text field. You could do something like this: -io.asciidoc POST requests use the same builder pattern: -io.asciidoc Server returned headers are a bit trickier to read. You need to subclass the connection request and override the `readHeaders` method for example: -io.asciidoc Since strings might be null sometimes you also included convenience methods to implement such externalization. This effectively writes a boolean before writing the UTF to show whether the string is null: -io.asciidoc Since you assume most developers reading this will be familiar with Java here is the way to implement the multipart upload in the servlet API: -io.asciidoc Some headers are built-in as direct APIs for example: content type is directly exposed within the API since it's a pretty common use case. You can set the content kind of post request using: -io.asciidoc Some objects make sense as global objects, you can use the `Preferences` API to store that data directly but then you don't have the type safety that property objects bring to the table. That's where the binding of property objects to preferences makes sense. For example: say you have a global `Settings` property object you can bind it to preferences using: -io.asciidoc That's what the new method of `URLImage` does: -io.asciidoc The JSON ("JavaScript Object Notation") format is popular on the web for passing values to/from webservices since it works so well with JavaScript. Parsing JSON is as easy but has two different variations. You can use the https://www.codenameone.com/javadoc/com/codename1/io/JSONParser.html[JSONParser] class to build a tree of the JSON data as such: -io.asciidoc The XML processor handles global selections by using a double slash anywhere within the expression, for example: -io.asciidoc The `CachedDataService` will fetch data if it isn't cached locally and cache it. When you "refresh" it will send a special HTTP request that will send back the data if it has been updated since the last refresh: -io.asciidoc The `Rest` API makes it easy to invoke a RESTful webservice without many of the nuances of `ConnectionRequest`. You can use it to define the HTTP method and start building based on that. To get a parsed JSON result from a URL you could do: -io.asciidoc The cool thing is that this works with many component types and property types almost magically. Binding works by using an adapter class to convert the data to/from the component. The adapter itself works with a generic converter for example: this code: -io.asciidoc The sample code below demonstrates listing storage content, adding and viewing entries, and deleting entries: -io.asciidoc The simplest usage of `XMLParser` looks a bit like this: -io.asciidoc There are many methods of interest to keep an eye for: -io.asciidoc This can be tedious to do if you want all requests from your app to use this header. For this use case you can use: -io.asciidoc This means that this code won't compile: -io.asciidoc This should display an error message to the user if there was a problem sending the SMS: -io.asciidoc This simple example allows you to create a server and a client assuming the device supports both: -io.asciidoc This still carries most of the flexibilities of the regular binding for example: you can still get a binding object using: -io.asciidoc To parse a CSV use the https://www.codenameone.com/javadoc/com/codename1/io/CSVParser.html[CSVParser] class as such: -io.asciidoc Up until now this was pretty cool but if you looked at the UI construction code above you would see that it's pretty full of boilerplate code. The thing about boilerplate is that it shows where automation can be applied, that's the exact idea behind the magical "InstantUI" class. This means that the UI above can be generated using this code: -io.asciidoc When you used a POJO you did this: -io.asciidoc With properties you do this: -io.asciidoc Without this feature the code would look like this: -io.asciidoc Working with a database is pretty trivial, the application logic below can send arbitrary queries to the database and present the results in a `Table`. You can probably integrate this code into your app as a debugging tool: -io.asciidoc You added a new syntax: -io.asciidoc You can also add any arbitrary header type you want, for example: a common use case is basic authorization where the authorization header includes the Base64 encoded user/password combination as such: -io.asciidoc You can also override the error callbacks of the various types in the request for example: for a server error code you can do: -io.asciidoc You can always submit data in the `buildRequestBody` but this is flaky and has some limitations in devices/size allowed. HTTP standardized file upload capabilities through the multipart request protocol, this is implemented by countless servers and is well documented. Codename One supports this out of the box: -io.asciidoc You can build a UI that would allow you to edit the `Contact` property in memory: -io.asciidoc You can do some more elaborate bindings such as: -io.asciidoc You can now send hello world as an SMS to the end user. Once this is in place sending an SMS through REST is a matter of using the `Rest` API: -io.asciidoc You can pick either one of these approaches based on your personal preferences. Here you show both uses with the server API: -io.asciidoc You can send a get request to a URL using something like: -io.asciidoc You can then add entries to the contact table using: -io.asciidoc You can update an entry using: -io.asciidoc You might not have noticed this but in the previous verbose code you had lines like: -io.asciidoc You want to extract some data above into simpler string results. You can do this using: -io.asciidoc You've some special case defaults for some common property names, so if your property is named email it will use an email constraint by default. If it's named url or password etc. It will do the "right thing" unless you explicitly state otherwise. You can customize the constraint for a specific property using something like: -io.asciidoc `NetworkManager` also supports synchronous requests which work in a similar way to `Dialog` through the `invokeAndBlock` call and thus don't block the EDT footnote:[Event Dispatch Thread] illegally. For example: you can do something like this: -io.asciidoc `Storage` also offers a simple API in the form of the https://www.codenameone.com/javadoc/com/codename1/io/Preferences.html[Preferences] class. The `Preferences` class lets developers store simple variables, strings, numbers, booleans, and similar values without writing storage code. This is a common use case in applications. For example, you might need to store a server token: -io.asciidoc `com.codename1.io.JSONWriter` class is the complement of `JSONParser`: -io.asciidoc database by name: -io.asciidoc directly: -io.asciidoc download a `ConnectionRequest` to `Storage` using code like this: -io.asciidoc out any change made to the property: -io.asciidoc than a `Map` literal: -io.asciidoc that prevents setting the property to null and defaults it to an empty string: -io.asciidoc the array directly: -io.asciidoc values from parsed JSON: -io.asciidoc you will notice the `final` keyword: -performance.asciidoc All you need to do now is update the `lastScroll` variable whenever user interaction is in place. This works for user touches: -performance.asciidoc For example, Codename One annotates: -performance.asciidoc In the new Contacts demo you have a share button for each contact, the code for constructing a `ShareButton` looks like this: -performance.asciidoc Then you did this within the background loading thread: -performance.asciidoc These icons are in a shared resource file that you load and don't cache. The initial workaround was to cache this resource but a better solution was to convert this code: -performance.asciidoc This works for general scrolling: -performance.asciidoc `Simd` is accessed as a singleton: -performance.asciidoc method returns. This is what the `alloca*` family exposes: -performance.asciidoc the allocation helpers on `Simd`: -security.asciidoc Baking an API key (for example a Google Maps key) into your app is unsafe: anyone can extract it from the binary, and rotating it means shipping a new build. `com.codename1.security.Secrets` solves this. You define the secret once in the Codename One Cloud vault, and the app fetches it at runtime and caches it in `SecureStorage`: -security.asciidoc For HOTP, supply the counter explicitly and increment it after every successful authentication: -security.asciidoc For RSA-signed tokens (RS256/384/512) and ECDSA-signed tokens (ES256/384/512), pass the key to the dynamic overloads: -security.asciidoc Shared secrets are commonly distributed as Base32 strings (the format embedded in QR codes by authenticator apps). `com.codename1.security.Base32` handles the encoding: -security.asciidoc The runtime API is identical on both platforms -- request a token bound to a fresh server nonce, then POST it to your backend for verification: -security.asciidoc There are two simple methods in the `Util` class: -security.asciidoc This works through a new mechanism in storage where you can replace the storage instance with another instance using: -security.asciidoc To block copy & paste on a specific field do: -security.asciidoc To use a key that was generated outside the app, feed the DER bytes to the static factory methods: -security.asciidoc You can leverage that knowledge to change the encryption password on the encryption storage using pseudo-code like this: -security.asciidoc `com.codename1.components.OtpField` is a segmented input -- one box per character, that advances to the next box as the user types and steps back on backspace. This is the standard pattern for SMS confirmation and authenticator-app entry screens: -security.asciidoc `com.codename1.security.Cipher` also covers RSA encryption, and `com.codename1.security.Signature` covers digital signatures (RSA and ECDSA). Keys are represented by `com.codename1.security.PublicKey` and `com.codename1.security.PrivateKey`, which wrap X.509 SubjectPublicKeyInfo and PKCS#8 DER blobs respectively -- the same encodings used by OpenSSL: -security.asciidoc `com.codename1.security.Cipher` exposes AES through the platform's native AES implementation. AES-GCM is the recommended default because it's authenticated -- a single tag-mismatch failure detects any tampering of the ciphertext or the associated data: -security.asciidoc `com.codename1.security.Hash` implements MD5, SHA-1, SHA-224, SHA-256, SHA-384, and SHA-512. SHA-256 is the recommended default for new code; MD5 and SHA-1 are exposed only for compatibility with older protocols since both are broken for collision resistance: -security.asciidoc `com.codename1.security.Hmac` implements HMAC (RFC 2104) on top of any of the hash algorithms above. Use HMAC whenever a message authentication code is needed -- API request signing, session cookie tamper detection, JWT signing with the `HS` family, or TOTP token generation: -security.asciidoc `com.codename1.security.SecureStorage` keeps small string values in the operating system's secure keychain (iOS Keychain Services and the Android Keystore) so they're protected by the platform's secure enclave instead of being written to app storage. The quiet, no-prompt API is three calls: From 945164f8e0f5eced706e54e6b6d1e350d1dbfbf9 Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 12:55:38 +0300 Subject: [PATCH 19/21] Finish the video capture constraints chapter before publishing it Review is right that including this chapter published an incomplete one: four of its five examples were holes, so a reader could not create a constraint, pass it to Capture.captureVideo, query support, or test whether an explicit size was honored. The originals are recoverable, so they are restored rather than the include deferred -- the chapter documents a real API and was invisible before. One of them never compiled. The original wrote `new VideoCaptureConstraint()`, a class that does not exist; it is `VideoCaptureConstraints`. Corrected while restoring, along with the multiplication signs in the comments, which no .java file in this repository may contain. The support table was also wrong, and this is the finding worth having: it told JavaScript/iOS readers that size, quality and maximum length are all supported. `HTML5Implementation.captureVideo(VideoCaptureConstraints, ...)` takes the unconstrained path whenever `isIOS()` is true and says so in a comment, so an application following that table silently gets video ignoring every constraint. The prose directly above the table already said "not on iOS" and contradicted it. The row now reads No across the board. Co-Authored-By: Claude Opus 5 (1M context) --- ...VideoCaptureConstraintsJava001Snippet.java | 90 +++++++++++++++ ...VideoCaptureConstraintsJava002Snippet.java | 87 +++++++++++++++ ...VideoCaptureConstraintsJava003Snippet.java | 99 +++++++++++++++++ ...VideoCaptureConstraintsJava005Snippet.java | 105 ++++++++++++++++++ .../Video-Capture-Constraints.asciidoc | 18 ++- 5 files changed, 398 insertions(+), 1 deletion(-) create mode 100644 docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava001Snippet.java create mode 100644 docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava002Snippet.java create mode 100644 docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava003Snippet.java create mode 100644 docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava005Snippet.java diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava001Snippet.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava001Snippet.java new file mode 100644 index 00000000000..2a214471e85 --- /dev/null +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava001Snippet.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codenameone.developerguide.snippets.generated; + +import com.codename1.gpu.*; +import com.codename1.ui.*; +import com.codename1.ui.animations.*; +import com.codename1.ui.events.*; +import com.codename1.ui.geom.*; +import com.codename1.ui.layouts.*; +import com.codename1.ui.list.*; +import com.codename1.ui.plaf.*; +import com.codename1.ui.util.*; +import com.codename1.components.*; +import com.codename1.charts.models.*; +import com.codename1.charts.renderers.*; +import com.codename1.charts.views.*; +import com.codename1.capture.*; +import com.codename1.io.*; +import com.codename1.l10n.*; +import com.codename1.location.*; +import com.codename1.maps.*; +import com.codename1.media.*; +import com.codename1.messaging.*; +import com.codename1.payment.*; +import com.codename1.processing.*; +import com.codename1.properties.*; +import com.codename1.push.*; +import com.codename1.security.*; +import com.codename1.social.*; +import com.codename1.ui.spinner.*; +import java.io.*; +import java.util.*; + + +class VideoCaptureConstraintsJava001Snippet { + + Object context; + Object url; + Object value; + Object body; + Object event; + String apiKey = "test-key"; + String myHttpsURL = "https://example.com"; + java.util.List validKeysList = new java.util.ArrayList<>(); + Image myImage; + Graphics graphics; + Graphics g; + GraphicsDevice device; + Form form; + Form hi; + Container cnt; + Container myForm; + Component component; + Button button; + MultiButton myMultiButton; + Label label; + BrowserComponent browserComponent; + Resources theme; + void snippet() throws Exception { + // tag::video-capture-constraints-java-001[] + // The original wrote "new VideoCaptureConstraint()" -- a class that does + // not exist. Corrected while restoring it: a snippet that cannot compile is + // worse than no snippet. + VideoCaptureConstraints cnst = new VideoCaptureConstraints() + .preferredQuality(VideoCaptureConstraints.QUALITY_LOW) + .preferredMaxLength(5); + // end::video-capture-constraints-java-001[] + } +} diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava002Snippet.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava002Snippet.java new file mode 100644 index 00000000000..8bb96ee9ffa --- /dev/null +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava002Snippet.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codenameone.developerguide.snippets.generated; + +import com.codename1.gpu.*; +import com.codename1.ui.*; +import com.codename1.ui.animations.*; +import com.codename1.ui.events.*; +import com.codename1.ui.geom.*; +import com.codename1.ui.layouts.*; +import com.codename1.ui.list.*; +import com.codename1.ui.plaf.*; +import com.codename1.ui.util.*; +import com.codename1.components.*; +import com.codename1.charts.models.*; +import com.codename1.charts.renderers.*; +import com.codename1.charts.views.*; +import com.codename1.capture.*; +import com.codename1.io.*; +import com.codename1.l10n.*; +import com.codename1.location.*; +import com.codename1.maps.*; +import com.codename1.media.*; +import com.codename1.messaging.*; +import com.codename1.payment.*; +import com.codename1.processing.*; +import com.codename1.properties.*; +import com.codename1.push.*; +import com.codename1.security.*; +import com.codename1.social.*; +import com.codename1.ui.spinner.*; +import java.io.*; +import java.util.*; + + +class VideoCaptureConstraintsJava002Snippet { + + Object context; + Object url; + Object value; + Object body; + Object event; + String apiKey = "test-key"; + String myHttpsURL = "https://example.com"; + java.util.List validKeysList = new java.util.ArrayList<>(); + Image myImage; + Graphics graphics; + Graphics g; + GraphicsDevice device; + Form form; + Form hi; + Container cnt; + Container myForm; + Component component; + Button button; + MultiButton myMultiButton; + Label label; + BrowserComponent browserComponent; + Resources theme; + VideoCaptureConstraints cnst; + + void snippet() throws Exception { + // tag::video-capture-constraints-java-002[] + String videoPath = Capture.captureVideo(cnst); + // end::video-capture-constraints-java-002[] + } +} diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava003Snippet.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava003Snippet.java new file mode 100644 index 00000000000..02d12bb7e2a --- /dev/null +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava003Snippet.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codenameone.developerguide.snippets.generated; + +import com.codename1.gpu.*; +import com.codename1.ui.*; +import com.codename1.ui.animations.*; +import com.codename1.ui.events.*; +import com.codename1.ui.geom.*; +import com.codename1.ui.layouts.*; +import com.codename1.ui.list.*; +import com.codename1.ui.plaf.*; +import com.codename1.ui.util.*; +import com.codename1.components.*; +import com.codename1.charts.models.*; +import com.codename1.charts.renderers.*; +import com.codename1.charts.views.*; +import com.codename1.capture.*; +import com.codename1.io.*; +import com.codename1.l10n.*; +import com.codename1.location.*; +import com.codename1.maps.*; +import com.codename1.media.*; +import com.codename1.messaging.*; +import com.codename1.payment.*; +import com.codename1.processing.*; +import com.codename1.properties.*; +import com.codename1.push.*; +import com.codename1.security.*; +import com.codename1.social.*; +import com.codename1.ui.spinner.*; +import java.io.*; +import java.util.*; + + +class VideoCaptureConstraintsJava003Snippet { + + Object context; + Object url; + Object value; + Object body; + Object event; + String apiKey = "test-key"; + String myHttpsURL = "https://example.com"; + java.util.List validKeysList = new java.util.ArrayList<>(); + Image myImage; + Graphics graphics; + Graphics g; + GraphicsDevice device; + Form form; + Form hi; + Container cnt; + Container myForm; + Component component; + Button button; + MultiButton myMultiButton; + Label label; + BrowserComponent browserComponent; + Resources theme; + VideoCaptureConstraints cnst; + + void snippet() throws Exception { + // tag::video-capture-constraints-java-003[] + if (cnst.isMaxLengthSupported()) { + // The max length constraint we specified is supported on this platform. + } else { + // It is not. Check the effective value to see whether it is partially + // supported. + int effectiveMaxLength = cnst.getMaxLength(); + if (effectiveMaxLength == 0) { + // Not supported at all: the user can capture without a duration limit. + } else { + // Set to a different value than preferredMaxLength asked for, so the + // platform is at least trying to accommodate us. + } + } + // end::video-capture-constraints-java-003[] + } +} diff --git a/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava005Snippet.java b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava005Snippet.java new file mode 100644 index 00000000000..f5d47f1434e --- /dev/null +++ b/docs/demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava005Snippet.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2026, Codename One and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Codename One designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Codename One through http://www.codenameone.com/ if you + * need additional information or have any questions. + */ +package com.codenameone.developerguide.snippets.generated; + +import com.codename1.gpu.*; +import com.codename1.ui.*; +import com.codename1.ui.animations.*; +import com.codename1.ui.events.*; +import com.codename1.ui.geom.*; +import com.codename1.ui.layouts.*; +import com.codename1.ui.list.*; +import com.codename1.ui.plaf.*; +import com.codename1.ui.util.*; +import com.codename1.components.*; +import com.codename1.charts.models.*; +import com.codename1.charts.renderers.*; +import com.codename1.charts.views.*; +import com.codename1.capture.*; +import com.codename1.io.*; +import com.codename1.l10n.*; +import com.codename1.location.*; +import com.codename1.maps.*; +import com.codename1.media.*; +import com.codename1.messaging.*; +import com.codename1.payment.*; +import com.codename1.processing.*; +import com.codename1.properties.*; +import com.codename1.push.*; +import com.codename1.security.*; +import com.codename1.social.*; +import com.codename1.ui.spinner.*; +import java.io.*; +import java.util.*; + + +class VideoCaptureConstraintsJava005Snippet { + + Object context; + Object url; + Object value; + Object body; + Object event; + String apiKey = "test-key"; + String myHttpsURL = "https://example.com"; + java.util.List validKeysList = new java.util.ArrayList<>(); + Image myImage; + Graphics graphics; + Graphics g; + GraphicsDevice device; + Form form; + Form hi; + Container cnt; + Container myForm; + Component component; + Button button; + MultiButton myMultiButton; + Label label; + BrowserComponent browserComponent; + Resources theme; + VideoCaptureConstraints cnst; + + void snippet() throws Exception { + // tag::video-capture-constraints-java-005[] + if (cnst.isSizeSupported()) { + // This platform supports the constraint, so the captured video will be + // exactly 320x240. + } else { + // Not supported -- see whether the platform will get close. + int effectiveWidth = cnst.getWidth(); + int effectiveHeight = cnst.getHeight(); + int quality = cnst.getQuality(); + if (effectiveWidth == 0 && effectiveHeight == 0) { + // No control over width and height. In many cases the platform will + // approximate with the quality setting instead. + if (quality != 0) { + // Quality was set for us. Since 320x240 is small, it would + // probably be QUALITY_LOW. + } + } else { + // Could not capture at 320x240, but supplied the closest size it can. + } + } + // end::video-capture-constraints-java-005[] + } +} diff --git a/docs/developer-guide/Video-Capture-Constraints.asciidoc b/docs/developer-guide/Video-Capture-Constraints.asciidoc index 725b6cf5843..cb1121acd7e 100644 --- a/docs/developer-guide/Video-Capture-Constraints.asciidoc +++ b/docs/developer-guide/Video-Capture-Constraints.asciidoc @@ -14,9 +14,17 @@ Support for these constraints vary by platform and device, but the API allows yo Suppose you want to allow the user to capture a short (5 second) clip, in a low resolution, appropriate for sharing on a social media platform. You create your `VideoCaptureConstraints` object as follows: +[source,java] +---- +include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava001Snippet.java[tag=video-capture-constraints-java-001,indent=0] +---- This constraint can then be passed to `Capture.captureVideo()` to get the captured file: +[source,java] +---- +include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava002Snippet.java[tag=video-capture-constraints-java-002,indent=0] +---- === Not all platforms support all constraints @@ -24,6 +32,10 @@ How do you know whether your constraints will be honored? When the platform does For example: +[source,java] +---- +include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava003Snippet.java[tag=video-capture-constraints-java-003,indent=0] +---- You can probe a constraint to see whether the entire constraint is supported (that is, whether it will be obeyed), or whether any particular aspect of it will be supported using the following methods: @@ -46,6 +58,10 @@ Explicit width and height constraints aren't well-supported across platforms. An Find out if this constraint will be obeyed: +[source,java] +---- +include::../demos/common/src/main/java/com/codenameone/developerguide/snippets/generated/VideoCaptureConstraintsJava005Snippet.java[tag=video-capture-constraints-java-005,indent=0] +---- === Constraint support by platform @@ -58,7 +74,7 @@ Find out if this constraint will be obeyed: | Simulator/Java SE | No | No | No | No | JavaScript (Desktop) | Yes | Yes | Yes | No | JavaScript (Android) | Yes | Yes | Yes | No -| JavaScript (iOS) | Yes | Yes | Yes | No +| JavaScript (iOS) | No | No | No | No |==================== `*` If size is specified, the platform will try to translate to the appropriate quality constraint. From e113f04ab950949c1179fca290ac99197348d6ad Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 14:50:39 +0300 Subject: [PATCH 20/21] The JavaScript port honoured a quality request and then reported it unsupported This chapter is newly published by this stack, so its support table starts advertising that JavaScript Desktop and Android honour the quality constraint. Review checked that against the runtime and found the API disagreeing. The compiler expresses quality as a resolution -- QUALITY_LOW becomes 640x480, QUALITY_HIGH becomes 1280x720 -- and then called `out.preferredQuality(0)`. Since build() copies the resolved quality straight off that object, every caller that asked for a quality got isQualitySupported() -> preferredQuality == 0 || quality == preferredQuality -> false because the preferred value was nonzero and the resolved value was zero. The capture had in fact been constrained exactly as requested. Note the table is right about the platform and the predicate was wrong about it, so the fix belongs here rather than in the table: marking the row unsupported would have made the guide describe the bug instead of the behaviour. The resolved quality is now reported back, but only when the device actually produced the size that quality maps to. getUserMedia negotiates rather than guarantees, so a camera that cannot reach 1280x720 returns something smaller, and in that case the quality genuinely was not honoured and still reports false. A caller that pins width and height itself never enters the branch, so an explicit size plus a quality still reports the quality unsupported, which is correct -- the size wins. Verified the module really compiles this source rather than unpacking it from a jar: a deliberate syntax error fails the parparvm build, and the restored file builds clean. Co-Authored-By: Claude Opus 5 (1M context) --- .../JSVideoCaptureConstraintsCompiler.java | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/videojs/JSVideoCaptureConstraintsCompiler.java b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/videojs/JSVideoCaptureConstraintsCompiler.java index e952809439f..6c6d6ddb024 100644 --- a/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/videojs/JSVideoCaptureConstraintsCompiler.java +++ b/Ports/JavaScriptPort/src/main/java/com/codename1/impl/html5/videojs/JSVideoCaptureConstraintsCompiler.java @@ -37,6 +37,11 @@ public VideoCaptureConstraints compile(VideoCaptureConstraints vcc) { VideoCaptureConstraints out = new VideoCaptureConstraints(); int prefW = vcc.getPreferredWidth(); int prefH = vcc.getPreferredHeight(); + // The size this platform uses to express the requested quality, or 0 when the + // caller asked for no quality or pinned the size itself. Kept so the negotiated + // result below can be compared against what the quality actually asked for. + int qualityW = 0; + int qualityH = 0; if (vcc.getPreferredQuality() != 0 && prefW == 0 && prefH == 0) { switch (vcc.getPreferredQuality()) { case VideoCaptureConstraints.QUALITY_LOW: @@ -48,12 +53,23 @@ public VideoCaptureConstraints compile(VideoCaptureConstraints vcc) { prefH = 720; break; } - out.preferredQuality(0); + qualityW = prefW; + qualityH = prefH; } if (prefW > 0 || prefH > 0) { MediaResult res = new MediaTool().query(prefW, prefH); out.preferredWidth(res.getWidth()) .preferredHeight(res.getHeight()); + // Report the quality back only when the device actually gave us the size that + // quality maps to. Leaving the resolved quality at 0 unconditionally, as this + // did, made isQualitySupported() answer false for every caller that asked for + // QUALITY_LOW or QUALITY_HIGH -- the resolved value differed from the nonzero + // preferred one -- even though the capture was constrained exactly as asked. + // getUserMedia negotiates, so a device that cannot reach 1280x720 returns + // something smaller, and then the quality genuinely was not honored. + if (qualityW > 0 && res.getWidth() == qualityW && res.getHeight() == qualityH) { + out.preferredQuality(vcc.getPreferredQuality()); + } } out.preferredMaxLength(vcc.getPreferredMaxLength()); return out; From e68153f5ea3c47596a9037544e1997ec0113ee2a Mon Sep 17 00:00:00 2001 From: Shai Almog <67850168+shai-almog@users.noreply.github.com> Date: Wed, 2 Sep 2026 15:15:55 +0300 Subject: [PATCH 21/21] Two support predicates answered before resolving the constraint Follow-on from the quality fix: preserving the negotiated quality in the JavaScript compiler still did not reach a caller who asked the obvious question first, because isQualitySupported() never triggered the resolve. Every getter of a resolved value calls build() -- getWidth, getHeight, getQuality, getMaxFileSize, getMaxLength -- and so do isSizeSupported() and isMaxLengthSupported(). isQualitySupported() and isMaxFileSizeSupported() did not. Calling one of those two FIRST on a fresh constraint therefore compared the caller's preference against a field the platform had never filled in, and answered "unsupported" for a constraint the platform honours. isSupported() hid the defect completely, because it evaluates isSizeSupported() first and that builds. So did any code that read a getter before probing. Only the direct probe -- exactly what the guide's example tells readers to write -- saw it. The new test asserts the ordering rather than the value: it calls each predicate as the first method on the object. It fails on the unfixed code with "expected: but was: " and passes after. Full core-unittests suite: 6078 tests, 0 failures. SpotBugs regenerated: 0. Co-Authored-By: Claude Opus 5 (1M context) --- .../capture/VideoCaptureConstraints.java | 9 ++++++ .../capture/VideoCaptureConstraintsTest.java | 29 +++++++++++++++++++ 2 files changed, 38 insertions(+) diff --git a/CodenameOne/src/com/codename1/capture/VideoCaptureConstraints.java b/CodenameOne/src/com/codename1/capture/VideoCaptureConstraints.java index ad4dc3dda21..3a3f864e678 100644 --- a/CodenameOne/src/com/codename1/capture/VideoCaptureConstraints.java +++ b/CodenameOne/src/com/codename1/capture/VideoCaptureConstraints.java @@ -630,6 +630,13 @@ public boolean isSupported() { /// /// - #preferredQuality(int) public boolean isQualitySupported() { + // Resolve first. Every getter of a resolved value builds, and so do + // isSizeSupported() and isMaxLengthSupported(); these last two predicates + // did not, so asking one of them FIRST compared the caller's preference + // against a `quality` field the platform had never filled in, and answered + // "unsupported" for a constraint the platform honours. isSupported() + // masked it, because isSizeSupported() runs first and builds. + build(); return preferredQuality == 0 || quality == preferredQuality; } @@ -647,6 +654,8 @@ public boolean isQualitySupported() { /// /// - #getPreferredMaxFileSize() public boolean isMaxFileSizeSupported() { + // Resolve first, for the same reason as isQualitySupported() above. + build(); return preferredMaxFileSize == 0 || maxFileSize == preferredMaxFileSize; } diff --git a/maven/core-unittests/src/test/java/com/codename1/capture/VideoCaptureConstraintsTest.java b/maven/core-unittests/src/test/java/com/codename1/capture/VideoCaptureConstraintsTest.java index 7ed8d417e29..b1d4ea1685c 100644 --- a/maven/core-unittests/src/test/java/com/codename1/capture/VideoCaptureConstraintsTest.java +++ b/maven/core-unittests/src/test/java/com/codename1/capture/VideoCaptureConstraintsTest.java @@ -81,6 +81,35 @@ public void testMaxLengthSupportedWhenNoLimitRequested() { Assertions.assertTrue(vcc.isSupported()); } + /// A support predicate must resolve the constraint before it answers. These two + /// did not, so asking one of them FIRST -- before any getter had triggered + /// build() -- compared the caller's preference against a field the platform had + /// never filled in. isSupported() hid it, because isSizeSupported() runs first + /// and does build. The call order in each case below is the whole point. + @FormTest + public void testSupportPredicatesResolveBeforeAnswering() { + try { + VideoCaptureConstraints.init(new VideoCaptureConstraints.Compiler() { + public VideoCaptureConstraints compile(VideoCaptureConstraints cnst) { + // A platform that honours everything it is handed. + return new VideoCaptureConstraints(cnst); + } + }); + + VideoCaptureConstraints quality = new VideoCaptureConstraints() + .preferredQuality(VideoCaptureConstraints.QUALITY_HIGH); + Assertions.assertTrue(quality.isQualitySupported(), + "isQualitySupported() must resolve before comparing"); + + VideoCaptureConstraints fileSize = new VideoCaptureConstraints() + .preferredMaxFileSize(1024); + Assertions.assertTrue(fileSize.isMaxFileSizeSupported(), + "isMaxFileSizeSupported() must resolve before comparing"); + } finally { + VideoCaptureConstraints.init(null); + } + } + /// Partial support: the platform limits duration but not to the requested value. /// Installing a compiler is safe to undo because Java SE never registers one. @FormTest