From 843d28c8a439750154b56804c845729b18202804 Mon Sep 17 00:00:00 2001 From: Muukii Date: Fri, 30 Jan 2026 17:40:44 +0900 Subject: [PATCH 01/34] WIP --- Dev/Brightroom.xcodeproj/project.pbxproj | 18 +- .../xcshareddata/swiftpm/Package.resolved | 87 +-- Dev/Sources/SwiftUIDemo/ContentView.swift | 42 +- Dev/Sources/SwiftUIDemo/DemoCropView.swift | 26 +- Dev/Sources/SwiftUIDemo/DemoCropView2.swift | 30 +- Dev/Sources/SwiftUIDemo/DemoFilterView.swift | 24 +- .../SwiftUIDemo/IsolatedEditingView.swift | 4 +- .../BrightroomEngineTests/LoadingTests.swift | 41 +- .../BrightroomEngineTests/RendererTests.swift | 2 +- Package.resolved | 87 +-- Package.swift | 14 +- .../BrightroomEngine/Core/EditingStack.swift | 520 +++++++++--------- .../DataSource/ImageProvider.swift | 411 ++++++-------- .../DataSource/ImageSource.swift | 1 - .../BrightroomEngine/Engine/ImageTool.swift | 2 +- .../Shared/Components/Crop/CropView.swift | 418 +++++++------- .../Components/Crop/SwiftUICropView.swift | 8 +- .../Drawing/BlurryMaskingView.swift | 171 +++--- .../Components/Drawing/CanvasView.swift | 78 ++- .../ImageViews/ImagePreviewView.swift | 43 +- .../ClassicImageEditViewController.swift | 61 +- .../ClassicImageEditViewModel.swift | 82 +-- .../ClassicImageEditControlViewBase.swift | 22 +- .../ClassicImageEditEditMenuControlView.swift | 8 +- .../ClassicImageEditPresetListControl.swift | 160 +++--- .../ClassicImageEditClarityControl.swift | 12 +- .../ClassicImageEditContrastControl.swift | 10 +- .../ClassicImageEditExposureControl.swift | 12 +- .../ClassicImageEditFadeControl.swift | 12 +- .../ClassicImageEditFilterControlBase.swift | 3 +- .../ClassicImageEditGaussianBlurControl.swift | 12 +- .../ClassicImageEditHighlightsControl.swift | 12 +- .../ClassicImageEditSaturationControl.swift | 12 +- .../ClassicImageEditShadowsControl.swift | 12 +- .../ClassicImageEditSharpenControl.swift | 12 +- .../ClassicImageEditTemperatureControl.swift | 11 +- .../ClassicImageEditVignetteControl.swift | 11 +- .../PhotosCropAspectRatioControl.swift | 500 ++++++++--------- .../PhotosCrop/PhotosCropViewController.swift | 146 ++--- .../PhotosCropRotating.swift | 36 +- docs/MIGRATION_VERGE_TO_STATEGRAPH.md | 286 ++++++++++ submodules/muukii/Reveal-SDK | 2 +- 42 files changed, 1747 insertions(+), 1714 deletions(-) create mode 100644 docs/MIGRATION_VERGE_TO_STATEGRAPH.md diff --git a/Dev/Brightroom.xcodeproj/project.pbxproj b/Dev/Brightroom.xcodeproj/project.pbxproj index 7acdaa15..016b165e 100644 --- a/Dev/Brightroom.xcodeproj/project.pbxproj +++ b/Dev/Brightroom.xcodeproj/project.pbxproj @@ -38,7 +38,6 @@ 4B58E82F260F0027004A834F /* orientation_left.HEIC in Resources */ = {isa = PBXBuildFile; fileRef = 4B254FE8260BB32600F77E9A /* orientation_left.HEIC */; }; 4B58E830260F0027004A834F /* orientation_up_mirrored.HEIC in Resources */ = {isa = PBXBuildFile; fileRef = 4B254FEB260BB32600F77E9A /* orientation_up_mirrored.HEIC */; }; 4B58E831260F0027004A834F /* orientation_up.HEIC in Resources */ = {isa = PBXBuildFile; fileRef = 4B254FED260BB32600F77E9A /* orientation_up.HEIC */; }; - 4B5E72BC2B6B761800DE7A2A /* Reveal-SDK in Frameworks */ = {isa = PBXBuildFile; productRef = 4B5E72BB2B6B761800DE7A2A /* Reveal-SDK */; }; 4B75114025F13276002D804A /* DemoCropView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B75113F25F13276002D804A /* DemoCropView.swift */; }; 4B7513C82B6BEDFD00A4743E /* SwiftUIHosting in Frameworks */ = {isa = PBXBuildFile; productRef = 4B7513C72B6BEDFD00A4743E /* SwiftUIHosting */; }; 4B7513CB2B6BEE2100A4743E /* SwiftUISupport in Frameworks */ = {isa = PBXBuildFile; productRef = 4B7513CA2B6BEE2100A4743E /* SwiftUISupport */; }; @@ -91,6 +90,7 @@ 4BF934202614CAD600E0637A /* CGContextTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BF9341F2614CAD600E0637A /* CGContextTests.swift */; }; 4BF934282614CB6000E0637A /* screenshot-8bit-lcd.png in Resources */ = {isa = PBXBuildFile; fileRef = 4BF934272614CB6000E0637A /* screenshot-8bit-lcd.png */; }; 4BF934412614D08300E0637A /* screenshot-8bit-p3-alpha.png in Resources */ = {isa = PBXBuildFile; fileRef = 4BF934402614D08300E0637A /* screenshot-8bit-p3-alpha.png */; }; + B08FEAC52F2C76B2002E67C4 /* Reveal-SDK in Frameworks */ = {isa = PBXBuildFile; productRef = B08FEAC42F2C76B2002E67C4 /* Reveal-SDK */; }; FC06C87F2A53AF4500215B89 /* BrightroomEngine in Frameworks */ = {isa = PBXBuildFile; productRef = FC06C87E2A53AF4500215B89 /* BrightroomEngine */; }; FC06C8812A53AF4500215B89 /* BrightroomUI in Frameworks */ = {isa = PBXBuildFile; productRef = FC06C8802A53AF4500215B89 /* BrightroomUI */; }; FC110D2B2660F90200CC45EA /* ImageSourceInitTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = FC110D2A2660F90200CC45EA /* ImageSourceInitTest.swift */; }; @@ -945,8 +945,8 @@ 4B7513CB2B6BEE2100A4743E /* SwiftUISupport in Frameworks */, FC06C87F2A53AF4500215B89 /* BrightroomEngine in Frameworks */, 4B7513C82B6BEDFD00A4743E /* SwiftUIHosting in Frameworks */, + B08FEAC52F2C76B2002E67C4 /* Reveal-SDK in Frameworks */, 4BA2B1B42B86282D00E6DE64 /* BrightroomUIPhotosCrop in Frameworks */, - 4B5E72BC2B6B761800DE7A2A /* Reveal-SDK in Frameworks */, FC06C8812A53AF4500215B89 /* BrightroomUI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1584,11 +1584,11 @@ packageProductDependencies = ( FC06C87E2A53AF4500215B89 /* BrightroomEngine */, FC06C8802A53AF4500215B89 /* BrightroomUI */, - 4B5E72BB2B6B761800DE7A2A /* Reveal-SDK */, 4B7513C72B6BEDFD00A4743E /* SwiftUIHosting */, 4B7513CA2B6BEE2100A4743E /* SwiftUISupport */, 4B46E71A2B762E20003179C2 /* MondrianLayout */, 4BA2B1B32B86282D00E6DE64 /* BrightroomUIPhotosCrop */, + B08FEAC42F2C76B2002E67C4 /* Reveal-SDK */, ); productName = SwiftUIDemo; productReference = 4B98CCB925EFF31300E4F61F /* SwiftUIDemo.app */; @@ -2275,7 +2275,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.6; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; ONLY_ACTIVE_ARCH = YES; @@ -2335,7 +2335,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 16.6; + IPHONEOS_DEPLOYMENT_TARGET = 17.0; MTL_ENABLE_DEBUG_INFO = NO; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "app.muukii.brightroom.$(TARGET_NAME)"; @@ -2596,10 +2596,6 @@ isa = XCSwiftPackageProductDependency; productName = BrightroomEngine; }; - 4B5E72BB2B6B761800DE7A2A /* Reveal-SDK */ = { - isa = XCSwiftPackageProductDependency; - productName = "Reveal-SDK"; - }; 4B7513C72B6BEDFD00A4743E /* SwiftUIHosting */ = { isa = XCSwiftPackageProductDependency; package = 4B7513C62B6BEDFC00A4743E /* XCRemoteSwiftPackageReference "swiftui-hosting" */; @@ -2614,6 +2610,10 @@ isa = XCSwiftPackageProductDependency; productName = BrightroomUIPhotosCrop; }; + B08FEAC42F2C76B2002E67C4 /* Reveal-SDK */ = { + isa = XCSwiftPackageProductDependency; + productName = "Reveal-SDK"; + }; FC06C87E2A53AF4500215B89 /* BrightroomEngine */ = { isa = XCSwiftPackageProductDependency; productName = BrightroomEngine; diff --git a/Dev/Brightroom.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Dev/Brightroom.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3546f2f7..aebb7d07 100644 --- a/Dev/Brightroom.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Dev/Brightroom.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -9,15 +9,6 @@ "version" : "0.10.0" } }, - { - "identity" : "normalization", - "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/Normalization", - "state" : { - "revision" : "6e7cb1ddeda4d0f1d2fbf8ca6d25ecd8ed6ba917", - "version" : "1.1.0" - } - }, { "identity" : "precisionlevelslider", "kind" : "remoteSourceControl", @@ -28,57 +19,30 @@ } }, { - "identity" : "rxswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/ReactiveX/RxSwift.git", - "state" : { - "revision" : "9dcaa4b333db437b0fbfaf453fad29069044a8b4", - "version" : "6.6.0" - } - }, - { - "identity" : "swift-atomics", + "identity" : "swift-state-graph", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-atomics.git", + "location" : "https://github.com/VergeGroup/swift-state-graph", "state" : { - "revision" : "6c89474e62719ddcc1e9614989fff2f68208fe10", - "version" : "1.1.0" + "revision" : "47e4f536c2e1ae75f00cb547934e43f3728e3bfc", + "version" : "0.17.0" } }, { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections", - "state" : { - "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", - "version" : "1.1.0" - } - }, - { - "identity" : "swift-concurrency-task-manager", - "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/swift-concurrency-task-manager", - "state" : { - "revision" : "3fcfb05f115a3b5bc24f0dfe0d35137f3e398f0b", - "version" : "1.3.1" - } - }, - { - "identity" : "swift-macro-state-struct", + "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/swift-macro-state-struct", + "location" : "https://github.com/swiftlang/swift-syntax.git", "state" : { - "revision" : "88e3937abd26b76bafe9027938538966457f156f", - "version" : "1.0.0" + "revision" : "4799286537280063c85a32f09884cfbca301b1a1", + "version" : "602.0.0" } }, { - "identity" : "swift-syntax", + "identity" : "swift-typed-identifier", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-syntax.git", + "location" : "https://github.com/VergeGroup/swift-typed-identifier", "state" : { - "revision" : "0687f71944021d616d34d922343dcef086855920", - "version" : "600.0.1" + "revision" : "ea7afa2ce943c6bf3ef87d385c8a6e9f67fea4f3", + "version" : "2.0.4" } }, { @@ -107,33 +71,6 @@ "revision" : "88aac5305fe568ef62f73a3cf19f0b8392ffb52c", "version" : "1.0.5" } - }, - { - "identity" : "typedcomparator", - "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/TypedComparator", - "state" : { - "revision" : "337ce0e573e7637ddd29392cb88c3b852f042c8e", - "version" : "1.0.0" - } - }, - { - "identity" : "typedidentifier", - "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/TypedIdentifier", - "state" : { - "revision" : "284340409ba47858a1b3c353dcef9c21303953db", - "version" : "2.0.3" - } - }, - { - "identity" : "verge", - "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/Verge.git", - "state" : { - "revision" : "b2e5d4b162797119ae0bf2e7609fc74faca00418", - "version" : "14.0.0-beta.2" - } } ], "version" : 2 diff --git a/Dev/Sources/SwiftUIDemo/ContentView.swift b/Dev/Sources/SwiftUIDemo/ContentView.swift index ed81d6c4..2f67b16a 100644 --- a/Dev/Sources/SwiftUIDemo/ContentView.swift +++ b/Dev/Sources/SwiftUIDemo/ContentView.swift @@ -17,9 +17,7 @@ struct ContentView: View { Form { NavigationLink("ImagePreviewView") { - DemoCropView2(editingStack: { - horizontalStack - }) + DemoCropView2(editingStack: horizontalStack) } NavigationLink("Isolated", destination: IsolatedEditinView()) @@ -31,15 +29,13 @@ struct ContentView: View { } NavigationLink("Custom Filter") { - DemoFilterView(editingStack: { - horizontalStack - }) + DemoFilterView(editingStack: horizontalStack) } Section("Restoration Horizontal") { Button("Crop") { fullScreenView = .init { - DemoCropView(editingStack: { horizontalStack }) + DemoCropView(editingStack: horizontalStack) } } @@ -55,7 +51,7 @@ struct ContentView: View { Section("Restoration Vertical") { Button("Crop") { fullScreenView = .init { - DemoCropView(editingStack: { verticalStack }) + DemoCropView(editingStack: verticalStack) } } @@ -73,7 +69,7 @@ struct ContentView: View { Button("Local") { fullScreenView = .init { DemoCropView( - editingStack: { Mocks.makeEditingStack(image: Mocks.imageHorizontal()) } + editingStack: Mocks.makeEditingStack(image: Mocks.imageHorizontal()) ) } } @@ -84,30 +80,22 @@ struct ContentView: View { content: { Button("Horizontal 1") { fullScreenView = .init { - SwiftUICropView( - editingStack: { - let stack = Mocks.makeEditingStack( - image: Asset.horizontalRect.image - ) - stack.cropModifier = .faceDetection(aspectRatio: .square) - return stack - }() + let stack = Mocks.makeEditingStack( + image: Asset.horizontalRect.image ) + stack.cropModifier = .faceDetection(aspectRatio: .square) + return SwiftUICropView(editingStack: stack) } } Button("Horizontal 2") { fullScreenView = .init { - SwiftUICropView( - editingStack: { - let stack = Mocks.makeEditingStack( - image: Asset.horizontalRect.image - ) - stack.cropModifier = .faceDetection(aspectRatio: .square) - return stack - }() + let stack = Mocks.makeEditingStack( + image: Asset.horizontalRect.image ) - .croppingAspectRatio(.square) + stack.cropModifier = .faceDetection(aspectRatio: .square) + return SwiftUICropView(editingStack: stack) + .croppingAspectRatio(.square) } } @@ -320,7 +308,7 @@ struct WorkingOnPicked: View { Button("Crop") { fullScreenView = .init { - DemoCropView(editingStack: { stack }) + DemoCropView(editingStack: stack) } } diff --git a/Dev/Sources/SwiftUIDemo/DemoCropView.swift b/Dev/Sources/SwiftUIDemo/DemoCropView.swift index e1a5216a..4b2de223 100644 --- a/Dev/Sources/SwiftUIDemo/DemoCropView.swift +++ b/Dev/Sources/SwiftUIDemo/DemoCropView.swift @@ -14,20 +14,20 @@ import UIKit struct DemoCropView: View { - @StateObject var editingStack: EditingStack + let editingStack: EditingStack @State var resultImage: ResultImage? init( - editingStack: @escaping () -> EditingStack + editingStack: EditingStack ) { - self._editingStack = .init(wrappedValue: editingStack()) + self.editingStack = editingStack } var body: some View { ZStack { VStack { - PhotosCropRotating(editingStack: { editingStack }) + PhotosCropRotating(editingStack: editingStack) // Button("Done") { // let image = try! editingStack.makeRenderer().render().cgImage // self.resultImage = .init(cgImage: image) @@ -72,22 +72,20 @@ struct DemoCropView: View { #Preview("local") { DemoCropView( - editingStack: { Mocks.makeEditingStack(image: Mocks.imageHorizontal()) } + editingStack: Mocks.makeEditingStack(image: Mocks.imageHorizontal()) ) } #Preview("remote") { DemoCropView( - editingStack: { - EditingStack( - imageProvider: .init( - editableRemoteURL: URL( - string: - "https://images.unsplash.com/photo-1604456930969-37f67bcd6e1e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1" - )! - ) + editingStack: EditingStack( + imageProvider: .init( + editableRemoteURL: URL( + string: + "https://images.unsplash.com/photo-1604456930969-37f67bcd6e1e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1" + )! ) - } + ) ) } diff --git a/Dev/Sources/SwiftUIDemo/DemoCropView2.swift b/Dev/Sources/SwiftUIDemo/DemoCropView2.swift index 44c00210..7626260a 100644 --- a/Dev/Sources/SwiftUIDemo/DemoCropView2.swift +++ b/Dev/Sources/SwiftUIDemo/DemoCropView2.swift @@ -15,16 +15,16 @@ import SwiftUISupport struct DemoCropView2: View { - @StateObject var editingStack: EditingStack + let editingStack: EditingStack @State var resultImage: ResultImage? @State var angle: EditingCrop.AdjustmentAngle = .zero @State var baselineAngle: EditingCrop.AdjustmentAngle = .zero @State var isDragging: Bool = false init( - editingStack: @escaping () -> EditingStack + editingStack: EditingStack ) { - self._editingStack = .init(wrappedValue: editingStack()) + self.editingStack = editingStack } var body: some View { @@ -55,9 +55,9 @@ struct DemoCropView2: View { Rectangle() .fill(kind == nil ? Color.white : Color.white.opacity(0.6)) }, - stateHandler: { state in - state.ifChanged(\.adjustmentKind).do { kind in - if kind.isEmpty { + stateHandler: { state, previous in + if state.adjustmentKind != previous?.adjustmentKind { + if state.adjustmentKind.isEmpty { isDragging = false } else { isDragging = true @@ -123,21 +123,19 @@ struct DemoCropView2: View { #Preview("local") { DemoCropView2( - editingStack: { Mocks.makeEditingStack(image: Mocks.imageHorizontal()) } + editingStack: Mocks.makeEditingStack(image: Mocks.imageHorizontal()) ) } #Preview("remote") { DemoCropView2( - editingStack: { - EditingStack( - imageProvider: .init( - editableRemoteURL: URL( - string: - "https://images.unsplash.com/photo-1604456930969-37f67bcd6e1e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1" - )! - ) + editingStack: EditingStack( + imageProvider: .init( + editableRemoteURL: URL( + string: + "https://images.unsplash.com/photo-1604456930969-37f67bcd6e1e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1" + )! ) - } + ) ) } diff --git a/Dev/Sources/SwiftUIDemo/DemoFilterView.swift b/Dev/Sources/SwiftUIDemo/DemoFilterView.swift index cbf7bf04..0477ebd7 100644 --- a/Dev/Sources/SwiftUIDemo/DemoFilterView.swift +++ b/Dev/Sources/SwiftUIDemo/DemoFilterView.swift @@ -93,7 +93,7 @@ struct DemoFilterView: View { let grayscaleFilter: GrayscaleFilter = .init() let motionBlurFilter: MotionBlurFilter = .init() - @StateObject var editingStack: EditingStack + let editingStack: EditingStack @State var invertToggle: Bool = false @State var grayscaleToggle: Bool = false @State var motionBlurToggle: Bool = false @@ -101,9 +101,9 @@ struct DemoFilterView: View { @State var resultImage: ResultImage? init( - editingStack: @escaping () -> EditingStack + editingStack: EditingStack ) { - self._editingStack = .init(wrappedValue: editingStack()) + self.editingStack = editingStack } var body: some View { @@ -155,21 +155,19 @@ struct DemoFilterView: View { #Preview("local") { DemoFilterView( - editingStack: { Mocks.makeEditingStack(image: Mocks.imageHorizontal()) } + editingStack: Mocks.makeEditingStack(image: Mocks.imageHorizontal()) ) } #Preview("remote") { DemoFilterView( - editingStack: { - EditingStack( - imageProvider: .init( - editableRemoteURL: URL( - string: - "https://images.unsplash.com/photo-1604456930969-37f67bcd6e1e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1" - )! - ) + editingStack: EditingStack( + imageProvider: .init( + editableRemoteURL: URL( + string: + "https://images.unsplash.com/photo-1604456930969-37f67bcd6e1e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1" + )! ) - } + ) ) } diff --git a/Dev/Sources/SwiftUIDemo/IsolatedEditingView.swift b/Dev/Sources/SwiftUIDemo/IsolatedEditingView.swift index a0da9e49..1da8ee4a 100644 --- a/Dev/Sources/SwiftUIDemo/IsolatedEditingView.swift +++ b/Dev/Sources/SwiftUIDemo/IsolatedEditingView.swift @@ -3,7 +3,7 @@ import BrightroomUI import SwiftUI struct IsolatedEditinView: View { - @StateObject var editingStack = Mocks.makeEditingStack(image: Mocks.imageHorizontal()) + let editingStack = Mocks.makeEditingStack(image: Mocks.imageHorizontal()) @State private var fullScreenView: FullscreenIdentifiableView? var body: some View { @@ -20,7 +20,7 @@ struct IsolatedEditinView: View { } Button("Custom Crop") { - fullScreenView = .init { DemoCropView(editingStack: {editingStack}) } + fullScreenView = .init { DemoCropView(editingStack: editingStack) } } Button("Blur Mask") { diff --git a/Dev/Tests/BrightroomEngineTests/LoadingTests.swift b/Dev/Tests/BrightroomEngineTests/LoadingTests.swift index 2082caaf..21dcbe05 100644 --- a/Dev/Tests/BrightroomEngineTests/LoadingTests.swift +++ b/Dev/Tests/BrightroomEngineTests/LoadingTests.swift @@ -20,36 +20,41 @@ // THE SOFTWARE. import XCTest -import Verge +import StateGraph @testable import BrightroomEngine final class LoadingTests: XCTestCase { - - var subs = Set() - + + var subscriptions: [Any] = [] + func testOrientation() throws { - + func fetch(image: ImageProvider) -> CGImagePropertyOrientation { - + image.start() - + let exp = expectation(description: "") var result: CGImagePropertyOrientation? - - image.sinkState { (state) in - - state.ifChanged(\.orientation).do { orientation in - result = orientation - exp.fulfill() + var previousOrientation: CGImagePropertyOrientation? + + let subscription = withGraphTracking { + withGraphTrackingGroup { + let orientation = image.orientation + if orientation != previousOrientation { + previousOrientation = orientation + if let orientation = orientation { + result = orientation + exp.fulfill() + } + } } - } - .store(in: &subs) - + subscriptions.append(subscription) + wait(for: [exp], timeout: 10) - withExtendedLifetime(subs) {} - + withExtendedLifetime(subscriptions) {} + return result! } diff --git a/Dev/Tests/BrightroomEngineTests/RendererTests.swift b/Dev/Tests/BrightroomEngineTests/RendererTests.swift index 51f28ec2..20d0540c 100644 --- a/Dev/Tests/BrightroomEngineTests/RendererTests.swift +++ b/Dev/Tests/BrightroomEngineTests/RendererTests.swift @@ -20,7 +20,7 @@ // THE SOFTWARE. import MobileCoreServices -import Verge +import StateGraph import XCTest @testable import BrightroomEngine diff --git a/Package.resolved b/Package.resolved index ad79ef1a..74dbe787 100644 --- a/Package.resolved +++ b/Package.resolved @@ -1,14 +1,5 @@ { "pins" : [ - { - "identity" : "normalization", - "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/Normalization", - "state" : { - "revision" : "6e7cb1ddeda4d0f1d2fbf8ca6d25ecd8ed6ba917", - "version" : "1.1.0" - } - }, { "identity" : "precisionlevelslider", "kind" : "remoteSourceControl", @@ -19,57 +10,30 @@ } }, { - "identity" : "rxswift", + "identity" : "swift-state-graph", "kind" : "remoteSourceControl", - "location" : "https://github.com/ReactiveX/RxSwift.git", + "location" : "https://github.com/VergeGroup/swift-state-graph", "state" : { - "revision" : "7e01c05f25c025143073eaa3be3532f9375c614b", - "version" : "6.1.0" - } - }, - { - "identity" : "swift-atomics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-atomics.git", - "state" : { - "revision" : "cd142fd2f64be2100422d658e7411e39489da985", - "version" : "1.2.0" + "revision" : "47e4f536c2e1ae75f00cb547934e43f3728e3bfc", + "version" : "0.17.0" } }, { - "identity" : "swift-collections", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-collections", - "state" : { - "revision" : "94cf62b3ba8d4bed62680a282d4c25f9c63c2efb", - "version" : "1.1.0" - } - }, - { - "identity" : "swift-concurrency-task-manager", - "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/swift-concurrency-task-manager", - "state" : { - "revision" : "9f686139a7d08ecca12d64a7a9bdf81cb1a1c986", - "version" : "1.2.0" - } - }, - { - "identity" : "swift-macro-state-struct", + "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/swift-macro-state-struct", + "location" : "https://github.com/swiftlang/swift-syntax.git", "state" : { - "revision" : "e13aa7350af0407d74552198a438cd1cb4b74c7d", - "version" : "2.2.0" + "revision" : "4799286537280063c85a32f09884cfbca301b1a1", + "version" : "602.0.0" } }, { - "identity" : "swift-syntax", + "identity" : "swift-typed-identifier", "kind" : "remoteSourceControl", - "location" : "https://github.com/swiftlang/swift-syntax.git", + "location" : "https://github.com/VergeGroup/swift-typed-identifier.git", "state" : { - "revision" : "0687f71944021d616d34d922343dcef086855920", - "version" : "600.0.1" + "revision" : "ea7afa2ce943c6bf3ef87d385c8a6e9f67fea4f3", + "version" : "2.0.4" } }, { @@ -89,33 +53,6 @@ "revision" : "88aac5305fe568ef62f73a3cf19f0b8392ffb52c", "version" : "1.0.5" } - }, - { - "identity" : "typedcomparator", - "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/TypedComparator", - "state" : { - "revision" : "337ce0e573e7637ddd29392cb88c3b852f042c8e", - "version" : "1.0.0" - } - }, - { - "identity" : "typedidentifier", - "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/TypedIdentifier", - "state" : { - "revision" : "284340409ba47858a1b3c353dcef9c21303953db", - "version" : "2.0.3" - } - }, - { - "identity" : "verge", - "kind" : "remoteSourceControl", - "location" : "https://github.com/VergeGroup/Verge.git", - "state" : { - "revision" : "331d2ae4493c7aae0e5333c0d93d6173cced9325", - "version" : "14.0.0-beta.7" - } } ], "version" : 2 diff --git a/Package.swift b/Package.swift index 20cad274..91b31ad8 100644 --- a/Package.swift +++ b/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "Brightroom", platforms: [ - .iOS(.v16) + .iOS(.v17) ], products: [ .library(name: "BrightroomEngine", targets: ["BrightroomUI"]), @@ -12,18 +12,24 @@ let package = Package( .library(name: "BrightroomUIPhotosCrop", targets: ["BrightroomUIPhotosCrop"]) ], dependencies: [ - .package(url: "https://github.com/VergeGroup/Verge", from: "14.0.0-beta.7"), + .package(url: "https://github.com/VergeGroup/swift-state-graph", exact: "0.17.0"), .package(url: "https://github.com/FluidGroup/TransitionPatch", from: "1.0.3"), .package(url: "https://github.com/FluidGroup/PrecisionLevelSlider", from: "2.1.0"), ], targets: [ .target( name: "BrightroomEngine", - dependencies: ["Verge"] + dependencies: [ + .product(name: "StateGraph", package: "swift-state-graph"), + ] ), .target( name: "BrightroomUI", - dependencies: ["BrightroomEngine", "Verge", "TransitionPatch"] + dependencies: [ + "BrightroomEngine", + .product(name: "StateGraph", package: "swift-state-graph"), + "TransitionPatch", + ] ), .target( name: "BrightroomUIPhotosCrop", diff --git a/Sources/BrightroomEngine/Core/EditingStack.swift b/Sources/BrightroomEngine/Core/EditingStack.swift index 71f99385..b6b3a5a3 100644 --- a/Sources/BrightroomEngine/Core/EditingStack.swift +++ b/Sources/BrightroomEngine/Core/EditingStack.swift @@ -23,7 +23,7 @@ import CoreImage import MetalKit import SwiftUI import UIKit -import Verge +import StateGraph public enum EditingStackError: Error, Sendable { case unableToCreateRendererInLoading @@ -34,7 +34,7 @@ public enum EditingStackError: Error, Sendable { /// /// - Attension: Source text /// Please make sure of EditingStack is started state before editing in UI with calling `start()`. -open class EditingStack: Hashable, StoreDriverType { +open class EditingStack: Hashable { private static let centralQueue = DispatchQueue.init( label: "app.muukii.Brightroom.EditingStack.central", @@ -63,150 +63,143 @@ open class EditingStack: Hashable, StoreDriverType { ObjectIdentifier(self).hash(into: &hasher) } - /** - A representation of state in EditingStack - */ - public struct State: Equatable { - public struct Loading: Equatable {} + // MARK: - Nested Types - public struct Loaded: Equatable { + public struct Loaded: Equatable { - // MARK: - Properties + // MARK: - Properties - fileprivate let imageSource: ImageSource + fileprivate let imageSource: ImageSource - public let metadata: ImageProvider.State.ImageMetadata + public let metadata: ImageProvider.ImageMetadata - private let initialEditing: Edit - - /** + private let initialEditing: Edit - - TODO: Should be marked as `fileprivate(set)`, but compile fails in CocoaPods installed. - */ - public var currentEdit: Edit { - didSet { - editingPreviewImage = currentEdit.filters.apply(to: editingSourceImage) - } - } + /** - /// Won't change from initial state - public var imageSize: CGSize { - initialEditing.imageSize + - TODO: Should be marked as `fileprivate(set)`, but compile fails in CocoaPods installed. + */ + public var currentEdit: Edit { + didSet { + editingPreviewImage = currentEdit.filters.apply(to: editingSourceImage) } + } - /** - A stack of editing history - */ - public fileprivate(set) var history: [Edit] = [] - - public fileprivate(set) var thumbnailImage: CIImage + /// Won't change from initial state + public var imageSize: CGSize { + initialEditing.imageSize + } - public let editingSourceCGImage: CGImage - /** - An original image - Can be used in cropping - */ - public let editingSourceImage: CIImage + /** + A stack of editing history + */ + public fileprivate(set) var history: [Edit] = [] - public fileprivate(set) var editingPreviewImage: CIImage + public fileprivate(set) var thumbnailImage: CIImage - public fileprivate(set) var imageForCrop: CGImage + public let editingSourceCGImage: CGImage + /** + An original image + Can be used in cropping + */ + public let editingSourceImage: CIImage - public fileprivate(set) var previewFilterPresets: [PreviewFilterPreset] = [] + public fileprivate(set) var editingPreviewImage: CIImage - public var canUndo: Bool { - return history.count > 0 - } + public fileprivate(set) var imageForCrop: CGImage - /** - A boolean value that indicates if EditingStack has updates against the original image. - */ - public var isDirty: Bool { - return currentEdit != initialEditing - } + public fileprivate(set) var previewFilterPresets: [PreviewFilterPreset] = [] - public var hasUncommitedChanges: Bool { - guard currentEdit == initialEditing else { - return true - } + public var canUndo: Bool { + return history.count > 0 + } - guard let latestHistory = history.last else { - return false - } + /** + A boolean value that indicates if EditingStack has updates against the original image. + */ + public var isDirty: Bool { + return currentEdit != initialEditing + } - guard latestHistory == currentEdit else { - return true - } + public var hasUncommitedChanges: Bool { + guard currentEdit == initialEditing else { + return true + } + guard let latestHistory = history.last else { return false } - // MARK: - Initializers - - init( - imageSource: ImageSource, - metadata: ImageProvider.State.ImageMetadata, - initialEditing: EditingStack.Edit, - currentEdit: EditingStack.Edit, - history: [EditingStack.Edit] = [], - thumbnailCIImage: CIImage, - editingSourceCGImage: CGImage, - editingSourceCIImage: CIImage, - editingPreviewCIImage: CIImage, - imageForCrop: CGImage, - previewFilterPresets: [PreviewFilterPreset] = [] - ) { - self.imageSource = imageSource - self.metadata = metadata - self.initialEditing = initialEditing - self.currentEdit = currentEdit - self.history = history - self.thumbnailImage = thumbnailCIImage - self.editingSourceCGImage = editingSourceCGImage - self.editingSourceImage = editingSourceCIImage - self.editingPreviewImage = editingPreviewCIImage - self.previewFilterPresets = previewFilterPresets - self.imageForCrop = imageForCrop + guard latestHistory == currentEdit else { + return true } - // MARK: - Functions + return false + } - mutating func makeVersion() { - history.append(currentEdit) - } + // MARK: - Initializers + + init( + imageSource: ImageSource, + metadata: ImageProvider.ImageMetadata, + initialEditing: EditingStack.Edit, + currentEdit: EditingStack.Edit, + history: [EditingStack.Edit] = [], + thumbnailCIImage: CIImage, + editingSourceCGImage: CGImage, + editingSourceCIImage: CIImage, + editingPreviewCIImage: CIImage, + imageForCrop: CGImage, + previewFilterPresets: [PreviewFilterPreset] = [] + ) { + self.imageSource = imageSource + self.metadata = metadata + self.initialEditing = initialEditing + self.currentEdit = currentEdit + self.history = history + self.thumbnailImage = thumbnailCIImage + self.editingSourceCGImage = editingSourceCGImage + self.editingSourceImage = editingSourceCIImage + self.editingPreviewImage = editingPreviewCIImage + self.previewFilterPresets = previewFilterPresets + self.imageForCrop = imageForCrop + } - mutating func revertCurrentEditing() { - currentEdit = history.last ?? initialEditing - } + // MARK: - Functions - mutating func revert(to revision: Revision) { - history.removeSubrange(revision.. + // MARK: - Stored Properties public let options: Options @@ -216,8 +209,8 @@ open class EditingStack: Hashable, StoreDriverType { private let filterPresets: [FilterPreset] - private var subscriptions = Set() - private var imageProviderSubscription: (any Cancellable)? + private var subscriptions: [Any] = [] + private var imageProviderSubscription: Any? public var cropModifier: CropModifier @@ -228,6 +221,11 @@ open class EditingStack: Hashable, StoreDriverType { queue: DispatchQueue.init(label: "Brightroom.cgImage") ) + // MARK: - Change Tracking + + private var _previousThumbnailImage: CIImage? + private var _previousFilters: Edit.Filters? + // MARK: - Initializers /// Creates an instance @@ -246,9 +244,6 @@ open class EditingStack: Hashable, StoreDriverType { self.options = options self.cropModifier = cropModifier - store = .init( - initialState: .init() - ) filterPresets = colorCubeStorage.filters.map { @@ -270,30 +265,27 @@ open class EditingStack: Hashable, StoreDriverType { */ public func start(onPreparationCompleted: @escaping @MainActor () -> Void = {}) { - let previousHasCompleted = commit { s -> Bool in - /** - Mutual exclusion - */ - if s.hasStartedEditing { - return true - } else { - s.hasStartedEditing = true - return false - } - } - - guard previousHasCompleted == false else { + /** + Mutual exclusion + */ + if hasStartedEditing { DispatchQueue.main.async { onPreparationCompleted() } return } + hasStartedEditing = true - store.sinkState(queue: .specific(backgroundQueue)) { [weak self] (state: Changes) in - guard let self = self else { return } - self.receiveInBackground(newState: state) + // Set up state observation on background queue + let subscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } + self.backgroundQueue.async { + self.receiveInBackground() + } + } } - .store(in: &subscriptions) + subscriptions.append(subscription) /** Start downloading image @@ -303,103 +295,101 @@ open class EditingStack: Hashable, StoreDriverType { self.imageProvider.start() } - imageProviderSubscription = imageProvider - .sinkState(queue: .specific(backgroundQueue)) { [weak self] (state: Changes) in - - /* - In Background thread - */ - - guard let self = self else { return } + // Observe image provider's loaded image + let imageProviderSub = withGraphTracking { [weak self] in + withGraphTrackingMap { + self?.imageProvider.loadedImage + } onChange: { [weak self] image in + guard let self = self, let image = image else { return } + self.backgroundQueue.async { + self.handleImageLoaded(image: image, onPreparationCompleted: onPreparationCompleted) + } + } + } + imageProviderSubscription = imageProviderSub + } - state.ifChanged(\.loadedImage).do { image in + private func handleImageLoaded( + image: ImageProvider.LoadedImage, + onPreparationCompleted: @escaping @MainActor () -> Void + ) { + switch image { + case let .editable(imageSource, metadata): - guard let image = image else { - return - } + let thumbnailCGImage = imageSource.loadThumbnailCGImage(maxPixelSize: 180) - switch image { - case let .editable(image, metadata): + /** + An image resised from original image + */ + let editingSourceCGImage = imageSource.loadThumbnailCGImage( + maxPixelSize: self.editingImageMaxPixelSize + ) - let thumbnailCGImage = image.loadThumbnailCGImage(maxPixelSize: 180) + assert(editingSourceCGImage.colorSpace != nil) - /** - An image resised from original image - */ - let editingSourceCGImage = image.loadThumbnailCGImage( - maxPixelSize: self.editingImageMaxPixelSize - ) + /// resized + let _editingSourceCIImage: CIImage = editingSourceCGImage._makeCIImage( + orientation: metadata.orientation, + device: self.mtlDevice, + usesMTLTexture: self.options.usesMTLTextureForEditingImage + ) - assert(editingSourceCGImage.colorSpace != nil) + let _thumbnailImage: CIImage = thumbnailCGImage._makeCIImage( + orientation: metadata.orientation, + device: self.mtlDevice, + usesMTLTexture: self.options.usesMTLTextureForEditingImage + ) - /// resized - let _editingSourceCIImage: CIImage = editingSourceCGImage._makeCIImage( - orientation: metadata.orientation, - device: self.mtlDevice, - usesMTLTexture: self.options.usesMTLTextureForEditingImage - ) + let cgImageForCrop: CGImage = { + do { + return try Self.renderCGImageForCrop( + filters: [], + source: .init(cgImage: editingSourceCGImage), + orientation: metadata.orientation + ) + } catch { + EngineSanitizer.global.onDidFindRuntimeError( + .failedToRenderCGImageForCrop(sourceImage: editingSourceCGImage) + ) + assertionFailure() + return editingSourceCGImage + } + }() - let _thumbnailImage: CIImage = thumbnailCGImage._makeCIImage( - orientation: metadata.orientation, - device: self.mtlDevice, - usesMTLTexture: self.options.usesMTLTextureForEditingImage - ) + self.adjustCropExtent( + image: _editingSourceCIImage, + imageSize: metadata.imageSize, + completion: { [weak self] crop in - let cgImageForCrop: CGImage = { - do { - return try Self.renderCGImageForCrop( - filters: [], - source: .init(cgImage: editingSourceCGImage), - orientation: metadata.orientation - ) - } catch { - EngineSanitizer.global.onDidFindRuntimeError( - .failedToRenderCGImageForCrop(sourceImage: editingSourceCGImage) - ) - assertionFailure() - return editingSourceCGImage - } - }() - - self.adjustCropExtent( - image: _editingSourceCIImage, - imageSize: metadata.imageSize, - completion: { [weak self] crop in - - guard let self = self else { return } - - self.commit { (s: inout State) in - assert( - (_editingSourceCIImage.extent.width > _editingSourceCIImage.extent.height) - == (metadata.imageSize.width > metadata.imageSize.height) - ) - - let initialEdit = Edit(crop: crop) - - s.loadedState = .init( - imageSource: image, - metadata: metadata, - initialEditing: initialEdit, - currentEdit: initialEdit, - thumbnailCIImage: _thumbnailImage, - editingSourceCGImage: editingSourceCGImage, - editingSourceCIImage: _editingSourceCIImage, - editingPreviewCIImage: initialEdit.filters.apply(to: _editingSourceCIImage), - imageForCrop: cgImageForCrop - ) - - self.imageProviderSubscription?.cancel() - - DispatchQueue.main.async { - onPreparationCompleted() - } - } - } - ) + guard let self = self else { return } + assert( + (_editingSourceCIImage.extent.width > _editingSourceCIImage.extent.height) + == (metadata.imageSize.width > metadata.imageSize.height) + ) + + let initialEdit = Edit(crop: crop) + + self.loadedState = .init( + imageSource: imageSource, + metadata: metadata, + initialEditing: initialEdit, + currentEdit: initialEdit, + thumbnailCIImage: _thumbnailImage, + editingSourceCGImage: editingSourceCGImage, + editingSourceCIImage: _editingSourceCIImage, + editingPreviewCIImage: initialEdit.filters.apply(to: _editingSourceCIImage), + imageForCrop: cgImageForCrop + ) + + self.imageProviderSubscription = nil + + DispatchQueue.main.async { + onPreparationCompleted() } } - } + ) + } } /** @@ -443,48 +433,48 @@ open class EditingStack: Hashable, StoreDriverType { EngineLog.debug("[EditingStack] deinit") } - private func receiveInBackground(newState state: Changes) { + private func receiveInBackground() { assert(Thread.isMainThread == false) - if let loadedState = state.mapIfPresent(\.loadedState) { + guard let loadedState = self.loadedState else { return } - loadedState.ifChanged(\.thumbnailImage).do { image in + // Check if thumbnailImage changed + if _previousThumbnailImage != loadedState.thumbnailImage { + _previousThumbnailImage = loadedState.thumbnailImage - commit { - $0.loadedState!.previewFilterPresets = self.filterPresets.map { - PreviewFilterPreset(sourceImage: image, filter: $0) - } - } + let image = loadedState.thumbnailImage + let presets = self.filterPresets.map { + PreviewFilterPreset(sourceImage: image, filter: $0) } + self.loadedState?.previewFilterPresets = presets + } - loadedState.ifChanged(\.currentEdit.filters).do { currentEdit in + // Check if filters changed + if _previousFilters != loadedState.currentEdit.filters { + _previousFilters = loadedState.currentEdit.filters - self.debounceForCreatingCGImage.on { [weak self] in + let currentFilters = loadedState.currentEdit.filters - guard let self = self else { return } + self.debounceForCreatingCGImage.on { [weak self] in - let cgImageForCrop: CGImage = { - do { - return try Self.renderCGImageForCrop( - filters: currentEdit.makeFilters(), - source: .init(cgImage: loadedState.editingSourceCGImage), - orientation: loadedState.metadata.orientation - ) - } catch { - assertionFailure() - return loadedState.editingSourceCGImage - } - }() - - self.commit { - $0.loadedState?.imageForCrop = cgImageForCrop - } + guard let self = self, let loadedState = self.loadedState else { return } - } + let cgImageForCrop: CGImage = { + do { + return try Self.renderCGImageForCrop( + filters: currentFilters.makeFilters(), + source: .init(cgImage: loadedState.editingSourceCGImage), + orientation: loadedState.metadata.orientation + ) + } catch { + assertionFailure() + return loadedState.editingSourceCGImage + } + }() + self.loadedState?.imageForCrop = cgImageForCrop } - } } @@ -494,21 +484,17 @@ open class EditingStack: Hashable, StoreDriverType { Adds a new snapshot as a history. */ public func takeSnapshot() { - commit { - $0.loadedState?.makeVersion() - } + loadedState?.makeVersion() } public typealias Revision = Int public var currentRevision: Revision? { - self.state.primitive.loadedState?.history.count + loadedState?.history.count } public func revert(to revision: Revision) { - commit { - $0.loadedState?.revert(to: revision) - } + loadedState?.revert(to: revision) } /** @@ -516,10 +502,7 @@ open class EditingStack: Hashable, StoreDriverType { */ public func revertEdit() { _pixelengine_ensureMainThread() - - commit { - $0.loadedState?.revertCurrentEditing() - } + loadedState?.revertCurrentEditing() } /** @@ -527,10 +510,7 @@ open class EditingStack: Hashable, StoreDriverType { */ public func undoEdit() { _pixelengine_ensureMainThread() - - commit { - $0.loadedState?.undoEditing() - } + loadedState?.undoEditing() } /** @@ -538,15 +518,11 @@ open class EditingStack: Hashable, StoreDriverType { */ public func removeAllEditsHistory() { _pixelengine_ensureMainThread() - - commit { - $0.loadedState?.history = [] - } + loadedState?.history = [] } public func set(filters: (inout Edit.Filters) -> Void) { _pixelengine_ensureMainThread() - applyIfChanged { filters(&$0.filters) } @@ -560,7 +536,6 @@ open class EditingStack: Hashable, StoreDriverType { public func set(blurringMaskPaths: [DrawnPath]) { _pixelengine_ensureMainThread() - applyIfChanged { $0.drawings.blurredMaskPaths = blurringMaskPaths } @@ -568,16 +543,14 @@ open class EditingStack: Hashable, StoreDriverType { public func append(blurringMaskPaths: C) where C.Element == DrawnPath { _pixelengine_ensureMainThread() - applyIfChanged { $0.drawings.blurredMaskPaths += blurringMaskPaths } } public func makeRenderer() throws -> BrightRoomImageRenderer { - let stateSnapshot = state - guard let loaded = stateSnapshot.loadedState else { + guard let loaded = loadedState else { throw EditingStackError.unableToCreateRendererInLoading } @@ -606,12 +579,10 @@ open class EditingStack: Hashable, StoreDriverType { } private func applyIfChanged(_ perform: (inout Edit) -> Void) { - commit { - guard $0.loadedState != nil else { - return - } - perform(&$0.loadedState!.currentEdit) + guard loadedState != nil else { + return } + perform(&loadedState!.currentEdit) } private func adjustCropExtent( @@ -655,4 +626,3 @@ open class EditingStack: Hashable, StoreDriverType { } } - diff --git a/Sources/BrightroomEngine/DataSource/ImageProvider.swift b/Sources/BrightroomEngine/DataSource/ImageProvider.swift index f1664dd6..224a522e 100644 --- a/Sources/BrightroomEngine/DataSource/ImageProvider.swift +++ b/Sources/BrightroomEngine/DataSource/ImageProvider.swift @@ -22,7 +22,7 @@ import UIKit import CoreImage -import Verge +import StateGraph #if canImport(UIKit) import UIKit @@ -35,15 +35,15 @@ import Photos public enum ImageProviderError: Error { case failedToDownloadPreviewImage(underlyingError: Error) case failedToDownloadEditableImage(underlyingError: Error) - + case urlIsNotFileURL(URL) - + case failedToCreateCGDataProvider case failedToCreateCGImageSource case failedToCreateImageSource(underlyingError: Error) - + case failedToGetImageSize - + case failedToGetImageMetadata case failedToCreateCIFilterToLoadRAW @@ -54,220 +54,155 @@ public enum ImageProviderError: Error { /** A stateful object that provides multiple image for EditingStack. */ -public final class ImageProvider: Equatable, StoreDriverType { +public final class ImageProvider: Equatable { public static func == (lhs: ImageProvider, rhs: ImageProvider) -> Bool { lhs === rhs } - /** - To access, `ImageProvider.store.state` - To modify, `ImageProvider.store.commit()` - */ - public struct State: Equatable { + // MARK: - Nested Types - public struct ImageMetadata: Equatable { - public var orientation: CGImagePropertyOrientation - - /// A size that applied orientation - public var imageSize: CGSize + public struct ImageMetadata: Equatable { + public var orientation: CGImagePropertyOrientation - public init(orientation: CGImagePropertyOrientation, imageSize: CGSize) { - self.orientation = orientation - self.imageSize = imageSize - } - } - - public enum Image: Equatable { - case editable(imageSource: ImageSource, metadata: ImageMetadata) - } - - /** - Editable image's size - */ - public var imageSize: CGSize? - - public var orientation: CGImagePropertyOrientation? - - public var editableImage: ImageSource? - - @Edge public var loadingNonFatalErrors: [ImageProviderError] = [] - - @Edge public var loadingFatalErrors: [ImageProviderError] = [] - - public var loadedImage: Image? { - - if let editable = editableImage, let imageSize = imageSize, let orientation = orientation { - return .editable(imageSource: editable, metadata: .init(orientation: orientation, imageSize: imageSize)) - } + /// A size that applied orientation + public var imageSize: CGSize - return nil - } - - public init( - imageSize: CGSize? = nil, - orientation: CGImagePropertyOrientation? = nil, - editableImage: ImageSource? = nil - ) { - self.imageSize = imageSize + public init(orientation: CGImagePropertyOrientation, imageSize: CGSize) { self.orientation = orientation - self.editableImage = editableImage + self.imageSize = imageSize } + } + + public enum LoadedImage: Equatable { + case editable(imageSource: ImageSource, metadata: ImageMetadata) + } + + // MARK: - State Properties - public mutating func resolve(with metadata: ImageMetadata) { - imageSize = metadata.imageSize - orientation = metadata.orientation + @GraphStored public var imageSize: CGSize? = nil + @GraphStored public var orientation: CGImagePropertyOrientation? = nil + @GraphStored public var editableImage: ImageSource? = nil + @GraphStored public var loadingNonFatalErrors: [ImageProviderError] = [] + @GraphStored public var loadingFatalErrors: [ImageProviderError] = [] + + // MARK: - Computed Properties + + public var loadedImage: LoadedImage? { + if let editable = editableImage, let imageSize = imageSize, let orientation = orientation { + return .editable(imageSource: editable, metadata: .init(orientation: orientation, imageSize: imageSize)) } - + return nil } - - public let store: Store - - private var pendingAction: (ImageProvider) -> VergeAnyCancellable - + + // MARK: - Private Properties + + private var pendingAction: (ImageProvider) -> AnyCancellable + private var cancellable: AnyCancellable? + + // MARK: - Initializers + #if os(iOS) - - private var cancellable: VergeAnyCancellable? /// Creates an instance for your own external data provider. public init( - initialState: State, - pendingAction: @escaping (ImageProvider) -> VergeAnyCancellable + imageSize: CGSize? = nil, + orientation: CGImagePropertyOrientation? = nil, + editableImage: ImageSource? = nil, + pendingAction: @escaping (ImageProvider) -> AnyCancellable ) { - - self.store = .init(initialState: initialState) + self.imageSize = imageSize + self.orientation = orientation + self.editableImage = editableImage self.pendingAction = pendingAction - } public init(rawData: Data) { - - store = .init( - initialState: .init() - ) - - pendingAction = { `self` in + self.pendingAction = { `self` in guard let filter = CIFilter(imageData: rawData, options: [:]) else { - self.commit { - $0.loadingFatalErrors.append(.failedToCreateCIFilterToLoadRAW) - } - return .init {} + self.loadingFatalErrors.append(.failedToCreateCIFilterToLoadRAW) + return AnyCancellable {} } guard let outputImage = filter.outputImage else { - self.commit { - $0.loadingFatalErrors.append(.failedToGetRenderedImageFromRAW) - } - return .init {} + self.loadingFatalErrors.append(.failedToGetRenderedImageFromRAW) + return AnyCancellable {} } let ciContext = CIContext() guard let cgImage = ciContext.createCGImage(outputImage, from: outputImage.extent) else { - self.commit { - $0.loadingFatalErrors.append(.failedToCreateCGImageFromRAW) - } - return .init {} + self.loadingFatalErrors.append(.failedToCreateCGImageFromRAW) + return AnyCancellable {} } - self.commit { - $0.editableImage = .init(cgImage: cgImage) - $0.resolve(with: .init(orientation: .up, imageSize: outputImage.extent.size)) - } + self.editableImage = .init(cgImage: cgImage) + self.resolve(with: .init(orientation: .up, imageSize: outputImage.extent.size)) - return .init {} + return AnyCancellable {} } - } public init(rawDataURL: URL) { - - store = .init( - initialState: .init() - ) - - pendingAction = { `self` in + self.pendingAction = { `self` in guard let filter = CIFilter(imageURL: rawDataURL, options: [:]) else { - self.commit { - $0.loadingFatalErrors.append(.failedToCreateCIFilterToLoadRAW) - } - return .init {} + self.loadingFatalErrors.append(.failedToCreateCIFilterToLoadRAW) + return AnyCancellable {} } guard let outputImage = filter.outputImage else { - self.commit { - $0.loadingFatalErrors.append(.failedToGetRenderedImageFromRAW) - } - return .init {} + self.loadingFatalErrors.append(.failedToGetRenderedImageFromRAW) + return AnyCancellable {} } let ciContext = CIContext() guard let cgImage = ciContext.createCGImage(outputImage, from: outputImage.extent) else { - self.commit { - $0.loadingFatalErrors.append(.failedToCreateCGImageFromRAW) - } - return .init {} - } - self.commit { - $0.editableImage = .init(cgImage: cgImage) - $0.resolve(with: .init(orientation: .up, imageSize: outputImage.extent.size)) + self.loadingFatalErrors.append(.failedToCreateCGImageFromRAW) + return AnyCancellable {} } - return .init {} + + self.editableImage = .init(cgImage: cgImage) + self.resolve(with: .init(orientation: .up, imageSize: outputImage.extent.size)) + + return AnyCancellable {} } } /// Creates an instance from data public init(data: Data) throws { - + guard let provider = CGDataProvider(data: data as CFData) else { throw ImageProviderError.failedToCreateCGDataProvider } - + guard let imageSource = CGImageSourceCreateWithDataProvider(provider, nil) else { throw ImageProviderError.failedToCreateCGImageSource } - + guard let metadata = ImageTool.makeImageMetadata(from: imageSource) else { throw ImageProviderError.failedToGetImageMetadata } - - store = .init( - initialState: .init( - editableImage: .init(cgImageSource: imageSource) - ) - ) - - store.commit { - $0.resolve(with: metadata) - } - - pendingAction = { _ in - return .init {} - } + + self.editableImage = .init(cgImageSource: imageSource) + self.pendingAction = { _ in AnyCancellable {} } + + resolve(with: metadata) } - + /// Creates an instance from UIImage /// /// - Attention: To reduce memory footprint, as possible creating an instance from url instead. public init(image uiImage: UIImage) { precondition(uiImage.cgImage != nil) - - store = .init( - initialState: .init( - editableImage: .init(image: uiImage) - ) - ) - - store.commit { - $0.resolve(with: .init(orientation: .init(uiImage.imageOrientation), imageSize: .init(image: uiImage))) - } - - pendingAction = { _ in return .init {} } - + + self.editableImage = .init(image: uiImage) + self.pendingAction = { _ in AnyCancellable {} } + + resolve(with: .init(orientation: .init(uiImage.imageOrientation), imageSize: .init(image: uiImage))) } - + #endif - + /** Creates an instance from fileURL. This is most efficient way to edit image without large memory footprint. @@ -278,30 +213,23 @@ public final class ImageProvider: Equatable, StoreDriverType { guard fileURL.isFileURL else { throw ImageProviderError.urlIsNotFileURL(fileURL) } - + guard let provider = CGDataProvider(url: fileURL as CFURL) else { throw ImageProviderError.failedToCreateCGDataProvider } - + guard let imageSource = CGImageSourceCreateWithDataProvider(provider, nil) else { throw ImageProviderError.failedToCreateCGImageSource } - + guard let metadata = ImageTool.makeImageMetadata(from: imageSource) else { throw ImageProviderError.failedToGetImageSize } - - store = .init( - initialState: .init( - editableImage: .init(cgImageSource: imageSource) - ) - ) - - store.commit { - $0.resolve(with: metadata) - } - - pendingAction = { _ in return .init {} } + + self.editableImage = .init(cgImageSource: imageSource) + self.pendingAction = { _ in AnyCancellable {} } + + resolve(with: metadata) } #if canImport(Photos) @@ -314,7 +242,7 @@ public final class ImageProvider: Equatable, StoreDriverType { self.init(editableRemoteURL: url) } #endif - + /** Creates an instance */ @@ -325,76 +253,57 @@ public final class ImageProvider: Equatable, StoreDriverType { editableRemoteURLRequest: URLRequest(url: editableRemoteURL) ) } - + public init( editableRemoteURLRequest: URLRequest ) { - - store = .init( - initialState: .init( - imageSize: nil, - orientation: nil, - editableImage: nil - ) - ) - - pendingAction = { `self` in - + self.pendingAction = { `self` in + let editableTask = URLSession.shared.downloadTask(with: editableRemoteURLRequest) { [weak self] url, response, error in - + guard let self = self else { return } - + if let error = error { - self.store.commit { - $0.loadingFatalErrors.append(.failedToDownloadEditableImage(underlyingError: error)) - } + self.loadingFatalErrors.append(.failedToDownloadEditableImage(underlyingError: error)) } - - self.commit { state in - if let url = url { - - guard let provider = CGDataProvider(url: url as CFURL) else { - state.loadingFatalErrors.append(ImageProviderError.failedToCreateCGDataProvider) - return - } - - guard let imageSource = CGImageSourceCreateWithDataProvider(provider, nil) else { - state.loadingFatalErrors.append(ImageProviderError.failedToCreateCGImageSource) - return - } - - guard let metadata = ImageTool.makeImageMetadata(from: imageSource) else { - state.loadingNonFatalErrors.append(ImageProviderError.failedToGetImageMetadata) - return - } - - state.resolve(with: metadata) - state.editableImage = .init(cgImageSource: imageSource) + + if let url = url { + + guard let provider = CGDataProvider(url: url as CFURL) else { + self.loadingFatalErrors.append(ImageProviderError.failedToCreateCGDataProvider) + return + } + + guard let imageSource = CGImageSourceCreateWithDataProvider(provider, nil) else { + self.loadingFatalErrors.append(ImageProviderError.failedToCreateCGImageSource) + return } + + guard let metadata = ImageTool.makeImageMetadata(from: imageSource) else { + self.loadingNonFatalErrors.append(ImageProviderError.failedToGetImageMetadata) + return + } + + self.resolve(with: metadata) + self.editableImage = .init(cgImageSource: imageSource) } } - + editableTask.resume() - - return .init { + + return AnyCancellable { editableTask.cancel() } } } - + #if canImport(Photos) - + public init(asset: PHAsset) { // TODO: cancellation, Error handeling - - store = .init( - initialState: .init( - editableImage: nil - ) - ) - - pendingAction = { `self` in - + + self.pendingAction = { `self` in + let finalImageRequestOptions = PHImageRequestOptions() finalImageRequestOptions.deliveryMode = .highQualityFormat finalImageRequestOptions.isNetworkAccessAllowed = true @@ -407,42 +316,64 @@ public final class ImageProvider: Equatable, StoreDriverType { contentMode: .aspectFit, options: finalImageRequestOptions ) { [weak self] image, info in - + // FIXME: Avoid loading image, get a url instead. - + guard let self = self else { return } - - self.commit { state in - - if let error = info?[PHImageErrorKey] as? Error { - state.loadingFatalErrors.append(.failedToDownloadEditableImage(underlyingError: error)) - return - } - - guard let image = image else { return } - - state.resolve(with: .init( - orientation: .init(image.imageOrientation), - imageSize: .init(width: asset.pixelWidth, height: asset.pixelHeight) - )) - state.editableImage = .init(image: image) + + if let error = info?[PHImageErrorKey] as? Error { + self.loadingFatalErrors.append(.failedToDownloadEditableImage(underlyingError: error)) + return } + + guard let image = image else { return } + + self.resolve(with: .init( + orientation: .init(image.imageOrientation), + imageSize: .init(width: asset.pixelWidth, height: asset.pixelHeight) + )) + self.editableImage = .init(image: image) } - - return .init { + + return AnyCancellable { PHImageManager.default().cancelImageRequest(request) } } } - + #endif - + + // MARK: - Methods + func start() { guard cancellable == nil else { return } cancellable = pendingAction(self) } - + + public func resolve(with metadata: ImageMetadata) { + imageSize = metadata.imageSize + orientation = metadata.orientation + } + deinit { EngineLog.debug("[ImageProvider] deinit") } } + +// MARK: - AnyCancellable + +public final class AnyCancellable { + private let onCancel: () -> Void + + public init(_ onCancel: @escaping () -> Void) { + self.onCancel = onCancel + } + + public func cancel() { + onCancel() + } + + deinit { + onCancel() + } +} diff --git a/Sources/BrightroomEngine/DataSource/ImageSource.swift b/Sources/BrightroomEngine/DataSource/ImageSource.swift index 1bf77735..44d847e2 100644 --- a/Sources/BrightroomEngine/DataSource/ImageSource.swift +++ b/Sources/BrightroomEngine/DataSource/ImageSource.swift @@ -21,7 +21,6 @@ import CoreImage import UIKit -import Verge #if canImport(UIKit) import UIKit diff --git a/Sources/BrightroomEngine/Engine/ImageTool.swift b/Sources/BrightroomEngine/Engine/ImageTool.swift index 2f8bc28f..081ab092 100644 --- a/Sources/BrightroomEngine/Engine/ImageTool.swift +++ b/Sources/BrightroomEngine/Engine/ImageTool.swift @@ -29,7 +29,7 @@ public enum ImageTool: Sendable { public static func makeImageMetadata( from imageSource: CGImageSource - ) -> ImageProvider.State.ImageMetadata? { + ) -> ImageProvider.ImageMetadata? { let propertiesOptions = [kCGImageSourceShouldCache: false] as CFDictionary guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, 0, propertiesOptions) diff --git a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift index e4ca4d02..57908579 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift @@ -22,7 +22,7 @@ import CoreImage import SwiftUI import UIKit -import Verge +import StateGraph #if !COCOAPODS import BrightroomEngine @@ -73,7 +73,7 @@ public final class CropView: UIView, UIScrollViewDelegate { */ public private(set) weak var cropOutsideOverlay: UIView? - public let store: UIStateStore + @GraphStored public var state: State = .init() /** A Boolean value that indicates whether the guide is interactive. @@ -93,9 +93,7 @@ public final class CropView: UIView, UIScrollViewDelegate { */ public var clipsToGuide: Bool = false { didSet { - store.commit { - $0.layoutVersion += 1 - } + state.layoutVersion += 1 } } @@ -112,9 +110,7 @@ public final class CropView: UIView, UIScrollViewDelegate { public var isZoomEnabled: Bool = true { didSet { - store.commit { - $0.layoutVersion += 1 - } + state.layoutVersion += 1 } } @@ -203,7 +199,7 @@ public final class CropView: UIView, UIScrollViewDelegate { return view }() - private var subscriptions = Set() + private var subscriptions: [Any] = [] /// A throttling timer to apply guide changed event. /// @@ -217,10 +213,15 @@ public final class CropView: UIView, UIScrollViewDelegate { private var isBinding = false - private var stateHandler: @MainActor (Verge.Changes) -> Void = { _ in } + private var stateHandler: @MainActor (State, State?) -> Void = { _, _ in } var isAutoApplyEditingStackEnabled = false + // Change tracking for state + private var _previousState: State? + private var _previousLoadedState: EditingStack.Loaded? + private var _previousIsLoading: Bool? + // MARK: - Initializers /** @@ -250,8 +251,6 @@ public final class CropView: UIView, UIScrollViewDelegate { self.editingStack = editingStack self.contentInset = contentInset - self.store = .init(initialState: .init(), logger: nil) - super.init(frame: .zero) scrollBackdropView.accessibilityIdentifier = "scrollBackdropView" @@ -284,17 +283,8 @@ public final class CropView: UIView, UIScrollViewDelegate { guideView.didUpdateAdjustmentKind = { [weak self] kind in guard let self else { return } - self.store.commit { - $0.adjustmentKind = kind - } - } - - #if false - store.sinkState { state in - EditorLog.debug(state.primitive) + self.state.adjustmentKind = kind } - .store(in: &subscriptions) - #endif // apply defaultAppearance do { @@ -305,10 +295,17 @@ public final class CropView: UIView, UIScrollViewDelegate { }) } - store.sinkState { [weak self] state in - self?.stateHandler(state) + // Set up state observation + let stateSubscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self else { return } + let currentState = self.state + let previousState = self._previousState + self._previousState = currentState + self.stateHandler(currentState, previousState) + } } - .storeWhileSourceActive() + subscriptions.append(stateSubscription) } @available(*, unavailable) @@ -318,7 +315,7 @@ public final class CropView: UIView, UIScrollViewDelegate { // MARK: - Functions - func setStateHandler(_ handler: @escaping @MainActor (Verge.Changes) -> Void) { + func setStateHandler(_ handler: @escaping @MainActor (State, State?) -> Void) { self.stateHandler = handler } @@ -335,98 +332,114 @@ public final class CropView: UIView, UIScrollViewDelegate { editingStack.start() binding: do { - store.sinkState(queue: .mainIsolated()) { [weak self, areAnimationsEnabled] state in + let localSubscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self else { return } + self.handleLocalStateChange() + } + } + subscriptions.append(localSubscription) + + // To restore current crop from editing-stack + let editingStackSubscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self else { return } + self.handleEditingStackStateChange() + } + } + subscriptions.append(editingStackSubscription) + } - guard let self = self else { return } + } - state.ifChanged({ - ( - $0.frame, - $0.layoutVersion - ) - }).do { (frame, _) in + } - guard let crop = state.proposedCrop else { - return - } + private func handleLocalStateChange() { + let currentState = state + let previousState = _previousState - guard frame != .zero else { - return - } + // Check if frame or layoutVersion changed + let frameChanged = previousState?.frame != currentState.frame + let layoutVersionChanged = previousState?.layoutVersion != currentState.layoutVersion - setupScrollViewOnce: do { - if self.hasSetupScrollViewCompleted == false { - self.hasSetupScrollViewCompleted = true - - self.imagePlatterView.bounds = .init( - origin: .zero, - size: crop.scrollViewContentSize() - ) - - let scrollView = self.scrollView - - // Do we need this? it seems ImageView's bounds changes contentSize automatically. not sure. - UIView.performWithoutAnimation { - let currentZoomScale = scrollView.zoomScale - let contentSize = crop.scrollViewContentSize() - if scrollView.contentSize != contentSize { - scrollView.contentInset = .zero - scrollView.zoomScale = 1 - scrollView.contentSize = contentSize - scrollView.zoomScale = currentZoomScale - } - } - } - } + if frameChanged || layoutVersionChanged { + guard let crop = currentState.proposedCrop else { + return + } - self.updateScrollContainerView( - by: crop, - preferredAspectRatio: state.preferredAspectRatio, - animated: areAnimationsEnabled && state.previous?.proposedCrop != nil /* whether first time load */, - animatesRotation: state.hasChanges(\.proposedCrop?.rotation) - ) + guard currentState.frame != .zero else { + return + } - } + setupScrollViewOnce: do { + if self.hasSetupScrollViewCompleted == false { + self.hasSetupScrollViewCompleted = true - if self.isAutoApplyEditingStackEnabled { - state.ifChanged(\.proposedCrop).do { crop in - guard let crop = crop else { - return - } - self.editingStack.crop(crop) + self.imagePlatterView.bounds = .init( + origin: .zero, + size: crop.scrollViewContentSize() + ) + + let scrollView = self.scrollView + + // Do we need this? it seems ImageView's bounds changes contentSize automatically. not sure. + UIView.performWithoutAnimation { + let currentZoomScale = scrollView.zoomScale + let contentSize = crop.scrollViewContentSize() + if scrollView.contentSize != contentSize { + scrollView.contentInset = .zero + scrollView.zoomScale = 1 + scrollView.contentSize = contentSize + scrollView.zoomScale = currentZoomScale } } - } - .store(in: &subscriptions) - - // To restore current crop from editing-stack - editingStack.sinkState { [weak self] state in - - guard let self = self else { return } + } - if let loaded = state.mapIfPresent(\.loadedState) { + self.updateScrollContainerView( + by: crop, + preferredAspectRatio: currentState.preferredAspectRatio, + animated: areAnimationsEnabled && previousState?.proposedCrop != nil /* whether first time load */, + animatesRotation: previousState?.proposedCrop?.rotation != currentState.proposedCrop?.rotation + ) + } - loaded.ifChanged(\.imageForCrop).do { image in - self.setImage(image) - } + if self.isAutoApplyEditingStackEnabled { + if previousState?.proposedCrop != currentState.proposedCrop { + if let crop = currentState.proposedCrop { + self.editingStack.crop(crop) + } + } + } - loaded.ifChanged(\.currentEdit.crop).do { crop in - self.setCrop(loaded.currentEdit.crop) - } + _previousState = currentState + } - } + private func handleEditingStackStateChange() { + let loadedState = editingStack.loadedState + let isLoading = editingStack.isLoading - state.ifChanged(\.isLoading).do { isLoading in - self.updateLoadingState(displays: isLoading) - } + // Check imageForCrop changed + if _previousLoadedState?.imageForCrop !== loadedState?.imageForCrop { + if let image = loadedState?.imageForCrop { + self.setImage(image) + } + } - } - .store(in: &subscriptions) + // Check crop changed + if _previousLoadedState?.currentEdit.crop != loadedState?.currentEdit.crop { + if let crop = loadedState?.currentEdit.crop { + self.setCrop(crop) } + } + // Check isLoading changed + if _previousIsLoading != isLoading { + self.updateLoadingState(displays: isLoading) } + _previousLoadedState = loadedState + _previousIsLoading = isLoading } private func updateLoadingState(displays: Bool) { @@ -482,7 +495,7 @@ public final class CropView: UIView, UIScrollViewDelegate { Applies the current state to the EditingStack. */ public func applyEditingStack() { - guard let crop = store.state.proposedCrop else { + guard let crop = state.proposedCrop else { EditorLog.error(.cropView, "EditingStack has not completed loading.") return } @@ -492,12 +505,10 @@ public final class CropView: UIView, UIScrollViewDelegate { public func resetCrop() { _pixeleditor_ensureMainThread() - store.commit { - if let proposedCrop = $0.proposedCrop { - $0.proposedCrop = proposedCrop.makeInitial() - $0.preferredAspectRatio = nil - $0.layoutVersion += 1 - } + if let proposedCrop = state.proposedCrop { + state.proposedCrop = proposedCrop.makeInitial() + state.preferredAspectRatio = nil + state.layoutVersion += 1 } guideView.setLockedAspectRatio(nil) @@ -506,79 +517,60 @@ public final class CropView: UIView, UIScrollViewDelegate { public func setRotation(_ rotation: EditingCrop.Rotation) { _pixeleditor_ensureMainThread() - store.commit { - - guard $0.proposedCrop?.rotation != rotation else { - return - } - - if let crop = $0.proposedCrop { - - $0.proposedCrop?.updateCropExtent( - crop.cropExtent.rotated((crop.rotation.angle - rotation.angle).radians) - ) - - $0.proposedCrop?.rotation = rotation - } + guard state.proposedCrop?.rotation != rotation else { + return + } - $0.layoutVersion += 1 + if let crop = state.proposedCrop { + state.proposedCrop?.updateCropExtent( + crop.cropExtent.rotated((crop.rotation.angle - rotation.angle).radians) + ) + state.proposedCrop?.rotation = rotation } + state.layoutVersion += 1 } public func setAdjustmentAngle(_ angle: EditingCrop.AdjustmentAngle) { - let records = store.commit { - - guard $0.proposedCrop?.adjustmentAngle != angle else { - return false - } - - $0.proposedCrop?.adjustmentAngle = angle - $0.layoutVersion += 1 - - return true + guard state.proposedCrop?.adjustmentAngle != angle else { + return } - if records { - record() - } + state.proposedCrop?.adjustmentAngle = angle + state.layoutVersion += 1 + record() } public func setCrop(_ crop: EditingCrop) { _pixeleditor_ensureMainThread() - store.commit { - guard $0.proposedCrop != crop else { - return - } + guard state.proposedCrop != crop else { + return + } - $0.proposedCrop = crop - if let ratio = $0.preferredAspectRatio { - $0.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) - } - $0.layoutVersion += 1 + state.proposedCrop = crop + if let ratio = state.preferredAspectRatio { + state.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) } + state.layoutVersion += 1 } public func setCroppingAspectRatio(_ ratio: PixelAspectRatio?) { _pixeleditor_ensureMainThread() - store.commit { - - guard $0.preferredAspectRatio != ratio else { - return - } + guard state.preferredAspectRatio != ratio else { + return + } - $0.preferredAspectRatio = ratio - if let ratio = ratio { - $0.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) - } else { - $0.proposedCrop?.purgeAspectRatio() - } - $0.layoutVersion += 1 + state.preferredAspectRatio = ratio + if let ratio = ratio { + state.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) + } else { + state.proposedCrop?.purgeAspectRatio() } + state.layoutVersion += 1 guideView.setLockedAspectRatio(ratio) } @@ -598,18 +590,14 @@ public final class CropView: UIView, UIScrollViewDelegate { public func swapCropRectangleDirection() { - store.commit { - - guard let crop = $0.proposedCrop else { - return - } - - $0.proposedCrop?.updateCropExtentIfNeeded( - toFitAspectRatio: PixelAspectRatio(crop.cropExtent.size).swapped() - ) - $0.layoutVersion += 1 - + guard let crop = state.proposedCrop else { + return } + + state.proposedCrop?.updateCropExtentIfNeeded( + toFitAspectRatio: PixelAspectRatio(crop.cropExtent.size).swapped() + ) + state.layoutVersion += 1 } /** @@ -676,10 +664,8 @@ extension CropView { /// to update masking with cropOutsideOverlay guideView.setNeedsLayout() - store.commit { - if $0.frame != frame { - $0.frame = frame - } + if state.frame != frame { + state.frame = frame } #if DEBUG @@ -917,78 +903,74 @@ extension CropView { @inline(__always) private func didChangeGuideViewWithDelay() { - guard let currentProposedCrop = store.state.proposedCrop else { + guard state.proposedCrop != nil else { return } record() - updateScrollViewInset(crop: currentProposedCrop) + updateScrollViewInset(crop: state.proposedCrop!) /// Triggers layout update later debounce.on { [weak self] in guard let self = self else { return } - self.store.commit { - $0.layoutVersion += 1 - } + self.state.layoutVersion += 1 } } private func record() { - store.commit { state in - guard let crop = state.proposedCrop else { - return - } + guard let crop = state.proposedCrop else { + return + } - // remove rotation while converting rect - let current = scrollView.transform - let currentGuideViewCenter = guideView.center + // remove rotation while converting rect + let current = scrollView.transform + let currentGuideViewCenter = guideView.center - do { - // rotating support - let croppingRect = guideView.convert(guideView.bounds, to: guideBackdropView) + do { + // rotating support + let croppingRect = guideView.convert(guideView.bounds, to: guideBackdropView) - // offsets guide view rect in maximum size - // for case of adjusted guide view by interaction - let offsetX = croppingRect.midX - guideBackdropView.bounds.midX - let offsetY = croppingRect.midY - guideBackdropView.bounds.midY + // offsets guide view rect in maximum size + // for case of adjusted guide view by interaction + let offsetX = croppingRect.midX - guideBackdropView.bounds.midX + let offsetY = croppingRect.midY - guideBackdropView.bounds.midY - // move focusing area to center - scrollView.transform = CGAffineTransform(rotationAngle: crop.aggregatedRotation.radians) - .concatenating(.init(translationX: -offsetX, y: -offsetY)) - .concatenating(.init(rotationAngle: -crop.aggregatedRotation.radians)) + // move focusing area to center + scrollView.transform = CGAffineTransform(rotationAngle: crop.aggregatedRotation.radians) + .concatenating(.init(translationX: -offsetX, y: -offsetY)) + .concatenating(.init(rotationAngle: -crop.aggregatedRotation.radians)) - // TODO: Find calculation way withoug using convert rect - // To work correctly, ignoring transform temporarily. + // TODO: Find calculation way withoug using convert rect + // To work correctly, ignoring transform temporarily. - // move the guide view to center for convert-rect. - guideView.center = guideBackdropView.center - } + // move the guide view to center for convert-rect. + guideView.center = guideBackdropView.center + } - // calculate - let guideRectInImageView = guideView.convert(guideView.bounds, to: imagePlatterView) + // calculate + let guideRectInImageView = guideView.convert(guideView.bounds, to: imagePlatterView) - do { - // restore guide view center same as displaying - guideView.center = currentGuideViewCenter + do { + // restore guide view center same as displaying + guideView.center = currentGuideViewCenter - // restore rotation - scrollView.transform = current - } + // restore rotation + scrollView.transform = current + } - // make crop extent for image - // converts rectangle for display into image's geometry. - let resolvedRect = crop.makeCropExtent( - rect: guideRectInImageView - ) + // make crop extent for image + // converts rectangle for display into image's geometry. + let resolvedRect = crop.makeCropExtent( + rect: guideRectInImageView + ) - state.proposedCrop?.updateCropExtent( - resolvedRect - ) - } + state.proposedCrop?.updateCropExtent( + resolvedRect + ) } @inline(__always) @@ -1030,9 +1012,7 @@ extension CropView { guard let self = self else { return } - self.store.commit { - $0.layoutVersion += 1 - } + self.state.layoutVersion += 1 } } @@ -1048,9 +1028,7 @@ extension CropView { return } - self.store.commit { - $0.layoutVersion += 1 - } + self.state.layoutVersion += 1 } } @@ -1086,7 +1064,7 @@ extension CropView { var remainingScroll: UIEdgeInsets { - guard let crop = store.state.proposedCrop else { + guard let crop = state.proposedCrop else { return .zero } diff --git a/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift b/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift index 0b71237a..0f5675ec 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift @@ -21,7 +21,7 @@ import UIKit import SwiftUI -import Verge +import StateGraph #if !COCOAPODS import BrightroomEngine #endif @@ -79,7 +79,7 @@ public struct SwiftUICropView: UIViewControllerRepresentable { private var _croppingAspectRatio: PixelAspectRatio? private var _resetAction: ResetAction? - private let stateHandler: @MainActor (Verge.Changes) -> Void + private let stateHandler: @MainActor (CropView.State, CropView.State?) -> Void private let isGuideInteractionEnabled: Bool private let isAutoApplyEditingStackEnabled: Bool private let areAnimationsEnabled: Bool @@ -93,7 +93,7 @@ public struct SwiftUICropView: UIViewControllerRepresentable { contentInset: UIEdgeInsets? = nil, @ViewBuilder cropInsideOverlay: @escaping (CropView.State.AdjustmentKind?) -> InsideOverlay, @ViewBuilder cropOutsideOverlay: @escaping (CropView.State.AdjustmentKind?) -> OutsideOverlay, - stateHandler: @escaping @MainActor (Verge.Changes) -> Void = { _ in } + stateHandler: @escaping @MainActor (CropView.State, CropView.State?) -> Void = { _, _ in } ) { self.editingStack = editingStack self.isGuideInteractionEnabled = isGuideInteractionEnabled @@ -111,7 +111,7 @@ public struct SwiftUICropView: UIViewControllerRepresentable { isAutoApplyEditingStackEnabled: Bool = false, areAnimationsEnabled: Bool = true, contentInset: UIEdgeInsets? = nil, - stateHandler: @escaping @MainActor (Verge.Changes) -> Void = { _ in } + stateHandler: @escaping @MainActor (CropView.State, CropView.State?) -> Void = { _, _ in } ) { self.cropInsideOverlay = nil self.cropOutsideOverlay = nil diff --git a/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift b/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift index 4a3fb2c3..b82d2aba 100644 --- a/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift +++ b/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift @@ -24,36 +24,31 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDelegate { - private struct State: Equatable { - fileprivate(set) var bounds: CGRect = .zero + @GraphStored private var stateBounds: CGRect = .zero + @GraphStored private var proposedCrop: EditingCrop? = nil + @GraphStored private var brushSize: CanvasView.BrushSize = .point(30) - fileprivate(set) var proposedCrop: EditingCrop? - - fileprivate(set) var brushSize: CanvasView.BrushSize = .point(30) - - func brushPixelSize() -> CGFloat? { + private func brushPixelSize() -> CGFloat? { + guard let proposedCrop = proposedCrop else { + return nil + } - guard let proposedCrop = proposedCrop else { - return nil - } + let aspectRatio = PixelAspectRatio(proposedCrop.cropExtent.size) + let size = aspectRatio.sizeThatFits(in: stateBounds.size) - let aspectRatio = PixelAspectRatio(proposedCrop.cropExtent.size) - let size = aspectRatio.sizeThatFits(in: bounds.size) - - let (min, _) = proposedCrop.calculateZoomScale(visibleSize: size) + let (min, _) = proposedCrop.calculateZoomScale(visibleSize: size) - let scale = proposedCrop.scaleForDrawing() + let scale = proposedCrop.scaleForDrawing() - switch brushSize { - case let .point(points): - return points / scale / min - case let .pixel(pixels): - return pixels - } + switch brushSize { + case let .point(points): + return points / scale / min + case let .pixel(pixels): + return pixels } } @@ -88,28 +83,31 @@ public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDele private let containerView = ContainerView() private let blurryImageView = _ImageView() - + private let drawingView = SmoothPathDrawingView() - + private let canvasView = CanvasView() - - private var subscriptions = Set() - + + private var subscriptions: [Any] = [] + private let editingStack: EditingStack - private let store: UIStateStore - private var currentBrush: OvalBrush? - + private var loadingOverlayFactory: (() -> UIView)? private weak var currentLoadingOverlay: UIView? - + private var isBinding = false + + // Change tracking + private var _previousCrop: EditingCrop? + private var _previousEditingPreviewImage: CIImage? + private var _previousBlurredMaskPaths: [DrawnPath]? // MARK: - Initializers public init(editingStack: EditingStack) { - + self.editingStack = editingStack self.backingView = .init( editingStack: editingStack, @@ -118,11 +116,6 @@ public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDele self.backingView.areAnimationsEnabled = false self.backingView.accessibilityIdentifier = "BlurryMasking" - store = .init( - initialState: .init(), - logger: nil - ) - super.init(frame: .zero) setUp: do { @@ -152,12 +145,12 @@ public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDele drawingView.handlers = drawingView.handlers&>.modify { $0.willBeginPan = { [unowned self] path in - - guard let pixelSize = store.state.primitive.brushPixelSize() else { + + guard let pixelSize = brushPixelSize() else { assertionFailure("It seems currently loading state.") return } - + currentBrush = .init(color: .black, pixelSize: pixelSize) let drawnPath = DrawnPath(brush: currentBrush!, path: path) @@ -168,47 +161,46 @@ public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDele } $0.didFinishPan = { [unowned self] path in canvasView.updatePreviewDrawing() - + let _path = (path.copy() as! UIBezierPath) - + let drawnPath = DrawnPath(brush: currentBrush!, path: _path) - + canvasView.previewDrawnPath = nil editingStack.append(blurringMaskPaths: CollectionOfOne(drawnPath)) - + currentBrush = nil } } - - editingStack.sinkState { [weak self] (state: Changes) in - - guard let self = self else { return } - - if let state = state.mapIfPresent(\.loadedState) { - state.ifChanged(\.currentEdit.crop).do { cropRect in - - // scaling for drawing paths - [self.canvasView, self.drawingView].forEach { view in - view.bounds = .init(origin: .zero, size: cropRect.imageSize) - let scale = Geometry.diagonalRatio(to: cropRect.scrollViewContentSize(), from: cropRect.imageSize) - view.transform = .init(scaleX: scale, y: scale) - view.frame.origin = .zero - } + let subscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } + + if let loadedState = self.editingStack.loadedState { + let cropRect = loadedState.currentEdit.crop + if self._previousCrop != cropRect { + self._previousCrop = cropRect + + // scaling for drawing paths + [self.canvasView, self.drawingView].forEach { view in + view.bounds = .init(origin: .zero, size: cropRect.imageSize) + let scale = Geometry.diagonalRatio(to: cropRect.scrollViewContentSize(), from: cropRect.imageSize) + view.transform = .init(scaleX: scale, y: scale) + view.frame.origin = .zero + } - /** - To avoid running pending layout operations from User Initiated actions. - */ - if cropRect != self.store.state.proposedCrop { - self.store.commit { - $0.proposedCrop = cropRect + /** + To avoid running pending layout operations from User Initiated actions. + */ + if cropRect != self.proposedCrop { + self.proposedCrop = cropRect } } } } - } - .store(in: &subscriptions) + subscriptions.append(subscription) defaultAppearance: do { setLoadingOverlay(factory: { @@ -228,25 +220,26 @@ public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDele editingStack.start() binding: do { - - editingStack.sinkState { [weak self] (state: Changes) in - - guard let self = self else { return } - - if let state = state.mapIfPresent(\.loadedState) { - - state.ifChanged(\.editingPreviewImage).do { image in - self.blurryImageView.display(image: BlurredMask.blur(image: image)) + let bindingSubscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } + + if let loadedState = self.editingStack.loadedState { + let previewImage = loadedState.editingPreviewImage + if self._previousEditingPreviewImage !== previewImage { + self._previousEditingPreviewImage = previewImage + self.blurryImageView.display(image: BlurredMask.blur(image: previewImage)) + } + + let paths = loadedState.currentEdit.drawings.blurredMaskPaths + if self._previousBlurredMaskPaths != paths { + self._previousBlurredMaskPaths = paths + self.canvasView.setResolvedDrawnPaths(paths) + } } - - state.ifChanged(\.currentEdit.drawings.blurredMaskPaths).do { paths in - self.canvasView.setResolvedDrawnPaths(paths) - } - } - } - .store(in: &subscriptions) + subscriptions.append(bindingSubscription) } } } @@ -257,9 +250,7 @@ public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDele } public func setBrushSize(_ size: CanvasView.BrushSize) { - store.commit { - $0.brushSize = size - } + brushSize = size } override public func layoutSubviews() { @@ -267,12 +258,10 @@ public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDele backingView.frame = bounds - store.commit { - if $0.bounds != bounds { - $0.bounds = bounds - } + if stateBounds != bounds { + stateBounds = bounds } - + } } diff --git a/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift b/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift index c27415b5..5756893b 100644 --- a/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift +++ b/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift @@ -24,19 +24,15 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph public final class CanvasView: PixelEditorCodeBasedView { - + public enum BrushSize: Equatable { case point(CGFloat) case pixel(CGFloat) } - - private struct State: Equatable { - var resolvedDrawnPaths: [DrawnPath] = [] - } - + public override class var layerClass: AnyClass { #if false return CATiledLayer.self @@ -44,48 +40,50 @@ public final class CanvasView: PixelEditorCodeBasedView { return CALayer.self #endif } - - private let store = UIStateStore(initialState: .init()) - private var subscriptions: Set = .init() - + + @GraphStored private var resolvedDrawnPaths: [DrawnPath] = [] + private var _previousResolvedDrawnPaths: [DrawnPath] = [] + private var subscriptions: [Any] = [] + private var resolvedShapeLayers: [CAShapeLayer] = [] private var previewShapeLayer: CAShapeLayer? - + override init(frame: CGRect) { super.init(frame: frame) isOpaque = false - + if let tiledLayer = layer as? CATiledLayer { tiledLayer.tileSize = .init(width: 512, height: 512) } - - store.sinkState { [weak self] (state) in - - guard let self = self else { return } - - state.ifChanged(\.resolvedDrawnPaths).do { paths in - - let layers = paths.map { path -> CAShapeLayer in - let layer = Self.makeShapeLayer(for: path.brush) - layer.path = path.bezierPath.cgPath - return layer - } - - // TODO: Get better way for perfromance - self.resolvedShapeLayers.forEach { - $0.removeFromSuperlayer() - } - - layers.forEach { - self.layer.addSublayer($0) + + let subscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } + + if self._previousResolvedDrawnPaths != self.resolvedDrawnPaths { + self._previousResolvedDrawnPaths = self.resolvedDrawnPaths + + let paths = self.resolvedDrawnPaths + let layers = paths.map { path -> CAShapeLayer in + let layer = Self.makeShapeLayer(for: path.brush) + layer.path = path.bezierPath.cgPath + return layer + } + + // TODO: Get better way for perfromance + self.resolvedShapeLayers.forEach { + $0.removeFromSuperlayer() + } + + layers.forEach { + self.layer.addSublayer($0) + } + self.resolvedShapeLayers = layers } - self.resolvedShapeLayers = layers - } - } - .store(in: &subscriptions) - + subscriptions.append(subscription) + } private static func makeShapeLayer(for brush: OvalBrush) -> CAShapeLayer { @@ -134,9 +132,7 @@ public final class CanvasView: PixelEditorCodeBasedView { } public func setResolvedDrawnPaths(_ paths: [DrawnPath]) { - store.commit { - $0.resolvedDrawnPaths = paths - } + resolvedDrawnPaths = paths } public override func layoutSubviews() { diff --git a/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift b/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift index 6ad29b0f..fc0d8979 100644 --- a/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift +++ b/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift @@ -23,7 +23,7 @@ import BrightroomEngine #endif import UIKit -import Verge +import StateGraph /** A view that displays the edited image, plus displays original image for comparison with touch-down interaction. @@ -40,13 +40,17 @@ public final class ImagePreviewView: PixelEditorCodeBasedView { #endif private let editingStack: EditingStack - private var subscriptions = Set() + private var subscriptions: [Any] = [] private var loadingOverlayFactory: (() -> UIView)? private weak var currentLoadingOverlay: UIView? private var isBinding = false - private var cachedCroppedImage: (state: EditingStack.State.Loaded, image: CIImage)? = nil + private var cachedCroppedImage: (state: EditingStack.Loaded, image: CIImage)? = nil + + // Change tracking + private var _previousIsLoading: Bool? + private var _previousCurrentEdit: EditingStack.Edit? // MARK: - Initializers @@ -102,28 +106,33 @@ public final class ImagePreviewView: PixelEditorCodeBasedView { if isBinding == false { isBinding = true - editingStack.sinkState { [weak self] state in - - guard let self = self else { return } - - state.ifChanged(\.isLoading).do { isLoading in - self.updateLoadingOverlay(displays: isLoading) - } + let subscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } + + let isLoading = self.editingStack.isLoading + if self._previousIsLoading != isLoading { + self._previousIsLoading = isLoading + self.updateLoadingOverlay(displays: isLoading) + } - UIView.performWithoutAnimation { - if let state = state.mapIfPresent(\.loadedState) { - if state.hasChanges({ ($0.currentEdit) }, .equality()) { - self.requestPreviewImage(state: state.primitive) + UIView.performWithoutAnimation { + if let loadedState = self.editingStack.loadedState { + let currentEdit = loadedState.currentEdit + if self._previousCurrentEdit != currentEdit { + self._previousCurrentEdit = currentEdit + self.requestPreviewImage(state: loadedState) + } } } } } - .store(in: &subscriptions) + subscriptions.append(subscription) } } } - private func requestPreviewImage(state: EditingStack.State.Loaded) { + private func requestPreviewImage(state: EditingStack.Loaded) { let croppedImage: CIImage if @@ -177,7 +186,7 @@ public final class ImagePreviewView: PixelEditorCodeBasedView { override public func layoutSubviews() { super.layoutSubviews() - if let loaded = editingStack.store.state.loadedState { + if let loaded = editingStack.loadedState { requestPreviewImage(state: loaded) } } diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift index 6ee48666..ce25dfe8 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift @@ -21,7 +21,7 @@ import Photos import UIKit -import Verge +import StateGraph #if !COCOAPODS import BrightroomEngine @@ -114,7 +114,14 @@ public final class ClassicImageEditViewController: UIViewController { action: #selector(didTapCancelButton) ) - private var subscriptions: Set = .init() + private var subscriptions: [Any] = [] + + // Change tracking + private var _previousTitle: String? + private var _previousMaskingBrushSize: CanvasView.BrushSize? + private var _previousMode: ClassicImageEditViewModel.Mode? + private var _previousIsLoading: Bool? + private var _previousProposedCrop: EditingCrop? private lazy var loadingView = LoadingBlurryOverlayView( effect: UIBlurEffect(style: .dark), @@ -292,22 +299,24 @@ public final class ClassicImageEditViewController: UIViewController { animated: false ) - viewModel.sinkState(queue: .mainIsolated()) { [weak self] state in - - guard let self = self else { return } - - self.updateUI(state: state) + let subscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } + self.updateUI() + } } - .store(in: &subscriptions) - - cropView.store.sinkState { [viewModel] state in - - state.ifChanged(\.proposedCrop).do { value in - guard let value = value else { return } - viewModel.setProposedCrop(value) + subscriptions.append(subscription) + + let cropViewSubscription = withGraphTracking { [viewModel, weak cropView] in + withGraphTrackingGroup { + guard let cropView = cropView else { return } + let proposedCrop = cropView.state.proposedCrop + if let value = proposedCrop { + viewModel.setProposedCrop(value) + } } } - .store(in: &subscriptions) + subscriptions.append(cropViewSubscription) viewModel.editingStack.start() } @@ -329,16 +338,22 @@ public final class ClassicImageEditViewController: UIViewController { handlers.didCancelEditing(self) } - private func updateUI(state: Changes) { - state.ifChanged(\.title).do { title in + private func updateUI() { + let title = viewModel.title + if _previousTitle != title { + _previousTitle = title navigationItem.title = title } - state.ifChanged(\.maskingBrushSize).do { - maskingView.setBrushSize($0) + let brushSize = viewModel.maskingBrushSize + if _previousMaskingBrushSize != brushSize { + _previousMaskingBrushSize = brushSize + maskingView.setBrushSize(brushSize) } - state.ifChanged(\.mode).do { mode in + let mode = viewModel.mode + if _previousMode != mode { + _previousMode = mode switch mode { case .crop: @@ -392,9 +407,9 @@ public final class ClassicImageEditViewController: UIViewController { } } - let editingState = state.map(\.editingState) - - editingState.ifChanged(\.isLoading).do { isLoading in + let isLoading = viewModel.editingStack.isLoading + if _previousIsLoading != isLoading { + _previousIsLoading = isLoading switch isLoading { case true: diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewModel.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewModel.swift index 78296d70..15e843d4 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewModel.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewModel.swift @@ -20,12 +20,12 @@ // THE SOFTWARE. import UIKit -import Verge +import StateGraph #if !COCOAPODS import BrightroomEngine #endif -public final class ClassicImageEditViewModel: Equatable, StoreDriverType { +public final class ClassicImageEditViewModel: Equatable { public static func == (lhs: ClassicImageEditViewModel, rhs: ClassicImageEditViewModel) -> Bool { lhs === rhs } @@ -37,29 +37,18 @@ public final class ClassicImageEditViewModel: Equatable, StoreDriverType { case preview } - public struct State: Equatable { - public var editingState: Changes - - public fileprivate(set) var title: String = "" - public fileprivate(set) var mode: Mode = .preview - - public fileprivate(set) var maskingBrushSize: CanvasView.BrushSize = .point(30) - - // TODO: Rename - fileprivate var drawnPaths: [DrawnPath] = [] - fileprivate(set) var proposedCrop: EditingCrop? - } - public let options: ClassicImageEditOptions - public let store: Store - public let editingStack: EditingStack - private var subscriptions: Set = .init() - public let localizedStrings: ClassicImageEditViewController.LocalizedStrings + @GraphStored public var title: String = "" + @GraphStored public var mode: Mode = .preview + @GraphStored public var maskingBrushSize: CanvasView.BrushSize = .point(30) + @GraphStored var drawnPaths: [DrawnPath] = [] + @GraphStored public var proposedCrop: EditingCrop? = nil + public init( editingStack: EditingStack, options: ClassicImageEditOptions, @@ -68,7 +57,6 @@ public final class ClassicImageEditViewModel: Equatable, StoreDriverType { self.localizedStrings = localizedStrings self.options = options self.editingStack = editingStack - store = .init(initialState: .init(editingState: editingStack.state)) if options.isFaceDetectionEnabled { editingStack.cropModifier = .faceDetection(aspectRatio: options.croppingAspectRatio) @@ -79,30 +67,24 @@ public final class ClassicImageEditViewModel: Equatable, StoreDriverType { completion(new) } } - - editingStack.assign(to: assignee(\.editingState)).store(in: &subscriptions) } func setTitle(_ title: String) { - commit { - $0.title = title - } + self.title = title } func setMode(_ mode: Mode) { - commit { - $0.mode = mode - - switch mode { - case .crop: - $0.title = localizedStrings.editAdjustment - case .masking: - $0.title = localizedStrings.editMask - case .editing: - break - case .preview: - $0.title = "" - } + self.mode = mode + + switch mode { + case .crop: + title = localizedStrings.editAdjustment + case .masking: + title = localizedStrings.editMask + case .editing: + break + case .preview: + title = "" } } @@ -115,38 +97,30 @@ public final class ClassicImageEditViewModel: Equatable, StoreDriverType { } func setBrushSize(_ brushSize: CGFloat) { - commit { - $0.maskingBrushSize = .point(brushSize) - } + maskingBrushSize = .point(brushSize) } func setDrawinPaths(_ drawnPaths: [DrawnPath]) { - commit { - $0.drawnPaths = drawnPaths - } + self.drawnPaths = drawnPaths } func endCrop(save: Bool) { if save { - if let proposed = state.proposedCrop { + if let proposed = proposedCrop { editingStack.crop(proposed) editingStack.takeSnapshot() } } else { - commit { - guard let loadedState = $0.editingState.loadedState else { - assertionFailure() - return - } - $0.proposedCrop = loadedState.currentEdit.crop + guard let loadedState = editingStack.loadedState else { + assertionFailure() + return } + proposedCrop = loadedState.currentEdit.crop } } func setProposedCrop(_ proposedCrop: EditingCrop) { - commit { - $0.proposedCrop = proposedCrop - } + self.proposedCrop = proposedCrop } } diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift index 0982bff6..d77013bb 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift @@ -24,27 +24,29 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditControlBase : UIView, ClassicImageEditControlChildViewType { - - open func didReceiveCurrentEdit(state: Changes) { - + + open func didReceiveCurrentEdit() { + } public let viewModel: ClassicImageEditViewModel - - private var subscriptions: Set = .init() + + private var subscriptions: [Any] = [] public init(viewModel: ClassicImageEditViewModel) { self.viewModel = viewModel super.init(frame: .zero) setup() - - viewModel.sinkState { [weak self] (state) in - self?.didReceiveCurrentEdit(state: state) + + let subscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + self?.didReceiveCurrentEdit() + } } - .store(in: &subscriptions) + subscriptions.append(subscription) } @available(*, unavailable) diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditEditMenuControlView.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditEditMenuControlView.swift index e6e04338..fd66b96e 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditEditMenuControlView.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditEditMenuControlView.swift @@ -24,7 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditEditMenuControlBase: ClassicImageEditControlBase { override public required init(viewModel: ClassicImageEditViewModel) { @@ -284,10 +284,10 @@ public enum ClassicImageEditEditMenu: CaseIterable { } } - override open func didReceiveCurrentEdit(state: Changes) { - if let edit = state.editingState.loadedState?.currentEdit { + override open func didReceiveCurrentEdit() { + if let edit = viewModel.editingStack.loadedState?.currentEdit { maskButton.hasChanges = !edit.drawings.blurredMaskPaths.isEmpty - + contrastButton.hasChanges = edit.filters.contrast != nil exposureButton.hasChanges = edit.filters.exposure != nil temperatureButton.hasChanges = edit.filters.temperature != nil diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift index 0f48b700..8e47660b 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift @@ -24,7 +24,7 @@ import UIKit import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditPresetListControlBase : ClassicImageEditControlBase { @@ -36,83 +36,83 @@ open class ClassicImageEditPresetListControlBase : ClassicImageEditControlBase { } open class ClassicImageEditPresetListControl: ClassicImageEditPresetListControlBase, UICollectionViewDelegateFlowLayout, UICollectionViewDataSource { - + private enum Section : Int, CaseIterable { - + case original case selections } - - private struct State: Equatable { - - struct Content: Equatable { - var previews: [PreviewFilterPreset] - var originalImage: CIImage - } - - var content: Content? - var currentSelected: FilterPreset? + private struct Content: Equatable { + var previews: [PreviewFilterPreset] + var originalImage: CIImage } // MARK: - Properties public lazy var collectionView: UICollectionView = self.makeCollectionView() - + private let feedbackGenerator = UISelectionFeedbackGenerator() - - private let store: Store - - private var subscriptions: Set = .init() + + @GraphStored private var content: Content? = nil + @GraphStored private var currentSelected: FilterPreset? = nil + + private var subscriptions: [Any] = [] + + // Change tracking + private var _previousContent: Content? + private var _previousCurrentSelected: FilterPreset? + private var _previousThumbnailImage: CIImage? + private var _previousPreviewFilterPresets: [PreviewFilterPreset]? + private var _previousPreset: FilterPreset? // MARK: - Functions public required init( viewModel: ClassicImageEditViewModel ) { - - self.store = .init(initialState: .init(content: nil, currentSelected: nil)) - + super.init(viewModel: viewModel) - - viewModel.sinkState { [weak self] state in - - guard let self = self else { return } - - self.store.commit { viewState in - - if let state = state.mapIfPresent(\.editingState.loadedState) { - - state.ifChanged(\.thumbnailImage, \.previewFilterPresets).do { image, filters in - - viewState.content = .init(previews: filters, originalImage: image) + + let viewModelSubscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } + + if let loadedState = self.viewModel.editingStack.loadedState { + let thumbnailImage = loadedState.thumbnailImage + let previewFilterPresets = loadedState.previewFilterPresets + if self._previousThumbnailImage !== thumbnailImage || + self._previousPreviewFilterPresets != previewFilterPresets { + self._previousThumbnailImage = thumbnailImage + self._previousPreviewFilterPresets = previewFilterPresets + self.content = Content(previews: previewFilterPresets, originalImage: thumbnailImage) } - } - } - } - .store(in: &subscriptions) - - store.sinkState { [weak self] (state) in - - guard let self = self else { return } - - if state.hasChanges(\.content) { - self.collectionView.reloadData() - } + subscriptions.append(viewModelSubscription) + + let storeSubscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } - state.ifChanged(\.currentSelected).do { value in - self.collectionView.visibleCells.forEach { - self.updateSelected(cell: $0, selectedItem: value) + if self._previousContent != self.content { + self._previousContent = self.content + self.collectionView.reloadData() + } + + if self._previousCurrentSelected != self.currentSelected { + self._previousCurrentSelected = self.currentSelected + let value = self.currentSelected + self.collectionView.visibleCells.forEach { + self.updateSelected(cell: $0, selectedItem: value) + } + self.scrollTo(selectedItem: value, animated: true) } - self.scrollTo(selectedItem: value, animated: true) } - } - .store(in: &subscriptions) - + subscriptions.append(storeSubscription) + } open override func setup() { @@ -173,37 +173,37 @@ open class ClassicImageEditPresetListControl: ClassicImageEditPresetListControlB return layout } - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.preset).do { value in - store.commit { - $0.currentSelected = value - } + open override func didReceiveCurrentEdit() { + + let value = viewModel.editingStack.loadedState?.currentEdit.filters.preset + if _previousPreset != value { + _previousPreset = value + currentSelected = value } - + } open override func layoutSubviews() { super.layoutSubviews() collectionView.collectionViewLayout.invalidateLayout() - scrollTo(selectedItem: store.state.currentSelected, animated: false) + scrollTo(selectedItem: currentSelected, animated: false) } // MARK: - UICollectionViewDeleagte / DataSource open func numberOfSections(in collectionView: UICollectionView) -> Int { - guard store.state.content != nil else { + guard content != nil else { return 0 } return Section.allCases.count } open func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - - guard let content = store.state.content else { + + guard let content = content else { return 0 } - + switch Section.allCases[section] { case .original: return 1 @@ -224,13 +224,13 @@ open class ClassicImageEditPresetListControl: ClassicImageEditPresetListControlB } open func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - - guard let content = store.state.content else { + + guard let content = content else { preconditionFailure() } - let currentSelected = store.state.currentSelected - + let currentSelected = currentSelected + switch Section.allCases[indexPath.section] { case .original: let cell = collectionView.dequeueReusableCell(withReuseIdentifier: NormalCell.identifier, for: indexPath) as! NormalCell @@ -248,30 +248,30 @@ open class ClassicImageEditPresetListControl: ClassicImageEditPresetListControlB } open func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) { - - guard let content = store.state.content else { + + guard let content = content else { preconditionFailure() } - + switch Section.allCases[indexPath.section] { case .original: - + viewModel.editingStack.set(filters: { $0.preset = nil }) - + viewModel.editingStack.takeSnapshot() - + case .selections: - + viewModel.editingStack.set(filters: { let filter = content.previews[indexPath.item] $0.preset = filter.filter }) - + viewModel.editingStack.takeSnapshot() } - + feedbackGenerator.selectionChanged() } @@ -287,9 +287,9 @@ open class ClassicImageEditPresetListControl: ClassicImageEditPresetListControlB } private func scrollTo(selectedItem: FilterPreset?, animated: Bool) { - - guard let content = store.state.content else { - + + guard let content = content else { + return } diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditClarityControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditClarityControl.swift index 180bf574..8643af6a 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditClarityControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditClarityControl.swift @@ -24,7 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditClarityControlBase : ClassicImageEditFilterControlBase { @@ -70,12 +70,14 @@ open class ClassicImageEditClarityControl : ClassicImageEditClarityControlBase { } } - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.unsharpMask).do { value in + private var _previousUnsharpMask: FilterUnsharpMask? + + open override func didReceiveCurrentEdit() { + let value = viewModel.editingStack.loadedState?.currentEdit.filters.unsharpMask + if _previousUnsharpMask != value { + _previousUnsharpMask = value slider.set(value: value?.intensity ?? 0, in: FilterUnsharpMask.Params.intensity) } - } @objc diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditContrastControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditContrastControl.swift index fec1e870..8369c2bd 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditContrastControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditContrastControl.swift @@ -23,7 +23,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditContrastControlBase : ClassicImageEditFilterControlBase { public required init(viewModel: ClassicImageEditViewModel) { @@ -68,12 +68,14 @@ open class ClassicImageEditContrastControl : ClassicImageEditContrastControlBase } - open override func didReceiveCurrentEdit(state: Changes) { + private var _previousContrast: FilterContrast? - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.contrast).do { value in + open override func didReceiveCurrentEdit() { + let value = viewModel.editingStack.loadedState?.currentEdit.filters.contrast + if _previousContrast != value { + _previousContrast = value slider.set(value: value?.value ?? 0, in: FilterContrast.range) } - } @objc diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditExposureControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditExposureControl.swift index 27493f5e..63da3718 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditExposureControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditExposureControl.swift @@ -24,7 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditExposureControlBase : ClassicImageEditFilterControlBase { @@ -69,12 +69,14 @@ open class ClassicImageEditExposureControl : ClassicImageEditExposureControlBase } } - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.exposure).do { value in + private var _previousExposure: FilterExposure? + + open override func didReceiveCurrentEdit() { + let value = viewModel.editingStack.loadedState?.currentEdit.filters.exposure + if _previousExposure != value { + _previousExposure = value slider.set(value: value?.value ?? 0, in: FilterExposure.range) } - } @objc diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFadeControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFadeControl.swift index 42552cb9..698abd9c 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFadeControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFadeControl.swift @@ -24,7 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditFadeControlBase : ClassicImageEditFilterControlBase { @@ -70,12 +70,14 @@ open class ClassicImageEditFadeControl : ClassicImageEditFadeControlBase { } } - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.fade).do { value in + private var _previousFade: FilterFade? + + open override func didReceiveCurrentEdit() { + let value = viewModel.editingStack.loadedState?.currentEdit.filters.fade + if _previousFade != value { + _previousFade = value slider.set(value: value?.intensity ?? 0, in: FilterFade.Params.intensity) } - } @objc diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFilterControlBase.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFilterControlBase.swift index ea4e429f..e69e3514 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFilterControlBase.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFilterControlBase.swift @@ -20,9 +20,10 @@ // THE SOFTWARE. import UIKit +import StateGraph open class ClassicImageEditFilterControlBase : ClassicImageEditControlBase { - + open var title: String { fatalError("Must be overrided") } diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditGaussianBlurControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditGaussianBlurControl.swift index 7859021a..dea12197 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditGaussianBlurControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditGaussianBlurControl.swift @@ -24,7 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditGaussianBlurControlBase : ClassicImageEditFilterControlBase { @@ -70,12 +70,14 @@ open class ClassicImageEditGaussianBlurControl : ClassicImageEditGaussianBlurCon } } - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.gaussianBlur).do { value in + private var _previousGaussianBlur: FilterGaussianBlur? + + open override func didReceiveCurrentEdit() { + let value = viewModel.editingStack.loadedState?.currentEdit.filters.gaussianBlur + if _previousGaussianBlur != value { + _previousGaussianBlur = value slider.set(value: value?.value ?? 0, in: FilterGaussianBlur.range) } - } @objc diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditHighlightsControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditHighlightsControl.swift index f36d216f..a524008d 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditHighlightsControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditHighlightsControl.swift @@ -23,7 +23,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditHighlightsControlBase : ClassicImageEditFilterControlBase { @@ -69,12 +69,14 @@ open class ClassicImageEditHighlightsControl : ClassicImageEditHighlightsControl } } - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.highlights).do { value in + private var _previousHighlights: FilterHighlights? + + open override func didReceiveCurrentEdit() { + let value = viewModel.editingStack.loadedState?.currentEdit.filters.highlights + if _previousHighlights != value { + _previousHighlights = value slider.set(value: value?.value ?? 0, in: FilterHighlights.range) } - } @objc diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSaturationControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSaturationControl.swift index 1fa9f0b6..7f0e776a 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSaturationControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSaturationControl.swift @@ -24,7 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditSaturationControlBase : ClassicImageEditFilterControlBase { @@ -69,12 +69,14 @@ open class ClassicImageEditSaturationControl : ClassicImageEditSaturationControl } } - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.saturation).do { value in + private var _previousSaturation: FilterSaturation? + + open override func didReceiveCurrentEdit() { + let value = viewModel.editingStack.loadedState?.currentEdit.filters.saturation + if _previousSaturation != value { + _previousSaturation = value slider.set(value: value?.value ?? 0, in: FilterSaturation.range) } - } @objc diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditShadowsControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditShadowsControl.swift index 2b25248b..d2b92263 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditShadowsControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditShadowsControl.swift @@ -24,7 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditShadowsControlBase : ClassicImageEditFilterControlBase { @@ -69,12 +69,14 @@ open class ClassicImageEditShadowsControl : ClassicImageEditShadowsControlBase { } } - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.shadows).do { value in + private var _previousShadows: FilterShadows? + + open override func didReceiveCurrentEdit() { + let value = viewModel.editingStack.loadedState?.currentEdit.filters.shadows + if _previousShadows != value { + _previousShadows = value slider.set(value: value?.value ?? 0, in: FilterShadows.range) } - } @objc diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSharpenControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSharpenControl.swift index 5d33f5d2..d0a7b9ef 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSharpenControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSharpenControl.swift @@ -24,7 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditSharpenControlBase : ClassicImageEditFilterControlBase { @@ -70,12 +70,14 @@ open class ClassicImageEditSharpenControl : ClassicImageEditSharpenControlBase { } } - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.sharpen).do { value in + private var _previousSharpen: FilterSharpen? + + open override func didReceiveCurrentEdit() { + let value = viewModel.editingStack.loadedState?.currentEdit.filters.sharpen + if _previousSharpen != value { + _previousSharpen = value slider.set(value: value?.sharpness ?? 0, in: FilterSharpen.Params.sharpness) } - } @objc diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditTemperatureControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditTemperatureControl.swift index 917ee98e..75ab5648 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditTemperatureControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditTemperatureControl.swift @@ -24,7 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditTemperatureControlBase : ClassicImageEditFilterControlBase { @@ -69,13 +69,14 @@ open class ClassicImageEditTemperatureControl : ClassicImageEditTemperatureContr } } - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.temperature).do { value in + private var _previousTemperature: FilterTemperature? + open override func didReceiveCurrentEdit() { + let value = viewModel.editingStack.loadedState?.currentEdit.filters.temperature + if _previousTemperature != value { + _previousTemperature = value slider.set(value: value?.value ?? 0, in: FilterTemperature.range) } - } @objc diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditVignetteControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditVignetteControl.swift index a4028bc6..029b51ec 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditVignetteControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditVignetteControl.swift @@ -24,7 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif -import Verge +import StateGraph open class ClassicImageEditVignetteControlBase : ClassicImageEditFilterControlBase { @@ -70,9 +70,12 @@ open class ClassicImageEditVignetteControl : ClassicImageEditVignetteControlBase } } - open override func didReceiveCurrentEdit(state: Changes) { - - state.ifChanged(\.editingState.loadedState?.currentEdit.filters.vignette).do { value in + private var _previousVignette: FilterVignette? + + open override func didReceiveCurrentEdit() { + let value = viewModel.editingStack.loadedState?.currentEdit.filters.vignette + if _previousVignette != value { + _previousVignette = value slider.set(value: value?.value ?? 0, in: FilterVignette.range) } } diff --git a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift index aa5ebdce..70b01b07 100644 --- a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift +++ b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift @@ -22,134 +22,135 @@ import UIKit import SwiftUI -import Verge +import StateGraph #if !COCOAPODS import BrightroomEngine #endif final class PhotosCropAspectRatioControl: PixelEditorCodeBasedView { - struct State: Equatable { - enum Direction { - /** - +----+ - | | - | | - | | - | | - | | - | | - +----+ - */ - case vertical - /** - +---------------+ - | | - | | - +---------------+ - */ - case horizontal - - mutating func swap() { - switch self { - case .horizontal: self = .vertical - case .vertical: self = .horizontal - } - } - } - + + enum Direction: Equatable { + /** + +----+ + | | + | | + | | + | | + | | + | | + +----+ + */ + case vertical /** - A rectangle whose width is longer than its height +---------------+ | | | | +---------------+ */ - let horizontalRectangleApectRatios: [PixelAspectRatio] = [ - .init(width: 16, height: 9), - .init(width: 10, height: 8), - .init(width: 7, height: 5), - .init(width: 4, height: 3), - .init(width: 5, height: 3), - .init(width: 3, height: 2), - ] - - let originalAspectRatio: PixelAspectRatio - let originalDirection: Direction - - var selectedAspectRatio: PixelAspectRatio? { - didSet { - guard oldValue != selectedAspectRatio else { return } - - if let selectedAspectRatio { - direction = selectedAspectRatio.height > selectedAspectRatio.width ? .vertical : .horizontal - } + case horizontal + + mutating func swap() { + switch self { + case .horizontal: self = .vertical + case .vertical: self = .horizontal } } + } - var direction: Direction { - didSet { - if let selectedAspectRatio = selectedAspectRatio { - if direction != selectedAspectRatio.direction { - self.selectedAspectRatio = selectedAspectRatio.swapped() - } - } + /** + A rectangle whose width is longer than its height + +---------------+ + | | + | | + +---------------+ + */ + let horizontalRectangleApectRatios: [PixelAspectRatio] = [ + .init(width: 16, height: 9), + .init(width: 10, height: 8), + .init(width: 7, height: 5), + .init(width: 4, height: 3), + .init(width: 5, height: 3), + .init(width: 3, height: 2), + ] + + let originalAspectRatio: PixelAspectRatio + let originalDirection: Direction + + var selectedAspectRatio: PixelAspectRatio? { + didSet { + guard oldValue != selectedAspectRatio else { return } + + if let selectedAspectRatio { + direction = selectedAspectRatio.height > selectedAspectRatio.width ? .vertical : .horizontal } } - - var canSelectDirection: Bool { - guard let selectedAspectRatio = selectedAspectRatio else { - return false - } - - guard selectedAspectRatio != .square else { - return false + } + + var direction: Direction { + didSet { + if let selectedAspectRatio = selectedAspectRatio { + if direction != selectedAspectRatio.direction { + self.selectedAspectRatio = selectedAspectRatio.swapped() + } } - - return true } } - + + var canSelectDirection: Bool { + guard let selectedAspectRatio = selectedAspectRatio else { + return false + } + + guard selectedAspectRatio != .square else { + return false + } + + return true + } + struct Handlers { - + var didSelectAspectRatio: (PixelAspectRatio) -> Void = { _ in } var didSelectFreeform: () -> Void = {} - + } - + var handlers: Handlers = .init() - + private let horizontalButton = AspectRatioDirectionButton(direction: .horizontal) private let verticalButton = AspectRatioDirectionButton(direction: .vertical) - + private let scrollView = UIScrollView() - + private let originalButton = AspectRatioButton() private let freeformButton = AspectRatioButton() private let aspectSquareButton = AspectRatioButton() - - private let store: UIStateStore - private var subscriptions = Set() - + + private var subscriptions: [Any] = [] + + // Change tracking + private var _previousSelectedAspectRatio: PixelAspectRatio?? + private var _previousCanSelectDirection: Bool? + private var _previousDirection: Direction? + private var isSupressingHandlers = false private let localizedStrings: PhotosCropViewController.LocalizedStrings - + + private var buttons: [CGFloat: UIButton] = [:] + init( originalAspectRatio: PixelAspectRatio, localizedStrings: PhotosCropViewController.LocalizedStrings ) { - + self.localizedStrings = localizedStrings - self.store = .init( - initialState: .init( - originalAspectRatio: originalAspectRatio, - originalDirection: originalAspectRatio.width > originalAspectRatio.height ? .horizontal : .vertical, - direction: originalAspectRatio.width > originalAspectRatio.height ? .horizontal : .vertical - ) - ) - + self.originalAspectRatio = originalAspectRatio + self.originalDirection = originalAspectRatio.width > originalAspectRatio.height ? .horizontal : .vertical + self.direction = originalAspectRatio.width > originalAspectRatio.height ? .horizontal : .vertical + super.init(frame: .zero) - + let directionStackView = UIStackView()&>.do { $0.addArrangedSubview(verticalButton) $0.addArrangedSubview(horizontalButton) @@ -157,16 +158,14 @@ final class PhotosCropAspectRatioControl: PixelEditorCodeBasedView { $0.spacing = 16 $0.alignment = .center } - - var buttons: [CGFloat: UIButton] = [:] - - buttons[ratioValue(from: store.state.originalAspectRatio)] = originalButton + + buttons[ratioValue(from: originalAspectRatio)] = originalButton buttons[ratioValue(from: .square)] = aspectSquareButton - + let itemsStackView = UIStackView()&>.do { stackView in - + stackView.spacing = 12 - + [ originalButton, freeformButton, @@ -175,88 +174,74 @@ final class PhotosCropAspectRatioControl: PixelEditorCodeBasedView { .forEach { stackView.addArrangedSubview($0) } - - store.state.horizontalRectangleApectRatios.forEach { ratio in - + + horizontalRectangleApectRatios.forEach { ratio in + let button = AspectRatioButton() - + stackView.addArrangedSubview(button) - - button.onTap { [unowned store] in - store.commit { - - switch $0.direction { - case .horizontal: - - $0.selectedAspectRatio = ratio - - case .vertical: - - $0.selectedAspectRatio = ratio.swapped() - } - + + button.onTap { [weak self] in + guard let self = self else { return } + + switch self.direction { + case .horizontal: + self.selectedAspectRatio = ratio + case .vertical: + self.selectedAspectRatio = ratio.swapped() } } - + buttons[ratioValue(from: ratio)] = button } } - - horizontalButton.onTap { [unowned self] in - store.commit { - $0.direction = .horizontal - } + + horizontalButton.onTap { [weak self] in + self?.direction = .horizontal } - - verticalButton.onTap { [unowned self] in - store.commit { - $0.direction = .vertical - } + + verticalButton.onTap { [weak self] in + self?.direction = .vertical } - + originalButton&>.do { $0.setTitle(localizedStrings.button_aspectratio_original, for: .normal) - $0.onTap { [unowned store] in - store.commit { - $0.selectedAspectRatio = $0.originalAspectRatio - } + $0.onTap { [weak self] in + guard let self = self else { return } + self.selectedAspectRatio = self.originalAspectRatio } - + } - + freeformButton&>.do { $0.setTitle(localizedStrings.button_aspectratio_freeform, for: .normal) - $0.onTap { [unowned store] in - store.commit { - $0.selectedAspectRatio = nil - } + $0.onTap { [weak self] in + self?.selectedAspectRatio = nil } } - + aspectSquareButton&>.do { $0.setTitle(localizedStrings.button_aspectratio_square, for: .normal) - $0.onTap { [unowned store] in - store.commit { - $0.selectedAspectRatio = .square - } + $0.onTap { [weak self] in + self?.selectedAspectRatio = .square } } - + addSubview(directionStackView) addSubview(scrollView) scrollView.addSubview(itemsStackView) - + itemsStackView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ itemsStackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), itemsStackView.rightAnchor.constraint(equalTo: scrollView.contentLayoutGuide.rightAnchor), itemsStackView.leftAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leftAnchor), itemsStackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), - + itemsStackView.topAnchor.constraint(equalTo: scrollView.frameLayoutGuide.topAnchor), itemsStackView.bottomAnchor.constraint(equalTo: scrollView.frameLayoutGuide.bottomAnchor), ]) - + directionStackView&>.do { $0.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ @@ -266,7 +251,7 @@ final class PhotosCropAspectRatioControl: PixelEditorCodeBasedView { $0.centerXAnchor.constraint(equalTo: centerXAnchor), ]) } - + scrollView&>.do { $0.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ @@ -278,189 +263,190 @@ final class PhotosCropAspectRatioControl: PixelEditorCodeBasedView { $0.contentInset = .init(top: 0, left: 24, bottom: 0, right: 24) $0.showsHorizontalScrollIndicator = false } - - store.sinkState { [weak self] state in - - guard let self = self else { return } - - state.ifChanged(\.selectedAspectRatio).do { selected in - - guard let selected = selected else { - // Freeform - - buttons.forEach { - $0.value.isSelected = false + + let subscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } + + if self._previousSelectedAspectRatio != self.selectedAspectRatio { + self._previousSelectedAspectRatio = self.selectedAspectRatio + let selected = self.selectedAspectRatio + + guard let selected = selected else { + // Freeform + + self.buttons.forEach { + $0.value.isSelected = false + } + + self.freeformButton.isSelected = true + + self.handlers.didSelectFreeform() + + return } - - self.freeformButton.isSelected = true - - self.handlers.didSelectFreeform() - - return - } - - self.freeformButton.isSelected = false - - buttons.forEach { - if $0.key == ratioValue(from: selected) { - $0.value.isSelected = true - } else { - $0.value.isSelected = false + + self.freeformButton.isSelected = false + + self.buttons.forEach { + if $0.key == ratioValue(from: selected) { + $0.value.isSelected = true + } else { + $0.value.isSelected = false + } } + + self.handlers.didSelectAspectRatio(selected) } - - self.handlers.didSelectAspectRatio(selected) - - } - - state.ifChanged(\.canSelectDirection).do { canSelectDirection in - self.horizontalButton.isEnabled = canSelectDirection - self.verticalButton.isEnabled = canSelectDirection - } - - state.ifChanged(\.direction).do { direction in - - /// Changes display according to image's rectangle direction. - switch direction { - case .horizontal: - - self.horizontalButton.isSelected = true - self.verticalButton.isSelected = false - - state.horizontalRectangleApectRatios.forEach { ratio in - buttons[ratioValue(from: ratio)]?.setTitle("\(Int(ratio.width)):\(Int(ratio.height))", for: .normal) - } - - case .vertical: - - self.horizontalButton.isSelected = false - self.verticalButton.isSelected = true - - state.horizontalRectangleApectRatios.forEach { ratio in - buttons[ratioValue(from: ratio)]?.setTitle("\(Int(ratio.height)):\(Int(ratio.width))", for: .normal) - } + if self._previousCanSelectDirection != self.canSelectDirection { + self._previousCanSelectDirection = self.canSelectDirection + let canSelectDirection = self.canSelectDirection + + self.horizontalButton.isEnabled = canSelectDirection + self.verticalButton.isEnabled = canSelectDirection } + if self._previousDirection != self.direction { + self._previousDirection = self.direction + let direction = self.direction + + /// Changes display according to image's rectangle direction. + switch direction { + case .horizontal: + + self.horizontalButton.isSelected = true + self.verticalButton.isSelected = false + + self.horizontalRectangleApectRatios.forEach { ratio in + self.buttons[ratioValue(from: ratio)]?.setTitle("\(Int(ratio.width)):\(Int(ratio.height))", for: .normal) + } + + case .vertical: + + self.horizontalButton.isSelected = false + self.verticalButton.isSelected = true + + self.horizontalRectangleApectRatios.forEach { ratio in + self.buttons[ratioValue(from: ratio)]?.setTitle("\(Int(ratio.height)):\(Int(ratio.width))", for: .normal) + } + } + } } } - .store(in: &subscriptions) + subscriptions.append(subscription) } - + func setSelected(_ aspectRatio: PixelAspectRatio?) { - + guard let fixed = aspectRatio else { - store.commit { - if $0.selectedAspectRatio != nil { - $0.selectedAspectRatio = nil - } + if selectedAspectRatio != nil { + selectedAspectRatio = nil } return } - - store.commit { - if $0.selectedAspectRatio != fixed { - $0.selectedAspectRatio = fixed - } + + if selectedAspectRatio != fixed { + selectedAspectRatio = fixed } } - + } private final class AspectRatioButton: UIButton { - + private let backdropView = UIView() - + override var isSelected: Bool { didSet { setNeedsLayout() } } - + convenience init() { self.init(frame: .zero) - + setTitleColor(.init(white: 1, alpha: 0.5), for: .normal) setTitleColor(.white, for: .selected) titleLabel?.font = .systemFont(ofSize: 12) - + } - + override init(frame: CGRect) { super.init(frame: .zero) - + if #available(iOS 13.0, *) { backdropView.layer.cornerCurve = .continuous } else { // Fallback on earlier versions } - + backdropView.backgroundColor = .init(white: 1, alpha: 0.5) - + titleEdgeInsets = .init(top: -2, left: 6, bottom: -2, right: 6) } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func layoutSubviews() { super.layoutSubviews() - + insertSubview(backdropView, at: 0) backdropView.frame = bounds backdropView.layer.cornerRadius = bounds.height / 2 - + backdropView.isHidden = !isSelected } - + override var intrinsicContentSize: CGSize { let originalContentSize = super.intrinsicContentSize let adjustedWidth = originalContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right let adjustedHeight = originalContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom return CGSize(width: adjustedWidth, height: adjustedHeight) } - + } private final class AspectRatioDirectionButton: UIControl { - + private let shapeLayer = CAShapeLayer() private let iconImageView = UIImageView(image: UIImage(named: "check", in: bundle, compatibleWith: nil)) - + override var isHighlighted: Bool { didSet { guard oldValue != isHighlighted else { return } - + UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in self.alpha = isHighlighted ? 0.4 : 1 } .startAnimation() } } - + override var isEnabled: Bool { didSet { update() } } - + override var isSelected: Bool { didSet { update() } } - - init(direction: PhotosCropAspectRatioControl.State.Direction) { + + init(direction: PhotosCropAspectRatioControl.Direction) { super.init(frame: .zero) - + layer.addSublayer(shapeLayer) iconImageView.tintColor = UIColor(white: 0, alpha: 0.8) - + addSubview(iconImageView) - + switch direction { case .horizontal: iconImageView.translatesAutoresizingMaskIntoConstraints = false @@ -470,7 +456,7 @@ private final class AspectRatioDirectionButton: UIControl { iconImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 6), iconImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -2), ]) - + case .vertical: iconImageView.translatesAutoresizingMaskIntoConstraints = false NSLayoutConstraint.activate([ @@ -479,52 +465,52 @@ private final class AspectRatioDirectionButton: UIControl { iconImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 2), iconImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -6), ]) - + } - + update() } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + override func layoutSubviews() { super.layoutSubviews() shapeLayer.frame = bounds - + let path = UIBezierPath(roundedRect: bounds, cornerRadius: 4) shapeLayer.path = path.cgPath } - + private func update() { - + guard isEnabled else { shapeLayer.strokeColor = UIColor(white: 0.6, alpha: 0.3).cgColor shapeLayer.fillColor = UIColor(white: 0, alpha: 0.3).cgColor iconImageView.isHidden = true return } - + if isSelected { - + shapeLayer.strokeColor = UIColor(white: 0.6, alpha: 1).cgColor shapeLayer.fillColor = UIColor(white: 0.6, alpha: 1).cgColor iconImageView.isHidden = false - + } else { shapeLayer.strokeColor = UIColor(white: 0.6, alpha: 1).cgColor shapeLayer.fillColor = UIColor(white: 0, alpha: 0.6).cgColor iconImageView.isHidden = true - + } - + } - + } extension PixelAspectRatio { - var direction: PhotosCropAspectRatioControl.State.Direction { + var direction: PhotosCropAspectRatioControl.Direction { if height > width { return .vertical } else { diff --git a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift index 4f4270bf..f323b025 100644 --- a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift +++ b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift @@ -22,7 +22,7 @@ import UIKit import SwiftUI -import Verge +import StateGraph #if !COCOAPODS import BrightroomEngine @@ -65,14 +65,10 @@ public final class PhotosCropViewController: UIViewController { public var didCancel: (PhotosCropViewController) -> Void = { _ in } } - private struct State: Equatable { - var isSelectingAspectRatio = false - var options: Options - } - // MARK: - Properties - - private let store: UIStateStore + + private var isSelectingAspectRatio = false + private var options: Options private let cropView: CropView private var aspectRatioControl: PhotosCropAspectRatioControl? @@ -88,8 +84,14 @@ public final class PhotosCropViewController: UIViewController { private let aspectRatioControlLayoutGuide = UILayoutGuide() - private var subscriptions = Set() + private var subscriptions: [Any] = [] private var hasSetupLoadedUICompleted = false + + // Change tracking + private var _previousIsSelectingAspectRatio: Bool? + private var _previousOptions: Options? + private var _previousHasUncommitedChanges: Bool? + private var _previousPreferredAspectRatio: PixelAspectRatio? public var localizedStrings: LocalizedStrings @@ -101,7 +103,7 @@ public final class PhotosCropViewController: UIViewController { localizedStrings: LocalizedStrings = .init() ) { self.localizedStrings = localizedStrings - self.store = .init(initialState: .init(options: options)) + self.options = options self.editingStack = editingStack cropView = .init(editingStack: editingStack) super.init(nibName: nil, bundle: nil) @@ -253,44 +255,48 @@ public final class PhotosCropViewController: UIViewController { view.layoutIfNeeded() } - editingStack.sinkState { [weak self] state in - - guard let self = self else { return } - - if let state = state.mapIfPresent(\.loadedState) { - - /// Loaded - - self.setUpLoadedUI(state: state.primitive) - - state.ifChanged(\.hasUncommitedChanges).do { hasChanges in - self.resetButton.isHidden = !hasChanges + let editingStackSubscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } + + if let loadedState = self.editingStack.loadedState { + + /// Loaded + + self.setUpLoadedUI(state: loadedState) + + let hasChanges = loadedState.hasUncommitedChanges + if self._previousHasUncommitedChanges != hasChanges { + self._previousHasUncommitedChanges = hasChanges + self.resetButton.isHidden = !hasChanges + } + + } else { + + /// Loading + + self.aspectRatioButton.isEnabled = false + self.resetButton.isEnabled = false + self.rotateButton.isEnabled = false + self.doneButton.isEnabled = false + } - - } else { - - /// Loading - - self.aspectRatioButton.isEnabled = false - self.resetButton.isEnabled = false - self.rotateButton.isEnabled = false - self.doneButton.isEnabled = false - } - } - .store(in: &subscriptions) - - store.sinkState { [weak self] state in - guard let self = self else { return } - self.update(with: state) + subscriptions.append(editingStackSubscription) + + let storeSubscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } + self.update() + } } - .store(in: &subscriptions) - + subscriptions.append(storeSubscription) + editingStack.start() } - private func setUpLoadedUI(state: EditingStack.State.Loaded) { + private func setUpLoadedUI(state: EditingStack.Loaded) { guard hasSetupLoadedUICompleted == false else { return @@ -335,28 +341,30 @@ public final class PhotosCropViewController: UIViewController { /// refresh current state UIView.performWithoutAnimation { - update(with: store.state) + update() } - - cropView.store.sinkState { [weak self] (state) in - - guard let self = self else { return } - state.ifChanged(\.preferredAspectRatio).do { ratio in - self.aspectRatioControl?.setSelected(ratio) + let cropViewSubscription = withGraphTracking { [weak self] in + withGraphTrackingGroup { + guard let self = self else { return } + + let ratio = self.cropView.state.preferredAspectRatio + if self._previousPreferredAspectRatio != ratio { + self._previousPreferredAspectRatio = ratio + self.aspectRatioControl?.setSelected(ratio) + } } - } - .store(in: &subscriptions) - + subscriptions.append(cropViewSubscription) + } - private func update(with state: Changes) { - - let options = state.map(\.options) - - options.ifChanged(\.aspectRatioOptions).do { value in - switch value { + private func update() { + + let currentOptions = options + if _previousOptions != currentOptions { + _previousOptions = currentOptions + switch currentOptions.aspectRatioOptions { case .fixed(let aspectRatio): aspectRatioButton.alpha = 0 cropView.setCroppingAspectRatio(aspectRatio) @@ -365,10 +373,12 @@ public final class PhotosCropViewController: UIViewController { cropView.setCroppingAspectRatio(nil) } } - - state.ifChanged(\.isSelectingAspectRatio).do { value in + + let selecting = isSelectingAspectRatio + if _previousIsSelectingAspectRatio != selecting { + _previousIsSelectingAspectRatio = selecting UIViewPropertyAnimator.init(duration: 0.4, dampingRatio: 1) { [self] in - if value { + if selecting { aspectRatioControl?.alpha = 1 aspectRatioButton.tintColor = .systemYellow } else { @@ -378,33 +388,31 @@ public final class PhotosCropViewController: UIViewController { } .startAnimation() } - + } @objc private func handleRotateButton() { - guard let proposedCrop = cropView.store.state.proposedCrop else { + guard let proposedCrop = cropView.state.proposedCrop else { return } let rotation = proposedCrop.rotation.next() cropView.setRotation(rotation) - if let ratio = cropView.store.state.preferredAspectRatio { + if let ratio = cropView.state.preferredAspectRatio { cropView.setCroppingAspectRatio(ratio.swapped()) } else { // CropView update automatically } } - + @objc private func handleAspectRatioButton() { - store.commit { - $0.isSelectingAspectRatio.toggle() - } + isSelectingAspectRatio.toggle() } - + @objc private func handleResetButton() { - switch store.state.options.aspectRatioOptions { + switch options.aspectRatioOptions { case .fixed(let ratio): cropView.setCroppingAspectRatio(ratio) case .selectable: diff --git a/Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift b/Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift index ab341102..c40f45ad 100644 --- a/Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift +++ b/Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift @@ -11,7 +11,7 @@ import BrightroomUI import PrecisionLevelSlider import SwiftUI import UIKit -import Verge +import StateGraph public struct PhotosCropRotating: View { @@ -51,7 +51,7 @@ public struct PhotosCropRotating: View { } } - @StateObject var editingStack: EditingStack + let editingStack: EditingStack @State private var rotation: EditingCrop.Rotation? @State private var adjustmentAngle: EditingCrop.AdjustmentAngle? @@ -60,18 +60,18 @@ public struct PhotosCropRotating: View { @State private var canReset: Bool = false @State private var isFocusingAspectRatio: Bool = false - @State private var cropViewState: Changes? + @State private var cropViewState: CropView.State? @State var reset: SwiftUICropView.ResetAction = .init() public init( - editingStack: @escaping () -> EditingStack + editingStack: EditingStack ) { - self._editingStack = .init(wrappedValue: editingStack()) + self.editingStack = editingStack } private var isLoading: Bool { - editingStack.state.isLoading + editingStack.isLoading } public var body: some View { @@ -110,7 +110,7 @@ public struct PhotosCropRotating: View { Spacer() - if editingStack.state.loadedState?.hasUncommitedChanges ?? false { + if editingStack.loadedState?.hasUncommitedChanges ?? false { Button { reset() } label: { @@ -145,7 +145,7 @@ public struct PhotosCropRotating: View { SwiftUICropView( editingStack: editingStack, isAutoApplyEditingStackEnabled: true, - stateHandler: { state in + stateHandler: { state, _ in Task { @MainActor in @@ -280,7 +280,7 @@ public struct PhotosCropRotating: View { let isDirectionButtonDisabled: Bool = { - guard let _ = editingStack.state.loadedState else { + guard let _ = editingStack.loadedState else { return true } @@ -366,7 +366,7 @@ public struct PhotosCropRotating: View { Group { - let sourceDirection = editingStack.state.loadedState.map({ Direction(.init($0.imageSize)) }) + let sourceDirection = editingStack.loadedState.map({ Direction(.init($0.imageSize)) }) let direction = croppingAspectRatio.map({ Direction($0) }) ?? sourceDirection switch sourceDirection { @@ -379,9 +379,9 @@ public struct PhotosCropRotating: View { case .vertical: AspectRationButton( title: Text("ORIGINAL"), - isSelected: croppingAspectRatio == editingStack.state.loadedState.map { .init($0.imageSize) } + isSelected: croppingAspectRatio == editingStack.loadedState.map { .init($0.imageSize) } ) { - guard let imageSize = editingStack.state.loadedState?.imageSize else { + guard let imageSize = editingStack.loadedState?.imageSize else { return } croppingAspectRatio = PixelAspectRatio.init(imageSize) @@ -389,9 +389,9 @@ public struct PhotosCropRotating: View { case .horizontal: AspectRationButton( title: Text("ORIGINAL"), - isSelected: croppingAspectRatio == editingStack.state.loadedState.map { .init($0.imageSize).swapped() } + isSelected: croppingAspectRatio == editingStack.loadedState.map { .init($0.imageSize).swapped() } ) { - guard let imageSize = editingStack.state.loadedState?.imageSize else { + guard let imageSize = editingStack.loadedState?.imageSize else { return } croppingAspectRatio = PixelAspectRatio.init(imageSize).swapped() @@ -404,9 +404,9 @@ public struct PhotosCropRotating: View { case .vertical: AspectRationButton( title: Text("ORIGINAL"), - isSelected: croppingAspectRatio == editingStack.state.loadedState.map { .init($0.imageSize).swapped() } + isSelected: croppingAspectRatio == editingStack.loadedState.map { .init($0.imageSize).swapped() } ) { - guard let imageSize = editingStack.state.loadedState?.imageSize else { + guard let imageSize = editingStack.loadedState?.imageSize else { return } croppingAspectRatio = PixelAspectRatio.init(imageSize).swapped() @@ -414,9 +414,9 @@ public struct PhotosCropRotating: View { case .horizontal: AspectRationButton( title: Text("ORIGINAL"), - isSelected: croppingAspectRatio == editingStack.state.loadedState.map { .init($0.imageSize) } + isSelected: croppingAspectRatio == editingStack.loadedState.map { .init($0.imageSize) } ) { - guard let imageSize = editingStack.state.loadedState?.imageSize else { + guard let imageSize = editingStack.loadedState?.imageSize else { return } croppingAspectRatio = PixelAspectRatio.init(imageSize) diff --git a/docs/MIGRATION_VERGE_TO_STATEGRAPH.md b/docs/MIGRATION_VERGE_TO_STATEGRAPH.md new file mode 100644 index 00000000..c8703048 --- /dev/null +++ b/docs/MIGRATION_VERGE_TO_STATEGRAPH.md @@ -0,0 +1,286 @@ +# Migration Plan: Verge → swift-state-graph + +## Overview + +Migrate Brightroom's state management from Verge to [swift-state-graph](https://github.com/VergeGroup/swift-state-graph). + +- **Files to migrate**: 29 files +- **Modules**: BrightroomEngine, BrightroomUI, BrightroomUIPhotosCrop +- **swift-state-graph version**: `0.17.0` (exact) + +--- + +## API Mapping + +| Verge | swift-state-graph | Notes | +|-------|-------------------|-------| +| `Store` | `@GraphStored` properties | No separate container needed | +| `UIStateStore` | `@MainActor` class + `@GraphStored` | For UI-bound state | +| `StoreDriverType` | Remove | Direct property access | +| `.commit { $0.prop = val }` | `object.prop = val` | Direct assignment | +| `.sinkState { }` | `withGraphTracking { }` | Basic observation | +| `.ifChanged(\.keyPath).do { }` | `withGraphTrackingMap { } onChange: { }` | Granular observation | +| `Changes` | N/A | Use `withGraphTrackingMap` | +| `@Edge` | `@GraphStored` | Change-detection property | +| `VergeAnyCancellable` | `AnyCancellable` / Task | Cancellation management | + +--- + +## Migration Pattern Examples + +### Before (Verge) +```swift +open class EditingStack: StoreDriverType { + public let store: Store + + public struct State: Equatable { + public fileprivate(set) var hasStartedEditing = false + public fileprivate(set) var loadedState: Loaded? + } + + func doSomething() { + store.commit { $0.hasStartedEditing = true } + } +} + +// Observation +editingStack.sinkState { state in + state.ifChanged(\.loadedState).do { loaded in + updateUI(loaded) + } +}.store(in: &subscriptions) +``` + +### After (swift-state-graph) +```swift +open class EditingStack: Hashable { + @GraphStored public var hasStartedEditing = false + @GraphStored public var loadedState: Loaded? + + func doSomething() { + hasStartedEditing = true // Direct assignment + } +} + +// Observation +subscription = withGraphTracking { + withGraphTrackingMap { + editingStack.loadedState + } onChange: { [weak self] loaded in + self?.updateUI(loaded) + } +} +``` + +--- + +## EditingStack Isolation Design + +```swift +// Maintain current behavior: nonisolated nonsendable +open class EditingStack: Hashable { + // Keep DispatchQueue-based concurrency + private let backgroundQueue = DispatchQueue(...) + + @GraphStored public var hasStartedEditing = false + @GraphStored public var loadedState: Loaded? +} +``` + +**Design decisions**: +- Keep `nonisolated nonsendable` (no change from current) +- No Actor isolation +- Continue using `DispatchQueue`-based concurrency patterns +- Remove `StoreDriverType` protocol conformance + +--- + +## Migration Order + +### Phase 1: BrightroomEngine (Foundation) + +| Order | File | Complexity | +|-------|------|------------| +| 1.1 | `Sources/BrightroomEngine/DataSource/ImageSource.swift` | Low | +| 1.2 | `Sources/BrightroomEngine/DataSource/ImageProvider.swift` | High | +| 1.3 | `Sources/BrightroomEngine/Core/EditingStack.swift` | Very High | + +**EditingStack is critical** - All UI components depend on it + +### Phase 2: BrightroomUI Shared Components + +| Order | File | Complexity | +|-------|------|------------| +| 2.1 | `Sources/BrightroomUI/Shared/Components/Crop/CropView.swift` | Very High | +| 2.2 | `Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift` | Medium | +| 2.3 | `Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift` | Medium | +| 2.4 | `Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift` | Medium | +| 2.5 | `Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift` | High | + +### Phase 3: ClassicImageEdit + +| Order | File | Complexity | +|-------|------|------------| +| 3.1 | `ClassicImageEditViewModel.swift` | High | +| 3.2 | `ClassicImageEditControlViewBase.swift` | Medium | +| 3.3 | `ClassicImageEditViewController.swift` | High | +| 3.4 | `ClassicImageEditPresetListControl.swift` | Medium | +| 3.5 | `ClassicImageEditEditMenuControlView.swift` | Medium | +| 3.6-3.16 | FilterControl files (11 files) | Low | + +FilterControls: Exposure, Contrast, Saturation, Highlights, Shadows, Temperature, Fade, Clarity, Sharpen, Vignette, GaussianBlur + +### Phase 4: PhotosCrop + +| Order | File | Complexity | +|-------|------|------------| +| 4.1 | `PhotosCropAspectRatioControl.swift` | Medium | +| 4.2 | `PhotosCropViewController.swift` | High | + +### Phase 5: BrightroomUIPhotosCrop + +| Order | File | Complexity | +|-------|------|------------| +| 5.1 | `PhotosCropRotating.swift` | Medium | + +### Phase 6: Tests + +| Order | File | +|-------|------| +| 6.1 | `Dev/Tests/BrightroomEngineTests/LoadingTests.swift` | +| 6.2 | `Dev/Tests/BrightroomEngineTests/RendererTests.swift` | + +--- + +## Package.swift Changes + +```swift +// Before +.package(url: "https://github.com/VergeGroup/Verge", from: "14.0.0-beta.7"), + +// After +.package(url: "https://github.com/VergeGroup/swift-state-graph", exact: "0.17.0"), + +// Target dependencies +.target(name: "BrightroomEngine", dependencies: ["StateGraph"]), +.target(name: "BrightroomUI", dependencies: ["BrightroomEngine", "StateGraph", "TransitionPatch"]), +``` + +--- + +## Technical Challenges and Solutions + +### 1. Multi-threaded State Management +**Challenge**: EditingStack uses backgroundQueue for image processing while UI observes on main thread + +**Solution**: +- Keep DispatchQueue-based concurrency +- Use `withGraphTracking` callbacks dispatch appropriately +- Separate UI state updates from heavy processing if needed + +### 2. Changes Pattern +**Challenge**: `.ifChanged(\.keyPath).do { }` is heavily used throughout the codebase + +**Solution**: Use `withGraphTrackingMap` for equivalent functionality +```swift +withGraphTrackingMap { editingStack.loadedState?.currentEdit.crop } onChange: { crop in + // Called only when crop changes +} +``` + +### 3. Optional State Unwrapping +**Challenge**: `state.mapIfPresent(\.loadedState)` pattern is common + +**Solution**: Use conditional observation or computed properties +```swift +withGraphTracking { + guard let loaded = editingStack.loadedState else { return } + // Work with loaded state +} +``` + +### 4. Nested State Structs +**Challenge**: Deep nesting like `EditingStack.State.Loaded` + +**Solution**: +- Option 1: Flatten into `@GraphStored` properties +- Option 2: Keep `Loaded` as a struct wrapped in `@GraphStored` + +--- + +## Verification + +### Unit Tests +```bash +cd Dev && xcodebuild test -scheme BrightroomEngineTests -destination 'platform=iOS Simulator,name=iPhone 16' +``` + +### Demo App Verification +```bash +open Dev/Brightroom.xcodeproj +# Run both UIKit Demo and SwiftUI Demo +``` + +### Verification Checklist +- [ ] Image loading (URL, Data, PHAsset, UIImage) +- [ ] Filter application (all 11 filters) +- [ ] Crop operations (rotate, aspect ratio, freeform) +- [ ] Undo/Redo +- [ ] Blur masking drawing +- [ ] Final image rendering +- [ ] No memory leaks + +--- + +## Critical Files + +1. **EditingStack.swift** - Core state container, all UI depends on it +2. **ImageProvider.swift** - Image loading state, uses `@Edge` +3. **CropView.swift** - Most complex UI component, uses `UIStateStore` +4. **ClassicImageEditViewModel.swift** - Uses `assign(to: assignee)` pattern +5. **ClassicImageEditControlViewBase.swift** - Base class for all FilterControls + +--- + +## Migration Progress + +### Completed +- [x] Package.swift - Updated dependency +- [x] ImageProvider.swift - Converted to @GraphStored +- [x] EditingStack.swift - Converted to @GraphStored, kept nonisolated nonsendable +- [x] ImageSource.swift - Removed Verge import +- [x] ImageTool.swift - Updated type references +- [x] CropView.swift - Converted UIStateStore to @GraphStored +- [x] SwiftUICropView.swift - Updated observation patterns +- [x] ImagePreviewView.swift - Converted to @GraphStored +- [x] CanvasView.swift - Converted to @GraphStored +- [x] BlurryMaskingView.swift - Converted to @GraphStored +- [x] ClassicImageEditViewModel.swift - Removed State struct, flattened to @GraphStored properties +- [x] ClassicImageEditControlViewBase.swift - Updated base class +- [x] ClassicImageEditViewController.swift - Updated observation +- [x] ClassicImageEditPresetListControl.swift - Converted +- [x] ClassicImageEditEditMenuControlView.swift - Converted +- [x] ClassicImageEditFilterControlBase.swift - Updated base class +- [x] ClassicImageEditExposureControl.swift - Converted +- [x] ClassicImageEditContrastControl.swift - Converted +- [x] ClassicImageEditSaturationControl.swift - Converted +- [x] ClassicImageEditHighlightsControl.swift - Converted +- [x] ClassicImageEditShadowsControl.swift - Converted +- [x] ClassicImageEditTemperatureControl.swift - Converted +- [x] ClassicImageEditFadeControl.swift - Converted +- [x] ClassicImageEditClarityControl.swift - Converted +- [x] ClassicImageEditSharpenControl.swift - Converted +- [x] ClassicImageEditVignetteControl.swift - Converted +- [x] ClassicImageEditGaussianBlurControl.swift - Converted +- [x] PhotosCropViewController.swift - Converted +- [x] PhotosCropAspectRatioControl.swift - Converted +- [x] PhotosCropRotating.swift - Converted + +### Test Files +- [x] LoadingTests.swift - Updated observation pattern +- [x] RendererTests.swift - Updated import + +### Pending +- [ ] Build verification (requires Xcode macro approval) +- [ ] Run unit tests +- [ ] Demo app testing diff --git a/submodules/muukii/Reveal-SDK b/submodules/muukii/Reveal-SDK index 76b02972..039bf6b6 160000 --- a/submodules/muukii/Reveal-SDK +++ b/submodules/muukii/Reveal-SDK @@ -1 +1 @@ -Subproject commit 76b02972c18f14c717751f10bbc2d6b07dab85c7 +Subproject commit 039bf6b6f75ef67b5a8678b8caaa1b01e6708745 From d9148deac7676385d444584091471ada905cfce3 Mon Sep 17 00:00:00 2001 From: Muukii Date: Sat, 31 Jan 2026 22:32:07 +0900 Subject: [PATCH 02/34] WIP --- CLAUDE.md | 21 +- Dev/Sources/SwiftUIDemo/DemoCropView2.swift | 6 +- .../BrightroomEngine/Core/EditingStack.swift | 17 +- .../DataSource/ImageProvider.swift | 19 +- .../Crop/CropView.CropInsideOverlay.swift | 20 +- .../Crop/CropView.CropOutsideOverlay.swift | 20 +- .../CropView._InteractiveCropGuideView.swift | 2 +- .../Shared/Components/Crop/CropView.swift | 216 ++++++++---------- .../Components/Crop/SwiftUICropView.swift | 16 +- .../Drawing/BlurryMaskingView.swift | 82 +++---- .../Components/Drawing/CanvasView.swift | 43 ++-- .../ImageViews/ImagePreviewView.swift | 35 +-- .../ClassicImageEditViewController.swift | 11 +- .../ClassicImageEditControlViewBase.swift | 7 +- .../ClassicImageEditPresetListControl.swift | 11 +- .../PhotosCropAspectRatioControl.swift | 7 +- .../PhotosCrop/PhotosCropViewController.swift | 15 +- .../PhotosCropRotating.swift | 4 +- 18 files changed, 243 insertions(+), 309 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 807b8605..c39ce86a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,19 +8,24 @@ Brightroom is a composable image editor library for iOS, powered by Metal for hi ## Build Commands -### Running Demo Apps +### Building via Xcode Project (Recommended) ```bash -# Open the development workspace -open Dev/Brightroom.xcodeproj +# Build BrightroomUI (includes BrightroomEngine) +cd Dev && xcodebuild -scheme BrightroomUI -destination 'platform=iOS Simulator,name=iPhone 15 Pro' build + +# Build Demo app +cd Dev && xcodebuild -scheme Demo -destination 'platform=iOS Simulator,name=iPhone 15 Pro' build -# Build using Fastlane -fastlane ios build_demo_apps +# Build SwiftUI Demo +cd Dev && xcodebuild -scheme SwiftUIDemo -destination 'platform=iOS Simulator,name=iPhone 15 Pro' build ``` -### Building the Package +Note: `swift build` does not work due to macOS version constraints. Always use `xcodebuild` with the Dev/Brightroom.xcodeproj. + +### Running Demo Apps ```bash -# Build the Swift package -swift build +# Open the development workspace +open Dev/Brightroom.xcodeproj ``` ## Architecture diff --git a/Dev/Sources/SwiftUIDemo/DemoCropView2.swift b/Dev/Sources/SwiftUIDemo/DemoCropView2.swift index af51b9c7..d9056b60 100644 --- a/Dev/Sources/SwiftUIDemo/DemoCropView2.swift +++ b/Dev/Sources/SwiftUIDemo/DemoCropView2.swift @@ -21,6 +21,7 @@ struct DemoCropView2: View { @State var angle: EditingCrop.AdjustmentAngle = .zero @State var baselineAngle: EditingCrop.AdjustmentAngle = .zero @State var isDragging: Bool = false + @State var previousAdjustmentKind: CropView.StateModel.AdjustmentKind? init( editingStack: EditingStack @@ -56,13 +57,14 @@ struct DemoCropView2: View { Rectangle() .fill(kind == nil ? Color.white : Color.white.opacity(0.6)) }, - stateHandler: { state, previous in - if state.adjustmentKind != previous?.adjustmentKind { + stateHandler: { state in + if state.adjustmentKind != previousAdjustmentKind { if state.adjustmentKind.isEmpty { isDragging = false } else { isDragging = true } + previousAdjustmentKind = state.adjustmentKind } } ) diff --git a/Sources/BrightroomEngine/Core/EditingStack.swift b/Sources/BrightroomEngine/Core/EditingStack.swift index b6b3a5a3..fd855c8d 100644 --- a/Sources/BrightroomEngine/Core/EditingStack.swift +++ b/Sources/BrightroomEngine/Core/EditingStack.swift @@ -23,6 +23,7 @@ import CoreImage import MetalKit import SwiftUI import UIKit +import Combine import StateGraph public enum EditingStackError: Error, Sendable { @@ -209,7 +210,7 @@ open class EditingStack: Hashable { private let filterPresets: [FilterPreset] - private var subscriptions: [Any] = [] + private var subscriptions: Set = .init() private var imageProviderSubscription: Any? public var cropModifier: CropModifier @@ -277,7 +278,7 @@ open class EditingStack: Hashable { hasStartedEditing = true // Set up state observation on background queue - let subscription = withGraphTracking { [weak self] in + withGraphTracking { [weak self] in withGraphTrackingGroup { guard let self = self else { return } self.backgroundQueue.async { @@ -285,7 +286,7 @@ open class EditingStack: Hashable { } } } - subscriptions.append(subscription) + .store(in: &subscriptions) /** Start downloading image @@ -296,15 +297,13 @@ open class EditingStack: Hashable { } // Observe image provider's loaded image - let imageProviderSub = withGraphTracking { [weak self] in - withGraphTrackingMap { - self?.imageProvider.loadedImage - } onChange: { [weak self] image in - guard let self = self, let image = image else { return } + let imageProviderSub = withGraphTracking { + withGraphTrackingMap(from: self, map: { $0.imageProvider.loadedImage }, onChange: { [weak self] image in + guard let self, let image else { return } self.backgroundQueue.async { self.handleImageLoaded(image: image, onPreparationCompleted: onPreparationCompleted) } - } + }) } imageProviderSubscription = imageProviderSub } diff --git a/Sources/BrightroomEngine/DataSource/ImageProvider.swift b/Sources/BrightroomEngine/DataSource/ImageProvider.swift index 224a522e..e346513a 100644 --- a/Sources/BrightroomEngine/DataSource/ImageProvider.swift +++ b/Sources/BrightroomEngine/DataSource/ImageProvider.swift @@ -20,7 +20,7 @@ // THE SOFTWARE. import UIKit - +import Combine import CoreImage import StateGraph @@ -360,20 +360,3 @@ public final class ImageProvider: Equatable { } } -// MARK: - AnyCancellable - -public final class AnyCancellable { - private let onCancel: () -> Void - - public init(_ onCancel: @escaping () -> Void) { - self.onCancel = onCancel - } - - public func cancel() { - onCancel() - } - - deinit { - onCancel() - } -} diff --git a/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropInsideOverlay.swift b/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropInsideOverlay.swift index 03aa88c6..919df51a 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropInsideOverlay.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropInsideOverlay.swift @@ -118,11 +118,11 @@ extension CropView { super.init(frame: .zero) } - open func didBeginAdjustment(kind: CropView.State.AdjustmentKind) { + open func didBeginAdjustment(kind: CropView.StateModel.AdjustmentKind) { } - open func didEndAdjustment(kind: CropView.State.AdjustmentKind) { + open func didEndAdjustment(kind: CropView.StateModel.AdjustmentKind) { } @@ -134,7 +134,7 @@ extension CropView { private let controller: UIHostingController private let proxy: Proxy - public init(@ViewBuilder content: @escaping (CropView.State.AdjustmentKind?) -> Content) { + public init(@ViewBuilder content: @escaping (CropView.StateModel.AdjustmentKind?) -> Content) { self.proxy = .init() self.controller = .init(rootView: Container(proxy: proxy, content: content)) @@ -147,17 +147,17 @@ extension CropView { AutoLayoutTools.setEdge(controller.view, self) } - open override func didBeginAdjustment(kind: CropView.State.AdjustmentKind) { + open override func didBeginAdjustment(kind: CropView.StateModel.AdjustmentKind) { proxy.activeKind = kind } - open override func didEndAdjustment(kind: CropView.State.AdjustmentKind) { + open override func didEndAdjustment(kind: CropView.StateModel.AdjustmentKind) { proxy.activeKind = nil } private final class Proxy: ObservableObject { - @Published var activeKind: CropView.State.AdjustmentKind? + @Published var activeKind: CropView.StateModel.AdjustmentKind? } @@ -165,11 +165,11 @@ extension CropView { @ObservedObject var proxy: Proxy - private let content: (CropView.State.AdjustmentKind?) -> Content + private let content: (CropView.StateModel.AdjustmentKind?) -> Content public init( proxy: Proxy, - content: @escaping (CropView.State.AdjustmentKind?) -> Content + content: @escaping (CropView.StateModel.AdjustmentKind?) -> Content ) { self.content = content self.proxy = proxy @@ -267,7 +267,7 @@ extension CropView { guideView.alpha = 0 } - public override func didBeginAdjustment(kind: CropView.State.AdjustmentKind) { + public override func didBeginAdjustment(kind: CropView.StateModel.AdjustmentKind) { currentAnimator?.stopAnimation(true) currentAnimator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { [weak self] in self?.guideView.alpha = 1 @@ -276,7 +276,7 @@ extension CropView { } } - public override func didEndAdjustment(kind: CropView.State.AdjustmentKind) { + public override func didEndAdjustment(kind: CropView.StateModel.AdjustmentKind) { currentAnimator?.stopAnimation(true) currentAnimator = UIViewPropertyAnimator(duration: 0.6, dampingRatio: 1) { [weak self] in self?.guideView.alpha = 0 diff --git a/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropOutsideOverlay.swift b/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropOutsideOverlay.swift index 70ab638c..c0d662e5 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropOutsideOverlay.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/CropView.CropOutsideOverlay.swift @@ -13,11 +13,11 @@ extension CropView { open class CropOutsideOverlayBase: PixelEditorCodeBasedView { - open func didBeginAdjustment(kind: CropView.State.AdjustmentKind) { + open func didBeginAdjustment(kind: CropView.StateModel.AdjustmentKind) { } - open func didEndAdjustment(kind: CropView.State.AdjustmentKind) { + open func didEndAdjustment(kind: CropView.StateModel.AdjustmentKind) { } @@ -49,7 +49,7 @@ extension CropView { AutoLayoutTools.setEdge(effectView, self) } - public override func didBeginAdjustment(kind: CropView.State.AdjustmentKind) { + public override func didBeginAdjustment(kind: CropView.StateModel.AdjustmentKind) { if kind == .guide { @@ -62,7 +62,7 @@ extension CropView { } } - public override func didEndAdjustment(kind: CropView.State.AdjustmentKind) { + public override func didEndAdjustment(kind: CropView.StateModel.AdjustmentKind) { if kind == .guide { currentAnimator?.stopAnimation(true) @@ -81,7 +81,7 @@ extension CropView { private let controller: UIHostingController private let proxy: Proxy - public init(@ViewBuilder content: @escaping (CropView.State.AdjustmentKind?) -> Content) { + public init(@ViewBuilder content: @escaping (CropView.StateModel.AdjustmentKind?) -> Content) { self.proxy = .init() self.controller = .init(rootView: Container(proxy: proxy, content: content)) @@ -95,17 +95,17 @@ extension CropView { AutoLayoutTools.setEdge(controller.view, self) } - open override func didBeginAdjustment(kind: CropView.State.AdjustmentKind) { + open override func didBeginAdjustment(kind: CropView.StateModel.AdjustmentKind) { proxy.activeKind = kind } - open override func didEndAdjustment(kind: CropView.State.AdjustmentKind) { + open override func didEndAdjustment(kind: CropView.StateModel.AdjustmentKind) { proxy.activeKind = nil } private final class Proxy: ObservableObject { - @Published var activeKind: CropView.State.AdjustmentKind? + @Published var activeKind: CropView.StateModel.AdjustmentKind? } @@ -113,11 +113,11 @@ extension CropView { @ObservedObject var proxy: Proxy - private let content: (CropView.State.AdjustmentKind?) -> Content + private let content: (CropView.StateModel.AdjustmentKind?) -> Content public init( proxy: Proxy, - content: @escaping (CropView.State.AdjustmentKind?) -> Content + content: @escaping (CropView.StateModel.AdjustmentKind?) -> Content ) { self.content = content self.proxy = proxy diff --git a/Sources/BrightroomUI/Shared/Components/Crop/CropView._InteractiveCropGuideView.swift b/Sources/BrightroomUI/Shared/Components/Crop/CropView._InteractiveCropGuideView.swift index 23b37798..7ca7d368 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/CropView._InteractiveCropGuideView.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/CropView._InteractiveCropGuideView.swift @@ -48,7 +48,7 @@ extension CropView { var updating: () -> Void = {} var didChange: () -> Void = {} - var didUpdateAdjustmentKind: (CropView.State.AdjustmentKind) -> Void = { _ in } + var didUpdateAdjustmentKind: (CropView.StateModel.AdjustmentKind) -> Void = { _ in } private let topLeftControlPointView = TapExpandedView(horizontal: 16, vertical: 16) private let topRightControlPointView = TapExpandedView(horizontal: 16, vertical: 16) diff --git a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift index 57908579..5ff0b078 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift @@ -22,6 +22,7 @@ import CoreImage import SwiftUI import UIKit +import Combine import StateGraph #if !COCOAPODS @@ -38,9 +39,9 @@ import BrightroomEngine /// - Implicit animations occurs in first time load with remote image. public final class CropView: UIView, UIScrollViewDelegate { - public struct State: Equatable { + public final class StateModel { - public struct AdjustmentKind: OptionSet { + public struct AdjustmentKind: OptionSet, Equatable { public var rawValue: Int = 0 @@ -53,19 +54,19 @@ public final class CropView: UIView, UIScrollViewDelegate { } - public fileprivate(set) var proposedCrop: EditingCrop? + @GraphStored public var proposedCrop: EditingCrop? = nil - public fileprivate(set) var frame: CGRect = .zero + @GraphStored public var frame: CGRect = .zero - public fileprivate(set) var adjustmentKind: AdjustmentKind = [] - - fileprivate var layoutVersion: UInt64 = 0 + @GraphStored public var adjustmentKind: AdjustmentKind = [] /** Returns aspect ratio. Would not be affected by rotation. */ - public fileprivate(set) var preferredAspectRatio: PixelAspectRatio? + @GraphStored public var preferredAspectRatio: PixelAspectRatio? = nil + + public init() {} } /** @@ -73,7 +74,9 @@ public final class CropView: UIView, UIScrollViewDelegate { */ public private(set) weak var cropOutsideOverlay: UIView? - @GraphStored public var state: State = .init() + public let state: StateModel = .init() + + @GraphStored private var layoutVersion: UInt64 = 0 /** A Boolean value that indicates whether the guide is interactive. @@ -93,7 +96,7 @@ public final class CropView: UIView, UIScrollViewDelegate { */ public var clipsToGuide: Bool = false { didSet { - state.layoutVersion += 1 + layoutVersion += 1 } } @@ -110,7 +113,7 @@ public final class CropView: UIView, UIScrollViewDelegate { public var isZoomEnabled: Bool = true { didSet { - state.layoutVersion += 1 + layoutVersion += 1 } } @@ -199,7 +202,7 @@ public final class CropView: UIView, UIScrollViewDelegate { return view }() - private var subscriptions: [Any] = [] + private var subscriptions: Set = .init() /// A throttling timer to apply guide changed event. /// @@ -213,14 +216,14 @@ public final class CropView: UIView, UIScrollViewDelegate { private var isBinding = false - private var stateHandler: @MainActor (State, State?) -> Void = { _, _ in } + private var stateHandler: @MainActor (StateModel) -> Void = { _ in } var isAutoApplyEditingStackEnabled = false // Change tracking for state - private var _previousState: State? - private var _previousLoadedState: EditingStack.Loaded? - private var _previousIsLoading: Bool? + private var _previousFrame: CGRect? + private var _previousProposedCrop: EditingCrop? + private var _previousLayoutVersion: UInt64 = 0 // MARK: - Initializers @@ -295,17 +298,19 @@ public final class CropView: UIView, UIScrollViewDelegate { }) } - // Set up state observation - let stateSubscription = withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self else { return } - let currentState = self.state - let previousState = self._previousState - self._previousState = currentState - self.stateHandler(currentState, previousState) - } + // Set up state observation for external handler + withGraphTracking { [weak self] in + guard let self else { return } + self.stateHandler(self.state) + } + .store(in: &subscriptions) + + // Set up layout version observation for internal layout updates + withGraphTracking { [weak self] in + guard let self else { return } + self.handleLocalStateChange() } - subscriptions.append(stateSubscription) + .store(in: &subscriptions) } @available(*, unavailable) @@ -315,7 +320,7 @@ public final class CropView: UIView, UIScrollViewDelegate { // MARK: - Functions - func setStateHandler(_ handler: @escaping @MainActor (State, State?) -> Void) { + func setStateHandler(_ handler: @escaping @MainActor (StateModel) -> Void) { self.stateHandler = handler } @@ -332,22 +337,25 @@ public final class CropView: UIView, UIScrollViewDelegate { editingStack.start() binding: do { - let localSubscription = withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self else { return } - self.handleLocalStateChange() - } - } - subscriptions.append(localSubscription) - // To restore current crop from editing-stack - let editingStackSubscription = withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self else { return } - self.handleEditingStackStateChange() - } + withGraphTracking { + // To restore current crop from editing-stack + withGraphTrackingMap(from: self, map: { $0.editingStack.loadedState?.imageForCrop }, onChange: { [weak self] image in + if let image { + self?.setImage(image) + } + }) + withGraphTrackingMap(from: self, map: { $0.editingStack.loadedState?.currentEdit.crop }, onChange: { [weak self] crop in + if let crop { + self?.setCrop(crop) + } + }) + withGraphTrackingMap(from: self, map: { $0.editingStack.isLoading }, onChange: { [weak self] isLoading in + self?.updateLoadingState(displays: isLoading) + }) } - subscriptions.append(editingStackSubscription) + .store(in: &subscriptions) + } } @@ -355,19 +363,31 @@ public final class CropView: UIView, UIScrollViewDelegate { } private func handleLocalStateChange() { - let currentState = state - let previousState = _previousState + // Access @GraphStored properties first to ensure tracking is set up + let currentFrame = state.frame + let currentLayoutVersion = layoutVersion + let currentProposedCrop = state.proposedCrop + + let previousFrame = _previousFrame + let previousLayoutVersion = _previousLayoutVersion + let previousProposedCrop = _previousProposedCrop + defer { + _previousFrame = currentFrame + _previousLayoutVersion = currentLayoutVersion + _previousProposedCrop = currentProposedCrop + } - // Check if frame or layoutVersion changed - let frameChanged = previousState?.frame != currentState.frame - let layoutVersionChanged = previousState?.layoutVersion != currentState.layoutVersion + // Check if frame, layoutVersion, or proposedCrop changed + let frameChanged = previousFrame != currentFrame + let layoutVersionChanged = previousLayoutVersion != currentLayoutVersion + let proposedCropChanged = previousProposedCrop != currentProposedCrop - if frameChanged || layoutVersionChanged { - guard let crop = currentState.proposedCrop else { + if frameChanged || layoutVersionChanged || proposedCropChanged { + guard let crop = currentProposedCrop else { return } - guard currentState.frame != .zero else { + guard currentFrame != .zero else { return } @@ -398,48 +418,19 @@ public final class CropView: UIView, UIScrollViewDelegate { self.updateScrollContainerView( by: crop, - preferredAspectRatio: currentState.preferredAspectRatio, - animated: areAnimationsEnabled && previousState?.proposedCrop != nil /* whether first time load */, - animatesRotation: previousState?.proposedCrop?.rotation != currentState.proposedCrop?.rotation + preferredAspectRatio: state.preferredAspectRatio, + animated: areAnimationsEnabled && previousProposedCrop != nil /* whether first time load */, + animatesRotation: previousProposedCrop?.rotation != currentProposedCrop?.rotation ) } if self.isAutoApplyEditingStackEnabled { - if previousState?.proposedCrop != currentState.proposedCrop { - if let crop = currentState.proposedCrop { + if proposedCropChanged { + if let crop = currentProposedCrop { self.editingStack.crop(crop) } } } - - _previousState = currentState - } - - private func handleEditingStackStateChange() { - let loadedState = editingStack.loadedState - let isLoading = editingStack.isLoading - - // Check imageForCrop changed - if _previousLoadedState?.imageForCrop !== loadedState?.imageForCrop { - if let image = loadedState?.imageForCrop { - self.setImage(image) - } - } - - // Check crop changed - if _previousLoadedState?.currentEdit.crop != loadedState?.currentEdit.crop { - if let crop = loadedState?.currentEdit.crop { - self.setCrop(crop) - } - } - - // Check isLoading changed - if _previousIsLoading != isLoading { - self.updateLoadingState(displays: isLoading) - } - - _previousLoadedState = loadedState - _previousIsLoading = isLoading } private func updateLoadingState(displays: Bool) { @@ -508,7 +499,6 @@ public final class CropView: UIView, UIScrollViewDelegate { if let proposedCrop = state.proposedCrop { state.proposedCrop = proposedCrop.makeInitial() state.preferredAspectRatio = nil - state.layoutVersion += 1 } guideView.setLockedAspectRatio(nil) @@ -517,28 +507,24 @@ public final class CropView: UIView, UIScrollViewDelegate { public func setRotation(_ rotation: EditingCrop.Rotation) { _pixeleditor_ensureMainThread() - guard state.proposedCrop?.rotation != rotation else { + guard var crop = state.proposedCrop, crop.rotation != rotation else { return } - if let crop = state.proposedCrop { - state.proposedCrop?.updateCropExtent( - crop.cropExtent.rotated((crop.rotation.angle - rotation.angle).radians) - ) - state.proposedCrop?.rotation = rotation - } - - state.layoutVersion += 1 + crop.updateCropExtent( + crop.cropExtent.rotated((crop.rotation.angle - rotation.angle).radians) + ) + crop.rotation = rotation + state.proposedCrop = crop } public func setAdjustmentAngle(_ angle: EditingCrop.AdjustmentAngle) { - - guard state.proposedCrop?.adjustmentAngle != angle else { + guard var crop = state.proposedCrop, crop.adjustmentAngle != angle else { return } - state.proposedCrop?.adjustmentAngle = angle - state.layoutVersion += 1 + crop.adjustmentAngle = angle + state.proposedCrop = crop record() } @@ -554,7 +540,6 @@ public final class CropView: UIView, UIScrollViewDelegate { if let ratio = state.preferredAspectRatio { state.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) } - state.layoutVersion += 1 } public func setCroppingAspectRatio(_ ratio: PixelAspectRatio?) { @@ -570,7 +555,6 @@ public final class CropView: UIView, UIScrollViewDelegate { } else { state.proposedCrop?.purgeAspectRatio() } - state.layoutVersion += 1 guideView.setLockedAspectRatio(ratio) } @@ -589,15 +573,14 @@ public final class CropView: UIView, UIScrollViewDelegate { } public func swapCropRectangleDirection() { - - guard let crop = state.proposedCrop else { + guard var crop = state.proposedCrop else { return } - state.proposedCrop?.updateCropExtentIfNeeded( + crop.updateCropExtentIfNeeded( toFitAspectRatio: PixelAspectRatio(crop.cropExtent.size).swapped() ) - state.layoutVersion += 1 + state.proposedCrop = crop } /** @@ -639,12 +622,16 @@ public final class CropView: UIView, UIScrollViewDelegate { // MARK: Internal extension CropView { - private func setImage(_ cgImage: CGImage) { - imagePlatterView.image = UIImage( + private func setImage(_ cgImage: CGImage) { + setImage(uiImage: UIImage( cgImage: cgImage, scale: 1, orientation: .up - ) + )) + } + + private func setImage(uiImage: UIImage) { + imagePlatterView.image = uiImage } override public func layoutSubviews() { @@ -902,22 +889,13 @@ extension CropView { @inline(__always) private func didChangeGuideViewWithDelay() { - - guard state.proposedCrop != nil else { + guard let crop = state.proposedCrop else { return } record() - updateScrollViewInset(crop: state.proposedCrop!) - - /// Triggers layout update later - debounce.on { [weak self] in - - guard let self = self else { return } - - self.state.layoutVersion += 1 - } + updateScrollViewInset(crop: crop) } private func record() { @@ -1012,7 +990,7 @@ extension CropView { guard let self = self else { return } - self.state.layoutVersion += 1 + self.layoutVersion += 1 } } @@ -1028,7 +1006,7 @@ extension CropView { return } - self.state.layoutVersion += 1 + self.layoutVersion += 1 } } diff --git a/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift b/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift index 0f5675ec..c2560fa5 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift @@ -69,8 +69,8 @@ public struct SwiftUICropView: UIViewControllerRepresentable { public typealias UIViewControllerType = _PixelEditor_WrapperViewController - private let cropInsideOverlay: ((CropView.State.AdjustmentKind?) -> AnyView)? - private let cropOutsideOverlay: ((CropView.State.AdjustmentKind?) -> AnyView)? + private let cropInsideOverlay: ((CropView.StateModel.AdjustmentKind?) -> AnyView)? + private let cropOutsideOverlay: ((CropView.StateModel.AdjustmentKind?) -> AnyView)? private let editingStack: EditingStack @@ -79,7 +79,7 @@ public struct SwiftUICropView: UIViewControllerRepresentable { private var _croppingAspectRatio: PixelAspectRatio? private var _resetAction: ResetAction? - private let stateHandler: @MainActor (CropView.State, CropView.State?) -> Void + private let stateHandler: @MainActor (CropView.StateModel) -> Void private let isGuideInteractionEnabled: Bool private let isAutoApplyEditingStackEnabled: Bool private let areAnimationsEnabled: Bool @@ -91,9 +91,9 @@ public struct SwiftUICropView: UIViewControllerRepresentable { isAutoApplyEditingStackEnabled: Bool = false, areAnimationsEnabled: Bool = true, contentInset: UIEdgeInsets? = nil, - @ViewBuilder cropInsideOverlay: @escaping (CropView.State.AdjustmentKind?) -> InsideOverlay, - @ViewBuilder cropOutsideOverlay: @escaping (CropView.State.AdjustmentKind?) -> OutsideOverlay, - stateHandler: @escaping @MainActor (CropView.State, CropView.State?) -> Void = { _, _ in } + @ViewBuilder cropInsideOverlay: @escaping (CropView.StateModel.AdjustmentKind?) -> InsideOverlay, + @ViewBuilder cropOutsideOverlay: @escaping (CropView.StateModel.AdjustmentKind?) -> OutsideOverlay, + stateHandler: @escaping @MainActor (CropView.StateModel) -> Void = { _ in } ) { self.editingStack = editingStack self.isGuideInteractionEnabled = isGuideInteractionEnabled @@ -111,7 +111,7 @@ public struct SwiftUICropView: UIViewControllerRepresentable { isAutoApplyEditingStackEnabled: Bool = false, areAnimationsEnabled: Bool = true, contentInset: UIEdgeInsets? = nil, - stateHandler: @escaping @MainActor (CropView.State, CropView.State?) -> Void = { _, _ in } + stateHandler: @escaping @MainActor (CropView.StateModel) -> Void = { _ in } ) { self.cropInsideOverlay = nil self.cropOutsideOverlay = nil @@ -135,6 +135,7 @@ public struct SwiftUICropView: UIViewControllerRepresentable { view.isAutoApplyEditingStackEnabled = isAutoApplyEditingStackEnabled view.isGuideInteractionEnabled = isGuideInteractionEnabled view.areAnimationsEnabled = areAnimationsEnabled + view.setStateHandler(stateHandler) if let cropInsideOverlay { view.setCropInsideOverlay(CropView.SwiftUICropInsideOverlay(content: cropInsideOverlay)) @@ -159,7 +160,6 @@ public struct SwiftUICropView: UIViewControllerRepresentable { uiViewController.bodyView.setAdjustmentAngle(_adjustmentAngle) } - uiViewController.bodyView.setStateHandler(stateHandler) uiViewController.bodyView.setCroppingAspectRatio(_croppingAspectRatio) _resetAction?.onCall = { [weak uiViewController] in diff --git a/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift b/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift index b82d2aba..65266e6a 100644 --- a/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift +++ b/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift @@ -24,6 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif +import Combine import StateGraph public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDelegate { @@ -88,7 +89,7 @@ public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDele private let canvasView = CanvasView() - private var subscriptions: [Any] = [] + private var subscriptions: Set = .init() private let editingStack: EditingStack @@ -99,11 +100,6 @@ public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDele private var isBinding = false - // Change tracking - private var _previousCrop: EditingCrop? - private var _previousEditingPreviewImage: CIImage? - private var _previousBlurredMaskPaths: [DrawnPath]? - // MARK: - Initializers public init(editingStack: EditingStack) { @@ -173,34 +169,27 @@ public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDele } } - let subscription = withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self = self else { return } - - if let loadedState = self.editingStack.loadedState { - let cropRect = loadedState.currentEdit.crop - if self._previousCrop != cropRect { - self._previousCrop = cropRect - - // scaling for drawing paths - [self.canvasView, self.drawingView].forEach { view in - view.bounds = .init(origin: .zero, size: cropRect.imageSize) - let scale = Geometry.diagonalRatio(to: cropRect.scrollViewContentSize(), from: cropRect.imageSize) - view.transform = .init(scaleX: scale, y: scale) - view.frame.origin = .zero - } - - /** - To avoid running pending layout operations from User Initiated actions. - */ - if cropRect != self.proposedCrop { - self.proposedCrop = cropRect - } - } + withGraphTracking { + withGraphTrackingMap(from: self, map: { $0.editingStack.loadedState?.currentEdit.crop }, onChange: { [weak self] cropRect in + guard let self, let cropRect else { return } + + // scaling for drawing paths + [self.canvasView, self.drawingView].forEach { view in + view.bounds = .init(origin: .zero, size: cropRect.imageSize) + let scale = Geometry.diagonalRatio(to: cropRect.scrollViewContentSize(), from: cropRect.imageSize) + view.transform = .init(scaleX: scale, y: scale) + view.frame.origin = .zero } - } + + /** + To avoid running pending layout operations from User Initiated actions. + */ + if cropRect != self.proposedCrop { + self.proposedCrop = cropRect + } + }) } - subscriptions.append(subscription) + .store(in: &subscriptions) defaultAppearance: do { setLoadingOverlay(factory: { @@ -220,26 +209,17 @@ public final class BlurryMaskingView: PixelEditorCodeBasedView, UIScrollViewDele editingStack.start() binding: do { - let bindingSubscription = withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self = self else { return } - - if let loadedState = self.editingStack.loadedState { - let previewImage = loadedState.editingPreviewImage - if self._previousEditingPreviewImage !== previewImage { - self._previousEditingPreviewImage = previewImage - self.blurryImageView.display(image: BlurredMask.blur(image: previewImage)) - } - - let paths = loadedState.currentEdit.drawings.blurredMaskPaths - if self._previousBlurredMaskPaths != paths { - self._previousBlurredMaskPaths = paths - self.canvasView.setResolvedDrawnPaths(paths) - } - } - } + withGraphTracking { + withGraphTrackingMap(from: self, map: { $0.editingStack.loadedState?.editingPreviewImage }, onChange: { [weak self] previewImage in + guard let self, let previewImage else { return } + self.blurryImageView.display(image: BlurredMask.blur(image: previewImage)) + }) + withGraphTrackingMap(from: self, map: { $0.editingStack.loadedState?.currentEdit.drawings.blurredMaskPaths }, onChange: { [weak self] paths in + guard let self, let paths else { return } + self.canvasView.setResolvedDrawnPaths(paths) + }) } - subscriptions.append(bindingSubscription) + .store(in: &subscriptions) } } } diff --git a/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift b/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift index 5756893b..43576d31 100644 --- a/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift +++ b/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift @@ -24,6 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif +import Combine import StateGraph public final class CanvasView: PixelEditorCodeBasedView { @@ -42,8 +43,7 @@ public final class CanvasView: PixelEditorCodeBasedView { } @GraphStored private var resolvedDrawnPaths: [DrawnPath] = [] - private var _previousResolvedDrawnPaths: [DrawnPath] = [] - private var subscriptions: [Any] = [] + private var subscriptions: Set = .init() private var resolvedShapeLayers: [CAShapeLayer] = [] private var previewShapeLayer: CAShapeLayer? @@ -56,33 +56,28 @@ public final class CanvasView: PixelEditorCodeBasedView { tiledLayer.tileSize = .init(width: 512, height: 512) } - let subscription = withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self = self else { return } + withGraphTracking { + withGraphTrackingMap(from: self, map: { $0.resolvedDrawnPaths }, onChange: { [weak self] paths in + guard let self else { return } - if self._previousResolvedDrawnPaths != self.resolvedDrawnPaths { - self._previousResolvedDrawnPaths = self.resolvedDrawnPaths - - let paths = self.resolvedDrawnPaths - let layers = paths.map { path -> CAShapeLayer in - let layer = Self.makeShapeLayer(for: path.brush) - layer.path = path.bezierPath.cgPath - return layer - } + let layers = paths.map { path -> CAShapeLayer in + let layer = Self.makeShapeLayer(for: path.brush) + layer.path = path.bezierPath.cgPath + return layer + } - // TODO: Get better way for perfromance - self.resolvedShapeLayers.forEach { - $0.removeFromSuperlayer() - } + // TODO: Get better way for perfromance + self.resolvedShapeLayers.forEach { + $0.removeFromSuperlayer() + } - layers.forEach { - self.layer.addSublayer($0) - } - self.resolvedShapeLayers = layers + layers.forEach { + self.layer.addSublayer($0) } - } + self.resolvedShapeLayers = layers + }) } - subscriptions.append(subscription) + .store(in: &subscriptions) } diff --git a/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift b/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift index fc0d8979..355c2d93 100644 --- a/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift +++ b/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift @@ -23,6 +23,7 @@ import BrightroomEngine #endif import UIKit +import Combine import StateGraph /** @@ -40,7 +41,7 @@ public final class ImagePreviewView: PixelEditorCodeBasedView { #endif private let editingStack: EditingStack - private var subscriptions: [Any] = [] + private var subscriptions: Set = .init() private var loadingOverlayFactory: (() -> UIView)? private weak var currentLoadingOverlay: UIView? @@ -48,10 +49,6 @@ public final class ImagePreviewView: PixelEditorCodeBasedView { private var isBinding = false private var cachedCroppedImage: (state: EditingStack.Loaded, image: CIImage)? = nil - // Change tracking - private var _previousIsLoading: Bool? - private var _previousCurrentEdit: EditingStack.Edit? - // MARK: - Initializers public init(editingStack: EditingStack) { @@ -106,28 +103,18 @@ public final class ImagePreviewView: PixelEditorCodeBasedView { if isBinding == false { isBinding = true - let subscription = withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self = self else { return } - - let isLoading = self.editingStack.isLoading - if self._previousIsLoading != isLoading { - self._previousIsLoading = isLoading - self.updateLoadingOverlay(displays: isLoading) - } - + withGraphTracking { + withGraphTrackingMap(from: self, map: { $0.editingStack.isLoading }, onChange: { [weak self] isLoading in + self?.updateLoadingOverlay(displays: isLoading) + }) + withGraphTrackingMap(from: self, map: { $0.editingStack.loadedState?.currentEdit }, onChange: { [weak self] currentEdit in + guard let self, let loadedState = self.editingStack.loadedState else { return } UIView.performWithoutAnimation { - if let loadedState = self.editingStack.loadedState { - let currentEdit = loadedState.currentEdit - if self._previousCurrentEdit != currentEdit { - self._previousCurrentEdit = currentEdit - self.requestPreviewImage(state: loadedState) - } - } + self.requestPreviewImage(state: loadedState) } - } + }) } - subscriptions.append(subscription) + .store(in: &subscriptions) } } } diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift index ce25dfe8..f9b6834d 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift @@ -21,6 +21,7 @@ import Photos import UIKit +import Combine import StateGraph #if !COCOAPODS @@ -114,7 +115,7 @@ public final class ClassicImageEditViewController: UIViewController { action: #selector(didTapCancelButton) ) - private var subscriptions: [Any] = [] + private var subscriptions: Set = .init() // Change tracking private var _previousTitle: String? @@ -299,15 +300,15 @@ public final class ClassicImageEditViewController: UIViewController { animated: false ) - let subscription = withGraphTracking { [weak self] in + withGraphTracking { [weak self] in withGraphTrackingGroup { guard let self = self else { return } self.updateUI() } } - subscriptions.append(subscription) + .store(in: &subscriptions) - let cropViewSubscription = withGraphTracking { [viewModel, weak cropView] in + withGraphTracking { [viewModel, weak cropView] in withGraphTrackingGroup { guard let cropView = cropView else { return } let proposedCrop = cropView.state.proposedCrop @@ -316,7 +317,7 @@ public final class ClassicImageEditViewController: UIViewController { } } } - subscriptions.append(cropViewSubscription) + .store(in: &subscriptions) viewModel.editingStack.start() } diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift index d77013bb..69c69e72 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift @@ -24,6 +24,7 @@ import UIKit #if !COCOAPODS import BrightroomEngine #endif +import Combine import StateGraph open class ClassicImageEditControlBase : UIView, ClassicImageEditControlChildViewType { @@ -34,19 +35,19 @@ open class ClassicImageEditControlBase : UIView, ClassicImageEditControlChildVie public let viewModel: ClassicImageEditViewModel - private var subscriptions: [Any] = [] + private var subscriptions: Set = .init() public init(viewModel: ClassicImageEditViewModel) { self.viewModel = viewModel super.init(frame: .zero) setup() - let subscription = withGraphTracking { [weak self] in + withGraphTracking { [weak self] in withGraphTrackingGroup { self?.didReceiveCurrentEdit() } } - subscriptions.append(subscription) + .store(in: &subscriptions) } @available(*, unavailable) diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift index 8e47660b..56c502cc 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift @@ -24,6 +24,7 @@ import UIKit import BrightroomEngine #endif +import Combine import StateGraph open class ClassicImageEditPresetListControlBase : ClassicImageEditControlBase { @@ -57,7 +58,7 @@ open class ClassicImageEditPresetListControl: ClassicImageEditPresetListControlB @GraphStored private var content: Content? = nil @GraphStored private var currentSelected: FilterPreset? = nil - private var subscriptions: [Any] = [] + private var subscriptions: Set = .init() // Change tracking private var _previousContent: Content? @@ -74,7 +75,7 @@ open class ClassicImageEditPresetListControl: ClassicImageEditPresetListControlB super.init(viewModel: viewModel) - let viewModelSubscription = withGraphTracking { [weak self] in + withGraphTracking { [weak self] in withGraphTrackingGroup { guard let self = self else { return } @@ -90,9 +91,9 @@ open class ClassicImageEditPresetListControl: ClassicImageEditPresetListControlB } } } - subscriptions.append(viewModelSubscription) + .store(in: &subscriptions) - let storeSubscription = withGraphTracking { [weak self] in + withGraphTracking { [weak self] in withGraphTrackingGroup { guard let self = self else { return } @@ -111,7 +112,7 @@ open class ClassicImageEditPresetListControl: ClassicImageEditPresetListControlB } } } - subscriptions.append(storeSubscription) + .store(in: &subscriptions) } diff --git a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift index 70b01b07..15eeee71 100644 --- a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift +++ b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift @@ -22,6 +22,7 @@ import UIKit import SwiftUI +import Combine import StateGraph #if !COCOAPODS @@ -127,7 +128,7 @@ final class PhotosCropAspectRatioControl: PixelEditorCodeBasedView { private let freeformButton = AspectRatioButton() private let aspectSquareButton = AspectRatioButton() - private var subscriptions: [Any] = [] + private var subscriptions: Set = .init() // Change tracking private var _previousSelectedAspectRatio: PixelAspectRatio?? @@ -264,7 +265,7 @@ final class PhotosCropAspectRatioControl: PixelEditorCodeBasedView { $0.showsHorizontalScrollIndicator = false } - let subscription = withGraphTracking { [weak self] in + withGraphTracking { [weak self] in withGraphTrackingGroup { guard let self = self else { return } @@ -334,7 +335,7 @@ final class PhotosCropAspectRatioControl: PixelEditorCodeBasedView { } } } - subscriptions.append(subscription) + .store(in: &subscriptions) } func setSelected(_ aspectRatio: PixelAspectRatio?) { diff --git a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift index f323b025..3051a10f 100644 --- a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift +++ b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift @@ -22,6 +22,7 @@ import UIKit import SwiftUI +import Combine import StateGraph #if !COCOAPODS @@ -84,7 +85,7 @@ public final class PhotosCropViewController: UIViewController { private let aspectRatioControlLayoutGuide = UILayoutGuide() - private var subscriptions: [Any] = [] + private var subscriptions: Set = .init() private var hasSetupLoadedUICompleted = false // Change tracking @@ -255,7 +256,7 @@ public final class PhotosCropViewController: UIViewController { view.layoutIfNeeded() } - let editingStackSubscription = withGraphTracking { [weak self] in + withGraphTracking { [weak self] in withGraphTrackingGroup { guard let self = self else { return } @@ -283,15 +284,15 @@ public final class PhotosCropViewController: UIViewController { } } } - subscriptions.append(editingStackSubscription) + .store(in: &subscriptions) - let storeSubscription = withGraphTracking { [weak self] in + withGraphTracking { [weak self] in withGraphTrackingGroup { guard let self = self else { return } self.update() } } - subscriptions.append(storeSubscription) + .store(in: &subscriptions) editingStack.start() } @@ -344,7 +345,7 @@ public final class PhotosCropViewController: UIViewController { update() } - let cropViewSubscription = withGraphTracking { [weak self] in + withGraphTracking { [weak self] in withGraphTrackingGroup { guard let self = self else { return } @@ -355,7 +356,7 @@ public final class PhotosCropViewController: UIViewController { } } } - subscriptions.append(cropViewSubscription) + .store(in: &subscriptions) } diff --git a/Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift b/Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift index c40f45ad..b60855ac 100644 --- a/Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift +++ b/Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift @@ -60,7 +60,7 @@ public struct PhotosCropRotating: View { @State private var canReset: Bool = false @State private var isFocusingAspectRatio: Bool = false - @State private var cropViewState: CropView.State? + @State private var cropViewState: CropView.StateModel? @State var reset: SwiftUICropView.ResetAction = .init() @@ -145,7 +145,7 @@ public struct PhotosCropRotating: View { SwiftUICropView( editingStack: editingStack, isAutoApplyEditingStackEnabled: true, - stateHandler: { state, _ in + stateHandler: { state in Task { @MainActor in From 6ef35c3308fa8073878daf668ebb46e975bbf024 Mon Sep 17 00:00:00 2001 From: Muukii Date: Mon, 2 Feb 2026 19:56:11 +0900 Subject: [PATCH 03/34] Update --- .../Shared/Components/Crop/CropView.swift | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift index 5ff0b078..978db047 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift @@ -299,16 +299,19 @@ public final class CropView: UIView, UIScrollViewDelegate { } // Set up state observation for external handler - withGraphTracking { [weak self] in - guard let self else { return } - self.stateHandler(self.state) + withGraphTracking { + withGraphTrackingGroup { [weak self] in + guard let self else { return } + self.stateHandler(self.state) + } } .store(in: &subscriptions) // Set up layout version observation for internal layout updates - withGraphTracking { [weak self] in - guard let self else { return } - self.handleLocalStateChange() + withGraphTracking { + withGraphTrackingGroup { [weak self] in + self?.handleLocalStateChange() + } } .store(in: &subscriptions) } @@ -540,6 +543,7 @@ public final class CropView: UIView, UIScrollViewDelegate { if let ratio = state.preferredAspectRatio { state.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) } + layoutVersion += 1 } public func setCroppingAspectRatio(_ ratio: PixelAspectRatio?) { From 9fcb1b03e54cf63571cfae6e7702ffdcef7ff69c Mon Sep 17 00:00:00 2001 From: Muukii Date: Sun, 17 May 2026 22:15:25 +0900 Subject: [PATCH 04/34] Fix debounced crop layout update --- .../Shared/Components/Crop/CropView.swift | 227 +++++++++--------- 1 file changed, 116 insertions(+), 111 deletions(-) diff --git a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift index 978db047..481bfb6c 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift @@ -76,8 +76,6 @@ public final class CropView: UIView, UIScrollViewDelegate { public let state: StateModel = .init() - @GraphStored private var layoutVersion: UInt64 = 0 - /** A Boolean value that indicates whether the guide is interactive. If false, cropping adjustment is available only way from scrolling image-view. @@ -96,7 +94,7 @@ public final class CropView: UIView, UIScrollViewDelegate { */ public var clipsToGuide: Bool = false { didSet { - layoutVersion += 1 + updateCropLayout() } } @@ -113,7 +111,7 @@ public final class CropView: UIView, UIScrollViewDelegate { public var isZoomEnabled: Bool = true { didSet { - layoutVersion += 1 + updateCropLayout() } } @@ -220,10 +218,7 @@ public final class CropView: UIView, UIScrollViewDelegate { var isAutoApplyEditingStackEnabled = false - // Change tracking for state - private var _previousFrame: CGRect? - private var _previousProposedCrop: EditingCrop? - private var _previousLayoutVersion: UInt64 = 0 + private var lastLaidOutCrop: EditingCrop? // MARK: - Initializers @@ -307,13 +302,6 @@ public final class CropView: UIView, UIScrollViewDelegate { } .store(in: &subscriptions) - // Set up layout version observation for internal layout updates - withGraphTracking { - withGraphTrackingGroup { [weak self] in - self?.handleLocalStateChange() - } - } - .store(in: &subscriptions) } @available(*, unavailable) @@ -365,77 +353,6 @@ public final class CropView: UIView, UIScrollViewDelegate { } - private func handleLocalStateChange() { - // Access @GraphStored properties first to ensure tracking is set up - let currentFrame = state.frame - let currentLayoutVersion = layoutVersion - let currentProposedCrop = state.proposedCrop - - let previousFrame = _previousFrame - let previousLayoutVersion = _previousLayoutVersion - let previousProposedCrop = _previousProposedCrop - defer { - _previousFrame = currentFrame - _previousLayoutVersion = currentLayoutVersion - _previousProposedCrop = currentProposedCrop - } - - // Check if frame, layoutVersion, or proposedCrop changed - let frameChanged = previousFrame != currentFrame - let layoutVersionChanged = previousLayoutVersion != currentLayoutVersion - let proposedCropChanged = previousProposedCrop != currentProposedCrop - - if frameChanged || layoutVersionChanged || proposedCropChanged { - guard let crop = currentProposedCrop else { - return - } - - guard currentFrame != .zero else { - return - } - - setupScrollViewOnce: do { - if self.hasSetupScrollViewCompleted == false { - self.hasSetupScrollViewCompleted = true - - self.imagePlatterView.bounds = .init( - origin: .zero, - size: crop.scrollViewContentSize() - ) - - let scrollView = self.scrollView - - // Do we need this? it seems ImageView's bounds changes contentSize automatically. not sure. - UIView.performWithoutAnimation { - let currentZoomScale = scrollView.zoomScale - let contentSize = crop.scrollViewContentSize() - if scrollView.contentSize != contentSize { - scrollView.contentInset = .zero - scrollView.zoomScale = 1 - scrollView.contentSize = contentSize - scrollView.zoomScale = currentZoomScale - } - } - } - } - - self.updateScrollContainerView( - by: crop, - preferredAspectRatio: state.preferredAspectRatio, - animated: areAnimationsEnabled && previousProposedCrop != nil /* whether first time load */, - animatesRotation: previousProposedCrop?.rotation != currentProposedCrop?.rotation - ) - } - - if self.isAutoApplyEditingStackEnabled { - if proposedCropChanged { - if let crop = currentProposedCrop { - self.editingStack.crop(crop) - } - } - } - } - private func updateLoadingState(displays: Bool) { if displays, let factory = self.loadingOverlayFactory { @@ -499,9 +416,9 @@ public final class CropView: UIView, UIScrollViewDelegate { public func resetCrop() { _pixeleditor_ensureMainThread() - if let proposedCrop = state.proposedCrop { - state.proposedCrop = proposedCrop.makeInitial() + if let crop = state.proposedCrop { state.preferredAspectRatio = nil + setProposedCrop(crop.makeInitial(), previousCrop: crop, forcesLayout: true) } guideView.setLockedAspectRatio(nil) @@ -518,7 +435,7 @@ public final class CropView: UIView, UIScrollViewDelegate { crop.cropExtent.rotated((crop.rotation.angle - rotation.angle).radians) ) crop.rotation = rotation - state.proposedCrop = crop + setProposedCrop(crop) } public func setAdjustmentAngle(_ angle: EditingCrop.AdjustmentAngle) { @@ -527,7 +444,7 @@ public final class CropView: UIView, UIScrollViewDelegate { } crop.adjustmentAngle = angle - state.proposedCrop = crop + setProposedCrop(crop) record() } @@ -535,15 +452,11 @@ public final class CropView: UIView, UIScrollViewDelegate { public func setCrop(_ crop: EditingCrop) { _pixeleditor_ensureMainThread() - guard state.proposedCrop != crop else { - return - } - - state.proposedCrop = crop + var crop = crop if let ratio = state.preferredAspectRatio { - state.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) + crop.updateCropExtentIfNeeded(toFitAspectRatio: ratio) } - layoutVersion += 1 + setProposedCrop(crop) } public func setCroppingAspectRatio(_ ratio: PixelAspectRatio?) { @@ -554,10 +467,16 @@ public final class CropView: UIView, UIScrollViewDelegate { } state.preferredAspectRatio = ratio + var crop = state.proposedCrop if let ratio = ratio { - state.proposedCrop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) + crop?.updateCropExtentIfNeeded(toFitAspectRatio: ratio) } else { - state.proposedCrop?.purgeAspectRatio() + crop?.purgeAspectRatio() + } + if let crop { + setProposedCrop(crop, forcesLayout: true) + } else { + updateCropLayout() } guideView.setLockedAspectRatio(ratio) @@ -584,7 +503,7 @@ public final class CropView: UIView, UIScrollViewDelegate { crop.updateCropExtentIfNeeded( toFitAspectRatio: PixelAspectRatio(crop.cropExtent.size).swapped() ) - state.proposedCrop = crop + setProposedCrop(crop, forcesLayout: true) } /** @@ -626,18 +545,93 @@ public final class CropView: UIView, UIScrollViewDelegate { // MARK: Internal extension CropView { - private func setImage(_ cgImage: CGImage) { + private func setImage(_ cgImage: CGImage) { setImage(uiImage: UIImage( cgImage: cgImage, scale: 1, orientation: .up )) } - + private func setImage(uiImage: UIImage) { imagePlatterView.image = uiImage } + private func setProposedCrop( + _ crop: EditingCrop, + previousCrop: EditingCrop? = nil, + forcesLayout: Bool = false + ) { + let previousCrop = previousCrop ?? state.proposedCrop + let hasChanges = updateProposedCrop(crop) + + guard hasChanges || forcesLayout else { + return + } + + updateCropLayout(previousCrop: previousCrop) + } + + @discardableResult + private func updateProposedCrop(_ crop: EditingCrop) -> Bool { + guard state.proposedCrop != crop else { + return false + } + + state.proposedCrop = crop + + if isAutoApplyEditingStackEnabled { + editingStack.crop(crop) + } + + return true + } + + private func updateCropLayout(previousCrop: EditingCrop? = nil) { + guard let crop = state.proposedCrop else { + return + } + + guard state.frame != .zero else { + return + } + + setupScrollViewOnce: do { + if hasSetupScrollViewCompleted == false { + hasSetupScrollViewCompleted = true + + imagePlatterView.bounds = .init( + origin: .zero, + size: crop.scrollViewContentSize() + ) + + let scrollView = self.scrollView + + // Do we need this? it seems ImageView's bounds changes contentSize automatically. not sure. + UIView.performWithoutAnimation { + let currentZoomScale = scrollView.zoomScale + let contentSize = crop.scrollViewContentSize() + if scrollView.contentSize != contentSize { + scrollView.contentInset = .zero + scrollView.zoomScale = 1 + scrollView.contentSize = contentSize + scrollView.zoomScale = currentZoomScale + } + } + } + } + + let animationSourceCrop = previousCrop ?? lastLaidOutCrop + updateScrollContainerView( + by: crop, + preferredAspectRatio: state.preferredAspectRatio, + animated: areAnimationsEnabled && animationSourceCrop != nil /* whether first time load */, + animatesRotation: animationSourceCrop?.rotation != crop.rotation + ) + + lastLaidOutCrop = crop + } + override public func layoutSubviews() { super.layoutSubviews() @@ -655,8 +649,10 @@ extension CropView { /// to update masking with cropOutsideOverlay guideView.setNeedsLayout() - if state.frame != frame { + let previousFrame = state.frame + if previousFrame != frame { state.frame = frame + updateCropLayout() } #if DEBUG @@ -897,15 +893,22 @@ extension CropView { return } - record() + let recordedCrop = record() ?? crop + + updateScrollViewInset(crop: recordedCrop) - updateScrollViewInset(crop: crop) + /// Triggers layout update later + debounce.on { [weak self] in + guard let self else { return } + self.updateCropLayout() + } } - private func record() { + @discardableResult + private func record() -> EditingCrop? { - guard let crop = state.proposedCrop else { - return + guard var crop = state.proposedCrop else { + return nil } // remove rotation while converting rect @@ -950,9 +953,11 @@ extension CropView { rect: guideRectInImageView ) - state.proposedCrop?.updateCropExtent( + crop.updateCropExtent( resolvedRect ) + updateProposedCrop(crop) + return crop } @inline(__always) @@ -994,7 +999,7 @@ extension CropView { guard let self = self else { return } - self.layoutVersion += 1 + self.updateCropLayout() } } @@ -1010,7 +1015,7 @@ extension CropView { return } - self.layoutVersion += 1 + self.updateCropLayout() } } From 0230584c9d0cb188487d54309ff006e5b0514885 Mon Sep 17 00:00:00 2001 From: Muukii Date: Fri, 22 May 2026 18:03:19 +0900 Subject: [PATCH 05/34] Migrate editor surfaces to SwiftUI --- Dev/Brightroom.xcodeproj/project.pbxproj | 11 +- Dev/Sources/SwiftUIDemo/ContentView.swift | 74 +- Dev/Sources/SwiftUIDemo/DemoCropView.swift | 20 +- Dev/Sources/SwiftUIDemo/DemoCropView2.swift | 1 - .../SwiftUIDemo/RenderingDemoView.swift | 551 +++++++ Package.swift | 6 +- .../ColorCube/ColorCubeHelper.swift | 2 +- .../BrightroomEngine/Core/EditingStack.swift | 158 +- .../Library/EngineSanitizer.swift | 4 +- .../BrightroomUI/Shared/BrightroomUI.swift | 11 +- .../CropView._InteractiveCropGuideView.swift | 2 - .../Shared/Components/Crop/CropView.swift | 47 +- .../Components/Crop/SwiftUICropView.swift | 54 +- .../Drawing/BlurryMaskingView.swift | 2 - .../Components/Drawing/CanvasView.swift | 2 - .../ImageViews/CIImageDisplaying.swift | 5 +- .../ImageViews/ImagePreviewView.swift | 2 - .../ImageViews/MetalImageView.swift | 5 +- .../Components/ImageViews/_ImageView.swift | 2 - .../Shared/Utils/EditingCrop+.swift | 2 - .../ClassicImageEditOptions.swift | 2 - .../ClassicImageEditViewController.swift | 5 +- .../ClassicImageEditViewModel.swift | 2 - .../ClassicImageEditControlStackView.swift | 2 - .../ClassicImageEditControlViewBase.swift | 2 - .../ClassicImageEditEditMenuControlView.swift | 2 - .../ClassicImageEditMaskControlView.swift | 2 - .../ClassicImageEditPresetListControl.swift | 29 +- .../ClassicImageEditStepSlider.swift | 2 - .../ClassicImageEditClarityControl.swift | 2 - .../ClassicImageEditContrastControl.swift | 2 - .../ClassicImageEditExposureControl.swift | 2 - .../ClassicImageEditFadeControl.swift | 2 - .../ClassicImageEditGaussianBlurControl.swift | 2 - .../ClassicImageEditHighlightsControl.swift | 2 - .../ClassicImageEditSaturationControl.swift | 2 - .../ClassicImageEditShadowsControl.swift | 2 - .../ClassicImageEditSharpenControl.swift | 2 - .../ClassicImageEditTemperatureControl.swift | 2 - .../ClassicImageEditVignetteControl.swift | 2 - .../SwiftUIPixelEditorView.swift | 1463 +++++++++++++++++ .../PhotosCropAspectRatioControl.swift | 525 ------ .../PhotosCrop/PhotosCropContentView.swift | 510 ++++++ .../PhotosCrop/PhotosCropViewController.swift | 386 +---- .../PhotosCrop/SwiftUIPhotosCropView.swift | 74 +- .../PhotosCropRotating.swift | 534 ------ 46 files changed, 2863 insertions(+), 1660 deletions(-) create mode 100644 Dev/Sources/SwiftUIDemo/RenderingDemoView.swift create mode 100644 Sources/BrightroomUI/builtin/ClassicImageEdit/SwiftUIPixelEditorView.swift delete mode 100644 Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift create mode 100644 Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropContentView.swift delete mode 100644 Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift diff --git a/Dev/Brightroom.xcodeproj/project.pbxproj b/Dev/Brightroom.xcodeproj/project.pbxproj index 016b165e..77cac756 100644 --- a/Dev/Brightroom.xcodeproj/project.pbxproj +++ b/Dev/Brightroom.xcodeproj/project.pbxproj @@ -52,7 +52,6 @@ 4B98CCC325EFF31400E4F61F /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B98CCC225EFF31400E4F61F /* Preview Assets.xcassets */; }; 4B98CCE725EFF4EF00E4F61F /* Mocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4B98CCE625EFF4EF00E4F61F /* Mocks.swift */; }; 4B98CF5325F0122000E4F61F /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 4B600B29216B7C9E001E1456 /* Assets.xcassets */; }; - 4BA2B1B42B86282D00E6DE64 /* BrightroomUIPhotosCrop in Frameworks */ = {isa = PBXBuildFile; productRef = 4BA2B1B32B86282D00E6DE64 /* BrightroomUIPhotosCrop */; }; 4BA41C8D260CE817005E6FA7 /* orientation_up_mirrored.HEIC in Resources */ = {isa = PBXBuildFile; fileRef = 4B254FEB260BB32600F77E9A /* orientation_up_mirrored.HEIC */; }; 4BA41C8E260CE817005E6FA7 /* orientation_right_mirrored.HEIC in Resources */ = {isa = PBXBuildFile; fileRef = 4B254FE7260BB32600F77E9A /* orientation_right_mirrored.HEIC */; }; 4BA41C8F260CE817005E6FA7 /* orientation_left_mirrored.HEIC in Resources */ = {isa = PBXBuildFile; fileRef = 4B254FE6260BB32600F77E9A /* orientation_left_mirrored.HEIC */; }; @@ -64,6 +63,7 @@ 4BA9896F2B70BF2000EB7983 /* RenderedResultView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA9896E2B70BF2000EB7983 /* RenderedResultView.swift */; }; 4BA989712B70F10300EB7983 /* ObjectEdge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA989702B70F10300EB7983 /* ObjectEdge.swift */; }; 4BA989732B7122C800EB7983 /* DemoMaskingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BA989722B7122C800EB7983 /* DemoMaskingView.swift */; }; + CD1A00022F33A00000000002 /* RenderingDemoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CD1A00012F33A00000000001 /* RenderingDemoView.swift */; }; 4BB7B9CA277A439C0014B62A /* LUT_64_Dark_HighContrast_v3.png in Resources */ = {isa = PBXBuildFile; fileRef = 4BB7B9C8277A439B0014B62A /* LUT_64_Dark_HighContrast_v3.png */; }; 4BB7B9CD277A452A0014B62A /* LUT_64_Gloss.png in Resources */ = {isa = PBXBuildFile; fileRef = 4BB7B9CB277A452A0014B62A /* LUT_64_Gloss.png */; }; 4BB9180C25F5460000C446B8 /* Launch Screen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 4BB9180B25F5460000C446B8 /* Launch Screen.storyboard */; }; @@ -532,6 +532,7 @@ 4BA9896E2B70BF2000EB7983 /* RenderedResultView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderedResultView.swift; sourceTree = ""; }; 4BA989702B70F10300EB7983 /* ObjectEdge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ObjectEdge.swift; sourceTree = ""; }; 4BA989722B7122C800EB7983 /* DemoMaskingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DemoMaskingView.swift; sourceTree = ""; }; + CD1A00012F33A00000000001 /* RenderingDemoView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RenderingDemoView.swift; sourceTree = ""; }; 4BB4B104260F8589007617D7 /* LUT_64_Neutral.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; name = LUT_64_Neutral.png; path = Resources/LUT_64_Neutral.png; sourceTree = SOURCE_ROOT; }; 4BB7B9C8277A439B0014B62A /* LUT_64_Dark_HighContrast_v3.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = LUT_64_Dark_HighContrast_v3.png; sourceTree = ""; }; 4BB7B9CB277A452A0014B62A /* LUT_64_Gloss.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = LUT_64_Gloss.png; sourceTree = ""; }; @@ -946,7 +947,6 @@ FC06C87F2A53AF4500215B89 /* BrightroomEngine in Frameworks */, 4B7513C82B6BEDFD00A4743E /* SwiftUIHosting in Frameworks */, B08FEAC52F2C76B2002E67C4 /* Reveal-SDK in Frameworks */, - 4BA2B1B42B86282D00E6DE64 /* BrightroomUIPhotosCrop in Frameworks */, FC06C8812A53AF4500215B89 /* BrightroomUI in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1080,6 +1080,7 @@ 4B98CCBB25EFF31300E4F61F /* SwiftUIDemoApp.swift */, 4B3957C82C16D9ED000BD249 /* DemoFilterView.swift */, 4B98CCBD25EFF31300E4F61F /* ContentView.swift */, + CD1A00012F33A00000000001 /* RenderingDemoView.swift */, 4B8CAF6D25FFCE720075032A /* FullscreenIdentifiableView.swift */, 4B8CAF6825FFCD720075032A /* IsolatedEditingView.swift */, 4B8CAF7225FFD13D0075032A /* BlurryMaskingViewWrapper.swift */, @@ -1587,7 +1588,6 @@ 4B7513C72B6BEDFD00A4743E /* SwiftUIHosting */, 4B7513CA2B6BEE2100A4743E /* SwiftUISupport */, 4B46E71A2B762E20003179C2 /* MondrianLayout */, - 4BA2B1B32B86282D00E6DE64 /* BrightroomUIPhotosCrop */, B08FEAC42F2C76B2002E67C4 /* Reveal-SDK */, ); productName = SwiftUIDemo; @@ -2168,6 +2168,7 @@ 4B7EE66D25F515B400E17AF1 /* XCAssets+Generated.swift in Sources */, 4BA9896F2B70BF2000EB7983 /* RenderedResultView.swift in Sources */, 4B98CCBE25EFF31300E4F61F /* ContentView.swift in Sources */, + CD1A00022F33A00000000002 /* RenderingDemoView.swift in Sources */, 4B75114025F13276002D804A /* DemoCropView.swift in Sources */, 0451857F2B8F4A9E00A74713 /* DemoCropView2.swift in Sources */, 4B9325832B6A923400DDABAC /* RotateScrollView.swift in Sources */, @@ -2606,10 +2607,6 @@ package = 4B7513C92B6BEE2100A4743E /* XCRemoteSwiftPackageReference "swiftui-support" */; productName = SwiftUISupport; }; - 4BA2B1B32B86282D00E6DE64 /* BrightroomUIPhotosCrop */ = { - isa = XCSwiftPackageProductDependency; - productName = BrightroomUIPhotosCrop; - }; B08FEAC42F2C76B2002E67C4 /* Reveal-SDK */ = { isa = XCSwiftPackageProductDependency; productName = "Reveal-SDK"; diff --git a/Dev/Sources/SwiftUIDemo/ContentView.swift b/Dev/Sources/SwiftUIDemo/ContentView.swift index 2f67b16a..4d78fd6f 100644 --- a/Dev/Sources/SwiftUIDemo/ContentView.swift +++ b/Dev/Sources/SwiftUIDemo/ContentView.swift @@ -32,6 +32,10 @@ struct ContentView: View { DemoFilterView(editingStack: horizontalStack) } + NavigationLink("Rendering") { + RenderingDemoView() + } + Section("Restoration Horizontal") { Button("Crop") { fullScreenView = .init { @@ -255,6 +259,32 @@ struct ContentView: View { } } + Button("PixelEditor 4:5") { + fullScreenView = .init { + DemoPixelEditor( + editingStack: { + EditingStack.init( + imageProvider: .init(image: Asset.l1000316.image) + ) + }, + options: .init(croppingAspectRatio: .init(width: 4, height: 5)) + ) + } + } + + Button("PixelEditor 5:4") { + fullScreenView = .init { + DemoPixelEditor( + editingStack: { + EditingStack.init( + imageProvider: .init(image: Asset.l1000316.image) + ) + }, + options: .init(croppingAspectRatio: .init(width: 5, height: 4)) + ) + } + } + Button("PixelEditor left") { fullScreenView = .init { DemoPixelEditor( @@ -407,6 +437,8 @@ struct DemoPhotosCropView: View { struct DemoPixelEditor: View { + @Environment(\.dismiss) private var dismiss + @ObjectEdge var editingStack: EditingStack @State var resultImage: ResultImage? @@ -421,13 +453,15 @@ struct DemoPixelEditor: View { } var body: some View { - DemoPixelEditWrapper( + SwiftUIPixelEditorView( editingStack: editingStack, options: options, - - onCompleted: { + onEndEditing: { editingStack in let image = try! editingStack.makeRenderer().render().cgImage self.resultImage = .init(cgImage: image) + }, + onCancelEditing: { + dismiss() } ) .sheet(item: $resultImage) { @@ -436,40 +470,6 @@ struct DemoPixelEditor: View { } } -struct DemoPixelEditWrapper: UIViewControllerRepresentable { - - typealias UIViewControllerType = UINavigationController - - private let onCompleted: () -> Void - - let editingStack: EditingStack - let options: ClassicImageEditOptions - - init( - editingStack: EditingStack, - options: ClassicImageEditOptions, - onCompleted: @escaping () -> Void - ) { - self.editingStack = editingStack - self.onCompleted = onCompleted - self.options = options - } - - func makeUIViewController(context: Context) -> UINavigationController { - editingStack.start() - let cropViewController = ClassicImageEditViewController( - editingStack: editingStack, - options: options - ) - cropViewController.handlers.didEndEditing = { _, _ in - onCompleted() - } - return UINavigationController(rootViewController: cropViewController) - } - - func updateUIViewController(_ uiViewController: UINavigationController, context: Context) {} -} - #Preview { ContentView() } diff --git a/Dev/Sources/SwiftUIDemo/DemoCropView.swift b/Dev/Sources/SwiftUIDemo/DemoCropView.swift index 119e155d..3a67ef92 100644 --- a/Dev/Sources/SwiftUIDemo/DemoCropView.swift +++ b/Dev/Sources/SwiftUIDemo/DemoCropView.swift @@ -8,7 +8,6 @@ import BrightroomEngine import BrightroomUI -import BrightroomUIPhotosCrop import SwiftUI import UIKit import StateGraph @@ -27,13 +26,11 @@ struct DemoCropView: View { var body: some View { ZStack { - VStack { - PhotosCropRotating(editingStack: editingStack) -// Button("Done") { -// let image = try! $editingStackState.driver.makeRenderer().render().cgImage -// self.resultImage = .init(cgImage: image) -// } - } + SwiftUICropView( + editingStack: editingStack, + isAutoApplyEditingStackEnabled: true + ) + .background(Color.black) VStack { HStack { @@ -54,13 +51,6 @@ struct DemoCropView: View { .ignoresSafeArea() } -// .safeAreaInset(edge: .top, content: { -// Button("Done") { -// let image = try! $editingStackState.driver.makeRenderer().render().cgImage -// self.resultImage = .init(cgImage: image) -// } -// }) - .onAppear { editingStack.start() } diff --git a/Dev/Sources/SwiftUIDemo/DemoCropView2.swift b/Dev/Sources/SwiftUIDemo/DemoCropView2.swift index d9056b60..3270ba99 100644 --- a/Dev/Sources/SwiftUIDemo/DemoCropView2.swift +++ b/Dev/Sources/SwiftUIDemo/DemoCropView2.swift @@ -8,7 +8,6 @@ import BrightroomEngine import BrightroomUI -import BrightroomUIPhotosCrop import SwiftUI import UIKit import SwiftUISupport diff --git a/Dev/Sources/SwiftUIDemo/RenderingDemoView.swift b/Dev/Sources/SwiftUIDemo/RenderingDemoView.swift new file mode 100644 index 00000000..43cd23b4 --- /dev/null +++ b/Dev/Sources/SwiftUIDemo/RenderingDemoView.swift @@ -0,0 +1,551 @@ +import BrightroomEngine +import BrightroomUI +import MetalKit +import PhotosUI +import SwiftUI +import UIKit + +struct RenderingDemoView: View { + + var body: some View { + Form { + Section("Preview") { + NavigationLink("ImagePreviewView") { + ImagePreviewDemoView() + } + } + + Section("Rendering") { + NavigationLink("Metal / CIImage Display") { + MetalRenderingDemoView() + } + + NavigationLink("RAW") { + RawRenderingDemoView() + } + } + + Section("LUT") { + NavigationLink("Import LUT") { + LUTImportDemoView() + } + } + } + .navigationTitle("Rendering") + } +} + +private enum DemoResource { + static func url(forResource name: String, ofType type: String) -> URL { + Bundle.main.path(forResource: name, ofType: type).map { + URL(fileURLWithPath: $0) + }! + } +} + +private struct ImagePreviewDemoView: View { + + @ObjectEdge private var retainedStack = EditingStack( + imageProvider: .init(image: Asset.leica.image), + cropModifier: .init { _, crop, completion in + var new = crop + new.updateCropExtent(toFitAspectRatio: .square) + completion(new) + } + ) + + @State private var previewStack = EditingStack(imageProvider: .init(image: Asset.leica.image)) + @State private var selectedItem: PhotosPickerItem? + @State private var status = "Example" + + var body: some View { + VStack(spacing: 0) { + Form { + Section { + PhotosPicker("Pick Image", selection: $selectedItem) + + Button("Example") { + previewStack = EditingStack(imageProvider: .init(image: Asset.leica.image)) + status = "Example" + } + + Button("Example with keeping") { + previewStack = retainedStack + status = "Example with keeping" + } + + Button("Oriented image") { + previewStack = EditingStack( + imageProvider: try! .init( + fileURL: DemoResource.url(forResource: "orientation_right", ofType: "HEIC") + ) + ) + status = "Oriented image" + } + + Button("Remote image") { + previewStack = EditingStack( + imageProvider: .init( + editableRemoteURL: URL( + string: + "https://images.unsplash.com/photo-1604456930969-37f67bcd6e1e?ixid=MXwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHw%3D&ixlib=rb-1.2.1" + )! + ) + ) + status = "Remote image" + } + } footer: { + Text(status) + } + } + .frame(maxHeight: 340) + + ImagePreviewRepresentable(editingStack: previewStack) + .id(ObjectIdentifier(previewStack)) + .background(Color.black) + } + .navigationTitle("ImagePreviewView") + .onChange(of: selectedItem, perform: loadPickedImage) + } + + private func loadPickedImage(_ item: PhotosPickerItem?) { + guard let item else { return } + + Task { + do { + guard let data = try await item.loadTransferable(type: Data.self) else { + await MainActor.run { + status = "Failed to load selected image." + } + return + } + + let stack = EditingStack(imageProvider: try .init(data: data)) + await MainActor.run { + previewStack = stack + status = "Picked image" + } + } catch { + await MainActor.run { + status = "Failed to load selected image: \(error)" + } + } + } + } +} + +private struct ImagePreviewRepresentable: UIViewRepresentable { + + let editingStack: EditingStack + + func makeUIView(context: Context) -> ImagePreviewView { + ImagePreviewView(editingStack: editingStack) + } + + func updateUIView(_ uiView: ImagePreviewView, context: Context) {} +} + +private struct MetalRenderingDemoView: View { + + @State private var sourceKind: MetalRenderingSourceKind = .cgImage + @State private var displayKind: MetalRenderingDisplayKind = .metal + @State private var blurRadius: Double = 0 + @State private var image: UIImage = Asset.horizontalRect.image + @State private var selectedItem: PhotosPickerItem? + @State private var status = "horizontal-rect" + + var body: some View { + VStack(spacing: 0) { + Form { + Section { + Picker("Source", selection: $sourceKind) { + ForEach(MetalRenderingSourceKind.allCases) { kind in + Text(kind.title).tag(kind) + } + } + + Picker("Display", selection: $displayKind) { + ForEach(MetalRenderingDisplayKind.allCases) { kind in + Text(kind.title).tag(kind) + } + } + + Slider(value: $blurRadius, in: 0...200) { + Text("Blur") + } + + PhotosPicker("Pick Image", selection: $selectedItem) + } footer: { + Text(status) + } + } + .frame(maxHeight: 320) + + Group { + if let sourceImage = makeSourceImage() { + switch displayKind { + case .metal: + MetalImageDisplayRepresentable( + image: sourceImage, + blurRadius: blurRadius + ) + case .uiImageView: + UIImageCIImageDisplayRepresentable( + image: sourceImage, + blurRadius: blurRadius + ) + } + } else { + Text("Unable to create CIImage.") + .foregroundStyle(.secondary) + } + } + .aspectRatio(1, contentMode: .fit) + .frame(maxWidth: .infinity) + .background(Color.black) + .padding() + } + .navigationTitle("Metal / CIImage") + .onChange(of: selectedItem, perform: loadPickedImage) + } + + private func makeSourceImage() -> CIImage? { + guard let cgImage = image.cgImage else { + return nil + } + + switch sourceKind { + case .cgImage: + return CIImage(cgImage: cgImage) + case .mtlTexture: + guard let device = MTLCreateSystemDefaultDevice() else { + return nil + } + let loader = MTKTextureLoader(device: device) + guard let texture = try? loader.newTexture(cgImage: cgImage, options: [:]) else { + return nil + } + return CIImage(mtlTexture: texture, options: [:]) + } + } + + private func loadPickedImage(_ item: PhotosPickerItem?) { + guard let item else { return } + + Task { + do { + guard let data = try await item.loadTransferable(type: Data.self) else { + await MainActor.run { + status = "Failed to load selected image." + } + return + } + + guard let pickedImage = UIImage(data: data) else { + await MainActor.run { + status = "Selected data was not a UIImage." + } + return + } + + await MainActor.run { + image = pickedImage + status = "Picked image" + } + } catch { + await MainActor.run { + status = "Failed to load selected image: \(error)" + } + } + } + } +} + +private enum MetalRenderingSourceKind: CaseIterable, Identifiable { + case cgImage + case mtlTexture + + var id: Self { self } + + var title: String { + switch self { + case .cgImage: + "CIImage <- CGImage" + case .mtlTexture: + "CIImage <- MTLTexture <- CGImage" + } + } +} + +private enum MetalRenderingDisplayKind: CaseIterable, Identifiable { + case metal + case uiImageView + + var id: Self { self } + + var title: String { + switch self { + case .metal: + "MTKView" + case .uiImageView: + "UIImageView" + } + } +} + +private struct MetalImageDisplayRepresentable: UIViewRepresentable { + + let image: CIImage + let blurRadius: Double + + func makeUIView(context: Context) -> MetalImageView { + let view = MetalImageView(frame: .zero, device: MTLCreateSystemDefaultDevice()) + view.contentMode = .scaleAspectFit + return view + } + + func updateUIView(_ uiView: MetalImageView, context: Context) { + uiView.postProcessing = { image in + image + .clampedToExtent() + .applyingGaussianBlur(sigma: blurRadius) + .cropped(to: image.extent) + } + uiView.display(image: image) + } +} + +private struct UIImageCIImageDisplayRepresentable: UIViewRepresentable { + + let image: CIImage + let blurRadius: Double + + func makeCoordinator() -> Coordinator { + Coordinator() + } + + func makeUIView(context: Context) -> UIImageView { + let view = UIImageView() + view.contentMode = .scaleAspectFit + view.backgroundColor = .clear + return view + } + + func updateUIView(_ uiView: UIImageView, context: Context) { + let processed = image + .clampedToExtent() + .applyingGaussianBlur(sigma: blurRadius) + .cropped(to: image.extent) + .removingExtentOffset() + + guard let cgImage = context.coordinator.context.createCGImage( + processed, + from: processed.extent + ) else { + uiView.image = nil + return + } + + uiView.image = UIImage(cgImage: cgImage, scale: 1, orientation: .up) + } + + final class Coordinator { + let context = CIContext() + } +} + +private struct RawRenderingDemoView: View { + + @State private var resultImage: ResultImage? + @State private var fullScreenView: FullscreenIdentifiableView? + @State private var status = "AppleRAW_1.DNG" + + var body: some View { + Form { + Section { + Button("Load RAW") { + loadRAW() + } + + Button("Open PhotosCrop") { + fullScreenView = .init { + DemoPhotosCropView { + EditingStack( + imageProvider: .init( + rawDataURL: DemoResource.url(forResource: "AppleRAW_1", ofType: "DNG") + ) + ) + } + } + } + + Button("Write JPEG") { + writeJPEG() + } + } footer: { + Text(status) + } + } + .navigationTitle("RAW") + .sheet(item: $resultImage) { + RenderedResultView(result: $0) + } + .fullScreenCover( + item: $fullScreenView, + onDismiss: {}, + content: { + $0 + } + ) + } + + private func loadRAW() { + let url = DemoResource.url(forResource: "AppleRAW_1", ofType: "DNG") + + guard + let filter = CIFilter(imageURL: url, options: [:]), + let outputImage = filter.outputImage + else { + status = "Failed to load RAW." + return + } + + let context = CIContext() + guard let cgImage = context.createCGImage(outputImage, from: outputImage.extent) else { + status = "Failed to create CGImage from RAW." + return + } + + resultImage = .init(cgImage: cgImage) + status = "Loaded RAW: \(Int(outputImage.extent.width)) x \(Int(outputImage.extent.height))" + } + + private func writeJPEG() { + let url = DemoResource.url(forResource: "AppleRAW_1", ofType: "DNG") + + guard + let filter = CIFilter(imageURL: url, options: [:]), + let outputImage = filter.outputImage + else { + status = "Failed to load RAW." + return + } + + let targetURL = FileManager.default.temporaryDirectory + .appendingPathComponent("brightroom-raw-image.jpeg") + + do { + try CIContext().writeJPEGRepresentation( + of: outputImage, + to: targetURL, + colorSpace: outputImage.colorSpace ?? CGColorSpaceCreateDeviceRGB(), + options: [:] + ) + status = "Wrote \(targetURL.lastPathComponent)" + } catch { + status = "Failed to write JPEG: \(error)" + } + } +} + +private struct LUTImportDemoView: View { + + @State private var selectedItem: PhotosPickerItem? + @State private var fullScreenView: FullscreenIdentifiableView? + @State private var status = "Select a 512 x 512 LUT image." + + var body: some View { + Form { + Section { + PhotosPicker("Import LUT", selection: $selectedItem) + + Button("Open Classic Editor") { + fullScreenView = .init { + DemoPixelEditor( + editingStack: { + EditingStack(imageProvider: .init(image: Asset.l1000316.image)) + }, + options: .init(croppingAspectRatio: nil) + ) + } + } + } footer: { + Text(status) + } + } + .navigationTitle("Import LUT") + .onChange(of: selectedItem, perform: loadPickedLUT) + .fullScreenCover( + item: $fullScreenView, + onDismiss: {}, + content: { + $0 + } + ) + } + + private func loadPickedLUT(_ item: PhotosPickerItem?) { + guard let item else { return } + + Task { + do { + guard let data = try await item.loadTransferable(type: Data.self) else { + await MainActor.run { + status = "Failed to load selected LUT." + } + return + } + + guard let image = UIImage(data: data) else { + await MainActor.run { + status = "Selected data was not a UIImage." + } + return + } + + await MainActor.run { + importLUT(image) + } + } catch { + await MainActor.run { + status = "Failed to load selected LUT: \(error)" + } + } + } + } + + private func importLUT(_ image: UIImage) { + guard image.scale == 1 else { + status = "Invalid LUT: image scale is \(image.scale)." + return + } + + guard image.size == CGSize(width: 512, height: 512) else { + status = "Invalid LUT: image size is \(image.size)." + return + } + + let identifier = "Imported_\(Int(Date().timeIntervalSince1970))" + let filter = FilterColorCube( + name: identifier, + identifier: identifier, + lutImage: .init(image: image), + dimension: 64 + ) + let preset = FilterPreset( + name: filter.name, + identifier: filter.identifier, + filters: [filter.asAny()], + userInfo: [:] + ) + PresetStorage.default.presets.insert(preset, at: 0) + + status = "Imported \(identifier)" + } +} + +#Preview { + NavigationView { + RenderingDemoView() + } +} diff --git a/Package.swift b/Package.swift index 91b31ad8..8e917592 100644 --- a/Package.swift +++ b/Package.swift @@ -9,7 +9,6 @@ let package = Package( products: [ .library(name: "BrightroomEngine", targets: ["BrightroomUI"]), .library(name: "BrightroomUI", targets: ["BrightroomUI"]), - .library(name: "BrightroomUIPhotosCrop", targets: ["BrightroomUIPhotosCrop"]) ], dependencies: [ .package(url: "https://github.com/VergeGroup/swift-state-graph", exact: "0.17.0"), @@ -28,12 +27,9 @@ let package = Package( dependencies: [ "BrightroomEngine", .product(name: "StateGraph", package: "swift-state-graph"), + "PrecisionLevelSlider", "TransitionPatch", ] ), - .target( - name: "BrightroomUIPhotosCrop", - dependencies: ["BrightroomUI", "PrecisionLevelSlider"] - ) ] ) diff --git a/Sources/BrightroomEngine/ColorCube/ColorCubeHelper.swift b/Sources/BrightroomEngine/ColorCube/ColorCubeHelper.swift index 5e27ea87..9dccc033 100644 --- a/Sources/BrightroomEngine/ColorCube/ColorCubeHelper.swift +++ b/Sources/BrightroomEngine/ColorCube/ColorCubeHelper.swift @@ -93,4 +93,4 @@ extension ColorCubeHelper { } } -let cache = NSCache() +nonisolated(unsafe) let cache = NSCache() diff --git a/Sources/BrightroomEngine/Core/EditingStack.swift b/Sources/BrightroomEngine/Core/EditingStack.swift index fd855c8d..97682a7c 100644 --- a/Sources/BrightroomEngine/Core/EditingStack.swift +++ b/Sources/BrightroomEngine/Core/EditingStack.swift @@ -66,6 +66,16 @@ open class EditingStack: Hashable { // MARK: - Nested Types + private struct PreviewFilterPresetRequest: Equatable { + var thumbnailImage: CIImage + } + + private struct CropImageRenderRequest: Equatable { + var filters: Edit.Filters + var editingSourceCGImage: CGImage + var orientation: CGImagePropertyOrientation + } + public struct Loaded: Equatable { // MARK: - Properties @@ -211,7 +221,9 @@ open class EditingStack: Hashable { private let filterPresets: [FilterPreset] private var subscriptions: Set = .init() - private var imageProviderSubscription: Any? + private var imageProviderSubscription: AnyCancellable? + + private let startLock = NSLock() public var cropModifier: CropModifier @@ -222,11 +234,6 @@ open class EditingStack: Hashable { queue: DispatchQueue.init(label: "Brightroom.cgImage") ) - // MARK: - Change Tracking - - private var _previousThumbnailImage: CIImage? - private var _previousFilters: Edit.Filters? - // MARK: - Initializers /// Creates an instance @@ -237,7 +244,6 @@ open class EditingStack: Hashable { /// - modifyCrop: A chance to modify cropping. It runs in background-thread. CIImage is not original image. public init( imageProvider: ImageProvider, - colorCubeStorage: ColorCubeStorage = .default, presetStorage: PresetStorage = .default, options: Options = .init(), cropModifier: CropModifier = .init(modify: { _, c, completion in completion(c) }) @@ -246,15 +252,7 @@ open class EditingStack: Hashable { self.options = options self.cropModifier = cropModifier - filterPresets = - colorCubeStorage.filters.map { - FilterPreset( - name: $0.name, - identifier: $0.identifier, - filters: [$0.asAny()], - userInfo: [:] - ) - } + presetStorage.presets + filterPresets = presetStorage.presets self.imageProvider = imageProvider } @@ -269,24 +267,14 @@ open class EditingStack: Hashable { /** Mutual exclusion */ - if hasStartedEditing { + guard markStartedIfNeeded() else { DispatchQueue.main.async { onPreparationCompleted() } return } - hasStartedEditing = true - // Set up state observation on background queue - withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self = self else { return } - self.backgroundQueue.async { - self.receiveInBackground() - } - } - } - .store(in: &subscriptions) + bindLoadedStateProcessing() /** Start downloading image @@ -308,6 +296,64 @@ open class EditingStack: Hashable { imageProviderSubscription = imageProviderSub } + private func markStartedIfNeeded() -> Bool { + startLock.lock() + defer { + startLock.unlock() + } + + guard hasStartedEditing == false else { + return false + } + + hasStartedEditing = true + return true + } + + private func bindLoadedStateProcessing() { + withGraphTracking { [weak self] in + guard let self else { return } + + withGraphTrackingMap( + from: self, + map: { stack -> PreviewFilterPresetRequest? in + stack.loadedState.map { + PreviewFilterPresetRequest(thumbnailImage: $0.thumbnailImage) + } + }, + onChange: { [weak self] request in + guard let self, let request else { return } + + self.backgroundQueue.async { + let presets = self.filterPresets.map { + PreviewFilterPreset(sourceImage: request.thumbnailImage, filter: $0) + } + self.loadedState?.previewFilterPresets = presets + } + } + ) + + withGraphTrackingMap( + from: self, + map: { stack -> CropImageRenderRequest? in + stack.loadedState.map { + CropImageRenderRequest( + filters: $0.currentEdit.filters, + editingSourceCGImage: $0.editingSourceCGImage, + orientation: $0.metadata.orientation + ) + } + }, + onChange: { [weak self] request in + guard let self, let request else { return } + + self.scheduleCropImageRender(request) + } + ) + } + .store(in: &subscriptions) + } + private func handleImageLoaded( image: ImageProvider.LoadedImage, onPreparationCompleted: @escaping @MainActor () -> Void @@ -432,48 +478,24 @@ open class EditingStack: Hashable { EngineLog.debug("[EditingStack] deinit") } - private func receiveInBackground() { - - assert(Thread.isMainThread == false) - - guard let loadedState = self.loadedState else { return } - - // Check if thumbnailImage changed - if _previousThumbnailImage != loadedState.thumbnailImage { - _previousThumbnailImage = loadedState.thumbnailImage - - let image = loadedState.thumbnailImage - let presets = self.filterPresets.map { - PreviewFilterPreset(sourceImage: image, filter: $0) - } - self.loadedState?.previewFilterPresets = presets - } - - // Check if filters changed - if _previousFilters != loadedState.currentEdit.filters { - _previousFilters = loadedState.currentEdit.filters - - let currentFilters = loadedState.currentEdit.filters - - self.debounceForCreatingCGImage.on { [weak self] in - - guard let self = self, let loadedState = self.loadedState else { return } + private func scheduleCropImageRender(_ request: CropImageRenderRequest) { + debounceForCreatingCGImage.on { [weak self] in + guard let self else { return } - let cgImageForCrop: CGImage = { - do { - return try Self.renderCGImageForCrop( - filters: currentFilters.makeFilters(), - source: .init(cgImage: loadedState.editingSourceCGImage), - orientation: loadedState.metadata.orientation - ) - } catch { - assertionFailure() - return loadedState.editingSourceCGImage - } - }() + let cgImageForCrop: CGImage = { + do { + return try Self.renderCGImageForCrop( + filters: request.filters.makeFilters(), + source: .init(cgImage: request.editingSourceCGImage), + orientation: request.orientation + ) + } catch { + assertionFailure() + return request.editingSourceCGImage + } + }() - self.loadedState?.imageForCrop = cgImageForCrop - } + self.loadedState?.imageForCrop = cgImageForCrop } } diff --git a/Sources/BrightroomEngine/Library/EngineSanitizer.swift b/Sources/BrightroomEngine/Library/EngineSanitizer.swift index b6108b6e..c3346ee8 100644 --- a/Sources/BrightroomEngine/Library/EngineSanitizer.swift +++ b/Sources/BrightroomEngine/Library/EngineSanitizer.swift @@ -28,11 +28,11 @@ public enum EngineRuntimeError: Swift.Error, Sendable { case failedToRenderCGImageForCrop(sourceImage: CGImage) } -public final class EngineSanitizer { +public struct EngineSanitizer: Sendable { public static let global = EngineSanitizer() - public var onDidFindRuntimeError: (EngineRuntimeError) -> Void = { _ in } + public var onDidFindRuntimeError: @Sendable (EngineRuntimeError) -> Void = { _ in } public init() { diff --git a/Sources/BrightroomUI/Shared/BrightroomUI.swift b/Sources/BrightroomUI/Shared/BrightroomUI.swift index e16f0656..e2c85aa0 100644 --- a/Sources/BrightroomUI/Shared/BrightroomUI.swift +++ b/Sources/BrightroomUI/Shared/BrightroomUI.swift @@ -39,21 +39,12 @@ extension TODOL10n { public typealias NonL10n = String -#if COCOAPODS -let bundle = Bundle.init(for: Dummy.self) - .path(forResource: "BrightroomUI", ofType: "bundle") - .map { - Bundle.init(path: $0) -}! -#else let bundle = Bundle.module -#endif public let BrightroomUIBundle = bundle -fileprivate final class Dummy {} - @inline(__always) +@MainActor func _pixeleditor_ensureMainThread() { assert(Thread.isMainThread) } diff --git a/Sources/BrightroomUI/Shared/Components/Crop/CropView._InteractiveCropGuideView.swift b/Sources/BrightroomUI/Shared/Components/Crop/CropView._InteractiveCropGuideView.swift index 7ca7d368..17188db9 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/CropView._InteractiveCropGuideView.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/CropView._InteractiveCropGuideView.swift @@ -20,9 +20,7 @@ // THE SOFTWARE. import UIKit -#if !COCOAPODS import BrightroomEngine -#endif extension CropView { diff --git a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift index 481bfb6c..9261ce4c 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/CropView.swift @@ -25,9 +25,7 @@ import UIKit import Combine import StateGraph -#if !COCOAPODS import BrightroomEngine -#endif /// A view that previews how crops the image. /// @@ -39,6 +37,13 @@ import BrightroomEngine /// - Implicit animations occurs in first time load with remote image. public final class CropView: UIView, UIScrollViewDelegate { + public struct StateSnapshot: Equatable { + public var proposedCrop: EditingCrop? + public var frame: CGRect + public var adjustmentKind: StateModel.AdjustmentKind + public var preferredAspectRatio: PixelAspectRatio? + } + public final class StateModel { public struct AdjustmentKind: OptionSet, Equatable { @@ -66,6 +71,15 @@ public final class CropView: UIView, UIScrollViewDelegate { */ @GraphStored public var preferredAspectRatio: PixelAspectRatio? = nil + public var snapshot: StateSnapshot { + .init( + proposedCrop: proposedCrop, + frame: frame, + adjustmentKind: adjustmentKind, + preferredAspectRatio: preferredAspectRatio + ) + } + public init() {} } @@ -214,7 +228,8 @@ public final class CropView: UIView, UIScrollViewDelegate { private var isBinding = false - private var stateHandler: @MainActor (StateModel) -> Void = { _ in } + private var stateHandler: @MainActor (StateSnapshot) -> Void = { _ in } + private var stateHandlerSubscription: AnyCancellable? var isAutoApplyEditingStackEnabled = false @@ -293,15 +308,6 @@ public final class CropView: UIView, UIScrollViewDelegate { }) } - // Set up state observation for external handler - withGraphTracking { - withGraphTrackingGroup { [weak self] in - guard let self else { return } - self.stateHandler(self.state) - } - } - .store(in: &subscriptions) - } @available(*, unavailable) @@ -311,8 +317,23 @@ public final class CropView: UIView, UIScrollViewDelegate { // MARK: - Functions - func setStateHandler(_ handler: @escaping @MainActor (StateModel) -> Void) { + func setStateHandler(_ handler: @escaping @MainActor (StateSnapshot) -> Void) { self.stateHandler = handler + bindStateHandler() + } + + private func bindStateHandler() { + stateHandlerSubscription = withGraphTracking { + withGraphTrackingMap( + from: self, + map: { $0.state.snapshot }, + onChange: { [weak self] snapshot in + Task { @MainActor in + self?.stateHandler(snapshot) + } + } + ) + } } public func setOverlayInImageView(_ overlay: UIView) { diff --git a/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift b/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift index c2560fa5..37a1e85e 100644 --- a/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift +++ b/Sources/BrightroomUI/Shared/Components/Crop/SwiftUICropView.swift @@ -22,9 +22,7 @@ import UIKit import SwiftUI import StateGraph -#if !COCOAPODS import BrightroomEngine -#endif public final class _PixelEditor_WrapperViewController: UIViewController { @@ -67,6 +65,19 @@ public struct SwiftUICropView: UIViewControllerRepresentable { } } + public final class ApplyAction { + + var onCall: () -> Void = {} + + public init() { + + } + + public func callAsFunction() { + onCall() + } + } + public typealias UIViewControllerType = _PixelEditor_WrapperViewController private let cropInsideOverlay: ((CropView.StateModel.AdjustmentKind?) -> AnyView)? @@ -78,8 +89,9 @@ public struct SwiftUICropView: UIViewControllerRepresentable { private var _adjustmentAngle: EditingCrop.AdjustmentAngle? private var _croppingAspectRatio: PixelAspectRatio? private var _resetAction: ResetAction? + private var _applyAction: ApplyAction? - private let stateHandler: @MainActor (CropView.StateModel) -> Void + private let stateHandler: @MainActor (CropView.StateSnapshot) -> Void private let isGuideInteractionEnabled: Bool private let isAutoApplyEditingStackEnabled: Bool private let areAnimationsEnabled: Bool @@ -93,7 +105,7 @@ public struct SwiftUICropView: UIViewControllerRepresentable { contentInset: UIEdgeInsets? = nil, @ViewBuilder cropInsideOverlay: @escaping (CropView.StateModel.AdjustmentKind?) -> InsideOverlay, @ViewBuilder cropOutsideOverlay: @escaping (CropView.StateModel.AdjustmentKind?) -> OutsideOverlay, - stateHandler: @escaping @MainActor (CropView.StateModel) -> Void = { _ in } + stateHandler: @escaping @MainActor (CropView.StateSnapshot) -> Void = { _ in } ) { self.editingStack = editingStack self.isGuideInteractionEnabled = isGuideInteractionEnabled @@ -111,7 +123,7 @@ public struct SwiftUICropView: UIViewControllerRepresentable { isAutoApplyEditingStackEnabled: Bool = false, areAnimationsEnabled: Bool = true, contentInset: UIEdgeInsets? = nil, - stateHandler: @escaping @MainActor (CropView.StateModel) -> Void = { _ in } + stateHandler: @escaping @MainActor (CropView.StateSnapshot) -> Void = { _ in } ) { self.cropInsideOverlay = nil self.cropOutsideOverlay = nil @@ -145,6 +157,14 @@ public struct SwiftUICropView: UIViewControllerRepresentable { view.setCropOutsideOverlay(CropView.SwiftUICropOutsideOverlay(content: cropOutsideOverlay)) } + _resetAction?.onCall = { [weak view] in + view?.resetCrop() + } + + _applyAction?.onCall = { [weak view] in + view?.applyEditingStack() + } + let controller = _PixelEditor_WrapperViewController.init(bodyView: view) return controller @@ -152,6 +172,18 @@ public struct SwiftUICropView: UIViewControllerRepresentable { public func updateUIViewController(_ uiViewController: _PixelEditor_WrapperViewController, context: Context) { + if uiViewController.bodyView.isGuideInteractionEnabled != isGuideInteractionEnabled { + uiViewController.bodyView.isGuideInteractionEnabled = isGuideInteractionEnabled + } + + if uiViewController.bodyView.isAutoApplyEditingStackEnabled != isAutoApplyEditingStackEnabled { + uiViewController.bodyView.isAutoApplyEditingStackEnabled = isAutoApplyEditingStackEnabled + } + + if uiViewController.bodyView.areAnimationsEnabled != areAnimationsEnabled { + uiViewController.bodyView.areAnimationsEnabled = areAnimationsEnabled + } + if let _rotation { uiViewController.bodyView.setRotation(_rotation) } @@ -165,6 +197,10 @@ public struct SwiftUICropView: UIViewControllerRepresentable { _resetAction?.onCall = { [weak uiViewController] in uiViewController?.bodyView.resetCrop() } + + _applyAction?.onCall = { [weak uiViewController] in + uiViewController?.bodyView.applyEditingStack() + } } public func rotation(_ rotation: EditingCrop.Rotation?) -> Self { @@ -198,4 +234,12 @@ public struct SwiftUICropView: UIViewControllerRepresentable { } + public func registerApplyAction(_ action: ApplyAction) -> Self { + + var modified = self + modified._applyAction = action + return modified + + } + } diff --git a/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift b/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift index 65266e6a..61a044a0 100644 --- a/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift +++ b/Sources/BrightroomUI/Shared/Components/Drawing/BlurryMaskingView.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import Combine import StateGraph diff --git a/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift b/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift index 43576d31..cb33b2f1 100644 --- a/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift +++ b/Sources/BrightroomUI/Shared/Components/Drawing/CanvasView.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import Combine import StateGraph diff --git a/Sources/BrightroomUI/Shared/Components/ImageViews/CIImageDisplaying.swift b/Sources/BrightroomUI/Shared/Components/ImageViews/CIImageDisplaying.swift index 8f089101..c7c956b9 100644 --- a/Sources/BrightroomUI/Shared/Components/ImageViews/CIImageDisplaying.swift +++ b/Sources/BrightroomUI/Shared/Components/ImageViews/CIImageDisplaying.swift @@ -20,10 +20,7 @@ // THE SOFTWARE. import UIKit - -#if !COCOAPODS - import BrightroomEngine -#endif +import BrightroomEngine public protocol CIImageDisplaying: AnyObject { func display(image: CIImage?) diff --git a/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift b/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift index 355c2d93..62ff19e6 100644 --- a/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift +++ b/Sources/BrightroomUI/Shared/Components/ImageViews/ImagePreviewView.swift @@ -19,9 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -#if !COCOAPODS import BrightroomEngine -#endif import UIKit import Combine import StateGraph diff --git a/Sources/BrightroomUI/Shared/Components/ImageViews/MetalImageView.swift b/Sources/BrightroomUI/Shared/Components/ImageViews/MetalImageView.swift index 37ceda3a..51bc76ce 100644 --- a/Sources/BrightroomUI/Shared/Components/ImageViews/MetalImageView.swift +++ b/Sources/BrightroomUI/Shared/Components/ImageViews/MetalImageView.swift @@ -21,10 +21,7 @@ import MetalKit import UIKit - -#if !COCOAPODS - import BrightroomEngine -#endif +import BrightroomEngine /// https://developer.apple.com/metal/Metal-Feature-Set-Tables.pdf open class MetalImageView: MTKView, CIImageDisplaying, MTKViewDelegate { diff --git a/Sources/BrightroomUI/Shared/Components/ImageViews/_ImageView.swift b/Sources/BrightroomUI/Shared/Components/ImageViews/_ImageView.swift index def49fd2..9cba2cca 100644 --- a/Sources/BrightroomUI/Shared/Components/ImageViews/_ImageView.swift +++ b/Sources/BrightroomUI/Shared/Components/ImageViews/_ImageView.swift @@ -7,9 +7,7 @@ // import UIKit -#if !COCOAPODS import BrightroomEngine -#endif final class _ImageView: UIImageView, CIImageDisplaying { var postProcessing: (CIImage) -> CIImage = { $0 } { diff --git a/Sources/BrightroomUI/Shared/Utils/EditingCrop+.swift b/Sources/BrightroomUI/Shared/Utils/EditingCrop+.swift index 7a93db17..fd81b5d2 100644 --- a/Sources/BrightroomUI/Shared/Utils/EditingCrop+.swift +++ b/Sources/BrightroomUI/Shared/Utils/EditingCrop+.swift @@ -9,9 +9,7 @@ import Foundation import CoreGraphics -#if !COCOAPODS import BrightroomEngine -#endif extension EditingCrop { func scrollViewContentSize() -> CGSize { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditOptions.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditOptions.swift index 1008efdc..b7da3d8a 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditOptions.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditOptions.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif public struct ClassicImageEditOptions: Sendable { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift index f9b6834d..ee259050 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewController.swift @@ -23,10 +23,7 @@ import Photos import UIKit import Combine import StateGraph - -#if !COCOAPODS - import BrightroomEngine -#endif +import BrightroomEngine @available(*, deprecated, renamed: "ClassicImageEditViewController") public typealias PixelEditViewController = ClassicImageEditViewController diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewModel.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewModel.swift index 15e843d4..4a005cad 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewModel.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/ClassicImageEditViewModel.swift @@ -21,9 +21,7 @@ import UIKit import StateGraph -#if !COCOAPODS import BrightroomEngine -#endif public final class ClassicImageEditViewModel: Equatable { public static func == (lhs: ClassicImageEditViewModel, rhs: ClassicImageEditViewModel) -> Bool { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlStackView.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlStackView.swift index 8d58bee3..36b28a06 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlStackView.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlStackView.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif protocol ClassicImageEditControlChildViewType { } diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift index 69c69e72..662d9bcc 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditControlViewBase.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import Combine import StateGraph diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditEditMenuControlView.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditEditMenuControlView.swift index fd66b96e..5eec275b 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditEditMenuControlView.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditEditMenuControlView.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditEditMenuControlBase: ClassicImageEditControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditMaskControlView.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditMaskControlView.swift index 4f955528..a4f43a04 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditMaskControlView.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditMaskControlView.swift @@ -19,9 +19,7 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. import UIKit -#if !COCOAPODS import BrightroomEngine -#endif open class ClassicImageEditMaskControlBase : ClassicImageEditControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift index 56c502cc..adba3754 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditPresetListControl.swift @@ -20,9 +20,7 @@ // THE SOFTWARE. import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import Combine import StateGraph @@ -290,23 +288,28 @@ open class ClassicImageEditPresetListControl: ClassicImageEditPresetListControlB private func scrollTo(selectedItem: FilterPreset?, animated: Bool) { guard let content = content else { - return } + let targetIndexPath: IndexPath + if let current = selectedItem, let index = content.previews.firstIndex(where: { $0.filter == current }) { - collectionView.scrollToItem( - at: IndexPath.init(item: index, section: Section.selections.rawValue), - at: .centeredHorizontally, - animated: animated - ) + targetIndexPath = IndexPath(item: index, section: Section.selections.rawValue) } else { - collectionView.scrollToItem( - at: IndexPath.init(item: 0, section: Section.original.rawValue), - at: .centeredHorizontally, - animated: animated - ) + targetIndexPath = IndexPath(item: 0, section: Section.original.rawValue) + } + + guard collectionView.numberOfSections > targetIndexPath.section, + collectionView.numberOfItems(inSection: targetIndexPath.section) > targetIndexPath.item + else { + return } + + collectionView.scrollToItem( + at: targetIndexPath, + at: .centeredHorizontally, + animated: animated + ) } // MARK: - Nested Types diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditStepSlider.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditStepSlider.swift index f6545748..47f988d4 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditStepSlider.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/ClassicImageEditStepSlider.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import TransitionPatch public final class ClassicImageEditStepSlider : UIControl { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditClarityControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditClarityControl.swift index 8643af6a..2e4bff91 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditClarityControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditClarityControl.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditClarityControlBase : ClassicImageEditFilterControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditContrastControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditContrastControl.swift index 8369c2bd..fd7799fa 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditContrastControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditContrastControl.swift @@ -20,9 +20,7 @@ // THE SOFTWARE. import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditContrastControlBase : ClassicImageEditFilterControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditExposureControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditExposureControl.swift index 63da3718..21646a88 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditExposureControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditExposureControl.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditExposureControlBase : ClassicImageEditFilterControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFadeControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFadeControl.swift index 698abd9c..d76c2357 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFadeControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditFadeControl.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditFadeControlBase : ClassicImageEditFilterControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditGaussianBlurControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditGaussianBlurControl.swift index dea12197..eab173cc 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditGaussianBlurControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditGaussianBlurControl.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditGaussianBlurControlBase : ClassicImageEditFilterControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditHighlightsControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditHighlightsControl.swift index a524008d..28c5c801 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditHighlightsControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditHighlightsControl.swift @@ -20,9 +20,7 @@ // THE SOFTWARE. import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditHighlightsControlBase : ClassicImageEditFilterControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSaturationControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSaturationControl.swift index 7f0e776a..f7a879a1 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSaturationControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSaturationControl.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditSaturationControlBase : ClassicImageEditFilterControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditShadowsControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditShadowsControl.swift index d2b92263..8a36b9ed 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditShadowsControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditShadowsControl.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditShadowsControlBase : ClassicImageEditFilterControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSharpenControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSharpenControl.swift index d0a7b9ef..91083adf 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSharpenControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditSharpenControl.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditSharpenControlBase : ClassicImageEditFilterControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditTemperatureControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditTemperatureControl.swift index 75ab5648..6201b425 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditTemperatureControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditTemperatureControl.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditTemperatureControlBase : ClassicImageEditFilterControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditVignetteControl.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditVignetteControl.swift index 029b51ec..211ff4d1 100644 --- a/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditVignetteControl.swift +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/Components/FilterControl/ClassicImageEditVignetteControl.swift @@ -21,9 +21,7 @@ import UIKit -#if !COCOAPODS import BrightroomEngine -#endif import StateGraph open class ClassicImageEditVignetteControlBase : ClassicImageEditFilterControlBase { diff --git a/Sources/BrightroomUI/builtin/ClassicImageEdit/SwiftUIPixelEditorView.swift b/Sources/BrightroomUI/builtin/ClassicImageEdit/SwiftUIPixelEditorView.swift new file mode 100644 index 00000000..4f5876ef --- /dev/null +++ b/Sources/BrightroomUI/builtin/ClassicImageEdit/SwiftUIPixelEditorView.swift @@ -0,0 +1,1463 @@ +// +// Copyright (c) 2026 Muukii +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import CoreImage +import PrecisionLevelSlider +import SwiftUI +import UIKit + +import BrightroomEngine + +public struct SwiftUIPixelEditorView: View { + + @State private var viewModel: ClassicImageEditViewModel + @State private var controlRoute: PixelEditorControlRoute = .root + @State private var displayedRootPanel: PixelEditorRootPanel = .filter + + private let onEndEditing: (EditingStack) -> Void + private let onCancelEditing: () -> Void + + public init( + editingStack: EditingStack, + options: ClassicImageEditOptions = .default, + localizedStrings: ClassicImageEditViewController.LocalizedStrings = .init(), + onEndEditing: @escaping (EditingStack) -> Void = { _ in }, + onCancelEditing: @escaping () -> Void = {} + ) { + self._viewModel = State( + initialValue: ClassicImageEditViewModel( + editingStack: editingStack, + options: options, + localizedStrings: localizedStrings + ) + ) + self.onEndEditing = onEndEditing + self.onCancelEditing = onCancelEditing + } + + public var body: some View { + GeometryReader { proxy in + VStack(spacing: 0) { + PixelEditorTopBar( + mode: viewModel.mode, + title: viewModel.title, + cancelText: viewModel.localizedStrings.cancel, + doneText: viewModel.localizedStrings.done, + onCancel: onCancelEditing, + onDone: { + onEndEditing(viewModel.editingStack) + } + ) + + PixelEditorCanvas(viewModel: viewModel) + .frame(width: proxy.size.width, height: proxy.size.width) + + PixelEditorControlPanel( + viewModel: viewModel, + route: $controlRoute, + displayedRootPanel: $displayedRootPanel + ) + } + .frame(width: proxy.size.width, height: proxy.size.height, alignment: .top) + } + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .top) + .background(PixelEditorColor.background) + .task { + viewModel.editingStack.start() + } + .accessibilityIdentifier("swiftui.pixel.editor") + } +} + +private enum PixelEditorControlRoute: Equatable { + case root + case crop + case masking + case filter(PixelEditorFilterKind) +} + +private enum PixelEditorRootPanel { + case filter + case edit +} + +private enum PixelEditorFilterKind: CaseIterable { + case exposure + case gaussianBlur + case contrast + case temperature + case saturation + case highlights + case shadows + case vignette + case fade + case sharpen + case clarity +} + +private enum PixelEditorLayout { + static let topBarContentHeight: CGFloat = 56 + static let topBarHeight: CGFloat = 72 + static let previewButtonHeight: CGFloat = 32 + static let horizontalMargin: CGFloat = 20 + static let controlHorizontalMargin: CGFloat = 44 +} + +private enum PixelEditorColor { + static let background = Color(uiColor: .systemBackground) + static let primary = Color(uiColor: .label) + static let secondary = Color(uiColor: .secondaryLabel) + static let controlFill = Color(uiColor: .tertiarySystemFill) + static let accent = Color(uiColor: .systemBlue) + static let cropGuide = Color.white +} + +private struct PixelEditorTopBar: View { + + let mode: ClassicImageEditViewModel.Mode + let title: String + let cancelText: String + let doneText: String + let onCancel: () -> Void + let onDone: () -> Void + + var body: some View { + ZStack { + switch mode { + case .preview: + HStack { + PixelEditorPreviewButton( + title: cancelText, + style: .cancel, + action: onCancel + ) + .accessibilityIdentifier("swiftui.pixel.cancel") + + Spacer() + + PixelEditorPreviewButton( + title: doneText, + style: .done, + action: onDone + ) + .accessibilityIdentifier("swiftui.pixel.done") + } + + case .crop, .masking, .editing: + Text(title) + .font(.system(size: 17, weight: .semibold)) + .foregroundStyle(PixelEditorColor.primary) + } + } + .padding(.horizontal, PixelEditorLayout.horizontalMargin) + .frame(height: PixelEditorLayout.topBarContentHeight) + .frame(height: PixelEditorLayout.topBarHeight, alignment: .top) + } +} + +private struct PixelEditorPreviewButton: View { + + enum Style { + case cancel + case done + } + + let title: String + let style: Style + let action: () -> Void + + var body: some View { + Button(action: action) { + Text(title) + .font(.system(size: 15, weight: fontWeight)) + .foregroundStyle(foregroundColor) + .padding(.horizontal, 13) + .frame(height: PixelEditorLayout.previewButtonHeight) + .background { + Capsule() + .fill(backgroundColor) + } + } + .buttonStyle(.plain) + } + + private var foregroundColor: Color { + switch style { + case .cancel: + return PixelEditorColor.primary + case .done: + return .white + } + } + + private var fontWeight: Font.Weight { + switch style { + case .cancel: + return .regular + case .done: + return .semibold + } + } + + private var backgroundColor: Color { + switch style { + case .cancel: + return PixelEditorColor.controlFill + case .done: + return PixelEditorColor.accent + } + } +} + +private struct PixelEditorCanvas: View { + + let viewModel: ClassicImageEditViewModel + + var body: some View { + ZStack { + PixelEditorImagePreviewRepresentable(editingStack: viewModel.editingStack) + .opacity(viewModel.mode.isCrop ? 0 : 1) + .allowsHitTesting(false) + + SwiftUIBlurryMaskingView(editingStack: viewModel.editingStack) + .blushSize(viewModel.maskingBrushSize) + .hideBackdropImageView(true) + .hideBlurryImageView(viewModel.mode.isEditing || viewModel.mode.isCrop) + .opacity(viewModel.mode.displaysMaskingView ? 1 : 0) + .allowsHitTesting(viewModel.mode.isMasking) + + SwiftUICropView( + editingStack: viewModel.editingStack, + isGuideInteractionEnabled: viewModel.options.croppingAspectRatio == nil, + isAutoApplyEditingStackEnabled: false, + contentInset: .zero, + cropInsideOverlay: { adjustmentKind in + if viewModel.options.croppingAspectRatio == nil { + PixelEditorFreeCropGuideOverlay(isAdjustmentActive: adjustmentKind != nil) + } + }, + cropOutsideOverlay: { _ in + PixelEditorColor.background + }, + stateHandler: { state in + if let proposedCrop = state.proposedCrop { + viewModel.setProposedCrop(proposedCrop) + } + } + ) + .croppingAspectRatio(viewModel.options.croppingAspectRatio) + .opacity(viewModel.mode.isCrop ? 1 : 0) + .allowsHitTesting(viewModel.mode.isCrop) + + if viewModel.editingStack.isLoading { + ProgressView() + .progressViewStyle(.circular) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(PixelEditorColor.background.opacity(0.5)) + } + } + .clipped() + } +} + +private struct PixelEditorFreeCropGuideOverlay: View { + + let isAdjustmentActive: Bool + + var body: some View { + ZStack { + Rectangle() + .stroke(PixelEditorColor.cropGuide, lineWidth: 1) + + GeometryReader { proxy in + PixelEditorCropGuideGrid() + .stroke(PixelEditorColor.cropGuide.opacity(0.3), lineWidth: 1) + .opacity(isAdjustmentActive ? 1 : 0) + + PixelEditorCropGuideHandles() + .stroke( + PixelEditorColor.cropGuide, + style: StrokeStyle(lineWidth: 3, lineCap: .square, lineJoin: .miter) + ) + .frame(width: proxy.size.width, height: proxy.size.height) + } + } + .allowsHitTesting(false) + .animation(.easeInOut(duration: 0.2), value: isAdjustmentActive) + } +} + +private struct PixelEditorCropGuideGrid: Shape { + + func path(in rect: CGRect) -> Path { + Path { path in + let oneThirdX = rect.width / 3 + let oneThirdY = rect.height / 3 + + for index in 1...2 { + let x = rect.minX + oneThirdX * CGFloat(index) + path.move(to: CGPoint(x: x, y: rect.minY)) + path.addLine(to: CGPoint(x: x, y: rect.maxY)) + + let y = rect.minY + oneThirdY * CGFloat(index) + path.move(to: CGPoint(x: rect.minX, y: y)) + path.addLine(to: CGPoint(x: rect.maxX, y: y)) + } + } + } +} + +private struct PixelEditorCropGuideHandles: Shape { + + func path(in rect: CGRect) -> Path { + Path { path in + let length: CGFloat = 20 + + path.move(to: rect.origin) + path.addLine(to: CGPoint(x: rect.minX + length, y: rect.minY)) + path.move(to: rect.origin) + path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + length)) + + path.move(to: CGPoint(x: rect.maxX, y: rect.minY)) + path.addLine(to: CGPoint(x: rect.maxX - length, y: rect.minY)) + path.move(to: CGPoint(x: rect.maxX, y: rect.minY)) + path.addLine(to: CGPoint(x: rect.maxX, y: rect.minY + length)) + + path.move(to: CGPoint(x: rect.maxX, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.maxX - length, y: rect.maxY)) + path.move(to: CGPoint(x: rect.maxX, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - length)) + + path.move(to: CGPoint(x: rect.minX, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.minX + length, y: rect.maxY)) + path.move(to: CGPoint(x: rect.minX, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY - length)) + } + } +} + +private struct PixelEditorControlPanel: View { + + let viewModel: ClassicImageEditViewModel + @Binding var route: PixelEditorControlRoute + @Binding var displayedRootPanel: PixelEditorRootPanel + + var body: some View { + ZStack { + switch route { + case .root: + PixelEditorRootControl( + viewModel: viewModel, + displayedPanel: $displayedRootPanel, + onSelectRoute: showRoute + ) + .transition(.move(edge: .bottom).combined(with: .opacity)) + + case .crop: + PixelEditorCropControl( + viewModel: viewModel, + onCancel: { + viewModel.endCrop(save: false) + showRoute(.root) + }, + onDone: { + viewModel.endCrop(save: true) + showRoute(.root) + } + ) + .transition(.move(edge: .bottom).combined(with: .opacity)) + + case .masking: + PixelEditorMaskControl( + viewModel: viewModel, + onCancel: { + viewModel.endMasking(save: false) + showRoute(.root) + }, + onDone: { + viewModel.endMasking(save: true) + showRoute(.root) + } + ) + .transition(.move(edge: .bottom).combined(with: .opacity)) + + case let .filter(kind): + PixelEditorFilterControl( + viewModel: viewModel, + kind: kind, + onCancel: { + viewModel.editingStack.revertEdit() + showRoute(.root) + }, + onDone: { + viewModel.editingStack.takeSnapshot() + showRoute(.root) + } + ) + .transition(.move(edge: .bottom).combined(with: .opacity)) + } + } + .animation(.spring(response: 0.32, dampingFraction: 1), value: route) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + private func showRoute(_ route: PixelEditorControlRoute) { + withAnimation(.spring(response: 0.32, dampingFraction: 1)) { + self.route = route + } + } +} + +private struct PixelEditorRootControl: View { + + let viewModel: ClassicImageEditViewModel + @Binding var displayedPanel: PixelEditorRootPanel + let onSelectRoute: (PixelEditorControlRoute) -> Void + + var body: some View { + VStack(spacing: 0) { + Group { + switch displayedPanel { + case .filter: + PixelEditorPresetList(viewModel: viewModel) + + case .edit: + PixelEditorEditMenu( + viewModel: viewModel, + onSelectRoute: onSelectRoute + ) + } + } + .frame(height: 118) + + HStack(spacing: 0) { + Button { + displayedPanel = .filter + } label: { + Text(viewModel.localizedStrings.filter) + .font(.system(size: 17, weight: .bold)) + .foregroundStyle(displayedPanel == .filter ? PixelEditorColor.primary : PixelEditorColor.secondary) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .accessibilityIdentifier("swiftui.pixel.filter") + + Button { + displayedPanel = .edit + } label: { + Text(viewModel.localizedStrings.edit) + .font(.system(size: 17, weight: .bold)) + .foregroundStyle(displayedPanel == .edit ? PixelEditorColor.primary : PixelEditorColor.secondary) + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + .accessibilityIdentifier("swiftui.pixel.edit") + } + .frame(height: 50) + } + .onAppear { + viewModel.setMode(.preview) + } + } +} + +private struct PixelEditorPresetList: View { + + let viewModel: ClassicImageEditViewModel + + var body: some View { + ScrollViewReader { proxy in + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 16) { + Button { + setPreset(nil) + } label: { + PixelEditorPresetCell( + title: viewModel.localizedStrings.control_preset_normal_name, + image: viewModel.editingStack.loadedState?.thumbnailImage, + isSelected: currentPreset == nil + ) + } + .buttonStyle(.plain) + .id("normal") + + if let previews = viewModel.editingStack.loadedState?.previewFilterPresets { + ForEach(previews, id: \.filter.identifier) { preview in + Button { + setPreset(preview.filter) + } label: { + PixelEditorPresetCell( + title: preview.filter.name, + image: preview.image, + isSelected: currentPreset == preview.filter + ) + } + .buttonStyle(.plain) + .id(preview.filter.identifier) + } + } + } + .padding(.horizontal, 44) + .frame(minHeight: 100) + } + .onAppear { + scrollToSelection(proxy: proxy) + } + } + } + + private var currentPreset: FilterPreset? { + viewModel.editingStack.loadedState?.currentEdit.filters.preset + } + + private func setPreset(_ preset: FilterPreset?) { + viewModel.editingStack.set(filters: { + $0.preset = preset + }) + viewModel.editingStack.takeSnapshot() + } + + private func scrollToSelection(proxy: ScrollViewProxy) { + if let currentPreset { + proxy.scrollTo(currentPreset.identifier, anchor: .center) + } else { + proxy.scrollTo("normal", anchor: .center) + } + } +} + +private struct PixelEditorPresetCell: View { + + let title: String + let image: CIImage? + let isSelected: Bool + + var body: some View { + VStack(spacing: 12) { + PixelEditorMetalImageView(image: image, contentMode: .scaleAspectFill) + .frame(width: 64, height: 64) + .clipped() + + Text(title) + .font(.system(size: 12, weight: .medium)) + .foregroundStyle(isSelected ? PixelEditorColor.primary : PixelEditorColor.secondary) + .lineLimit(1) + .frame(width: 76) + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(title) + } +} + +private struct PixelEditorEditMenu: View { + + let viewModel: ClassicImageEditViewModel + let onSelectRoute: (PixelEditorControlRoute) -> Void + + var body: some View { + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 16) { + ForEach(displayedMenus, id: \.self) { menu in + Button { + onSelectRoute(menu.route) + } label: { + PixelEditorEditMenuCell( + title: menu.title(localizedStrings: viewModel.localizedStrings), + imageName: menu.imageName, + hasChanges: menu.hasChanges(in: viewModel.editingStack.loadedState?.currentEdit) + ) + } + .buttonStyle(.plain) + } + } + .padding(.horizontal, 36) + .frame(minHeight: 100) + } + } + + private var displayedMenus: [ClassicImageEditEditMenu] { + let control = viewModel.options.classes.control + return control.editMenus.filter { !control.ignoredEditMenus.contains($0) } + } +} + +private struct PixelEditorEditMenuCell: View { + + let title: String + let imageName: String + let hasChanges: Bool + + var body: some View { + VStack(spacing: 10) { + ZStack(alignment: .topTrailing) { + Image(uiImage: UIImage(named: imageName, in: bundle, compatibleWith: nil) ?? UIImage()) + .renderingMode(.template) + .resizable() + .scaledToFit() + .foregroundStyle(PixelEditorColor.primary) + .frame(width: 50, height: 50) + + if hasChanges { + Circle() + .fill(PixelEditorColor.accent) + .frame(width: 7, height: 7) + .offset(x: -4, y: 4) + } + } + + Text(title) + .font(.system(size: 12, weight: .medium)) + .foregroundStyle(PixelEditorColor.secondary) + .lineLimit(1) + .frame(width: 76) + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(title) + } +} + +private struct PixelEditorCropControl: View { + + let viewModel: ClassicImageEditViewModel + let onCancel: () -> Void + let onDone: () -> Void + + var body: some View { + VStack(spacing: 0) { + Spacer(minLength: 0) + PixelEditorControlNavigation( + cancelText: viewModel.localizedStrings.cancel, + doneText: viewModel.localizedStrings.done, + onCancel: onCancel, + onDone: onDone + ) + } + .onAppear { + viewModel.setMode(.crop) + } + } +} + +private struct PixelEditorMaskControl: View { + + let viewModel: ClassicImageEditViewModel + let onCancel: () -> Void + let onDone: () -> Void + + var body: some View { + VStack(spacing: 0) { + VStack(spacing: 16) { + Button(viewModel.localizedStrings.clear) { + viewModel.editingStack.set(blurringMaskPaths: []) + viewModel.editingStack.takeSnapshot() + } + .font(.system(size: 17, weight: .bold)) + .foregroundStyle(PixelEditorColor.primary) + + HStack(spacing: 10) { + Text(viewModel.localizedStrings.brushSizeSmall) + .font(.system(size: 18, weight: .medium)) + .foregroundStyle(PixelEditorColor.primary) + + PixelEditorStepSlider( + value: brushSliderValue, + range: -0.5...0.5, + mode: .plusAndMinus, + onChange: { value in + let position = CGFloat(value + 0.5) + let size = (5 + position * (50 - 5)).rounded() + viewModel.setBrushSize(size) + } + ) + .frame(height: 44) + + Text(viewModel.localizedStrings.brushSizeLarge) + .font(.system(size: 18, weight: .medium)) + .foregroundStyle(PixelEditorColor.primary) + } + .padding(.horizontal, 36) + + Circle() + .stroke(PixelEditorColor.primary, lineWidth: 1) + .background(Circle().fill(PixelEditorColor.background)) + .frame(width: brushSize, height: brushSize) + .frame(width: 50, height: 50) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + + PixelEditorControlNavigation( + cancelText: viewModel.localizedStrings.cancel, + doneText: viewModel.localizedStrings.done, + onCancel: onCancel, + onDone: onDone + ) + } + .onAppear { + viewModel.setMode(.masking) + } + } + + private var brushSize: CGFloat { + switch viewModel.maskingBrushSize { + case let .point(value), let .pixel(value): + return value + } + } + + private var brushSliderValue: Double { + Double((brushSize - 5) / (50 - 5)) - 0.5 + } +} + +private struct PixelEditorFilterControl: View { + + let viewModel: ClassicImageEditViewModel + let kind: PixelEditorFilterKind + let onCancel: () -> Void + let onDone: () -> Void + + var body: some View { + VStack(spacing: 0) { + PixelEditorStepSlider( + value: kind.value(in: viewModel.editingStack.loadedState?.currentEdit.filters), + range: kind.range, + mode: kind.sliderMode, + onChange: { + kind.setValue($0, editingStack: viewModel.editingStack) + } + ) + .frame(height: 44) + .padding(.horizontal, PixelEditorLayout.controlHorizontalMargin) + .frame(maxWidth: .infinity, maxHeight: .infinity) + + PixelEditorControlNavigation( + cancelText: viewModel.localizedStrings.cancel, + doneText: viewModel.localizedStrings.done, + onCancel: onCancel, + onDone: onDone + ) + } + .onAppear { + viewModel.setMode(.editing) + viewModel.setTitle(kind.title(localizedStrings: viewModel.localizedStrings)) + } + } +} + +private struct PixelEditorStepSlider: View { + + enum Mode: Hashable { + case plus + case plusAndMinus + case minus + } + + let value: Double + let range: ClosedRange + let mode: Mode + let onChange: (Double) -> Void + + var body: some View { + PixelEditorPrecisionLevelSlider( + value: positionBinding, + haptics: .init(trigger: { position in + step(forPosition: position) == 0 ? .selection : nil + }), + range: .init( + range: mode.positionRange, + transform: { position in + sliderPosition(forEditingValue: editingValue(forPosition: position)) + } + ), + centerLevel: { _, _ in + HStack { + Spacer() + VStack { + Spacer(minLength: 12) + Rectangle() + .frame(width: 1) + Spacer(minLength: 12) + } + Spacer() + } + .foregroundStyle(.tint) + }, + track: { position, _ in + PixelEditorStepSliderTickTrack(value: position) + } + ) + .tint(PixelEditorColor.primary) + .id(mode) + } + + private var positionBinding: Binding { + Binding( + get: { + sliderPosition(forEditingValue: value) + }, + set: { position in + let newValue = editingValue(forPosition: position) + guard newValue != value else { return } + onChange(newValue) + } + ) + } + + private func sliderPosition(forEditingValue value: Double) -> Double { + guard value != 0 else { return 0 } + + if value > 0 { + guard range.upperBound != 0 else { return 0 } + let ratio = (value / range.upperBound).clamped(to: 0...1) + return PixelEditorStepSliderMetrics.deadZone + ratio * (mode.positionRange.upperBound - PixelEditorStepSliderMetrics.deadZone) + } else { + guard range.lowerBound != 0 else { return 0 } + let ratio = (value / range.lowerBound).clamped(to: 0...1) + return -PixelEditorStepSliderMetrics.deadZone + ratio * (mode.positionRange.lowerBound + PixelEditorStepSliderMetrics.deadZone) + } + } + + private func editingValue(forPosition position: Double) -> Double { + if (-PixelEditorStepSliderMetrics.deadZone...PixelEditorStepSliderMetrics.deadZone).contains(position) { + return 0 + } + + if position > 0 { + let ratio = ((position - PixelEditorStepSliderMetrics.deadZone) / (mode.positionRange.upperBound - PixelEditorStepSliderMetrics.deadZone)) + .clamped(to: 0...1) + let step = (ratio * Double(mode.maxStep)).rounded() + return range.upperBound * step / Double(mode.maxStep) + } else { + let ratio = ((position + PixelEditorStepSliderMetrics.deadZone) / (mode.positionRange.lowerBound + PixelEditorStepSliderMetrics.deadZone)) + .clamped(to: 0...1) + let step = (ratio * Double(abs(mode.minStep))).rounded() + return range.lowerBound * step / Double(abs(mode.minStep)) + } + } + + private func step(for value: Double) -> Int { + let step: Int + + if value > 0 { + guard range.upperBound != 0 else { return 0 } + step = Int((value / range.upperBound * Double(mode.maxStep)).rounded()) + } else if value < 0 { + guard range.lowerBound != 0 else { return 0 } + step = -Int((value / range.lowerBound * Double(abs(mode.minStep))).rounded()) + } else { + step = 0 + } + + return step + } + + private func step(forPosition position: Double) -> Int { + step(for: editingValue(forPosition: position)) + } +} + +private struct PixelEditorPrecisionLevelSlider: UIViewRepresentable { + + @Binding var value: Double + + let haptics: PrecisionLevelSlider.Haptics? + let range: PrecisionLevelSlider.ValueRange + let centerLevel: (Double, Bool) -> CenterLevel + let track: (Double, Bool) -> Track + + init( + value: Binding, + haptics: PrecisionLevelSlider.Haptics?, + range: PrecisionLevelSlider.ValueRange, + @ViewBuilder centerLevel: @escaping (Double, Bool) -> CenterLevel, + @ViewBuilder track: @escaping (Double, Bool) -> Track + ) { + self._value = value + self.haptics = haptics + self.range = range + self.centerLevel = centerLevel + self.track = track + } + + func makeUIView(context: Context) -> PrecisionLevelSlider { + let view = PrecisionLevelSlider( + range: range, + haptics: haptics, + centerLevel: centerLevel, + track: track + ) + + view.onChangeValue = { value in + Task { @MainActor in + self.value = value + } + } + + return view + } + + func updateUIView(_ uiView: PrecisionLevelSlider, context: Context) { + uiView.range = range + + guard uiView.value != value else { + return + } + + uiView.value = value + } +} + +private enum PixelEditorStepSliderMetrics { + static let deadZone: Double = 0.05 +} + +private struct PixelEditorStepSliderTickTrack: View { + + let value: Double + + var body: some View { + VStack { + HStack { + Spacer() + Circle() + .frame(width: 6, height: 6) + .opacity(value == 0 ? 0 : 1) + .animation(.spring, value: value == 0) + Spacer() + } + + HStack(spacing: 0) { + ForEach(0..<4) { _ in + PixelEditorStepSliderShortBar() + .foregroundStyle(.primary) + Group { + Spacer(minLength: 0) + PixelEditorStepSliderShortBar() + Spacer(minLength: 0) + PixelEditorStepSliderShortBar() + Spacer(minLength: 0) + PixelEditorStepSliderShortBar() + Spacer(minLength: 0) + PixelEditorStepSliderShortBar() + Spacer(minLength: 0) + PixelEditorStepSliderShortBar() + Spacer(minLength: 0) + PixelEditorStepSliderShortBar() + Spacer(minLength: 0) + PixelEditorStepSliderShortBar() + Spacer(minLength: 0) + PixelEditorStepSliderShortBar() + Spacer(minLength: 0) + PixelEditorStepSliderShortBar() + Spacer(minLength: 0) + } + .foregroundStyle(.secondary) + } + PixelEditorStepSliderShortBar() + .foregroundStyle(.primary) + } + } + .foregroundStyle(.tint) + } +} + +private struct PixelEditorStepSliderShortBar: View { + var body: some View { + RoundedRectangle(cornerRadius: 8) + .frame(width: 1, height: 10) + } +} + +private extension PixelEditorStepSlider.Mode { + + var positionRange: ClosedRange { + switch self { + case .plus: + return 0...1 + case .plusAndMinus: + return -1...1 + case .minus: + return -1...0 + } + } + + var minStep: Int { + switch self { + case .plus: + return 0 + case .plusAndMinus, .minus: + return -100 + } + } + + var maxStep: Int { + switch self { + case .plus, .plusAndMinus: + return 100 + case .minus: + return 0 + } + } +} + +private struct PixelEditorControlNavigation: View { + + let cancelText: String + let doneText: String + let onCancel: () -> Void + let onDone: () -> Void + + var body: some View { + HStack(spacing: 0) { + Button(cancelText, action: onCancel) + .font(.system(size: 17, weight: .regular)) + .foregroundStyle(PixelEditorColor.primary) + .buttonStyle(.plain) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .accessibilityIdentifier("swiftui.pixel.control.cancel") + + Button(doneText, action: onDone) + .font(.system(size: 17, weight: .bold)) + .foregroundStyle(PixelEditorColor.primary) + .buttonStyle(.plain) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .accessibilityIdentifier("swiftui.pixel.control.done") + } + .frame(height: 50) + } +} + +private struct PixelEditorImagePreviewRepresentable: UIViewRepresentable { + + let editingStack: EditingStack + + func makeUIView(context: Context) -> ImagePreviewView { + ImagePreviewView(editingStack: editingStack) + } + + func updateUIView(_ uiView: ImagePreviewView, context: Context) {} +} + +private struct PixelEditorMetalImageView: UIViewRepresentable { + + let image: CIImage? + let contentMode: UIView.ContentMode + + func makeUIView(context: Context) -> MetalImageView { + let view = MetalImageView() + view.clipsToBounds = true + view.contentMode = contentMode + return view + } + + func updateUIView(_ uiView: MetalImageView, context: Context) { + uiView.contentMode = contentMode + uiView.display(image: image) + } +} + +private extension ClassicImageEditViewModel.Mode { + + var isCrop: Bool { + switch self { + case .crop: + return true + case .masking, .editing, .preview: + return false + } + } + + var isMasking: Bool { + switch self { + case .masking: + return true + case .crop, .editing, .preview: + return false + } + } + + var isEditing: Bool { + switch self { + case .editing: + return true + case .crop, .masking, .preview: + return false + } + } + + var displaysMaskingView: Bool { + switch self { + case .masking, .preview: + return true + case .crop, .editing: + return false + } + } +} + +private extension ClassicImageEditEditMenu { + + var route: PixelEditorControlRoute { + switch self { + case .adjustment: + return .crop + case .mask: + return .masking + case .exposure: + return .filter(.exposure) + case .gaussianBlur: + return .filter(.gaussianBlur) + case .contrast: + return .filter(.contrast) + case .temperature: + return .filter(.temperature) + case .saturation: + return .filter(.saturation) + case .highlights: + return .filter(.highlights) + case .shadows: + return .filter(.shadows) + case .vignette: + return .filter(.vignette) + case .fade: + return .filter(.fade) + case .sharpen: + return .filter(.sharpen) + case .clarity: + return .filter(.clarity) + } + } + + var imageName: String { + switch self { + case .adjustment: + return "adjustment" + case .mask: + return "mask" + case .exposure: + return "brightness" + case .gaussianBlur: + return "blur" + case .contrast: + return "contrast" + case .temperature: + return "temperature" + case .saturation: + return "saturation" + case .highlights: + return "highlights" + case .shadows: + return "shadows" + case .vignette: + return "vignette" + case .fade: + return "fade" + case .sharpen: + return "sharpen" + case .clarity: + return "structure" + } + } + + func title(localizedStrings: ClassicImageEditViewController.LocalizedStrings) -> String { + switch self { + case .adjustment: + return localizedStrings.editAdjustment + case .mask: + return localizedStrings.editMask + case .exposure: + return localizedStrings.editBrightness + case .gaussianBlur: + return localizedStrings.editBlur + case .contrast: + return localizedStrings.editContrast + case .temperature: + return localizedStrings.editTemperature + case .saturation: + return localizedStrings.editSaturation + case .highlights: + return localizedStrings.editHighlights + case .shadows: + return localizedStrings.editShadows + case .vignette: + return localizedStrings.editVignette + case .fade: + return localizedStrings.editFade + case .sharpen: + return localizedStrings.editSharpen + case .clarity: + return localizedStrings.editClarity + } + } + + func hasChanges(in edit: EditingStack.Edit?) -> Bool { + guard let edit else { + return false + } + + switch self { + case .adjustment: + return false + case .mask: + return !edit.drawings.blurredMaskPaths.isEmpty + case .exposure: + return edit.filters.exposure != nil + case .gaussianBlur: + return edit.filters.gaussianBlur != nil + case .contrast: + return edit.filters.contrast != nil + case .temperature: + return edit.filters.temperature != nil + case .saturation: + return edit.filters.saturation != nil + case .highlights: + return edit.filters.highlights != nil + case .shadows: + return edit.filters.shadows != nil + case .vignette: + return edit.filters.vignette != nil + case .fade: + return edit.filters.fade != nil + case .sharpen: + return edit.filters.sharpen != nil + case .clarity: + return edit.filters.unsharpMask != nil + } + } +} + +private extension PixelEditorFilterKind { + + var range: ClosedRange { + switch self { + case .exposure: + return FilterExposure.range.min...FilterExposure.range.max + case .gaussianBlur: + return FilterGaussianBlur.range.min...FilterGaussianBlur.range.max + case .contrast: + return FilterContrast.range.min...FilterContrast.range.max + case .temperature: + return FilterTemperature.range.min...FilterTemperature.range.max + case .saturation: + return FilterSaturation.range.min...FilterSaturation.range.max + case .highlights: + return FilterHighlights.range.min...FilterHighlights.range.max + case .shadows: + return FilterShadows.range.min...FilterShadows.range.max + case .vignette: + return FilterVignette.range.min...FilterVignette.range.max + case .fade: + return FilterFade.Params.intensity.min...FilterFade.Params.intensity.max + case .sharpen: + return FilterSharpen.Params.sharpness.min...FilterSharpen.Params.sharpness.max + case .clarity: + return FilterUnsharpMask.Params.intensity.min...FilterUnsharpMask.Params.intensity.max + } + } + + var sliderMode: PixelEditorStepSlider.Mode { + switch self { + case .gaussianBlur, .highlights, .vignette, .fade, .sharpen, .clarity: + return .plus + case .exposure, .contrast, .temperature, .saturation, .shadows: + return .plusAndMinus + } + } + + func title(localizedStrings: ClassicImageEditViewController.LocalizedStrings) -> String { + switch self { + case .exposure: + return localizedStrings.editBrightness + case .gaussianBlur: + return localizedStrings.editBlur + case .contrast: + return localizedStrings.editContrast + case .temperature: + return localizedStrings.editTemperature + case .saturation: + return localizedStrings.editSaturation + case .highlights: + return localizedStrings.editHighlights + case .shadows: + return localizedStrings.editShadows + case .vignette: + return localizedStrings.editVignette + case .fade: + return localizedStrings.editFade + case .sharpen: + return localizedStrings.editSharpen + case .clarity: + return localizedStrings.editClarity + } + } + + func value(in filters: EditingStack.Edit.Filters?) -> Double { + guard let filters else { + return 0 + } + + switch self { + case .exposure: + return filters.exposure?.value ?? 0 + case .gaussianBlur: + return filters.gaussianBlur?.value ?? 0 + case .contrast: + return filters.contrast?.value ?? 0 + case .temperature: + return filters.temperature?.value ?? 0 + case .saturation: + return filters.saturation?.value ?? 0 + case .highlights: + return filters.highlights?.value ?? 0 + case .shadows: + return filters.shadows?.value ?? 0 + case .vignette: + return filters.vignette?.value ?? 0 + case .fade: + return filters.fade?.intensity ?? 0 + case .sharpen: + return filters.sharpen?.sharpness ?? 0 + case .clarity: + return filters.unsharpMask?.intensity ?? 0 + } + } + + func setValue(_ value: Double, editingStack: EditingStack) { + editingStack.set(filters: { filters in + switch self { + case .exposure: + filters.exposure = value.nonZeroFilter { FilterExposure(value: $0) } + case .gaussianBlur: + filters.gaussianBlur = value.nonZeroFilter { FilterGaussianBlur(value: $0) } + case .contrast: + filters.contrast = value.nonZeroFilter { FilterContrast(value: $0) } + case .temperature: + filters.temperature = value.nonZeroFilter { FilterTemperature(value: $0) } + case .saturation: + filters.saturation = value.nonZeroFilter { FilterSaturation(value: $0) } + case .highlights: + filters.highlights = value.nonZeroFilter { FilterHighlights(value: $0) } + case .shadows: + filters.shadows = value.nonZeroFilter { FilterShadows(value: $0) } + case .vignette: + filters.vignette = value.nonZeroFilter { FilterVignette(value: $0) } + case .fade: + filters.fade = value.nonZeroFilter { FilterFade(intensity: $0) } + case .sharpen: + filters.sharpen = value.nonZeroFilter { + var filter = FilterSharpen() + filter.sharpness = $0 + filter.radius = 1.2 + return filter + } + case .clarity: + filters.unsharpMask = value.nonZeroFilter { + var filter = FilterUnsharpMask() + filter.intensity = $0 + filter.radius = 0.12 + return filter + } + } + }) + } +} + +private extension Double { + + func clamped(to range: ClosedRange) -> Double { + min(max(self, range.lowerBound), range.upperBound) + } + + func nonZeroFilter(_ makeFilter: (Double) -> Filter) -> Filter? { + guard self != 0 else { + return nil + } + return makeFilter(self) + } +} + +private extension ClosedRange where Bound == Double { + + var length: Double { + upperBound - lowerBound + } +} + +private extension FilterExposure { + init(value: Double) { + self.init() + self.value = value + } +} + +private extension FilterGaussianBlur { + init(value: Double) { + self.init() + self.value = value + } +} + +private extension FilterContrast { + init(value: Double) { + self.init() + self.value = value + } +} + +private extension FilterTemperature { + init(value: Double) { + self.init() + self.value = value + } +} + +private extension FilterSaturation { + init(value: Double) { + self.init() + self.value = value + } +} + +private extension FilterHighlights { + init(value: Double) { + self.init() + self.value = value + } +} + +private extension FilterShadows { + init(value: Double) { + self.init() + self.value = value + } +} + +private extension FilterVignette { + init(value: Double) { + self.init() + self.value = value + } +} + +private extension FilterFade { + init(intensity: Double) { + self.init() + self.intensity = intensity + } +} diff --git a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift deleted file mode 100644 index 15eeee71..00000000 --- a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropAspectRatioControl.swift +++ /dev/null @@ -1,525 +0,0 @@ -// -// Copyright (c) 2021 Muukii -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -// THE SOFTWARE. - -import UIKit -import SwiftUI - -import Combine -import StateGraph - -#if !COCOAPODS -import BrightroomEngine -#endif - -final class PhotosCropAspectRatioControl: PixelEditorCodeBasedView { - - enum Direction: Equatable { - /** - +----+ - | | - | | - | | - | | - | | - | | - +----+ - */ - case vertical - /** - +---------------+ - | | - | | - +---------------+ - */ - case horizontal - - mutating func swap() { - switch self { - case .horizontal: self = .vertical - case .vertical: self = .horizontal - } - } - } - - /** - A rectangle whose width is longer than its height - +---------------+ - | | - | | - +---------------+ - */ - let horizontalRectangleApectRatios: [PixelAspectRatio] = [ - .init(width: 16, height: 9), - .init(width: 10, height: 8), - .init(width: 7, height: 5), - .init(width: 4, height: 3), - .init(width: 5, height: 3), - .init(width: 3, height: 2), - ] - - let originalAspectRatio: PixelAspectRatio - let originalDirection: Direction - - var selectedAspectRatio: PixelAspectRatio? { - didSet { - guard oldValue != selectedAspectRatio else { return } - - if let selectedAspectRatio { - direction = selectedAspectRatio.height > selectedAspectRatio.width ? .vertical : .horizontal - } - } - } - - var direction: Direction { - didSet { - if let selectedAspectRatio = selectedAspectRatio { - if direction != selectedAspectRatio.direction { - self.selectedAspectRatio = selectedAspectRatio.swapped() - } - } - } - } - - var canSelectDirection: Bool { - guard let selectedAspectRatio = selectedAspectRatio else { - return false - } - - guard selectedAspectRatio != .square else { - return false - } - - return true - } - - struct Handlers { - - var didSelectAspectRatio: (PixelAspectRatio) -> Void = { _ in } - var didSelectFreeform: () -> Void = {} - - } - - var handlers: Handlers = .init() - - private let horizontalButton = AspectRatioDirectionButton(direction: .horizontal) - private let verticalButton = AspectRatioDirectionButton(direction: .vertical) - - private let scrollView = UIScrollView() - - private let originalButton = AspectRatioButton() - private let freeformButton = AspectRatioButton() - private let aspectSquareButton = AspectRatioButton() - - private var subscriptions: Set = .init() - - // Change tracking - private var _previousSelectedAspectRatio: PixelAspectRatio?? - private var _previousCanSelectDirection: Bool? - private var _previousDirection: Direction? - - private var isSupressingHandlers = false - private let localizedStrings: PhotosCropViewController.LocalizedStrings - - private var buttons: [CGFloat: UIButton] = [:] - - init( - originalAspectRatio: PixelAspectRatio, - localizedStrings: PhotosCropViewController.LocalizedStrings - ) { - - self.localizedStrings = localizedStrings - self.originalAspectRatio = originalAspectRatio - self.originalDirection = originalAspectRatio.width > originalAspectRatio.height ? .horizontal : .vertical - self.direction = originalAspectRatio.width > originalAspectRatio.height ? .horizontal : .vertical - - super.init(frame: .zero) - - let directionStackView = UIStackView()&>.do { - $0.addArrangedSubview(verticalButton) - $0.addArrangedSubview(horizontalButton) - $0.distribution = .equalCentering - $0.spacing = 16 - $0.alignment = .center - } - - buttons[ratioValue(from: originalAspectRatio)] = originalButton - buttons[ratioValue(from: .square)] = aspectSquareButton - - let itemsStackView = UIStackView()&>.do { stackView in - - stackView.spacing = 12 - - [ - originalButton, - freeformButton, - aspectSquareButton, - ] - .forEach { - stackView.addArrangedSubview($0) - } - - horizontalRectangleApectRatios.forEach { ratio in - - let button = AspectRatioButton() - - stackView.addArrangedSubview(button) - - button.onTap { [weak self] in - guard let self = self else { return } - - switch self.direction { - case .horizontal: - self.selectedAspectRatio = ratio - case .vertical: - self.selectedAspectRatio = ratio.swapped() - } - } - - buttons[ratioValue(from: ratio)] = button - } - } - - horizontalButton.onTap { [weak self] in - self?.direction = .horizontal - } - - verticalButton.onTap { [weak self] in - self?.direction = .vertical - } - - originalButton&>.do { - $0.setTitle(localizedStrings.button_aspectratio_original, for: .normal) - $0.onTap { [weak self] in - guard let self = self else { return } - self.selectedAspectRatio = self.originalAspectRatio - } - - } - - freeformButton&>.do { - $0.setTitle(localizedStrings.button_aspectratio_freeform, for: .normal) - $0.onTap { [weak self] in - self?.selectedAspectRatio = nil - } - } - - aspectSquareButton&>.do { - $0.setTitle(localizedStrings.button_aspectratio_square, for: .normal) - $0.onTap { [weak self] in - self?.selectedAspectRatio = .square - } - } - - addSubview(directionStackView) - addSubview(scrollView) - scrollView.addSubview(itemsStackView) - - itemsStackView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - itemsStackView.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor), - itemsStackView.rightAnchor.constraint(equalTo: scrollView.contentLayoutGuide.rightAnchor), - itemsStackView.leftAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leftAnchor), - itemsStackView.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor), - - itemsStackView.topAnchor.constraint(equalTo: scrollView.frameLayoutGuide.topAnchor), - itemsStackView.bottomAnchor.constraint(equalTo: scrollView.frameLayoutGuide.bottomAnchor), - ]) - - directionStackView&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: topAnchor, constant: 24), - $0.rightAnchor.constraint(lessThanOrEqualTo: rightAnchor), - $0.leftAnchor.constraint(greaterThanOrEqualTo: leftAnchor), - $0.centerXAnchor.constraint(equalTo: centerXAnchor), - ]) - } - - scrollView&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: directionStackView.bottomAnchor, constant: 24), - $0.rightAnchor.constraint(equalTo: rightAnchor), - $0.leftAnchor.constraint(equalTo: leftAnchor), - $0.bottomAnchor.constraint(equalTo:bottomAnchor), - ]) - $0.contentInset = .init(top: 0, left: 24, bottom: 0, right: 24) - $0.showsHorizontalScrollIndicator = false - } - - withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self = self else { return } - - if self._previousSelectedAspectRatio != self.selectedAspectRatio { - self._previousSelectedAspectRatio = self.selectedAspectRatio - let selected = self.selectedAspectRatio - - guard let selected = selected else { - // Freeform - - self.buttons.forEach { - $0.value.isSelected = false - } - - self.freeformButton.isSelected = true - - self.handlers.didSelectFreeform() - - return - } - - self.freeformButton.isSelected = false - - self.buttons.forEach { - if $0.key == ratioValue(from: selected) { - $0.value.isSelected = true - } else { - $0.value.isSelected = false - } - } - - self.handlers.didSelectAspectRatio(selected) - } - - if self._previousCanSelectDirection != self.canSelectDirection { - self._previousCanSelectDirection = self.canSelectDirection - let canSelectDirection = self.canSelectDirection - - self.horizontalButton.isEnabled = canSelectDirection - self.verticalButton.isEnabled = canSelectDirection - } - - if self._previousDirection != self.direction { - self._previousDirection = self.direction - let direction = self.direction - - /// Changes display according to image's rectangle direction. - switch direction { - case .horizontal: - - self.horizontalButton.isSelected = true - self.verticalButton.isSelected = false - - self.horizontalRectangleApectRatios.forEach { ratio in - self.buttons[ratioValue(from: ratio)]?.setTitle("\(Int(ratio.width)):\(Int(ratio.height))", for: .normal) - } - - case .vertical: - - self.horizontalButton.isSelected = false - self.verticalButton.isSelected = true - - self.horizontalRectangleApectRatios.forEach { ratio in - self.buttons[ratioValue(from: ratio)]?.setTitle("\(Int(ratio.height)):\(Int(ratio.width))", for: .normal) - } - } - } - } - } - .store(in: &subscriptions) - } - - func setSelected(_ aspectRatio: PixelAspectRatio?) { - - guard let fixed = aspectRatio else { - if selectedAspectRatio != nil { - selectedAspectRatio = nil - } - return - } - - if selectedAspectRatio != fixed { - selectedAspectRatio = fixed - } - } - -} - -private final class AspectRatioButton: UIButton { - - private let backdropView = UIView() - - override var isSelected: Bool { - didSet { - setNeedsLayout() - } - } - - convenience init() { - self.init(frame: .zero) - - setTitleColor(.init(white: 1, alpha: 0.5), for: .normal) - setTitleColor(.white, for: .selected) - titleLabel?.font = .systemFont(ofSize: 12) - - } - - override init(frame: CGRect) { - super.init(frame: .zero) - - if #available(iOS 13.0, *) { - backdropView.layer.cornerCurve = .continuous - } else { - // Fallback on earlier versions - } - - backdropView.backgroundColor = .init(white: 1, alpha: 0.5) - - titleEdgeInsets = .init(top: -2, left: 6, bottom: -2, right: 6) - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layoutSubviews() { - super.layoutSubviews() - - insertSubview(backdropView, at: 0) - backdropView.frame = bounds - backdropView.layer.cornerRadius = bounds.height / 2 - - backdropView.isHidden = !isSelected - } - - override var intrinsicContentSize: CGSize { - let originalContentSize = super.intrinsicContentSize - let adjustedWidth = originalContentSize.width + titleEdgeInsets.left + titleEdgeInsets.right - let adjustedHeight = originalContentSize.height + titleEdgeInsets.top + titleEdgeInsets.bottom - return CGSize(width: adjustedWidth, height: adjustedHeight) - } - -} - -private final class AspectRatioDirectionButton: UIControl { - - private let shapeLayer = CAShapeLayer() - private let iconImageView = UIImageView(image: UIImage(named: "check", in: bundle, compatibleWith: nil)) - - override var isHighlighted: Bool { - didSet { - guard oldValue != isHighlighted else { - return - } - - UIViewPropertyAnimator(duration: 0.4, dampingRatio: 1) { [self] in - self.alpha = isHighlighted ? 0.4 : 1 - } - .startAnimation() - } - } - - override var isEnabled: Bool { - didSet { - update() - } - } - - override var isSelected: Bool { - didSet { - update() - } - } - - init(direction: PhotosCropAspectRatioControl.Direction) { - super.init(frame: .zero) - - layer.addSublayer(shapeLayer) - iconImageView.tintColor = UIColor(white: 0, alpha: 0.8) - - addSubview(iconImageView) - - switch direction { - case .horizontal: - iconImageView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - iconImageView.topAnchor.constraint(equalTo: topAnchor, constant: 2), - iconImageView.rightAnchor.constraint(equalTo: rightAnchor, constant: -6), - iconImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 6), - iconImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -2), - ]) - - case .vertical: - iconImageView.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - iconImageView.topAnchor.constraint(equalTo: topAnchor, constant: 6), - iconImageView.rightAnchor.constraint(equalTo: rightAnchor, constant: -2), - iconImageView.leftAnchor.constraint(equalTo: leftAnchor, constant: 2), - iconImageView.bottomAnchor.constraint(equalTo: bottomAnchor, constant: -6), - ]) - - } - - update() - } - - required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func layoutSubviews() { - super.layoutSubviews() - shapeLayer.frame = bounds - - let path = UIBezierPath(roundedRect: bounds, cornerRadius: 4) - shapeLayer.path = path.cgPath - } - - private func update() { - - guard isEnabled else { - shapeLayer.strokeColor = UIColor(white: 0.6, alpha: 0.3).cgColor - shapeLayer.fillColor = UIColor(white: 0, alpha: 0.3).cgColor - iconImageView.isHidden = true - return - } - - if isSelected { - - shapeLayer.strokeColor = UIColor(white: 0.6, alpha: 1).cgColor - shapeLayer.fillColor = UIColor(white: 0.6, alpha: 1).cgColor - iconImageView.isHidden = false - - } else { - shapeLayer.strokeColor = UIColor(white: 0.6, alpha: 1).cgColor - shapeLayer.fillColor = UIColor(white: 0, alpha: 0.6).cgColor - iconImageView.isHidden = true - - } - - } - -} - -extension PixelAspectRatio { - var direction: PhotosCropAspectRatioControl.Direction { - if height > width { - return .vertical - } else { - return .horizontal - } - } -} - -func ratioValue(from ratio: PixelAspectRatio) -> CGFloat { - ratio.height * ratio.width -} diff --git a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropContentView.swift b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropContentView.swift new file mode 100644 index 00000000..6e8eb669 --- /dev/null +++ b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropContentView.swift @@ -0,0 +1,510 @@ +// +// Copyright (c) 2026 Muukii +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in +// all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +// THE SOFTWARE. + +import SwiftUI +import UIKit + +import BrightroomEngine + +struct PhotosCropContentView: View { + + let editingStack: EditingStack + let options: SwiftUIPhotosCropView.Options + let localizedStrings: SwiftUIPhotosCropView.LocalizedStrings + let onDone: @MainActor () -> Void + let onCancel: @MainActor () -> Void + + @State private var cropState: CropView.StateSnapshot? + @State private var rotation: EditingCrop.Rotation? + @State private var croppingAspectRatio: PixelAspectRatio? + @State private var isSelectingAspectRatio = false + @State private var resetAction = SwiftUICropView.ResetAction() + @State private var applyAction = SwiftUICropView.ApplyAction() + + init( + editingStack: EditingStack, + options: SwiftUIPhotosCropView.Options, + localizedStrings: SwiftUIPhotosCropView.LocalizedStrings, + onDone: @escaping @MainActor () -> Void, + onCancel: @escaping @MainActor () -> Void + ) { + self.editingStack = editingStack + self.options = options + self.localizedStrings = localizedStrings + self.onDone = onDone + self.onCancel = onCancel + + switch options.aspectRatioOptions { + case .fixed(let aspectRatio): + self._croppingAspectRatio = State(initialValue: aspectRatio) + case .selectable: + self._croppingAspectRatio = State(initialValue: nil) + } + } + + var body: some View { + let loadedState = editingStack.loadedState + let originalAspectRatio = loadedState.map { PixelAspectRatio($0.imageSize) } + let isLoaded = loadedState != nil + let topBarHeight: CGFloat = 44 + let aspectRatioPickerHeight: CGFloat = 112 + let bottomBarHeight: CGFloat = 50 + + ZStack { + VStack(spacing: 0) { + Color.clear + .frame(height: topBarHeight) + + SwiftUICropView( + editingStack: editingStack, + isAutoApplyEditingStackEnabled: true, + stateHandler: handleCropState + ) + .rotation(rotation) + .croppingAspectRatio(croppingAspectRatio) + .registerResetAction(resetAction) + .registerApplyAction(applyAction) + .layoutPriority(1) + + Color.clear + .frame(height: aspectRatioPickerHeight + bottomBarHeight) + } + + VStack(spacing: 0) { + PhotosCropTopBar( + resetTitle: localizedStrings.button_reset_title, + isEnabled: isLoaded, + hasUncommitedChanges: loadedState?.hasUncommitedChanges ?? false, + isAspectRatioControlAvailable: isAspectRatioControlAvailable, + isSelectingAspectRatio: isSelectingAspectRatio, + onRotate: rotate, + onReset: reset, + onToggleAspectRatio: toggleAspectRatioControl + ) + .frame(height: topBarHeight) + .padding(.horizontal, 16) + + Spacer(minLength: 0) + + PhotosCropAspectRatioPicker( + originalAspectRatio: originalAspectRatio, + selectedAspectRatio: croppingAspectRatio, + localizedStrings: localizedStrings, + onSelect: selectAspectRatio + ) + .frame(height: aspectRatioPickerHeight) + .opacity(isSelectingAspectRatio && originalAspectRatio != nil ? 1 : 0) + .allowsHitTesting(isSelectingAspectRatio && originalAspectRatio != nil) + .animation(.spring(response: 0.35, dampingFraction: 1), value: isSelectingAspectRatio) + + PhotosCropBottomBar( + cancelTitle: localizedStrings.button_cancel_title, + doneTitle: localizedStrings.button_done_title, + isDoneEnabled: isLoaded, + onCancel: onCancel, + onDone: finish + ) + .frame(height: bottomBarHeight) + .padding(.horizontal, 16) + } + } + .background { + Color.black + .ignoresSafeArea() + } + .foregroundStyle(.white) + .task { + editingStack.start() + } + .onChange(of: isAspectRatioControlAvailable) { _, isAvailable in + if isAvailable == false { + isSelectingAspectRatio = false + } + } + .accessibilityIdentifier("photos.crop") + } + + private var isAspectRatioControlAvailable: Bool { + switch options.aspectRatioOptions { + case .selectable: + return true + case .fixed: + return false + } + } + + @MainActor + private func handleCropState(_ state: CropView.StateSnapshot) { + cropState = state + + if let proposedCrop = state.proposedCrop, rotation != proposedCrop.rotation { + rotation = proposedCrop.rotation + } + + if croppingAspectRatio != state.preferredAspectRatio { + croppingAspectRatio = state.preferredAspectRatio + } + } + + private func rotate() { + guard let proposedCrop = cropState?.proposedCrop else { + return + } + + rotation = proposedCrop.rotation.next() + + if let aspectRatio = cropState?.preferredAspectRatio { + croppingAspectRatio = aspectRatio.swapped() + } + } + + private func reset() { + switch options.aspectRatioOptions { + case .fixed(let aspectRatio): + croppingAspectRatio = aspectRatio + case .selectable: + croppingAspectRatio = nil + } + + resetAction() + } + + private func toggleAspectRatioControl() { + guard isAspectRatioControlAvailable else { + return + } + + isSelectingAspectRatio.toggle() + } + + private func selectAspectRatio(_ aspectRatio: PixelAspectRatio?) { + croppingAspectRatio = aspectRatio + } + + private func finish() { + applyAction() + onDone() + } +} + +private struct PhotosCropTopBar: View { + + let resetTitle: String + let isEnabled: Bool + let hasUncommitedChanges: Bool + let isAspectRatioControlAvailable: Bool + let isSelectingAspectRatio: Bool + let onRotate: () -> Void + let onReset: () -> Void + let onToggleAspectRatio: () -> Void + + var body: some View { + HStack { + Button(action: onRotate) { + Image(systemName: "rotate.left") + .font(.system(size: 22, weight: .regular)) + .symbolRenderingMode(.monochrome) + .foregroundStyle(Color(white: 0.6)) + .frame(width: 44, height: 44) + } + .buttonStyle(.plain) + .disabled(!isEnabled) + .accessibilityElement(children: .ignore) + .accessibilityLabel("Rotate") + .accessibilityIdentifier("photos.crop.rotate") + + Spacer() + + Button(action: onReset) { + Text(resetTitle) + .font(.system(size: 14)) + .foregroundStyle(Color(uiColor: .systemYellow)) + } + .buttonStyle(.plain) + .opacity(hasUncommitedChanges ? 1 : 0) + .disabled(!isEnabled || !hasUncommitedChanges) + .accessibilityIdentifier("photos.crop.reset") + + Spacer() + + Button(action: onToggleAspectRatio) { + Image(systemName: "aspectratio") + .font(.system(size: 22, weight: .regular)) + .symbolRenderingMode(.monochrome) + .foregroundStyle(isSelectingAspectRatio ? Color(uiColor: .systemYellow) : Color(white: 0.6)) + .frame(width: 44, height: 44) + } + .buttonStyle(.plain) + .opacity(isAspectRatioControlAvailable ? 1 : 0) + .disabled(!isEnabled || !isAspectRatioControlAvailable) + .accessibilityElement(children: .ignore) + .accessibilityLabel("Aspect Ratio") + .accessibilityIdentifier("photos.crop.aspect") + } + } +} + +private struct PhotosCropBottomBar: View { + + let cancelTitle: String + let doneTitle: String + let isDoneEnabled: Bool + let onCancel: () -> Void + let onDone: () -> Void + + var body: some View { + HStack { + Button(action: onCancel) { + Text(cancelTitle) + .font(.system(size: 17)) + .foregroundStyle(.white) + } + .buttonStyle(.plain) + .accessibilityIdentifier("photos.crop.cancel") + + Spacer() + + Button(action: onDone) { + Text(doneTitle) + .font(.system(size: 17)) + .foregroundStyle(isDoneEnabled ? Color(uiColor: .systemYellow) : Color(uiColor: .darkGray)) + } + .buttonStyle(.plain) + .disabled(!isDoneEnabled) + .accessibilityIdentifier("photos.crop.done") + } + } +} + +private struct PhotosCropAspectRatioPicker: View { + + let originalAspectRatio: PixelAspectRatio? + let selectedAspectRatio: PixelAspectRatio? + let localizedStrings: SwiftUIPhotosCropView.LocalizedStrings + let onSelect: (PixelAspectRatio?) -> Void + + var body: some View { + VStack(spacing: 24) { + HStack(spacing: 18) { + PhotosCropAspectRatioDirectionButton( + direction: .vertical, + selectedDirection: selectedDirection, + isEnabled: canSelectDirection, + onSelect: selectDirection + ) + + PhotosCropAspectRatioDirectionButton( + direction: .horizontal, + selectedDirection: selectedDirection, + isEnabled: canSelectDirection, + onSelect: selectDirection + ) + } + .frame(height: 28) + + ScrollView(.horizontal, showsIndicators: false) { + HStack(spacing: 12) { + if originalAspectRatio != nil { + PhotosCropAspectRatioButton( + title: localizedStrings.button_aspectratio_original, + isSelected: selectedAspectRatio == originalAspectRatioForCurrentDirection + ) { + onSelect(originalAspectRatioForCurrentDirection) + } + .accessibilityIdentifier("photos.crop.aspect.original") + } + + PhotosCropAspectRatioButton( + title: localizedStrings.button_aspectratio_freeform, + isSelected: selectedAspectRatio == nil + ) { + onSelect(nil) + } + .accessibilityIdentifier("photos.crop.aspect.freeform") + + PhotosCropAspectRatioButton( + title: localizedStrings.button_aspectratio_square, + isSelected: selectedAspectRatio == .square + ) { + onSelect(.square) + } + .accessibilityIdentifier("photos.crop.aspect.square") + + ForEach(Self.horizontalRectangleAspectRatios) { ratio in + let displayedRatio = displayedRatio(for: ratio) + + PhotosCropAspectRatioButton( + title: "\(Int(displayedRatio.width)):\(Int(displayedRatio.height))", + isSelected: selectedAspectRatio == displayedRatio + ) { + onSelect(displayedRatio) + } + .accessibilityIdentifier("photos.crop.aspect.\(Int(displayedRatio.width))x\(Int(displayedRatio.height))") + } + } + .padding(.horizontal, 24) + } + } + } + + private var selectedDirection: PhotosCropAspectRatioDirection { + selectedAspectRatio.map(PhotosCropAspectRatioDirection.init) ?? originalDirection + } + + private var originalDirection: PhotosCropAspectRatioDirection { + originalAspectRatio.map(PhotosCropAspectRatioDirection.init) ?? .horizontal + } + + private var canSelectDirection: Bool { + guard let selectedAspectRatio else { + return false + } + + guard selectedAspectRatio != .square else { + return false + } + + return true + } + + private var originalAspectRatioForCurrentDirection: PixelAspectRatio? { + guard let originalAspectRatio else { + return nil + } + + if PhotosCropAspectRatioDirection(originalAspectRatio) == selectedDirection { + return originalAspectRatio + } else { + return originalAspectRatio.swapped() + } + } + + private func displayedRatio(for horizontalRatio: PixelAspectRatio) -> PixelAspectRatio { + switch selectedDirection { + case .horizontal: + return horizontalRatio + case .vertical: + return horizontalRatio.swapped() + } + } + + private func selectDirection(_ direction: PhotosCropAspectRatioDirection) { + guard let selectedAspectRatio else { + return + } + + guard PhotosCropAspectRatioDirection(selectedAspectRatio) != direction else { + return + } + + onSelect(selectedAspectRatio.swapped()) + } + + private static let horizontalRectangleAspectRatios: [PixelAspectRatio] = [ + .init(width: 16, height: 9), + .init(width: 10, height: 8), + .init(width: 7, height: 5), + .init(width: 4, height: 3), + .init(width: 5, height: 3), + .init(width: 3, height: 2), + ] +} + +private struct PhotosCropAspectRatioButton: View { + + let title: String + let isSelected: Bool + let action: () -> Void + + var body: some View { + Button(action: action) { + Text(title) + .font(.system(size: 12)) + .foregroundStyle(isSelected ? Color.white : Color.white.opacity(0.5)) + .padding(.horizontal, 8) + .padding(.vertical, 3) + .background { + Capsule() + .fill(Color.white.opacity(0.5)) + .opacity(isSelected ? 1 : 0) + } + } + .buttonStyle(.plain) + } +} + +private struct PhotosCropAspectRatioDirectionButton: View { + + let direction: PhotosCropAspectRatioDirection + let selectedDirection: PhotosCropAspectRatioDirection + let isEnabled: Bool + let onSelect: (PhotosCropAspectRatioDirection) -> Void + + var body: some View { + Button { + onSelect(direction) + } label: { + ZStack { + RoundedRectangle(cornerRadius: 4) + .fill(isSelected ? Color(white: 0.6) : Color.black.opacity(0.6)) + + RoundedRectangle(cornerRadius: 4) + .stroke(Color(white: 0.6).opacity(isEnabled ? 1 : 0.3), lineWidth: 1) + + if isSelected && isEnabled { + Image(systemName: "checkmark") + .font(.system(size: 10, weight: .bold)) + .foregroundStyle(Color.black.opacity(0.8)) + } + } + .frame(width: size.width, height: size.height) + .opacity(isEnabled ? 1 : 0.5) + } + .buttonStyle(.plain) + .disabled(!isEnabled) + } + + private var isSelected: Bool { + selectedDirection == direction + } + + private var size: CGSize { + switch direction { + case .horizontal: + return .init(width: 28, height: 18) + case .vertical: + return .init(width: 18, height: 28) + } + } +} + +private enum PhotosCropAspectRatioDirection { + case vertical + case horizontal + + init(_ aspectRatio: PixelAspectRatio) { + if aspectRatio.height > aspectRatio.width { + self = .vertical + } else { + self = .horizontal + } + } +} diff --git a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift index 3051a10f..ca9ee614 100644 --- a/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift +++ b/Sources/BrightroomUI/builtin/PhotosCrop/PhotosCropViewController.swift @@ -19,85 +19,33 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN // THE SOFTWARE. -import UIKit import SwiftUI +import UIKit -import Combine -import StateGraph - -#if !COCOAPODS import BrightroomEngine -#endif /** Apple's Photos app like crop view controller. - + You might use `CropView` to create a fully customized user interface. */ public final class PhotosCropViewController: UIViewController { - - public struct LocalizedStrings { - public var button_done_title: String = "Done" - public var button_cancel_title: String = "Cancel" - public var button_reset_title: String = "Reset" - public var button_aspectratio_original: String = "ORIGINAL" - public var button_aspectratio_freeform: String = "FREEFORM" - public var button_aspectratio_square: String = "SQUARE" - - public init() {} - } - - public struct Options: Equatable { - - public enum AspectRatioOptions: Equatable { - case selectable - case fixed(PixelAspectRatio?) - } - - public var aspectRatioOptions: AspectRatioOptions = .selectable - - public init() { - - } - } - + + public typealias LocalizedStrings = SwiftUIPhotosCropView.LocalizedStrings + public typealias Options = SwiftUIPhotosCropView.Options + public struct Handlers { public var didFinish: (PhotosCropViewController) -> Void = { _ in } public var didCancel: (PhotosCropViewController) -> Void = { _ in } } - - // MARK: - Properties - private var isSelectingAspectRatio = false - private var options: Options - - private let cropView: CropView - private var aspectRatioControl: PhotosCropAspectRatioControl? - + private let options: Options + private var hostingController: UIViewController? + public let editingStack: EditingStack public var handlers = Handlers() - - private let doneButton = UIButton(type: .system) - private let cancelButton = UIButton(type: .system) - private let aspectRatioButton = UIButton(type: .system) - private let resetButton = UIButton(type: .system) - private let rotateButton = UIButton(type: .system) - - private let aspectRatioControlLayoutGuide = UILayoutGuide() - - private var subscriptions: Set = .init() - private var hasSetupLoadedUICompleted = false - - // Change tracking - private var _previousIsSelectingAspectRatio: Bool? - private var _previousOptions: Options? - private var _previousHasUncommitedChanges: Bool? - private var _previousPreferredAspectRatio: PixelAspectRatio? - public var localizedStrings: LocalizedStrings - - // MARK: - Initializers - + public init( editingStack: EditingStack, options: Options = .init(), @@ -106,13 +54,12 @@ public final class PhotosCropViewController: UIViewController { self.localizedStrings = localizedStrings self.options = options self.editingStack = editingStack - cropView = .init(editingStack: editingStack) super.init(nibName: nil, bundle: nil) } - + /** Creates an instance for using as standalone. - + This initializer offers us to get cropping function without detailed setup. To get a result image, call `renderImage()`. */ @@ -129,306 +76,63 @@ public final class PhotosCropViewController: UIViewController { localizedStrings: localizedStrings ) } - + @available(*, unavailable) public required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - - // MARK: - Functions - + /** Renders an image according to the editing. - + - Attension: This operation can be run background-thread. */ - public func renderImage(options: BrightRoomImageRenderer.Options, completion: @escaping (Result) -> Void) { + public func renderImage( + options: BrightRoomImageRenderer.Options, + completion: @escaping (Result) -> Void + ) { do { try editingStack.makeRenderer().render(options: options, completion: completion) } catch { completion(.failure(error)) } } - + override public func viewDidLoad() { super.viewDidLoad() - - editingStack.start() - cropView.isAutoApplyEditingStackEnabled = true + view.backgroundColor = .black view.clipsToBounds = true - - aspectRatioButton&>.do { - $0.setImage(UIImage(named: "aspectratio", in: bundle, compatibleWith: nil), for: .normal) - $0.tintColor = .systemGray - $0.addTarget(self, action: #selector(handleAspectRatioButton), for: .touchUpInside) - } - - resetButton&>.do { - // TODO: Localize - $0.setTitle(localizedStrings.button_reset_title, for: .normal) - $0.titleLabel?.font = UIFont.systemFont(ofSize: 14) - $0.setTitleColor(UIColor.systemYellow, for: .normal) - $0.addTarget(self, action: #selector(handleResetButton), for: .touchUpInside) - $0.isHidden = true - } - - rotateButton&>.do { - $0.setImage(UIImage(named: "rotate", in: bundle, compatibleWith: nil), for: .normal) - $0.tintColor = .systemGray - $0.addTarget(self, action: #selector(handleRotateButton), for: .touchUpInside) - } - - let topStackView = UIStackView()&>.do { - $0.addArrangedSubview(rotateButton) - $0.addArrangedSubview(resetButton) - $0.addArrangedSubview(aspectRatioButton) - $0.distribution = .equalSpacing - } - - cancelButton&>.do { - $0.setTitle(localizedStrings.button_cancel_title, for: .normal) - $0.titleLabel?.font = UIFont.systemFont(ofSize: 17) - $0.setTitleColor(UIColor.white, for: .normal) - $0.addTarget(self, action: #selector(handleCancelButton), for: .touchUpInside) - } - - doneButton&>.do { - $0.setTitle(localizedStrings.button_done_title, for: .normal) - $0.titleLabel?.font = UIFont.systemFont(ofSize: 17) - $0.setTitleColor(UIColor.systemYellow, for: .normal) - $0.setTitleColor(UIColor.darkGray, for: .disabled) - $0.addTarget(self, action: #selector(handleDoneButton), for: .touchUpInside) - } - - let bottomStackView = UIStackView()&>.do { - $0.addArrangedSubview(cancelButton) - $0.addArrangedSubview(doneButton) - $0.distribution = .equalSpacing - $0.axis = .horizontal - $0.alignment = .fill - } - - view.addSubview(cropView) - view.addSubview(topStackView) - view.addSubview(bottomStackView) - - view.addLayoutGuide(aspectRatioControlLayoutGuide) - - topStackView&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor), - $0.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16), - $0.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16), - ]) - } - - cropView&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: topStackView.bottomAnchor), - $0.leftAnchor.constraint(equalTo: view.leftAnchor), - $0.rightAnchor.constraint(equalTo: view.rightAnchor), - ]) - } - - aspectRatioControlLayoutGuide&>.do { - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: cropView.bottomAnchor), - $0.leftAnchor.constraint(equalTo: view.leftAnchor), - $0.rightAnchor.constraint(equalTo: view.rightAnchor), - ]) - } - - bottomStackView&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: aspectRatioControlLayoutGuide.bottomAnchor), - $0.leftAnchor.constraint(equalTo: view.leftAnchor, constant: 16), - $0.rightAnchor.constraint(equalTo: view.rightAnchor, constant: -16), - $0.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor), - $0.heightAnchor.constraint(equalToConstant: 50), - ]) - } - - UIView.performWithoutAnimation { - view.layoutIfNeeded() - } - - withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self = self else { return } - - if let loadedState = self.editingStack.loadedState { - - /// Loaded - - self.setUpLoadedUI(state: loadedState) - - let hasChanges = loadedState.hasUncommitedChanges - if self._previousHasUncommitedChanges != hasChanges { - self._previousHasUncommitedChanges = hasChanges - self.resetButton.isHidden = !hasChanges - } - - } else { - /// Loading - - self.aspectRatioButton.isEnabled = false - self.resetButton.isEnabled = false - self.rotateButton.isEnabled = false - self.doneButton.isEnabled = false - - } - } - } - .store(in: &subscriptions) - - withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self = self else { return } - self.update() + let contentView = SwiftUIPhotosCropView( + editingStack: editingStack, + options: options, + localizedStrings: localizedStrings, + onDone: { [weak self] in + guard let self else { return } + self.handlers.didFinish(self) + }, + onCancel: { [weak self] in + guard let self else { return } + self.handlers.didCancel(self) } - } - .store(in: &subscriptions) - - editingStack.start() - } - - private func setUpLoadedUI(state: EditingStack.Loaded) { - - guard hasSetupLoadedUICompleted == false else { - return - } - hasSetupLoadedUICompleted = true - - /** - Setup - */ - - self.aspectRatioButton.isEnabled = true - self.resetButton.isEnabled = true - self.rotateButton.isEnabled = true - self.doneButton.isEnabled = true - - let control = PhotosCropAspectRatioControl( - originalAspectRatio: .init(state.imageSize), - localizedStrings: localizedStrings ) - - control.handlers.didSelectAspectRatio = { [unowned self] aspectRatio in - cropView.setCroppingAspectRatio(aspectRatio) - } - - control.handlers.didSelectFreeform = { [unowned self] in - cropView.setCroppingAspectRatio(nil) - } - - self.aspectRatioControl = control - - view.addSubview(control) - - control&>.do { - $0.translatesAutoresizingMaskIntoConstraints = false - NSLayoutConstraint.activate([ - $0.topAnchor.constraint(equalTo: aspectRatioControlLayoutGuide.topAnchor), - $0.leftAnchor.constraint(equalTo: aspectRatioControlLayoutGuide.leftAnchor), - $0.rightAnchor.constraint(equalTo: aspectRatioControlLayoutGuide.rightAnchor), - $0.bottomAnchor.constraint(equalTo: aspectRatioControlLayoutGuide.bottomAnchor), - ]) - } - - /// refresh current state - UIView.performWithoutAnimation { - update() - } - - withGraphTracking { [weak self] in - withGraphTrackingGroup { - guard let self = self else { return } - - let ratio = self.cropView.state.preferredAspectRatio - if self._previousPreferredAspectRatio != ratio { - self._previousPreferredAspectRatio = ratio - self.aspectRatioControl?.setSelected(ratio) - } - } - } - .store(in: &subscriptions) - } - - private func update() { + let hostingController = UIHostingController(rootView: contentView) + hostingController.view.backgroundColor = .clear - let currentOptions = options - if _previousOptions != currentOptions { - _previousOptions = currentOptions - switch currentOptions.aspectRatioOptions { - case .fixed(let aspectRatio): - aspectRatioButton.alpha = 0 - cropView.setCroppingAspectRatio(aspectRatio) - case .selectable: - aspectRatioButton.alpha = 1 - cropView.setCroppingAspectRatio(nil) - } - } + addChild(hostingController) + view.addSubview(hostingController.view) + hostingController.didMove(toParent: self) - let selecting = isSelectingAspectRatio - if _previousIsSelectingAspectRatio != selecting { - _previousIsSelectingAspectRatio = selecting - UIViewPropertyAnimator.init(duration: 0.4, dampingRatio: 1) { [self] in - if selecting { - aspectRatioControl?.alpha = 1 - aspectRatioButton.tintColor = .systemYellow - } else { - aspectRatioControl?.alpha = 0 - aspectRatioButton.tintColor = .systemGray - } - } - .startAnimation() - } + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + NSLayoutConstraint.activate([ + hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), + hostingController.view.leftAnchor.constraint(equalTo: view.leftAnchor), + hostingController.view.rightAnchor.constraint(equalTo: view.rightAnchor), + hostingController.view.bottomAnchor.constraint(equalTo: view.bottomAnchor), + ]) - } - - @objc private func handleRotateButton() { - guard let proposedCrop = cropView.state.proposedCrop else { - return - } - - let rotation = proposedCrop.rotation.next() - cropView.setRotation(rotation) - - if let ratio = cropView.state.preferredAspectRatio { - cropView.setCroppingAspectRatio(ratio.swapped()) - } else { - // CropView update automatically - } - - } - - @objc private func handleAspectRatioButton() { - isSelectingAspectRatio.toggle() - } - - @objc private func handleResetButton() { - switch options.aspectRatioOptions { - case .fixed(let ratio): - cropView.setCroppingAspectRatio(ratio) - case .selectable: - cropView.setCroppingAspectRatio(nil) - } - cropView.resetCrop() - } - - @objc private func handleCancelButton() { - handlers.didCancel(self) - } - - @objc private func handleDoneButton() { - cropView.applyEditingStack() - handlers.didFinish(self) + self.hostingController = hostingController } } - diff --git a/Sources/BrightroomUI/builtin/PhotosCrop/SwiftUIPhotosCropView.swift b/Sources/BrightroomUI/builtin/PhotosCrop/SwiftUIPhotosCropView.swift index 41b65fc2..9c916d78 100644 --- a/Sources/BrightroomUI/builtin/PhotosCrop/SwiftUIPhotosCropView.swift +++ b/Sources/BrightroomUI/builtin/PhotosCrop/SwiftUIPhotosCropView.swift @@ -21,42 +21,70 @@ import SwiftUI -#if !COCOAPODS import BrightroomEngine -#endif /** - Apple's Photos app like crop view controller. + Apple's Photos app like crop view. You might use `CropView` to create a fully customized user interface. */ -@available(iOS 13, *) -public struct SwiftUIPhotosCropView: UIViewControllerRepresentable { - public typealias UIViewControllerType = PhotosCropViewController - +@available(iOS 14, *) +public struct SwiftUIPhotosCropView: View { + + public struct LocalizedStrings { + public var button_done_title: String = "Done" + public var button_cancel_title: String = "Cancel" + public var button_reset_title: String = "Reset" + public var button_aspectratio_original: String = "ORIGINAL" + public var button_aspectratio_freeform: String = "FREEFORM" + public var button_aspectratio_square: String = "SQUARE" + + public init() {} + } + + public struct Options: Equatable { + + public enum AspectRatioOptions: Equatable { + case selectable + case fixed(PixelAspectRatio?) + } + + public var aspectRatioOptions: AspectRatioOptions = .selectable + + public init() { + + } + } + private let editingStack: EditingStack - private let onDone: () -> Void - private let onCancel: () -> Void + private let options: Options + private let localizedStrings: LocalizedStrings + private let onDone: @MainActor () -> Void + private let onCancel: @MainActor () -> Void - public init(editingStack: EditingStack, onDone: @escaping () -> Void, onCancel: @escaping () -> Void) { + public init( + editingStack: EditingStack, + options: Options = .init(), + localizedStrings: LocalizedStrings = .init(), + onDone: @escaping @MainActor () -> Void, + onCancel: @escaping @MainActor () -> Void + ) { self.editingStack = editingStack + self.options = options + self.localizedStrings = localizedStrings self.onDone = onDone self.onCancel = onCancel - editingStack.start() } - - public func makeUIViewController(context: Context) -> PhotosCropViewController { - let cropViewController = PhotosCropViewController(editingStack: editingStack) - cropViewController.handlers.didFinish = { _ in - onDone() - } - cropViewController.handlers.didCancel = { _ in - onCancel() - } - return cropViewController + + public var body: some View { + PhotosCropContentView( + editingStack: editingStack, + options: options, + localizedStrings: localizedStrings, + onDone: onDone, + onCancel: onCancel + ) } - - public func updateUIViewController(_ uiViewController: PhotosCropViewController, context: Context) {} } #Preview { diff --git a/Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift b/Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift deleted file mode 100644 index b60855ac..00000000 --- a/Sources/BrightroomUIPhotosCrop/PhotosCropRotating/PhotosCropRotating.swift +++ /dev/null @@ -1,534 +0,0 @@ -// -// DemoCropView.swift -// SwiftUIDemo -// -// Created by Muukii on 2021/03/05. -// Copyright © 2021 muukii. All rights reserved. -// - -import BrightroomEngine -import BrightroomUI -import PrecisionLevelSlider -import SwiftUI -import UIKit -import StateGraph - -public struct PhotosCropRotating: View { - - private enum Direction { - /** - +----+ - | | - | | - | | - | | - | | - | | - +----+ - */ - case vertical - /** - +---------------+ - | | - | | - +---------------+ - */ - case horizontal - - mutating func swap() { - switch self { - case .horizontal: self = .vertical - case .vertical: self = .horizontal - } - } - - init(_ ratio: PixelAspectRatio) { - if ratio.width > ratio.height { - self = .horizontal - } else { - self = .vertical - } - } - } - - let editingStack: EditingStack - - @State private var rotation: EditingCrop.Rotation? - @State private var adjustmentAngle: EditingCrop.AdjustmentAngle? - @State private var croppingAspectRatio: PixelAspectRatio? - - @State private var canReset: Bool = false - @State private var isFocusingAspectRatio: Bool = false - - @State private var cropViewState: CropView.StateModel? - - @State var reset: SwiftUICropView.ResetAction = .init() - - public init( - editingStack: EditingStack - ) { - self.editingStack = editingStack - } - - private var isLoading: Bool { - editingStack.isLoading - } - - public var body: some View { - VStack { - ZStack { - Color.black - .ignoresSafeArea() - - VStack { - - HStack { - - // rotation - Button( - action: { - - if self.rotation == nil { - self.rotation = .angle_0 - self.rotation = self.rotation!.next() - } else { - self.rotation = self.rotation!.next() - } - - croppingAspectRatio = cropViewState?.preferredAspectRatio?.swapped() - - }, - label: { - Image(systemName: "rotate.left") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 22) - .foregroundStyle(rotation == .angle_0 || rotation == nil ? .secondary : .primary) - } - ) - .disabled(isLoading) - - Spacer() - - if editingStack.loadedState?.hasUncommitedChanges ?? false { - Button { - reset() - } label: { - Text("RESET") - .font(.subheadline) - } - .tint(.yellow) - .disabled(isLoading) - } - - Spacer() - - // aspect ratio - Button( - action: { - isFocusingAspectRatio.toggle() - }, - label: { - Image(systemName: isFocusingAspectRatio ? "aspectratio.fill" : "aspectratio") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 22) - .foregroundStyle(isFocusingAspectRatio ? .primary : .secondary) - } - ) - .disabled(isLoading) - } - .tint(.white) - .padding() - .zIndex(1) - - SwiftUICropView( - editingStack: editingStack, - isAutoApplyEditingStackEnabled: true, - stateHandler: { state in - - Task { @MainActor in - - cropViewState = state - - if let proposedCrop = state.proposedCrop { - if rotation != proposedCrop.rotation { - rotation = proposedCrop.rotation - } - - if adjustmentAngle != proposedCrop.adjustmentAngle { - adjustmentAngle = proposedCrop.adjustmentAngle - } - - } - - if croppingAspectRatio != state.preferredAspectRatio { - croppingAspectRatio = state.preferredAspectRatio - } - - } - } - ) - .rotation(rotation) - .adjustmentAngle(adjustmentAngle) - .croppingAspectRatio(croppingAspectRatio) - .registerResetAction(reset) - .zIndex(0) - - Group { - if isFocusingAspectRatio { - aspectRatioView - } else { - rotationAdjustmentView - } - } - .zIndex(1) - } - } - } - .onAppear { - editingStack.start() - } - - } - - private var rotationAdjustmentView: some View { - SwiftUIPrecisionLevelSlider( - value: .init( - get: { (adjustmentAngle?.degrees ?? 0) }, - set: { value in - adjustmentAngle = .init(degrees: value) - } - ), - haptics: .init(trigger: { value in - if value.truncatingRemainder(dividingBy: 5) == 0 { - return .impact(style: .light, intensity: 0.4) - } else { - return nil - } - }), - range: .init( - range: -45...45, - transform: { source in - source.rounded(.toNearestOrEven) - } - ), - centerLevel: { value, _ in - HStack { - Spacer() - VStack { - Spacer(minLength: 12) - Rectangle() - .frame(width: 1) - Spacer(minLength: 12) - } - Spacer() - } - .foregroundStyle(.tint) - }, - track: { value, _ in - VStack { - HStack { - Spacer() - Circle() - .frame(width: 6, height: 6) - .opacity(value == 0 ? 0 : 1) - .animation(.spring, value: value == 0) - Spacer() - } - HStack(spacing: 0) { - ForEach(0..<4) { i in - ShortBar() - .foregroundStyle(.primary) - Group { - Spacer(minLength: 0) - ShortBar() - Spacer(minLength: 0) - ShortBar() - Spacer(minLength: 0) - ShortBar() - Spacer(minLength: 0) - ShortBar() - Spacer(minLength: 0) - ShortBar() - Spacer(minLength: 0) - ShortBar() - Spacer(minLength: 0) - ShortBar() - Spacer(minLength: 0) - ShortBar() - Spacer(minLength: 0) - ShortBar() - Spacer(minLength: 0) - } - .foregroundStyle(.secondary) - } - ShortBar() - .foregroundStyle(.primary) - } - } - .foregroundStyle(.tint) - } - ) - .tint(.white) - .accentColor(.white) - .frame(height: 50) - .disabled(isLoading) - } - - private var aspectRatioView: some View { - - let isDirectionButtonDisabled: Bool = { - - guard let _ = editingStack.loadedState else { - return true - } - - guard let croppingAspectRatio else { - return true - } - - if croppingAspectRatio == .square { - return true - } - - return false - - }() - - return VStack(spacing: 20) { - HStack(spacing: 18) { - - // vertical - Button { - if croppingAspectRatio.map({ Direction($0) }) != .vertical { - croppingAspectRatio = croppingAspectRatio?.swapped() - } - } label: { - ZStack { - RoundedRectangle(cornerRadius: 2) - .stroke(style: .init(lineWidth: 2)) - .fill(Color(white: 0.7, opacity: 1)) - - RoundedRectangle(cornerRadius: 2) - .fill(Color(white: 0.4, opacity: 1)) - - if isDirectionButtonDisabled == false, croppingAspectRatio.map({ Direction($0) }) == .vertical { - Image(systemName: "checkmark") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12) - .foregroundStyle(Color.black) - } - } - .tint(.white) - .frame(width: 18, height: 28) - .compositingGroup() - .opacity(isDirectionButtonDisabled ? 0.5 : 1) - - } - .disabled(isDirectionButtonDisabled) - - // horizontal - Button { - if croppingAspectRatio.map({ Direction($0) }) != .horizontal { - croppingAspectRatio = croppingAspectRatio?.swapped() - } - } label: { - ZStack { - RoundedRectangle(cornerRadius: 2) - .stroke(style: .init(lineWidth: 2)) - .fill(Color(white: 0.7, opacity: 1)) - - RoundedRectangle(cornerRadius: 2) - .fill(Color(white: 0.4, opacity: 1)) - - if isDirectionButtonDisabled == false, croppingAspectRatio.map({ Direction($0) }) == .horizontal { - Image(systemName: "checkmark") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 12) - .foregroundStyle(Color.black) - } - } - .tint(.white) - .frame(width: 28, height: 18) - .compositingGroup() - .opacity(isDirectionButtonDisabled ? 0.5 : 1) - - } - .disabled(isDirectionButtonDisabled) - - } - - ScrollView(.horizontal, showsIndicators: false) { - HStack { - - Group { - - let sourceDirection = editingStack.loadedState.map({ Direction(.init($0.imageSize)) }) - let direction = croppingAspectRatio.map({ Direction($0) }) ?? sourceDirection - - switch sourceDirection { - case .none: - EmptyView() - case .vertical: - switch direction { - case .none: - EmptyView() - case .vertical: - AspectRationButton( - title: Text("ORIGINAL"), - isSelected: croppingAspectRatio == editingStack.loadedState.map { .init($0.imageSize) } - ) { - guard let imageSize = editingStack.loadedState?.imageSize else { - return - } - croppingAspectRatio = PixelAspectRatio.init(imageSize) - } - case .horizontal: - AspectRationButton( - title: Text("ORIGINAL"), - isSelected: croppingAspectRatio == editingStack.loadedState.map { .init($0.imageSize).swapped() } - ) { - guard let imageSize = editingStack.loadedState?.imageSize else { - return - } - croppingAspectRatio = PixelAspectRatio.init(imageSize).swapped() - } - } - case .horizontal: - switch direction { - case .none: - EmptyView() - case .vertical: - AspectRationButton( - title: Text("ORIGINAL"), - isSelected: croppingAspectRatio == editingStack.loadedState.map { .init($0.imageSize).swapped() } - ) { - guard let imageSize = editingStack.loadedState?.imageSize else { - return - } - croppingAspectRatio = PixelAspectRatio.init(imageSize).swapped() - } - case .horizontal: - AspectRationButton( - title: Text("ORIGINAL"), - isSelected: croppingAspectRatio == editingStack.loadedState.map { .init($0.imageSize) } - ) { - guard let imageSize = editingStack.loadedState?.imageSize else { - return - } - croppingAspectRatio = PixelAspectRatio.init(imageSize) - } - } - } - - AspectRationButton( - title: Text("FREEFORM"), - isSelected: croppingAspectRatio == nil - ) { - croppingAspectRatio = nil - } - - AspectRationButton( - title: Text("SQUARE"), - isSelected: croppingAspectRatio == .square - ) { - croppingAspectRatio = .square - } - - ForEach(Self.horizontalRectangleApectRatios) { ratio in - - switch direction { - case .none: - EmptyView() - case .vertical?: - AspectRationButton( - title: Text("\(Int(ratio.height)):\(Int(ratio.width))"), - isSelected: croppingAspectRatio == ratio.swapped() - ) { - croppingAspectRatio = ratio.swapped() - } - case .horizontal?: - AspectRationButton( - title: Text("\(Int(ratio.width)):\(Int(ratio.height))"), - isSelected: croppingAspectRatio == ratio - ) { - croppingAspectRatio = ratio - } - } - } - } - .tint(.white) - - } - .padding(.horizontal, 20) - } - } - } - - private static let horizontalRectangleApectRatios: [PixelAspectRatio] = [ - .init(width: 16, height: 9), - .init(width: 10, height: 8), - .init(width: 7, height: 5), - .init(width: 4, height: 3), - .init(width: 5, height: 3), - .init(width: 3, height: 2), - ] -} - -struct AspectRationButton: View { - - let title: Text - let isSelected: Bool - let onTap: @MainActor () -> Void - - var body: some View { - Button( - action: { - onTap() - }, - label: { - title - .font(.caption) - .foregroundStyle(.primary) - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background( - Capsule() - .foregroundStyle(.tertiary) - .opacity(isSelected ? 1 : 0) - ) - .foregroundStyle(.tint) - } - ) - } -} - -struct AspectRationButtonStyle: PrimitiveButtonStyle { - - let isSeleted: Bool - - func makeBody(configuration: Configuration) -> some View { - configuration - .label - .foregroundStyle(.primary) - .padding(.horizontal, 10) - .padding(.vertical, 6) - .background( - Capsule() - .foregroundStyle(.tertiary) - .opacity(isSeleted ? 1 : 0) - ) - .foregroundStyle(.tint) - - } - -} -struct ShortBar: View { - var body: some View { - RoundedRectangle(cornerRadius: 8) - .frame(width: 1, height: 10) - } -} From 147075b9d508198b0ce92806be41e3d81c6b48e1 Mon Sep 17 00:00:00 2001 From: Muukii Date: Fri, 22 May 2026 18:03:35 +0900 Subject: [PATCH 06/34] Add SwiftUI demo validation flows --- .maestro/crop.yaml | 15 ++ .maestro/photos-crop.yaml | 29 ++++ .maestro/pixel-editor.yaml | 142 ++++++++++++++++++ CLAUDE.md | 11 +- .../xcshareddata/xcschemes/Demo.xcscheme | 93 ------------ Dev/Sources/Demo/nasa.jpg | Bin 18774733 -> 0 bytes 6 files changed, 189 insertions(+), 101 deletions(-) create mode 100644 .maestro/crop.yaml create mode 100644 .maestro/photos-crop.yaml create mode 100644 .maestro/pixel-editor.yaml delete mode 100644 Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme delete mode 100644 Dev/Sources/Demo/nasa.jpg diff --git a/.maestro/crop.yaml b/.maestro/crop.yaml new file mode 100644 index 00000000..aad71f94 --- /dev/null +++ b/.maestro/crop.yaml @@ -0,0 +1,15 @@ +appId: app.muukii.brightroom.SwiftUIDemo +name: Crop +tags: + - crop +--- +- launchApp: + clearState: true +- tapOn: "Crop" +- assertVisible: "Done" +- swipe: + start: 78%, 52% + end: 62%, 52% + duration: 500 +- tapOn: "Done" +- assertVisible: "Dismiss" diff --git a/.maestro/photos-crop.yaml b/.maestro/photos-crop.yaml new file mode 100644 index 00000000..570e2140 --- /dev/null +++ b/.maestro/photos-crop.yaml @@ -0,0 +1,29 @@ +appId: app.muukii.brightroom.SwiftUIDemo +name: PhotosCrop +tags: + - photos-crop +--- +- launchApp: + clearState: true +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- tapOn: "Horizontal" +- assertVisible: + id: "photos.crop" +- assertVisible: "Cancel" +- assertVisible: "Done" +- assertVisible: "Rotate" +- assertVisible: "Aspect Ratio" +- tapOn: "Aspect Ratio" +- assertVisible: "ORIGINAL" +- assertVisible: "FREEFORM" +- assertVisible: "SQUARE" +- tapOn: "SQUARE" +- tapOn: + text: "Rotate" +- tapOn: "Reset" +- tapOn: "7:5" +- tapOn: "Done" +- assertVisible: ".*size:.*" diff --git a/.maestro/pixel-editor.yaml b/.maestro/pixel-editor.yaml new file mode 100644 index 00000000..62a065ef --- /dev/null +++ b/.maestro/pixel-editor.yaml @@ -0,0 +1,142 @@ +appId: app.muukii.brightroom.SwiftUIDemo +name: PixelEditor +tags: + - pixel-editor +--- +- launchApp: + clearState: true +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- tapOn: + point: 50%, 72% +- assertVisible: "Done" +- assertVisible: "Normal" +- tapOn: "Edit" +- assertVisible: "Adjust" +- assertVisible: "Mask" +- assertVisible: "Brightness" +- assertVisible: "Contrast" + +- launchApp: + clearState: true +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- tapOn: + point: 50%, 72% +- tapOn: "Edit" +- tapOn: "Adjust" +- assertVisible: "Adjust" +- assertVisible: "Cancel" +- assertVisible: "Done" +- swipe: + start: 99%, 49% + end: 70%, 36% + duration: 500 +- tapOn: "Done" +- assertVisible: "Edit" +- tapOn: "Done" +- assertVisible: ".*size:.*" +- assertNotVisible: "size: 5952, 3968" + +- launchApp: + clearState: true +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- tapOn: + point: 50%, 72% +- tapOn: "Edit" +- tapOn: "Mask" +- assertVisible: "Mask" +- assertVisible: "Clear" +- swipe: + start: 50%, 76% + end: 15%, 76% + duration: 500 +- assertVisible: "Cancel" +- assertVisible: "Done" + +- launchApp: + clearState: true +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- tapOn: + point: 50%, 72% +- tapOn: "Edit" +- tapOn: "Brightness" +- assertVisible: "Brightness" +- swipe: + start: 50%, 76% + end: 15%, 76% + duration: 500 +- tapOn: "Done" +- assertVisible: "Adjust" +- assertVisible: "Done" + +- launchApp: + clearState: true +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- tapOn: + point: 50%, 72% +- tapOn: "Done" +- assertVisible: ".*size:.*" + +- launchApp: + clearState: true +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- swipe: + start: 50%, 82% + end: 50%, 24% + duration: 500 +- tapOn: + point: 50%, 72% +- tapOn: "Cancel" +- assertVisible: "PixelEditor" + +- tapOn: "PixelEditor Square" +- assertVisible: "Cancel" +- tapOn: "Cancel" +- assertVisible: "PixelEditor Square" +- tapOn: "PixelEditor 4:5" +- assertVisible: "Cancel" +- tapOn: "Cancel" +- assertVisible: "PixelEditor 4:5" +- tapOn: "PixelEditor 5:4" +- assertVisible: "Cancel" +- tapOn: "Cancel" +- assertVisible: "PixelEditor 5:4" +- tapOn: "PixelEditor left" +- assertVisible: "Cancel" diff --git a/CLAUDE.md b/CLAUDE.md index c39ce86a..b210c069 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,10 +13,7 @@ Brightroom is a composable image editor library for iOS, powered by Metal for hi # Build BrightroomUI (includes BrightroomEngine) cd Dev && xcodebuild -scheme BrightroomUI -destination 'platform=iOS Simulator,name=iPhone 15 Pro' build -# Build Demo app -cd Dev && xcodebuild -scheme Demo -destination 'platform=iOS Simulator,name=iPhone 15 Pro' build - -# Build SwiftUI Demo +# Build SwiftUI Demo app cd Dev && xcodebuild -scheme SwiftUIDemo -destination 'platform=iOS Simulator,name=iPhone 15 Pro' build ``` @@ -79,10 +76,8 @@ The project uses Verge (swift-state-graph) for state management. When modifying - Focus on testing image processing logic, not UI - Use provided test images in Resources for consistency -### Demo Apps -- **UIKit Demo**: `Dev/Sources/Demo/` - Traditional UIKit implementation -- **SwiftUI Demo**: `Dev/Sources/SwiftUIDemo/` - Modern SwiftUI examples -- Both demos showcase all major features and serve as implementation references +### Demo App +- **SwiftUI Demo**: `Dev/Sources/SwiftUIDemo/` - SwiftUI examples and UIKit-based checks wrapped with representables ## Common Tasks diff --git a/Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme b/Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme deleted file mode 100644 index 85ed34da..00000000 --- a/Dev/Brightroom.xcodeproj/xcshareddata/xcschemes/Demo.xcscheme +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/Dev/Sources/Demo/nasa.jpg b/Dev/Sources/Demo/nasa.jpg deleted file mode 100644 index ef806ec04dc56109f64de1014baabeb2b03a4f96..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18774733 zcmeFa3s}?FmiMpKqG&9Fiiy%75I`kDE`m@49E;op4+RwnAPPyK3RMC15CXJSnJTvpmJM+#nGw<`CGyTuIlLrmq zNAml9ve({geb-u>zxMt0*`n2ZynMYDEnd86(PD64^w(b(c`W*9vHqt2_apsn=@R|F zA1__HWU0Z&1_mF#mKiQLSZ27)z+m~x<%TQtH}D9tas@*Fjs8Xc_NyO#ykyD8E0!57 z`*6sA>Yu+hFEUyt%@CE#~gFIf#P_4lv8`KJTm14sZ8fCL}`NB|Om z1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}` zNB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6 zfCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C z0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K z5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)% zAOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O z01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om z1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}` zNB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6 zfCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C z0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K z5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)% zAOT1K5`Y9C0Z0H6fCL}`NB|Om1Rw!O01|)%AOT1K5`Y9C0Z0H6fCL}`NB|Om1pX%x zSTeYJ?f<0ff!Fe1PatJ#@t^+d-vz$I{~!X(no?Mc;6(F3=o9@rT#A9O`kNJhhxdVR z@jr*ak{BO`CthdVP9`ww@wIa10$zXzas(#!h$ z&skTdUbMZr`i}?d|9AL@UuUKM-<|;cy+1wygGkim_W$H*8U>(P;64}KuD>TwC%5xO zliJ7Ez;N>$elR?^&(y=zL)yd_A@%o0q{xAl=AV|5tb4fra?3;WG_)(4&dXvpkQifm z4(nTb+RF0sEcEx`Zpxc0(m~(h2Wmlb@^lknio|%f0vu(L=U}9NPm=x}r<14k@5>k2 z|HBZ41N1-n0si3+M>htL7Jcy_Jx#RG7416RR@hcJ_2#NW8W^1@1Q|h2roVb9y{z62I zi;4-1!c;qcg!rx2;ScL23fAn(Uaer(A zC@b^S|CkW*w_ytgEjEjlhNt3n55UY<{kQR(!YA5eC5Qzp9U*FhVvx=*M-q{YLNG`z zaIr5XMQfDA>HD^s2-#V}bH!nBkDBl>C${JF^SKAI>M{2PCp5dx?9va_cV5a?sp2wv zt#075IdLjMyGaK|?YpRnNd7z+spavl>7#h66%|h#%5(SWXOiS=*4LvMT)(V(L@F?h zwQansXrIvTWIlaU4zQVg3rdWY@6cKNSW|OryN#3G)U(YZP24&E(Q~5Jt9s1&>)h}D3lIlVjFQW_!wO5Yp+7@@oX?U z(?@X{PxgFS#fHzl*hsp?37_lTn4GPbeQ@%DOYYY9Pxy#UDMo~L*1&_Tdb{PZ-zeK# z`LjcL*3t5ZGSqJWscfxgk9MGu>y*xClwC!L@S4xPM}+p+1(7?ZwM9mT9Hw)6-ISSD3JrtxPT)+jNnRTF+_;^E|J%IM#QzId{& z5Ue8~W_lm17#tNWD5y2-FN4V)Oz%^vDWpY4z|zrlHh3Z0klsW3kFRb2E@^nc-v zTb1(6zheOU(XV%N-;zGe>D@vWDg_Kjqr|sh>VBpl{#OwaY$KjZ(z^Sj<_;?;qv?DM z5;N1@y?mk2dm^1ZUvk?A7XwT|Z!rucW>A-ChlF@$pCVe><BdrWV%GEUbJi zeN|BtU*;*^SCZuIQbhxcq_cENU^Zw(a(91{^8Q35SR6w8-$jj5r2~x-C?=_pT|Nn{ zBK|#2>VDKFeV+~acDY88z{aMlUjn;AVzB8xK7u)~qUP=ASbFOLmq7*RYR<`iU$n+F z@^29VunZ6vAZLNu=uM~X#ng+*MIe%x23|e3;Siej_x(`eyZwJc0MRg`RvPa5)4%y* zi^qT?PBucHeK7nB<7Kq`QOke?s~^dE1jm2-1 zOyHsyf@Prg4Df2qMW*gaz*tiBi^Q4jXP2IFry7?Vi*t#LV!egX^`3oMCbAv;suAiP z1sK%qCKhUGx_ro7H0Q}|G9HkBY;tC$EaWI}ldr?S+6OYHGCGfs+S<-{^MWbeR*T@0VPT;TaLV zz%SFDzne~81caRrUYCA(fw%uZ5m@|2V5Lm>?)<-X&U)npEGH>T9s*Hi9(YMvO#RxS z8{B7}h-MVF3^d|n$Id$V2O;#4(k0j*}6*Qf9qJpGa*L61KCU|eD( zh#vTDeEnF};m^AJEGWE+kXeSs&vzB1j~oWyG`rjUoTuEKq!Mg&_fIYAHXCWpGh84s zaz^Nf8B9KMIQoVedOk_rR3V83;q^Xcn%}U+u*LY(l-sjqnFIz^i(Rnv8_E(e)8v-h zg}{cga!8iVq1jVKYs_NKxW0WITtymP)8Dm8Bjr5S9LHvnByO=|M)k>b9>>dYX*@6s zOGa^sZVQ$rGtTTCO-`e6M5IAKze z>_qH`_~tpZtAA@y{;{85{4zzi^52S6jJ%my!f)SPV|geV{aqDx^M!4^8^d@In&=k~ zF>;=F)lEGHgdGA1s>#Nx2`}t|)l7g-Lus>nk_;aMtSyTJ)}F(iq4SoZ3kp7iQ=_vu z>+YY{847H!?5d#wLIQN6EKW2kyL-7v-gjSUJW8M)5SMkZ1~DWs)#Dgr8v=aNin_6l z)2zWneCOEnfaO!lB}RnFmQ8KrHja50(sWO>*!Mgmf{okAA(Roy%8Vlza$0FhZ>R4h zf~Y&%fr`YJT8YKXAv*+B@5_aD;4~5097|FR_-?naD==GNQT=mzcMh0JStbIwhhSmC z=pUoiBT=Kty z2Kc)wyWJC!G_G$Z9pS2=6j>R76>a9W4nui!6Z|Hg#y*Q>k}n%22D^>meDHc7O$fv> zjPZO~`4Ly(c{P8%e4!J`00VJ82aTQ$CQ5?biA+`xSp2|RVN`EK(h>)v6>?buNfVfv zzMGNhfpBftDH?H_Q2$fiLrFkKFl$t3TqrDp?nxVmL?y*OAEvCtqxY>xXcf`{F?bo1 z00cNuhqUP&8){@buHegTa`x?-#nm;|B9cnFIYF}5A79qi;*=0>cDbj1JSk@QlVlD< z7`PFivEJf(%$ac4t}V1EC%m~}Zg*#3`6Nq*N3%7I+S0OvmNP0!=)3gScyN&RIg#$QgI=0^e^{ecJ=MA8yl2CYB-*QbOQ63~n>lCJxm?x9&D z$d&+2%->aRWkAQ$X)mLdK%D@FndzoxZ*=$RPNsl`Z{GGQ2+lMS{zXcy?ppp;XTKq^R0~FcEMt$J^9SMNXtFS6QfGL=O5=H0Zi#8y9lvnB zAE+BG2I;Mld#-bUp#tZWB?F!)a5tB~FKPoRB@p?Y#b*h?>Fo9mV*{|$06Qb==}ebNpP-wV-~E;qz5Behd2JJ$z9VBxswEKI!D_3&XZODYAs~ni zee_-|uvsQa5v6Xr<*fSl)z7dl>0b~1Kh`rkMbV&Z^O=8@-P9Xs3Q!D5Z^zV4BXtac z-mgP3334l=q-lMOC&y-Yr+81$ftHYoB6tg^HL9{3Y5~=A!ecP4{XXzgMYxUtA8<@r z6MvXj{5)Wj{Gk#olfGc-W#WrkOh0!|L^Kw%P=V(4j8G6rI;2%5>YhxM;B?lMH%$_3 zao%>Y7z4!*ryB%wLyIEmR?L~-Jb`>7E6X4^RuklXE&Z-rXX8G)nVP*I?{=UvrQ6Rq zM3bb-c#Z#UULW=6JXu^hK6dmPGfO~YuNN4($}QcA$a$`piyBkcCAQzPlu&+Mt8><( zC{{7o#Y$(XA>LAotr$$4vC@*Z8U-O|Idsbk654Q6(0cQZAh*C`9%~ z(q0St^YdxU-~ECku5K!L6om~c2fhWXDA{8is6aqYnIuvy+eU<#WcfPE@RKf=DG-1H z_cITeAQcy@It;F1paHlVc@91xj8uTTa)~mnoh54seP1L4$p(FP01f>l7KOT z2-Hh)xYJk+KCg@;Ak6?)^LNqUl`u@LQB>#Kp+Ehx#w9$Pe@!4oI44mq{pDZ$U|OHb z`u2r(Fimfw1bL5Mky+3?v*dRaF375uUByaJR8ptFG?GPjV9!!^;Nm4Vtnq;)x6vNvC1T#sZZ|q z;!)>0e9cmRd$>9qqeE8pBzlj~S)yC$Sk$LGw~qr)Ky02gDMvKm<%3>?WDxWsDL2TfxlWmTH$?O)hq}@^KdcMlGJlPMmRJb>^Tw;~kb~HB?yW4GKEB*LJyI5?3 zH3R8&WRfdNwnH?W*P59DYWbDa9B-wJs>O8R5=&93IN5mA$R!NWeL$uTET_Qf)^)sPU5#`FS?Z4a zTpPDoRf?5u{qFP;99U}`3%y6`3RRo(HW~vtc$RH^B=}DvfouJy{79;m)*{mdBsh|Loz+0DS_2H4IGtZsan^xij?n-Y1;iIjhyU+IfBPCp8bol0l)oY5kS13Q7e4^MM^?1r!M<`6j&Ll7Kle+M^v1TY{{kgMVU?bKXXDCBc`< z+9C=ak?$k{p%*AJ2r2^`7@BXz&i8#IkelsmqbAX}duKF-e;oJ4wR+#@t!&#HHJw>9 zPk(Gk&mCH+0qQ=rQ1z8nR{)Jg%F7b`iQ6be9i@mLPlQ#u*Lx7Hr6^Jsh+_3>E84ff zZ)D%+XS=b&hl&RUCeJ-bn)avr<>cYBM08^YkelD*wGKWFMICuE-}RiEq(*yZ`H08( z)52?$Ikp*k&39RiTKJ5rUDKzMG}z>h4OOP;)_?@R4G?l`vNiNj`sOTIE15P?9w}4R z%?k0uvgP}tJ-Cs8=tNCLMVR$zG8-Zn&Y*yVbd;a-zu0Uo| ze+Q%{3w6^R{Y^bm#m0lxI8kneSZHq(?xx4S1pX~HUB!vAkW z)+F>UsiBQ!0GF;4IbU)q76iY*g6Vv)GV2@0;aY<;>g(#4gq;U_h>ZyFOy_*>C%g!_z#wM$ai@T#^c};vzw`GF) zF|-*7H8JDyt$z{@Ql9&ke4u%EvR*C zu-Wg=M@=M4knPv^WozCdXL*^3DLmsfnAA)|CHT&7Gfn1i9I`{o(d2n!+M8k<2MWSw zy88S(aAX!=4MObYjmiyEoEoXqRn+_tPdIOHXpA=T4vfMu6p^tiI@gW0I77&ux2M;d zzTY}aY5qz4j6Y(JROjS51Z|gG(vc`2D_Y=k+o!+6G+mzO6rW7{He6N_^Q+FOJXy}H zW4t+Qc;L{=&^G^LE1U<{u0OH0lCrwIwe_D5B>4D`LqLX}KPNQ&?w_4d3jNJ@;0xqH zf&Z-n=yv~)0ur19UU5DMv=Tly1lN>X;C@*zS5)T+r!l9D(G z!sdAk17dcgz7&OhZIZQRiHu-X)aIMj#8!_cumr;apvscd(42~(D%)EW3)kuqL0%Mf z6aU@YHN*(=hguy9Lfqam@Brx3WU!Qj*j%4OH3#Z8^LmTTGWMvrPrJI(5lwNVaOv_D ztug2koFroT&CvTObxB*c_xj=@XCk-b9%Z^q5~L1{vH?XhT-!59*E})^Da3<`N+*PM z0Sk*ceY@UqZT)C;k(D_$*iH3PFMWa19}^;;{jwR90W=%X`!j?pY;xyNR?+U-=iKca z{{^nO%@oj`ESODxEBu4RPS-N738Jv|!njlQSMwkiI{(^BmjTZJmJ=Ws@er5vSL=j9hiU%=o`DB4#TUu7saS zTGAy<-7Pc&sU6_BTTkZ6a+ZnaZqHjs^k2%k7$pov58GgxRIk|-i)ekp2ZB_zR%-Sh zr?VUyrLttCh=f9~cJFf;f8zC2ol^vJo*Ex?!W@TP1OLpl{vZ!^mjAo6j?VfH`0Aj@ z0T}%JOFD>48GNZjB3@2n0&o3;_3L#pj*IKeWt}@^>DjeS{2}*@eQl1Q=EuuqEf_fF z)PhF&K$hTLN-Hp=rF|&+L?BqIqeg_mE)2mZK}IO5$A|zKWxv~nP$^=dJSakq{pUdYj_kt(zl>!(+K zc}trE60M39kIgYikL}hGDAV`aK6a54mO6zHg<6$t+~nHB4Zw7s`Pw@7_t}e{m>tWH ze05XEcykrM*O-XC@~uOT({fdEu(M2cxNB*M*fp-2f~71Kc%j=v(-eg&-c@Ahn7M$5 zh-q!(fu$D6&y9ruUZvaK+ymr}j{a~-C`mkWn=t{hOgWj!LotE=q2+I5+*IQ=4I0F4 zS1m#j6ipuuWu|g!K)`RFHYqi0yk4&C^qKtO=F_n0>-=38`j8Y5%18I;WH=V7$hvA| zs^46+;*}*LKKX#8cU?$7UOX__5fNRwmW3_1nu$4O#F5m~o#^||jE5c24LX)liIr0w zq`sju9?s=DC**i@y@%5lQ+j}daM)h_2{F1ZWm>C{nRB8k1$$ZsGUDH7gA51r3`mG+ zA6J+cMf2$scEb)vMm~b)QB0ZL(DAmL;*8IWmi+t&u?u(_9|#~CI+Q1LtF8YDhrWTr zby}avO4B_86N5i+&4HE+9^rKE{z-BoLfmO7cMma7dJD?M^;$Y&q3|4t09X!GyyVbw zb@NdvDj^$`1ku#j>ih)jzkfBLpcO=8HPc!BB)t5=8v&I9svuOntR|9c3PFm`^z^|(F2~8L7_RYk}KP|4DAF@wYIn+rx45vtW0U1bie5qmq=L3oe{axFIbis+Y z7&c?OpNe7Je?I-s2b#xEibqeGbCQAb#B6NO2ZkmaTTA{^uySC$%lTNVDOD5dnprHI zU-fJ$UO<#{-22V@aaeP!W>IvEUrHZr(Cw7j z)!QL;eH0nYsz#y|&%)#sy1I#dWs)_}Xf@*>fSyH1IXQ;QS6UWGE71Oz2Zp#2IKJlu z{Xi;K!f!dY%}zbBWg=3XO6NT1I!m^siY1>rkN!*>^}>#XJyog-RZa)Oe_egVob#?w zcAnbg8v0%BhF#~M_O2nc<*iKjGYf9WjpH8)*@%1@l>v&($}*h$r?$k}7Oze6j6sv? zby(`5)FSLl=kkIp!YbwfuH-_^tSLj0JAI{Q;-^^k*l6HALPz6ea`a44&)L{>CE-(# zR1c$U1#i`F1htkLe{0b4g_Nt(0v(aOFS86JrK!KB9aEYt~7 zidd5Fg=h>-Q?@Vn##NvDX+>nj0GjIwbiMJwYOwjtSn8@?4C$YD{9vch$3hm`(z6xw{?zueVZ8p5RkZm4jWP? zu?3475t}8hZa)t)-rPIH%ruz6+RBQmv1iL!3li1J*S9^;+CM~Z^6mtAEqiVUqg?6$-W$Nb=F_R0!0L6CT0 z#+qgU#b2{)ef@|x^DHPUpo0>g7%O2@44F~dKKIOP4k2%mVvqq8F(SG^P&HfH% zs?H@%Yo@v-HofY`xDfRzbGfJ^ZIg~9J~DiCm5)|-W4vxQh|XeO6c=nTh*51cS^gHoV(WZMJx0j+42P(#I9~iIgl}{{Tgfi&KAimDZA|d}KM-(juh*J< z@ek>Iqco7dA07-K=s;4i5w9EoEjd=EpbCa}O)x7MjU4sl)n9c{l?;{D^J%;rpw>Ey zOGeUoAYtex8!8~rhN@m@6`D<5{&77`60{s-6;dc8H@`pL9$z+y>EvP?HYpY39sUF! z*0nIp-5`l#!6Y7z8ma3=WRU&XONB*7uAmj}J&|rv5ENZE?~o+~X$VdV+aspR+vZE@ z_Ny*5&d~GyGF}BsFv4fp=AFfI9H`TI{RTir)H={|CcGR@h<6C&6JPk=;0dt$3Ua@E zI?ybKTnWvaWzUtCwK@3Q+4-DL$gPZu`moUF;z*g6hUKfX3;ZoN2lg-H_}_EN+nZe;(5-cr!609 z56%W-_o9%N-cjL_t0X~XzD>!1x{!{^hf(`8u0o@=oAiA=Fw_GYqv(i~)2|(VV z$(Ui2QvANm@UZ6V=*_Pgfoj`g7H3!eI6=NnFu!_K(#?~{_qruLQdbra z`I*hUy}}4i%ne`BYu%N>xghMybop^EC^z`d?Bl)p#~q2<&)NbkwFQ--6{oLqQh(@3 znpYJK+5S-c{k^qyuG6f6M`%KH)JUHVvh&*l0_d1oJ?pfriE-W|V+u=8xjtF8WLf=H zeM5>eDUrrDbs}Y_o%a|`4|bvAV|?Bh?k;L3(IlWK!wNJFA3oD|0^0i@fWTs&VBofH zo#CH8oXfIn!Z~y!NeA>#-9tqqDBu$ZfFK8~zrg*F48$mWeIrQ01L$keAKuhL}(@x%N*DN>oUo= zwov|-Q_(jcNlCt=!IropRPZgLcapk();=(_wWU9mNYmFYd4n1Vwfd@K)njpQkQ^O3 zQk{m3<@y$oQz#QOsm#@%Fpcl{$Y1o9B6o=1!N<@WADS(An3Y>{y{@81Dp7Vw^%-kP z;HP`D&iTJ?^$?PGC-`27%5M+mHl@+HR+9VGMTqfQp_`iAfNZ(2aU&MEH-ZWR%?0!$s}YJBrplD_dzKaFqMPQH^=V=6v%RgD=A+@IT?=B z3~$S}3sGNx@wA9rn?4@*XgvPu*u|yu#_Ot&PK56-a%%lyZnV2R`DoIk;i}G`ew04x z`jNGLn)WO0#=X^{oLi+U3gc_-UfnHm?i*rwU;WMF6t|=-{Koc!t5)5`);%aoyu0(W zTCcmm4m{oY>D9`;pCrCU{!KG#E;L_P45k{TyWAEu80o^n{T1rUE!Q zdUuDz0G<6YKp_Q9n>huMBfuhr^P^F=*945Q0&QL=Nvov<9js)rxm!K{eTU-=m zn(Xo5rf)lbf~z)5oJ#7l`$7{DfJ&iHc$kS>waPS7LA)JeO2ObJWYy1sx@I(O!pq1* zA-3uW4DVmC@;x(a_CVsS%rDkl*d;vbI}ses2%RmwU(#^aJjj>8*`KaLGS0^q`Wf=- zX!bz|m{nDMuWCuf7lJwl4orLzi<(_K>R7eg(>^iBRk+1_`=fJL1czr*Yw=2lIJ1Sn zsl!8g7HY8ZL3^|g%+0L@iqn>xM>Dbvan;ru4;g7XifdfVNLY89+xSI&*mS6P?fY>| zai)s|BkjpEB!Vo~l}yy6Xv-zhu8gPXxsFg1MksN**)W5kdX87N-4BVPei6Ub?;2;~ zAl}CGRnjA8r)_&qKfB|{iA>4eGFgdA0fH@(F+p`i4cz2R{D_U2{y5U)p#!;7--)PuK6fNRSS^x+dN;N}DM=-n;8!PUYF6 zmo5Y0r)~xBb=z@dm{EN)yh-plq_pd^dvO7xo~K`E)V^!$Lc2dT52%!vNM=a7AN!Ja z_9%FF<2uzDvmToYdQI*#`wFi}+QIC|K6EV=k8js{pfGe7!ZaBeL+D;Q;bYsSeXVAMrjHl z4Ws>DC>?+}+yiQvK}n$z$Rq((xMe_#(}!ycitQzmxT{9zKmlzMpf0Q$H5bM8Frpg! za2j-=J`?QwK`|3`fST!KQ1MSTCQepRWLUXDfS z+fhumvHqH-$a5JcO8c{ubPss-$SBa;G1V8X0hJTBs! z-lKm>sq%BQk6PO|94i=h6wcK;vg*~VJ71r1w^j&+z{%b1*5*zXP8EV44oiJK z=^BG%q?{?waN6V>fB$IMWM9E|1);1o-kL#=7NS{_#O}ep2u<_nM@~rBo7J#EahVTA z_u0%L49_gK8YI`N$NTmt^yORVuyNR@5*Jd~L%KDe=*?*j)1Caa!fu;KDbuvvGE;GY zd3tz@^k~zS`WoGO1-0#jZ!T%tn;QrkP3|7xM4{8w4jeVMX5O}h56GkJguAM{U+om! z5&8YtC{sFk1~t45-&l(k2mkKq6`Ir&YZ^?JELREBJkR&fe(w8K8yfe>Yfsxrl)vrozZ;$To5#A!<&!0wuSNgF z7atHF=UF5;rQ^)K5>INRa+PVC*&iMK9E9%r2m-g;3o^GP-xN!2br#oBd zabGpOxb&j-NnFe?kJOEgX>my}H+;QgUg=&ne#5&f`bAaDi(?Z>`Hp=<*)s`217VN6 zn_qwFd(RPleC*VhZ+>HPl4%tu<|#o~L62Y-zlza*ZRFj$RN*>y!D+g>eqBS!XLz;_c|@UC4E+2Wb=N@Dl z6etWtlWCDLUHBPKy@({9)`GG&9X4Ade~8d1w2I=h*vpdi5P^sU_Kqlw8n53^2D&5N z2=pW$=2JFhk*L?N`|&A(3_cD^7%%zPqlS>W-Z^O)oFOk@Mf)Trm?@+!=I&On1cGMb4DnqEMZGbF5T!?DfUv=;nZCm`6nosbwQ8BIJx~sM` zrmRU_siLgx?=|V*nk|StKzC=9O)Mw2*tMNLdQ=K3ape{TpiNZX0=Ai?EWgU^IG$v8 zKl+ET((X1h;krx0jTqT@ov2k4Hq%bwR_+t7JYYkfKkr6hUX^an+qk+XY1iS{PqSz8 zpHTZ5FNZT~E~DOuP020_X}lUVewIutW3xEaFPiDdU}eRQkC;M4)4v> zTqg-XB5$fedc<0C*H`rEur#eQv1VwEsRrLMWGLApBCQ=p84uEDdmFQlg>`ziSX{srl+KF^t_5c zpXqC5Zrxbd8$Y*yN7O)9Jf{0lnlyk~Mb)9KvMHR+9Cri5@5|p(iIUW#KO`KI-3BU? z$>#Cs0~$qOmN?~$nE^+0QA+?RX8z^!D#}W;w^+8io{OHR64Voy&4vk#3m#wr#EaL~ zL|rhMS94m{7mpGttG(U%w__3ef)lHS@z_25l8L6*?Tp#w=s#iatBae_>Z z8b3oSm*F*%Wg<){=pjWcbbP+rTr8!%>YAL2jl8R0QLcg8`)W6lH&c*1;c@ zOah%+#7Rm1le^yMgSXPR9Au>y^&gdI%(G`l-sWGxGpCdvVH3OBqwekJn7!g%%NiKC z-k+fyNVCXPC0<}Hw;Ra55!`!vf6b0-+=qTZT{YPQF6ri-`Z-tm*87{Z3b2I>9&Bwp z=%1$FE=-BJZz)G@^`GK|od`Hrx?G*-gh=Y>_~qAXvTCG;X&N1h-M#j8h>XX1R7sO= ztsAy*i@&ryMz`ml-9fvE3dYt4d&hVJ2G-3_#r7Lbd|Y*UTL519QRDS-An}Hgdu&7< zF{ch4th;g}v$^>CX!1B_7**|<7L#h*<*12Zw%HI=%t)bk3Z`m%o92)eW@c+C z@5a&Z#k8#RdED)Dir+4N#-ZK0ChEqb=6`J2u0(}D|+cZKiGs~!3AtK9d0U3B|$xjIFj`qxDVesxR} zx{hvh8xF}ovLj`0rsK8JLp-z2u)*S>FG(YP8$PjNccSQGYMBi-Rn)pYO=fyIlw_2Hn;^E)?%enV?d zaono=M3AT{gES}*??$7ZO`jp#{&BR(s;E%P6r!QnrT)mm*z~wtF-26 z7P0w!zg1_=`jIH(`SDh)_+q>J zN%cQvRldB}RI%c*oh=v7rewBJkTH`T+>Q$&_dd-iUC$Xhp9n-pyjh)c|AZ&jGfqjn z#oey#j!2cuNpDMfwOgWm_VDW30otc$W%I9?1_1`@5+acJMUte*^Cekx} zy{BhI%+2b9A%EUF@w>$Ss;W+BRnU4MVB7gC{&)BFcSSXp|8V5^P}j??$FI8NH)(|r zWBm>mg!@!j7a_N_jhzxcA5?~1j30mW<6|PBEa-gj0@v*Q&47^|*_uYLO}%8H^TLj= zWJ6`gI~Ht{LJ*Jce(y&M*bx`tSIuqPcJ-#s*gHRM+1EQzJ#l-x2$i4xnaAr+t9JYa zJ$$=rL)s_58e)%~d|722z!ldG;JadA^KULoR zrEB}QM{fyfLpBog^$w(?l%qHMahUkprh^B|_B|h{y?N*44EeZxw9a~@f z{oS8?4>rg5SB8|9&um2Q$F>q(nbBgyNOxSvnSl%U%)~>F(b9Y}?M6{SP1(RpGy5T0 zyuh#EcuD|@-Z1p$rq1krb`8&LAhl)S5PpV4PGvQFxcZaipW+H4EZqq@Cv1(fywsUm zvx>C>J$U~p!)kd#(W*mIp`e(6_!c38_~2vt&)nclBh42z_2rFt{@eDw$@ zQ(f_})Nq0-AGrEmbE^$5G*@XO?Wa_eVhD^eRrrZk!mOq5x(i8hzUJjq>YHH{OJ6UycRemwCYelnN@&U7jT*d4A zG1y>1=?F17ygHwpbiY76a);3U)L3o2H{YvbQPrMaAMryYjpq$$~Ek?}r?H zN$luy*|YZ($^D!6E{4_Kxjb6B;)^+9NF(QEe8kUUcMh^GEVRJxd^UbBvXLWhepJ%! zal2w2u6{fYKQQ;D>rbCxk$YV|MMAajo&w#$m=YI_`EUl=8JrzQ;5Gkkoeb>{OMDb`_ zf6N`IJFWL~$^$b8z8dZ2o~yi~`AdAG zWbd8_{j3dHB$3!4@+f5_7Ze79)wcdgpLqBo;z}9$CfI2X%cW$de|5<4lA0VfzSmrQ zG@6I*&r?iw>UWni=`DEba=XP1Z&u?QIm_d^(r&Og$zbaXW3Zhy*q06T>AJ69fBEMR ze``bc^WST(_<#C+QiST~iUgZ)7A*#;fH{H=qumZR(iDu6sdOQkAIpt#QL(c`$*eR^ z`3$IOm__=IpG75D2zRbCP42?+9vWHyOykC&Qe1Td3Xt(qVwkz{G1X}^pJU5grC88| zidCRMk7}g8`vb86iqn|53m#mTtLY-ky(rUt$srv9Mno+U5%UdiFi~`_EcN>P+Ki^z z3kjLY>ezb5c$4S$)AUTxgZ3sfGL~I%#9B(4sx+Bn+e{Zkh^?9f>?G;@7==`s2)4DR zAVk5DBGODiQUaChT-hsjNV`QZ_Ty5g^)UGE~aRXhH(ysD4y=7@1K$5Vhy>3 z#}OyWHsWeN33S?jOE@GnX#i!RwCt9F)`U>79YmwmUiL=;M@c~GbV$=Tis_T8U|S~R zd2l^hLQv5kfYlCup>+fmVOfgzyM=owk*ye$*_Q!j^_||Uva>Iht!=Zh3qgJ4@*Jbx zl6d7p-x`s8Yv7YJV&N4rf|kMYQ5pB2!DqG}$RtZ*w>w?hjD3vzao^U=wolKom3v0x z>mqJkJTepN*U+-6E&#`Cu8rI8X!riyF36Z-+MIW%6Ok=e0DyDJ>8v|kbu zE=e=qsUz6i%U#q#)+{5Rm*dYJo1SDkJ?Jgk^Y&F#p6idIlbwFGV{)fbnajZG9@Rj9 z+_t#f&lZF(4XUr+_NOPfM{y%+yLi-wr|R$erj-_~sNxI9%3hpT&0g#(m~i~AcRE$= z|Aq21txIfHNNg8=PPt8ai$%MnOn&ktw?sL9;MmSBb-(pIy%sq5{{2MNNtcivRjnH-Uy`NsG3-Ua`!CR(s)d6kYb@l|T=#*Ma(@~>8PcG9|X7FsDx zK6}@(LY`UstKV`f=rM-GJkqQDtg9-Qn6fB+Ti!9_LnNOQD=HFc9FEm0jHCm(QfrQ{I)7{y`@0tp)Iy%A2benlpPtM9E*DSK?mSP9{(kc! z1SsOCm(>et6JIL(jeu`y1`;6H1(I?=LX1Yu$C%8s9qQjvMw+|`3~RdCRAG3*{ov$y zq1d<_skXt8hqf_FH){tnaed7kF<}xUU;@&VKZE{a>o(jToKFN1{HdO_HiC3;k49QT z=iOdU(LU-3U<4t?OlqH@VLe|hgUa;m#=Y1m{bl}6XXMzTbr9X%?SMv--xf{O_%8ah!R2PE!UY-6-a6hiS zz{Sd*o|!nT${yaJtLO>Fl~^=?S@9WnVe^2bAC%ukHH=4de%f}Q$03KyQY%TxyF%hj z4&)stCw_mf%4clCGPl1^Q7I%ClH^{5{g~=?EeA)nYXXJMmQALKVa`vhS4&+JBTk&Z ze0s0%-CDoD+_-W!EzEA*awdFr$BopIFMFlpjK?9r+8xB!R@mUZYj1zQrd5Oe?ctw~ zr%FCm+ziV-cw97F%rFVHC_xyJtg8|cM?0!=g|{539hHwvGlQ|w)osU@cCK2FVmM`-OnrKMpy!km<>FgE>QSl^Lt zqhHNzsAybIjo<6k3mvHqkBgjZ)s{)BEF|{7wmu8#V}~Bbxm@d742yGJ)%e4Z=EN*y-U&L;TY_rHKe43W}lkq+}6}M(&(F#?s+te zNY2AfOExB#)W@zV{)x%1=J^C%!ge3xOUpN}=j+x9a%R6h4*A4b0H79_-0s$MR{%a@EC4bP<6*W?4EDzbNoQ{^-B>;HgfjAUx6pTtB}$1BQp{|# z3lu@@z2li~vo1@8zM;dGIRERaSR_GnS#HQmE9%K}Pa}1f43|DsXk?aPGZuaJHj+Q= zWMdd*dq!c)vauUJES$g4{YovB<2hGu>QIRmy3IFRiI17z$3%3v)3OY;A}kg`D)}xq zA|S9m)`na-za3K}Tg`LXilkQ28g7^FP-12-?ZMAPfVSeU2IYgYY6_xJKaTMr&PCck zzHT@8J+&~vk*=B(UCkZXf97G*`h3qL_UtEJslTNrJq~?8@{uYFLYKx^iE;;_L zK1BX0x=JyClGMy%FgclSzC&nov`Kb9@SCZiuLA74<_Z>k4})a>ti6pxqLqBmM>8E& z#zE5F)EpvVdRp8Aw+2|BMaA_vy*d~`jCCw9dFDA%XX6|@bdwu#Wjcg2WFP5qbaTaM zIys|$1?Rb#vMsLfQA+rR+CR4j9SxWGCnQ!Kt67)5tq0$qQMH{&zBtFjS2-$5cw6F! z+8(JAo36wp_neSD{c9PUMFH(;7v-0O z%N2B8JdPUW1r!+@#O#HftI@wi%XjC-j3(tf6JvS{4MRTi zuc?Q%+Mx$ zgrc}!51MagTP>qI=V-z;vh$6x=A?oPAv5N4Qb!NQ?_axQbp!-+^NV6ONhn* zWmg>>!97;Wmx*;$ReCJi|j%K26hEV z%WMNF6ITs%05RdF1(JCKLkt()lQ!X+SR{}gA(-dJ8ZNR#_$MS0?rk;T3W{yRYiY^C z%d^}G?6yNDGv~R$0y&n|s%4E|$jHLLP->kjgp){FT+!g*z{tY@NR-_TH+?^?j$X|j zwXTFakZt7o+8KFIpURO4?M^TOIbb@a-BnZcM9P~Vx1}Ev$@TDy`&=j;_r?RK71^yz z`>~$?44{zJNmGm3EJEwH5l8wtjXzESzsDcz`|(*Ku>FYPBn~x zpaEXg53;epBA}R>>apSiIL0plgePa?7586s0(i`9Q15#7$%Zy(3=6Z&!WkGJw9VCt zE&eeMDCm@4cMZ=zc^>oh4pC}WCcGfal&e^gu8Xo{9#=SbRTY!r`eA%&yJ#yX0#!&! z>W>#~`x{p;`c)$#(MQ$=HD1+7^L(KPE}jHJ@u&XcluGO?4|tAIqkKcJF>0n~A31$x z9x}hSdDLRF_76ve@Wp_ngkhbR84p?=!nbou2i@TY0cJ}T672Kp42<`=B!2Gg1ID7} zfY;~ymtttN*OwQ!WFl+UEW>d6E-l|VeKdyu>*k_z*jNu%KY?p{tGM9(HS70j@pU(d z9ku3~zOWO>`cb)!S2cu(_ff}BZ;j|;yN{k4tk$F&%dlpZW%R_?J0Eql*s&G+-i)F* zha$cg8#EVePC7U$3iaHR%$2YQ?>5yq6n@}qSEqLntIBIf|M|Bw4d$P3J$AN=_!rQ?ui>of7(NPnr$B|GtsT&o^_lI-ajC`oifQ9n+&y7 z9`UsnBVr5twG1k5jBMD&KeWINylxAW=rHyA0?+8XE7G*6_rO-ps=-rMr+k_Zm8pPd z3}U&v@cxkSC%9?&%Lqu4t8mP~XY>x+ETD)b`QSvouHS#~q-s^O7sMiP(Eq*tqDUw_YTk*LH&xzdDij)|udN6)<4 z?ZEm~SZRL=Z(~qVpDp(n5%LQ|HGI_sUk>WFzCEd`AE%XmTW{j2@jz?rl(T%X(`T|x zavwzP2jCZ2UIMbFJOD8SNCfZjyE}P|{0}`J1dzJByBCZlYM;2BfBbu& zh5yQL&*d%eNPqgT0#$O5be5`k^_~caHsP!D7+a_Cy1UEdA8iyu{$2nXE-PO3TJ~0> zVm4q&u8(2Dllc6TuFsanQ?i_A5(%#jO?aIbOsPgZr22bNf<`#3YX_pGmJJZ8IQBh( z*1+hL2(SWI2yvfxR6d(>&Z)TvNlC z{8K#+oxDDAH4zfoZNxsm#G`-(m${ZIgFgft0GjU`z%kJ5>MqFysF$$aylzry$Tued zPTxQjl}(bH}hz zDH}7(6my}A2hJko#8ii#cBj+t4M(tV8MkpP+Ssx&)g-gz1A-HViO9?bq2tAI-fhkWjqc^9wTLYc?xkjvoH8 z8IehT^=#C+tINbkIwRw|h!9IqprpYfS5Myp<`9gRHVrn!q05h_zx=)70TN%3jhkBf zmJt_(B+{_FS_hpwA9)K+p)xn;nVPQKG?i#gctsB092Qlm6rY^m5Xq@-8|E71)o{03 zc6)so)X}o-L+1AfF1U@IE%M$g8F0J#PI<~pa@vjrx34akYw)@2wj4MXETP78@0~eY z@O)I%>2t8~n6^FD%)Da!^T?U%F??XPtrES{jJ)D;cSYVHU?#B5AI+hlee-6ZH`9A8 zEzlNR~CYP`cov|CYbaOZnZ6(Z1L=;ZapsC#3ArrEMz`k1-@i&fL`|;{5NX z4E<^5m33EwCYCh?kj)SJHt`5zPJ7lTB-A%IgXQSo9cZ5XG5qMIHHL5)ZQjf6R1dnIiQxQl_y1b_(olavH* z@q@?2{AZE%uQ`Q*%x88#Mn-x$)+83Ubk0seMq|gUCkP-Ns0ENc$l@Fz?(+he%zFIf z7iA<$Oo{efx0X99J?Jft?|bk?UU*|zE^jW(RP-a*SW%N`=@?+&dlr9|r>1IvjT zoxNGDC9djkjnfA-fJK^0Wbehq_qjEHp3}g^Ud$yBUK5R%cVEnWDyPs&kvReHZ&ZFg|} z%xDOTL>I4BBH0k&4X;DDKKR8R!@!!GljSZ?82_66la)B-Rfk{5%WL1DB775f{cTpg zUz6k0IA(6E|6mJB6jqPr8_@Y=tTDU->NmK?q|cVLX>3m znK&+n1${?|Vr~3a?sx52GlJV}1;`+K*yGV+{WK$nEx@ zf|aB0-d{C;&Yqa?osPP`X|`%^5~?#D^QA$VoKcv1=8?}#>KTjN5J!Xh56#%h`{&#q z;@yvV<7Qeg*H(`n*!$<-LKqz(UliVIh#Px+*Gl7t=D+QV|9AvBa)Zn-Al0r!3vvu+ zMUOHp2Nx*5?V{%Fr)-w+6A4O$T8-0WyucPSO!Bl>2^b3!4O|_JWhe%aKN@GBF{j^3 zm5Cb+b+Wkb2F{w%MBTm+++!lTp8hpDSi9gSl~Bi2XqAt*BDgcih@01Gb5NOC(}SBy z%illHvPc|~uf!;b!e?Z)toq~`K`27w%@_s|0=)AX(~5H;&7#mS4G!#UmQ5CHqneg# zYr=jnr+`HZwLs)9wug1F31bcf*NEiR#TQCX=}l0N%7Z~;1xiH2fPRL5h;A$HE_$k? zwT_Ls^u2Vtb>f1yd{z$*FrgyPck)X0@I{zLmxX_5T-iC*InD+F`16^wtQfq=Ax%pM z*f-DsvDE)uhkwZ6pBPH}1AP(If6XcMZ~mz#k;NSF=R=*UJ<)o%5NpOm{@uJ5a z#Q=0>Zp&{0rL*adXHSSNDJ1@+zP*ozTYd%T3abk`nSt5J}uNPR~jJ(px^^(v=BnWcEM6|RRg2R8QlBdH@B#F%^d zN(|?PfX?shZ6O^gG>q)0_m2Su zaJp;&qC0D#fup+`6`taoo+$ea+=2ne`_6^KgVI3gUnR&J-1=-<2;0j7M2$TmsEk2A zxH|w@6Onlw^+bF! z6yta`_PL0gH3gpNZ`CtuM%=?j#$S1E-h2jK0iSk{jN(_no7@mV-TLEmeZ)isI5oRp zKNre0o`ZXnZl&$@G~BkVOmBV^%?uZ(gy7DWEunUMPGawkhqrMzznl7y@3+5t_pou4 z@iG;U&bT)`GpY2D+)cZ!Fj zIZnaf#h=LcFZhsqEWvjl(Vs-VIqt={6=)uEewrfd2ssoEOLWAlMgy=?r&&f)dAkn1 zpYpWtl}cG~z+9d@v;aIV>iBSTV@fG+KZBO&xeE?SlI?$<1mgr0r97^gN7bxeFPG)| zz;%{cf{+<(G$_6|v~Srhz!AzzCeJ6?QX>H{Y^`o{6&#LIQa?61~@TYf95~vH1s@#>g07L z018}SQVjHLA{9-5=$C}oa{J%>ApKW-eW=82XH+R~k82YPGy)=wb_AIg7MJtUI4!I( ziSLf-iMg8dV-CO<=uK_#*rW2~lQGCZ+sin-uDyZ(O_-Y;YhO#7g?L;0s!W z9RX6Ed-m!`#WvRp@fh|7Mh%RW5Ua$Gh4-t4EnuH#^zDe2JWjx|0-%&`L`%|&`g*F< zKpq;mVVv$;-h|)l0LRhEeQDi~U1!wE(mgREOd^dee39;s)|o}K&>`U|U+j&AekP2* zmt^X!+2GRF33dyDQA@5TzM0b4fGdDLWkJ*XVG`}!WO7+Y7}TgI4$F(rV}o83vc5Q+ zDG;}fCsNEB1xX2WSGa$Q0zs;tyv*5;(sBL3!~(z?83T|n07Lu{B@gp#!N2?l(_d7Pg(HVd#}XAQ{#3&N6O2W@rzrx3hzzx^+>pzXixy$I z2JfFy`W4Fj0$&FyE^U7^W;OS3s_a0OA4Ue>rbK%5rwGvf2RWr9`8P+@+EHQgM8pnr zhqsEY{WMwz!50nVDQ9*UW*3&m>H=C&$I|aqZ zO9E=o-)DXMGsAjlo|4a^y<37FFU0WRn^ui&6dQr#t7?xo4J{7u-7O93BYPftl_2n5 zk)Du^42wGYM5Xc0ZH29Z6k#j*v=67yrYt^T_J-_5%n@$C)=OJz3|Olz^N&@Ff)Ysf zf&zVa%zh*$xZ2rUGtJ!XS6_p2#8PaW;1g#c6ioGJct#?9t`526`zXiczHRm6N|&F< z=p*^y6k~A+MKpC+ePSdn5+&;npc|dmu3X=a!2UvWgz=>VKI$xWXIwMI%Wb4MgNQNPW8?6C|P_uFYlr<%Gs52uVf-QP!h->ZIT zvw{&89&a~*8mvB`4H+%$6zG`=LSm0$;LMR_*U#jl2CSZ2M7&)gW!kjnJ{39wu(Acpm`Nc`DRRRbJbmbH= zgV7C4ypj|r2%S8-G39MnB{_!Ni9A>{mza9gnEP`+PC8z(pTJBy+3A)rPz^R2ZVQ8JuMPIuLlmi z!vmJcdAsD3yXi<76JS`En1BJcBhLgeND+(-)dIKNR{iq(zvAHfcm7Pgmi3Yqd(p2I zWyqofR`@KSAdj-~6vP!9rK50UkB%UARw)nxxTlNQ(k=siraR7!gW%~%5R}71lFxTo zN5G9}N-;p(zYtWtp)+H`m*PYC|IlvBM~RTqEs&k5L5Rda(bZLR#~cXg0!aeg($HzT z?5=@2kS5&WySDI6K+v1l1zkHT@w$kbhp*pK%>yeK=DBowPJH%|lu=$oa;A%&-9B7c z6h8Mp#@X9XI_yR(%&9|hHq9BWXY8qi6~v0^V8pjFkFqJc&h!qaGSv$Bsr}&F>IvQu zBS^~e*>cXRH!hnD-=@I32b^K~PON%gksqYOo)h#|ZDyiw4q> z5i?2Q8JbxzdY(6*e^MDFqU{<V6kQ2(fFncM zIK^G>kiCpaFW`)F2QJ&E+sL^29hD~^P5h*@oFN0S%BkNSqLmeZVGcK=k%LV9(*3Zp zCfHU9b-{}(xgj_KXP?9^!VJ6_XKgQ)4-{jO?Z*b%Q&+e43TiuVqpR6i)!3}tA@cM* zJj7G9unOaQUKt-6W}R*mEq-MmZ)|T8#SY+K7wx)GX?E1wM>eZ?j9f*fM(b_9g+Qg8 z&k3Jc+M)x;zFWj#>hxm~HXLV;R(ho8<$^4Gu8*Kqc8sl9<8|+UDk9w$?ZNf;BP~)f z|CM^wbmVUI12!~v?7w*&mCe$66ykJ(f2_tm%)Bs%ni9quXL3s*x1{c9J|N3ZTy9j`$SPN`g2RHUVQ1eGU`Ya9d9WC>QAsJq!Q~2#g=aAaITnsW7@d&Sj&R<2V7hO;r(2M z$R~GZ`F)>Z4%JcV3w3n*9-bHv(5U>HP?8THj}9sJd2dTZ;Z(cnT(@>RiDy||KbKRW z{;HvGwYqg6G|JkYLa|`+14~6>Jnx~Tfd;GqN-Z?fumSnXIvgB_cy&9T-qNVaQ7P3! zk>#~+IOo9eAd3W4sdSTEWG_YXfG+(V*)M zD7t@!33n3k`AMGxsx0c`p4~ME^B!>e^^~&~ES2CuX)vzfTYAJKousmVHMVucih_j) zHH-6o$h!-3kusXegHAy_>X|oAEog`I7#Wk#>F(i*E`W)|>$wJ1hYZ@kE?$J9)qdEA zzu=OP|HCiNRC4tJ0vW*k*{W|w!TMP%d+1$qS@%4UyU2+9|B$i&&Ij!O;dck&ipjG% z-liL_OMP)KP^9M?v^iX$o+D#d4F&X$Fhx&sp5C6C87BORG@>J8)@sMx!6GOX6jAOW zW5RQgd+$Po?`3}BaV@Y4F|K55#2e(`Xd?l_51cog_5K)kZ^Nzl)^qK1BO7~JF!rQr`?1G~5=$-0 z+tE)S+ErzbD;0XtILv&iNzcjQ9I=a6=tXd}8WjZ$`k?7J;rkgz1`hC<5DgmS+?}hBrJ!$Xjbef}9|_Qs9SfV#KK1vTN&|(JBOh)h zRTa1Lt&i`N^zW|u$SYeGTN+QImwpjMxI7|_uQwP)-M;WKs=HMz)x_Be=Y_7bc22Xb zt0r1WCSf*Bg%QPWHKA^2aINM>zMmfDHy=}-w3!HrACruJYE4lHz+P-Ja76#aEWVF= z7UpgC)dwT_U~_D>FpTt~Uu`zM!_?QjsNd}tzw*_5C<=k20~su>oaacTCw&{bs5|x+ z5v6)wK8L^MJocM4d7|PD+4%?f!Hf1e#S$NJUY@n%r|9H`+#r@8TL|MySKAotm_5`**2d5KtoF0tEYww=`q4L_Jt0TIL2A_dij{L@ znFm?MDIvj4A%TYyUa&Ep(kW|1moWknH-%ah5LNQeaY`u@qjS zqC47N-QQzX-NS)JP8IU#zNbcsuRHu)bwc9Ki;GH6+}hR!9u&*CF8FqFBdN#B*=x_y;g&iH9TlV6$XxGHTh8@cpJLm*6P6`xIEt0E8g% zD=_*uqmGc%{O~>~u&Po0BIEwkE)Y_VR|OJu3;*#*{hyuUQ1?jlB30*NR7n;!f4=~? zmJRq+FBAW2N(#IeUgW%1s_>TK`Yi_#uBmZ#WH1}PfEldE)bR#iZasrVnTqUCW9!Jo z6`0r>PpECXAC_zfmS&+Z=8*H|RKEa0jv&AiXTEbGewf|YWrtZdMg4FOl2M;#5;Nhp zDzo7DZSrhpI&1(>23@vxKjY{Tb{INfgP%*Xn>_-nTz-1NG`;WsSVN(AKbT^BeUkY2 z10G5&?=VLFUAoc#O%SKf(Xw$^5Rf116sPgJC^-WHMjm_IpKm;v%v?p~ME`}b?pzG2 zkzy5kF`xgHRo6I6jymZ3a7g)upHb!8m;UgM_Zn@yyLut#!%Sw3N^B$x8ck_gu|x3G z4Qx>VaERG{aKXw^iBBNktnyIDF;j-8+Vn|wp+|_(Xoxt~(yEp7R${E+zYjw@=Jgu& zEzCjyeS{XUQrwv$%8|9C*MVA|H2e!LgOz8ZxYx^;QitH@*@n`edoS~q-Ug@!*g3>h zBo=hzZ+L=USAQH_YxcI}rkyOzxAU9ED>86dLf@jT1JSQI#d=Jp5nuM& zcKvrr-Q%qUUjVvXm0SPNyfC-Qp9C>U9P^!AIu)-Ex858HcssTuwwukGI5EH1cCEhP z*~5($-G}?8SrZ=O<@W0FAtxqzrt)-Qf-QWtbuKn!vDWz81M%8er*c{U!xs3a>J#F; z1hF#xxT^aa(ygYjWP!{HHCQA6&ZsKrXBXV!-DvQ6*vLzyqSH3M7yR>Y`hMg|)ucZC zQAV*|pOau}c$(p^&?l!&QxmI9!#(w6*?HxDK|~kzP8FnlhMvmrx#-7W4UU;hQRbX| z;&d+Y0q|TmgMBh4smY*Dly_|-W+69uNMfW9ZQCl_H}>k|?)h045&y^-%HV(rZbly3 z{=BlH{xM&#PyPjrlC-hlQi#%2GSUU)B!dudsC0oOd)Gvw7mO}n>~ zt5qPtq^Olh3m^X)58W^)fBO9AZDlY!*Q08fB2re7+*^2rsJRMNpm*naj{A%TJBZ>N z`@OdYpv;DqfHbcp-cj#WlkWBhB#YcL7;2SPv4jm^5ld|RRvQXewh}Tf=%c&u z)7|5@*m&lsedlDk1RRSir3RON%}+^ZkvyaYu<=D5P5=Br?+G)w&3K@ zM%;~Jy)C_|vJI(ty%~&LhSV&CeL)_I3%E94DI^4zPIsSlgt#2C1|*_~WW^^sFQ@@E zj-+FW5r2wevf)VsA^=C^xN1LM-eaEaw&0=sqNm9>uRy0}NLX_#AJ#y*bA_EIGC-kKg$5JE0x76u za_nu;DFuh_%s7b}(1lXPWrpMb`ae6t{=GlwACMr{HsmuE#f2;wvdT|Ee_@4Z=zQHw2K1Vkr{sJWRqMHY6!9v5Dx23za$3*s?SyATS78Lk7+>FmuiwW+(C5JtZ>LC!LeP}?CNp}hv?N1TnN5F4!pc@UKr*Fcw zm@0Gr6IC}hV9Ut@{hZ~1{JO{P3WmS*l5{?)PhS^|F%3MOGfrJO_x;np2!Kq-DUpiWE6~fSj~!6$SK{W*r|C}xFPXoZkW@0+ zq}F0Y16AM_(n=}t7c53cR85wZ@t+mw7-(df$coB2#fvrvJ&0xYzU(e)$?!z0wB`=t14nje24f!eXfFKpVIEFLqje-~CV82$C7}czeH9k_X~eI;JEyF# zfD?{{#@ugDuKZPsHt1rFMBS~~y$-KbSijJ??}0yLj7CT>u7Q0THN>Vem-KQv_WRT< za-0>lZQG`Cefdp45;5m!9gsogYOcgjLxafGQ+u8RTb>;Hs0HztLJOOnmK4~;=%|e? zqOE9SpiOMqUtG1-D>-|8CU#>;nA$+Tq7%WMaxSkZnlcX^4&$0dM+n+08%!?%C^f|u znzND^@xl6@fb>)OslMu&9Jtt{PwqY8Eyf?2e&q&iqc>v9z}We>beJY{&O>o5`xGbg zeKvBn%}?+ebpwAknbtR>YCWs^iYL)IWb7_PqLt-htK+gev|!}DJj$_owKlk$(wj$7 zrZujN{an`-2Y)>_s?g@2e~Yq!59f!xD^VFEdI9{R7~+MW>c+iKOl%ls<}bt1m-&N- zL%mJ1LDFi49pJA(b-R(X0fnaUpW2cUY8_#R!)ecD=rIn7UtRQS0DbubzYq>wbU-m}IMQ=5c@H?`73l_s|GB3BUp$*Y zT7c<~L1=R;-Zf-slg0VV9$hX^_t2+*8SgGZb$a9$*jll)S)Nl8lgx9XglFF{9%Ai! zXF^_wYu2BOx13)wjzIgsG2Gw$yKt(z2KH7MMYl|CR*WMVC;xW97Q%j>SWEE>CfZjh#Gzn`eAnL7l3-Vb70yK_$Wz`xOo_=RPM}pbnR2amLnj z#wyZ`+TAhtGjtX;DaNasugzk*-3>2#Wqoo(`+YjP#i)!TPw2QuD5k(dWXozp%_onc zeAwNE1w;xLU_B1reVrCoxzuIb`4aBIYcw#ddMemm8JuUh1ObaOSVUA5W0g-e1n-(GDlENYcw zR0z-DqQ(cAX@jDO#r=rU`*jWLLp7hGl^|jBr=wW2{>$cBAoeOpty6byeJY(t#aeiD zd!i7W=judkxJ!_+b0SSlj5Z0DMBlns>`S2+m(z0l6Lx&IKKyCw+13Q~_f|sU#ZA_zWB3DaVR)Kuzlxx-qSL10 zX|*P$fAjN0w)NZQNL#=OK zqkokplqJ#jaAyeW)|7q^wsvVGPEAPozIbuDqvg>-$jV`41EG#iBb@gi%N%Ktb|B@S z=tg#@_|ykw-Ex}aYC3=)B%h6Z(8)jKDfIeW*xPg4q9&y#M;VBLz`U>IzSOe)w~q4>02~xf&on z4$u}H^Dsa^Hv<$)qbma80HBAn;ePtiWj60e+B=WF9*&y%&)xoieeVBV%fxT*D&CUL z$u@LF0Z^j5)iqTlaG6VvWzfUWVCU!){)VMKS*B1v$llOZb34>^4jn+EqiLj$v?fxN z1`-^g@+Hp^eM%t#gwZbbL;;>OnEL~M_tt{iRY1Bx;ze$O_VY#V?#5i*^%s8T_4-ZE$;;N2Xr91WXCBOhpS)|qX;LB z>KbS#m$~2Hy^aJQwEY~2p{K7Sqz0FOY=CefOkWzKv3-Yp8}-EIjd&-vf8HN#>YkSG zP>kSD&@_d5Nn~ZFkZ3#3EwWhG9d1_@QtGN_5tRWA)ywo`mBnBIcB{~bB1movYIDqa z`fI?8o#8}K5QHz~B;jgw8UiXhLp^EK=5zAjfs(>!=*}>Nd*nwkx1!N`8F?(v$&?fP z+zhY{?;;Jg;KqC%>_s~T6vP{ql+Jl<4~fWkus9Hj%NOc>ak&Cbval~|tC4Qa0{0QV zF2me^yQDV~b&h}TN^xquF{K$11Kc=|`zGlu3%tu%=0+rWjAzmNQOzE#?{a{v#`qSQO?1y_>;VHxKM@!NL?w9F6W$F}WfJU46;U&q@y z#ECbbNG002EgXgXc==1U4zEU2ikfwCo(#@(K;dx<{k#AM-;*o}KhRXY(h0@^InWn# z$fr}%U|2b-T5#z`UkdKn#1_I@>XTl!dmR36wBLu z$lNX?OG$!jJz{V6D=-2faheUDs5HkjCB{{D{r;xOR$F>PO&486`Z|?(D|ptj;WhW& z?#V^ys2CxIUSJlCK4=({(|36N%!>QtXg8JUOSVY)AFwB`(!A!)A^XdEi?N`#UzHW3 z2YR}F86=;&$2&6aWM?7IYJ3Am?>IQ1?DagN#_VPpJ)NgN=n}1i&4+33^cl0Lh$%f$ zI2uC+aXQ$U9`$^GuWi_;w&qCp;wMOt@1;hQ-c-SB!nba?NskFp%G+6Y?>>VPJ8oPc zXUw6m|Mlmy&R$QB!tZbHw4SRZac(fTk4AJ7+HOY9&Pzbx4c8#zVf`_q$8n~CBo)Fs z(Ukv7-ayefS)An97Ld7m@``hM;Az$iO+rUfPc^o5Bh>k81A*o)LR-y#!W5mN0&N~? zZ_0~V2nHZ7I4tMo1?YQDQ3e24lt<^mf^D1`j!Jf3cKiVd`D3djnT$5VeEKW9Q#z=N z{Uf~isfXWj!4ya?0%yKE2uQ;?{HN3YzdrDPZbOs%2u&O|(Uhv?dWY1-1*sdc5PSUP zM!1M$HS4uBvz6u)_X|uNiuX3NJ3V(?u@eq&}zW4lOB_Y%mZ-A1of<6*b|?h zYMj&NaxZ(w8JL@P^a_c8KTEp;N9r8Jo)RRqnBE+zibcMYC?Er>r<@JQp=?`J)!2De zKB1%aC5}Y@oh9oYMI~1+RI|}1oguW&K-NK9RSno2<@tjQeNS@52CR%Nl^9ZP{@wra z+tFncC-cJCToKzh6QfHjOpTpnv5;Udz(<;z@F(`F>_s~UK+$kC!v#AZih31xwIWc@ z_!@DruF*}}r6DQk`r|2O5(Tvy>MOTgD08wq{@R*b%WBP%KANAN@B+1ZIY#)nU;a%N zgx1v)qYWWTz2O5I#olCr5hV43G_5x<`yc!WRV1n0%E>)^XQU@i`&qF6Yk%J{e|nq- z^15R%Gn0Ef8pf4DJux(4aAo-h^7?<&RbPaXyR|gd_n&7lYaH_?Yn_jW>xab;fTv&n zdV0M4iAAfg%?Gq|w#v6c)vfODkx|6Z---jk1#^XS{tw#t+QU|@cD7=XENbj&TVej5 zOv%`<6@#H)+nOh&W`?&zJ`E4V=AZOza5-K0azf(U?`mNo`r^L_jw-Ijx$_@d*c{%A zY>#wr>=C`Z|23|mRj9F$IzfJtn(^9;WocKS6d|tu40pF55|7XCn4PLwXp7%6IJ0Gi z0vtZP_;S6#qZ0oMUm;e&7s-l5*-YwYk*4*?7p@qhSabBf)ew7aWMX~^* zp6+)z-^sgUzDzUY@Dg|CIQ$DT`=|3|FEY6AbCykv9wV~YD&!Y@K=f?=iN!9CunwpW zJvVj-G5YZxAu6`n<{mD#{*eUW!T)-uhy1-SCin(&s3g!&7wu%Ec zPrFc$#i!Mmiw%+W-M>Z9xTJWI(s=;i$ud%4Tm%8H_H3(zQ>osY16?{GI%=;dvcGWf}4+iEEc z{cPN43#NXv4Vi*TCM!A^D0GSvoX8N8Tjj^Q5mWx+`m!(cq4H;%7UW(y(GaN}Fzsg@ z%Hbz4CA8#f_rMaV@RVroHx>_UnePH@1iJ1H2`2&h-a?auB#3;94h_p{u^0- zrL)VUPs=3J-cG|k{#>@~a-@Uo#jt;P+1Tbwo>-Bk)`L}Aaxy*!{wVFYaMd)(SHR!Y z(Z(C@hrecbr~M%J%%q$A54dy}2t@+jqpX-lpm1aury2`v1XBMaWcyz|4P=7YP>l7b zR@DKrABt<1tOHd~kCCo756j1=P0$9yc8eISV?uURExDi6CWg5Y(I;m$P0?f*Lf5p* zT%(B7`&XV5|2#)(7MK;Dlm@+usmb)Lr<~xlwK?Yh zOr{x2&M$$&^R}&xQby2^x$uCjUHOcYiU!i5G_4H2Le8k>)m_wY9%r5^Ly2%rihqxS zcQO*ewG%uJPKwraqAr1rCH^z*XG)ssdV28O*sY`Mhxv9xU}^k< z)F$U6NoBtVr9s#_q42T<fc?HR5cu;hdX{>PJd{4oSTSaz8v=*eP}BaF^WZb z&c?I8DT|eq&%}&Xyd8-W23tiyxd-#taLeUw#Uo!M=j|?FA{||PN&Z!C$EQfsr$Q^4 zV*n$rabWFsWbbWK|EJCMMboy8Hrp0#PEBhC#(WepE!X(%<3Lp5WLtv1OPtU9-47V* znB#cF!tcW?I8WzmbE;H3`L!ixTEn;)@JJKN6ay2s`t0jVUbCmM@WI^2d8&c)(WTG4*I7s zBfp0#ch~&$Z^516JZN!y51A~Ev|4S z&IG-)5o!BT``x1UaA332X4yhP{SF=JSNcKAxjZG9LSvteywgBsv4X1yB_p=hqf}TA zifpVgAMa^sKpIX6u0qKt=O0XYXcb&FCe&-`akWm)$i3}8TrOsW z&RZa^eGPx_&97rDM?`N3H9WXEp19(r1^>|{W^(~yWjW6I{QLfN@-XiRjKbZx%7R1vBMgR-RArp_#Y%q_D~GQu#Nsp zN{ldDg|3IuPR{zv^=*D=7#JzvLIa&<^<8J1^lCmaRne}+%&}ZD8pR-FUX3u%^_P-P znLBiVl<0uiKOd6X*NPZKs$}n;3wS&Qy!B>XHVMdS+w1K#gsNf|fI^coA1L>ABTbv^gd4^!xY1_(i9&+TW{h>a^kN=K>vHW;PYhz4o^jm_nV7d!I%-+}Z96!T_?ht$>agb>aO>ClQrgpk_kjZ$ z3Cy6B{(=w#ObcJk8U=OUDX6LT^sLU9VBAG7!(?co_ne&vQ#1vFHU@-qM81RZ&e07 zK}=_}@ZbmoQ0%jF?ebo&-XBuxUz6a3^+h-Rd#R^JcJx&7Ds%_%&u>EDkcfTrOCfQr zp4L1C*tgI2*rU3g5lqB}jA>A-_h{(d6>kRUYl8uzwU(+I+uu_W`H1u)P{6Dh&&D<& z^u?mR!xqK6;pRIN_ou3m*G98YNj@cQxb*z}BriXkJ;fNOh=~u^81Y`EEraUN; zyW5%Mrp-hcTdHp>DjLsgo4su_(f4Y{vStLg@OyIca;#+N{_Yn^&{V{)m&}TPhBg=M z3f-=$mOv)(8?-gfq zlI$?gb6eC^f&Lt?Pou*{9rM@Dl(R8=Qhft3C($;uz#DpEivux;4&7a(JewCzcQ-v@ zZ)V--h*3NGZd^N6cjOx4ltSQRTk~}x(AN<}5OnWHGkZIS7mmdV>`%EXL?NoDzLDkGcyqoKBddz~R=+9kE{Kb!K$#KR*)1L@G7SZCUp zJGa`aW>9-PB6`c>+WmysNj77EDY;bvY#96?+hE3>9cjxFW7^D(VB23hL0jq#NjSQr zG@bqQ8F;`6P_>u>$5y7Qf~onJ5Zd_}kAbojb1hEK_3H)A_6Cm(e2N#X10ssP=SNSs zu_=9!%sy+b3R5!JR?;-IzB&Ji<%t;YSBU@mh#D;E=T?X;;jO}c@1g#sPUY`{nvP)w zuk@H@2?Rot&O5s?AQ4ku{^;vHA$sS9 ziYSFEF(SVQg&I$kWx=okI>Gh`ZTW$}-egUXMDUiR^7V5E=yzy(*bxvHu0NBL>eKfU z(&Kt{eP4sTJC`bo;i`6PY2xCyjBr^QjsRUo6eByEC*gfE;`7~+*(Wc=_jZE9VPE;c zOzCq5@2e{WKz0GRxvm5>>?`jMx9hVZfR>N_4~_Q!?{fl#5yf^*o?mrY{_Za2p^DF= ztcGWvaxu(C(7);J#g2)x$t@V`RSx+~%(6ujl=q=}mf;*;lq}8AV4ZoxmZnpYWZLqy zGk}?p4oJt{gtWrS76BXxRN!}!+_ZBwO8{hL$l3oYCBNGfA-n96(^Sz@kM{qg@{ z?>wWLOxLzO?;dqTQ3n+yfT%PDLMPM&wlata$PlE2P=o-HUIIvH?okA#1ZfF1s5GfD zgwVl}5=wwb3xq0!5a}flr0D*h+3)&(f8T!p?)Mjqx>gozJy-7gI?v;HOBt9LO)K^l zU387b_iJ>F2vV3Vf5%Q+Mb9?I{ZS2`l&%JVOYEN0xQFpHN@}yc5S%c}NjM-hN_uR_ zD+e7?u*&M6@_SxN*NfSZFlTR}40BA^BZD?#A|<=ED;}7>(w8Wy=my*AbfLuJr!@#* z^})Wafs>I*2gXWNFP{6e~VWqvhBk4-Cs!7xs}b#$0baYSCZOk9N|Sc=C`v!_bd# z8gKf>6qiJM3b7qoJ5x?_lopD0v`Tj*&hD#I5N|+v&bTd_VcFrQvzz{Y*b>tIJDya^ zh?&?M2|`>C%9^>e_oTe7B%?@mgTjAPIeMxc7XkGQ8Ww*8SONTZi|_aaLT&%+T0S z4T3tppO1U)Rb7LxC`l=By6m*k*q+qv6rg+*QxX4F4_!&#<{3INj~SY45hUW={M1*+ zDC*jCFU?6mN*UP08&AQ`bycu{|4iCp_vr-j&)!xA42XuAVB`D~=p;R9CW_@@<+{R% zPR%`qNV!h17Afa3D_Qia;e3M%eXD-IJ&_1Z*7Fz&y9tUfE-<;^#uF)>;YIEqN)8saU9-qu0k$WFV4urjH>971 z4}%bN5ob$JNlReu?x8}lt;J?Ik7khIFV&eYGW2U2sJJy@oJJ)sDhltb+NYYDGoMlD zWEmiEr!%ng^}wPDE=D*gSAlG;P}Y)GHQ=Z?RUvvn{0(&P<$N>83b%36Qi+yrx?%Gi zEZf0wS3m_zRci?LheT0P zZXCL@r4rA#A&ZiX5{M}#<)XyAbHTx#KeVPpTZi2*{%W;+0<=CnRqKsrDp z$9ke(B#d44$zY39`D*89 zM=f5*PDgf6ijr5Nub_R>5`yE|`fbe;RzO zEKD4-V@q*nwc8g=RGU?|k7^lbI6uq?quzx(N#{8Z9N9c1aK}s%xWotf9ia-mF?}|NO6_J+K@(79-uDN(1p(tHh)HKy{PL1ii z=*c+wz8{SB2pw|CRPH5Bj5ZzZc_-P*@Af0_wVHorjnNlHP_M2xO06mZ?}j6{uY4e8 zdnUh6hBn^!AsIzc?{v9&wfn^|v;9?I6g|GY$ng&R#Y!N7O}wNiT%>GF3|hJv9^T`c z)F9cl`Fx`Ey^q7rLmk$2^5s_^L`Xah)TmJlr8{NTe;MYGc6a97zoj=r+0Y1v25 z6)_0LheK#$I28Vd6T+ zvw1-@xFQ5hcfQ&hg7bZUWuwCwgCh~tJ{1;V4{sWEuc>`3cwz`!X=Z1SI**hFbVvJe zqc)Lmrg*`_yqS;Y@XcN3LC9qAm=+q@&xhhd&B_a#2D;Xkbj(0;lAdNn9VQ|&JB7Fy zX_$^cf{GLYd6K}fG&#O5V!s_K*YjLAR%h_Wv)L0ESG6^*8+ZwHC(T?n=}nJua?7f< z$Y5=430<^%dgpxEKRPZGeY$p!A3#YB+xgtHhcAl}?)Joe^7&Zoppn?3?F4 zvpE<_4kKC6omC^mE=b(FB{#2bV1G5AsIr z$fsa>gJ=pDyU8^h^{jFzM$Iivn4UP=o}F0Uz^s#LH&u*sR&{5b1V8(JzsT&6Z{RKQ zkx`*-r6^Kv+`8l2h+Z{xvWak3d7kat`!%I_==YtPcy)NeEtGi1y0UN0`{+Tb>omFCNI#GGf);$1*U+EwmNg0#IQria#@@Rpj^ItHBZ~wAjbaoK{Rf{7i zJgar#z;h@ocih&Z6T*jy~jN84vfJ%y<$(YzpW*dKe344#P2!e9JkQ% ziyk*(gdgO|6uOS!*l)-EN~4PRbx}Bbrf+di!RPjl&gcC#&;q)!Abi1fBPNx*;SO0! zDqSR;*$>R??(vi4N-QBx8T|5w2X%)%*Xtz;(83*DU<^@}@#t7nF9qia-U#x2+JV50!Nv`o1YWVHEGU!CL< zYr2!B*NH~%o^`A}7yaJfEVb`be>oHy?!ulgSEk`DKE`{R=nTM0KAcF{p;FxT1?r$natnv=>ez}t zlz{<)7!xVVh%ZVtUEzv6j;q~X@rVl+<;E6=%nG|l*i-Hz{ETCRoW<=|jLuD#*7;1R zRz&>mS~MU3)$!%6&BrK*b1AhJ0M;hVxHnAV&b%GggamUQUUS^9Bv((|)Q*7plUq7c zSFRupI&|H?l@5J7|BZJGvxi+|2-r7I?tHwz6JcP{%o?3-%jOPNZY}SgdwQuQ(Q1{? zNABmo3)pKSmM@LrjEV-|F_*tDCw#PH8#E^m4DRLph&*`FHokZ?5N%Kx{gu6UPs_pb zW}!`Q^v_G*5B&7Z#2-XU6_dGar_Tco8qcrJLf%d@+W@K9ibB|RDBeZ7-63*~rVFb< zsHOD;nYk!JHg7oP{ z;5w$oQGHO|E?w5#m-eCC=QL@h1wk(QXmQ1+pVM_3cX^%^0qj_f<7xyF8<$ zm`(BZQhn;y(UaEk{+PJgdCS?fX^e+(2_IL&zoo&clVr*@GAt|Cph?8Oq6jVpP7tSBPkXfMY%M2Md;9y4d=>(< z%VBc+$mGBlhEES>+E&qQWso*@_Q%H)<9p-VVb+#6bH^nq@0fJP@YjP4R&(AFlvVC~ zqmR!(KG8vnTl$5#IRlw&ZHA)6(2XAQhE61QT( z&t;mr;^osPie8{;0TyA{@ayzW`dKR#;&FNdiq9vH|Nn7xX-IByRqXn<8X(Ao#bm;$l_Wi} z3a4(GCMRHRn^E}AXWmEVtJY+G@BKX9c>QBDr7HI{*15htv#_Yx*1PwTRzA76qFgjx zssha0ri%R;9_^zoT@8w`Plmd(VoR0p%q~&+}xtKI4-twU7qW>#6Zk9 zRVDMR{q<~pWvZN=Dx1or(d$TeL@(%E*;S0KOJGg0U;f_|4SJPK3;SHa*c;X?$WmWh zV0M@DkhsUPi@1(R?NpOY#CnnzVo+S2d!LCP*j0aoC8Uc+^golQsK%M6xRF6?&ZElc z2UbXwGg8fZoVIc6xI(Aqm6vIzMSKnhlLC}SmrV4;Wzz&uAn$&Qb!@pe5AdRRD)8lx zr2v0w_M^zf^sGp4kXxKD{Itr$avmxxQE!pxDvu98R~@=E_-;XeF#NNro(z|7laO<@ z#3=~UCsH};t(O&qjlndgU^)9+;{#uBEY)yZOHWefn@x8LQFDFM)RMeNm6Br@pT*5x z3){kK_V5Lj-wAfgb7`}IgW{32+@RtqA923|waMW1?wy$jloBm!*o-#J$KVf$xp#{0 zaIk}40Jq0v@JV3w*^!Q-w@Q{bORf9;S9iNsL&Lr?3Apu;d|mz&%!3H?Gwa`#3)6@z zq}ktVrQOSsI3{`EP!egZq(;Fax)f9n2lN3)`iVq#K~c=nt8|H;{Z1Vr;&B4j5VW{ zK%@t%V$bEQJ`s9QGAwO}r^$P>pPng;JZo$UjjfQh2eXXB2IRy~yJcusFl*ZAmhPH* z-*IGK<&zHq_?jP~m?u|D2daWr`76f-R{4jMOWt31AMWSuhkhpP#q=iIR}`pZCN!ly zNnWZyt$=F|IOSix+pL6s!&>!5hc-;FM?xpxM?e2q04-!LGp#CWIc|M|%3#-P>4>9P zN+5Sbgdgeji}BLlFi#t{bL{eD5Hl{lVeb_)6U`mkfj75|@LDB;=H|zBPzjEYHz2k* ziT7Ei&VvxSFyWd%JbPjMjC$|ezY}z7lBDVS();qv6W@wV6(q5Qw4A8C1IKya?jAWBk!?;Mwekr&0h}MV( zL2t2xPsc^UBum~GKZ8fL4BEHw6&M0)FBv}l!MkO7Tb(+6FDs2!dh?%N?AJN8uMjz@ zh&^x*ns-Ih8M28gQTH#po+Pk~|K_`xFdK>kgtCI$CF&?i^(~YHBU1-enuDuJ_uw2W?uIE2^d&MjHSN*+M|@W7EK-d=Z8xbA zp1Pu*VttNT?<%?)_KTyxHrZsN)0f5ha(-47eCl51b8ZaNOLC}=R#j0v}` zcOF7nJW;=f-YP|g-tBcK>LTBJ(Pcs(jed;J?#ukI4~R0!(MDlJAN~tdI%|BxYZ8{A zP{JNi?y2%lep}Ecyqnu#J=cqi&}C&zDI%Cc{_vr%6C36`h>(!OfCnhp zvCv(mxV*6$rWr=7L-JL~gfC>o?xuyDsy%5$jg3Bce;fU3-6!WA(#8`%7+MM~i;!g= zke@-csw}KsWxB-0kfzBg{+Zu9{>XzZ z1@l};RZ8|$(+qbhXC5fm*vnvr*S^TmohuL~QM?HZEM3P(=91|NqP#|XfND>w^QSyG z4J}9%0(4u8)TNtoQPS7k{v<+Wr94ud6(NN*1EzE2viB^-L&-5ZcFdz?oc@4dBv3LE z2Bk6-$E`@ECeECf+|VN>U$hw(AKHIR+RXbA&=ooZnfy|1H5*BBz%~(QA1M1LnnFG- z?;?VhTZ-RrY=>^GliaqZkP+vzr{VZTyk{;VcRibGVs?swVHB^Awrqcn>Uhda%s%Kd z^xb42@AI2e9?;X*4yIS_-cEXHcEvoHN_W}nYDFVD( zz1|_kN^7!Z4jU)VhSLRS-ApA7B)juGRq2bjZJBvHm3D5`EBOf%-gRnYunbVT9YrZ; zEliJvO1$;%UU1+0PZ8CAkNxJAUdf|8%6aa$lMBxL?x#R2v4~Hsejao(W1;zlQCvrU zjlQB*NK1DRC(q(MA$in!cyC;Nza={6szHHEc71or1bqoK(or?^Sd9XT_?j7U|d2Dq%zs zE^vc2kb>dG@Cfg0{b3<3cAviuMr$7b^CugIN5N-EZpvLPO-)QdfeCf7h*sCT!nFxS zDvASyZNH3$iM}=KkD0BO(W#={K>GHs`9jGRa`_MLiUtGnynoyLdgGZDWn;LO4p%6{ z`Zu#R5tAA_Sj0t>8&}d!e+LA?0AVOKh^M074!WTl5B6cf`m3(?NP(iVo;}5sHYzgY zx^A?G3#zB8WFKWffw9nYti*MAoF>rXKs1#^bZExaovV^MBMu;s+9MMCe)Q2na0&O$ z(&y?(&8lo8H>rYjLG>NA%`D@_#}SV4(>D0Cl1Hy;_T#r;427|!-HMxOmjlTa%qK!) zRHb`L3szGr{s`IZAjd5Nc+%eC*&F8i?!KDwdWEtx&DVz(gSM*KN@1L`G8HCTo_Eh; zLmRMApA%X^6hYn&AhtN|M*XjAB%dpV5|}@E0FGwI{5rr}m;DoP6awEB3<4G^|C%e3 z=!N;evRYX{yjK$a>Nyk;6$5E5p@V|Nzj9kZYO4?sY`6XUkJ1^cOBn`;oBK%9a42{mllE<+M~Y{yT}=tQ{Up-yU#%btUgY41Niv${0N7z z-TGpSS&EMg|7|@aHno}`ZvhS@QVUyZY=kw&k zISIWI4XNGhK46=4uU}D`aiDJxK|`h==qIX#Clu`rF0z6_L3rqmhSZH(B!oEzAa2~J zRLZUCaEtlIo%gKd9~t_}F2mFne?Cl6)JGBS-U zsP6+5XU1|tZg~5e9qFDb*RlY;;kyDaB-rBw&Q2=SVG8dO4?7h)-0?mSUSCk* z;Bc-*&HCNgnk`jlnZ+`?%xc40dKjHZhq{;lF#ShWgj7{Sv=Ww3L_B&WEf5?0^5P@Q zOi;>=W(ljo0q^~}=t>3WmsNPgZ!y@^ImbuxKwR?Sf41=|F_^ zR;2Ty@l(uBZ&c!BT3#k4u+U$Ww4TRl*F{}4_O3w7xf>u}cWcQwR293WnH-`5{lqJ{{)E{B{TeZObj@R_DetpU8;6~K!ku9;a@Bc~h^%}`#I>3T#xJlluN zgR~NLFWL~RQhzTE&$%aqSJZZurrD$dfF@{sR`(5SMD3Vd-z`71Gbaw6exQIAe(x=q zO&9S^m&jI%qWmLwE-=L^q7hiT<3!vPIxv5$T(i4XnJ{cR4@9c04z#pfQ$@S9V+Eur zDnXrq`c_!!5bK)cu_S=$?k6yyJ2F*TR+)%}@)1-Lyo}0BpR5XbF0gXBCQV~{J7Zt3 z1Dr~{`73EABD<6hx)L&2>Zc;y76XPVad|oe~q4&CyEyzyQ6aN0(^} z`iUOoVH>(oXU12xe`Vd>)#l0aB$kp|>=%e9s#I#2zan;S@EucNri#DDDTc1z%$AJU z5kdh9lGvQ}!N9mq=y14!z_vwquIS>9FLD`r1svIHP zF7e=L*2=!#AW2>|sSa3~Bl8=tDY0=uo{PKS+lCKf(1KGOZL|K6qrnlI+V`tC+F6=4 zO3UAXyN>dd-;?Qv-g^}k)X}@jD(bpkFlVmeiQO<_$O6sK1=R{nR|j}rl#_3Cd2d92 zuaeH5HdpIAs~)f77H1dAN69s<7;)=XuPIC=1Rv$wO{{MIb~#MQ+i7c{0^BfmHtP}o zjb~Zlwk&Q=wrp;m_SpB(nS_zle#>xmX22?H@by%gnt}BgnnUe=#7d62E1nJeucl|z zM419H^{Hangh72#nhv`KvZtk&<5(Om7Wv^JUMfe6JX#7ScPnL?NiHsZpBbqFT+oC4 zLDTlMGCz+Gt`Cmk%tTjN%%KzKs%H|ul2)>I*{3oW8L4ee$l7HPInD!Z9WEj5;m2h) zxbSWPwHl(-=RSv>Ubm#JMwiLPD|Pp`C%e4Z{!PYcD_>G%@435knRBn#cZYJ9_YUaL z3t*x|`RLB5adH^lQoI>jJ?}M6Ew5qb2D~TB(zlDtK8UT4d7okvXRP!Dr5Ji60SA8T zxvkMvCn&z`_Tol{U)mbtUJW**4&@vr=w*>@U~mt;*(g~POHTaTplGG+uD4Ra(a(nuJXNHARCw9P_bL!}o4?AO}kx4JZ|-csyipV@$SaQ^yiy>;xOmR>%)VQy$DX!R2Y$%x=_VhEOg z$zDDO9|nee>ztRSQCNRVWwF~XhBr3rE(vS{=$YxHS+u7C=M~=h9@e)1u01+6wm(U= z;Ag?so{niUtiuqvH+Q|+*wNo&&t_cecSo>Q1h11p)^opQeN&aXnE+mnvv~`-XBR{w zxhWnH$j^OnIcw|=#hCFdti#H2xX;Ph% zZdsqG)m8~V&Q_4>${(hb59O@rw-As)XM&OiW^6PzKf@+Xncf^Z7`T}`cdEkxg(1%e z7KE1?riRRCr1sTSuS6I))gtc)Wuaw+cWo4Z#0=POx-g;$zwFJ}LIwlskH^iI55M2- zt_!u_)O?bO3<+9hpx10Ps(x$|R~q=u-qdA}1h2~C>gEAf47idmGcj9Jv#b7T12E>j zOwgAsl9#?-p$Q+O*xUCh-i3$Dbj;n0?l)P>GD+HUxlXpPrlF=kWO>8|$>{>YJ73v2 zMahe>otAE}v;aG;82U1hAl7Xtr)c6PrJ}w=bM7%pPU2Jum$$39tLA*H>5a1oO^o}x zQN2*mgzzLXCCRI<$s*g6n&U2nc)8;OlOn~Gd1tN(*s~PCZa%^IMnZOvHTg>1J%K+d_R+BKAYyyLA0iZRWfb&#JKNWwr0gGVOgs_I;3p0h!2*tN|>} zUep!p9;0T`_6>V^I8~_|d=bkrLqY{X0QegK*czPD4HxBtmUP37@wzNY-{NgTYMkz0 z>;1p(&HidBz5>K(e|>6nDn{twuS>O8ICWQhc8;oPLN_p&T6cj~3K3w51Lq)smM_}6 zh5c`2_`mto{m=bz5LPbQm8=pqg89*ZFV6cCHo71dUNY(_?b?~efr>3|@I!BimOlN@^Ea+$=2u0}Sbt~{V zutpO(0Ycj%s-O`_WPRz@v7Y4JY3zF)r)b3lsVC@~$!4w_X#&KLFxUI{y~y~Mp;K4h zR{5qbWQhpMQk{GdgJ$Z_Va9my?;}3q%2P|)RDDT`71+R)uJ09G1I&Zf(+Foa7ls4fAch`u*W3zhc28kC1?m zE>YzpwH#U!9XMX_<6t=93|^HPy=~um?j_wuW6+6NTv;JXmoJ*Kl*M~~%6Bes5%g;r zn^v)WVCtTrSdxew362;XAF!dIlf8ebmv@|QcE{Fc9SF9n(TuFjcAK9Wrys zjHqks4B$$~l5L&`K65KR*Y=^T_N{MUb+;pCCP2PMD}$ZaH>A`QzIDhLf=u-0!p8?l z0fy$KMIVzpMCwgjo(OX&jEKJ02eFArj7#MNaJhDIs3L6Gaw+1~6A@Lext@|--v<(< z;?hj+!`efCmCBmwrYZPj0`-|UJUml*B#Xa6wp+w~!UqmwH!fyzmfUSNjBVWcy%qd@ zawR!W7)^7JL4zbge|NxVkloI<(bwR2j4S87O%)lj#=_;RNaC8ps-fP>d(*N8fJjA; zh$^HpuFp;wgpTdh=Y2|zbrl!RO#V0Xs}*D z(Qpp2gts8LAI0C9Ej7fHkJ8Jk;PQ z8>dKYS*r3n=3sxOiW2%^qQH)~VbL|I&Bk2$-7RjVEp7aTD5+a`i~MYS(EQagZXn|W z>yr}H_gz(G6f$P~V_8*2bKzxKioE4n9r}qU(EGs?T|OMSQxI_bZEj|WVn@eC~{(4};}WabZpr(>Ity#2o5r>4x1!Fnvi zJ9IE9GRLXmAs)PUbIPkuqSGF-$i~YYn54=%dzwfzB*|_iBoD~@?*`v5=zl3q)L^0d zX}Sw9-O-XL?M$S=F~I+~71U@4%`dN!If!xmrD<7FvloAG-&fhLE9_Wked!Qb?T|Uk zDYrYlo!fB+sh66B_|u4!Q`|R{MDBBws6f6_K*r`ch+h<3sP^@uC*2lq+FShNg_@)r6%75y6fbLjGzuN3L zOToPIqo=%`?mIk;d@@%(5eiXd$a@Sb+P}73QKhRbK0Cys+uUK#2Gx)(-%4H6%*<#? ztX;REUz~iQxdvVq*A}tZH-;VN+}i?MQ{XU50p&KJXcBsHXUYuh!)J~1)k*nZtL&#r zWi8)O(Ga7zWi2a*PMU(ddX*##J4c#51iv*}A^B&pPC#h1uIXODGd`RTM-d8!Y}Z7` zzEKkFuMcu^zE^880BHr?wt7i(r|LaHbYXf-0WU8*K`BS#^%VuJ7c#w(J_a67K&GVx zJ1x{1b)!mFqr}0ewecToWBVu6yDrz)l)Z}mR)IPt_Ki2=KLW2Z)0Zhkl?;m|XJ~~& zCq^4fFgCUB{1Z8yYV^w!=BcZjpB!ql%_K6Tk*R&jw)O`|C0v2GrSnEDx9QB0l!EVo zUzPKh&zo$LXKo}d`o!D9LL}m=&nGRv=vGDSScS6Bp+t&H z3@UDO7~YF$e9VQ4U`FVfIevXD70$B+dMF7|J+`L6dXc?uR`EQtn&7gS+5dGKQ8l_Z z*?fKR&@z9Q@%eBtAeQTE0(QdAON8y`I$iyW4Jqz6C@mXs_{`$A5}}OGXCs8=11Eg6 z;t~ln?C}D-xc1WX%ym1WlHWz|+l!4EGCL>*ei_s5LmnwENS34_(6ZZcHaxRtUo#$j z3>%Q@c#+i~wTtDFvp)z%ZS7x+QtJZAsR$mYOqzWu5R7{HHtmuCEQ`kqmZD9|=|rYp zjqI>n>XRbc!Q_GQB9FSB&>7s7NBj*w&+&cJahcH5;`pKCZ;!XyH9D@@iGqJiRjaO( zezw)^P9e7cz&2(3eYW4uJ>AMVNpw|ek55~uQ)G>HH_g1bgeYJ3R*^;q+}Hi*PXT){ zPSv;)yTH@F!+g)sDJL>K;oEHV%q6 zAN=qWkfs+#?xti+?H|MxSjVj%@8ff!FaxCF?ZthfB^nXn)XW*NtDdwQ#gEPkaW`(( zuGvXh%ok+!*zjp0HfDkm>0K3fz0vhmC}B5>V>c*@Bul$Duo?dkI!)Hkf5g_e^7Eu; z$GJRj@XbFvD|)_Xv}l znYM$!PmJ@1z4z5_6i56G55J2Iu1Z}Byt1}js`aZ?KPAID6{y5ogSMeGUuh_&TU&Hn zn$=RiO&5;bh~dQ9uX;4vi9(bQ@NS@1x&#rK2k%4^(u|*i#vDCgspnMttOdJ@9&E<{ z5rvkw)we>p7zTk3HUfy~d7SPy;|;_^^OmIOh`Wq-P2xjSXcCkm1$FOC0YyonlTl0F zz_0?o)Nw&(D@EYLOBNtJ@9OAPRoyYSmT>P1M9)7r6ome3Hem}A6X$**CLzMZ|D9*@ zf9%hqlL)$@j{8HQ8kx#o_!GZ#&v`iSfz(KtTP8m3v(Ay$#%>ZTS3`3|E#Qu76@ma@ zliOjfC`$L7d&6y#OrQRacrn>*Wk>-!!?8PTgA(16(e62SRh&H~x?2U14FJgT67Y8A zLZcvRF?;zk@d&Sr<#?RTax=vAJkysHUPvppjh_bMkLF33KBFG@WLZ*espF4mEiPhV zw{@xRflpA=vl_A;8nTFC+DpxN!Exc^%%~j~K@JbHBd`V8G0P-+pjjc|em*Ql&`0Zo z_@{>h_{{tr(EU)mY|H`L&oNNaVoXTyZcjG!txGMsBShRH$jMwEuLmTT6Y6w0;gf zlLv$I!uU_j51RR4rsazYX<|!`nSb$!7@lh7-%^7(w}!@xF8A8q^79fqF)Dqv)G#_s zQ)}#nzIU3%y%nn7kq~)={}8nlU9VU_osKT1Yg=SGeAi`_){ToqJm%*$S=DFM9Nd|M zc__2FGcN!#4o0t{vcS?kbLZK>3#7yIC#AE+pM7gp17OSo>v6Ort~=mPU!CIWxk08F zwmFA7Uq!rR^6NTecVJ9$vYju?KWQ$LP-kHMk7-0l`)A5TNhhx}ZFFbW#!evmmOWl7 zN+Tn#xUYz+ykwUy*w3xfgg0L<`erKWkAwYatHZ8n30<|Qvf-kpy{jMlJ?>IwX`RSk z(Q?TCofZDrYgZ_fJ2^5ihSe%+eBb))Yi;Y@9Io+TXu}gu`OXFjQSVYdV)D89W9}|7 z=6GB;uKpuzqoZ;hovwstb%=Q z6$jR8Ivf_Y9G-Onua9hl<25}N>^>a0&vxZeTnTZKX7hr!Zqhsz-$a2A z_B{7T>9;Db7I>2=UtICdqOCp8S(KNs*N=KsJ&iN6Q} zE(M9GA1Le4=Uv1_i72AYsVl1TNA3-r%`%7977r#@34VOJMGpTuN3@jyrmztGMh%6z zmP|#Wm3yudbVB$o#o0lm^<`Pf^w(A7S|HV1K|kOLuK$|RA~tkQ=UQph)Fn%~qjQ6V zr8&DY##4QIWn#6)Sb!z~kr^OvV(hNDVB-B;e%W(24*%y*YWw30@t`lvizaEk!;iHk zBBPb-2i6acrWTuVKGl%?P1x|#MomJua*m(vKYubywmH~j>6*s;PUsDLYR)#OT4nRf zov!Im#oB@)I6`z6-`Uc3%GDVML7knPQkg0Y%QAiW2w!bw#|)64Bp8Aa!b-`(GU{e< zFt^6BVq&1e1^xH!jZnb`(~GDKG)W!Weoq!xGzOPU&s#2sdhXcoFfR2dh2No1kgjHV zc-VeUGdFLc51-lp;9!OL1NNtgRDZg}j>Fb~lSW*RNuDRg_0E`CY$ijM-jkx^0i#GP zIvgD~hUz%H2kh+$PR8G3zL1uN|K@vhEccZ2ud943B3p3rVSqKc?(=41pktgvszXoW z#D-Xk*4}*$%XuTvBXMu7#2^mGByT7Kh6mT|vo1*Er6|*t_>inIbMRs&nJ6|qK5H|p zfF*b6@#NL<@~}bsR)(TXNBhkE0$?gO z!YzOcwad0klQ$)5lWbFt#eRs3c;;Qd@FWX7#_bTnx6PPOtg-O{jVO7S{#K@Mq;{(u z_X&KKSCHxqWsOW^cSAij(67{#--S73AFat5pbS)qHKLyC^f~Wal?#@aKZ7DyXuicX zP?W~A``omVmn_ywOLL7kM4TJOw^&t`&QfZ@jJ9@ua5IwJM6rA<2 z;H-8KCKus8vgs!C>F-p^a~sJ*27Iu|QZTALRBg&}&A8vwBKEfC8t@;$bjDqfSeF_$D-Ax5b}5#rJ_Aqa{m5Y+A>1 z*Jc0t6DA~bUE3HfAL&J^qQ*R_7#LgDI}K47IJE$=_FGnJgn;gL=dJEhy^CL3M6>Z?KTCWD^3&M9@~4R#N#G#jM6lU|JYpMc4aW1fl8eV1R$y ztM(F>u3l{M!rJNFS9~pBS?qppzB^FP(ZDz)O9xaZgg-x9Yw1Q!CFPilWN5Lkzip_D z>OP;6To2=zAF|t+Ur9a%L9~F#^AV$%=()jSrbk>nNk4pfe?~j$a{Uzh!TA&q20#k_ z$P8}2mcJWi=K{^XO(>;%f<$N4Z%c7tmbRva8@jto>wA#}h5^}Y0joZxMEt7Q_$oi= z*xS!y+HUc{Pm0#G-qxM67dXBf@guEv?@5D4Im;v)U$W6Y_)I?t6|uf3W5$O-Xt?w3Vju?6=y&yo{wZhw;hw(MUKqZJFEgsUQtxm-930LxN+Ow4zU?^)emB z^sZyg+M}0z&0Afd5#jx}uOUA9(a!dj^VwQ4i(W0w7P1t2$XVzv zN{jWGR!pNKXm}RtzvMIU^$jY(h)H`lI69iYr zWghO?^(xDNEaO52ZCfag6Fw`tYNN~j7}p1id*Qv2+5y-cSYz|ak}q-ljL@6k(X*xAmI|Qa;gDn$%()U>MrBb zH4R`}e(V@U|LcRQ$HKP@&>pwl|2;zeKjzb^@Lmi7+5g=Tj)gBB#F8%qy&&1~nK!AZ zYYogFXD8RuPUH%d9#wFx-1iaT3NkJbWsO=2k{-I;=27+pQ!&HuN={a^eOP~fl(;LB zBDsjojFfwF%V~69CMWBYfoPj40k9dR(_*Yueg;S1jAJS~n8Ga826dhVz}_#jim-{| z%GMoTBEbxSpz-wz7E=71aMgSU+3Sg-?)Qz4k0Sju-;Y`2N=oRHn6!DjiyX<6JIc0s zH0nTTZj{s(t>DUZK%Uk+s`iVOk=K)@%Ob&lT9*d6cwBV?2#Ad3npJJ6{&*E69eIBE zkK}|Ogj?9ub2+(ZQjjslxey&reLUsY{u-OI52WAC1hsqv2FeEt_0}IYJ9k=5dhgWe zLU&0mBWuaDL?2uz#3wu_PixdSuB#%oa(sQ)1`jv0aZMP*zvJmfobz0Hk;zBQw^-c7 zi_sJ_&P-MB@J25g< zSMk~*>0{Nzjx@RplaGxi<^hCX<<_DC_0@OdwYyw|+XYMoHj0gDVYiIVz}}KN5G&a4 zd5z41=)#@;#?8W=ctl01k#^{{!5?qS&W~{NO)*~YyR{;b{;qDzwbFhjO8vpjWh%Di zbCBUokLPYEL+V*Lf)8r^1VWB zMPa%#4jc`(!?~UPU?%BBVmtI}+4vH>d?r4atsjJH@`SqF%w(Q}D{cj4(7meO`Gttj z-f@=Z{_`iR1D6=#AAj)jS`D6-jj(^bUE1~g5jyzzaObcawIw-T@L=lAqNYUTlf$RP zz3*xh^PC4T|F3nEkt#-RWz69z>+7u6fzQLV(YoOFi2BI0B4|x#7W>AcwQZUDBh$;| z-_qFY_7~Bc*;7cC`mYQ?-Tu&Y-L}utj)9?rrE@fTr2#^(Lk-XH%z?%76+EyoFu&m3 zsGs#_ULx^OMD?c}^O6TL}f)3xHqT0&x9NnK*omEWiftnvF>zHUwrwBO?2^;@|m_ncm!ONn~M z+0Um*wTP_QHsQwYgCYV*zP<;$krff6`FC-+MpX5Lk;m0*CimFVv)*WnxuFBenuSec z(j2DtK(XaiX%pycWR!KJjFT>a_uMOQbU_`K+d#js_<5*LBfM>#>fLNPbj9lmwzjv>1&bNe05Jw?Dd`I8%Dbx*`AW{_xPmIv z=fI-9MB&e>?_3i=KW|?c69CTPakZs>62!a8M{o#(->5_U7@TBZUPU{HB$9^Ea z7h0Cpc^j-X;L(hfHZA)~(IjYDu}&e+MK!JY+C!$Y*h*#(f-Y|xBLcfkJ)Xg7qKA=j z&Y97D;k>h=zkv?cP?z_3QN3Tg4dGCamo$(9D^4q_e9zxbss%6O3LOgv&wGkG@*Tdh za8l_4-a6>4>cLAo#XI(t9BDzf_TJya5!IbIajS=~Yt(3+Kk*&iUS}QLcw{zzt!*JP zi6}&&sqxgF58-s%T{EngliCiAxHUrTf>a^e5+#QsnJ_HLgec=M{EkMoA} zy*(7pI74(PZM80^hv^?0Z2&&J%GE8dy#7%s`7Q&2pu>G9R!x7=mj>IKR`g_ZU(D1I zJjmmD+M(ksgM)aQQuuC~U@Hl23hX`;7%I``(SpPS^OnxYR~~#7YerBOnOg`AuCDpJ zobJ)D?FA(w73PG>u|Aj3AwMusVZsYgaB7LC!Ob#ouub)!sZbm>Q%i^iNSr{&Q}k2M zeJV#MkfVNuPMqY_Hxtb1adF6@I5$#0aInQh7=MXI1!|Ns&ufNEH16;#ucWz*n*=f}#0wdGpJ?y=$nt@zdSfNOI5U!iv?@wYD+CvW9SM2P1P z7P@@oj^lX6oWq@U$hJQ=WNf0IfoNv>Ez@h}o4@&3IIN%5YQFjaJD%ycQaQ!A+AtzDXZSN543oY2n<`1Q(am&_f&F81ON8_&k8<@M&OIXdLBuMoL9Xx$RY3ps(e zFj%se-_@He##@Wca0Jtpq6Z?n26q4XlcA=I`f5lF$)G?V-wK8B%*IUzHVi7dv8|d~ z)0@a-jKO!4j>UieWIhO9wULVe{wWr=PP)5SmCln0mQu9A(5IGYBc*el`nTf7oLccU zjH{MGJI0wrEo1oX9mOnT`we6B?|hAINETz>72i^}cIlfN^@QP8O+OiWKo$ zv?WXLFx9^RXvg3!UT<(hnRF$WRGa*?&L^9_;qlbHy|4rPntpOmbRrV|*cdHV!TFky zMNdL)wx#}+FbfvyMqR8=Z;wnNigFh_z^(6H$(Mms#r*^NyrA_LGD_ik`kcUHhO<;$ zlt_#12lgZ)pw84??B)^d2A$ia#BAtv&f=aCQ3p z#Ua6s8%0fXt&qJ9d$C1f3jSqi9xTRC%KL$CyDJ{@$rb@MM!4y2yNPHp^DVh`^OS}T zZjrylMoAT#aPTcwJgG+zm*>`*<4JS^cv*eAeCTOuS+wivh*;Mvct-fv3D_w^Fir8WI|qD?N{<>85qqD@=E? zvaw5C$e21OKP%PXlbD8ghW#2h>KOuZ=laJy-8yrk31U}F`;8EVUE!zWl|~6AM2%fo zZ?m?qUI~q&C9^ex*lw?}t}Jn?2g0ab+QqK z&n4$&*>drX3k`ExIu~nvZgn^85>zc(f%)H^19oTUH2~|N!;|{S1_;TBzWNZ$1agPd z=u&{9ya#;w-*C7yhOlgFL!EOC2sf6Y6fhx_!^!^Dcg0yC00-@7?}avy9Z=BvX!BQr zCJo3i@cHkcN`TT3z1apNKyCc$qpFZr`OTKM3p;9R@mSj zox5TsE9X~X@*23t)wuipDb*&T`lrTnI(&x(-y!ar5|NPdRow| zu(L$3;u8v8`!=rFHWwLhCyVS%0)Iz(R}(0Oc|f$`z-}u?h%X%*yj0Tmdccae)kM^A zDCbfwOIJWNcb;l!%BJu^OEq>ohTOKQXeiQbO>d}yu?p+I3yT(3eyPdsUHYyC$W%mU%Zt=-B z<(oKBhj|r!3&XGh$2NtX3z+ub&ccRAvgej~Y-v#JXeGvR(k8W1pBkPevcod%E|Clp z$3rSXziQ5F-L5wQ-+B!I_y#mVl^xd7FP(Ux1x8Hahb2G){6UumCV&vKc%LCuNUnRl zKV{r13Lk@T&ban*<}>bO?~*S0Y^VMe`SWn7V$&9};T!T)tSnqhN9L^0?V}_BTQK$- zZcJSp%VwWvWI}wbQetKI6LiF5@!5PWov}ZPq$m1iOn!)3kaFXMHB6JGkV`t++16<7 z2#H>osZX3iLBtjow`{hb_{SWf-Uxxuo(lhXUN4nuUYsi^ZojctxsvWy8>AWj;0omN1)*Z|I;w?xN;vsEymG-pZ5w7Ozq6bRdsc613Y)!^6UZ=k&Xs@dQ}<;YeKsiM zhD^XOnQT_6p*;1btsY<^1UipGAxvZj)L$y2CM`5N7=lGhWE&^pHP zqoRMLd8CBI=gC%f{bmWys%T-mpynFAj3)Q?)8~>JGM8a*wVt|X1+=#yc4fb z!k+bGU5Hadji=_|%H#CBrP!Gtbva5HyOTU~;;}@zFPE8a-6g$_lD?ZF55Qnm?M?_0 z^%W6X8WAw3DU~nrJae6ny9{8NWxsT-Q$nYr4f>`07aw*vtLO3Mo=Z@+l+^y!EE$MO zD$wKCT;J(eI`b}l#^8B2$IV;Wa_WnY$j&af8xA}jfM_tX=T&?ou-ocRFqx+UQC@1` z-e2qiJJp}S^}3cnWWV}rv-K*zjR5xht2709I>iv`-zOhk$RyMLaVk1Z*0 z+_9&u(ov!9CmDl<{olQ}+6%RDJ!?B2J7ui+*3rWLOLGxpBjgC~@CvW7jJ(3!4=^0yZ9P+ZyU1{5-gm>1z894t~OT zGoe_EJTct}nO(%olqtR&i!#PUX$5(v%tkL_!rNfmVG3d+ID5Gs%1nh?6tx4xz>!c= zkY(Y{KZhm`Ph_oPLnkG}J$97KE7RBUQTLzSI6F;f+RY01T3KbK_B=Ax?vEbe%;q`Xz@$fQCK)~deB=yV30_zdJ+6c&-O}yS^v(UL8Skw6pr09Yd!#i7 zg9>k8ndtBOyJ%d$+K4+oOIJYauVip*wd zsVYV3-~sGv1*r5(qJ+-nyBaLiHS!vbO>D4zV2M0dqPUzua{EGEBmL|&c2o11aPHjK ziAeMt#yQ$dk?&Afbq(5_SuyywUaTe4j%F`Sk3TL5D#}_pOvNrMbOr39o!?H5R9V;0 zh<95Qwy$pR95U0QS|%l79*cz4wg{P#k{SH)=0NB{Q*g^h|F>3t5%uk|UYN6TeCaQO zUDz|zwYAY{T=NQe{K~IBR;okuvABnIg%~~1McCOh7w8SsT zo5EFUp>H(KPz&vJS)h$RS~pYTReT-TP1*PM&Sel%K|;aAtDh>;+2K_kmy3z-D(>?a(AT%e{Yv(ZP#UQXdd>1q;?peyM)%i7@RC_Bz+)>b2<#tu+=-^7Emb~_TcBB$nQ=%>4@-{wO4p{0i93p2ltSdlpsiAc(p-Ct()9bW1MOA7cB zeiB#J_YG~2ICY)#VK_PZSMar~`JT$~6~RRZ6}iyg^>BzW2bd@h)EHA(JBWmiIxe&t^_bL zad~dPBtYia>|YvFI^4enkaPRq&=HTs-L2GyYr8ccE{j_6I*nBmkdXK+Ht||0q-g?& zb%?`fwOt zs7M}Zb#&8fbbreb9yw$EcaqYv9esdMx)*jLQc$njk=6Pm<8Yf>Lp*jbr2s8QV|h(5 zH5#)Prcnt4e4TqkQhE7-H#ILtQxL5NNo%8XP1R^a!!_F=2Ogp&C<3H@r_!K(b;@wfqhnn+vt=|hMY>rcE#}tN9H?x z@v@(B3M+jq$c%HZneWcRvty35=2`&kPQk_7pAI9I^6^^-N1ZE;1KTN|&r3PVVm=i(Qp=~%tSV!bQX?W;uzB+*yv(pJW>+F9h#Z%&Xo{UYr ziQqpiX}O|GAR#slvEGCm0gdkNj&ny05+~M_&Bqq!?$&ku(3-pTxhvv|d+wfBKr^{x z$Ic|BwC(Fr?tUz?{n$!GjCs5Mp^Yepm)F15W@d?v_bfD_{ka96`#g$|_PobGiBy^r zr3e6`Yawx~P%ab1bI@6@U3Qh+Nhu)db;XpJR~4NY;`p6?wLWJ;<()79z@d^D z*Yd@u{YPu|07+Qq>}@6ft5YU714LGV%DEa}-^F7nbT#k%RFj{AR3FMn5z&r4*uk~y zbl_j{puD&51&5S~%;g~+Ju!+y7qZZu2G%6kK8eKmoGPvhqt`;C$0Eb78Rd;llNOQ$>N z&lA6*=f^)KJf8V7YFMp_GMq~Sx%R5mpV1n46b z=t=$Us4n?$Zp`n$`y}O?Vcc+eT=qGQqdbc=Cbnf1Ck}cme7KGZX!2pE;vc6>Wp0`0 zna`CH(IuP=TafPH1X>$yc#sl}LPh8fl?C1r+^lZpOs%zYZNl9f%Lt4lz01QBBK@%9 zJg2<&Q`?*X(p+9;NkQy^*{Qf>u2ND_x=ApvCkgUrhPCjP#h-46w7Iz8eI+R^>8oUE z6jCt{Gi^e6)S{|nlZ+>wJyzD?o(XSI?@G_J$ou$(*%(VBX!)dF@%x6_Bqj0E+jGwEer#!uI|=@>u6M7}$&< z0%|_)Ou?0l0@F?oCr~zOx>TqdLuZ&ewi2%6!!|dbqushp0v*9y>2um?XS_3M?k4Z3 zklS#WOg7O!!!r^6`-vRw0 z0J`{V3Xs}ZVa#X#f=vFcTPH*Ig@u%o3Hh{d%5@~*DfIE#>Ff*n%b5)8c5C5vfd89> z0W^Yus_{r9;1L7HmH-ap^S{YUFs1+br2miqw%0>t#^_&vbNRGso#UJ2O>;}9G})-q z{VT8Cudia(#b8d>iGOrKj~~4;5E8u=)lNgg=JS2C_X+y!ix6hC6KuRpE8U!3>S<)s zHWRTu*V{F#s*RNK9f#-Q=DWC&h+7aeTJ5rkmZw@+R^2-ZTGUm+OMyg*K>bf zU3yIkuIYnzM!Zt{^U_ni%fnP}HS!KZLUQd`fc4`EGy$Uvn?VD87ZI%QNg@o;&|K;AsyHdj~{+C*uihItt+Husr^EHvdFx&&xM z1h@Uep=lvXJR4YkQ#&`&32oTM-`&{A-*ZkyI279BF=e1G&{ADVV?}cG!S!@E@LSfm04~8$>*9q$)-cwR7v7FF}Q_ZFeb-B|F?BS8-2Y%w4srHCt z5ls@|O4h=#^O=WqNbPc?!03j#kgj=uoRf@m-!5K$?^0KLd0H&vRi8>b&xjb}hljE2 z^K3L4i1AI)zRhwp#MKy%OrBr3jg3cK-c zuk52mCRa?N&=v=ST?#FE(e>=TlTYaE#e3~{Jr~O@CsPal^*6^WLTq56 z?Id9mQPtjAS@&hT5`%W9Pek6vE&aOiZQ%huIo;ibJYrm#=0GIi3C65O+gC{;#~W>> zEl=`XGPmAnLxwS**B;wFOvI3Dm=EqX?EYAsR;ppE@?)+UuBsX9SmVFZ6<3mk?C>o8 z*WZ$6eRi8aZ3Ks!2J)O5dwFoLL35)f3@HpYaV=GTf-Wm2vClqe)r{IfXSEPaS-hB+ z>5*1KHxm4{m<4dIeY{`6aHf}-TopUe-2LJ&V%`x0OsBNt9%J| zyfQ7^#B&v2TN4cK5xDs`9S~X3d2NG1XWgFxfI;%PEr z8GZf-bV9KUXb-vW`MfDnW~=9%nQ^6f68xdR+@|1qeI|CImGCT5jCr?=I*Igp&u#Rq z+b}CAH9aXbwOu3SM5T9E(_SfHJG>L+qDk^U8nYFwUt|+n%^nun%k8033 z?Ht6K<&`sT=s8fG2J{sQ`WAzG?{x>!IZ<>jxL9my5mdW@yZI!Jkoua>v^iCYgqx?^ zh%#=!+JP%!`?7e_)ogcBMa@T+TuhQ5%lvXmB%G)FN2=j12=eoMY7yX6P9~|!`sx7F zc78?Ia)${xVGu8Ed+r-k=^L2o;iKP>HU~d*F+{3ZO7BZ5;EqPjrJRYpmC=^s_RxI>%NP3yZplEEuy`Dz(W}5}`BurY_V2R5g@jmiV zul>R}+%nS2R>8-dKW#Lj4E=0A1bpOdsoU06;5q^`uJ zuj$(%3$i_;kA6E63I-&b`-gzA#Dt+dS;WSlqk(?HU0 z^B(*1@}|)l)R}KJJxGK+>?pQ7l;gS4szujweb2w|Ue9)+zbX9fb^q8vUB2sO^JdBz zE9d@p)nu7r*y$WJhKW&dT|Cf_~vCZoj>EgNA_Sd^QQj8?vO*j2;8NNVvHRZ&}15w~7auj>5!slYY9;RRq% zGKwR;Nrlk*t7iC`pUVERiT~t6MOTj8-qBDIrT0D@@3Nx{+XHOZa5*+g&alH1rNP4_ z=;K{?-qi&EXzrtmGQ#}zl*R71DigOMP9<{a1(N&Ojj%d@tqG3F+=v2Ic+klQN@^FV2-G&aX2>hE@WBt4AJ@;H$-+729Xl#BHx($P3wRcX4 zpZwRK;F&5_SROAZ1Glv-JeUmym+t?rUwC&hN|9eiRt852|0F3-cgAKp#&Pt<(YFGD z@ah-YOKMPZXjP%c6*g#9h2|14uiOE&l!D9YB}82csMcWmoU}Evyl-m%jAqZ<*^vrc zn6!ENkM!Ho5^v)N)*AwCG+!HB|UC0y7 z<;`?>NY*;@aM{4f(^P0 zRoS$Mzwp3CK9_8Ivc+ZfG~%OJTkrbtdSlC!=1o5kq)eUT!Zdsk?=79-H2!N-i}w9Q zea%OUlcN3OE7lP{Ep;ua?^AH=BDz0)}7qylG;)RJkm+!ujclsb}G=9?Fer zWft@XL^+qoTLvRn2yTZR=W5>4<#0=Zj~5HPFJGf2U8lC$yx?W) zUC-z{KNa%jLz42Ej&w*jvv^#pNB>rxI)q?c205Q?;~Z$cfL!{Sl_l&iKS*&@px2^5 zx68Ro=)Qkk`1hm+Hc*+e7t_2&(PxgWb@;FcdBHu=Opiieve*h!rKdD4Oz@_z~Yowj7_d2jm;cTIoMHT?2R9Poka6{)Sk(qJZtAl3*WU zJ%E)h>Mw?(GC=G782f+RY?0Z!g?~*#fz%NiFctlGc?7HnXD{mhyIT5rfO%*Cz%BhR ztM`AZkZTS0l>ru$T|0(=G2o*~XDH9QzBGW5he6!$4+Rf7FNen54*TTNY#W(SyLCkg z{dTEwL!J>ZAIwgEDxtBPnKPVTN*sITOA&s)Bi!USuIejVL5J7osj_xV;cSRGYItx}(~2=U1+0_`??u2_@A!lKQH;1%XG&y#^kQ3J1@hMzZP#foaf%+&c?yMfE?pmNXQ>0t~WCH;xna zD&LR)kv`;@zEW=*If6$WFT_BNd2WM5<+qN8G(j+A7q75Q?|!sBwjZ5)Vpw}~5nn%w zYMCLCjzu)g8uhyr2ZtcGln%mkBj-d>0V{O}Rb{-`-z%1ZgT9C_&p|4+9a`rLf?JB1 zRC7_bQC?rh7|_rh2H6`LiysQPeR)1xBwNp*ZA{%Z*JuN${erBbncEC(?ycde?Z%+qZ33$P@ilK0jXDN&Mf_^kYhjVx zpR5bpB@GPo&Xc(C8%S{8Ih^wG74cH|!PZGnri(wt`VA$H9+6VYJ+{dmTFC{Q_pmI8HUx@?9 zC>>d6cco|k!2Q%)!%8uc`hb#tiVl?ocl7-FJ(gJ|cd1;r(+qU8sVJ`(-_DQ7s~OJi zDMxQ!2LqGZHwG1R4pD(+?rev-fYv#d^c#ln9Bq61{cR7qK`T+ zZ3R8Dq0dj0j<`eWi|j#<9WrfxUB6I7d!~EBc~_}nFYX;CRdZuYUg;jsZ)~08N^Zab4#pQ*u}o|rkO=y`)+?p%&$NshDMC|gzOQ#up}eM!%5*jLs` z(i^_i>z^Dta#h_MI2uqp=OtBAu60(YIzG?%4Iw|w^thgWzZ!3-5?PkSgyM9W1@M}=i?IDt zi{Fu8toJL^`!pY#0d9GOThPt9ab-9phaT?1a|?i{oBOqD)cUlWG!2HVtT&V}Un5o= zp9FEN;z=Hhs#ws2wYN1SQ5}zJcIZ-wFL%E-PaYf^Q{ImLXtgA*L=LJ_e)fb?6Dog9 z^n4Kz0sIs_eEhquHITALQtw2{eAaP%5&hJ25Zv0jty&My9DKOW=RzXJx0cZn5-DU~ zFq2uiT5sB@*SL)0*Hr3_Hm2V_=g6HUENeK^o53yXffc2t{5Qiwy*7N|gg^){s;$Tc z>~>;T?Z!NWw5bP(YLbLEyS5ya63A$|iWA*ydqQ1Pvt`$N0!PQ!_ScUO9j@-<``PtWs={l(adk9(o%``v8nY75d}!7NV^7Y8e@&@L3&0Q_(=-V zg@yVnDZCeZZMmS}&9fmw(HXXnSV^BRVt;qZHMTK#Iq$ z9XWN_DZ({)i?vFCEm5V?-sO)JhsUZSlSHY-WOTTD&swvg1zW2qOp;O5D4_(|^|N64 z66`dzYcY>Po_YrIor0?dJqaXvdj%V6dWhQAu5Ia`W%Dty0dr-wTe!fO)P?CA#KCO` zUX(C_R~79us^;aeiaS0PwR3otV`ws%RyHIsZS{as?78x`Q&C?EF#F*SCktZ3yfRn6 zn+y_z2ObyR;=X-)^1G3t&;gM!j305OsQzT9D%+DZe~fbem&U&MzO|bB zFGzy=z49=*DcPYsU)07;UBrtDh9S(4w)=Ngs!#d3GdEoi$dBq5&1+lCZ@h6?N%keK zi$`*2Qofgze-;}SOne_2uJo2e{8(J`sk(Y#7M)&STW6XVjqXZz9|_ztv}#{ysp@EJ z`;K{3@|6TiiF~{%LfnhVLIsC(s*m%FVPQur1A!IlvoXf}^KOD3bJ`3uK;BU?p0k5Dgi|@%(0hjslRKldap$=b_#YW;*mbaPsehJln zFTdpK$?`X!>3Vn*Pne5&w=F(5&IxZtvVRvw_9!8rXStj(c9C0Lc@FWlx=PD|Z~XoX zFq%lx;YkBS2!OF78589#NxNm-Bb{S^uK$dks{-<6XCD*PlB#QAi~4%w)YJ(`Q|?(-XJxf#S)L#0i=5lG`-tW+RgZFn)>s1P@ckK-`k4q5sDC-b zuf`z)T6;BKQgW{I2O*L3_rdv7g^*<}UI%+9D9rOXhu`!(14^PhAx~$4>YuiFS;cyW z?rclP^|pd0+J;K}=DnT#LHWF!9wkt3&D=W?`i_rU#y{%UlR!}%QVkiAY!A@441MzT z0?*zI89qLV;y;B#bEiQ=X#u|s@?DKgJXRUzuqqQ-(*nP*17kZ@1(+C$U$1=SJnE#< zV5I{_)hIUpiUB@drG~ z!1(1LUQFYrB=w4%36B(2F=q!RzhvLf#*>8r1gqo{*j~Dl%wulA;%Mr<5{-vvot0^8bwcqzb zHp7S4b~rm%&+K{?(o6XGQ_enR&HOM^rDx9pR!LPSahC70xt}W&puV`!)bbT|lRPte zy&3i)>HCDb?M87*D?Z4d9Q>>b!g6p5T0o2hUdKlDJ<`jzA zHs$|$qF>X$&OM4;R(?d>C!TCM1RHF9Z!KrmnqTGN_em_zuTx}U=C+s2V*gAupR&$+ zGiUnbHQFq;^#}fDL4@Vv=0H8NFR-p7WokqWc)BaMfY!GBo>S?la1}uR{uK|Vp@Xsn z<$hJ5o!O1LP-bWd^gK+h5ytI0i{V`@9IY;kTdyH0ZgBc5uc_86fV@Bb&upBk z`lbT_o>I{Jl3)V&Q2GWirzDZ0OQN7-8FS8XhJxin0|~xlozbP66O~0~{|*uAtjljL zrkYAsv@|M}-wr71n6RnLyW&Y5a~aBmE9o@j$U1*i+18mYW8EmrAL>@)HAcpc86LxI+2 z)ZwW4uBK*HU|SY2kA}9fuDTWvhH`|R3t>^>y^|D~eZn5v;Ht+w8EH6p!$9XZZB zaS=aKC8O`63GgIwhdZk$ItiY6^JEJOOiuyFJtC+b=9trx20nA0DJLiTy9NNGtKash z^}y0p3E=p@5oV^FU%57H7}_1&3^gRQBi%hEcf>CZA7fTud@Da9H6C93xC)Q)8|cA` z&-Ts8;TLXY&!s(2yJsccYf`JcGxj#N^bg{Ql+anEiH~a$xsOyb479OSZ-TNY`l=NB z%t%5<_t|-n+I705UU!Pg98q@R51whG3NQLAmJ-|IR8lQ=eCifZ*d1u=a9UND$$J5r zuWoqTDE+wJEl!h|bzAL-1tM($7YQaxgd~9bg}+LwN*8}Y#Qh3Hfdhlod}KO&j-PIG zzvnYVs?W+D;hlt);$;3q;=1m(or6xdcU+{@zWQ~s@o1kOAk=yH@{3p=oDzLP$r22I z-cSicq&>BJ_ag~N5>^Er>WKS+PSjd~mR+;i3B16GfGh9@ZYTp3=;%wLC&!`z_6~qC z>j`!PimbNE$JCzd{~dpG_K4+aWq=X$6J zh6`NER&p`EgaWRq&1O?yW_2@G$tlW#n5VE}sk5*zA0)s#@NpKH6;Pj}A_+q|EQdpoi&ZcG_zi|jOd?gIZ1fm=2AjGIgsfD$|p42nmB zwhUxO*$B<6t~@>W&5??Hr+yfo-%JWBQ_?E!bXwn?wcC&2dwq)4ejCNujh-pYJg9%d z-j8;Ezb8e{*- zZ!_TqDKb4&%^NWLTrb-EEF9bYZ6`hITaIA6j!`hZ9%wvkUo!$>Zl6J4f{uh{Wl-2z z^sHT6j0GR}ZoXV#DikWkL&iMP>!K{-N%oFCC)wG&w4ooP z8Nk)Ut3F?=jdAEXYq_PJ77=5z`LoFIYwHXvh{Al=TcEz$GU)3NQfuJq9oGgzMJY<7 zn=ZIya2#Aa3y3=SJE1Ms58e!|e}~Lk<|&@siSOJ0wg%}A{S@j)(l$EtDee)1Mq|SL z?=9#GOfXE-Ow|3=wOoa5liDv-S8R+b6`p6#!Mxu#3(kUq)nAN*e#X21;Qw1bkPv)% z2NaO#Ro`1Xu4Z^1w0alH-E8Q|K(n% z!}7e7&?B^E=k37Yx~X_Jnwhd_(z9=x*O&jVzxC@+HSIhx+z>K z&f52d70L;C#j9&%Pu8A6&=fnUN}8EwHf4ay9im#!%aIt70?KZ?Za); zjl~-4WnN-9aHM<8`ooqI*D7SI{7!OzwS{#xYUKEMD1=BFb634(|&mozj~f)_%H1J z82Nl^X87qhd$_@I?BNr|&~rG!coT>4ZpTi@XG@O3nd64R_&faa6Vf%x0DBjLzpvV% z#=^=LshB_b;G`VyzM){Igrq-{wj9 zDCi|UyUvpXU*{_HpcY?>e-_y#U19uwLQO^ibnFXPPcAzBazUD8c&$5W9bAb^9K3<3 zLI@*Y)}%d#zv4H}%X$N@M)sL5=ruW=9Hp_Y_W`>(=Sh4JjJ<$cXpou%S(AJ#LwaPr zwc~(P!n0sIh@@kr_rg=d*w_1juOA`XwTJ`qvFER*UHnTp1pEbDujex>sZ9#5r3^Sv zpIwSGNm2qH(azKV4meGmmExCl5nugz|3A2p{@=I$ch$2V{H=feEj#v~Gs-qg7|7#% zr^a{p9G3IX$AY>@IQ=O3J+>)9S2GN<;jR}t1WcaUbA8CgBp+af+oM}~OwJA!RsbZ$na8Gif$?mNEA5jO_thk5d5Q= z93yf1D7^XWzRxowrTvNsHG6**vg%iS3@5Z^{Y|?<4cu>FfYL_bI&f27WT_+c9eH67 z6FqBZX>+W$A8*A*to)9482obeB$Xtnh?c3J!CMAbFJUhQbYED{1 zNi)2x#fHY@04z0cGhfJ}YHp}a9Pmw-4fdlLMjG4ZNZ1P>=a#h=795nhU1xuLHLg4H1nuJ|+ow|{64GMlhRdHiX_=9TRFy&y7$ZQF0f;MA<{@=rs)t zKy*=e3m~spbUOHQn>_-;QsG_>L3ipSrYehs&O4?jT}`%*&y~l>TPZ}?_zYViT+hqz zNf6vtz5SS7m=p{cNkJIzJVd!|2tJ#a7^7^1Inv@y*9;|VY?U~5?X$eGO&*ngGEaqS zs5`)#K2>ImOqj84qY-9}T%m6UGQZ@i?@0Kn?fP9~O@&;KoAQ*9eqCAT8EV)v}7kqn3{+|lE($U7tzE>&Js z^O9e&)q;Asj5|ffr5Ch1rw4=$RF18Mh0Jzna^3M~eY|1ZMcn)CT5c?9hQcM6Eg3p< z=Z>Tf@=14h_gd%&xY@V@5Ir?Vkp}-UanzS~D?N_qcYbDs5kOJz1uC1I=hj6oK z)Bf@hiO^o4ycwUr=6)|sv>!fgczgBkh|1LbxtenP z`ytZlP@U#Ndm6z$w8`ggwY!FU`bW!Jo&*ZQo>U%Vl8vp?PNK7p)n$c1#xjOF>RhAU zuG5aVc`xXsD0$GNv+3{6dpWOhJ|@wf*Fa)p5JbuF3ULYu`{C&(1q11N66Tk;-MZi9 zo$amjCLU9%$rP-&c*5lGM)et~@imtTDnq#H^70c3^9u6~&OF{}4ak?cP_*)yJxeWF z$?87}LD5OS&`-K>0|4o55}_uf&c;F4(&2F?j{myX#O^v*Hvs;TeXTM_$yT*#qv`d4 z&4WqiH4y~-evW7ooI2nwv`W^}IZs-!hAkq>6|(8)a_(B?UuFh(H?+P6SUVgzK{*zY z9LXMIE3{l@dL3)^1wym3eS52ShJ5rPRSYCf1rp4!wPwRje9>ZYs{4qLQ7=IBixs`>kdfr#x{w9Wce|UsHJoq0`AUdT~j^Y=!hnFK_A1hi#7)}eJ*pH_w@BNzDmdw zy-jM($QT(P`DgAD!<^?ZT4*-lQlC6fLL1HP#cyR9#&$D8(i`8~@8~K44Bj461{0it~`xPl2XiS9i_%=E*JuPoCW} zHPp<)Q{Rk~uyOicx^U#XsXc8zP~J9w@tk0F`D3#~K6~)ut+9~lK%>TX=^mf@Xr}=`bV&l#HI&B$)7ceOqc`H0m_}k&|Mc1&}iLU})(4S;2m-njy z!dkb@hUtp?9L*S)`7}DMx!Xw_PVU4_t;eFbKr>Ei8k&KlwiNsJ-q;l`Q2Y^Z z2x0h_6;SB~GHxF1(OZkF7dX8S^xjsvC1pC++YOYDxLA_OX-m6EaI+5kIjwPyX z1cUm|=2c~==A_?l&Fp98ZfFh-ZMU)1aJCQd@~sx%;ZUb1)S;)3nlbmz7w0Yoy06p@ zbW!P)q47uSf+*xk?TDL~$?(jPs99t7J})Rskyu#g!Wgz+j|wkg+ymVA%q|q^Zial0 z8kAWZrARGkr2c>Gy=gex>HqgVTRYP!ZMCJemT4_5f}&ze_;(bwx6~3LN=iyBCAQc) z(<-&smXHu_?Mo18s3lBkV+lzSORQr{5K9SBl({q4aUa+5e{w&)e*fdR?nlmNr-XdZ zoS)C<{d#o=5h}We{CXaatx*6xz#0l2i-?fG)pqC4E+K6wEI_bTJY2zRO7oF$eC zZ@zSb81tE_kXlEV6}L}yhGbbUp-xh8@;p;G|y`sW>*ESmT$@y{1uZ4A&P$7awR^tXT2IJRy*SdQB z%fQkTrboqSfKCg8Z2o>YDrPwPr{f|pUdkq|TcD8@I`h=rxfK6`I~+XujlZS|btaOS zwRI*D9?Nqcbqs+n8#Z#gs}AS8AUr<0-QYl4)f49^OBvF;J@-)`Zc7IzbZ_E?pv|g%LDP-VipRBy#37dNK2LJF)N@8C-rSlH|MF{5-)*j zUc5qo>JzCSPKKWa5UZ#{7FeM(82@G>8m361hl*Q94b#N^@9B~q8Uq&7Kfw|;B!p#H zzI5^3UA*5TV1>WaCmgo_4X*jc7001QZBMCjn)TF8nuFA9N$-OAdyx1pV^peVmi%~Z zXFZ834Q1_8IQ+r)N;ODy*pyvU_~{c{p_V z*7z?Gfl(oZphNl!F{eZ_9Y6@^L`35pYk_ga~gS3H}`~O+Lt7Z zJm4(`BA=}-Sz~4@jyue)sh72Q`TQ?-zzQOyZikQ{0*w@^Nmp~3b8rxZv^VP~}Z zNuRv*x~3XjV(CAEB=fp7k~W(a;RT34x&U8lq+aj5N8d;zoYXr^%j`Cem$mp)=(j*~ zGxHZ-G9|aQy3R-EcxPYA%pfel&_yLMt2HNvn{^Cxv%kSYUwsUaU4oM5!_Ae1|2+D3)BiTKtSmEUvDZmA81s5|7rR+pT&CvD?wv z8_m3F)VtQQ>PkfqFrze~*4nlCp1-mMj7)jaHhY8Fd}G&@%HhkB0TIjzeYb{hs)|%1 z6y?ejVn`~h-zNl~*7Wi73yiJKnmF1c|XZO(J(NkrT_dGc{;Y} z{Kxx|SyPpu_g!m^LPt(&YoB8~rp>G?sEk!v-;rig&mVa9;n4TebP!*=XC|)UZXe^T`jxw*(EW2HLQDoZC(-*gnAj9uB06KDUBy(?=Uwn;!cO`4*{O~iu2^nLAS+)O{=8N2{XMsv1 zGmUg96+QmP&XFr(o}yXp#ZlzE@!oK~nL^)C!_zoaDl2G?N54#UD!=?Zg0VB~`q%)M z)IW?Xf<@aVa$Bb+V}l0v&||(ILk&__6N-G_t)wHpsUw_?=F01^YQ|Gk8p_aGkGfFK zUa&J54tuZwG}b#fGd}licHO^AUE@8YrVF-GTYl+x6!2lAp8+wfp`BdWS2>UnEI}Vn zMfz(IZJB*bRiE#AU~>6#DDnfu=ptU!r~JMKvaT9n)xer>dwOcO_UAgIrg>w_NeJJ# z)OnR(hv_y!v^_R3s&)%Ba-O+|y*(V8`1i{083ci+Nww=WR6S~btlLC!nH@OZs~!%z zqVBh6n$~#1mD?)dr|6`+utL3a7#3)I=mUg&X$w`HPo-mp{Wp%=+tzaBZM@3K1-)>w zMpmGXxU0xK2^goARPB~R5cc-rsk%)!Oo6H653((xLab~z3+S4b*+4n*ETOZ`xN>;p z^I~Y!x;O+GfVA-Gq~N>HHjLX1-_mh+3;y_t%56dP`q0s02e%bndD3mdiFvHOyc5+J$TMmEm)5UpvZkFgx z^r}P~olciMJh!B-bn2&IyQH)E5A!v9gp8Pqh3pH`B?3Dze+0hqgA_@1zlh>W{i;~` zBG0>9-}F|V;!BsDm4HfGiWC+1jD^Zuia%i>BL{vI!?Z}lp?S`u5i*x_8?o4|M(9BE z+64E#ANu=_?Q&;z{B#7V@=g25DzeI>4)vOQV2c_N7HubY(Y3KZa%WN72S#D94-yTT z=J=AZ8zx{p*dprL}c2-!*Za-AtB9Ie{~pHPf&9e_ch zih)**`$2(mM=5zcXV3a)fzT93l$1A4SsvQ@MwTBT{Sg+WoqY<^7JXSWK9B3tkbZO2 z|C*_x3td>my^66;Rg04E)|7zEd&h6(apOPQp>BHrNr-!78cz~tr0+Y>WeJBlX0q^{ zcF5l3HKwL*`k1-eAxQ9r<{#2vbB0MsKl^sjdoqKC;zk967s!c8X5jcopgo(e1?PQ2 z)2mx@ri=V!!%56&nhk^a8@sgnij2E`*)PhJ%M#YA^YYLHB_kgfwvh98K>{YZ|1Zvn z5GCyMF3ev1ZwJ!4(2}P@r=z1WR)tgR-UOq+UX3@qPHG?!juIg|nfIo6x|+KuH;y{uukCNso!P0D*CLIZeJ>)+O_8gZ`K z_aI|#%C415Hg=aK{HyjKRZ}v&G59yzog)eTVQI!8HjkWQYkX%0^uN_QHP;nRTNy&? zF@C2nHvXiAxb3sHzdpYav!{0T*&@(1cOW<+3wu2==3{9AN~`68S5yyr^>-!%n+rz`Hn<~G zOOw9Wbz0Nnci-YB9k<_+m+Va1cI?zX zk6VxB!HlxPfVKAVPH?|cLMI$-s3{s1i9beo_gn$o9D$T`1qL$(YeV-~v;MnB*Nrj< z2=pt&npQBrN}`1Jjz7VM|dr?5cmV6!=jRK#Uh3i0N1525ZBD!I;0a(|$k}I4kb%>~BY1>xrjtAXO{6qe$yg4``9Gt5pGvc z3F!vQJJADaL=W`Frt14K$oR&)L(sIuxn-5RK=Fa=(R{m7h>{~HfrqgN_^}vxZ?`8ZeHGCPX+Fu!MJ|{JE9}Ljqt9L1U+of-N{62J` z$KvVRk_r4ocm=mL*k3?;Q#$ndw@OOQ{mI7>AH~-k0Pg8worP89&E9`Mdie!AQOAMZ zPNsGN>_7v;#+tG$M3~L@l%5f;fu89uwR(q(K73Zjska_C>2%L(pIR4f$%R)hisBu^ z#k#@iV-U4RO_mex)bmG?AxdNDM=XALd$z?a2J3B7x$}fs-&L_46~qe{Zw}qpu`An#)fy0Wo1^s1JwzE_ zH>1~2&T@`^&E(&TT4-wJGiX5cIH{W$b;Wv2?d<1c>)g$)jtA>$HHW`6hfeUb9;7Aa zDpZly8X7>t(#267w5f`MXUou2qgBZ#SdYl^cB5?Gyo;3lzaR=6$iVQWRo*!>|Q&zZC zD_;V2?a%m zkP%ol%;a(M-4ZRFVOVZP$kk^89g#e{`%ZnT;6hG-|Iy2_St2Rb6AyCZV5G;ZxF^fv zKowMm0#hVd=|Ue7Nf+#kS+G>jsVyKk3s zTcvps672(6xXA>)RLoEgrhU8cB4;&can@qy1lB zL4;wTY+vF*2JGows(7kPeV<8R&hUF#SFh0L!tq z``q+H%9{O5*jyW_y1ItRM8LQ+SZG}B#MmPkrF47}{(3a%ERb+8k_NvAWt2%k1%&p- z+=kk~`pz=@pyfc!QzR2`d3gnb^n6R8XF9X3e)kgq>WCBb*(;4MrBO{am%&F-v?m_P zP0-U37*%aVi6(UP`~yLYLn+XEAW&9)hANtA_O9hoBE5kXw^?CD6fDwhq4k%i8f9^5 zmnFh-;XSGhB6;eAU+e zkB0s{8#VW{c5fRlKL}EYdDdh-@}{HMr`TKRW7pf_`lT^Z;*}xpX8WD!LZ6*%tFTq4H&UQB212j<17iYumbt|ND4{pZr-PkY!bkP^g?-(^i_E0kKJ7&< z9j919FtK-O(vH`NrxJ%E50c#j;vz0L84cfg936Klx@LTwPgU0ynXuXKTLWfajx9272ce@N z!F8llI-G`S)hIVYd_H9RS4wmBVz3d+%&LBHqiVWJua6;;qO(YR)bfpDDw-RZ!y1~} zQmkVCb&o_?YvMDCunn_UiVw!(4C}D58P6BSB;A~Z%y-9Ffv!pX{BhUvTs0lZC&ViA z9iuXyCY(8ZGyIiWWIjSKo*S$Ey65|bm-h4Uo@)&fE~>9xEoOIz9|YUFq}j^x>z@>Y z3&64%_rg`GblcA+%%GvTMDs?hLuVK$ygaCUlFx3*D2_R#vWP~Gs8C3>QOPhOiLtrA zz9PzqVo^;YyO|~xbBC72B|Nl)Z^8sTkN55ZVf)l+Bp=O4~vUh;cU9 zuf0r?3uppPh?eOPI#e*x&;VGqRegz_vq~>1OSEC-@rI7CM&8%x%`7prx~g0jS~gNh z3l|RVR(#u&+}4k=mdBo#uHT_cQ}43(@0ZM4praoG0MX_7_f zMz?!Z9TZ*V@p<8fU|__Ke!#@`?#Sl*Sat(Pb9^+hB7LQ8a%4{zpFy8SNeF5;FS*wb zb6tj(k&v|!HLGmNrOufuEBtaG;@)9_Il)RyG`28W6%Ev{)80uuEh{^QJ%1;Y;0aQY zfaIe$zQ?{P@Hwk>HQ{rDKS{GXCu@Jr$@iSbqvqs1Y9?Olvy zb1eD<^M4W7YrJbona&_mWrLmUZ3Uz>WH>d^;T*ImB}zPu#=&b@&baBh~E(69-izJ&Y@mwBlwP3p;D#Y>Hg7dL(X zqmUuYydaGdqkf4r&$CF%$WM)-k7S5^w9wR~*@=L6gfu9zeI->P0P1^gz6k6;888BW ziRS#zkab3@UqGHePTn`42+LpGo0?^ z3h1_LrGJfwXU_#e5|Fq!L&1wG{gK|7@fzS;fyDxLBK@)dl&7@4Z?sxKC2C~4gv3r9 zu#){)lke%>@@s@H5QnEmP7kl4*?}&V&13p)IBL@q<_QV~$H7FY#i61Dj6lHsU58JW z&{yzB!kCgfUUdo!RZB}SoC+mcj^gN+&V5pLE}c}ogh@t&3E%&&S1(goWna7)DxpgBP7D{Nqo93Ru z0=v_WL3b#_oyb+@yh(0bs)|e1tt-^nf{{YvIwCjabti=kVRy^Fr^?v3!tdPfo@F0^ zCW5NqJIAug(Wmvcco#?4eO78+Unmu2PmfgT@lP&5K6Q-|Cb_z;>Qi&gBtF(^^2nBJ zKpQ}-szC3C-O~5hvG)hnEw)bI2^)r1j6ZSLbVl-5z_M|z?C>_Qz6YjT{T0{G%aH7h zl?Hes3eR8zE}=e>=76=$JlFI@2m$&9;kq0!L$+%ei-NEMR<7eGveYMU5Oa|1634OI zAq?NaA)B~KW9JBmXH8~2dxZ1@+)*?bgK6EB5DVeMqL*Do4ktLZuiHYHPPrZ zRKLCge*f*TeQ<`cvD!j4({1aV_FsJ*v%gzpzMlb4Ah&VShp31ydBq+*>>*@X#=i|ShSMNW#B{d0QaJK~!Eq5*PH=$w=dqJ*&0o~qI}jbRhM zjWN5N4C106rs|u{XWs5ucJ}Pt2c)q(S5LjAoKpWcsZKu~U(I9QC(eE|j9iOyFY4nh z&t9dJZ+0=8Vv2VzsZYc*W@8t3P@qlUEuO&{xs?g(LgoCfa&$nGax-o7le!jDtuT0< zXggXJFgInE0;Tp5?S6u^&5SnE7>N$==8FeHn^fU%mi4o}Tb?a8>Ju9a<;iOl6ofjA zJ3}s5UWlLa)9?#5XnlwB6SQTxnskO2$d-TB*+XDGHm)Rva^T(pusD*_o>76dK22d24YX?fs!FavEc}$J<543%7GFCO6b3ZM zn8?8-4`jiT(U>3QVnC6(36V-h4<+Z4avt}VR$kR5^m@Cjjd_4A#OBm>()C}q(Up;1 z@9LCVCv!2sc*o=9RL$Rg8LK@`XF8wv3xCJFK57p%@Vo4eP`L-6#B^GY`uAONKs*VX zYBHgR?^Gn&Q196ZMmF@JvZ5$Fj99|1z_rM_-)qtjJ6!Am- zQskcgQ2InTul zt#n_6{Bc+M5n=9e%G83a)t^5%KY}m9x?j=HcPoYeI((Syd97IqcGp~bTn!L2!WkWN zo>mksBYRoFXvMneqwMFCbJJnp2r=L*ajnk|Bn;qQ#N2Y>&{)jm%@3BgW#uhZ)7@(5 zC&`u{M|CE>7D)~|4!yZ>Sew}BKCuYp4MvLPy7C=c#Vqja%B!-@Ip%&IXr3hMWv9M8 znW>fDo&S5fgna3=xQaEtfGg8yuLdddMYQDUH|YB2D;pA5F(8GW zKem}Md-^VObD~w*5I+D4tGD$>wurxzkrfP7bk;(P(a`HO5}KqD|H`$j{exeXyuj*a z@;Y34RFOYx|G|E<1vjeGr>Da*o5n4XU#L|bBWDx$+;UsI3(zi8>rt?2N3wf+0e|J?Tfd*>e&7?1ak&__=zXdD2^CTR(3uruUcJ`Jf} zLX9lAn~}jWRARwbLpA$d?n+PuGmv-s{PaqZVmSuTa*8s1e|;|dGBv@dyy0p?w9XRt z^n0&Qr(b;lN1SeY>la<=*c^Y^5lBt6Lm+=LR_o!0k_!kB~O<12m0nUCJ;ibbBz zh_=v4BfmnbjJkH_4*DPutzI|CIZzA$$$EG=4OuWt3T&GKR<<_`kD+bbhrs*MRo!=k z=?*n(-PE$Fs$)DB#8dR%EVK#3u^ofJy%bNVh9S;|I6qyeKD*^rU2!vU=9dl+Lz=ch`uixs7XC;WGH z43FjDZJUwl<|~E+F^v*rV(b~d>*GBV$0FF^Lr9^jZXO@{9DM0^#a(b6McLc#VK9j6 zjB=QNXY`_OcRyzB#wMt}+I~qxJ;UL8D>#;t4lX?{Kgkn>3}7susp?(Vg+FzwvCeW* zSsq#E>!8=K-C8HQGB)1!yLYpB?)2T`0)UJ=RAOhc-%WL`LBE>pUHOK%lct7CstC;= zd;iI8VpSdOHZAw|aah5QyRmH8Ie(zu*r%4X4Dcjj@^!U2=G(H7^G*QwY4wzE^A1Pd zwWDTXt&v}Gt;_IMAxMg@bfZr*NT=k&$UC@KZWsJT&9%mi8jjz$iql`XE`6~uALeYB zMSHawu{G#|*?H&J_sBJWeQg)mHg(9-EDLqduqgLX=BC9&D0C#-af-7b&5wptb5a+PwP-cCf;5;5!PXG(6RfVVII3 z$KqH1ek4QMXg`Xzy0r-z=)=UF3qZ237sD;Y!*T8}P@^T4i2GHfUi#pUTa)woZFfu~ zEc)HNL7J^OIkAex3aIttyU>$r+0K>FB(%FzP(YDxvq4@!NRm1*)D^xIH)<68Gvs=l zD_E#yKtR>Axknx9IRMBr1>B?oSevn7)%mZ>^ zBI&aq0&8OCIu5CUPN61Kr_1nYq74jbehwoYa;jOpM&8bn9*#yVTIr?j?v5SNC6u;Dwq%HCe-VMeX7 zB?kL?fq`7ePDiSM`gND8(cdT=_s@4GQ!!VnZ1J9UlW1hLnsVFZAfY*~sbIAx!&sr} z*e=N#S;=-=T1j&*`ry|XN0-s7gAnwX=N4{-EF=V9n}r535%#iFl*f!5A#NE zHMk}Ya@Nou#UbCopgs7f86JI^O~=X}Btwa2{<-k?nW*TE|Jqm&F7$LVY_KHMI>Abx ziq|;@Jt+6t)wXLJ+yqQG@41+~f9~s}*K6j1?0u2P$i7TI37EQu?|Ckz%1(D#GH(_$ zzx9KM7AV>C%GKwfpC}*=o3}X=tT*-=(Zo(_g4-Hx{S+$1`>G{z$)2F9>7e23NY^RB zSmsB=JsO^Q$Ua1?J@seodMGBV-pi{e6erw)r<~U6bMfBOBP*o<>|z-5{Xut}_@6Sg za2}dY;I3KgFMIM+Tf4#3+{kp0k9>8=j}l_+wGeBIRFc*2{s7+1C3W=Zg+pm_G)8Nm z15*%#JhxWt^>ML^C+#mP1b-4GxaHq1w>nFXG$F*jLK`W@_|J)UO>K3z1mw}!>kB@O!mr(z}7E5fIuH=4p?_=n&=J*Sz*(MB!;)3_TD z7Esc*(I@2~nw~ha;x%TUR{D4eVD9s*cP-b!yMudBO6>`kli)FV*yf`mb=(2=>pDk| zf?u7P-Ujh-6CXaa>xS1gbufs@(ZqolkGD_Xyu7bSy0^ASzUbLJ_xtHBVkEcB%ApSv zpo)O$+=T5xaMMq{34#u9KvZ-AhkyP4Z~(tnwPQ3$Go*7#FU@K=thR5Z@9Tct){Z*D zyf>U%d}yxRF*UyCHSuM=w)=H_X+yFf(ZDNawa0NZ;HaVP>-ftG;Zt;Txyi?kYg(>f zi_PGz&Vonx(|m8vIugU3bbKlHElpxHXo7+HZ9-zH+JbZnw13GGujgF}CuO=)r+zsv z{5YU~CQ2i;H7}$5??*<@ zPu2x9qmIg|rrqnHp?-QEZ&#WM6$h^Cw#7PN$_Eh-B3j}Eu#ngs$Y@glf1~mi*z`SS zvt8J&4}5rSWPSG}YNUr$F)^NwG7cP>)EgdX+5TiD;Wo%JSqeFQ!5MJ9Q{A(sK5*G@ zOU=Y7E#O`AvPG!&+8XD;2-#BYepYjo^R4yv=P%UYUD4e$s+hxp7z4>_K4W<4INd2M z)S$x-j|jnP(96wRSM))FYG)x}_s^?Fx%h#hL&Qg=mW=A-81CYFq-fEPJbtY1j>eKO zO>7io-v5r(B=N1A*LG>jAY`sdR}dFue!FBjzrhvjs74nm$s5LgBHw|RmMf{j8b*gWOmE-4wk1)x76KOL(?&X*MoOvSi;>J99CMZl_ z<4sIg$pBrsnpm|%!QpKaU)CYK)3H-C*#ID9v`X|v8X{#i97i`ibA?jP^e~(npA5xZ zszHsWixR5n6Hjm81KiyyPI!o>(?%82FzhsYB$DV{_0^>l@#Bwx*q)4k#hq?apWsPz%9HmF7;w9(5A797B&=eJce&}{8VXOzmUUK>S%PK{3%=Arh)4*Urb4CQ=S!2T~ zZ|?#H&nGt33wK-cIcvLknE=-*B%)!H z&l98cS}8K$k?_v5;C!_*`~v#2w}&*A4JXjzuGQB0GOha7DaV>tzSZNLpcTir zD<`u?jLiu?S1vBM)kP^bBS4tB->y$0(|9b8Yz<=Vw_6t2l^2WQSe2t_yG#bdsXx;{Lt6pnM zKKFYCsfP%REbFC~B40Dz!Bbt#<>Kop2c7$qS$*%v`#W>T;T}3i?)YD*3_k;zGfF9w zl(kCn8b)-mRrtN_sbG7Wz8au*NU=}!1#S~8`zgGn9nNhOa@!UOW=h4O)kZ>nffrQs zPzq#~nV>gMX(MsSKVekpiSHvVF0$*IGc%fd>;*2uen94;^NGl^T$XAfaSf zNfuf{w)$mP2lZ=&gwwZoja- z&>5tkn}ox2 z=mJNjCyQ3X+_Eq$b&f8a@%BO|M2@AlrN-Svi|7si6ak4+t!g~RGsik2ZX0cN~cwp zciue+sbqaAJQ>(E2}BJQ-8T1BZ}-D2rGp8traj-jcdw?7sBPdxX{ENt5%&Gz7!`AI zKH;D5@VsQ1nWh>H%%O5*z?+tq7+2h>z1Ywi|5r>h*PmqSvlrXF*hfd)KfxlCm8yW+|=!IWHw_zE*= zJF&=_6H8z2kT{s_KZriXLpFyltMY)2(2nYfS|BQ`s$0V3qq1LPgcBk9SAqRvt9w|1 zpzHRSqUE}EQ^4&u-1evg-9a_(VpV`+kJaEThrHzB_*#+t{XeQnH zj00&j6yp7f2OhD45~7FPTW!KH&1bS9!4Tfn;-Xg4$xZv0%!=Ih%;`_oKkib09zIwg@It@r zx30%2Q}-{$0h^?bveEm-{e}cx_Tr||=tHPllnZjxBUgaW?9%9m&5Bt6k-x58iZ(X?Wc7qME%3oJRPm{7gN*W z^ea*L@$#9iu}dK{coY)ukTqTG6FRccxZMnuXwU(!`KSAAH2GQ76G_RGO{c1HrnKjx zklm~P-A;FZy_vpnawpU59P<-kvE>p84&$ZeXbYhl^0it0tpXgjxzq zi$U%D_!AJ+Sr>ga6L7)WA2^|g(W&POT6&7?qOI;f*r*Cb&E6fgEX4K*VowHL!2I)_ zV`cwLu`w)ibae&c7H|gdJytZ}$qRFDKB|u1?kN2VjyQH4MUBR6M(hN=4wCy}!*hh| z{#IXqBT5VHq?j;o=c`n!PhYsD-6V5A+t_0Jng2xi_5SL$8LJo2%yS>qA~7D?*Y>9D zeETyL=s!xYxB%vKq6F5(srM%u>e|O2KupfD$d*tvT-j-?-O0%Iu0odO3&4GXRMdn0 zL{^-)!cM()SA-*wG#D~@96laHm;Ht!5Ue8V1_Sk?v~iodX@dJ(;5qXb%RmX#<@KCP zmt}d$2e1Iqvv!WnHv*dG1@KXML91(AWS+13q|g7Y)R#UG%}!Os`%7ic*OGH?AN&wT z!N2!aQpV6e?&^kYom+V>>A#Vk6cJ~f;N4Mu!lL|}LV!b&qwhTEO-dbdyWhM$rEk!YagD@tJ+ zqHd$$yOnG7pWAy(6CyvMse;#aq@7L;FSUhSO`!gRS6x@*`57_Yr(&K(BF{egD|;5C z)g|38^$>os=Q;b+Q}4ez0laLBntXs$b3A3n^bvvX>18AG{UV!V#C6Ujkh{YRsoN(GFBVD?+y0PsaU3!x^ z+O&rQ=X!dCgOM;hsdO!xSC+P%!Y4Rl-kf71r@ia-FRGd;B$fQXSM(qLm)qgSf97(m zkp4fRvQC!*WS4N;2ZxJ>eQ0hTDpM7>=|^Av?u`V!3$ZVczE3Y}^%hN!BC;OuO)w1? z7~q@C-hjj!jsbX{BfN%u+t)*tcA<-L9znduOWMdahcQFL({XT}K(x$y?kd3#4b?AA zKE`F(y31zQa>Ml0s|kB-e)Qv2|8Gw%f0@gcA=4tU(lA~@_@Z#qwlZ)VM;rTtr=Jm# zYypXm5m6X;GO(6!lghSGm)Q5S<)JvT_ddjPW~VboO|iVsUVH-U;Y+;?tv33rw{G* zas{@M9s-*+mkf2)TblgzGs){k+KB+<;OD8Gg2b>dwFO@4{X8}P+g>Q6S*7Qb2Q(LiqG?m8`j zCeU7xVM|Jx;D@_QJ9H+>lC>v^liu|?^_Qj6UAdH?-HbBkKv2wa+KKf$u8ch{ z_+(L?dbV!`dz-h>iVQ&4;!VOCT~-aHY17^LYyKSK+VzIq%MWVauzZD;GhNo+Y4URw z^!sj)Kxam7ma5pGfhcgy(mzy2!mQPNlJC8*;_hyD8~h{GXeKi}BQsZLa{7A`D*$-E zFL>`@nv+nTG8#%d%;v8jY4(N1Vs$v1ezo1FLhS?Jg(Dcg`x^!O({*Y^P4r!&tn@5) zu=PlH-_SqEC~RzOg$bWHGUPv1r=Dzr?w~w}X7*=g+k|wT41aK3K&Q#Lh9kYZBT~$n{Oc#_-1)~Lsm~2|md024e{_oQKI(M^_)iPo z29N3FF0s8^Sz}sbwSg<)Bgor1Ha^yfk9Cw{9VpJW#0*V+g)^PJ3&Qn!^)b_49-mfo&3@eThwFwbAJ zcuRtHDW*5m5JWJxR_Ge?knd0YWieOj!etG@=DTrPgOzy8R)x^tCKViL9NXC=lvqxL z0(FdnJRDEbr-JfkefQVl#6$w`Y{BOP+3LvgnFrl>kNQfQkSMSk&MNw!?-W^~-;O^n z=Wedv<_sm78)nf@I&_J1EPMbw0wR7y_WYdqJPt6JQ;!A(UnFpZwLoKaJm0*lx! zfd10zL!rc0d+P~eMfUtJzjdVEFDEBc3<1BrXj4_24i7%3ly)_nZfZJF64@tDg29ag z?qf!)M(Z5d!F?Ygy#Sa+5=Zu~X<1jC=aNb4_(y%42dzJz zLe;4|iYINk{Y{dF|Nc<|PJr1%7J3t~&DkA%|Q%Pu`b^QI|XQz6yDM8u@{`p4Yb{m@D!y_%VMjDMS? zIsAt*l*PrERw<9(ZDoM@{gQMko6DcGK1v|O>lBGaB&8?gFSl}1Bp}{mXo7+!B)OO@ zS0?M)`CEcO$4=^+q+QabW-m|S?*Ph4IR(<(cIl7Z75WXM*W&Lp#pLpT5qg3pB1iQP z7fFZ9|8~szSDy=e?DKGr$w`<7AqvFTIQySD)=x%SNd9Mb{%^bfOg0B@A$QNx+UIJ< zu~NZEUd;+15#$f48(9l$KvJLqh@u)T@=}f9pkVLv${a3>cHT26xNPlH9F!GD;AH6 zG>{|Pg}^Y`3-G0b_A!#h&5i`u{s=#d+89~~;JEu^w}v}v1J=*dro#u`rTBxB%fjc>R zB-ou#G^A|6YoprtFwK;yFR^MNa=$OO;8jpyvj+Fjq4B8~KG$mA&nVmIX!X62T%`c3 zM7I9wa;|4NLH6gImQI-3>N5eK+^gY*pH|*Q9>NyD z@~V#Do7KP$djVn5{;^>z%h`P~XI|NFC&__tylRY%SlY@c87=BiE&8wFFv3$iLRV50 zhW5;S&BckU;xs>y7B}e0lsx-*UB|42=Azz#sF=KB0jFr>`_#ntJj4!B3Ql44l~X%! z&9Ll+GHZZ^syx6Y;R8nUu4igU9v54t3|ILCI`NW<(Gg=ZAuW{^YZn%G;{NdZw4rq1--zk^%D~nfxm#Z zLo6xi1tOC({ioAE-|6)742dJE=jq*o9J}0DC9d)qVQg8>=%7fwK#eFdL^<*=QnF3( zc(u{OC-w+51xs&v(Je78kH4m49cw=H9M>e({SnrI+^<#Nv$e%rSUH1drO zo5j3kI+S1=XOL*7ap2yfqXA>8_8Y%=g`!t92$F+#mp85kY_%L5?hMG;y`3Hzkl?I6 zUPzxG$JyDZx5qe5T)C6eXg~)VK({`%!>d_V^qtFvnQx11{J3wr!%7(yZX5v)4x{ zbaOFTxo>C4(oko7}lbO>>*44!6v6?PLTR7=r%Q)0=Tchpg!@V4@5ek-gf=65*AVV7xpib` zSh9b1IRn4L95|ejf4#f!H!$G9;(>;biZbH5=I|R4gxZZ;Y`3GPWUemAVArrCKP++< zd6EmoJ4?T1HRW0_k$uwWihHvvE?YGDJFn(`2hLxk}{jk%ukXvjvi{x?RjjP-?lVWlPNjx_xZ2Jl5H#P$_P9B zN8f|A3pqv>&u&|Rs7B$Di>&*0vZb+@j#~%fG8%A>z}>TiR*Q;rSz%6IIo&*ME5B^4 zG78LeYw{j2cGxs3Ksf8jAj&HP8>1dKwM^TRX5A7`jL(TT$I$MUK9fT7jF<@_-LQ`a z1dAdetZUvxv_jjfckL#&C*!PrHwV4ysnP5W1)br~t|xgx6w`m&?v8&3x{j-|0GdfM zFFIvQtB$iv^0+V;#G88gURS!Ro@J${nl-hfa!nsC-j<@mkOIkiy9cLRDW{&l#0=T# zx>Ry+{7VYxNOg4lp>OfDgU@*fRy`m*rk@Ge!uErAy85Z0gu8F=F67h^(2@&?6QxTn zD+M7RZR(1Chymq$bw)1G#h4F<%}j)ZvcJFZg_!T?R74Ie|1jMuRf1%xOuG8aAHZjS za|vc%JNxJ_IF`iG_A)2Eh^!Sh z+=XX+Uc*)lf5?me>C1mR@&1o3Pr}gofBYZY?f+w=@3Rn~*t4*wNM2jE@{=N%`CB#* z(49O{ zeCO}972D_V;qwg2SKSFmNIkGUj#c$z^)5&X4X#p>Qr^4`i|@>I#79Iy7`A@`bZwc&nc_N60)w!Xm8-&^7Jd-kLOj zKgj?ybPh^nKDFaTWXi9Csg}n zZoAvk+MSe|cGb`ti<*Vg+Nz3Xs~RGPlA^>+5D}i;fz}W;#hg^lC8h{9?@c9$BsC>w zTage!35l3`^8D92Z_Zi&wa%OKJg;)+&b{(`bFcfpe%JN=e!gkB`<|#v*Exayi?3Jr z+!*dIsDk2e;W2Si)!mvRce{d(j)}5Z%=hB2;g`OSMc^kD!4de#(Dl0_m@u-pVZ`2K zdk<>L?e(ZbjvLsw91nj$YUIku=?e*X7iYs)^+s)R?yuB6p8Bb7=-j=6K>Cqkf@sGH z)^9D3FcUn?>z3%bf-8UHqGld%)omJ^iWyCKEXBRisJ0WIa#Qb2`T6Rkzah>SpN5{s zO4W`XiCqPLqLnPt;kz>N8r_B*xLNt8q|oJsC&$_Vu;l&4)^CgZwfIr?@jYeP_D@dw z`Bd^!yzSosYpr!9G#V^I$`o&OSjPkC(@fJ6cjM&bh2fg~h-){dST@dTcxa^61L4Ej zHEh@#UOY2CfPAMLxjTIo#fuzCwVvt()ZGOIG=V#92)E`IYuMG(Q@_pNiUJ*Q!QhUa z>2;+8Otoi`Lu+xaTVtd`?%CH%ww5__Xoj))@QUvo3Tn2$Q)d!c5UWPD$a?uQ*j)E z0h4wua|{d!NNilYV^jxoT5iu4gZRbg7S6AEk3OK13ZxA83uGLb(|*;{UTakmPob82 z))L>+ZjvuE@^bh4lLs1Vbhr~l!#g*V&CczC{(6=xwf6`wPvWaOH_nB$C=nj@HEqxNh0U+tsBy-+1>4JstMRKMKFUFl^VKw6JCpzq`=4ETuBc9sP+cci zA7HAwBUzijq#d@tWH1EU2CDHo3%$RF$~_!shBV>yS#-s;VUlsyx~{!+w!85!Gi~ni zpIP%NM{-)dok_-kHdp#AgB8jJBhVo-j2jKBWT+C}6sbKqHtipvB829DePPrO(<^wW zcS)dJ^}DqrEjI7m_Fp!6%DGl=ESc_4LqIQu;fFdQzm=B2SIDBpRmQfEWNdpG#=akclSx#feFT@ z)Vmk13e$d(c$b2KbK=k;NXq*Mjwa>KZw$fHcWF9Ykzdn1tHE2#E*cg}KQ~PNt3h;} zSx!EC^VWy%r@nY$H2Z?OK__NNkv#Cc$7pGSw6ICot{FyLW)#S0r~GQ&yt^8}NK_$7 zh50*n4LU=uA32vL^rbicnb6e}meu;VPLE43`D$A1&^?ZjEqW{Q?4b}TkNW@WfTb1} zTEIvfFy*~9tKM0KT=?7jnLl5utz90tTKUK8FVWV{-(NOv$)L4JlkNwvtv616-!e%M zTEA)I$!cGmttI6?`&cNMiaP|{H@xXAFCe-&t8gUEaSf?^_5bs3{*Rshx3bQo@$l8IraM9skW%$d)b}i1cEj5Js1obnnjK4; zuYV3&`XAe2l4XM`tNgh975(i8%`!O1hcTztiyOK|=T-(Tcn`J z5hf1KRI*`4FX~grP5f)QuU7uXe`IZbulTB$PZjF-4vDL=6|HsRWejJJ2!t&l!)hRE zS;etZg#J9)?D9UA)CO7iIKvO`ID#w{VACw8_cyV^?cF`ft_cq|xDlZ>xkC;A*idBk z*POs{i=T!$6r%4}X*>G*Tbm!8oVKu~5d&MeYpXPJ7s@QO7?nv`;oDJ;R4dFrgf&GS z2V0W6%1YPt^RXBAv(*>P+qh90z1fr78S7et&9NA6prEdMYwb%wBaRl;yibOT>qR<3 zBcngk)1bdG{@R$Lm<}e)fnWtL)n$`GIy92|p$-%Nn!8dqfkwn=q6keFP@*q>a(Uhgq`yn44&^FsZG;Q(Jx%i+}%s?+m#s}z^2 z#e|D$@O^U>L{2C^B5VGP*?++AozeMT{bpq_*Da;&)P!Rb=7Dp8Q(isp87&vQIvY6K!Cp;jtgqgz6O;+!+Bo84?TxYS+X z2LMbdCY8;D9<|7LD&z%gP4I@jvQretEt(Pt^zF5h96TK&HH%b_gw>TWUIeU^F6}x- znE@twy1lVKPQZI&!k3eHfO#W&Hzmz9J>tiS2m4XfeK~`JM7b?L)8ef>lXdckq_V;_ zBcU>*I5pgWBkU6I&N189e~X@pJ6On`x)#w8*1@}X*CAkPOc>{W{2V`oAsReKqeG@{ z-Sai{8nn~TJvfJk`H-fXZJwbey&CH3J+bxZu&UeEezk2*xNr<$njQOC=RF=c&s8Ld z#TU@{E;ww?d^h2-DN-fiE~2gT^_X>bXOBGaWQUm6#FpD(cbf2lYR*7-*KK<72lM)05EfXtu!rK)^2wD!Jf>gSlKNgP^EgZV7Q< zlfj2J9mH&q=Z)1>u+;AuZfV&0PnNp)V$Qp} z;{iCUz`s9FyaXW$F86U#Pe6}m=t&0i2`(8ENgWmH5A&p7o03F*uCgS$_+Z zQ3l5=!{ZLG^s{5~v`*)Sw?{}84uDlXqmY$05caF*`vlj1rG8HiM#7@-lHj%TZ|Q<} zS-;a;^PVBp{T%KZT4M|!Lr$eU{51LY)pYumx<11zPlS_?0;ykVh7Y%QHS7k?Smxp) ze=<;eP6#C|T!?`hTm?IO-kDdqw5Eds&8#`X7*=%N(LQz>h7&@IYEpG0n#q)V8d*5 zah}weIxTH7g4e&PVFq4e@-G~A(9Y^!vQRY+s(aJ9v>8TVTgh_oL7Zwbr98~mpq)8( zqbO7Wf>y%anZMTyW$sIof*r^mQu9%Y0A<@s6u4kDqNsy+Tvag}vU86bs8VYz_1mklFB$iha6LZ(^^=%I+)>p0}1T1iu@x2&Tl^RjGoR+?G^Aj#w0 z=s>mu*@R*P{$!<`hfr51wmbOn&}lxww~Jjj!U6tP8@h_E>oBWR-Ir1&x0G3JsA!to zFx#t^nPA<=cg4_|yo*rpb=b9=ITlpANxAi;sXdw|y6saQPj^fBc={aAEaNCCErS%>7#AAhO4HqTj>kI3ZLmb?axw z8k4<6yIAf>$=YvV6?HUr|6&?QL_K!k09;5RgesuBWM@v&Ns$ZT{7qi3GdJt2Y{4d-}b9 z&i$fgn*x#H_-AGM*kku05Yd_~aNH-?VQZ_bXZJ2yzUsG^Bl4+cg9m&$gaC zq#4UlbF7;2mUw0~ZcUg5uV1###80~Hi(xzBBKARnu{|SHkIn|-Q;&Oxo7Fq;gAFM} z)f%~&69=Pz-`ov0y*J#ga!0NUSq`}0P^Qk9xMdof~-VynH_cD{D&!vw1(pN4$wkY=1K;nUwP z13mdv)6-kknd(J)8N&W$nC*Sd(M0XiC--W`uoHAfQ&9RnNH5TIt}M?1e;@n{v~p&%X0Qgq4RE=@ir4L zeEP3&soyfmYbiSPq-d9yWIK;C&DH@+d#SAT2Mt9p&q`Wn!I_ppl4lg_(xuP>l1>RNmU5k2~q=j_;tkk$DXyY>9sLa&~rxL$O* z!N9v$X5xD8vhdAUv$F!b1i&eK7z!l02M}OHs)2n;2ZOvn)XM zh&To;k^`B$-h=$ui1mj3?6?Y3l!veGgYWpDIenq=%8zodW@e=}B4d7SbSi_(eOkMQ zu)YZNz}nQK6}lmVx=<4hVwy)=wW3hk^)0nc%Aq8zBe`Q@O#w|CH2s6<8WwN4fVdrn zKq}k9YSC5!(`;TWrffQNI>!XcQ1g8vV~duUxKmxlq%*5h@qBI!rIY>F*k{vv+a@v-H3z z=hcBVk~_I6e7nk_JtOwpkqcli)|?#!EZsNU=?c8t1$1(tMwn`d|C+^mF_i}Dot1NX z{@FLnfmh}~El8P|OX}-@ValCZR6Gi#D(3s0JhP=WD0z0o(;luTgqiF=9b_uHF1_u& zeMf$H$GyCH98^XuE#~AFn#GIm=O4{ejUzJ50Qs1h=8w+jxDH<5Ah-fAC%RjOxIu~@ zm|Ehk7FyR0coLV7$epLE2k*t@&PL3{htuLUH`dmplC@VNbLX&$`y|7TZE?NApe>$8 zBsDF^Het?ajhj{#E7!PAT63kk&dYYnbYqbxmLfYXb-)w`*?1LxK7JTpU`Q(oDcb42 zBWM|mK<*v=)Hpi1Fhx`C`UHK}@O&vKaKU;;8Q9N&Qzurv_r!qX{_R_HmZPiDkrS5g zs)osCkLcJNf*&VF0`?7%)~wlUB!gX2xu9YO5q5Ymhs39h?bXd0std`UZfWr~-1vvM zFYtqH6DkYJ+5X=BSkO&}g@}X}` z)hW#heRQNUI*~78GA(RW)Tb#fw*RxM*S4{Gw-n{3KI`76PtfIwVIsb+LgDuPiy|?d zRbz**R!ma;T)q(p7A z!~i2w=TEbX@>B1+lOZrFNlo2sT#X=ld~v&7m{fw)YbF=e`9~ZKf<@UK z_$j=#Q`Ea!UcwwWiI>zcOYHYwSGii8AqnO9`=8H<@o^y^NG$IIeT=50+D7PaqCmvs zHcs=ihJTJFJgniFWO(L$7S<1h1ou5}+2{vatbb)lCmUG4w{qS_4>tB;KnfvbyYRB8 zk-Em;EFIxqHIC(4H0e*$>VMt?8-!JxOP=f{)qtL`M_EHAE^(5|)XUhS%QN7{)^(2F!u!B|HY z1^!W)M68Bm-)`1Wv1Xg5P`L^+&onsYAXt5#V_*uz9EfFe0Zt|8`E?JymfrHrjPpa^RCnUJ;o5ssHP3Vp5E}qarwJw@c^Z==e>Y8*YV5Nt@oDd83_+$ zty0qArm8sWlvYbMEotYq^O%dQs~!19+Y-u;@V*@Qn;0K-m#c92w412gv@oxt|5+w{ z=KGmWtiA$#XHm(nyeqRy;5&L~=49T=uq#*%7@LIDG(*7}g7xqTok5jwneq++CUb2b zu(>>@oY*2K`|$m)Nw+1c)2-0ROux=HW=8iTefPTSq`JeDbX@pWK5Nvvj-kPa6bY)w zc-LD4Cze{;hv)(f8njdq8cuY5A{O9f?mnM=#pWzXI6U}C!sj(#bjyUK85+vH`}yCQ zuikp@5Wqbp!!su7mxYP~$-m)fi)x_f?XD7iA z0?^jtPtfGIC1d~fD0lXIvCIT#0xk~fvXy-@@H%50RG!o?`!{4MR}HoyU;b#<>!J2L zDCHj>YG+Nfzcm0)HA)}hWCg5pckh3ggjjm#PcGdHGIXqHd|N{J!=32x&3~lFI?M4) z#(|otwZzL8gOk>8qP;w`a`QT@uQ+x*!c6^{Jfxt-y)_glvenjHV}IU z@s4Te)#ZtOCzN+Y_cthf7e>Ws4Xn!LrI_?L2-5FxS-opm?x9wq{xrv1&2kuTY&qPE z@F{S2EsK7Om_QuVoq$Fj9$L7sC*Pd%_HxPYpnu`}k-R${KVCDfn=hCXw*Th=vx?I* zO{-%Alt3m=d||@YxuSlcvIIJUNvo~XZzh~aV3J<2oL|gVhu0Eb7v!(w3OPyi`>OhP zw`bGbZbXTNQD}zM%*9Ww)nkJJ-t-Wz(diJ+vvtg7*GTUE{owaVV-)|AV+IHhg+NkO zeUbou=c<85R$0b_uxgxTWO~(eM_y5Z9yl&6fK9)iR(wSlyb?3zM-JHQp;K zR@#UW%hVj>gv1gxhfVk$9!0dDZ&YP#gl_^2k8SsTbxL*xae1?c_{J@Sp4qtrR_{UB z98#DzGAcNT<*pjZ(y+js-Bw0*`$H9GUxIP6;Sr9bvSDm32oK$WCElRF;Kw|v^HSA4 zn2op4Fr8%Cdbst&v<85jQ>MwwJY5XgvUMT##|aA}*l1m}y<_gDuCH#N4$gZI_)bIc z#8#VzOgIU%25YBIp$CfsBaGi})hXn!(jr`@PhJk##sf5@O&S8Ylb8JuG-7ICg@nWD zY*^G=4r3yJPWj`of1oA@CJx=0#TAT^tJ~F0IE~CNsFqhW>CjXiLkeyW^290&w z)#|D8s2T5omtFe5f_|r5bgl5ICNcl{C{wXWgZ>wFqQ$}8BHuiv>x(u*e$l%~9;Gv= z&$e)8@pLb8*jZlatF20nTR}DBHqRW=Op*)(MhkudxX(L!^m^olCVZl(%~t4FohAb- z#|oyw1%_5eDyIQUMk!FC>Sj@y6(xCP7cApsv%`xt(a^1}#xo*VQC7{P>=1-1pe}%I zWUDK>XrC~>3efsq*L0-MWy$U?Fm_C_n!!UyKZ6!{hoencR_AB_FT3-4#KXFY;GI1z zvLjh0PKff@_&zpulxl0`oi>GEw?2iC__N@gzCUwqWBExfd};V~*fTrgkT&T`w$2N; zF>vFfNyaM^u`S?24Qlrx&c`4{tJ9gu-gTe+)4z+r#dJgzbxZAt z=!4buSZO6dNU%HTSUDJ>5O153ue$pFSyGo6F*R{AhZ!6;t*qsh&akLC` zR!kh&mM5#NiQ;A98d@$=&U90sJ4DwrUf4?p<=Y+*>WkV;*{C7yFI>7qcCbmBR)M^f zyEkO0LwJ$A#<=4jw6zA! z>F!3xJZ))WwoU(v=Lp7h7%11n-hZC`fEjFsz;7hPsK(s#Nup^t=t{lWTNzMG`%QLY z+#kesYOR90G__5SK6j$2l_lK@zdc8AQ>2rk`RDlJrFbIzl1tiG^1N1>{PsUzDDEUq zCsm}@c!>#d-b1m9ypAo+48-q@dR3+0Ol@D1n9&$>Bi*kYwyZ#u_zU^lGwdm{8sMGI zE5&5d?T6LNy4m%Ue>&jN+I{D{&)md2mS)O9&N2S}1+i=a5jpL=PeGksqq`pzIugem z614w@VAC-R8>fLk3%6_LsYw+tOAt52bsoSoCm_EjN@~e(2Rz)$>5+T>MOa7N^nI6x z$r)03+xPyj_o=zb19!BzX5wc5_8)fq8;sxi{lCHY)A{SatYG{H+t*7K8OqbF`q~{{ zkHO8jU$$6o)+S=~A~1cCO`m%U^KBHX$k7g{4F_b!XI73{EqITlX z&*B z>j0ZQZ^ZdRurPvc?P$Tq^(7udB% z5HQ4s!<-{XUt8w2uo(XQEl%K0Uyuns24))EB`GUE{dcea*D0TxrdoUN^ve zN29svMng?5;hoIAv`>Z1ie{9lw>3P`#v8a2ubsbtp(K63S@S#rlWnPVke)m*E1%f#4 zuA4BcU3BX3XU}RLynJ6yvu%M6pvmoMxFMqbRt-1i-As3uHYdlV(zIr-Tig2&@Lk$1 z;IQ`l%_LCgEl8-#P$?aRp0{YD@LAoz2{2`yL`2OzmM15>86Y6hyL{_cQ%Q!k(~~W$ zcd2d~pb1}1oniXxB_mwW_ndg*x_67?7Yn!Y#1b4T|EP3i^KLjDToic`?6=%j078JJ z7lZ3?zcmg8>J;>k&ZC>lirh)ALA!P?Id&tY(JO&7qlK~KH-wCMwCmibEU#>=UZNQk zQ&lzPR@Okln0byE1b8`6oijmMd1P9k&crqEG5IBT+iL;rsK(~2H4ZKXF_E5VcLf8o zWMbaFP^&GW{U=`ysnm1tJXlLitG^;^e?0RXM&_!m`&jX5?nwqoxc~LcE-eu!ej4qu z5sMx4IvXnKMC&;d=Z)t+_O?ue`jbb={DLMx z{l|%$AFIim1Vi=mf~eNfP^tZN;)d$(RL%=O6`x&c4-izJ)xPEe06uF`;wIR+Q%Ked zj&36XkTC&AJYuepH_VNPgpD2za$q%`(>a!N+xB~%Z5aRaj0)PQ z=hs*)ZRJSP_a%;0VMiE)Jz?)l#Hzy^a91l{61fS6&wX8@%eV&V>wZ3!6)|_HY9Dby zg$-+wh3TAT!n?-m*FLZi<}MAFh-Pe>-5U;d0c^LI3WGOjTs7q9!H0Ozv&^%FHlIws zXR0-ByGHbyXrui?3jpdzkVQE=?txKk)ne~t7d5*;p5gkjp56RQ`ScW8r!okNv~FuH ztra6I86Ov}&62RheM%(S*w|=%Ie$?=tMRK5G*_@^l-)0F=r-%r7U0WJMk9;+ngNrR zKbxh(_|X`24JnJ~kguvF@%Rq84D)9ci$ z^PlUIuI*ENJGifrQA`Og>B1a%p@JSGLhae!M3(neO>fMsp(4>R1TP6?!Y{y|hFKTd z57Gz^{33XcE8A16E*>Go(qsc%wZd3NfJw9e<^)lNfzhRbHrV}I)ptG3%F@neUp7fP zp7>YqWT(7$UV^se#phpWqR3yjV{eMGoz)f#f6iV0qtGTrQuN7#F0UP;$gMFFq5QpV zViwx?;ssDl17h=)B=i9so}Qo`?L?LIKf|hfzURw92=`nrA{YrvpUcu~(^?32-_=i~ zR*0ITRVw6Ezs=1I9qGB>KrwZf7cTaiT1eIt@qAI533p=qb^`#B(U;OtA)I6-<~@_o zyd(+P@iaXdcOaRTaVEp-v*0~J*|LAwg1@)oMO%Xn(~(9LrGmaeSe3pMuDX$RjcHZ8%Pkrq(g-m`a=0 zRKKX}vm3hQ6%BMIBF0&s@J`8=WZb=E{GHL4=*ba$=X4e?^QqJf!FnDmrQA?KJ7<7fr+=j@nbd7I8$R#2eZXU&4P&-mjz=&Ut*N?2 z;?2(ATWt~wr>~&j%qUjbd&*=?Wx7~Wccw0bX!BnU%!gdYo^C^H6@Q$ljg6`&@}hR7 zR%^To6vxrf{)pYLwEOfJIKVbzl33Q?vH_}fT65LOF|k<#p2Eak_ZD%_LpaW%{suMLvjFx1R_J?}{0USiN`Vd;T>)?GVa*LQvgHRoq-k2Ox^r$vgx zOygGckve^m1RrN5)N!zW8QVaHEX}gCj9!YsKCT$Wf)$}%!4@I~=Pe329odn2kh{-% z9`#B|V4PZQwqu$H@O6sLd`aj$^s%Kr21B|5;0|-(-|js=x~yqZ6!P-XKoAKK8nSq> z5$wDWS3r&we-V}WqM;Mz(DZXScZ25SoeI>NSBjye4zW|u9i7KY&arv3dozR_P;YY@ zv#8_9@UR84D#y){TAZBdoHKR<@l0&vd2#C zL&4@uoLeV7X3h1QP7zY1^l)|mAKFVsra+_p6k(IX5q2{F_@&!bnVcbA(;TPJULUqW zV6Y{dd-PNA=6J`^u?N?Ka7d?R9+49S{2-4o0m*nu&S;GnZ#i}-ARaA-cZ33hFLy68 ztRRWxI<>ZSkMN?%!#X)c&f{us;(C`De9?GtDg}BLCcgHbex$mp7ux;n#@;0SY&$TY zzsOIO^MsPvdp}N$$02uua#waRL9G#l){19!fLRyK?oLD|}c zvsj}WsY|!)z%@}qXHhUNF3rx7md1;1v8Wke=t}+`RCwS~6H8l(-M6Lik@d5KLA&6A z2&3c0-yz|0)<&vM-)2)5alz;S&~htbWU-vu3iM2^WEqN;#P-%A&Kh`BaL37aYne0j zDcsYxjj$<@Rf$_y1=LZ#wFVDbwDf`=)j#UMdZ*Wp!aCh6`nxI!W6ZBzd_7wF(9KK{ zt%QJwlW?1`UrKv~7ITdAo`v*|=n~yQFYd2zWRCdxScFU{YT8m7mLD9MUDm3lKos(X z3v}zgRbd{Az&^)4F?2joc`cWF>+Ynxx$BThPg(W?gec-%m%U!W&>LLH*u8RH@_>I9*h?u~V* z*OxAzMm6(X)@ECsHv}5^qLORpckoQ9qOQ6+(?>yJ$GdI&-DKkpcy$f~zNuN~8xNv$ z>B-dzF|TYt4@<4TIX~p*n}8f*CXP-tR*RWXbq|d6*=HdX#h1?bScVIOxh9`<-iO(E z1P|$zfT2XtPWxsmLk2>K?QYm2+^OaA9RN4BktXInsGm}nV08zrr`w$a3LaP=w}SXi zW6G&qtJL3D4eMgK2PubQRtx*HfKJ%VPX6tL94p4ETGEw^%ScVDnvJDb(!nn`4BZlg z09g(9Xf5@irhHp!IK#Mxql+h?3N+;1H3*+OQf zw|{k<3%)9hAW^4kFQD+vs|Itgq}bJBYfQnOyCNG@K zB$|sJ|8k&QZYqq*O|yyRkDkP+#r@kj5$%~$mVCBeXsqZJ1`NQl$tc6UXXM^L;mrRq zVNmot?LS&T{2z<)qHBVsjpG>8jqv;A^JwlTQ3S4Md`9!n6a&%$!wi{d2{|y}%83 zA73nnxxluC3>}g!>p;(WGo{>{ll9#N%u#5Xm`&$ZR5fiKKk;~h-QaH`E*j|Acs}Q| z_2aDFfbH(~vrrFe6ZGR4+-GVXTqbfgL~Z4k^JDXZ0*|^2*A2BId!CPg2$qxWNU_U` z*#9AznL@SB&5cAjBCRU~wFfjdM}9mQU$aTafHo~XpJfAbpw1`{2u9{1#NzqzJ-r~K z)m^5;Rt6<#R5r)HA16#O^SV{XjL4ZU2yAkpBnR8eA|M~|_cV+)4vpTz5zQ_~_XOr^ zv292qwU(bb`q*E}d`e*RwCEp@ya{u!6n+0dnH@)he@gxO-Q2Pb9kUpS)8sbSKUiNl zPq(F&&9GSoxPaaLZ0+IoDES;&5Btx<41xyfHHV-VZghc_v5HD!s7^213-k=fiynV7 zqCu*rA0W<{sCyRq=ge5<0lg|0A^&WtIa|x##DJ=Gy_G+jg=O@Ye}`j2zGMaU+Bp#} z^|_N(ejjUp{%Q1!xQe^3DQANr6~wCQ=N~d>#zN$t3Z(?@@xN6M@TB>J5m=q}(ZXqp zIA4l|oZBiOwqJ<%W^<8x*CRT*iiB>@?I5(9!>lj7gWEwHdnSYd;*0y3M^SF2Oz+}Z zq3Hc{>xQYrI8>+Ffj|E#)lQh8ZwJ~(qHUWU8QDi|J#JPeu^#=w<-N7$Dx zbWS@_D?tKQHEow>=+PRwN(?Q$QUu?fk3S6WqQ|w9!V8lsOUG-g3iQs+pqM*n?-e-&dJ7ITy8^s+}-IEQha7 zr^GENV|-PHc|=7sjlN&t%db36O`PLe{#}gUzS}kT78isO3BIUBU)S9(D^;h z8nWZ^X)7_Eq0<3P1t!o?OA5Li>_Tsd$d1g~w+U^?bCP+jW$r1O?&7*;EtZr|B}s6J zl_NP1p+^aU+|DPqK^m_5c3d$h)LY)+Ob2bCMw>eq?Zr{r_g8EZies< zFR5Y#!DMurUe?`{?s{?LZnK5I?Cku~d(~(&*OxRd6cV_%B;w(vLKpBZ&b{si1NGxX zm4FouG!@_DeO+u3-Yr2M_G#8_BwMD|Z{TXEWbEkXCt~%R{feHp@D=qJk%?xzKy=hT zm04B`!MfAC^a6-(uy40E94M{oBD>hU-{71ilJxdlkibVX_6{m~aBr&b+4j8z(*p_L zHT{W2S#!^VlY5I#w9r5R|4)AJIsT2LMv^-iruKUyvw0~wq4ve4zP3EFjH(t$R(8qr z0W*n}&7dR(>zT3|Y-`y+P80&LCdJ)fVO%1g;xjYFVRoghs~)y{Ziq9aKj1aASdF(m z(M0!{xo_%}fc)A#2WVhp@qL8I$ySGyLz>R5D-S;fz+$LmyRUvet;ix_Kr%-MFJ?MC z!S4a7EI(l-gr3yj&aq^`zm)fs z5e!h88W%kawdp1PyV*e{9+D-Z$nQkSUsF8EJ=X{#BW$S7XK20KP(E2`-Ej5p9nc!< z9s2HZXV4(+tb!_wlPxMGCZ`hWND{}=x}pwJ-x z<3x*D>kjx_%EB09l*z@DRZTD$|CtT4N;_09yOC_8+m16c1|W91xTr=1`FFQ6Q?~qOZ9v;^C1F&tAt@Otv2F( zReG{X5$2v2cJ!cgsd`6QVUB|xIC$Yr7 z&H*2@JUG&A>tQdw%ESK7BJhe;+%uh>l0_ags~zFJKTV9?1xQCvqB7f-)`H{hA^OHK zDQi(^JmR`P@7Jl?QFHYyelt}3vs}p*0a(bcTQgHy*KLd-1&0+;W$JmH$R|0s$w^&O zHI@A>ie<|lxHk~bwVw=X#_zPc2rd{dSD}K%LNWKnV&Cs>JU3WAdpfR1?LJyVr)y+} z9b|42d@B^`7R2@@k;k@<>TdY(&iGr?V^ufuZMlCR=Lq=`m2|vaV@ay5@UYfl&JcXP z8Q(v!V;%Y8w)6fqQ>@8UWZP|Av$Q3wz`A^KAqTKY>&mJ6I7h<-pBfDt-qb%f-;<1MPlE?xY9D9vy?L_E{Hw|KxQ(SR7~3xgMdUG>@Ff!gym}wwq<2bj(o;3#*XU zEjQNvk97fO?O!dQ#`imZoUmfWwUlsUOkWWMyBdHvWIH~mBta;==~mY0kXv~y^}!}a zvmk^pvG|uTZY$zCCM|oBQNlXV%#?FX%-d+_^1a1#!2(vp1$B=RG8+Y(umNZc78APK zvVxnYwpJ;A4)2^xtk5m+FZu`2`~FQK!Nsxp$okD&pIqE}p{;iw%&xJO7Lo2WmA(hALgu~A`+N|tXupc zUb4zWBH%{90nDfX_cLy4>^L=dH}w`}QF*ZH)^`IE;D>@8K=KNMA(N`htHLgkPQpVsPq zoLA};Pgj^T|8midI9IYRh=y+#84RG@tyw7=&n?g(!OH;?(zH7w1;Zf}XviFJV55gX zLMZ`X&E{^8xG)fhh6}oJg|+ZgRU?wmGAbsZMi-F#ra1*ud&gE8?@mxprk zhTh=Q15BA{(SVrTe!l)Z5|kjjRPL@2Js{m+*L~J;UA?gB8C4}Lzq;eJ$)krha z+N4CrV7lU!TkG+*I{+szYR<1EHo+)7zq%f_{ggPQo0tc8lY?$|7l*AE3gDRR=DRGN zf*~gvobfQ{8ljr9kPLlnh(aDO1?9Z zQalLqB$`1*0q^UACsbUKJ0#Ki6#2}aH-Ijs^ybQZ-qVe!J8MA?B*{M2v`b)91PbQu zruG?7`5k>F$56-hPQLaTpSOwN$TCX7odL~y@;REza#+`s=v?n5i`StwbCy~O?j>%% zw>W%}5wfL76Le^M@FwM@;8e{cy^Ezl#l|fBQr8`x%cLN~>VkWXA%YE&&Q(){d;brq$Cz=^6%7=p$FvN)9xCk-V4d|KVwor|4D16XyRAS6g z0{R16zBzyT`dl_F2G9bKo~Iba0ml~^=9@(wUtKV#h$idr-7{~ui|bBvHjrk4TxqsO z#e7MHq@1OhM#fGD&<$0jNA~c|3G6$9bxb>SG!a;7=Ln*93Nfq7HMiEaTT9~AYw9Np zOr|8<=^J*m>r+|P8%>F-t?lV2xBca1PR%|jZ^@gu$4HfJ`7ps;CLBx!WB(W8_vGc$@E z?mYDr?%Oel8vYVSev@{j`NGF*CV7u>3LY|A%@|=Q^NO(=cHyzs?3^wmy1aef4J8dg zw!hU%kwMEdgG*ugHYJ6A?-vm5&s}$pgldzpHIVh_sNO-c5T*2XzpUp-A(parIlL#r zZLZ8?m2B5?SSDM%{eI08VV>N`Qc=^GkwR|Dy7}q-?;~7x zWfXwaH)oSwBXJT(H^c_TYU0{OLmVoq-Gsc(kIz||X&(^66oQW*M&)hgA{S#s3FiBM z&+vNXI1D{HRxeaknseQeEIdX}h@UV+mbB}=c?>HBzN_LRI??CbU*AWbsQy z|HHq&+mV#44hmr&M{4_fJqmEU?NAZeVs>vVP%V)~CM13-d$q6cw&A=)o6=W7mpoH1 z8|NdgGs;%><7T3@hqn(Hrgf}8YBtIjB6Qaq;c@P*qRY&d#d&j|d7*4K(sK(NzGmpv z8TC{lpn*1MX1N3YjZqY!9wqO@7TU-=(Xk61jh*lp@`7~N zNgKB~jX?X0N^(W*4}^FP#`X5R|8pEW``|nT$z(X=&Qc7}YyV*sbkcMe2gO*Tu@29sD0vaVaCxy4}|@M=j%EU+~(mQXs_F zt0)&O9kREdSi6(t)lxxYWioNzPus-6@~Y}NB46I06rSi%<#Lh#F%GK5m9XFL&MXXyCq;F5oMmh8_@N0TY9>t8@|gDRq~ z8LP{x;}%WR#Ge>~2z_k$0K;?SlFEx341D4+fjcorH-iZ0UO_JzolSqWV?rq#q>`37 zB3kkRriT)50(1m7nT|eda0l{c+!uVB>i039TN#E4m5YNT%S^Z0M<(ieA-+r^R8~LI zAh!QnYsG}6r0>{Wy^>--3w!0JNc6LnwN=j-O}JBN&dR5M?aHYo$j7KA4ShwhG17nh z3!Y38m_MZa>;5M-A|SQp*gRUyi7Xy@b~rne5b{2mM|Wgn844~3?1u-zA4k9ltgb!d zBEJMN+UE5>%1b(nu`%U;m{oQ?ZfafYDYlA^Xq|64Rv?&<=<&igsYUwBV+UHdherh_ zKg_qbzdO3@vMe+=nDO8m_cHe(8&TgF=^xx0H_sgeR}A|+(6n{~DR)Y1SO()Y!rauY zAwQ;QwB6qpe(w^_^Yq-SB_jD(vH-XHU-Y!~r?9-wi*mQb*+;IBoZcJGKL~)^W>DEB(GHUo=5i-8)*z_sGNjejB-|6Ud5G6B{=4OWB6|{eGlj&+&x=cJ0{2ET}N`{MV+(0uA6pgL=)}k#_xt!_1d1iE@VO zv|yNBOJ+cdQPJyKfy2p?(cDc9htC7V%=2RyFJ}O5=GX3kBs5a>clz^BFBi}Fpl%U; zuT;8yzD6-F8aEQeZcc){q*WIZ*@>4v6LLT<1(_7ax}wlA0bp ziIFNawMLJm)5c>rki5!+Cwi7x=!$qKF>vi61*Mj@cBUaH`^eX7l?wQhBzN@Sf}eyB z$}HEp$?bN}pIn7>9fl0bIQ&|}nM|5oM$Un)kfG5*}5!B4sH z43zYq&W>d|`5ZgF^a}lC5_KPvb?#+z23*)542ew+RXA8wp-Z5Y%l%TMppa_HFMK zcmTVG?u{C4U@kEuhgpo6vedolg97XVO*LEtX><0k?--m_6B&1g4d4ud`00Ba!Z=85I*?bO5$Ht!+x6 z)S#IVK42`Kn&?Cf#IO+t4dj)9qPzz>@dfX)dY$ztu%+C+N5lQ|8_k2S^~lenbL(4? z-K1WDr>f9o$YW}#yBsE8a%!)Pemut83VBhmE-FBpnKNe;7emTp%+l9SM#PJNh~Ay5 z?iLgpO=5L|qV>rHgF1H^PG~76p^}w-M*Oqr)1kJ8k6fnw~_q0wWC>t|IWB*D-2xPmvx<|1wYfcym9GF-3|oGP-m%U zvaw?R@!QN!qZF3ZSr@j#IObH@X6l27+sVv@sz80-8kEHWsV1)ovqrb-3(EWzft&G2 zR){H~-So0q{zllpXsFGINyZlw(|3rl#aM}SsS#Yz7tS&>D9D5&3EVVIFXZpdO;Hm* zuBos^?PdOHsKqP}Ig;(Wdi-caF)V$-kWVD}8%e&W|1=nH^}y_iPla)Gz0Pu@b>79A zzYKF{?<$JngUh=h!bXCTtI*$E4Y}S2J$}A!9C2vK5~quPrG)iFPDe3ZIfQlcEMoKa z+z5uhvWTCKHHwr|WiK1ejWrw$#Clo|)(GFp774c;~IQ+;-zr3jR^-3eQQX<-&@wl~LYh7cb z?JeK2KDl9Bd7$li|FcxI%X<{%?M^JP+7(8T0NyHT7*sYDYj(V{1^UJVxf8H)cx(R5 zJ)B(?Qua~(GCXoUV0x7lkKngA#$^rB*IJI^7tCWcLTcNKbA~d#EYkp1*;bw>*2`5 z1h2G5i8M{JSu2{!+BuzIKE1L#is=v!rioEB}kH4M+uGIu&(acK5}cpI^3*3$1l5 zo)}Yy6e>++XF$Fv0|oUThk7#3+V5FNRV>37b{ZsJe|hF9m~6a?ec-YYka2Kkh->tO zY!&dv%VgwkDC0+Xfl=pF_2Ag}6-1lh8Ya8ET$_<}dbM#I3SI-=#)}K%iSnQ*@ zfU2e{-pxsl#=qbOc{X-W6G8f>K(5Oe0N)KIZ*FOh$P&hl#|o?_MXgtu#Kk$+B-mHQ zi1en>{e=E_xUH|y*nJ0#E7U+He3j|ncbVCR-`Y^E?Hr2UOt^XUx0RiMuZwUE=-LI| z(QmU891XEvk?!mjUs7<~QE;!*m0S)ru5Qg_YB*=f{>sCoe(644NYtc^_<7i3Hx)yA z2|o<(@Bt9D8deFa>)wWsm+6~~-0ZWDRE<(MoSRg64eD_~(=_Gt90fRss0cuOcz8P? zhR=)045;h(S-e>rP|_wKFYOpd3*5pzI=LKs5HoBB92?~+dG&G@Yv!OYD4?J<>Z1%U z9+?lZYOSq0Trs12kD!Y`&IH0*b9HDSXXx_NU0uAERFn&=DsPyfHed}&eBhJA{~W+V zvmAcuFg^E+mBx*iQJh}YYq655?DB8B=h!R$D+&u#ghJ#-hWDkb!tZ6Wx~*(OE@-Pi zylMUCnW*AhJWtJnpaiq5saI!9muJ(7;rpP)B zqcXf^g4FtmdBQM2JLxb(>C($IS$!A`+tiSXxSq@t3xF=N8df*UnKCC$knEe6Ihuu$ z1UQ5R&=+d#{mz7cfy!^x%`>3id?D3M3h3gjAFCM3o#tWZTs}ki8W3?%)boax6>8(8 zuGNX*)9EHa1&+zs*yM2C{h)b`3x3jO=UluL4lYs}e)L&>MyKAxoXT8E`DMj2E&UXF z`x_B*(g{$LQ_-uOuTe>?VJBN}fUE7D>k;YA7MFalFiy>!IU ztvKpUffME0n~V)<-)uI@k`4$(-(N`vCVC|2f}~TOYOf}bD%XPMy{3%b9pzuc+IM?H zB<*fte(anJzsbhEIT8MP5hwLR?(iW1eQr@nPW2mMqd7z6U!BqbCE?J&!I|>_U$dvT zI=sXQ8dBnQ&8`0rn|1jktuG~I?mu)3|4kSErVP*dZz}ztDT`R~S1C6IPq*I*aroG< zRF>aACS>Z@Y(OC%y8zC-a^3+e|KYlIWJ}R2M1IpPhM}_oIBd;H?gqzbGg9_VYP~zw zYKZ2%l}`S4gh&FVFmoqKC=+|uW3Faa*2W$R$l0kh$zBu0l`u2=^F586Dih?Yh=HQ0 zNrB8Pqi2f|f{IzQ3tHxA&vYzN5oo{EMX}(`%ev)*1JBDQT5Z^Y@y>Ggy6oYaI|CNTBIALbO}xFpN>E{ zn{=2a&afCW84RVL4V~01ZuGVt4XdQV-c+}iREpVGP0Nucii{@Czn2eA9Q{^v^J1BO zFNx~d+!U%~MqF$4beVC8b&PNgLZ$gw^ zla_PS)C7A(L5cF!Hl;>1+bKKs4Y(wTOIXF-ZPM-#Cb*Zv#x^!#)$Uc~cWq8(ToZ(m z`Ku$yvKh*--Qp0MZ#Np*{%aJx{XID-7>BX7TTeJW8O7cwEwm-vVR%^$uGP{&_RuCB z?Y@0{bb5Yt!f)eFxCH#ZACRA*+wSh*%s*xOg` z0Jh;!c!Kpb*+7pJrOPoG1Dl?I3zq~0KeKO=`nIaabXq@#k7$kCG(A@t;ofAX_`b$0 zBdc?U4y3qZzf(PP8Iw<(04WgfOtHq|w%{N8Nhi3U>JmRw5zE3BRFZ>F>8w4v1hl_D zXNfcQ)g7DyQBjD@wJ4K96bU7CjwvoD6}!Vn)|KRadDmXO4s<*{In`4>^ZV48je+p* zZ#Q-hG-kLs{}0ud%3w9j;GWWanRbSc$J{z6$odyz-ad?!Zdo}D9>2~Uq2dhb2i2Y48eSiXTOdm0)$=39=DW<>9SKl`1Y4*)_?Z z3M0rHFXi1!EaI>}& z&tef_*tRBcOvw-5n02TTx4=MXR0eUm1sl~{Z!k&W57Y6Jvr)#un6%t=k%MvhE7v@gF%r*HnEnbYGR$#Zq1&0K6a#w z${Xg^kDF|B>pi0xZL`#=+WA;KW$bk9bNS0b7u#rxJH6KU%F!nlqgHjEdeOT~7lEQ= zcqYspfOR@Ijk<<8BN-{+X3d`f|o2w)A-;rJJTUDrnv37GH|q`;D;iS#*WP#*KFqu`V5-PewiC zdxWDdPH;lo!SjhS9^F8l*ggYwjk@PI`A*ktoujL^OkkOD26C|n?$f&712^^`DxswW z+L|B1CD-^S9A#stH??0ZHa<|?$C7{d{8MRiUBH49-4O(TMjm z{};V2Kh*YD^$T%{@ogNs-$o#*sAeedbxopgkj>>h(zu&R}A#1>GWg|m=M0$;UegMsO?1eqT=a8xH0lTC4Ey= zuPFpy*!1+mjSuh?@8he!jkaJRO%lPpZjt(?+A83k zDet9*1&U_Lc}dkTY`;mjy2j4abrgo~DTLMuV{Kf-;W*givjjoXDo^IPzsg#(a3H#T z{~n`mer=e`Wzz_Xfm1G!{PU*`N=T0y21M?sbEW?M!+i46twUYvT&{TT;f>b4){wef zoYs6Q`s(Z2LK^_0&ku+;yjV|%y`9*}W5`cVQN-iN<(nb8w@^$+A|LW+Rb~5_m3GnE zvr?RO%LoV1KV=r8=(_|*wW5Uw@W#V^sP_t@?%hM24nca{FL_ zJ`NkwVn(uc4g&S9XS;|t*%i+Tx!!BLjRR<$u5zP6cnB)0bShheOD=pK0`I3bJd2ow zHU_>AQXxq;V|467GVcT_HQ2jh)JohXPr~?DUs8Xmvhvk+9&lY0jwbzl)TD)Yzio`v z_PKH57B49>`{wP92+tqxB?5Qe*m-?{jL0+8C)fEg)%higa@?qBef>kV(QOQ7z0zag z!$Omcjqj0sL)?ty-*)ethKnxo32a2zs;Ok=R&&%=;HnVNvQ-%nh7NL^u1GoclSSQf zTYnammnRQDinUcMWw*53?#=169aj+e1MGAcvyfd6eO%(RvCki@t z-vEYf2fq-g(coKOWoH;+RUCuxfWz4oK}<4&;iElp$c%q6=(_;t2erif^C=l0koZYtLW|QPx7`NCuK1uRHcz99(sygjeL@Dw>*<(m8GxA({ ztNaM4dd)aUb&3;m_wub`-*MbpJ*Y$^9EL-c7h3}k2HN3C=J{?&g)P@Ds^4zByGcs9 zC;{eM=Fa4YPp@8OLq1OWN5RLkp~IueW2d#8W;ucQpGdN{S$FF4l1urIMx+Bp2e}PK zcEzh{mmgKGmK4X2fedm2R9rL<-b88tQ7(?H4r^6_kx3zRp@}Fy$bC)P)hdWf^|%6; zBCLNkbVH_VYg-yrj6#<2>Wlkke|?v=6IudoUEC%b$O5;t@=%>`H(;=iIdYAdysa^b z8YtC{yW;nievWH=Q{{E)wc0;lVi$bmnseH|rAPzSA5U~%^ue7uS_ z;u_Wg`Udy*r-gs7==kB;(^5Znt!B>r+Z5w#DwPTL)`gR$`UVkMy}DMKNCjSjO%m4W z3TUZLbWh3qA_zFX^^<07hr5g??jS=fM(dA_hKnIAJOxs;hD9jU9ibzDRzGS9 z3S%GDh-Yv=I!!~IW43_oRt4mBF@;OEYAwjpfL~5v#YLAIDuVYUkt6Bqm&WZ@=M6f& z&}BBbXm-*RVM|ZkK}?#JCemaGRcmdt{=w=Zt??N%#-;0lqc=xbP>CqMgGOuG*8PH8 zT=3^Yt4jk_N-3rVLH1DDoNFlsq$1YfZVVi*oWwayxQ+0m{!EwS)(rqA835P-TPVi{ zIp501eJG={o#M$n)FNY@V{BWNu{f9Xg3M@=Kam83xt8r}=Ln3}n4QHliytQtuh5YQ z&8IE>-Nnzq4b^ovUMC}UI{{~a!jW;RWn5_oBW_pYqQMoJO4lB+2$MPW{O8o_VS;b> z>6jJH5ygeLar{`pDkIUg^GffxOpP4knPS9(hzmYvp?B_Le1~h1eEqn$FD0i(zC!rC zwm)KC(dz@JepQpm-*Hatm;R&M`b@>Vn|5VifWA8={Lxt8E=HyT4UL$W1!w^#IdOUW zX$sLK_S$f;I$gWf4Dqb7f0gM=@k`W)7;UUH2y3g9aX(|n;N$6&>y1Jq zX2YXkxZHMHg$~HKy*8Uimj_9Yldnt``+5QMzn88P=Y!O`Rd^INj|wHj`iV|$j?PnU zh5g!Pp@o+D@HR&J{ean5P``@@IS%<#x~M=q*Hcu`pO;e?8Lryt$g8NpQ?Zo78&}E1 zR=>tjj7jdrOximv%l!ubSJXy$=h8&R=O?D>LsNg)zkt4}&I>t};l5J_KE7n3H%NCl zT_1VDDOaO2)t<4Gje!tzH4LuS=UTGs=nAd`Bf|KDtcm2x{(p zkJ13zzl@Vo{Z!;mOItiArQi0*@}*s@&0fIXaV|fHrskTYiVOa+hv5u-n7wdSG|XfvUQAKyr`K@dNgp zn58RssKW<1p8)4TaBsxM-&&EXs`kBqD@gvQaN^=2$-@7|UClsDRRnwHI9KG%6edSO zeH8fah4%hof<_p`y?rG)R^f$1617wNZ2rs0(@r;isL@qpP5LFp%_C2^P^STQ?(}fy z*y3m}gA-T2)LA?ZjxQ-U^`?$(>W=u3aOFHi_1gYfhx8yd=kkqx7(F48##qB!A*+og zqAe7I6+9|h{`E>okMR!u^lfo}?1B%M_?$Ce_pfww zO{Nt4`Tb>{3Mv8m=CHT6e{Jo?lD$)6=7j|q{z~SoY zj}y%9{9eD^Jrx5%ZNDN7pM!%ev$jNW?q$gZ89od1EfsPbd);$w%eq6UDLb z&HJ9wz;F8rcqTK3Bkx(TwG!7ft4D#yHMOmlvmZyt^JC^5@2yCW#!@gkes&eXz{O=36_y zY*73<78b{M*rr`uF}rJfGGrEgX#W^js^8`{RKfNFaCYP7f+Iw!0<(0pVa2l5y{N&E z_||lVwx8Vjii5)&@&1(?%c38TsKqhtmgu;+kixBF=kZ@$>F;(#$7CL^NW_Rsm^7*u zV(fCG$3#8xbFbvbaqWMjE*(??o_F2mHyTW0q*`=awGePqY( z+Yc`Nit)5vIqyjK%C^=s44nDq$!2hTCbo~LAyVzb!8gGO$oa6?_!qBc;|Vspc~nV)B^v#Og!?K+1TarXEy3xKtsINLycAx zF2aH2G#{XzQ)nYI;jH_-*tDP_%7>z9h8hU*R;^-vzK05ui1g%^I!EZO;cVQ@TQ`+c$|Ki+mUg2q`EP+fXh9b{JSjGwyqSj~Ec=xf z7SviQtB@8Q?`Z{`fz_=x*~9P%_o^5032QY-8$WY;J8nE$Mh^NC_vIjbOEYfnV|UAm z4ge=L6P;CRYzwPn1I=QQhUa#xvv6Z=n=4sHOXT&aHZ!`I-~no0FmvFGw|qWDRuFgA zaJB4NL|`C1A&@OATN5nJQ&tA|&{kpq@0O(A@-F*KL(xWVi@i#KC%?bL48Oe3=^8 z++mS|m>c-1`O7W39D#1c5BB($7o->N_wnEqb9?SgH5V?Rl}`Aq>`p?zqT}{BQ{9nf z>5efe`L9)&5&4Rs-GwPFlHEAxEW%r%t1^uh_1ooq5{T z=@n1@t=o0o!mPJ3C<^SHFQM`#Xs8dKn%x_Lou`4FVWsIPNnlAT{@ui zov#dM?7VhxhqHho4;zI&jyN*5;6}H=CIypPL`*5%XGCA>4J0*kO5VfIDbOcN!gjs) z*iN~vv^DZkp>KLIp$e8`_miC&*zWYBqYL6};>Lu3EDK{eQXA@v2zh&$7ljZR*}zA2 zQC+WgEc=Hxowa(yK@0RgV%)XNK5K+BT={gV5{BH7`XcN$8vfO9$#vLEH|2Nu59Ezq z;Z3*Ud9Wwn!fE%>W|mR)5ZFlptFF^6<+<$8XN~aQFLfP}CFV71x%8WZf*88d!fVxn25-ur4#wet4zFt;sf%EU z>bay$=WBkifLYgxhEyzM=X&QexTNeH*3)YG>9~5Q5x3F7^7Lpd97`_)~2~+8XW*G9~ zTu__)x1*m$wu^Us-Dg4^^aJ3^>}EYAII-UO{Y=OeKN7=)(1copP8nBCUEe%6`3Y?O zw)-EQ(xKvbI!`?n(Cw!=0|h82`}sabRoaO%IDzzzy>BKm=<~b>?QEa@o6vz!)1ltw2gCe!)G+ zyS%EtR#{fF#D3B`2+9a{BdFl*QyV`h@nvqPJUhgk3iWj>lX^`UJSg+*ZYeCjQ?(H@ zT6CtFqtjwmX2=FIvo6%KhNt+`;v7H%#`6!%yVc!B=VH=_0M4Q_d}3-vO&hwk;(u#$ zWOOyU#S;}7)tF`D`B_YM)O_^9-whh_a|Kt;cpsyqZY9^~*6BEQfi<*Qf<8J4_1Pki zr_QU=zo?s@14&z8^$XtiUje1!okFAvO~-oI;?xh&Kgvwm1guByIWxtR6uPJ>64MeR zniaku9v?6XPIqXF8z~t&axMc}_V3P_7q1-IPJw&}N;^*yf~GQAcFv>F#avQ?7rrxM zWTzz?&-TOCH%8{08hR|7SeoG$kIWxNxecWF(1`up`&QaM<|)T77Fr;VeSz0^*!Ko9 z723GJ&w{|cj;Y;gyWKF-nS@h7FMrXZf4skA!JezJ()D=A^yOzdS zHBUz7xku^?wkubcs?;jO>&7>BYM&rAHzoiKUw_>hOJ6GRWp>d*E5R}k#mJw|awn{W z=(HsfNehbR`;vV?K*B$~z3i5^z928Zo!p^8I(8@u`n?)guIspL08W2I(S9<#P*STP#IKVvA}=))HDc0KGd7jhAAJ?if)*vZV;T&6Fu=G= z;7n9sk^hO@9?&1zl~e!BE9{3`Q5S^dT0TX2cf0+)=c>caD{QUY1SiY>JO2>23 zN-7-XhxPk&an9~mrmm51SIWt_2lR6pZ1i27=|4kzGViFgaIn8WfP=^1l$Q-UdtMa$ zkq#98U8ZTB4p8uTFePF8DKv#;Z{qMrT}@+Np-;>CO_gC*)y|BBOFKQdNL`GWSYEFm z)pR}M6qX!m5an!TMzas9Wd?5WsvR)v+=w5MHWl`qh}MZ~iA;=_FZJCdp(m~q^AHKL z>lI83`Ds7qwD1zgB5~nEha(6^DRCAn;u8!L&hgL2ZYuiajgx_94@n+ZH-TFKYBdMN z6?rL=sQK7g`jc0a*~P7A4T62JD?vXKAKgMdOaA6QTJj};#eOzlQxw21V!9)paml_$Jlw_gxp=>kau>$=bqaG6m~gZEAL*b!{ivMH60v1Z8n@qjK0&5|!2 zvu}#k^(PI8G@Bq*hONhLD<&bk(tXrB?#@-hILeGf)-)3~H0_z9+VsmO0_wSp@PV;Q zihV`Mg`V004`}~6u4~L4F8InFv&efAuhV-%0UaJs%QUEN=$oQC`ga2#vp(PF^J)ZV z76DIrZ9elTXi`jL7qlrvQ{k zpZ8lw!Tr7(oK(8pxAfb67QY?W7lFGkUwXQouTszd{o_u0_oJ8JtR1{F-9-0qbw4^` zzi%m?4I{J@&-MR#LFb}Vk&@r5cbtv2bN!dBh)5R=&}06zIP@GSPPO-{VFv2oPMRp` z)mXF+C*-Qt`2?En(;2F1fAAiZcLIJ6_o#ap4fKr5Vp`9#wVUTNutp`cp6#mwm0a(4 zDVNiKE^3fIv*+vmPW;4;&F0;=c=fv19}&QqRxFXiMw)LzE)%qJSZZWBkY>WXRTIxdG<7cNu9 zNvq;bjdtAs?n(Z?@;<+R3VW3PSKjbndM`+Bzi+OH`nLn6CiVI#nFumAY=u9~9J$Mm zhCI1oVNtOHyRjN<*{?#4^0FGBa0l!pN5^NAk!6bExa2O1qZVg!nms$*sO=4iAOZwc_pfZ9}w>+w3n%$Hy zu|XWq`dp_g@JOwpPa?KTHR^7mT*Uu>*W@&QdviNGG$SLi(*J$3n6K-C?^-w3YHQT{ z$T4{`7fqZE-7?TYXjb6|z}=-PZ+yCXyPV#XRAz&cuf^3Q>y&9UI2}?;FSXUfr6x^N@zh+PzP(J07YT@Veu5T*E$SzGov@=_x_#14JPOHb0$Uo;+qU=CGAh;YRmhX; z)xQN96L6-Q)rs;O;fKT#f1JT|oq+8ytnh90Op z?Wv1)JBvtxsjTyv~xoF`8+mqkiQhDLq$Ve#6&pjj%u?37FDr^L2>6OKI-50|?@{TTGJkK&R z_oa(kyN=GbmDJEo7;-wgW86dIG%C&JY2M7ourIqFR<3w#`bUQ0S4s)>fM}i;ZeEmN z6ryN5bI51c?q_-P)C&;qOHuhVe(wg4iZhAWVPV!ToLUQ7Icf)T@JC)%>=8D_Ls0?4 z^}-1$E8E|Ws^=7@_v3GySM$gF5ee^Mar?82YMZbG%{znMI{=*{zefb#ev4gRph%vd zdB1UnEDqNMi3FXWH&&@PW`Ye1#n2BouaDEi!6MG-IjPMS2w}vAAII52CC3=;(j|@@ ztSn%nt#*aQH7}EWt|4gYukRj;K8+qB-f-dEJrL~Us-!%aDMq-`$(l{uTW8G)+DH`|6l`}F-02*@UABe z@|jsCw*!>&`N!LW*NyErG1YU=T24ksRM`~9q2h!`BJ0sGV7o$Fz{;@*e6?Zbt zm^dFh>kQwLy`mdg-^f4jl5|IliAsrZop5|Tz{m=4N)BWM1Zr3*?kMI*T4E9Ki#4u^ z_8y$2$4gCKYO|18UjEq}j@W%1L5*)^WFXbqM>@_h>>i3(Sgl_WOAI4`+X3PhdSl> zpx|taQMk848h0GG^qF0}GX(K6A3gdz#INUwyTSTA(E&9<3NJUZhqWM)4@HTj;^iZ% zo$m}}s?hvBpU<2A>$_-?^QyUa@vA8=gw6a6vj1hg_LY5!1|AWOpIn)*oPt)Iyp{9j ztieDU6_Z_f+Qim)fY*S)`}f5WZ8#>vz|H z5Oia&i&3<90C0EbO2Hw%7M0*NkaDG#!!Y_ri^)A3>$BloA^@x`4**t3LOHZ6#}3=5 zlBBVd52>VJ^Cht8c;tR;zGgdaq(dCaJ9b%dqSqpIO1j*ysT9qI^JmeS9KEMIq-hp3 zgxK7Y{nD8Nd@wmroVXiiGjAJMQwsiIuKMjoA5W=MgCl{CJ@oJgLF`jmH>s{PsiowA zPPyT6YdOCkCmz(Yf5z4?W%RGfBVs28(l+B7ss~%;u$ds4Wo?yF*c97#W#q}bgIUv3 z8WKI%OK^Ms>Q&}LPTo;zsPuZH$=-N?;k%D1uzNn4ZTow8g)e!m>r?v3OHvrj(5?q_ zug9u(k?S+D_LRJ7B3TgSap2w|pa!#ZO3DiRG@f^dff8Y(*9OcZHJx|QA4o)!HLl!^ zw=J18;6LFTfpkQFQ;wc5SHg<5n>|c`>br@_N^D@kmNjvj-6&($543a&h{Jo<&8S~= zwyT;NuN$v7*se+Jhd){=S0M%pZv;5S(0{PdRr=wBkxw^(-gAi{U#RQswv5QXTpk2_ zN@MAk>*Rfx_{8*=36i1;&!r91d4|%L!c=Q!uU*dk1CENx@m43054t$P0B=l`wn zb>I$rM|%_}ePCr9>!BWa-X^LyF+^SAQ&@naLEoBj8n+Z;0s1&Z4&Uyq-#1r(UwPS- ze~fc&nBVUYCxKu^i<~t*`NT%QLO!Bb$oJ<774h?&_&=@Om-Z(mJ{Owt2Lf+I9km@{ z1+ssAXTHYq=T@-lhIjNpvag$rN5j0T#eARf>)eWsz}_wqfpvI^`1vX}<^T+OS*oy7 zrAz-_gMZim2QW#e_s&-CqqMvG2bOlPcG{Y39h>rFted_C)egHW^lM2~jcZs*M6}k7 zbH}nXB$&$Kekwj^;VOa1miiEzYtBNWbgfTUH#H*aX7DR*^xlyx-nFVNN0s^~F3kTD z$ggz~BM-VCE@z%%`d6L_0}9n&f>o3%pFFS0zj2M%?OP{Na3bG%2%3gENT2ZC`8JOY zvhg8!J|1$bY+D@LJ}{bvU8=S;*M!9u1~(Opc0A~wOlEO8nbh4(pHb9)v}zzT^RUt` z)30Vc_%ami_I|mk?XM6jFZ5t_%KD;;Uk>mRAFK2Ax$Bl-lpj4Br+RF4baB~ z%SbmBpZxefj#RZywr!(YGW{unJ^v#~niu)wbXnxz~BGr z?aB&2niZE{FJc!nQpI9`g7y##ue8Vnb1qqoCTQ}S+5*}cwjc5t3BPm9q{ZjJ4n;9r z5btHxwa>sJ6J{3o)hsJoDX%hvPH3LA> z-B!02(VGU5rx9Skw|2lisRU3~_YzMcWI)33E&fVlLAG|q^RWb5Q1pkb5r^Wf7!q+$ zd%_4|T;4)K@jE^v@@LJ;pa=)XT;CR*ubeMYn7;p>7z1)+IP(uDEa=+O?8w2EBc9g4c`lL_fLq#kOs4e5gT6`r#Mas400iwo@aWq$&4V z5mHt$qKOpcX0-2+l-J;HGP82tcX2Z{5*H^t#a9HL;r}{dcP4JfJO5^9&)xWg*PVce z;ryYc=8R8JS#gd>$tUj9-#yR`=fKaebqCthYJC65yI>S35hp9B?kK-M%uI(k>5oQ_ zE#m6CSb^nkRgdn4==74$KkI+Yti0C}rRo&fsPe+9t=!)j%)#{&>e#{tfYTqO>v4TJ z2}+jLr3HsOXBXX^^2fXxZbRMqc^f2@qSlD@h{?f$%{~5(oL+1dYb&5_Exk03%M{{l z&w^Tki%3vzZ9ZjWqk^bdQ~POmdH0mL8Y^m9fGU2l9lKv`ho7Rqi~8Udtuy_4KC}9r z(m-8MQ%g|A#P}q0c{3+ai)j2b%hM+V*OFVsu!6@ri~UPWLXs05FGE$bY)|8SYlOlZu#fS#Gb`3O=`U-UHfJ2 zeT^D4YS{W(k)+1J#mtDj1XadEQmvP1@3OlCn9GSgL_Uh}z<73!kOU0{+-?I$`>ou! zqb!RnUd)ZOG9blSC3^q{=U!zu^4PEX9z<#EL6X5 z{gfG~p3QNqeY}jhUg_>{`;n0Ja%`hVD-SpmfzNa#O5}Vv>DsBSTaIUgmB*)!JWm_{dJ*2+&an(eXcZl58Wp_{&7WqSdjOv>w3h^tx>R+y{mTy{JxsF0{}$lP@dl&oG;MwiXd*VvTNFRHn4GmGJt99Q)RaZsMzx{3RpNL& z-4xdS8cdHj_O!kfBC^;~ViLc+8q}2okt2QLs;XZZCqcO2x@~0VIkeG>>7TIsJ}5B# zl!pdN{d=qf#MeyplKe@#M2@|Km&ggkT&(+~s_@ni<#?Ie0ayNZPsg?6OI;86K*9dA zJOgqDR&68K!V_aKojXRA#d{Hj%gXBBdcL|2T}_v>ei1z>!esQt5HqkYQmNT*;_}Xy zWo4ZNWIW+BP9nwRyB>&+Uxvi#G5;m%=NrJs#)yo|=yN~Sb$hcjbF9`WLd7SlF{5_( z&U+i*a|P(creDK#Q0?zZp8@pMig1Zndgd=@KJiY?$q=c%e0X?^W`0H|>vI|=Q7iyq z=f*st|MDM??EmU3eu=*S)g}M4^%O*TpeW*CEAQy<6C*sfB|53Huc4X+b;ectc~-ki zZ-pWi)|X3&hAmt2a9C@9)E3z0NA(tNGi99Txuw-48hDPQ@4NSX!@%CMkIHu9!LmOM z#|pV-$Q>;;+D?VPp^5Er%z$ZOsN9NZj7TX zOU@39ZGqBN7nd88d7wY=Pe!vZH=a08d3f0Sq4P`wL{6vK1M#EHrE6Ct&p6z3 zzz2M+S7N!@JU%!X00^?mFD;`yC+{^4S@%L)JoPO#!c^OvN27CFz!wN*kcyNuA+RDQ zrvaFJzadYwyhObNJ%!$o4Li~Mrh%9G8Gk`jrfHG<9DdtNNB9YsnK6S8C2H7X#q87e zx_(Dq!;fygC(obsD|5~c`~`Gdhi}B0_Wjl=$A5hk15tS7F2)w$gdwi4=(S2l4vl>J zxQemhGpcy9-B~rFvx_cUQGb1RZj^IdP%y`eG{W`or!OI1r1wOtKj| z^eWnjf>L9@<04K2vBXd|+#MQ}e#zxuu=$QNMlj z;&i$;(kq|cvQH-Eu>qU)Q*0|AyBmaua7PXeD#4L!8S0?jW#+dHBh`|;QrYRItBXRd zb{?05K5qJ^SR$l-YW(XvM-i3Jvf3%jAKLaB1W1l7k3DMyv;((RVfu!+I!@F!e|n$t zx(pK5s4rXXRpO=OUo~fa_B@MSDf+p1R*uW@k*-)LQ(ul-+0jj2$V2>D8KEDU0pB!=K@@^C3?|4k6gVxu725QX0e_tk`QWPxq5CXDz(k*w59!4>2X<{ z4Py%!js3m5-ejP=kzc+6m*23drZjkLqs0~gkX3`oE2i;RIa<fxzAjZ#J$}%9**Y1C4RClY)O*MG-_$>g)yV!7za^?hZ5L z&jh7XQEq!P%ks&Y1+E#7n@$pX{UJ4%kw_U060~I5LY5rjcXCT4<{tq;q;gF}P%A?>+5VAWUJV zcy0S2dv)cj5MUCTbzK!Y-Bwto@wi4T&^qn9S<_K4%t9B6T5@#fY+49A@V4k+Xd zj@uM)HU&{}I4}GB)^DwMy?;IHdEfIq|8c{8^AD_bfA8yiU7zb7r>&5^>kDvWV39tq z50`^KUMI~#S@@nE&N z;mwk~E0c%2cIsszt+yR9fbt!OF!TW^3AMu|zj*tLfDN--_YJMwBrCr9HA*oxbOI6& z+%~y~c>qn>6Gw5hXm(WCL!%@K$3x?;ob@4^k^Do{BGWPs!!bYSd>NK|FUBYFHBijz zJnWTinyKsydQT=2@YuELZjn6;fGpd_y&ZT`%ZfMHbLtqs@j%U~MuW9*iJd0{>~N2s ziE*Kedh@uSLg;8Ms!Qjo=NF$)B%qs^d%Ev)3+5FG^WGw;?>$hK56$9D8_z1FskRq#8{jhOoy+<6y+Z#5d&3xGf~iDy|d$*j`1L^SSd zp;p7O#m`WnnSWV)i^Xq?jv$r6v#(G3dUe=5XrT@N)dwfiIebJ*m%V@Wy(lTk`^?LW z@-dtkwaeTjXbR>pdVBIvdVz=+@}Iqj|Ci6BiEkGC`Cq>J|L($5P%OBR@57B{%~1Ri zpStMD??(cOq1%`~{fNq-=HRTfDZgEv7uV-xvV7FGs#72C{n_#S=!_ipddou4T|57m zYj>m&n1E0n!Ib)Y?hjoc*(=o8}*0$hjx%;3hAp9j3Nyb5fVzn!V@ zFvP;~Hhfd)kfr=!%^KuL9SfFQ?o}+LX*!$5-oSgzUrJt#LH^tw-YE^Lg@!8kA2U+# z(Y;P7Qms_Lf0VshgC%)8q^Uby_IYz_s2P_u3ad3sY5U;35hhI{XK7bmW>r3mq#(k~ zQkqYfUK$&ysI0q=kxeEerVL#x>aG*)hSq|u9&IC%^++2CU}i-1z8$Mf`Py@f4ZlFA zww~Vt;*~>HJ>Y>xT4T2l(1{Oer-{bsNl1q@JJ~?%s`WhNOiu5X;5N}N3W{bMCw>mn zMikW^=pOgSU$k4^-MV=mxA|-w(d3bpZ=|m)?h~s^BT+^$$u;j|u@4b79CLYzZXw%n1LKwhc^eD-Ds34!OYUlkDdgWA5QcsgMBWl^W0ztziZ*nc@jwhd z?|`efJRcO=7l!H%ROi2mk^AT0K(ng`X@|!1CHv;~oo&NCH-4XuoQ0MR!Q~_k@g*Uv z?3pKrk-?P7udP58Gw$%g)+E9OR6*Yzo!Al=J-i&4M7^Sj}w_P{h7|+n*+7R z|1obw_p7z3XulZwZY=2bL!T4dxf|wM6B%Cpv$RB%g5t-8!V3oVHP)E+Yl}!T=yZVP zn)FwfFYBN7W;PCS#dgE%uzD;07;IdGx*m)?9FH_M_bzl-+@qbXn%ei>Ak74AM$sA0Zrm;@g1rQ*>^4Myz*q9?>zj(#6kmq)ZEVi+TnKA zJ%IEaXNN4=Ib-aE;f82tfSf8(V2OqUYcPgu{i()xZsL9$X}WCKAs6X8*{oZhR{j;( zkTtk&L*fFYb_#C|dLK8IC0$s6$r;b0w;R=$>$991h_G`Q4ECY(WMzw)%kv$qUS!}F zN*c(rc$ zO`6w2mUoSVJRD20 z_UAp&)f(Qe6Kmg6UycmAjne|om3Q_ASz1m#{<&L`Y%0;^7(cFvJXl0>3&wwDRzb?l z{mrxYEmPr5BXhxm5-P+$eR@k{D+QYH&aw^9U_oBa1QZ(3n$aPvno~V8`$`@CV`1;8 z?N|12CSi{=t#hQG@vOfK)E;xWDR}cL*5Qn1XMv)|BL~6{C2>S3K%U`@!%?~ocA~{3 zk$mKv#E*nmE){QPVCXgYpm*>rE)Qgi6a~R8W8afO(ZUIt)-vgx^ip>bEq-KgcVa~t z2#eZggDi%8)G>YHY4VGfX$G__v>iohbJYm$j)ve=y6>O|O(H6nrbFD-8&LX#2m2Kn z{$-3|NbC)ZdbHKp9NIlGHth$`V}549L5*fzVOikidBOFWj*rBeL~ySo)hJH#1+}Uo z(_M+0TGG=d5-4mXz7kGYt0HrLNVg8G2(PL?4jmmNf>5>`XA8B*7LHnY#oP#&e(Db%v8Zj9fJYBrL$^2@&@C2Dy&@DlDBAx;~C%WV`GZB-qJUv|j_HOujc!>2QlT zDln2SK|P&GxXElsM8q>`zE*0=6|2_FCUVL-bR@Q?qyx#7Qh_4h_U zvz8f6M;6;A6?`rvpr%`=^<+6GsA)YxwFO?g7D4|sHH5vx+1cecFTg{~r@^L$Gmq!6 zt{kLLe~mfa?NJ#dM}B8*qD((JV>Sn|Uaj+QIc}0Q4=x+Oy%v@|h*?n9`~-l#%vgWz z00m@5ly5z4w47u@O2afM*#aoBn&`~#x!t3roIU%j?U`U8x>s5(c)&DEn-TiTMM7=q z12-mgD74jJf;BxmriPiwpOpeKY3CZlA5`YhE-VZ3-aoj>^LcEIEw@G8sxJDFWnEwn zsCe+TUj>SH1(eZ#%Dc7dT&dJCls#Kep^L0VdZH~hq%R<4eRAqELONWQC6&igX4SN~ zS^7#{-5d9{EE2^+pP4N(#XSLjul%Tzp84CY`A%San4L8!p!|j$l>tkx$=r~xyBlCbCbmLm~mY3jKphXeEYS-x_touTW>|~*EXVu-ruj=TQJ)BXNB(Y>uiK^hVIq?KT^~g$8LQhUQcwRAX8tt`R_aIOnqme-*gz*;gA%2 znnK#LbozCJNM?oRh2nEU+Wq$N6@vrZ@%@_BQUZ?N6pKIhT-9t%r^d|C5M17^jqx31 z{|QLzoZfMI&KSIhs{tRcok_RzAMSlDi3A znZx+QO@80oh=p_czTvUPE6u?NCm}j){cvXR;|ZaOad&Ysr5X(Xg*{QWinM9iKiJUT z=O4$K!w1&(J_sjn?JE%{qPT!)Ou*bw46Kzs@?mN3Tw}TizNQS4XC3p{uPP0qKN1-c zts5v{oNERwe=hddL*TlEsqw!>*2Flq_h-v&B-q=|MAe3_qW#q@l{&!$VqV4QN_8;L zr0kI1c1AFVjs7uWivP}+5+nlJzS&H>h~A7?)mL?>0m(w#_aPJeLGRi68|l~w2kL1@ z>fm|dx!9HXq~mtaV$YIgis{|)kXHGM%mp9avxBO?#p^WQ#|J#>^O(zb=*47O$c}AX z_=mZgbUxJh#6_6q71n8{jkG91nYl>{9+aP$n9i**3FRF07@V^`Xvz0i4btZqwvQ~Z zH=Fb933LihVS*y}s7b~s2F2W-h$hCbMqn8Bu>Q>eUi07w$8bX1wVRY<77|HX88WCM@?G#V$&a)a|46z`EtSg=o|Ppxpw1#G2SHtJwXNNLVVZcY z_{zHN=YUsWot-*rnpx$tYr10f9nx)9vXnv;?L$ZW)MfVCnlkUxlE4ty=D?3F*o@pF z#knaMw<0#K_%hsmhGIg;T$|d~I8mv08>!eC9^sMRM?WQW1 zXjZHKRLd(#%CsGFJSbjS&j3dZ@Eurpc-8RZk3W1sH9@l#qTN@7!2c?b3 zhO(Ej0F|tv$zPw0&ERWRwUW=M2Oq$t^k<1D!+lcM4Ybij2&~Q4Y0CG;csLN~ZV)|T zi(eJR1v;6JL?XbSp%nI=C*rZpcA#H zORP##gZy<|!GR&IkJ=dOE*0wN;#AOJENJ{|XluJ>z%R@`PdF2Oy+35pv}VRmZC+Nh zetd05kz(Oit@uuLQ^n=_F3{#_^AkMh;=O_~HLX>(a)X7i7KXZJ6f(u!aS`Fc_=Mq# zW!Ceq0ereuJiSiuDrWZN#$2{I7n~~%we3+;^d}GpLcK={0aZW(KfUiN2=7H$3_hrt zU(Aws#s&J7$>A%1rHfyN)~7iWL^c*|2PKiOCntN6PRjMS6S&8VJLawc2Q;zY z>9ysTlN!N|$h9L?2`RUS{B2^cWD$H2tp>pwl8LsikzmbugJ824Ia6_{8w-3|Hz?#SV>^_NGQ#e?3X!^>fp z^!hPe?5s!R>hayc!c)9WF5`D@vp-@48H?|EIC)&F2I8%}ATny3VX|EGV9u~fpch%q zZqm&4;-WRXiQBO0sEuC>yW7GQ+B=M_=vC+1MOwqJZc_H2#T>k&4OCBu^mf>2yG8Gg z$ZDksg0wPKqx-vK6k@jvRi-*&Ba%k0Q;$$1-Cs;q4g5>W>klQoTMw@r%uhOmMPz_6 zmBn!B#G@i;oaop5!|blDcSUNYy;-zQ-kP;7%3J$#a1AWzig|kUefdRmxhk5=wB6;; zE3Ft{dT4qF`|Uy&06u8vnP#CB!&cL`o{!hnybZniK?3if!o|R4C0DcIZj};YqvQf| zec40R&l0@3kUZV?+;7TGm@)?rIdYv-Yqwa?=Qu`mFU|0g?7r<^H46cFUYi(Sd-`C! z{1z8;LLQt_c;3?r2Yt1+I5dTekMOad-%tv6{#ND${qFH=-&56uI|#z#+JF!5>r`#* zLPYX>s=pCH#h$aEYVAKVrsY)Yf4w5R@m;@f1f$FfE!Pt4VZtW^#HLOj2XBK3rkR6@p z#HhHsgQv%uE~n*fr>`@p?#PiP9h9hg?wm}!&xbi9nb?LXe0qRNG%b7{IOaL-A5gz! zuv6v%2z}|$j5cD(DLoFK*X^-hL}-P$(%s*uUztR=aqXd)1B_9$0(OWF9pU8^u6gkH>8G?;y6?3;kl|JNZ4DLZj`TB?F(v${A?L zXH;J5KmVr63N;@%P%rY0gs!=%oAG65!_$QL&pL)@MNL7gI)EEJstuL4th1{-w7qIP zg-lo7aaD6*RJebet2ungs90NF3wB&sVsGPGywam+Yqy3t*v^WtOjfipC@BaAQ zT9T_gxmA=8lmj%2$Iu{A8NseDmq|6rnp2d!wEku9nK@3ORL9n6j#=k`dVN6Ng z@4e4*T-08J$BVi}D2XGbOyrCC8~xY6fc}Py$#ptwBBi$E6^(fhh z(VdoBFg@yP5vAK>5*K*&b+_qSxPRp(BBq-#!RchfBlETTRpz~veIHLc-g&$U$}M9n z2f^0^=nst(Fymn!G$-Atk3xR{JjfERdT%HOa4Tco7|GHOX)(Gn(&zI-nvq2ENB^w- zD&htEc5}l~MlqjzbGgJCmS5^>?5q1y>n2X;R)1>$mF((K3}=#`gA}M&?ra}z{6AoC!xC{_-4K5ToMwuae7CF9{!0bx`>1u?%9 zguaANY}-!g>7MDRkNW3+d4oU)tIQDjDI{@WrGPpPr0Ida)S=F_P+q1myc2r;8 zYbihWjwbdVfUZCw}9J%F%3()&b4Qk$Q)vN{xQbR{Y&iOT5`3dDfB zW1@VE2I?!*Z=w6f6sB&sao%Yo{o0U-GxaQw&J2Af8UQH z&nTV)?%M9iwA|F@)Ahhy{7U)U?Hv4m&2aMXT&OfzNG}*YD4uPp+t7&?S;B{EW*Jy{ z;rd{45d}_0@B4MaRc&;UF!eT?GJCD2>!OV&u01{Hg#DkU?BtC5GyMXK#zz42;1Hi; z3>5gxzoin?;lP`-Se;IZ-u+&-wb%U44*`KA+^xZ-7i*Ec@#qWb?u=kou7%$OX}$(W zs4MwWJWq+BC!UJ<4M&qgSR12t7{g|cg zA~e3u-qW~k-s8yf-kyC;VDK2#oqpy-mYMPza^$9f)Q^ddfgSoj%3Vw_dffYP2S1S% z!V?a@_whGbYUV%t56zI=v8aCZxxaRfxG=NW=f(C>NNvSvpnZ>4MwYFR^N3e z|GZ}*@li@$btFZF$;Z2NCoLjo!s@x~OHH+*Bb-tZX}t2qpT|9&+pAMiJrH-r{n{S; z^{s$NYbOUrS#<)Kh9GY8>T=~>kh*d8ipKxlJpM>P>w3q}ssSJKxX9P*GLU9$*^38*DEu}LM zZhNt#5G0f2_wIhh%#SGVdOFdnwKWY@ouX=yiZu7Z>s+2IRmZ9?#lT0fI)veHd5oMC zoX04OI5J9}{_zW;)-r&tYK>vo)@yKf4M(D@rnWMdDeBe1AWHQ2?`X}`twK@OrR4gb z3X;M{$rBn9Pz#EgBcN~%Y8RQ`1Mu7aT zlbxs>oe#S&xDoKi7k=gATVlujiC#Yd(r@g{Txzl=m!OOe8xGu9DuY9g z7y5EAJphL^kAv%46E*2Cvid*gl^Y*?MJ4NWFQxayzpyB(au(#<$X5-FVcZ8k!Vk0l=pfhg%RC~L^sUMEXT2in< z<5qX~pcSe;FCn@UAcWlPCSKmYW|Q=3bzo;yi%v2Otg2yLypjJ&2Wgo~4>9Lld>$T! zC3YL@L)W)D*974ckd*vs=yEkEX>D_e^`5oi{C z)%zKVF*>1^J7-8IOCe>NL{T1EpN}sjch_*K`J4~Fr^FJmhiq%iC3L)GH*U0<3Yu@? zz3}qK3>R2@ErIJ}%WU+XGyAB`~r@qu;co-ynvXCh6 zmw_sv2abL;YNS&F8#$1Nqtk>jD9T)_vHm!duXDa&Ej4U7H*VSA)Ig%?coc0-Z_~Z> zwDsqS=Ep=;t7M6~KgL-R{wh9+SQ0STWlCnpUO^)jF=g!M(dcq0@^34Zc0_5sDMI8L zh-rWGN%N35Q;+jM0^*?;6>j$EDnzAy=(&}p&*>oKWPdW#RnoL^)J``X<6MANN+REW zTvMS|tr@RMI`Gix19AKCn%o5@yu2m%&4+CuQDv&b7csUmUax;6%f6Q!$t)w66QNLd z)Rj)xPbn4bPY=hh)jQ>B-Un-4%~SXlkk>!{!aAmN`oSShBx+=E3?kvd1c?mTVq7qH z-6^f7Oqs_|zReu(G|YUK20rV3CH<^>vatyGPok1$Ju05XIA1?MD3<}(bBh-=It2^ zK*VzY(7OI?{-5ip^N%hw%9j9>+wM=CXC4~OKrBj@1#-4n3-(_DhQj9fIXm|17pTj_ zU0?7UUz}e6sNa8FHPt7iVM{)A2p@H32v;PiR5U16FPDh6UN$3rM>b^0xb$VJ0+ac4al5?%`aYs&KpHkjM zv2VQ{i;i|5q+J{Jh-PBkkC^%R;uiwftqT@a2Ep{!-Sd{0nejHaX&xn6Dj6PbIM>ZY z#n*s^8c%G8@`rpJU^ttX*D2{C58D)xsxwjwt}AE(#2GHVgk9}Zvs8(-f0#)=R`Zyk zS}nocy{f$~ky>BPfhN(#uVox{PFcExVhXlvvMr^SSgsEQ!Io$C^AT9sa8T^+L5T#g1V?ZvMid0^!rnM~=sd6|7uB;YjtQO`|1a zSlfCJKYlMHh~NnSS& zh4EA$Hc2m`fNgz0WKX#dKjH1}#@Z~m1=g>TUGI}ztf{?I&zt;mpQ~v>@Em<2n|rwt-5{SB95c0{+8+hGF!eHFz9C7_f7qGp8mK*l~0QwZ@FeXO=Jg$J-iZ`?Yrh% zK3BR>%UO@~h+Qg}ahzG(nGg-ChjTVf6EaU?Gavt@g@Qpv1)pL!0ipPDthO3FQ zWz4&#uOV(4q|w~-xH3oW;s)VU8-AO{GbZ-5S!yf0sn#`A=76Ecu;2MfvBx^Q$);F+ z(%53qzZTK9a}@LlRXm5@kXzgD`7$0JMaEzsp#~rIj~1C1f&xQNHbD%?1UR{64LFtr zvE(S>!iV(Gfi&#G3R@IYaEf;$VEo24Hnt{lm8CuR2po?|-tj^iUU_;1x2#|M&%YH; zG>!%+(g`^GCkV{85M`XGS9rVHJ16AuGAp;c3Yqs}+`U>H^$O|eBB1#w*ad0k>Bwf} zDBI{+^|9X)UUXQAd1bb=d8TPG3Fa*5kpFWQ+whB@e^`^MEJF9TGtERJr4piCN(**O zl{*`^;U;*p@{(thqHl1JfLS%RjH6{|1O-o!r&|{IzVO?KNqVwq`hNOl6?>1i)HSulfrD4f2|H9`tu2jOTfmUSQiG+<&R&h*xDn#rxrrX|Zk# zBa=N*AMg6f=O_8?O-zy74TUCIQv=kdNSZPyTUQ=E7w9w&CEg$K zlI^-NI_nwM;&DO5FiJy3GYTUU-}>(p6bT7c@q-Z^(i(#awES^# z=MIf*4WHjOKOLNxcD$Kfj1s@E-=Z>@ZGojc^W+w&zq5YKkd4<-GpqElDpl-o^E!Pc zI+$Akd!j~*Ln)E}+v8ELrS`_)}R*=7&s) zlIHn`<1sS(t<-9E?)|p45LW1%^Z3u)Kkz|AJ%rBVRx>$Y?w-c&vKMVG&y8k z91tPp{+drTr857ronD42iCT0;9@Gf4#r(@i>|axgg6NYJ(SB7uP78WfAv1y?>erR` z5xtjRE7qdPyDDO~d+q-5@6?qA-tj2Q11Sw9`b&IrH*|?x@V!p z+444$Lv44cd1&p@gZg&&5W#L7yJ5nyF~>rTMNL05x<(ketJvldPl${N%(Qt`1LAj{ z0$wxqYeoLjm-DF`VE2$p@QPqR;5WOU<~4qs1D~5?g@$CYY<^nyn0cPonxrE?a_hQm z&5#Cj!LN6Lbn3LLc#8J;wKQrgVyE^QxM$AFzb7#s?5*e`63y)LD9&y-V9wyKj6R;tFkxs10Wfuxq;&d%B*RxcFee>l?w$@26K+#QPkL zf>j}aE@#I=)y0NN4y__7mp!=ytU08H_Js^|?D$$56;OSuo|E|T3ZkM#Lv+c>I z+)vV^s^7M#OL{QkyUT8ohxP$&tDklkYMuwk%$)3jO{F9GsV17dqoeVW)YZM6 zU8l4&rBjah8h)&r(E5zpGDl%6xZH6cLG=5A0gdUcs?GH=RZUp5H@@G`gmw!kb42Zd;5xQF>pQi!mI|NIDlLmrihX4FD~`ZMjrugLHE~eDsIC@^X_`H~Ip{!Hw=vzK-^bRW9^)+EYel z29+3=d5j2!55b2#^)GzLuk5*^^l;vR@o%_=6-mxY_>4i+D&gJO759JsZEzA>D_Btv zZLXOnWtoFJzsz6a7w3WO=LsX6iPCdz^dIjsmv%a8@<^Y0w9mhb4oY7?dRaK(BPv2# zO{mSj=)TGwo5R2HU_u)fmPzKMk>H)@p)1!ZUjj;+0eRugZJ^^Ib=Qx>2~b}0csMWA zUCJ0M$vn~xoUQ~D8N6kd>ABHyOOF-^&1pRlFdx`|yf1C990G~z=!;|RUps?%D5=PJ z@GL?3#kTWeM-8FIV_)pkaB0RmdubTyeLWi+Zg0n@*c8^{iRJICpLKGg~?FC5X+H!rnxP&Tyb8wq1IFlw0UCrm4he> zwo%JmuOX>?Hi<7}3PcM?Js}H;d2REQvVp&-B7x6dv#8F+G{W!{ZZHep*pDkDo#M3e z0YTj8hC94w#4kiEFGXJjlKN7@mT<0-v}FMDAo+ae*SZ6> zn;Dj;HsrgU`(!D|jZ-&bqahl*olGv3p`Bn5l_hIu7-I^yOijL?$Anq9OxMk4hxy>P z?40QJ5Q57hE^NfZ@~kT1gx(HS(_is6Dx{@BwOcjclym>*H3Zqh>rENSQoj@kaM0CJ zg{oB#tohklU2V75Z|YR`KwYBT1%`PlhQ8BEjH<3H&jElcU*xeo<83YuWOiGHW>$gw z!B1b!)(#LpJI~8^tFS|)pS>Cg875umBe@0y|ImIzBQqz54romQi5{^-yQ5Gmq9LQk zfSVY9!3!!vl!=5A5u!}wArZmafg)i=6rQ1q3hfww@ya?+5q_b%iHTC8=S9R+>0l8) zM700u>XdDLu$9%_InV;+sf$JR{yX8{c=4z7u9y$O> zj)6HkCx-Aw#6bNeS*I8w+xNC|Lac85L9Gv&jah73m)}TzxAu(gw)hZ=EivSwo{a^J z<>sVt9@L?~JCkhtLwb}(e3uVqAyE#wG9{-XQQF7lcgf$t{7%uUyfi!b^BB|adjG7B z*jhi2yOaeGX>;EXydTJvCjPQbRdW8TOz0_6DnB=*v!hF1XwGrQ9B{miEq}8!de`lc z)X)iVY;g?E#DqbUzFEH^jdb5^;B-|xuG^(~paKe;_CGK!9365Eq7-38pvD5n&N~UG zwA_6PPmfh4Al-&LKPBpjudm=@{qH^-e)5jUj#N638JCO>n{z2(#(y0WtiNQk@V-%L z2K`@L-rS&Vh5I6Z(->-Ry29Hfd~T#SqA)ZY?xw5jd!hSdrcV={ z>^yki)xbW{FjZE@w35B6@b}qBM#&VuP>)XBr}ILyd*1Fwv^xa5o-?5><2V1#++~j- zq8PS|e16W0sgOCR@4?nmHVm2M$(Y5Zmsieb_=QYf7Gg#DkbV=3kcd`l!fI^BRy*Nt zXHZrx=h%{;Yz!AFEJW9q=f+}>>Icn37C(h8A~&m|+S?T(XKk7cXC!Jn8FNJ&w}$gD z`|8VvBD#IhvaIf4ZBqBS(%j|Q)=b{^`qk^1;y2K&`&2_APv$@Y4msJO4@;|?4z`^e zeGgjIv^;wp4=E0f*k9ki?YwFb1o=X%+Y-W$wA$7trqF^MAEGmUbkC zg5qC|UPlOFhE9l+@}A_hACap16jX0@kX-VQpr%(^^vKm`ldZV#t<@XYt<}VW(z0Qv z#M^;kVK0sY3AK@VW>w1YO($!!0>Ic!i;~*N*;xUv%VD;=X4%NYvKs%zEjE;=CKnNz z=e0?LHF1P(uEQG!#@p39Zfc^x)k^F8Qxne{i{=T0+8N9Ew(g72z|_nx`_5-}tN22T z!`JE>w8_di5{ytXw~}Se;7>k^T%=eF@Tuv+CnfoZjnJ*>RYW7lP=4CD**+5s3YqjR z%OfVoad)t_QM>4+oz#F070+5x@7mTNRgaHjbT?0LeoVHAgwxZL#`+pWySJj2qkd^# z5kNjSrUIqZbhF z>7a?+Mg&C8r*i5A2%6l6)o8w?N%>eAjnggHw_YRk+|iWPqTc=3UisEl;ir$VemQTQZebIq z>?uiN?$W9kiknl@eUt}oyHw9ID=?oX_$ZeWbreR9VFIhjHt@%T zhJd9{^ws~sS8xYixU;j)eQFO`Px2%4q&2~)N!GpN&nsgB1^YS00nuu96FX0sX3slW!Go#UcIi+iFD`k`6R(kWF~FF-;U8@I1BiMjYsMPg zo)XiK^ESH8_4@oM>b>^p`tM20Iy*69+P9qh$IyG^#}b@v1ob(9R?38`(!r~xxzDQ^ z-nyjbi~Zw%v>Tu}U2AjCqNem9OK;39^5=c;lzMRIcP$tCynmjqa?Z`QV*{LcM z|L6z;C8}5_o&od|EK)rtxHwC(+bmI=h_@eD1)T25a6>I_JLvno8(ohg-_M$6ek=Sg zMRJii7|lFg@AC!*Z0gONmQ2yeG*La#DHN`ZJd0C{a{{P?+(Q4?{fh=eFK|OuO6I6YvD2 zCz<;K=RtQ#-P>g#a;fFvA+7C_#ZA-ejMfSret*yVN(e?UcGO!&EPXN26GF27RQ-IL zE_EH;I67UwQHmgSAsmfLJyvmij11==|Q`0Xm4-21BdW1{ofwHc!J-nb)$iW=oWZd7q5{(+zCZ+@R6zr$RJ zDDV+GX>ax-jbmHuB3G2_>19!tpkg5XBKH2?oK>{8Z~xAx*~gA8=q24Ywf0FT`@Ws# zi*g>#?`zgP@6<7h6+7HdZs5$NfB_AYI47&<3v*|P+*)+{4dZfA4mZYNd;%(Vp|{kJ zCgmq(&@f&@$R=IGgC@7jPTNfo-!u0=qMg3j%ti4 z+c+}npMQHF7JF@ix4ct(Ew_O%yt!H+h(#+0DU`R%AJZ0i_@00M&3UDbwlu%rytJzv zyk04cHEN@_gMuMZGeZ5X=3`Vdw-q8MpzV#tW<J&aqSf*9<8&~b$0 zE>$=N&ti@*GfGH(z6+rfhcurNg%2BtS(#f0#`EX7N7OCh6(<`co4-H0N-h|$PG9=u zzRR`ySPW3XRi8MueHdwzzr*bf+1-p*$5NKUTH6yQZuU11ZmP8x#)u+C zZ}=~kV}YB;2u6j^4nFtDC+`uL+sb7SKKV^_^dE6mCM+BGdRTi0#-?+uMLkSH;>PhG zSjEGs9knYn3(RP}6%*}kVT{9c$d$lp_;VBUs0qwIq~v*9VsTXIn$X)YpzXXsy#Owo zH_?Web2I z$pz5zm(DD>q4k z$Ct|S25dMIO<8+tH@Z=otv=GK);5T~nSGS0QafxQG%5y~W2;6EHiAK^_Xno;ae{&; zbiiEYZ;H8^w-$Un?C`@wcTkb&z1IW3i+ba^M2KnQ2#q$evSpy(i14SGQEL2za*rb4 z&Ehocuy>s)b`3L>wTgUu@#n>R$LL%@ykX*L}* z+Xz)LB=1~ht^gc2sHMT#qF8J43GL4mrL2Im+<5eHdm1G=W+bF3sVpkDUPw5tl+Hjk~C(qsh&WHC;(-_7!HyQbO^D!!Bx%3ysA`6)-^2@@FkzqxAqH zd+*>yMdP+IGA8r$C2fbu1H)xB^#@c0(|K(eqloR->UOnmnhj**lMM+FKqLAvJseks z#*Klj9d`0|TmwS&t`8z%Vk%((^QRL;%<$Eb+UC6m^JH0}~AY9`vx{V}{g z24jX^S)q)lmHFA(NY*!&4pJsZU%lMWX*y1pGnfhYp}Vbe$NA{NM~x~#A%wbP8CqIj z0W(;rRJ#NC)e>u>7`RXwl}qMznq|NApF@__HJjYbblX{vbZ;(iA%9Kk&i=qlON&1p zebzF&{<0Z-_|$lbL2EgxwiqV6F{wOB8<^J2BnN%6P$U(MY6wVG^tRxPo+1n5mq$1H zUEvb$aaugbFvE3kl+4ujBu}56W{G%kwEO*ejShwe`g^8#FCM7>q^<02D^2Ux+Wk6D zZZ4!pjOcgZWeSO>IXuhO4bE0sFR8~Z*4Oo0z}f2M&8~zVaJ6E1(hCS+aw_Y7g)*UB z>x-q4s(W7)zOKXNn@g8SB(DN5=;V}9;vCC!`vn^`)mt@XRHHjT`9oip6&Fbsh1FZN z3Jg5%KJAo-7p!uqRHSpBJ*U&X(#IX+=uE0%dhbJ+R!aS_*8_El!97E1(37w?O3LvD zU)!MAxERad;`35R$U&Ji3*udp>fQ2uxy`C2g{;4$2@K*-_-lM6h&MV zH5O4#MAdqKZII-B694TU+>_44|A``-?}sflTvlnTb6%X7gsGI7bLE?$)#CuZv6NfI z^k?0TgO+)e?8fGq*8bbRK~u&~P?Yhv34er&+6Y7yP4eBrviBd%b-+$+xF4L*D(}mjIt))d!H1=?JjH=p-+CpMbQduam zMTD6yw3etX_Lx>tYmnNBX_1tW)Ec`{k|6d3jjjL8{d@U4=l|w8^E@x*MUEp!a^9 zk6jn%IGEJoiC-UGopHUjQ=+7Y;JDCb&x8x}N=CcSyJECJIc;>?oaO}Li6&)>AA(%V zLmM8elvGbVxArMPM723y!o;1fWe_adpP#9S@1dlY%owS)%5Ij+os#iytcv+l$VJ3A z+~>C7?=ec>2o)mohI(92lfniZ$-xzkw;s$lMw4tHojot>>Y2X`^ql0kE*heCO>^7_ zoV06;8ixu$D}4_0jN%uZbRR~$e|R_w8OV>fE}r#V7QLX=b_{v>FIhI`*o-PzaA)Ax zv-1rjhN$@^-RSi?F2#4rMCFUuXV2>zBO3yELk5Cp3?-XWT}NXev@k4~V`{2e$SMs~G7#koItZu{|T-7$=i3 zjtDdIdb?Rb0X8k@OIm*lfduKdvG(pjIWvE?-W%kZUfPaLiHZ!0*?hg!%;QsrGO|WE zht?aEZ^!0hVh%>E-r>A*W-UG+x^nlAM2M~TIjpXu=R1e)mVeFO?T>NVnbC&~Wi85) z&J0H4T5L;gd)xA+J(U-6~c^S&b-W;Hjahb;ilOxJQ(164Zj)u5KfkMF({N1;LA zRnn>kW#HH`p9Y;yC~dtWan|znI_3MT9K)~j4=o9%tR}ytSKfNsu7+qSzg24q(@Wfx zQj(IQ2)0&A$`QvPa^}uG%p~X2Wu!8)%73V-d1VEtsHU#|+$9Jk&YvhUc-v6_Z{o#d z>-U4jn46$Dc%6`-+jVQLHy{jjF2Cr`hyf`?uBPupq3GtGQoJ#yH7g8i%I`;Fhb` zp|04O^Yx6Q^tYVV$4l?)dv{O|26v;E5t@8lJG^Egq7k&4&*0091%nh$v<@))#r(;yZ@GA(K6)EyMYAeuf+#(ims#zOGlelgFBi^>ln*|7Q0E1 zo@^i}ZehP#A1mHo%;f25ceLvrq*wsjVlRwrp)b3CWjMm{$n7#wnUGU@k6%l|@HTF{ zY|FPHEPUlj%8BBHE~^Sx;3%5azg7q%7EB-B;r6Ukx5voMXm^OxegUL^T65xAOO+s1 z2BmrA%+Uj%$^6VNZodMoaVxK-heIN?+F1zWf?e@Z>w&q7w7N1N$p0vVfQT;WZC#`G zXOE>j<%QcjT-NuwardQ9+;nt>MWE^L zLSAZA>cQ9)ZZ%^1 zrZmG{q?IUB-O;NU7h|-yDWIZ(aB=BT_#Rjk&@zxh0=NTe!wYC0;soBxBLQy>qL40>=LLGG8|1qC z{Gd5ksb~$$=Wfji59I3rAne&=5wiX(*7e8dKWOb&PBfnwM`9)Zj0@Pj*}asnv#gw_ z7VgM!Tkv&^y1)^2-*QP!Lmu!<0y_mrpDKQ#wlWWNS>ZS|UQS$-@0Nn^+#D|*?=8E@ zxE2+4n5qn`~yXk+wro4W|r@SywZV zFLWAzzpMw(ZH=Ur)#I~r8s*s<2B@LoLX&m^;9*kAn2LQcwM=fA6_AuPu~(3J){4Z= zs9oO4eV7o%U)JiDM?Di^^P6}!p}6MnS6Ox z9}X115uhX&*Z9av^8($h{+Y1*CJA_t43Ov@A?J6RY&{!6RvY?W^DZR1)>vhDizjfT zFjt>R)M)2PeKFu}B$|-lwbLc}H;OBD?qW{{Uu30UlN!IwP8fh%>D>I-_0U}hv}N$J zJqoi>o4OCMz<@INfOum2&BQ*jl?U%7Q*<7hCw{UZ9v5G8Eg$QD(!CaGrQ+Zh7|dRB zm2vRDvwNveX`>=(MnkgtPv7-)ab3dC4Oh0eHN*~7C#azh(wYmJ;a$y$Kmw6~S@O5O zJkxbyI)6l1^Q6ooM5)5`=jb92_<8RaqVINZ_4xT!D@6H$cDCbC(A=%9(ccS_Dbj?( z|8V}zK`u6aJ0@Kief0A67!O@dX!{HUzcUgG3>zVpAB{er3bH13l+N5(3zM0)BUcL7 zz8y<}jE4PHMfXi=S^HUDE0PBY*q_AorvT+W&kr=u=(_JQiWcN3WPzR53`vIOItbS# z^?DRGB5*TWnQMDe{OXRfqQVqb!_IrPfmPV3PnES#V0bjA1+rgC@_6zq973Kyl7Uu*(q(_tp?)uK@*ju=hUeymcMihtv zvtUQ|dTIP3DI?U3=;XCMn}xg7q3{MCS*4XXpzwWxeKEgLp@^cv!r&kzTIiJ(-d<4F z(F^vZugN9`Ct;*5!Yw{B_pX35+m_1;b+UNj-_-3Y+LZj>$ z5C)2I-SkespI79!V_sRcV`!1auYlxB#aZ_(@j`k9!%h}J>!O*v_gl;Qo_c*AsS{l4 z*K9e^eIN;YmQCJgG5c^05A4mo?%0skx-=1h;^h z3O^I%$h)~lYkd>kM`c3&-Y9?n@Hj%Tz52z@drkLl+g6V9n4c(KTifW;D_p2CM;A?A z*`VGlK1YX0x3nh9-Y%Yve55x%N-o;($q4F63#oRg1<$bkylY`F{KWKX6;r`>KL=s3=l6NLz>dEa8&lXs=Coh; zwlB0Z{$sMQr9al(5fWA*l>tZom8>kEo9WCiWl07s2$(^y7w7S_D?mb67`xWFJ?mmp z#2+`nadRi8!u&tB85>zg`iixI$2@<(fvxGP z$jGj~$ogdlD{BQ_b&@>j8VG2y^YQh%H)2PGzhcI<9ihUoDwO%M@!Ooe{GAyq$3~W8|tpqDZ*U84Yb5(+ zBMzt@O^$aywj2NK{M75*(2u2TlC4a{&o^HK(uU%U3D$6rH^{dHRW`myvDZDg$Xscw zWJo&g-q(*t+lqj9NTw(qm?*^)p=Codae3Da6?Z&Lg(fxTfjSiTJBqn#q~tcoACTka z!LT&PcvuejF8!p$7g3p6MrpXe=2pXDyT2X?_CT@mrP$2R*W<1Uz)RLH@G}kwK&qvl zvxG#*i@(OqFC$hrNKVC1%oI(wWb|C>|v-1lXA zXO`5i@m<^xOc-ajI83oApS+LH3w7W9T7NZ8XHJAwp~-IEw{0a>5N2fN(QXUh>GR;Ya>B)rbrWUpt#9_97B^WYx<6G* zz{%$J<=MmODom@-RqslCeP(YY$3eseNet3qI%1!2JPO!%{%bW$uLvJNRc^ctl@G?8Bs~m-m?}T zoXMw}_swVDCZ0;#0MW+J`*p%H#q=M`q1KE5}DKaP)54=>ygM%#be3f50hcPAF(n zU940|Dc+8~wJ6ZPTUUFccU6CMHK~&M;l_iiBrx5gg`fi5L@i(T6J%TX>O!yz=T*uK zYdYQ(gc~kbDj%r&lbzD8Dd_xuD|#1JKFk%S6tV1cI|V&qcw?C68p6DKDRh#-kND`x zXqDM+(Zm@h$dpwM@r{#X$mQd_>%-)RglFMR&>^nd#FrhJ-2T;7n+Tx6%;SB#c=rC@ z%2%_us9F+T)@*TB)N7uSFI?ejk?{HN7R69?dmzD%u9C7^yyGx{~j@ctJ%`)`R$kj8s8EV zKR_#57Rc%sHsq$Lb6dOB>lZgE|FWw{(4RbKyg0VhzuJJMV22>BOa<8Z!Ty@5XsFiW zQs#@j@W@uXd(4+Z>qhpjg{i!w%pPIuK`YvexJqjfHgpsUf|pI7ZhDnjHBU#+;urfxUxMK&MdjGFZFM+&ArI@HMx zLcIxmNd<38YiR3$wr9Upx)^v+cA&6rMkU9jY{guE%VTuoza4Al4$YbQe-Y--#3z@= zVhYz{6=peyuAD>Dlq|ct_VYv*8eOpJ$8M>*vj*{-bW5`*GtZK?^2AA#4wJ zLSPDNXykF~K19`9OKLvwSLE zkD_TRKLRA|e92A(6uGHGm64h+aCtqy#ziBGsyl3KUBXK377VRwD#ClEp@|NfadOE@ z!&33&|TWjN8}^E?drd1ZATe2yibBK^@T|w z5uO~s(#~Bov(X?_mEaUrFK4NR-@!WIFB-D!hqvTP^hre&)^ZTS7b~4E{idU7i1 z<1_OOyjq&qT)rc#4%?FJzwXOK0VbTM7oYBNgNU3C_6gO$c|0C$qzNM^vQ zs)pM5ijQ2u%U<$|`cL*rz>ug>AGrtFj@&fK(ONGF6O}m74q(1VSBjS4R=->3ZvCjw zq}Po%)<+$VvCfa#cVjP{R?5(juk9k75l_HMt~|Iu{n2nbPHNe;-{d_UX7ApilWLosgYU6ts*R0bXf_vd&|!JH3v0KCbc+_XPN7n2sg%KLKug{PVq zc4F;%YsQ)!sOrwg$9@L0RuNPRR^|*a_b4snz4zphs{QkcG=tLqERp@dC@{~pj705f z$-Y~gcW)INGK%;7!Iwm?l=9QOIG^FY2E8!Sm@20ChYkx_PY|5}fJ#(W8r#H)GnX`Q zfh{^WwJYwPws}mrG=&q0df&nJ->u!Z^Pzt<3)%Fgdfsizx+v?vk)VFzRS!q0X--u% z0eL?oUJwHOP5(yGi7ru1sdN9vpK(atf0ZH%3R6A*UhilrZHkK57yl<#>}f(5wR0tf z|5*Q8hxY%1e5*@7QJsGjzn{p@S~6`bAFY+wY|NeTerQBVbQlcKGpeGHrw-`vg~8i_ zGJgzo$)F^7PJhhpQJ@g7+sw7bWNIR;j8R%u+RDlrYhGnE#-pkY7!-?g9&*cQUn*$4 zBIIUn34?@KoBOG!d=XG3D={iFFs?R|n}Dh^Us8$r6Hz%-T&7onWsVQcm&h2+bZbrq z+1V^}2>3OoFT&Q+nc%#Z`s+9s>eE-_2CzELedTxD)`fF?FY%R&OceI}*=Rwp8R%sN zYz}@p_$vNxN}xq9>r=X(LH7Pa{S(G)4G1WjqnIE>}> zW(Hmz776{JDuQJgK};q+LiPAuU;*2uSIs_Ti)VCi#=4zQou5Y8(oGxL&bK4GB=>jN z_zm&HF#^8FFu$dLV$ulhQJAyaVKf@0^#|I(I+3YSoQVJ#mxiKYXRn{C*q4jAV2C2Kb{Pn4$i`Q z&Uh?`J@UorRpgKhM6w&8zDa===G!s*voy=i!-mc6YV-6hD7knDJvPI%kHZ-*YT>E@ zp~CfXQ{WzgD8!Q&w$O_QJ-drI)1aJ~DdCkKK1uW&K3Eg+L2UhH+6Q zV>_Pfi?e1$WwV3h(WFwQg9t3{Jp^LQU}LaCzm*0^vsKO4MwV`jm0&IJOG^Qhc?8)F zJP4J-P4ds1=3DZDo*l|WPDGb9UKlD|f@E=o+8s4~Yh-j+ERu*6d}o|RT9#Op-Gmm9sh zLxb1T0QJ>hFpl4j)rpZhe9Lvn|kbojOisEy5k% z-(u)j_PjTk%(SZ26TdN06;#8Pf`m#ta7Q8_5;h z4m(k*!p4xJR&gq#SJjCT?gsSmwZB~JzpM)RsJg6ggq+cG808;qzMB?ZByA#&1`XyYacr^qCW7y$zrk36^_L?6FOj@m#b~$gF!H1c!_wTOrI|0gFi9KyEC5h|1u~}Xd@@R&0O`p41}B{LShvJ@>ksVAXO0I!;}kvZ4d#197(*{!_KzU1OB`k zaHb`1gdI0iXfS)*WBk{xM~?Nf+nl4(^w9c%)%ph4TK#8kIwBRpHFYCi>M24QAoHP$ zJMCG0vpnez)7Tp8A{2a{E$%Q0iXJt6E;yfh7?CnSav7FfJ&Q_qqvBA#)b=@Wti~_8 zqK;8KVl^0CFbiZ6cAj0if=X7`b*Z1ko~amIL-k~0>w_z6t5OlR?>C-0)cyJK46rv0 z1BmDh#+&52EE^ti9k~|eW5L_tPQ%bf$Cb_F<8a%lr1=8&yEP-27uMre>yIMBfGwG` zU<5#T5#W02xveAKd$5E_k)57d+Z?y}^0QeWf&c%5U^`9m|^3p42&+mYd)) zk=h4qPjQ;g51~unU7_{h+z|bjU8%zl^==Bhv$C5oaZ-M=7i82g2hlGZ`dF-|J;2O^ zHRjA8-rpK_*1$LvCgZ@Y0KhtNrrWk5;uHmQ(KpTIyt!m~!vzd1+fw}12i?f_x*@CN zb>&ev2D#K<=aFtXIL^)HVTt-cs|xun2wzQ4=P*RFI0_RSVzcO=ANKl1`}5O;QR#`L z+TxmLteH3&o%C_`5Yl8!x{Q<5G`#93QVkL)Vk>3hRj91m`ivS*(vwFPE}ho?BPID0 zn2IsF$5h>o(Q%ps*IRP`na(`h!-z>%6#r)XiLx+~8J8=!H#dljpHsd4YPy(D43vh7rUb ziVI{xf(XJ^@jyZU^htiU$+BV4>tee=h*j}kc;4bDJX|=!9`cs6KI1adhHQlQCoS88 zrpvvuTx$vT;Fd9fy|{Oxx?Tvcs=OmrWl#gPoF+A&94hf%^gb8Up<#V^t`sl}8uYAw z(_u{RpMq74U9K5fS^i-+rX(-u-DJAG<#;#}RzYDec)P6SHiVeG;BkKs6gavz^<1t8 zjN~%?@ou|)7GCga724Q&bmukjP?yy$ourUI`Lhod<5v6t+&?>2L)2|EPDXmXpq_36 zC7RqN@BDndR$|NsE1vX2Tkpl_n_aaE#2n%aPSR%9(<^ZfJ-`1xN0j<{Tl~crvye~8 z`o$(FsuR<|9^u{{me`~OwOX)v_A%eX%+>lN=cTL3RZU;V+Q!~zwGmTDjk}dTS)ko( zH&E7Kw+QXrM~&LK<17C6b2)d)IiZ8Lz*+e7O(h!^gY>sPr@4?y^&)gtC_(%+`%pFa86A@*5qQJK8fg*%uN#8bXI7}0t;7_6; zfdUy6IM{Tycp*?@lV3H20?!ysjl-tKro_vGjeKTtnx)aTRwd zRu4ZD)+jvu3M|Z9+nZ?RzFTND%jKQ*%I$!~!&*65l`{J1ZQ4M8s3)=jg9}1$+NJc1 zh<8r>76VTgfYcH7~8P1UNh?-17P;5EaObyn?QpXw ze(V5!>(j=VFW7FMW7I#t+G13AdwxPaLeF3-T0&ngZ39T+OyQ#;P12E7kqv2mp8=CGmAoMm>sN>(r|?gB^mmzzw*zK5UU$IsxQ4~db2c)&&@KuJY% z|JrcQRokr849vp^S+f^$@y4}~Y8|(P$1A`cnuLz-LK)$QlCCXd;^oc8-;a;*L{xjs43x_2Cm|$HhG+2H`#T zCeGzSq&31PG9B^fh=w@_W8*5bw}&*;WF(WF-vKvUTJNkneBN&PI=9Xe^jMvX5zb6pk&hbKW&YTuEymdBAAiZ^i#G z>CSG@Ln)>YGVNbC=bfUNmbp6FwvPKcgib&yqGPB}U9RzFm(>7#g}6gOPIzc29jt-( z1>~?Mx8I6L^%5oN?ron#9;b6l+Kw{ozXUH4T`@r|c3!+#{S|EyhNc4))UmM4YRa;2 zX1b@KPOR|qJJpP+Q4jOD?^H7yR) z^nqNVab}xi_2sVjN~TRwn!%I$ejH5Go!Y_>w=D?RP=mqWT33&`Kmsceuk$xPue?Jr zz0Q~h;T3$oa#`f1sW}x{P|TM<@tO6O5~0fh2*@oVs?JvLatxZ4Vu{rPPX@Hc#2Rs; zMEL5exT4^swgSxxEA$Bb#+q=g%@t19uYrMiy4_Nb+eju z+*mC7BTcj>LkQ8LPKHHza{BFjwp8Td@t=61Wib6u>=(O`y2|S# zZW^xjNr;vCvsTrUcLTI1m+L`0XKGbqmi;EoPu!cbtlF5Mi6>3C_p?*!3U$Ny9SL)- zCOZC7mxan>$lCwpuJ(0ax^rNUr-qqQo zt;oMBuk%-vsCgw45{Onpcj%p0iWGDtfBRo^#sB#O-x1}Bp#S@1J*hch-?w8v!c)t$ zmgfh4vX#A0*V4t1bV)-J2Ga)<=zY7Ge&%U_tUok*lt0u{ zwM2$v&zGbunkbAR2iDXv^x`VqG9eU?E@w0+Q<)xZ+eXvhj%82hNmsRD7j2)_inlda z3{POshD=@hXyKnBfy9x->H;Dhb0r&C$j|I^qo35_iu$P=vtAV~E>fc<@lGN;SK+0> zMSg@X?HV)c`HNP}<|h~6o?^^Y$}Yu0FB8sY>`D)YHB}k-$;?AwH9A)KIUSTaUWl z$)rv_WZv=mXM|R#v7C;yrfk z5T$bVv7LuQDbd>!hA}wbxkGMyjAxn9w}S|sO_=5LCYR@)#>ZByncZ9hri`|jG4{h& z9)D2}V{Zqkq7RJ6N5*O=vljREz8y1>pJ~M}ew7w#!LHA?%bJ9#R$*cUjFR8uUXLGM@AGa)`E3)UgK#O({IOU9oO6TNLTojIFI&Nb0*1p^WZ3A zGBb#RG_^o`EO{9U8$BaDGWDEbhT!#lA$;X;K#mTHo=bJIY zy#S*z$ZVV?oQP@Ri6@MDXsxG?a*UYFnwv&^u_2>Yp%dO%i^>3@zxuC zxG0z56(zEVKPSy&;s|D6xtsqaIR|Cf?_3a<^72c|)}G$eH!H=$bBTiM=Cl&XDd(+& zAOwF}^nvz$P}4de8J4-%P;XM|^@dDh35I07EXwIcZ2gzR3XGrwf9Caw`x$@disKp zg0ouM-Cb?Ef9dfZI&qmfp3!|U$vMC?eA&Q>0Z z=HXAw@+gD)z8i(l2frR&+%{n$Re9q3G3LBT)^`On1h4GSRyAp_j*3an8gMk|fw5fZ zs0g3sN=Pnm+%}XMQS;vh7cffoo*Nm9=}CS-7!8|;l+;!v^Z|>jf*?JN$L)>k)gGrK zV>(JTi&nlJlUi=t3p;X2rp9?p!>oMUINqVFLeth<2G4zh5ezg!J;;^SbCn!LsUpF< z2+rWTMdeC;qbz6n{gt11)vR@zlYdM~MBb7JG^t=IK01>NH_Wll|4c{5U~`l2+Z zQ#szLoYS^}_gx|VkF4s_3ii^@0Kc3M%cw74+|ts(sDDJMt>C&@OKpQ|52n_tf^Ibs zX~}LWvuK~VDdniFB@N|wU)05`3zytM@n;e}3{~*3#I-PV;g#pvVeFKtLrWF~Bkvv=?3?!r9ChFY;G}L04yGvm150ld&A7-6?04$DDRja97xo zeZ+;RZiFT-eon!uf7%vk3c00lF0Uk zYF&hFk83CwqrvXX$~UdYbB4D!sy*^z_EP;DWu)nPhOj3*8Ssud+Db0Z zYJqRGtG1Plo9J-aS_-_`zmLsCs{8})EH>SiZXY>Pia7u!gh-_o6MeNFPi zN@CWZp4Ps`*RqNJbccCSpC~WPP84N$wr)%4_|(u$thkg&ittrJd(xdtk$0G#vP%EO z4sY52Jub;!|C6Q=tWCZB%&`lT)O3OUDp52m(X_;ZMY|%=*^6r9Bw7^x7dtHQCW7BM%)cx&PyYb^OOO0rrk)DrXnfky|a;yWNSp!s=?K zd!~VjxrgQ{Ct{{FDqOV#$BGR)JX}GhH4j`G_ks!Wmy>Y?%PfswcR(#HZt&Hfd59R zhoAd3LetpFExvU}p=)4Or_aEdPe3)3n;O!i7ZrJD~PtJeA1AyEeD zmpgDkTe(#o5av?OAvCPuwSV}mc4M%n_9HrN;>Q=d)!LA(a^5qo;u1seB0e?k)XPyuJzLX21 z+*m>GozYha`)k)scWEUm0e46@mo&M%C z8U=H&UJP0{@v3!hwUdNT~mCr=(t|b zOOkK@9QoX6=U$0uuqb!>Gazh|;yLC?y_{eZD9cGlS61prA^dUYocwuZvvKcTk#I%t zn&Fj7x_e_XMmBtJtnnTvDnIZ`2Fy3=UfSBN%({DSbIF*>tna5+AtgapL;PBJpikay zt*fPrk+}JOLb~73l>rO@L^Erkt_1?6&@3-UTWY>p!9$4veWN2H5c?x^l%}?03uRPF z6St=AFC?-ya~VL5Lz(&6Us=YJ8WOW9k8U8toE?+*4DSyL!1-_E`UA0FJU2Dljsz>$bC&%!e&%eQiv($%$?JV6V&Y4g8DxI?Ph6#Jr%emkkF>U(4 zRJSQqMqDt7xg#DbXSfSW=`o(-73~1{@Y~q4z2XhT(F#@49RlJ$9NlWQLf23C%RA{5 zX`b=wj)T8?r++ipu>ItT#f3kv{2ay0W!X2cG>Lw7IU4AL3}p+pd%LxiX- zMQwr!(of|>m=4?!#a+4uY}Bq?92=Leii6HOqGj5g7K~!}9B85D(6ZN=Nl#qG4H2_? z#Fu!j+BtAssUFi+-?g~OTi`p4pxd>r>CO=;Cc8iGo+3zAXvvdOOvu=YkdE7YY*EqB z9J91fbdau$o*m$l$D#-2w)RR|xzqZlHA}{aj4(bl_vn%%c-)*>svYka&M<Bf8^zSF1#qi;s^(5&@gRvI?Y*m)8y@h;ne)Pn+D=I})H^fuk?!15gZhNce zZ1zsAbbtN?RtlY4D*o8nD1T?_xcrda$5 z_-LHarl&go^U)RL$1W+@edl&%xSru%tcDmntud##IV?CY_TLp6y|rW4@KtmTzw0?8 z(a0|@Wg!LW&UrcoXA2hjwwT@7& zn{p9z!547H)Ne^NB|c^-PU%W^>lE#3DW0OFwhI!OxSI*lHfoUH=zRxnn^~7mLM7)5 z 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�h(Xl(?KMe9iRek2U5)Czw% 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

R-GJY<0!SZZ(k1 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

~togDdLW zU2lq=(Ds&8yBd*CbRHhbZ1nUM#jQowblo&d|C!P>2&6W;VyCDCeLvLZ1BlEGN@5WSu5M~8*vb@8S87d|Te#vJ2(SFDeZVB-%D}r90-(VN>Q|8INcKC^zZCEBL*n`JE;-GJXMA z*yUQrbK)HU5eu<5K5l&1wxtbasf5ERt}cttNz!Fb>F}bG?LJVDf=JMRr{lPGyM27Q zkpjJamZBTU)wL!@Lf@|(X$Wpr z-DuTnyj!?02)Z11jAn7W@Th<5B9xc z02zr7ZsQocOex#q)YyX?+GU%f4~!=A20ay?y2zy(?H{l@zB zM6`A8t{`&L2n|G6E(bi|p;umY+bm;-6>~68P5O3+q)`D!|4UrVoul8b@+2<1sOjsZ z(g?$nL7u?E+!x^z#M}sg3jkV{rO)`s;Mm%WfWwIev}I(*(!`*>X$vjPhGx41aP_dQ zVfxWJIrr1GJVGx)+XA{0xtu|Z2P*S>MJ0g-3OWxC@OkQ4xmb?`z76#>Hm=U)XLqhV z-rhj46IW~aiLjIF;+Nv@Ry}VAU-}%ujiA$ra@hi~kCkb%F zJg+fCc~CYW&$GriM)g`C`V@zB zYwIqXP0R17TulqlpZ!m-zBRYm!=&JRLTv2SSM9cD!zNGVA?uCvg*FC}e-gZxFTDC3 z2^1!|R3!b%3;P)QLVZ*eHiL7LU$g&#RTOc+VoHAKyi!v7!XxTJA`4~{ z(I02>L}+)b&ac^b6S@*CKb7bD|M2$aVM*ru|Nopbb86bBeKB>W#iraUa~C{KV~&}b znc{9nN{UO08?yL0)#&7cQzob=RBpKvBBCNXWlD;GE4ZL!3aBWkY=O)C`2K$Xp6mD5 z=lA>f&2`-uaNYNPz3%t@d_Eq#Qo&cq;=?QX-zoZM{b8ibkh~7d#?NQFPJ+K*%gE<* z*qKfH6Uv`A5U^@SF&*ampvWcGuM+ot{xRyE**hN(w=j%qET@NM7wS~h?!RJCA-g4FlSAU(s*R`>%g* zQso6FW-mR@wr3J3m}Bp&=cuuDf6konu&^=vz-WUWao?Z$Vz{S0f7kw#)8S@5F~36Z zlg*!pZLbX6-B@5mJJl1g9$r-a$&b@?htKn;)OEk3o|@g+YRcVR@gX&j1w6dgXSI{| zs3u|jQ*CacZ{60{LOvm(y>;|iJn6zLct7UA{a<_iz-PQdO2=wXs85}3C5?*;45*8{ z6aL6It3EQrhP-fn*)rUEvb~gz`Il)5^2gTD@bIX}U5BaomI~3ne9JHe_hE#`yHPGj zOTsF7jpiSbbZL2y57l?BrF{u;WBt!r(aZ7BEKBDohu$;NzPi;LICGy?E~zD)kaP=v zrm=ud>y-~avHkuG-oJKxq4hF+^{tufQuULcWBOj+rc9ek~P;-NM%Yq!bl zP>@PmAqvSaj0$8VH62iQ0_C@y-#}jrBT7ndlHj@OaN&$Iq$AOKc z3;6!T$%78GvMW}i&=cd_U(||iHr8UB`RJQSmcgl>(C|C{c>k9ugDQTxmEE7t(B%@7`*!Y%+ z;o7^^$!2jMDY)~ZDy`i6D6KakFO=hc&T14E$f>F40?-~+`i8q8@{z_ElJE3|6ZP9N zkF$}bhj_gvoI3sh@4yR}rZTyYoHYGX!D__JA@ca#SVYT=S;CBsL2oTRU?VecynUh7 zh-6SvHZHX;b;cQCK#mtmExGE*A1AJHtE{U@?o$%l$wp}D9ztbcDF=6~w-$k=h$jQ5 zRu@tl#SYFz@`3neV+wm%S_4%TMr6kfzr>H#!%;T+6tfZLpxYq_mhna2nML^G*l5it zd4ct22^c5YD-6=QxHl&UpdR>-7$ddDF3g6sXex^;p^e72*3cfNeRZdh_-hMFBQSO{ zRkwL`ZZh^Loahl-MgOSjZ-a3pcne_e(NSQG5(C0#@0Fg&%-nSCO$CfJuOMa6dO2h; zUi#O(^s2HWxsN%xl_^#tuQkojDbj54Z4*w?;x7myK_eQGf04pt^Vq zuaN0Z=W&5++su-PX6(V2+Ve+atvYMvU^spL-eXgnfSmd9R}%^El+2VsOmT1qv(GiG zj_Y3vA#=#bm?R5vX1eX!<*~N4-%UXK5jIKt1{l%}!c^WR zrE?aX5p2t%rKN{r+@SmgR$Ra_6LOCz7!1t5I-ro&ZW?eCJ1o=9C*vp3!HCX+R*WFo zuxh_@k7V9SGx*hWA4O7(k&DrtmLq8Qc9Au)e5nI4&UcT(a`WP9HxR1r=f(0>+3Do~ z07S$v-;t(}IqoY&?K7BKeJ-5L%3%PS?f0%~dH)B)l zQSDGjzh{H#VmVJGGIX|S>lv!V`J4Yy6F!;C(jn~mZa3=2rYY&w1U7+Yo^1V!hqC%z zE~&ns+p&d62(xkM{XN6swnwPn3%|-AtKL1Wc)Fq#NpO)+Th*4br%TQ+*BXO=7bFlL z70)=v_;wL*#S$cth@KehlJQ51oitb|C3}Oz#MB>kZl7~pU2Jw@qH+@_FTmEMHPzd_ zs;lQEgrB(MyXR+jAo{;P`9sSe-a;&`@m}YK2u`ho`Z`q1=6sR}PB4_q9CxhNM<)XV zA>j7Gq~U;C1j8iu0|W2s?D_f)8X@T@J4)|XbjXv8_qHzwCV!904V;b`buY4QC(^n6 z=D)u&)1}~t$@58cLNL&KpQ~{QDx4h(;3Ae%EkGp40V$R7l{asl(vTdSVm%CJ7{yPe zoD1L7SWztc@*FtgOA##UV4y!#`y7D4viEZ10-AC2y?7@y#4pO`(s&4b{!gE=UvCd8 z4<(h}WZe26zBV*w*nrW~p|QXwsPA0BuCQx4OWJym#DIDY8n8W*H2rA#x*#Q@s?V=X zc=;nvkYIYX^cTP_Wj7r> zprE6Yg#{m;7I4H88tsdUAU+Da2L5M7{e!OH=0SrUk+BEam^=GjkND_?g)Y7tL&IPh z^tI{6Ie9yf(v=)~Lbq3;0^n>%5*9#CkW+(kNq08N3I*|{Usnp^Yn59NX?i6(b@Lx< z9cBK&PF%od#tRWn>b@T|cHhQ_9n02!P)jxmf@DS5R8Hj zZ(Kgw6(E7FkCb6+6r96=vO!51XK>2HQ3b#qAT?$!;NHD<;u~Ne4 zaHb~};l<48nk%FjOztE5rTfUJ@A%bjw_N*c_uR>l-PYQvy<^)#ihn+hfv8r$m zBmUXrDGr!Cf}T}x`U2|E2C9+%aF-?~b66Ru_Er9Bhudk5-HR*m;N=!z7pt@*BTDGF z9QQCLX)C?Vx2Nq^Npl;0M-6RZj1T<1KL1IWP0bdhMff3VH$@Hkw7T0LdRRptv#!4z zcrq=(W-d2@lL{Lae5nop^op9mGB9%;P0(m^`EZ2?$zwBPuOowrMnJ8LANQO9ryE<- za}P`bE_6lJv+IA~)6}8Q?(Aw;jGX1AC>AR+-xR%WnVyhzCQn>{4l#!(Pa0X>=X-a~ zn!^?b7Z>iebYpa40xhzZf~c72D)sOLb4N5%UtZb?Ua~(gi+c?K<1Z>#-zm&PY~SHL zDq95$Qg9W%-sbJ{6@Sp_GXCvcL+vcK;F)rVttF^br8Sqzy_JtJqFdR9rl2u!?gDjc zuO4H9A5IeZZ~_U*)8wgTAiPOmR!VA&w19(I6RU|tan#9DMshAmA z{_0-DFh7xsFG@1_Hr|3VHa^7@n1%0s(DEv06?5bLQs#>@PCY?i${X{{u3EZ@fo{Wg z;d2E~Vc^4Ws0YHFe(3>xHH1l^ic=0lvHrLYac zX6B+WF8$sTS>1D{1!bGA3YFncsU9XwYA6B2~yI8Pa=2OS>n)!l|5T0Eu zi0`?1HmknI7xHxf(aq5ylfIWP*7p_kzk6|kmW=U&^ub=Z%soi;`^a;T`)7f`yhxHq z{+DT+2q%o-Jz||dwHx-v|HH8gY`cmVq%x=>8y%V>*G_uZ_}HeM#(r7f>rGHun?vP4 zOG8AP#?JM^;qeW5?cR=!Ae1-c)?EL^+osTy9(;_=fl;YF znAL0&dA2Uue!Amf6~*AV_LvuW=O_jV6`I{_il=`G|Gnhk$p;v*>HcuPJDo?$Te`0J zj>IVuk~weBh=d64Z#B_Qjab`l3Qw?{4|hK;L0^Kt?jlhGmSC8$Gg2 zc$are7oqhdVP#ot^|{~+?LXf-Rpbd_JSg0@oTkcG_Eq*_DIpvit7Ji+xG)Af#7n4% zF?ZXnEZx=_gZ=V5vZANc_1BTRW74yowSuTU6%z>ah+`F)PiJc*AAJbA@dA9l0gK#c z?Pwkn{jzzYs16CoW9)ysYS_oSlnTbA9@CyHFuHu-<>}z~(W0+<8%=p??|(4%de+E% zxBTUQnSuZRSUQ^(Sc)44LR7{nd8*RBQm05XN)?9%nGgF+B#Xua^n5?*$G8rdd{Chd z1?vsy%m!UuaR}f_Q`@cdO1l=@5@$MV1d19>?2+e5?PA;KNM{Inz~7C$oC@dqP`CO0#VsC!`7>1OC1i+!KMU znna&%_vyw_x@CI)Gip|KS6jh0zL}Js_*D};t463~``)bTX!^#t&JM$>sZ374EcV?Zs zN7|FS@+tE{;j&oHti)?Uw}vmnhjG*4)d+XJmF8V5iQ;**xKxZxx=YL5$|ldw0wmmy z>Im-hDaAP7lpmQEQaGlicV)%~iin-S;N9^Q|!9qtct_+yxIk_quTqe)VV>$S1Fa|!R z-#zZ7tjR!9&hV^X?#bh8o$FFj8`~`f2}MFjXuXzp%WE->9t__Y1~3TIXavdnu+lC9}a7#)IChGF;_?{$0HFZ*MXGas3DRXHKc7lN1=4`)7)P-60g2Yyteglkf3_Sled0>%V zy!|8EUPlt!Ao;0FD#9+Av(Zu|U8mdAVO^yKC6!Iod-5zS&=d8gptT+Wjf-*=wYPac25e8a zp_p!^AqfGsHfgaJq%i$Rd4!CxvpGKH95aF~KX{vG2KGe@EL6x_Lc{bh=<>r3V}X_r z??Qn)O@^>R4wGevC+Gp@kGp{Z%G!Y_YWGwKX%8fnn~c>IB4SLq*e{Byw)f2(H*e{} z_`q1%`D+$Y&>S34&yK_RDA91K*fB084+j~#z0k)S zvgpLc0Eese^z%wxERKkqSISn4_?~4VL@q>9!iDr`W~)8&Lj1-^Fxz(%Lz(OasXegR zY5JGwRVimG3kCtjXG8}*$UMc7x(%@Jl8uIfyK{}UU%NqVdz_a1CKpgizXL?P@((n1 z&?Xg-q-n7EpW&D!*LYtx&WtDU@N2bjQW?G2dQmoKE3l09lefz2lO#OXsLuRBQC-Ac zs=}&EaC1fd&l9lQm$F9hOHYP>aJK$cm!2OWXU;nxV`QQYwiNiKmgM=4u*Uvrs#}^az{t^mK1_ z@i74z3Mp$Ss~Cwn-4EvX1Yw+QM}{7*RxGcM(O&6dE9G(r(=B62v4g%HtVRkW!#!e! zw?X8R2Ca(yR=R+0(AR)FA8ha^ zz$i~~ENy-Y&Q*H;`3R{Q>+Mo7;K^zAY?dXGvfMPRun`HNs*k^c}?p$rZyaLf?VAPuMgqJ)p#5Q z#qOxth)}o=6Dpgz(}JI&wn5EW7fhEwpX>ZQ97b#jzq~3wkci~{{S79W%0qijw>lY+ zY$-1F^H&)5QyK0GB%gj^s(mc(iuz(Juu>d|%BY72LQm!a2#i%Vj> zgH5z;3E*pe}6u6SA;GN)N78|W%JAsMCZj?CSy$86MN&p zl37=UD1sO3H6bdEoZK16#$-QVTpeo0y&81~vOLrMM`G@cK%MV)JzLbuVn~PEf~%|s zd~!MTlGoJ}3UxBX5fX0w()>L^uMXSbxR-LRTeJj)GQ`=%okqA>8>oWUd+hVZw1p5}$|$nZqWyLTqY%LFrI0@G85K?B;W%9loT*Mzl!-vOAp1rg%g0)|3yw$vph| z(Lev-_R_0G6MTQFfmzzAuTOq}(B~Y^E?B@cYm?mp4~fUgZx!m)p1Z806jy(6PJ99K zO=DzjuXp&<0PISaMG>=q;}p{=-|~T39ExPLZWLzud*asqvHRhHPP>uZLu)}+SoryS z{%cM(LHE>`*KWqwnJ9kN?6OX}&>JwC^Wfc8!_N2`pnvAkG1$tgi3ytb(k1y+Npo2p z!0(T$dmV1254nA-qy5xIf3Th6+5NR&c8hxaqTKhuWg)yD0!Su@qwl#S&@vX6>Ju+7hRvym_aR38K)ixj1nFReru;i1V2!> zn`+H^4us#v?)dots6te~ze*ajHe#u+FI8(g4f9$@~>S5Dq7NKUgk#`6VYy)^i zY&Eon-mh+X#-Ju<@3y`(Rk~mp-QYhr{N6kbB=tcYd~nzAY343J6Pq`Eew)wz;LaJq zG|!L+q{}Pa@=S90j*|yJKz}T`8s)d+{ABn2jP7R_{Qhcv|IHY9+jaQ$b9qi#>PvrS z#3eXkqP%=hGKK)$*i^@&x~}Yu((~q zkF${mwii44Qi73%{+BdAe>xw%Fp4Z+sXs?>e|m8y{{9URuibaXubq6DgD^yH!h%5w z(05Czjt4;TQXCgMPZsyS!f?o5(m0_5%~29XW7c5AyqeQX_sjD5L-0dem@{|DTW8 z?uT%W`tlQ)w3YnL1V%!6V% zOk$WJg^SPtP5STa?TD;E!*n!Adn?bQBc5GL=M!i?PDVrvU|pOwqaSWqVp0pXEav{{ zaoo&k-LeRh?l8)-0TG>a*R>@2s_@%1I?p(5czs@5(= zre*Zxu@@*~@wOG>XQ|$~KwUy{x!)T$QwC=RWLOg9`9=cA#%O~ZVdQ`TyL6|X)*l>1dNr?J3A<%10 z-mr0$KRXso3?IhLFB^7pjja|@-&V{5$#vb^!#MHMA;c@+E-~@nb4w#Kv@?#v9A9oB z3>#E{f(?s}Faa6>X((0rE|t-^HaTS!dxGAEKP^d4jdM1awF0}UfLO+Is6{*ES{0sT zUI0xyU!zZHYH_Khw=#r=rR=itD96>gD-6Ww?97|>6qbs6&9HcjXrgx`Gf1?d@p70&|=^D0LDZRGz02OC?kA>b_0y&@Pz3 zS{_A@7l*x)_FJJ1Ibsa_NzcSEV%hC~7jGg0f$(=vzoNuA3=F|V&gyMpql07Wq&RyBC z9BRsxSW;k9&ps}0RaD!d%dS#!5}Ouzh1~eGr~~&#$`z~cbq$LdycHot#LpxtJBuJ* zqE|wt#CHeS%}G(isn_2}zAX)?d_7#!^m;7L$mJc?x*8w;v?(QRp)9?3>4L$uJ~Iso zHJ$QVZcCVH0x%-rKkN4B2bwd6NqbAE2#02Cyryg{K?!=&oSm0m1gF<7tc@%x(?KFW zH72Rkc43w1_f^fMS-xHh)63o?>;2Xk#K0C@==CidusgWudPS~B{zA$`4TC!BVYr7A zmtwJHb7y{z1r48aVZ%QpraR2r65f&Wfbkp(~z6zj;tj{NUmQ0mV7IL>TGiK7(HuqP4<&^okfuPd0qP2<}yj)Yg@HRklrQ+m%Hg?Qv1ZO;yWd^ zw7%Zcn_D@X6c;Lyzg@ClFca7EvO5hkge8+;M23|@@MyX+n$|{79lt}V{ zn2%^RH#kRukg=XkJUTtJIONG~!d1p&Xz+5zYLx=A{Z`@{xGuK&WI+S05rfossm#xzU@;h!bCZ1gO`6 z&X)>D{YGv#dKfjxv}#bT2e-29Pux|0$)BqR(^c`9ig9V`^pSJ%nVQvlWDI z1+g=1w@sIC&<6-4Mr(j4nfHjMf25pr-KKsl)xTU>MU08q0@(Wbio47v%Fd=QSdWU_ zRt0K{0()?h7XN8n@aVZ~=&yRvHTG*g5iqHiG<`OeE6~AQ)MX~x_(Eg#;#z2{cUaQ7 zaF)gip>UWi{xs&p1yzS+3d79nfdPI==q0`jV2h z5BWC!MccKPjfG=t;k8ezFJaqDt!%5@o)R9oEgj3B#YAk+ESv-M?amIl`rPavnT0v0 ziqB;B_9Vox=G#Puy|@e4dX;l+oHnye-p>Y}@oVqXmk%*X zE|9AhtfIwB9%zo~kjPtbaMus@gqM!xn2)@0{?&JXW!-#g$0ar1h5rzqR{%UdyMKMD z+>g8Df`!I+S#Iyp!81bkGw=C4R;9ki8ZB5DN$}eYb|2_$t~G~;t*DJ7e)_Ov4RXA5 z%rooMh3=f6*}k*t3m=Xzo_!ne=I?KsR!utJGLBrqo9%peXJmXR4j4zL)wpCtZ@m}C zo?yHF$Y7rCd(SOTY_N~YU5JD&I6AHJ5PnnbsfD(B7_+-&C{tO)W7<`(zVp1%f3<$n z)oZFxo#wJ!cZTL^inF^ze^x!YC?7vEyD#Dm#^BP?d=sfFm{qt0FD1BIk`MVhHSVUY z6;f{Q+o@Dr*Lnj#n2XB;4bE4SohUw5(W?iuBgcg)~ScE75@xZjp(AdG5g;7GJ*a z%RO0RdGTfDPV)5=%LPGEyJJUi{bw=UAN2({C&+X5(=bdbzYww?V~_BHoB4+2=o#9? zXGZp4uCq=s``a&t=l<6rJ3O!BzpnppE}p@-m*$P2gO$X%d^PFt*b2`1|2PEIwrh*1 zLjbryh?Fb`5qC03GQe@I^4Jn1B%PuN&d8IjN8Q#P9fr8KD&pA zJ~=XcjgM_JueWHgUQ*GLE`R9hA_iZBHL!UzFiv5a;iOw7hYgU#ndA%N_5qkA<9)6j z2%|ZVvt4f`d|0SY<0|4DFWLAkFUl1bsu$f{&W@RD+Z6;bFr!*aJSvJ27{oBkcbXc= z=5$(tLB~$(JrGIW$2AUyAOK<*`S&-B2AViHlWiY2&COZ_oQztKjP;Hp6?I?Y>ugRi zh*cR(pXPytg(%U~Nt$hta8SE=%gA(FUf5Gq%kWZaN3G-fIf?vL{FbvX9oi;HWSUmU zQcG__{RD{|gn0*vEiBBKrZhhUo5#7nxQX=R{xdOk@w8~DCsn0wLx~%S&aQgyVYe&= z##_a28E}={o;JC0ZM)W-UtJl?Ii@G)8GFsgy_zrE6T5 zcbHq}Yx@XNm86s*T2|JVqZ+k5YigcIv%adk9+bNw?d19CBOQ9$dFa4+Ke9~L&I}1P zCneVyb+h;Q>mxE|EIdDZ#-7^IIoPYoIDw;;VzKMskXP$ zJj)+-Uhi2@O&EF-2UX4$NU(}zQE+is-l~_t8S9Szn|OM2yH+Zdgc&a30^K3|%>lkS zl9ev_VSUFbWGaGh?EM+vYexj(7gw}>Ytn13N;ys?dbe)N)a6KNUo}I(czo#|4w2*x zUW9)^tWUe2c!QDON+YIF+1{uVLwOKPEa79aZEhmN$SR<3-GK}W-#p&pzFz zo|AG-nCEfLD;-(umax=xIeXWGiG)VEA<1A=Q8-46$j`N^%t>rS{}kr(ojQxztaA(=SdU8m8@wr?v^a$N-HPT(rC+@8Rg7fK5H580%KWLSsel9}|HjumkSZh>T}$y_pnk;}hP;fFp;e zZmnO7z*UKp^`Z6QHGHllevfDTTMPcej)qyN0SQq5z*hoQ1jFaOHf@KyvUS1hTWB?j zPh*vLXb%U~_58Lt@utx|px5`M>S&+6kNfKQTaU)R{?3bCZ^?D5oz9C}k@GV(^r_8r zRn9onx1sLb`DIDDXgKsRev*rDvbpp&k`<(Z9g9j%Ys-gh`vk_Ge|g9CuxdRfI4X&L zqn17vl|uGKSno}r3&uGQ8}f#IS|xPEZ`M8H&SnowE|9p22gY#K6Y@~JIOW|q0vX!q zaN*8IXoEx(q%9Ze*bSvlW8OKc=12q2((x((>c!{*9Ixy9?8$=%X0dg2#f{EgQf_*c zZZC;s0dgIRa#+Y2~Z_RM+9|5=${Z?_AX+)VeUd>oe}2~(vF zag{TZ;=rjRnVoy|36ei`mt1E`Gi`-=VGZlVgI&#e(smrXLg#ASr>6I$S9BcYBuFU| zz>*F&u#m=X=bLv*I}FZ2`ryqYyv^-Cy^_H5rZF7TB*Q64-w)y_+kj>Yb%9E4RSK@M z^*6dETk4t&S-IbzGwlPYzVF_3k6vSFCb1|ZDavvCnkfhH0gpN zbY^3nJBdc(v`ba>1ygl_hqIn{b+(q>NK+Z*qr6k|A=m7o-DmeeCa>6@=teO-I;`oKxe&Bk z2TZ9^X!0R4CNbz#t8Wmbp8ON`{$J!Tx3khrC{WVGV)U6Vr>T@{^@2YGXeV8O4dYaG zW^LSwmNTucXWa=GKX$l2KW*&0o6^HN{vOhN36cwzp5~&ImzB@3*N^%ARsB?%GgrMB z1LkAZw{Lkwl!Q;*amU~JIOH#~xBjk>ZNJTEjCH(5+BDS_V5JX`nn4b|5Njr$>^PI9 zaN8F5Ty!Mn<3abFf=%~P94NGMdgj5!B`edV)9x>hm-VH98=r!HI`8gu-KXO5PY4m@ z(8VK(l#rTW6RSTVW@M*$Jq~Vsg2Ro9Uij5WvInx{qi=fdd=V6WS&jUHg*`x++K~uR zwIFZ+J8A4o?SSi&O900@N4}%I_SxCM_?I@*vzBD-nQOT@{Rc*Z;_Ls#<{DX8h5d8{ z9+(r>mqV7C`0vR9{HR)3rc&4Xy`eytGXVvg^BY%=v{o$CP zTEFsZe<{B%My)Ky_B`GJHTeFgSDr8Nx~}l;MYQu>H(P(-^Bj|Wv1U#CQ_jyr&Ivtb znEvt|?SB;MTF$QhsJ>a_Z1tC(kP!+{?Dvg&0a_Eq^bJdlqMI)r7;VapR$n~T=Xs&C z^$Q^X^iG7~gQoM`%GycPUM21Dr#mG!)DH!IwPs}*X3>f33F0?XRz$t8mj_#JDyd8w z4AyD_??kueisQI&QiVrp{E0*-hsEW7(e&e+r?#x?V3ayIe7!fZ*u0E(ou?Y|eI}mr zzyRHI1_JzT4n^Nw$M{At)P66HGu|hy=c0SWJvGy3cO4P?U24T7dD=HNz6nO*0IW~U z@53o(5k37EiQo3&?wwv^j6QDuK=mg768xoSXGQxA#rou(&jqWs*z&IvJpqZy#dJ>p z$^_Rhs;`DTnUnDlz;j|5B!fQqNhe zWNUM*?F~kU!e*~k*&MlDeZrg@d*$-lSrgq}OK0A?YZ>%-j7K}D%WOY#`Iq{UX5P&{o30+_ppdPLhXu+EQoCNg9yx@Djyy!Hjk7C+Czi$O0LlXXlQJl6-T@F7T>YB2Og-YI zv;3E`WoU^SXqNb;eZfq6!V#;o@^LtxG#5~9U3#g8X%tuma6L^P*>o}fQO{0xck)ok+j-J;ARod=N4*JA!kF(mF47Lz=cQ{s(L zYrlnx&Ze@ugdo082M--Qq7FEptd4>C^Sv7~-{Qo8-2+5NN~fN`5~JegqrT0A;-gdH z>3{^l_Ve-;cLYu`DAr-`-sjUaLlJ(>Am#PKJ4D@4x zt}+ih38%BTs$`C9wdQiqEU|xlp?PFaNEvKlt)b%rYiI=pum_^nNMz(SlD+*hATBO{ zVIl-A2Q0Os$`_jDKrn^rkvWXEQg)#1j;VriAt-Bf?IOY1_5D}kh;gaItR%6mG^LhZ z{y`rKl4BQG@iiu2GTe<>TlAk2ns~FzS zcH=swHW`s?$O+b_@fwv{B;H=E10vcg#Hz&O^VN#QG46d<>9+ia8DWVor4Ml&T`J43 zQK)OZr%GQo_>X+tT{nvHCQEklnhbXo1Y-{jd`T`Ltp0`m&(}~MB0+ca`25Cnmhi&z zj|;h#9|G+7Z8F>WV$Pdewu_Jt)Xq??aTQmIk(7*UQC7?MMd9=I^`Yr?8}g-~12(gC08@1Qn zS@@CD`bqs#^#^Uj;)%nA?-=w_i=~!k>1kOgq+`Gp7pr#rn6o{8U3AKdgpI^3^7s5} z0`L`{P(^K|yH<#XphLluwiK&BV8Q$L)Ga3AgF9o9{DJcmoGM0OT&`!zaZ24!_yZ@A zdg$7jgm4o(^8oDPb-Gh~P4REQaQ&}r*cgt)1La=~8x)PRQW&ab6WT1MO^15gD~ww< zCM!?LTe)@a>}X4#X!tGEW^5{ zFOXLmGCZPnD72zQlH}|-30p7q)x}`rIuEQLwGBGz+^Xe<1pNwUs?ebeq19`~|Nbfr zX?~%uJ+bha{<~WP`R&==C zBvw?)Nj(oASU9;hNf@hWUfC=n2T-=(w=uoy|!| zeK0zyM%eWU`(}Do$H{EvE7Lfgr3?0{8c+KclFFf$x|e{C|u_g@4vF9B-9ug|ar!iBWb;NSFmo51-vq2*b;soJ^d* zG%BUq9QU16i`*lDM2889(VpJ=R-;v#;GynhV!l5b8JnYd<^P2r(71d&?J?Co8P<>j z$yx6yDlwV$PB|-C5hQAy_QX}zedVcR5mjb%!=>2BrYVtw2BcQRP7B$tMtk#7jgxG)eybYQa$YkI5=T}JdIxJOm4+DwMh*n+^v zOe30Z%d>WNLOBLu))$tuBmE*?z_F$;k6buRwXF@}Hj=`9d0@-Se-XOj_vsf3;x07j zovlx>*4%CBc?k2*>qkyA-S51g4?n(KjK-eg zertB@#q6QyvSgT~C7$w>Y8u>Q)yn#WFBGlb<1Z&^tVPA|bBk@yLSF9jEy!93j@wyv zq~~%*;9^A}9F$u!vHY|HEP3ww^Xza;GUifp?z6M}(J^S9`e|DmHMX+*9PWHYuDtT{ zr8DZ?{a?5ToX$0XL3VXa!Q9(QePCwyJFFc5^Jz}aMueWcA6-_Iz;y~EQSa7@nkypf zJk0DSFh(vnIZhQ9z|aFZzL8x{B2w&K0?X1;`6s2`BH-_DF23m6O{oQ%9f~J=o3%4{ zs*%x=zUODzS(T|jFcT)C$ogb$WgB5-jnmaXR(b1Ab#i@Sn8n!=Rl?EN98&=6e0cqw z_6OF*yxvQHFu11o*KVHfN9CxU-jNQZUGF>AbG&wB-+q(JkRj+Q^{|`oRFMB14}vFW z)z=;`Gtz!(JcDIq4Gj&b5~0E5`l$NZm-{oSpcNi2yH$Z~DQ`*4+`U!L@ICRNnG%no zR9294leR>6xYaQ=1EOJ~m>ymBQj9KppRn+Y_zf3NE5^LR*p^YB>Nu&QlB>5=?)u>R zi+1A47G)2EYmhPR?Pi!~#d*{E%PIho& z!>@2tJfFX{RYgwJ4_Vb?!~jijC@T*g&DS^)nXMpx_kO zYAW74*?;A^bpdot5Q(<~`J3mWqE9J~kq?{eGvCM07x`D5p+PCU$nd%5tEc;AX_R|+ zXatJii`^G_mf*eK30kK+%z~WnD-qRs z6i!ej+1np$KYIaco3?v#-(}X3V@oc_U0%G(7ar}m`pEpK@;$$LX*D;;YX7Xs8MBSL zi@04st=;^s4F8>;DyXktLib%>YiIuS$A1}_zZC*j|MBnt^1=V+(gD{}yb(8hezU`w zPj@Kw;hL*EF(`yX)!IH9vchgEd>S+=)`iRx7g{s}k?J2*%xh|5zwq^?THCl$1BB@WGxS>Nf;Lj4O7T1l{jxaJ!Q7^XBA-85^-x?#K2YWk_X<_mXcgdSZ z=7_7w<)!jbKMCCtg_;f=23yCri=@g)XWP^!+aE`m7JWpEmktCV`3TOA5fU2f-9nzK zXY{BU+ln*uMnU-LFd_TZi6cv0#Q3+bqWcU{{4;JvV`(RQmN7cbE~HGcTLc!qjB-3^u0{r z%W;asjzp`l&_sid$Kony#;oRin47_$JYLm##S}J-A;6 zDie-OCoB-MN!6tZYsn{n5=~*sTRX+g+Y~;0Z(Ig%^AuRL>FV~p7fD<`!E$z3)c-ph zR(^Bm)g{7O7Tbd$p`CE{o=L9XddlZde+5i%iRhw@0r<5)iIa1!I-J7Rn@Ji^DhZYu zc5p|K>C1jo_O`kft{ZjoWS!%tKQOy)*!9w18liK;@EVj#kl5NR$Y&9Pf(Ms%T^mM<(!9~rTEhN?1 zacNDCUS6}TgRc*Kv8CUfvfwezEkjG3$hEqvqpg=`bwEN(b;bWwbCirP@tTWNvneAx z*4vTj+Ttj~2S)-0kzj1F&1~MtxI(*RoA*ap>uV(tMJj(ZCCnckj#dt8QCF&j@CeFO zy-ViUx}Cyhcx~6%JT=K8!1GOls%9W$AW1}i(m%Y1>x>R53&9!S-VDiB8KaIx5sRDS z>y_9*-LFJY6robK9Bq{xGT3QpU@~M;$TWHaErr@B0@&3!>QlSL8Z-#t9D*>)_U_1< zENpX%HSmW2&@0ET`wISb^V;`yzR#TgxEg48-y@mp*XEp`EU>L*YvS-74^ZcVnO^!@ z!cdEG=QyorB;8rqFtvE5z9A*$dAacF4)5J*mrES8GRoqP@Lm&-9e7B7%K7;JvNuP+8%O5Vz8Y1wmok_onj6I(?&QxN+eeiE4_uNqp;0gMdCA_3z*iX#$K223 zZfqZ>PGe4X4%xYTO(Dx(G4Rbg*LU#s4RE0G{H+r7St56fhsyM~HSZea5Arvjq-H)A zp(V0aWXFbo9Kc#8{L&6E*po9^4~b_bsq9r>r;IYIm0BPivEb~?Y1bW1_s)xzC`dFj zEz^~>zC8%ZC$8`7^`1u%3lia@TUJfukf3f%N?6^HJbJa2*Ae0>UGKq#%kqc;)Hc%z zHplrs`}J$xVZ0?Q(1YLr)BV0-Xj;SF!Lc~nXZ8i9eZeQ|)g z2W7e5n5Z<-FCFM4g_dAN85*99+5!#Gp#Ck2bxqPry+p~y9#L`3q7gBE%^^XcW|n=P znHeS2w$3Bp4BC4pfA@WCV3?-A=|ddAZ-!`oD*Jp&agoRDB{0+xsStRN-d&;K zpxU9hb7kTLcXrZ@G3Z7~Tvc`;fxgT=N9|cw6xi2ZX;;;hZlu(KQ*ghG5k*w zqIeSE8J>kqX0i^o#p_`>#9Jw-ve+qZo9Eeaw#BfawN0Dh%@I5yF3h@aTx!B|@YutS z{=ay8)3_wi{r$i5ouz47oHonc&P;o5EiU1Lr*X{8%u&l-88aotB}G(3@Jx$Ni&C0m zqEMNdEAEIWoH8XvAXijWMiCGZNDvSd`|*2x{tv(Z-_!d6uNU_N_}tg$y585s1Q55z zHW#QrR+rBnNU)!=ru!!~ERdLG_2WdAHmkLFdr#BrMANJ5LbGbzh4Yvl&^o7t2rk~6 zdP#;$@;M%dY{$T>A`K|qIqjw1Fi7CAKDk?PJrTD6yV_s)n9_ft9mrr=hn4{{S zPXK-d&an5-9~iKlUJ88E{)T9MB>%o}xB_3rf!KD;(do$vao|y)5V{9Zd9YLQNB+=> zy&>-cm@V{!tA;lu&%JiH-03D`eL#js3Wb$IORae+^NAzT@9AD3KGLsUSvS&%Pi7@8 zPJyrX#}8y#uzUD7eEt;JopdH7{&?@2@Ry(h3r!s9Tj*u?Mf%UtVQ6z?>&&Z%Nwh{P zspwP6qfH#(mfR{F18Z`t=^%8Xj}VSs3$;tB30b!LGM6?rM#T!)JB^FQVnrV===Io@ z7c>C4zv4qsJmKLGG?s7gH=h%93}!8kJ$C4E$-uv`huX^k^q+!WpDusgaSPu$a6{}F zQC)ocsm$$A8COIcmLtvufAAukDkR_{4q6u z{rF7WyMj5j?=ZcRM|r5&|3lLf5%@eOV^w5gDS{fBcg6+N-7hsi4pm&d8H|q86f7)z2J}| zsxvBKOwV+1{98h{X)O3y2c$}FabhCjT%pJD0jvJm$8yh#nTgg*GB-OY!n8PsYFgY) zIMl-UBd4+$W;~ggS`wBUOo;I;?r^C44Fe+w$MNEvAbs}s0j=oLq34--<7W;_-5!+0 zXqqNAYPu&TE4%A70$3&!W8t9HPKs9S(hhfh#PNRD7rOt8wIkWn^d%f!^a60}Qd!Ac zfG7V~*7Tnz9|R3NGCNQ6gx)c|!-y;dBp6A|? zYaO$T)idT+oBo$LOBBNWXhv!X103!T_9q^B;c$e~Eic$FKvk(*>ic0am-QW{C zsN#Wk+!fQT9}~OD3ID`)Db$+TS&UJ=3W;G6DG~Cl+S#P^Co<|@u@lRo`o31!-_gk~ z1-z67` z`g<`EEoi1JcM6XM9$TNiN@P9-9Iuz2c{e?koXx;PTHf6OUm{ zT!)3$nRR$ZtBAJqn5qY7ZXovD$6Wt~3;7s@aM^QFshFIBQbby}`IaPSoTLn=p|?K* zvbSSL-$Ntl^`fm-y3R2`C@iF&lU=DuFoDq>a9Jlp6vnI9)Hx_Mx2#Ds~Z@K&C@p2^aG|LC&9HJ)Jl`uju@Og#V@c8{FG&sY7DC)Gb8Edj5&y$= z!wE6htlpxLYwp-Hgs0CB=9-~m3i?{P;n!_dzy#^nZLR3S zESBu+wx8BCT54?;tDeWn4#=s-hf>`aD3o9(1DMGcclhp;X(W|Cehg2`f%?^?33Nb0 zWX@6NTWq#B^k%D-Sn_wQ9jEr(+quZZhNu_k1|X{E6YN*)l{XC2O1F^L2Oy{@eP%nA znZ@?{Ppa@k@0v|H(g3d=6=fG>`)zV7wim`_sP0#fTnla>JsvEa`%lHgMrX+?-%(-w zd*BnQ{qX9B@Q{1peGzMA7R)oKo!Pp_BgNrBKBJxac#rbb?mewbj;v2kTbGxSJIfZ* zjo9Yq9`gLTh>m)OG$i5`O3F6c&oynfM#wi$YZO<+AKyr_4yO#ZI4(Ao{(0Uy$+Lv33~W1{)9R8vA9@v*B9C$@6Q zQTP3QGzz-x@_hlI<9=kff*Yb?w4oao$XUzANAhkHJLd|MyA>moq@P5#8%QvKukV4H zleBI0R|Ufvis|=t4D1mJ*A*I7N)D_pJ@#C@v&aEz`hAO+x$z3Sd9g%nRcEFcufN+` z#w$)990bGwTW-naDduzh`%kN!Zv^)@duJQO1;H4{GeMz`Ae zU2B~~lFdDCki9bIum-lqn$aH*rFBGB&WXGYSR1Y-nOW>}<3sv44Mi)o+C1Z0k#Z4| zc2IqU*73HrcUTG!#WtA!1=B;u+rcjyZ<6j=e36}d-&6<2!M&;+nb@Lbc)A*w-_aZu z8TdzwxLF>zlD2ews%lmHqx0TMwWf}2AJ*UctN*l`Yy%<%GTr{U^E+>x(9c_?* zDUtRMm~Cl0AYr3)0y_JZwQOBVvYAnR>|}V2jM-Y@+-?V|CoR=nXt8t43x&;D=iG~b ztzrkGrF(aWYLXl$tK?^M#t=o)D0_L1tYD(S_Q6?DV_U~Fw|#MS%Z%a=Re%Y)w-2X0 zV6HK^C9w^;1MIsHdT2Syn!X!WF#hTMqsTwN*z-<6I5wmw?|RjTa?5M+np*veDmJ)f za=He)8|{4b(%t*Uz zAJ-kpkKSDt-|u-86hH7sj%QYJyKC}T!Q#Qi8J@n`-6*q!XX%xHKFWF@ya*i;##?De zpH*Er`Ns7)?j6F~;@gydgf!>`&Z_u6(aBEsxgjs_e>xms;}>oX8f%$;0X^luQ-R)I zw~Sv-Q~-|6oeF9AGkr$KkefVm<6PuyZ_S9#@vZI>4^M^8KG=Dm`^&}Xy6DV}BOQS` zi-rNEZ#yEIL!{}F(6)&miM8T=n$p&~UqU8B6~TmEpQ{du;4z0Fp4DxFr-*#teXh{% zlAW-VaqbbBB)6E#TAB&86m2w5qH(h-hM!pan_TE`qPbcaT=?=B;QTYq{;iqUp9jwO zLHF`sMlFXq)B8Ri?veldzr}0yQ1icj|L^1f)syctZe!Gr;dtVJwys)0)Mcnl*q>3b zJT(Vhg1q2RtZ^6v#eRiIMbRgleNcx`1!E2dY>CZkSr5_$Bb_fYXUrEG~B8 zCDOob@P|YOs(f=mi%@K5sdQhr@i&+|@nNJy0!Dq8fWoP(ckfxo&BfvBTpi(GSSCak z5L2e*?s)-d9OZYS-b|R2Ekcsk&Njb+C5@@DqM~X`v5+tC$ z%`*3Bk%y-jvMpZ#HZ0=6lffh&B~QK+uqVYvFTu@49%mwBCI@m|bo=>sl>2u7L2fTz zxlrp^+ZE1RPD4eHrijWoM<(LZ0_XSNX(KbgZgV@Bw~%;b#Ra~cY8ud5?bz+DORDZ$ zMDaLJev&g{1#jSw5ZUyoJeZv*KuERlJaqjcPR}<;_{e7wrWKl`3FmCLSF<-HS*SU; zJZ&JOeiFw0xwqQ20`4Iy(?-dxrl7N85J3B8_%m|<;!CoWD$vac>ve&Hp7Tcd%PCf0QazON6v&;i%XcrzcTSJ+l@b z4THvLmLR8%Kqe8x+-k0v00uUQ^-TZ7kGOM=X(oV0FMq7$`t3&{b4 zlAG%>j3r)LW&$W8+pC9>I-_paKmFhe4lv2X5{U~9PdeElq_)Mukl9$OP51iOZIEsW zF-sseo>sVk=0#F^a0jOs$HEl`($?Y~kx$y&9HD zQyD;Zt`?^NJ=A9R7wU1<5lKs%y6Ul0N{V?xy;_TNX*12j9>slLBksp?=3~>sWNxo6 zZdp|DbU=hDh(&(4ber&Ip6$Qeg^&pH99{#t@Ho)?tj6@Ai3UAO z+zMW8EKFJ}*&HRdDso-NIyVokf%O|I!~Dt~9#L{SPLtNWy2TdOC)`VlzwinC7 zx~$qbs&(n1Ms?6s))SHW1qpMHMeAJVMTP4Pp;#e4rqAS{V6Nx1XyJ@lVs0%4Tewdw zD;m)r-3t8mU(N7^BIb3vz*P65o9<>UP#aIt6^&&0b!JkOja@V#fl(i)nXA58KhiD) zBwOPryELu%9nFHuK#1F&Og)yBKjIlhl>^z77(v01-uhH1 z!@(Uk&7WSngO8{}CXRc`O6C9>>02`{x~=?z(gpus(2|f1!wdp_`Hm(;vdB?@fTV9C@nND~1@TT?TyVZ`Q%JhH-mrjC`&=u~(%CxMlymTfa;zuFoLl~0c zd|Vza|A;7y8=rm1H2dq0a8H7?@Z}UQ3*5pLvq&1EKAoGn$=ss5FU?Qs%zzNa(%%43 zg&f(L!YQ+a3DeVSm*7gOl%fV=U6^%ntn;eSP73GIQUc^pgNeV+1}o?fAL;tfy-sYl zAy^Xd&%JUoLK=`Hh0poIPZ-Au!}y9Xn2RkJdX0R}(?*BBd}3Z*ptap3-m6C@1G_Ry zpShDhR`B0;m6B)SI9UlLp@J>j3HYqC;a?r0yB3Xv?E9mwzBfdeO~AELZj`<*-WPMJ zj{1DJrsZdr^A3L9X~&BLarA&+$aNpN!^OYWKl8J;sK?^inFD-V`BMqjpw33MSGs;%iwsyzGCbvLRi1P$J4*hfFP zh&a4~>u7-c&AUFo1(x122mj~&p|=H>_JR+$UQpQVia?)b{m-tJPx(QcM?EE%R_1;~ zML*_U-)gbot%!GbgPo%vlxzqbqYB@DJ6Gae_S->0olS|m$dKpX@~9o_I9@w_DcZKI zgN2_+yj1)>DaO;~bxM0dpdtXHJW|$9j~!p-7_n>r?!sM$h`JI2rXl5$3`o#i=4=eP zd@$stT=If6%bRnJcgy$Xzrfg8DSGS`CR>0^D9sW&ayTnDh|36xK)PTCIUkEw+x4V) z)=b6oUE@#B4u*TbTZ!{9>yGm>qd&f6`WRfY@$5sGp+(S8 z(a~Z@!b#GuIhLZag1exD-%RQsD?NCpW2JG9zvqt~ZswhQ+03mbj(D4tSOy7mU^y2S zn0vv`LB_|Qa4)o+60ZGe82}eV9FDS$`Dqe;Eo;clq;v1Qy`hJR{*~$6Gd9xVc zQR4Mr!Yq1TpeGsr_k{lcLP8H(#B_u({_iA@@8lt_myrq2kv@-@Z&oN?%2{vlxw+)Y z?g?@Se?!hrosp&AXye>Du1)KoK_zm}{Jlj`fIz!#3Vl`ZcaiD7+=nI5858B?=2(0E z)bB0kZ#R2iGbF&MHPj(=B4jdB3;e+u&d&d!i+A2CS8;B3$q|511fr|&N?MCtR3U?W z(KLy)E*{mnRt{JYs4%o=T$A_twceWmgq;+j|BWJj*>Js(rB?Y2S+2XY>No4A1f!u? z&=SuoyW&f?GRbQumOmmTF+G1TrZL2Dy1^w?o#f`&7eXouaY)~1Ym9_p0U7g~}x zwFS|{ZZ3+j=89gU>`<`SA&jU?dcQu-MSJRWJd|>@PC%Y3)0FFjW!Bg|hZ<|TVH^fd zA15utC-p(jb44BdmgP(E)2OIV=^@$aPhhN-M7q8uX?n}^NJ5%Hg}mNtcbOEWF4E$k zaQje=8lTCnY-;G`IHNIVCarWXN$q@SL~&=6)}hG_OC}qefXKb0kan^Qm@t;+>|i!4 zbN}SgJN!=ZLe>+J=pdq+Z(P2ac{POPP|;R1z>4oyE;*)TZS2k_(ulJu>I-B?tesss zvm?Y={lRxO4wcJ5?^>NWr2GcVaa_!4EhrT~c%Q_)@MLsZFH z>g?BT=vhSISP+kMse6%pScCSb_S1W=FMQnwd=Ay&4`OM0H_3 zaA|atw{&WF%6}cwc`(yd8PR zZM6CBY@2C0@?6Ud#Bo>LL(}%CN3ADlRIBTBX6q3QG>?M3Q23qKgNTZL56Z@Tj{QSN z)fum%;H4M5M|(F9iL#FSFZK-q0YfcP;6l z<=7}YI502*kjF9}UryzOr-|*U#7H~5v;NvyMa!_4h;9(Pu7W_j}Lf}o`(N{b?@|2;2PB(;cR#Zug9E@dZeLM+f*f&=uqHtY0U)7hlvka$M|NT zFubzPQe5`92{;JNPeZf(b^YdvCg zHUY3ROpr9O#HU-+&)LwmQ4lq$7Wp!9$0qBPDy<}m;Ebg4PPLa=nKk{ zO#?a;C?GB$*32i{JeR79tR#jwY-|h0U4d>**7oOu))J%qtWJ7-OtM=}?38ZBwVyy& z=j+S-@D`!W5LGf|Ca{pN8`OB<&59)u!C>}@x(0ZZaUkHm?Zg;<=osDDPJ}~ooYPIT zzZVSY;1H~9pl{2ExW@^!!jM?)o*Ysh2v*ZIF6khLHfBSPogZe)=DqDox-8B}(oDad z!zUM6if2)Cq}-n;?;7`GN~>Ag?uQhp z`xY-QgpcdnD#FvxFULoPHsm+5u^fAxfDG^bk?nMs(8GS8H(8xOn27ToNY0V&ER0x^ zLe3aDzpj^c3tL)WGflN(mwrgOX*h*l$*)i9s%h9h{P!$eXM0)($byt=zzH8RH;Y#F z8bg*P;sgxs@ph@Xr@zA$t|aZ@B(NIaOej3CHFHbE5Z33q5HuQ;C+9GUY{80H>70%k z{REw*8nFw##`UdZ)64LPGPZ2Z#mq4wy)$&V+hBNcL*PKjF>s~d%|bD@#Zj!p`Bpe*^XoQIH2UPH%AzuU zofq`RLM$u`0Dj)e;&&_ok;HxqI@{~>Z^5AtR?S=Z+dC(^v;!^Rw~XeR=8!?G9=0A{ z+_Ag;8Lw4vysmLZehh0C^}411VAnnGS%WG8Q5q?s*kuXLaG z(g`EneAL`a0>4aI`aGBTgT{Eq2m?-(^Vpi#9R8?0~NIo<52hx{$+4rk}Tu z*sGDo01*otLfqjf-03|n%G0;VSEul}!AlRze$@CJ4=6~wGPNG{E93Bo@9eOSi1Rzg ztB<+wbMvxFC|!2?tAOa*{SFQ74@y=!m4-B8|J4PLpX^>vsFlH@=eO{Ru+*N$!ti}z z6_DKUG*fdE;{0=ZN5GjN-gMZTLGbC#VgT-5tQJ~h(}-MXz0OQ5Fphxn()xaA?RM$M zSLA%%mOd!VOI_6){xUi95nLc?BVy);DuZpB$i!9A&2JALNnf?#<8A}_Kj)HSBJ+Rk zG4?&|xe^n9+${R{OBTVy2^7dI!rAy*%oEz$KM6^u-muApxZmqK>Y#NEG1fDSr2x1r zdZj4}&i7lvi8~H9zLB#Z2Ed<#6T97x{YE=&860?ITIX=sEryZsb({0k*x)~Jt7y;P z4bEOwaL+xBY&GfnC>PttsE}8>oA>o7Em4}62Mp_Q6NSbl=UEIeH<<=K(oy@?GP*D0 zeY6S?$F$>H$J#%;Y1!b1SdNpb|U{uzrEc770t|3--kyuEdPg__86M6BI4FpA? zf1wA@3=mkk6-#AGZ<{?jk6_vipZ#KkC`5OJ-9=d8GDL>}4CFTiD|27YW)$Z7*KJ}v z+7avO{RMP_jsdmlJQQXe^!3qDn$jO+A#nBBESz1`wE4l6sDv6j9p0=P?`}rNTtC`0 zD{3VSf8A#Mx9jCiS<>M82GPa2tp&0vp$8v7?P5YKo4ZRdHWVc!=?e;-wCuuahfg<~ zM?BMaPIXD?p+1Mqow77i^HS7(O`!FRT`3%CZt$5zeMLzkg2&gmj;reRMw}#xq*$(^ zRi9E%$Dh1xmJlqeUAeOYF{Asbei0tMsUCYg0UQMGqgO{y(eS}q6Uf#W~ zJuN)&vTZ%-RThuvn6V+O>!)!KP59Oi&ounR&kvy>a(v4=!$0I49}u<(N8}m?I0|f* z30Z#G!2td-j&5$H+pcH;yJD0GW|;=` zN{sTf83%^esfT+h(yjgM6tPXV++|p~XM1QdXMixXwv84JEs|mJ4McbsaR=zD({e$ zlYzZ%P{$*$YL#ENmFoO+6FlH=2<2JqOHq|d4eF6A+PuzFkj1*DJ_0Jx6$rSd%DA9e zmx!%3a)<*Z)2newS)|w`m!DXj1STvZ!La@`XRTKymS@~AC(sfR&iK~=oQH3356*wZ znSDLmL3F%=lM7M!G~S)*T08Vsx}H9m8!ewnx<|xu#s%$byFu>!wYdrKApN*igehPi zKe8SR$GCe0N#L`yi7pw4VweuTHccW1&EaSx6ra$}cJj9~Aim>M=}J)(gBp6K$Qs^X z$odslF8QlBu%qsVOsv|ma;PH0BcYvI!+&?OkJ!YHKXaw}@gT)#pHOO6eC`vkXXsn*|L&A7^>-|js!P{0imVS^#|>*J5@ z<4W@)4J|(Tqva-9WcaUjcVGW{WGI( z@^b0!FZ{IneE)1~O4sg*)&!&BClUHy+T2ILr208Gv3z#9vrNyP|8061IhW5Q7LN9< zInx-9QEQCs^7M<%qjxPjMZo>a9&mI@aWBRj{PRG+P_^X>lXp+gW)E#R;qW|Du+gzD zEA`l0&0JmixPmnz$)X+9Zf7M%jiec)@zafkWMP~&Pe}D#m-P66$Cm6G^xHFq&Iw^Q0Hfz@0LF>)v%d&u^DQV}{uG@A&^p>}m7!URCn^_x6Wa=~eNUXN! zM>!GIFc2ngGnND-wpLB1On5g-rh*f?_W2I6UR!1QfDC7@wtE%YyLln0c+Kkd6x^0wJ_pI};LpMJ7k zUYg&%o;dy?%(k#@vBTMsVd~*@srZAU;yAKOQ8yDnbFs6R28-ygI4P;goaX7QjcC}& z`w2k$XXTHh;r0{7tRtgY8%c6XSM}l^qnqgdR@PA z+GVRel^vIEObe}DI=w1Zm^UDz{4tfyibHu5I&%yl!koOQb3Q9hHQnd8tjT}k#7VH@ zJN?^p6oA@@Qw$U8by2NfS-l`MQ|wC z>t(&YMem+K_8ZMF@E^OQjoutBF!BfJCHM3Bi7(}+{~qhz@#fs)pdSyj8muMja}PKh z`R9cp>>4-D|CF5{soOtXW89P8a^Ti9wDQp_4gq_m=~~OZEAneG^@~qV<>bq2YVxV= zzcvc=?UR&_lz_|`ah~>j!mZNTBGmN=acIgw)xC?4;F6-=#hL8S8cQDM*_?2+Wr?)s zQNs$%o#EP``(`+m`BU0F4ES(sB%(P zAK|aG(w?fy^6cW{`JOF*K?^Q?(mhZ)N7q-XPth%1#OM_CZqH;zFY5};;HZn&FY|@Q z&V;yqyY4=~f8TSP&@k11Hmuj-W&`&MR9mk4ceQ=vm1U^{8nY&MC;s!XF6hJ1me=(w zcCZjZe|}Z(MXBI3bI9*KVG1%@R6NW0WXJCQ%PP3bpJnX#zJ>M8Dfz$YHg}IUTOW&X z`}t(^A_DW!<%^ou0yhf^Id?RNRt!G@r0bd8;AVzNq{;eT-pQ(S^dCPHAV+rsysffN zJp4srBpz@sK0NRz$f)M|@1>Oi@~lHso8)7`q3`?&WrqGA`U-vhfr$&5U6OD<+l#^F zPe_8WOnSJFQ+K>ugo4bzdvRF>vXcU{=^3(x2CN?>wRe-t!Fyhq}_;^Re<%89Ic4Y5NPRn@vu@tzZ z=+k#w8_KO09&;uKa4?YZUUxlYvt|+1p0n>(3C-EwJtt#oCAfd+;?xKF%`Ej5OSalj z|EN!?rdXe41=+Z?SWOFl z{BYTEKv*;{k_HLOqaKvd%>eTe@Z$fT@zpG!|K&0NzdueNeD%V52aK}$I`6SB*}q8! zF~|Xr11(dl2o_i0XNmGaUvW@oZgL+@a_*uCt{k;VA`t@#k2wInJCAy#n0q;I$C-n2 z7n|Re3KLB__QAz+#AI@KuG#8%^Y@uanWj0|fu)HQ8Bch zr(G{FEux-mrD}@Nfl&MiKmt^#%yG&r11vST2de2gx!=K${g3vQMB0FcHNrh5qY1CQ z)|weqaCi$}HmEno(p?S2C_Rve_k~+IFF>K?`Se--L~7?+mg8N=InI)%8=TOW(w#Y8 zSKBO6tZ3$PUv^cWFx*(JT2Xn)uIo4(kQd+?#_QJR?)$J@~@JJ=g)UjMAPRDc`ERb4Cg&8(S;Wl~sGZ z5)<<Fy~)RD=YE)Hk!MUueyTdMHkth8q~w=87s4;?fy?JSZ_!n`uN!o0e#d zgW11gtRu?JUz}5}N27P856V0T6P>pjKUC$|IJAtF{-uRtUx?SY%4(yZPz~;~zKb@^ zY04c(2s?3J=G7G5izpe+=;?oIoPbamt&vx=pv9jg2TqFZoxA$frs>IowV^S89y@*2 zylb;j_;zCSahpr-Ki0y9d5cc2WvN1cQK{_J)MvIpW#Y6c6ymQFIcTHMW$g;YXB=v$ zXy~98H?Z+zTX4d#%=LGYo0$tLmL#y$ZZ=JbRdDLj$vBBNi`X%PA8($pN?HOvT-PDa z^qf44yw0Xs@{q3dbgGjQj>0oxS>N^qu!Y&F{aZhih;qH6uU~kd{tV8}MhmF^Up8XM z{;@1L6q#RYw-L4qOjxBhZdOn6>v%RkSlBImf^aQMP$_a;74Y?t3*TN`CL?d$439=Q zLVOis!^S&A<`K-RIm@Y<1*!Ftr9O2469XnFb3o(c$YR*ll^4;|V$nxCLiC!&Lue|m~A<8|I$6->t!zxM*q(JjTgV(gEe*cte-?wjt4>y zF`Zs$2*Yn0YQ_{)TY83;iu|aWaaNL$hLZ&V7QDWW!~uq4m_=(aytyQ}?f8R?20#(1(405aeItLB!&<=Nhg zTF>;Euuak!TL`Q%!P~6Wi9fzyGqdIMCx#`ob~@;gb+I0Y0PhefiKJ8!JWS9ChFBZw z8?f!GZh~K>p&%V4Oe(;VSm)k00Fu9m#OG?n75;sC?jTtxaIbG%0m`=u9JbnEQ#2# zY}@;xT5lBLF_AP8tCGKu!EF&q(M`gJFoXrHZr1Z@~=5ejweQmF$AM|(34&UTWd6$Zb4T_xcKzE{KImzAw6&0S8`pn4#HJPG9p zn+feTc5fEQdK$W-fw58u2A9o%Q+v3IM|8K(GARli1%=^^p`%(ZYmFM98M)*DA_(t@ z)4Nv*SgXx>=KQR@YsgQU5UzkqG9NWsqgv(&cAU7hPVr~OYz!{RGs!qDPSrK{*Zt^U z$2z?a*0w&pLtkGK*#G4AB5)Ek)X3Z)Y5uIV*3`1J1YBDhtl7dJ70Q)&lg_4Ou3JY& z88|^*vgB?D4j|3qAuJhz7HIm^@x_51MwN zSeHUNI7u`9Jw;@=FY;j1;7)7$!6X@IkS1-`FS(cSZX4(l-%)lrrPj>ivV}%Z0#y%(DtG%ZcKj3-rvh-y4eK&lOJaS z@O4|-k+>Rt9PZJXkPc7qsb6{?v(NPXb^AQ)he2rH-gY`8%dLtde(apMoL6ps^NSgi zQdwj??B?>YE2o@^3Q^3IaQptFeAsYV2r_r%12OWUEB2|ub82LttgdDfO0J1t_;CK- zRlx_GeIDUt`|y643HlF+y!D;U3>$WWUOpSANbQ`Ow{y`t{5%_w+SG6;?lZZn|IT9H zy|C39KJDVY_S&k8p3>^#l!CK_nC1h|nO>F=N@j%1SYYwRoOr=6@~j`SFgxxNPVD&R zRGd{B{c6<1CnI9X@|{~!C46)wBC#Q;dIai{7N9jfdC8`2`Co~4Q$Y@!6Z9Rhvnywo zlNR$SR`u<{#@@m8n@%F?g(fRA5XyCG}GZy7hO*dI^y zN3l|@&DJlV4=)zds&NQ@XQb2~Aw3_AeoH}|2oC7am~c(m>tUPbX8NFc-uC^EpE58vs~5M||-1M&*aWkrkyc9ilqI0Q;NDVr;)8wt_vHCqD~E zIvl=?5e#pnPOxd=99I4uKF~4hqk0o;jXiy#GQV)Pp)IkhYA&z4(U+4fiU=0+?&g54 zpL6bUoIS$-12f|qZv4>wMQ405g`EjiBj0h{r-vD|`tur6=^!YqD>C#4B5Nj^=6?in zb((eBpF9fVzY%Hh@sihQYcq68?^N>Z<~2e(sD17a#^Q@weUslM!{9Lb^*x9z7}Ce! zvo@>z;A6dY5;vVo8p|@se!Hf*XtK(ZprGJ2gJR@Kp)R~F?XKQb8amh^T8_;koJ<5v z%1w`Acb%)R1=}KyWq?C3#(nl~@A0mw8(+Owqj`W}QF?beI5T-6Sjy-`I`oba`ONUb zyBA7q=}v9j!rjq= zo%r+p-q&36VvPDuBz1a-ip{(%&rRmlpI3kEr5}D42{t}^%k1_)R4_j(I3M22-L_b=vE{rt^>iJDJ6;MO@l=^W8%lu zH0^araPzpfdsOGZSAEezHJBCZE)8Y&yKV{6z-Ea`$NOmrnBk5kxK)+J%E!B3)!z$m zkZRI3Ie;mB17u<3WQMNGlrEDbgoHJ!WAk@4FLU=THqmH@);tQw_h7B1Cj7 zJKiE~-h-7qqvwo4ST3s(>ma1Ml`*QoL)UHaqfl3uHGRWi@Z&8I?M!fd&((02lhQ!% z!l4Fplqj1lz5Tu-z8gicsGdlK)`HG$*grgVM}^2-Y)63p61#@YWwCq4<}El=O5r$- z%3n`oCjyL*up(OoHFP$OrcNGO0#b8e@{1eZCt4L0GF0Ib^N?u)6)DzM6?6&D7M*S?zl4&3q@l8!7m2-Q0DQ4`)KHYEf2HGW(JRtp@0d!n12&URK;j=iJMi`kSq5IXnJE|*U%gBvwbf!jGPjyn6Al}jbNEiMy6I?EMW;x^>|X# z&P^?0SdC+yq}|qXPm~3I)&jBU_=FHOZDnkXOS`?M?E+AL7}kZlNmqHUduN2!#?Qk_Y(+OJ_h40&tIdZF&dwl@&j@lo$S@3FwI;q&n#Gr22 z42bJX=W)dvR*~YIEJ{EAgt0SxxWhqy5RtO1f!kq5cco4CoGUFSY6`p2zxwlX3;!q# zp#B<=zplR$3oiZ%mk=I&COd)k^V~qvuKDCl{!blx{=N;j(l>eeKnHa4)e&U&2M7*w>hmlYqP&hni7CHuGr z+qJ=}S!66-vGQ6L^6iME-^VW{1_L_P1vErMK8~-xnlfb;CmwjTGW_{( z({`TqXY^wyn~L)Sj=jnWy3og6ygN<3rmuJ#YgtkMK2~z*oY-j&NZgzc7j`4mf{a(Y zJ7IA%O%B#=VyLu3*k4XuxpA8LrXe1XGzx92>lH=59l>Xt7?{q&bEc&yns_hzmwDt~ zM8a@Z$yB05W>XVuhhZS9`U(z}xn*kOzYyszoQ-Luh`kn_(euhL-NUpe>YS8ty#L|A&qs z;csud=#mQ{e$6ufca-98$;kj>!XG%>e%CG?|FYH6>JPun4K8ZSJqJ^h+=U#Nr zdzBH|J&v_i%Hno)p|DuFrWs&~C`P3Y@6`q9W{^{bXdQi97l%9Dt1z7zFP)&Y+VSeo z;A)e7pLRy!f=_dgdY70b{Y<^T>tS^+{QD}kae~$-zLfv);3ekt zrLDVf-4X1NYO&)~ofRQLw4b}Y@`Q+)p$ zuax@wl>?)7PT(`G5ACei+j@?w9DCS!JsfSR=cfKonaM4uw?@A8G(S0*dGl{kKSnre zFAbJ^Yy9Ng9!mb$WcuG`;8i;#{KS_J`l&`bo>vVx0{kA8Z#AUX$}{ zO9$&@EL${Qa(ZfzSHOa4UF+(GWp0f(*l6tK(GIgG%MjNfTL><6Rx>S|4JKPBPJ1{L zSAx7&J&b=Jh72KpS=jI(J)Ah%Hd?wt+H=AVXo)`YOdc+-|_V~@{c~O7vkF5 z>@amwl^2Zsru9Bl??Cs5?Li^(T&u?skub_-*DLe*Wr?WF$T~d_)gCjuBXoTkUYSwr z!i}>t|7BWrs{UNp5uq~J@YCOmeK{>AFBh#XvS{}JDkD?R{Y(D$EN1d6RkIexQMvUe z^5WDUhr*LyPCXugXC!X5$R;{lw@%A!3_d4r=&F}o{x{O*>-o!fe*V}0G3ftaequi? zx?v`@_s_2)wa3&|>){nwPGR0W$LLqYMWwq%3?uvWpU_S25R(bpvK^~hI&+1M zoDo+^`Z5O67q`+&&Tf!LcME*d&Jxl)riF`;K6ml1R<**?g+c3RW}NkeCkb1$EIXM^ z{e}_ioX*E1s_KgWAa zgHEKPcAV6c!}s?_OyW)YI9pOVh=3YMOV7tpm-e2E2eR5m;l0>L;|iDYv@d3{*o;yu z@0yYDVSS@p-H&SQF?eR*s`_wCnp>4DqZ~Ewrg)^QfBo#NrmfM{4CIz;&9Vf@$|#er zp4l-wy{fwqix`?IxZSC)Cg8+g1XpT8A96ZO@XC0$ewA1Nu*R1OQLMH3Z*rQ~7~(BR zhq?lo&rRNhxzwbSGYhJ)886m46_fR9E7J&+Ed8ZMI!vpSedaKpPstf{x7F?Z-A3gG zkcq0|#t6wUr)E^hHrKtvU5EEM&mFWGRi_B_2G8+j1}PegkQ{{n7D4o1c~Qd4nzXkf zqcq`=NrBr9u_3xIVKK1Yn)Jy@_C{qo+q(gq#ePuDUv39Br)6Oo8G#FXm0JZBa&*kj zMcr?FmyCyN>DM)049WN|>EIP|?y_^fnPeBfB=5K;F$nswidL+?=7cTGQyD^> zZ8wJYHCfW{^=8=)Yl{<>Z*v&nb{<0V{@nwg*^^;Zf_Tl=E3h7fk4Zh%6_Yl@qZG<& zhD7C4A>4QeE7+Hj_P`{SMO0z|nAv=4WU*qAo5QRTIB!nP3RM>-ARXdfKDqECDYds1 z*4;!y$}-ZQev`l>B|ea*ju`bU^1}-}#Clk}_+Bl10 zc|P2H2@5fnK)*Yn2vxR+SIpV_@#xh52!uUr&*8ly!E_haTsDWWzhEvss<7>CFc%CMTSJderaCmT zw@j#di;uI&Uv@U9WuZ1?g9cBpvyd6N@zLLk! zLp5=a&+v4|XNV6J=vtu{rx?rB*?>aY@=6{!<%Gem8(dzdd zW%f?OH*E$7nVM>EflY`86?M>)&y; zUs|{P=H5B=g%jn}pQaw>5y0dTcFjy0tdir}p-(xO#D}#jE^4jf@Az_(jJg-??K%?3 zGs`NwrkP-_$Cc3_-ejoBO{gJ`Q{TXhCLOmDq66=&7%nd*orKk7Co6xS_8}9A7(xgf z(@X|)k671^uxr)vQFv~2>`D}o>~?6ge*B@mwpSfFoD>{Wn;j2*9P(2!_fymMBwAKX zIyoM;4 zd+@o1nb+UWks_LEUOrmjHgCVRZ%PB)QX{Nt3G8p`sbs9B?^9K+y$zuEHf z$h;SmdrNi7$m52v;r)&P5bY(J3(Ow@tr`m~T#JS_cAll}IQ0)&wC3=y zQifRnQi5a{bvcdaUr(`T5?^;zE?aip-s=WU%3>f;V9RhHA!ZYH{&GlU|Ec;DJp70u zYkxWMIxAe~XEz;Qh;Z4oOhtWweNPUbOQRHp4|nBWiFdrLgK*x2>YbXJ(0mf#fSc-| z5jw~H4#BSMf^cf$nlj0@DeBr4fsQ%6?*ng|Ok;z;yZ#C0irjVZX9d=uv2$=Jb3`iJ zR7L*n0*7RHVqFxrseaiD;}lp^Vej=c{!!~;s}y1|x$xc7u|tWNlhvD%D$D(m_X+@? z-n(r5By+pzxZSxUJpH+)=)6Eq%Ks&(%iXr5OK_r(`Stfk7k{;XRC@1m#4RiI+0(3( zo0j*ldG9~&J9V0BQnqy`vLFd>?H0mIyb@zqwmZNJDUY?h(JE+M9q$jtpZ}4A?RRXk zGgdN*Dz(*QlTF$J7hz;}4lZ>=c=b2WR|(+55q%Bw-5!lS-&u3oUG~WOso{}FKu;pb z=a9Gm{EFt*^x2cbtVII$Ia`C8gy{*AP-pab|K}LsnSO(VfRQf`qTk%3oPaKcd!v6X z_4><1@^#Y8qIJ%V`Dkag0{q?Sb}{qy;CI$d*qlHQo7^eNKnNuJ7Wez)FLq_bGNY@q z(K*RwW}^;#+kboTzUT)r3sp@=peJLo>=0AOaLz-K@ytu883}2Ord}E`K~zk&A46WL z__lG#tKt&VsPSyURNvJjB9P(5nW20m?;B+lE1|vp_fyj@7pvN7nARS; z`kH^bMX!)^4UK=1f<{;e?+_M#{eAY~qcU=jAdbR~*u>sB&9am~J}_GB(%}`{7Xj$- zd_5F8t1v&RZ-d>)x3D#ZN`FlW{Y^IL@`EzCkue_-G5w9BXYhy_qVbPB&JFJAFr-`bfBca;#fFDs9qAprMq;h4lCTe8P{2kMEdMAqQqu&0G|-31tw zGqZwv$-Us!JwB~dZ!)#;4V}?lwg*;Z6k~ytq8$}_)22E-V|9xTm{`^l!`-do$I~kc z$|bab^#~7PSL4i_tkZ)XP%}VBcg`XH{Ob0m#%>onvFBVG6y@%QDp~Gc`|~UMmFdbE z*_dLXTcBw^R(D~?8YM>4J+?C@G&U8h+XZ6!NJfL>I`A`HS3&HSD3*~M(|UT9XvJ=A zgX{JLh`ZWH#B7PJHdWwB7|LXq=Tle=G-!}suUk(EKmGgsBRz&tD(ZSXdH^BNI5(m~76SoVzRF}CMbGSdh5@F3= zczjHv(XEcQe7bpV_vd2O6i(&;#h<@rBRYlU2?s5 z^|TmGa+uAp%9pFV`A*fY^A)#mMaj|D?=pjFe+)x4>pYthbFj;h&`)dVQzqqo7R32ADTJ7aczJ%J;kenA~6 zKkQ9Wqr$P)+}~Cth>YmB0`@aTUH;YM9bDMcX}Idy;3_Wgwr`N*y2nbJq98NImF-S; z%e$e#+UPvuB<{lE3l3_x#&^6r=^@N|ilfLY?81(_Z(470!mmblwTR;-sHVH3^YE;gZh|+gTBAUSL zVUaxfcwBIP1T!qN?h34A#d&LSyJ!CA{|1*srbmET~Ei6?j=K&!3tG`Dd?<9S{kD>)v9D#i`etWN(?Y9}0N9vb?E`Lr;A@{f^| z7h~Y9x~sL`ef`FY7ZSwPZFXVnWXOAlN!LP8LXQFr1?A1?yuj>&kmb2?{7^X8uIKxn z)|7QW$31uQcnF=tRm3Exxs6&@^^HrTlr`O#ulq+9w?l@m1l7-STxKR`UKanfQeCf) zf6XB?oi84XiS2Ub23WzLVp`z<7dl`v9E@f%0&)ad|U}>cu<@1C8E9?LVjWu z^n~|nmF6)D1Lza6XLMiv1m>Du*{sy-!`a)Rhfr+ca-5&wrC5=ALnOWKy|9+f0(34c zFN@I?-ib~GVn^1k#?j8&q&W6s;~*G_oQAC+9>$Zih|uSnKxTbuzsE;3-0WEB*DX0OLQbJoSO+ z3PCc)D(N|f9|s41fl8IL|IZh zEd(^YH!yNEz2u7j(pxW_?|MGu23y7fbKu>B=>el~$6?VMVf^yaIM!e(9Vt-6fkf2U z9kW3xva6SCH;VmvDC``RA>$WL8ing>V9#c&z_`dFz!fFBR(TB8KC4-`1DT9C)bu5e ziyfSe{FZ-Ok#GJ?{nRK-P{P0D0e*dYaQAO9-6pOS(i1eO&Z+QLE7m4RcUPkWMiW=@ zbM1CR$;M=Va8$XmieO=vP5*v}+^=H&-qgayq$Px8FM;aQOI*8F6Z)Mf;m)M+5R~7X z=DQzfwj^zoAeY%c*y2syUhs7jwCVjdwR~!)iC4PPas2F;ZThPXfI8ZeVU`XC%tb`7 zk|;IF;F?_cgK45sfZcCaMLX+92M-)Ocy6n*J_>(GDT(x1qD*043Qp8edk63N?BC!MeV%(ZRJy{fgZAhvBo zTS_uIC*~VTm_Uo`hf$#z^im{xsddfKN**=}hsAidc;bK7HC+d_hidDM6OI}-cz{A4 z4qFw#ri%_%7e6So)Z7UlZ0t8)jj^ny;)9$GWBl?TDlDSTq_i^q+?_ZOahb2@^Wutq z2_6eVLrADDt$i>!f&bAOHyn$!k~o~4elOW0{A2PFm|x;hUg5?6vMlvomd~t(NSxvj z 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%0A-9r?U#Q&OGu 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