From aa2dae9c80dd0dfcc143c277d3e780889e075182 Mon Sep 17 00:00:00 2001 From: DawudOsman Date: Tue, 18 Nov 2025 14:34:06 +0900 Subject: [PATCH 1/4] merge Luna with Kanzen --- .../ContentModel.xcdatamodel/contents | 16 + .../Controllers/KanzenRunnerController.swift | 73 +++ Kanzen/KanzenEngine/Model/KanzenEngine.swift | 56 ++ .../Model/KanzenModuleRunner.swift | 174 +++++ .../Model/KanzenOutputFormatter.swift | 8 + Kanzen/KanzenEngine/Utils/Bundle/bundle.js | 6 + .../KanzenEngine/Utils/ErrorDefinitions.swift | 68 ++ .../Utils/Extensions/JavaScriptCore.swift | 168 +++++ .../Controllers/ModuleManager.swift | 179 ++++++ Kanzen/KanzenModule/Models/Module.swift | 45 ++ Kanzen/Models/Settings.swift | 65 ++ Kanzen/Models/favouriteManager.swift | 93 +++ Kanzen/Models/mangaData.swift | 34 + Kanzen/Models/pageData.swift | 111 ++++ Kanzen/Models/readerManager.swift | 447 +++++++++++++ Kanzen/Models/readingMode.swift | 35 + Kanzen/Views/Browse/browseView.swift | 10 + Kanzen/Views/Browse/kanzenModuleView.swift | 397 ++++++++++++ Kanzen/Views/Library/Library.swift | 68 ++ Kanzen/Views/MainMenu.swift | 24 + .../Reader/Paged/pagedViewController.swift | 208 ++++++ .../WebToon/webToonViewController.swift | 597 ++++++++++++++++++ Kanzen/Views/Reader/readerManagerView.swift | 194 ++++++ Kanzen/Views/Reader/utils/chapterList.swift | 86 +++ .../Reader/utils/readerSettingsView.swift | 23 + Kanzen/Views/Search/search.swift | 156 +++++ Kanzen/Views/Settings/kanzenSettings.swift | 47 ++ .../subSettings/kanzenGeneralSettings.swift | 36 ++ Kanzen/Views/Util/circularLoading.swift | 36 ++ Kanzen/Views/Util/contentCell.swift | 86 +++ Kanzen/Views/Util/contentView.swift | 347 ++++++++++ Kanzen/Views/Util/customSlider.swift | 67 ++ Kanzen/Views/Util/favouriteViewWrapper.swift | 49 ++ .../mangaModel.xcdatamodel/contents | 16 + Luna.xcodeproj/project.pbxproj | 54 +- .../xcshareddata/swiftpm/Package.resolved | 12 +- .../xcshareddata/xcschemes/Luna.xcscheme | 6 +- Luna/SoraApp.swift | 42 +- Luna/Views/SettingsView.swift | 11 +- 39 files changed, 4138 insertions(+), 12 deletions(-) create mode 100644 ContentModel.xcdatamodeld/ContentModel.xcdatamodel/contents create mode 100644 Kanzen/KanzenEngine/Controllers/KanzenRunnerController.swift create mode 100644 Kanzen/KanzenEngine/Model/KanzenEngine.swift create mode 100644 Kanzen/KanzenEngine/Model/KanzenModuleRunner.swift create mode 100644 Kanzen/KanzenEngine/Model/KanzenOutputFormatter.swift create mode 100644 Kanzen/KanzenEngine/Utils/Bundle/bundle.js create mode 100644 Kanzen/KanzenEngine/Utils/ErrorDefinitions.swift create mode 100644 Kanzen/KanzenEngine/Utils/Extensions/JavaScriptCore.swift create mode 100644 Kanzen/KanzenModule/Controllers/ModuleManager.swift create mode 100644 Kanzen/KanzenModule/Models/Module.swift create mode 100644 Kanzen/Models/Settings.swift create mode 100644 Kanzen/Models/favouriteManager.swift create mode 100644 Kanzen/Models/mangaData.swift create mode 100644 Kanzen/Models/pageData.swift create mode 100644 Kanzen/Models/readerManager.swift create mode 100644 Kanzen/Models/readingMode.swift create mode 100644 Kanzen/Views/Browse/browseView.swift create mode 100644 Kanzen/Views/Browse/kanzenModuleView.swift create mode 100644 Kanzen/Views/Library/Library.swift create mode 100644 Kanzen/Views/MainMenu.swift create mode 100644 Kanzen/Views/Reader/Paged/pagedViewController.swift create mode 100644 Kanzen/Views/Reader/WebToon/webToonViewController.swift create mode 100644 Kanzen/Views/Reader/readerManagerView.swift create mode 100644 Kanzen/Views/Reader/utils/chapterList.swift create mode 100644 Kanzen/Views/Reader/utils/readerSettingsView.swift create mode 100644 Kanzen/Views/Search/search.swift create mode 100644 Kanzen/Views/Settings/kanzenSettings.swift create mode 100644 Kanzen/Views/Settings/subSettings/kanzenGeneralSettings.swift create mode 100644 Kanzen/Views/Util/circularLoading.swift create mode 100644 Kanzen/Views/Util/contentCell.swift create mode 100644 Kanzen/Views/Util/contentView.swift create mode 100644 Kanzen/Views/Util/customSlider.swift create mode 100644 Kanzen/Views/Util/favouriteViewWrapper.swift create mode 100644 Kanzen/mangaModel.xcdatamodeld/mangaModel.xcdatamodel/contents diff --git a/ContentModel.xcdatamodeld/ContentModel.xcdatamodel/contents b/ContentModel.xcdatamodeld/ContentModel.xcdatamodel/contents new file mode 100644 index 00000000..6b5a2177 --- /dev/null +++ b/ContentModel.xcdatamodeld/ContentModel.xcdatamodel/contents @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Kanzen/KanzenEngine/Controllers/KanzenRunnerController.swift b/Kanzen/KanzenEngine/Controllers/KanzenRunnerController.swift new file mode 100644 index 00000000..13f90528 --- /dev/null +++ b/Kanzen/KanzenEngine/Controllers/KanzenRunnerController.swift @@ -0,0 +1,73 @@ +// +// KanzenRunnerController.swift +// Kanzen +// +// Created by Dawud Osman on 12/05/2025. +// +import Foundation +import JavaScriptCore +class KanzenRunnerController +{ + private let moduleRunner: KanzenModuleRunner + private let outputFormatter: KanzenOutputFormatter + init(moduleRunner: KanzenModuleRunner, outputFormatter: KanzenOutputFormatter) { + self.moduleRunner = moduleRunner + self.outputFormatter = outputFormatter + } + func loadScript(_script: String) throws + { + try moduleRunner.loadScript(_script) + } + func getChapterImages(params:Any,completion: @escaping ([String]?) -> Void) + { + moduleRunner.getChapterImages(params: params) + { + jsResult, error in + guard let result = jsResult?.toArray() as? [String] else { + completion(nil) + return + } + completion(result) + } + } + func getChapters(params:Any, completion: @escaping ([String:Any]?) -> Void ) + { + moduleRunner.getChapters(params: params){ + jsResult, error in + guard let result = jsResult?.toDictionary() as? [String:Any] else + { + completion(nil) + return + } + completion(result) + } + } + func getContentData(params:Any, completion: @escaping ([String:Any]?)-> Void) + { + + moduleRunner.getContentData(params: params) + { + jsResult, error in + guard let result = jsResult?.toDictionary() as? [String:Any] else + { + completion(nil) + return + } + completion(result) + } + } + func searchInput(_input: String,page:Int = 0, completion: @escaping ([[String:Any]]?) -> Void) + { + moduleRunner.searchContent(input: _input,page: page) + { + jsResult,error in + guard let result = jsResult?.toArray() as? [[String:Any]] else { + completion(nil) + return + } + completion(result) + + } + } +} + diff --git a/Kanzen/KanzenEngine/Model/KanzenEngine.swift b/Kanzen/KanzenEngine/Model/KanzenEngine.swift new file mode 100644 index 00000000..54bc6207 --- /dev/null +++ b/Kanzen/KanzenEngine/Model/KanzenEngine.swift @@ -0,0 +1,56 @@ +// +// KanzenEngine.swift +// Kanzen +// +// Created by Dawud Osman on 12/05/2025. +// +import SwiftUI +class KanzenEngine: ObservableObject +{ + private let controller: KanzenRunnerController + init() { + Logger.shared.log("Launching Kanzen Engine",type: "General") + let moduleRunner = KanzenModuleRunner() + let outputFormatter = KanzenOutputFormatter() + self.controller = KanzenRunnerController(moduleRunner: moduleRunner, outputFormatter: outputFormatter) + Logger.shared.log("Successfully Kanzen Launched Engine",type: "General") + } + func loadScript(_ script: String) throws { + Logger.shared.log("Loading Module Script",type: "General") + try self.controller.loadScript(_script: script) + Logger.shared.log("Successfully Loaded Module Script",type: "General") + } + func getContentData(params:Any?, completion: @escaping ([String:Any]?) -> Void) + { + controller.getContentData(params: params) + { + result in + + completion(result) + } + } + func getChapterImages(params:Any?, completion: @escaping ([String]?)-> Void) + { + + controller.getChapterImages(params: params){ + result in + completion(result) + } + } + func getChapters(params: Any?, completion: @escaping ([String:Any]?)-> Void) + { + controller.getChapters(params: params){ + result in + completion(result) + } + } + func searchInput(_ input: String,page: Int = 0, completion: @escaping ([[String:Any]]?) -> Void) -> Void { + controller.searchInput(_input: input,page: page) + { + result in + + completion(result) + + } + } +} diff --git a/Kanzen/KanzenEngine/Model/KanzenModuleRunner.swift b/Kanzen/KanzenEngine/Model/KanzenModuleRunner.swift new file mode 100644 index 00000000..f2c955e7 --- /dev/null +++ b/Kanzen/KanzenEngine/Model/KanzenModuleRunner.swift @@ -0,0 +1,174 @@ +// +// KanzenModuleRunner.swift +// Kanzen +// +// Created by Dawud Osman on 12/05/2025. +// +import Foundation +import JavaScriptCore +class KanzenModuleRunner +{ + private let contextQueue: DispatchQueue = DispatchQueue(label: "com.Kanzen.Module.Runner.Context") + private var jsContext: JSContext? + private var lastJSException: String? + func getChapterImages(params:Any, completion: @escaping (JSValue?,Error?) -> Void) + { + guard let context = jsContext as? JSContext else { + completion(nil, NSError(domain: "JSContext", code: 1, userInfo: [NSLocalizedDescriptionKey: "JS function not found"])) + return + + } + guard let chaptersFunc = context.objectForKeyedSubscript("getChapterImages") else { + completion(nil, NSError(domain: "JSContext", code: 1, userInfo: [NSLocalizedDescriptionKey: "JS function not found"])) + return + } + guard let promise = chaptersFunc.call(withArguments: [params]) else { + completion(nil, NSError(domain: "JSContext", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to call JS async function"])) + return + } + // Prepare resolve and reject blocks + let resolveBlock: @convention(block) (JSValue) -> Void = { result in + + completion(result, nil) + } + let rejectBlock: @convention(block) (JSValue) -> Void = { error in + + let err = NSError(domain: "JSContext", code: 3, userInfo: [NSLocalizedDescriptionKey: error.toString()]) + completion(nil, err) + } + let resolveCallback = JSValue(object: resolveBlock, in: context) + let rejectCallback = JSValue(object: rejectBlock, in: context) + + // Attach callbacks to the Promise + promise.invokeMethod("then", withArguments: [resolveCallback as Any]) + promise.invokeMethod("catch", withArguments: [rejectCallback as Any]) + } + func getChapters(params:Any, completion: @escaping (JSValue?,Error?) -> Void) + { + guard let context = jsContext as? JSContext else { + completion(nil, NSError(domain: "JSContext", code: 1, userInfo: [NSLocalizedDescriptionKey: "JS function not found"])) + return + + } + guard let chaptersFunc = context.objectForKeyedSubscript("getChapters") else { + completion(nil, NSError(domain: "JSContext", code: 1, userInfo: [NSLocalizedDescriptionKey: "JS function not found"])) + return + } + guard let promise = chaptersFunc.call(withArguments: [params]) else { + completion(nil, NSError(domain: "JSContext", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to call JS async function"])) + return + } + // Prepare resolve and reject blocks + let resolveBlock: @convention(block) (JSValue) -> Void = { result in + + completion(result, nil) + } + let rejectBlock: @convention(block) (JSValue) -> Void = { error in + + let err = NSError(domain: "JSContext", code: 3, userInfo: [NSLocalizedDescriptionKey: error.toString()]) + completion(nil, err) + } + let resolveCallback = JSValue(object: resolveBlock, in: context) + let rejectCallback = JSValue(object: rejectBlock, in: context) + + // Attach callbacks to the Promise + promise.invokeMethod("then", withArguments: [resolveCallback as Any]) + promise.invokeMethod("catch", withArguments: [rejectCallback as Any]) + } + + func getContentData(params:Any, completion: @escaping (JSValue?,Error?) -> Void) + { + + guard let context = jsContext as? JSContext else { + completion(nil, NSError(domain: "JSContext", code: 1, userInfo: [NSLocalizedDescriptionKey: "JS function not found"])) + return + } + + guard let contentDataFunc = context.objectForKeyedSubscript("getContentData") else { + completion(nil, NSError(domain: "JSContext", code: 1, userInfo: [NSLocalizedDescriptionKey: "JS function not found"])) + return + } + guard let promise = contentDataFunc.call(withArguments: [params]) else { + completion(nil, NSError(domain: "JSContext", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to call JS async function"])) + return + } + // Prepare resolve and reject blocks + let resolveBlock: @convention(block) (JSValue) -> Void = { result in + + completion(result, nil) + } + let rejectBlock: @convention(block) (JSValue) -> Void = { error in + + let err = NSError(domain: "JSContext", code: 3, userInfo: [NSLocalizedDescriptionKey: error.toString()]) + completion(nil, err) + } + let resolveCallback = JSValue(object: resolveBlock, in: context) + let rejectCallback = JSValue(object: rejectBlock, in: context) + + // Attach callbacks to the Promise + promise.invokeMethod("then", withArguments: [resolveCallback as Any]) + promise.invokeMethod("catch", withArguments: [rejectCallback as Any]) + } + + func searchContent(input:String, page:Int = 0,completion: @escaping(JSValue?,Error?) -> Void) + { + + guard let context = jsContext as? JSContext else { + completion(nil, NSError(domain: "JSContext", code: 1, userInfo: [NSLocalizedDescriptionKey: "JS function not found"])) + return + } + + + guard let searchFunc = context.objectForKeyedSubscript("searchContent") else { + completion(nil, NSError(domain: "JSContext", code: 1, userInfo: [NSLocalizedDescriptionKey: "JS function not found"])) + return + } + + guard let promise = searchFunc.call(withArguments: [input,page]) else { + completion(nil, NSError(domain: "JSContext", code: 2, userInfo: [NSLocalizedDescriptionKey: "Failed to call JS async function"])) + return + } + + // Prepare resolve and reject blocks + let resolveBlock: @convention(block) (JSValue) -> Void = { result in + + completion(result, nil) + } + let rejectBlock: @convention(block) (JSValue) -> Void = { error in + + let err = NSError(domain: "JSContext", code: 3, userInfo: [NSLocalizedDescriptionKey: error.toString()]) + completion(nil, err) + } + let resolveCallback = JSValue(object: resolveBlock, in: context) + let rejectCallback = JSValue(object: rejectBlock, in: context) + + // Attach callbacks to the Promise + promise.invokeMethod("then", withArguments: [resolveCallback as Any]) + promise.invokeMethod("catch", withArguments: [rejectCallback as Any]) + } + func setUpEnvironMent() + { + jsContext = JSContext() + jsContext?.exceptionHandler = { _, exception in + print("JS Error: \(exception?.toString() ?? "unknown error")") + Logger.shared.log( "JS Error: \(exception?.toString() ?? "unknown error")",type: "Error") + self.lastJSException = "JS Error: \(exception?.toString() ?? "unknown error")" + } + jsContext?.setUpJSEnvirontment() + } + func loadScript(_ script: String) throws + { + + lastJSException = nil + setUpEnvironMent() + jsContext?.evaluateScript(script) + + + if let exception = self.lastJSException + { + + let errorMessage = exception + throw ScriptExecutionError.scriptLoadError(errorMessage) + } + } +} diff --git a/Kanzen/KanzenEngine/Model/KanzenOutputFormatter.swift b/Kanzen/KanzenEngine/Model/KanzenOutputFormatter.swift new file mode 100644 index 00000000..6249211a --- /dev/null +++ b/Kanzen/KanzenEngine/Model/KanzenOutputFormatter.swift @@ -0,0 +1,8 @@ +// +// KanzenOutputFormatter.swift +// Kanzen +// +// Created by Dawud Osman on 12/05/2025. +// + +class KanzenOutputFormatter{} diff --git a/Kanzen/KanzenEngine/Utils/Bundle/bundle.js b/Kanzen/KanzenEngine/Utils/Bundle/bundle.js new file mode 100644 index 00000000..838e51e6 --- /dev/null +++ b/Kanzen/KanzenEngine/Utils/Bundle/bundle.js @@ -0,0 +1,6 @@ +var KanzenBundle=(()=>{var _e=Object.create;var Au=Object.defineProperty;var Me=Object.getOwnPropertyDescriptor;var Be=Object.getOwnPropertyNames;var Pe=Object.getPrototypeOf,Ve=Object.prototype.hasOwnProperty;var Ue=(u,e)=>()=>(e||u((e={exports:{}}).exports,e),e.exports),ru=(u,e)=>{for(var t in e)Au(u,t,{get:e[t],enumerable:!0})},B0=(u,e,t,a)=>{if(e&&typeof e=="object"||typeof e=="function")for(let r of Be(e))!Ve.call(u,r)&&r!==t&&Au(u,r,{get:()=>e[r],enumerable:!(a=Me(e,r))||a.enumerable});return u};var $=(u,e,t)=>(t=u!=null?_e(Pe(u)):{},B0(e||!u||!u.__esModule?Au(t,"default",{value:u,enumerable:!0}):t,u)),Oe=u=>B0(Au({},"__esModule",{value:!0}),u);var Q=Ue(($a,he)=>{he.exports={trueFunc:function(){return!0},falseFunc:function(){return!1}}});var g1={};ru(g1,{cssSelect:()=>R0,htmlparser2:()=>m0});var m0={};ru(m0,{DefaultHandler:()=>P,DomHandler:()=>P,DomUtils:()=>eu,ElementType:()=>ku,Parser:()=>H,QuoteType:()=>D,Tokenizer:()=>z,createDocumentStream:()=>jt,createDomStream:()=>zt,getFeed:()=>xu,parseDOM:()=>le,parseDocument:()=>be,parseFeed:()=>Qt});var Tu=new Uint16Array('\u1D41<\xD5\u0131\u028A\u049D\u057B\u05D0\u0675\u06DE\u07A2\u07D6\u080F\u0A4A\u0A91\u0DA1\u0E6D\u0F09\u0F26\u10CA\u1228\u12E1\u1415\u149D\u14C3\u14DF\u1525\0\0\0\0\0\0\u156B\u16CD\u198D\u1C12\u1DDD\u1F7E\u2060\u21B0\u228D\u23C0\u23FB\u2442\u2824\u2912\u2D08\u2E48\u2FCE\u3016\u32BA\u3639\u37AC\u38FE\u3A28\u3A71\u3AE0\u3B2E\u0800EMabcfglmnoprstu\\bfms\x7F\x84\x8B\x90\x95\x98\xA6\xB3\xB9\xC8\xCFlig\u803B\xC6\u40C6P\u803B&\u4026cute\u803B\xC1\u40C1reve;\u4102\u0100iyx}rc\u803B\xC2\u40C2;\u4410r;\uC000\u{1D504}rave\u803B\xC0\u40C0pha;\u4391acr;\u4100d;\u6A53\u0100gp\x9D\xA1on;\u4104f;\uC000\u{1D538}plyFunction;\u6061ing\u803B\xC5\u40C5\u0100cs\xBE\xC3r;\uC000\u{1D49C}ign;\u6254ilde\u803B\xC3\u40C3ml\u803B\xC4\u40C4\u0400aceforsu\xE5\xFB\xFE\u0117\u011C\u0122\u0127\u012A\u0100cr\xEA\xF2kslash;\u6216\u0176\xF6\xF8;\u6AE7ed;\u6306y;\u4411\u0180crt\u0105\u010B\u0114ause;\u6235noullis;\u612Ca;\u4392r;\uC000\u{1D505}pf;\uC000\u{1D539}eve;\u42D8c\xF2\u0113mpeq;\u624E\u0700HOacdefhilorsu\u014D\u0151\u0156\u0180\u019E\u01A2\u01B5\u01B7\u01BA\u01DC\u0215\u0273\u0278\u027Ecy;\u4427PY\u803B\xA9\u40A9\u0180cpy\u015D\u0162\u017Aute;\u4106\u0100;i\u0167\u0168\u62D2talDifferentialD;\u6145leys;\u612D\u0200aeio\u0189\u018E\u0194\u0198ron;\u410Cdil\u803B\xC7\u40C7rc;\u4108nint;\u6230ot;\u410A\u0100dn\u01A7\u01ADilla;\u40B8terDot;\u40B7\xF2\u017Fi;\u43A7rcle\u0200DMPT\u01C7\u01CB\u01D1\u01D6ot;\u6299inus;\u6296lus;\u6295imes;\u6297o\u0100cs\u01E2\u01F8kwiseContourIntegral;\u6232eCurly\u0100DQ\u0203\u020FoubleQuote;\u601Duote;\u6019\u0200lnpu\u021E\u0228\u0247\u0255on\u0100;e\u0225\u0226\u6237;\u6A74\u0180git\u022F\u0236\u023Aruent;\u6261nt;\u622FourIntegral;\u622E\u0100fr\u024C\u024E;\u6102oduct;\u6210nterClockwiseContourIntegral;\u6233oss;\u6A2Fcr;\uC000\u{1D49E}p\u0100;C\u0284\u0285\u62D3ap;\u624D\u0580DJSZacefios\u02A0\u02AC\u02B0\u02B4\u02B8\u02CB\u02D7\u02E1\u02E6\u0333\u048D\u0100;o\u0179\u02A5trahd;\u6911cy;\u4402cy;\u4405cy;\u440F\u0180grs\u02BF\u02C4\u02C7ger;\u6021r;\u61A1hv;\u6AE4\u0100ay\u02D0\u02D5ron;\u410E;\u4414l\u0100;t\u02DD\u02DE\u6207a;\u4394r;\uC000\u{1D507}\u0100af\u02EB\u0327\u0100cm\u02F0\u0322ritical\u0200ADGT\u0300\u0306\u0316\u031Ccute;\u40B4o\u0174\u030B\u030D;\u42D9bleAcute;\u42DDrave;\u4060ilde;\u42DCond;\u62C4ferentialD;\u6146\u0470\u033D\0\0\0\u0342\u0354\0\u0405f;\uC000\u{1D53B}\u0180;DE\u0348\u0349\u034D\u40A8ot;\u60DCqual;\u6250ble\u0300CDLRUV\u0363\u0372\u0382\u03CF\u03E2\u03F8ontourIntegra\xEC\u0239o\u0274\u0379\0\0\u037B\xBB\u0349nArrow;\u61D3\u0100eo\u0387\u03A4ft\u0180ART\u0390\u0396\u03A1rrow;\u61D0ightArrow;\u61D4e\xE5\u02CAng\u0100LR\u03AB\u03C4eft\u0100AR\u03B3\u03B9rrow;\u67F8ightArrow;\u67FAightArrow;\u67F9ight\u0100AT\u03D8\u03DErrow;\u61D2ee;\u62A8p\u0241\u03E9\0\0\u03EFrrow;\u61D1ownArrow;\u61D5erticalBar;\u6225n\u0300ABLRTa\u0412\u042A\u0430\u045E\u047F\u037Crrow\u0180;BU\u041D\u041E\u0422\u6193ar;\u6913pArrow;\u61F5reve;\u4311eft\u02D2\u043A\0\u0446\0\u0450ightVector;\u6950eeVector;\u695Eector\u0100;B\u0459\u045A\u61BDar;\u6956ight\u01D4\u0467\0\u0471eeVector;\u695Fector\u0100;B\u047A\u047B\u61C1ar;\u6957ee\u0100;A\u0486\u0487\u62A4rrow;\u61A7\u0100ct\u0492\u0497r;\uC000\u{1D49F}rok;\u4110\u0800NTacdfglmopqstux\u04BD\u04C0\u04C4\u04CB\u04DE\u04E2\u04E7\u04EE\u04F5\u0521\u052F\u0536\u0552\u055D\u0560\u0565G;\u414AH\u803B\xD0\u40D0cute\u803B\xC9\u40C9\u0180aiy\u04D2\u04D7\u04DCron;\u411Arc\u803B\xCA\u40CA;\u442Dot;\u4116r;\uC000\u{1D508}rave\u803B\xC8\u40C8ement;\u6208\u0100ap\u04FA\u04FEcr;\u4112ty\u0253\u0506\0\0\u0512mallSquare;\u65FBerySmallSquare;\u65AB\u0100gp\u0526\u052Aon;\u4118f;\uC000\u{1D53C}silon;\u4395u\u0100ai\u053C\u0549l\u0100;T\u0542\u0543\u6A75ilde;\u6242librium;\u61CC\u0100ci\u0557\u055Ar;\u6130m;\u6A73a;\u4397ml\u803B\xCB\u40CB\u0100ip\u056A\u056Fsts;\u6203onentialE;\u6147\u0280cfios\u0585\u0588\u058D\u05B2\u05CCy;\u4424r;\uC000\u{1D509}lled\u0253\u0597\0\0\u05A3mallSquare;\u65FCerySmallSquare;\u65AA\u0370\u05BA\0\u05BF\0\0\u05C4f;\uC000\u{1D53D}All;\u6200riertrf;\u6131c\xF2\u05CB\u0600JTabcdfgorst\u05E8\u05EC\u05EF\u05FA\u0600\u0612\u0616\u061B\u061D\u0623\u066C\u0672cy;\u4403\u803B>\u403Emma\u0100;d\u05F7\u05F8\u4393;\u43DCreve;\u411E\u0180eiy\u0607\u060C\u0610dil;\u4122rc;\u411C;\u4413ot;\u4120r;\uC000\u{1D50A};\u62D9pf;\uC000\u{1D53E}eater\u0300EFGLST\u0635\u0644\u064E\u0656\u065B\u0666qual\u0100;L\u063E\u063F\u6265ess;\u62DBullEqual;\u6267reater;\u6AA2ess;\u6277lantEqual;\u6A7Eilde;\u6273cr;\uC000\u{1D4A2};\u626B\u0400Aacfiosu\u0685\u068B\u0696\u069B\u069E\u06AA\u06BE\u06CARDcy;\u442A\u0100ct\u0690\u0694ek;\u42C7;\u405Eirc;\u4124r;\u610ClbertSpace;\u610B\u01F0\u06AF\0\u06B2f;\u610DizontalLine;\u6500\u0100ct\u06C3\u06C5\xF2\u06A9rok;\u4126mp\u0144\u06D0\u06D8ownHum\xF0\u012Fqual;\u624F\u0700EJOacdfgmnostu\u06FA\u06FE\u0703\u0707\u070E\u071A\u071E\u0721\u0728\u0744\u0778\u078B\u078F\u0795cy;\u4415lig;\u4132cy;\u4401cute\u803B\xCD\u40CD\u0100iy\u0713\u0718rc\u803B\xCE\u40CE;\u4418ot;\u4130r;\u6111rave\u803B\xCC\u40CC\u0180;ap\u0720\u072F\u073F\u0100cg\u0734\u0737r;\u412AinaryI;\u6148lie\xF3\u03DD\u01F4\u0749\0\u0762\u0100;e\u074D\u074E\u622C\u0100gr\u0753\u0758ral;\u622Bsection;\u62C2isible\u0100CT\u076C\u0772omma;\u6063imes;\u6062\u0180gpt\u077F\u0783\u0788on;\u412Ef;\uC000\u{1D540}a;\u4399cr;\u6110ilde;\u4128\u01EB\u079A\0\u079Ecy;\u4406l\u803B\xCF\u40CF\u0280cfosu\u07AC\u07B7\u07BC\u07C2\u07D0\u0100iy\u07B1\u07B5rc;\u4134;\u4419r;\uC000\u{1D50D}pf;\uC000\u{1D541}\u01E3\u07C7\0\u07CCr;\uC000\u{1D4A5}rcy;\u4408kcy;\u4404\u0380HJacfos\u07E4\u07E8\u07EC\u07F1\u07FD\u0802\u0808cy;\u4425cy;\u440Cppa;\u439A\u0100ey\u07F6\u07FBdil;\u4136;\u441Ar;\uC000\u{1D50E}pf;\uC000\u{1D542}cr;\uC000\u{1D4A6}\u0580JTaceflmost\u0825\u0829\u082C\u0850\u0863\u09B3\u09B8\u09C7\u09CD\u0A37\u0A47cy;\u4409\u803B<\u403C\u0280cmnpr\u0837\u083C\u0841\u0844\u084Dute;\u4139bda;\u439Bg;\u67EAlacetrf;\u6112r;\u619E\u0180aey\u0857\u085C\u0861ron;\u413Ddil;\u413B;\u441B\u0100fs\u0868\u0970t\u0500ACDFRTUVar\u087E\u08A9\u08B1\u08E0\u08E6\u08FC\u092F\u095B\u0390\u096A\u0100nr\u0883\u088FgleBracket;\u67E8row\u0180;BR\u0899\u089A\u089E\u6190ar;\u61E4ightArrow;\u61C6eiling;\u6308o\u01F5\u08B7\0\u08C3bleBracket;\u67E6n\u01D4\u08C8\0\u08D2eeVector;\u6961ector\u0100;B\u08DB\u08DC\u61C3ar;\u6959loor;\u630Aight\u0100AV\u08EF\u08F5rrow;\u6194ector;\u694E\u0100er\u0901\u0917e\u0180;AV\u0909\u090A\u0910\u62A3rrow;\u61A4ector;\u695Aiangle\u0180;BE\u0924\u0925\u0929\u62B2ar;\u69CFqual;\u62B4p\u0180DTV\u0937\u0942\u094CownVector;\u6951eeVector;\u6960ector\u0100;B\u0956\u0957\u61BFar;\u6958ector\u0100;B\u0965\u0966\u61BCar;\u6952ight\xE1\u039Cs\u0300EFGLST\u097E\u098B\u0995\u099D\u09A2\u09ADqualGreater;\u62DAullEqual;\u6266reater;\u6276ess;\u6AA1lantEqual;\u6A7Dilde;\u6272r;\uC000\u{1D50F}\u0100;e\u09BD\u09BE\u62D8ftarrow;\u61DAidot;\u413F\u0180npw\u09D4\u0A16\u0A1Bg\u0200LRlr\u09DE\u09F7\u0A02\u0A10eft\u0100AR\u09E6\u09ECrrow;\u67F5ightArrow;\u67F7ightArrow;\u67F6eft\u0100ar\u03B3\u0A0Aight\xE1\u03BFight\xE1\u03CAf;\uC000\u{1D543}er\u0100LR\u0A22\u0A2CeftArrow;\u6199ightArrow;\u6198\u0180cht\u0A3E\u0A40\u0A42\xF2\u084C;\u61B0rok;\u4141;\u626A\u0400acefiosu\u0A5A\u0A5D\u0A60\u0A77\u0A7C\u0A85\u0A8B\u0A8Ep;\u6905y;\u441C\u0100dl\u0A65\u0A6FiumSpace;\u605Flintrf;\u6133r;\uC000\u{1D510}nusPlus;\u6213pf;\uC000\u{1D544}c\xF2\u0A76;\u439C\u0480Jacefostu\u0AA3\u0AA7\u0AAD\u0AC0\u0B14\u0B19\u0D91\u0D97\u0D9Ecy;\u440Acute;\u4143\u0180aey\u0AB4\u0AB9\u0ABEron;\u4147dil;\u4145;\u441D\u0180gsw\u0AC7\u0AF0\u0B0Eative\u0180MTV\u0AD3\u0ADF\u0AE8ediumSpace;\u600Bhi\u0100cn\u0AE6\u0AD8\xEB\u0AD9eryThi\xEE\u0AD9ted\u0100GL\u0AF8\u0B06reaterGreate\xF2\u0673essLes\xF3\u0A48Line;\u400Ar;\uC000\u{1D511}\u0200Bnpt\u0B22\u0B28\u0B37\u0B3Areak;\u6060BreakingSpace;\u40A0f;\u6115\u0680;CDEGHLNPRSTV\u0B55\u0B56\u0B6A\u0B7C\u0BA1\u0BEB\u0C04\u0C5E\u0C84\u0CA6\u0CD8\u0D61\u0D85\u6AEC\u0100ou\u0B5B\u0B64ngruent;\u6262pCap;\u626DoubleVerticalBar;\u6226\u0180lqx\u0B83\u0B8A\u0B9Bement;\u6209ual\u0100;T\u0B92\u0B93\u6260ilde;\uC000\u2242\u0338ists;\u6204reater\u0380;EFGLST\u0BB6\u0BB7\u0BBD\u0BC9\u0BD3\u0BD8\u0BE5\u626Fqual;\u6271ullEqual;\uC000\u2267\u0338reater;\uC000\u226B\u0338ess;\u6279lantEqual;\uC000\u2A7E\u0338ilde;\u6275ump\u0144\u0BF2\u0BFDownHump;\uC000\u224E\u0338qual;\uC000\u224F\u0338e\u0100fs\u0C0A\u0C27tTriangle\u0180;BE\u0C1A\u0C1B\u0C21\u62EAar;\uC000\u29CF\u0338qual;\u62ECs\u0300;EGLST\u0C35\u0C36\u0C3C\u0C44\u0C4B\u0C58\u626Equal;\u6270reater;\u6278ess;\uC000\u226A\u0338lantEqual;\uC000\u2A7D\u0338ilde;\u6274ested\u0100GL\u0C68\u0C79reaterGreater;\uC000\u2AA2\u0338essLess;\uC000\u2AA1\u0338recedes\u0180;ES\u0C92\u0C93\u0C9B\u6280qual;\uC000\u2AAF\u0338lantEqual;\u62E0\u0100ei\u0CAB\u0CB9verseElement;\u620CghtTriangle\u0180;BE\u0CCB\u0CCC\u0CD2\u62EBar;\uC000\u29D0\u0338qual;\u62ED\u0100qu\u0CDD\u0D0CuareSu\u0100bp\u0CE8\u0CF9set\u0100;E\u0CF0\u0CF3\uC000\u228F\u0338qual;\u62E2erset\u0100;E\u0D03\u0D06\uC000\u2290\u0338qual;\u62E3\u0180bcp\u0D13\u0D24\u0D4Eset\u0100;E\u0D1B\u0D1E\uC000\u2282\u20D2qual;\u6288ceeds\u0200;EST\u0D32\u0D33\u0D3B\u0D46\u6281qual;\uC000\u2AB0\u0338lantEqual;\u62E1ilde;\uC000\u227F\u0338erset\u0100;E\u0D58\u0D5B\uC000\u2283\u20D2qual;\u6289ilde\u0200;EFT\u0D6E\u0D6F\u0D75\u0D7F\u6241qual;\u6244ullEqual;\u6247ilde;\u6249erticalBar;\u6224cr;\uC000\u{1D4A9}ilde\u803B\xD1\u40D1;\u439D\u0700Eacdfgmoprstuv\u0DBD\u0DC2\u0DC9\u0DD5\u0DDB\u0DE0\u0DE7\u0DFC\u0E02\u0E20\u0E22\u0E32\u0E3F\u0E44lig;\u4152cute\u803B\xD3\u40D3\u0100iy\u0DCE\u0DD3rc\u803B\xD4\u40D4;\u441Eblac;\u4150r;\uC000\u{1D512}rave\u803B\xD2\u40D2\u0180aei\u0DEE\u0DF2\u0DF6cr;\u414Cga;\u43A9cron;\u439Fpf;\uC000\u{1D546}enCurly\u0100DQ\u0E0E\u0E1AoubleQuote;\u601Cuote;\u6018;\u6A54\u0100cl\u0E27\u0E2Cr;\uC000\u{1D4AA}ash\u803B\xD8\u40D8i\u016C\u0E37\u0E3Cde\u803B\xD5\u40D5es;\u6A37ml\u803B\xD6\u40D6er\u0100BP\u0E4B\u0E60\u0100ar\u0E50\u0E53r;\u603Eac\u0100ek\u0E5A\u0E5C;\u63DEet;\u63B4arenthesis;\u63DC\u0480acfhilors\u0E7F\u0E87\u0E8A\u0E8F\u0E92\u0E94\u0E9D\u0EB0\u0EFCrtialD;\u6202y;\u441Fr;\uC000\u{1D513}i;\u43A6;\u43A0usMinus;\u40B1\u0100ip\u0EA2\u0EADncareplan\xE5\u069Df;\u6119\u0200;eio\u0EB9\u0EBA\u0EE0\u0EE4\u6ABBcedes\u0200;EST\u0EC8\u0EC9\u0ECF\u0EDA\u627Aqual;\u6AAFlantEqual;\u627Cilde;\u627Eme;\u6033\u0100dp\u0EE9\u0EEEuct;\u620Fortion\u0100;a\u0225\u0EF9l;\u621D\u0100ci\u0F01\u0F06r;\uC000\u{1D4AB};\u43A8\u0200Ufos\u0F11\u0F16\u0F1B\u0F1FOT\u803B"\u4022r;\uC000\u{1D514}pf;\u611Acr;\uC000\u{1D4AC}\u0600BEacefhiorsu\u0F3E\u0F43\u0F47\u0F60\u0F73\u0FA7\u0FAA\u0FAD\u1096\u10A9\u10B4\u10BEarr;\u6910G\u803B\xAE\u40AE\u0180cnr\u0F4E\u0F53\u0F56ute;\u4154g;\u67EBr\u0100;t\u0F5C\u0F5D\u61A0l;\u6916\u0180aey\u0F67\u0F6C\u0F71ron;\u4158dil;\u4156;\u4420\u0100;v\u0F78\u0F79\u611Cerse\u0100EU\u0F82\u0F99\u0100lq\u0F87\u0F8Eement;\u620Builibrium;\u61CBpEquilibrium;\u696Fr\xBB\u0F79o;\u43A1ght\u0400ACDFTUVa\u0FC1\u0FEB\u0FF3\u1022\u1028\u105B\u1087\u03D8\u0100nr\u0FC6\u0FD2gleBracket;\u67E9row\u0180;BL\u0FDC\u0FDD\u0FE1\u6192ar;\u61E5eftArrow;\u61C4eiling;\u6309o\u01F5\u0FF9\0\u1005bleBracket;\u67E7n\u01D4\u100A\0\u1014eeVector;\u695Dector\u0100;B\u101D\u101E\u61C2ar;\u6955loor;\u630B\u0100er\u102D\u1043e\u0180;AV\u1035\u1036\u103C\u62A2rrow;\u61A6ector;\u695Biangle\u0180;BE\u1050\u1051\u1055\u62B3ar;\u69D0qual;\u62B5p\u0180DTV\u1063\u106E\u1078ownVector;\u694FeeVector;\u695Cector\u0100;B\u1082\u1083\u61BEar;\u6954ector\u0100;B\u1091\u1092\u61C0ar;\u6953\u0100pu\u109B\u109Ef;\u611DndImplies;\u6970ightarrow;\u61DB\u0100ch\u10B9\u10BCr;\u611B;\u61B1leDelayed;\u69F4\u0680HOacfhimoqstu\u10E4\u10F1\u10F7\u10FD\u1119\u111E\u1151\u1156\u1161\u1167\u11B5\u11BB\u11BF\u0100Cc\u10E9\u10EEHcy;\u4429y;\u4428FTcy;\u442Ccute;\u415A\u0280;aeiy\u1108\u1109\u110E\u1113\u1117\u6ABCron;\u4160dil;\u415Erc;\u415C;\u4421r;\uC000\u{1D516}ort\u0200DLRU\u112A\u1134\u113E\u1149ownArrow\xBB\u041EeftArrow\xBB\u089AightArrow\xBB\u0FDDpArrow;\u6191gma;\u43A3allCircle;\u6218pf;\uC000\u{1D54A}\u0272\u116D\0\0\u1170t;\u621Aare\u0200;ISU\u117B\u117C\u1189\u11AF\u65A1ntersection;\u6293u\u0100bp\u118F\u119Eset\u0100;E\u1197\u1198\u628Fqual;\u6291erset\u0100;E\u11A8\u11A9\u6290qual;\u6292nion;\u6294cr;\uC000\u{1D4AE}ar;\u62C6\u0200bcmp\u11C8\u11DB\u1209\u120B\u0100;s\u11CD\u11CE\u62D0et\u0100;E\u11CD\u11D5qual;\u6286\u0100ch\u11E0\u1205eeds\u0200;EST\u11ED\u11EE\u11F4\u11FF\u627Bqual;\u6AB0lantEqual;\u627Dilde;\u627FTh\xE1\u0F8C;\u6211\u0180;es\u1212\u1213\u1223\u62D1rset\u0100;E\u121C\u121D\u6283qual;\u6287et\xBB\u1213\u0580HRSacfhiors\u123E\u1244\u1249\u1255\u125E\u1271\u1276\u129F\u12C2\u12C8\u12D1ORN\u803B\xDE\u40DEADE;\u6122\u0100Hc\u124E\u1252cy;\u440By;\u4426\u0100bu\u125A\u125C;\u4009;\u43A4\u0180aey\u1265\u126A\u126Fron;\u4164dil;\u4162;\u4422r;\uC000\u{1D517}\u0100ei\u127B\u1289\u01F2\u1280\0\u1287efore;\u6234a;\u4398\u0100cn\u128E\u1298kSpace;\uC000\u205F\u200ASpace;\u6009lde\u0200;EFT\u12AB\u12AC\u12B2\u12BC\u623Cqual;\u6243ullEqual;\u6245ilde;\u6248pf;\uC000\u{1D54B}ipleDot;\u60DB\u0100ct\u12D6\u12DBr;\uC000\u{1D4AF}rok;\u4166\u0AE1\u12F7\u130E\u131A\u1326\0\u132C\u1331\0\0\0\0\0\u1338\u133D\u1377\u1385\0\u13FF\u1404\u140A\u1410\u0100cr\u12FB\u1301ute\u803B\xDA\u40DAr\u0100;o\u1307\u1308\u619Fcir;\u6949r\u01E3\u1313\0\u1316y;\u440Eve;\u416C\u0100iy\u131E\u1323rc\u803B\xDB\u40DB;\u4423blac;\u4170r;\uC000\u{1D518}rave\u803B\xD9\u40D9acr;\u416A\u0100di\u1341\u1369er\u0100BP\u1348\u135D\u0100ar\u134D\u1350r;\u405Fac\u0100ek\u1357\u1359;\u63DFet;\u63B5arenthesis;\u63DDon\u0100;P\u1370\u1371\u62C3lus;\u628E\u0100gp\u137B\u137Fon;\u4172f;\uC000\u{1D54C}\u0400ADETadps\u1395\u13AE\u13B8\u13C4\u03E8\u13D2\u13D7\u13F3rrow\u0180;BD\u1150\u13A0\u13A4ar;\u6912ownArrow;\u61C5ownArrow;\u6195quilibrium;\u696Eee\u0100;A\u13CB\u13CC\u62A5rrow;\u61A5own\xE1\u03F3er\u0100LR\u13DE\u13E8eftArrow;\u6196ightArrow;\u6197i\u0100;l\u13F9\u13FA\u43D2on;\u43A5ing;\u416Ecr;\uC000\u{1D4B0}ilde;\u4168ml\u803B\xDC\u40DC\u0480Dbcdefosv\u1427\u142C\u1430\u1433\u143E\u1485\u148A\u1490\u1496ash;\u62ABar;\u6AEBy;\u4412ash\u0100;l\u143B\u143C\u62A9;\u6AE6\u0100er\u1443\u1445;\u62C1\u0180bty\u144C\u1450\u147Aar;\u6016\u0100;i\u144F\u1455cal\u0200BLST\u1461\u1465\u146A\u1474ar;\u6223ine;\u407Ceparator;\u6758ilde;\u6240ThinSpace;\u600Ar;\uC000\u{1D519}pf;\uC000\u{1D54D}cr;\uC000\u{1D4B1}dash;\u62AA\u0280cefos\u14A7\u14AC\u14B1\u14B6\u14BCirc;\u4174dge;\u62C0r;\uC000\u{1D51A}pf;\uC000\u{1D54E}cr;\uC000\u{1D4B2}\u0200fios\u14CB\u14D0\u14D2\u14D8r;\uC000\u{1D51B};\u439Epf;\uC000\u{1D54F}cr;\uC000\u{1D4B3}\u0480AIUacfosu\u14F1\u14F5\u14F9\u14FD\u1504\u150F\u1514\u151A\u1520cy;\u442Fcy;\u4407cy;\u442Ecute\u803B\xDD\u40DD\u0100iy\u1509\u150Drc;\u4176;\u442Br;\uC000\u{1D51C}pf;\uC000\u{1D550}cr;\uC000\u{1D4B4}ml;\u4178\u0400Hacdefos\u1535\u1539\u153F\u154B\u154F\u155D\u1560\u1564cy;\u4416cute;\u4179\u0100ay\u1544\u1549ron;\u417D;\u4417ot;\u417B\u01F2\u1554\0\u155BoWidt\xE8\u0AD9a;\u4396r;\u6128pf;\u6124cr;\uC000\u{1D4B5}\u0BE1\u1583\u158A\u1590\0\u15B0\u15B6\u15BF\0\0\0\0\u15C6\u15DB\u15EB\u165F\u166D\0\u1695\u169B\u16B2\u16B9\0\u16BEcute\u803B\xE1\u40E1reve;\u4103\u0300;Ediuy\u159C\u159D\u15A1\u15A3\u15A8\u15AD\u623E;\uC000\u223E\u0333;\u623Frc\u803B\xE2\u40E2te\u80BB\xB4\u0306;\u4430lig\u803B\xE6\u40E6\u0100;r\xB2\u15BA;\uC000\u{1D51E}rave\u803B\xE0\u40E0\u0100ep\u15CA\u15D6\u0100fp\u15CF\u15D4sym;\u6135\xE8\u15D3ha;\u43B1\u0100ap\u15DFc\u0100cl\u15E4\u15E7r;\u4101g;\u6A3F\u0264\u15F0\0\0\u160A\u0280;adsv\u15FA\u15FB\u15FF\u1601\u1607\u6227nd;\u6A55;\u6A5Clope;\u6A58;\u6A5A\u0380;elmrsz\u1618\u1619\u161B\u161E\u163F\u164F\u1659\u6220;\u69A4e\xBB\u1619sd\u0100;a\u1625\u1626\u6221\u0461\u1630\u1632\u1634\u1636\u1638\u163A\u163C\u163E;\u69A8;\u69A9;\u69AA;\u69AB;\u69AC;\u69AD;\u69AE;\u69AFt\u0100;v\u1645\u1646\u621Fb\u0100;d\u164C\u164D\u62BE;\u699D\u0100pt\u1654\u1657h;\u6222\xBB\xB9arr;\u637C\u0100gp\u1663\u1667on;\u4105f;\uC000\u{1D552}\u0380;Eaeiop\u12C1\u167B\u167D\u1682\u1684\u1687\u168A;\u6A70cir;\u6A6F;\u624Ad;\u624Bs;\u4027rox\u0100;e\u12C1\u1692\xF1\u1683ing\u803B\xE5\u40E5\u0180cty\u16A1\u16A6\u16A8r;\uC000\u{1D4B6};\u402Amp\u0100;e\u12C1\u16AF\xF1\u0288ilde\u803B\xE3\u40E3ml\u803B\xE4\u40E4\u0100ci\u16C2\u16C8onin\xF4\u0272nt;\u6A11\u0800Nabcdefiklnoprsu\u16ED\u16F1\u1730\u173C\u1743\u1748\u1778\u177D\u17E0\u17E6\u1839\u1850\u170D\u193D\u1948\u1970ot;\u6AED\u0100cr\u16F6\u171Ek\u0200ceps\u1700\u1705\u170D\u1713ong;\u624Cpsilon;\u43F6rime;\u6035im\u0100;e\u171A\u171B\u623Dq;\u62CD\u0176\u1722\u1726ee;\u62BDed\u0100;g\u172C\u172D\u6305e\xBB\u172Drk\u0100;t\u135C\u1737brk;\u63B6\u0100oy\u1701\u1741;\u4431quo;\u601E\u0280cmprt\u1753\u175B\u1761\u1764\u1768aus\u0100;e\u010A\u0109ptyv;\u69B0s\xE9\u170Cno\xF5\u0113\u0180ahw\u176F\u1771\u1773;\u43B2;\u6136een;\u626Cr;\uC000\u{1D51F}g\u0380costuvw\u178D\u179D\u17B3\u17C1\u17D5\u17DB\u17DE\u0180aiu\u1794\u1796\u179A\xF0\u0760rc;\u65EFp\xBB\u1371\u0180dpt\u17A4\u17A8\u17ADot;\u6A00lus;\u6A01imes;\u6A02\u0271\u17B9\0\0\u17BEcup;\u6A06ar;\u6605riangle\u0100du\u17CD\u17D2own;\u65BDp;\u65B3plus;\u6A04e\xE5\u1444\xE5\u14ADarow;\u690D\u0180ako\u17ED\u1826\u1835\u0100cn\u17F2\u1823k\u0180lst\u17FA\u05AB\u1802ozenge;\u69EBriangle\u0200;dlr\u1812\u1813\u1818\u181D\u65B4own;\u65BEeft;\u65C2ight;\u65B8k;\u6423\u01B1\u182B\0\u1833\u01B2\u182F\0\u1831;\u6592;\u65914;\u6593ck;\u6588\u0100eo\u183E\u184D\u0100;q\u1843\u1846\uC000=\u20E5uiv;\uC000\u2261\u20E5t;\u6310\u0200ptwx\u1859\u185E\u1867\u186Cf;\uC000\u{1D553}\u0100;t\u13CB\u1863om\xBB\u13CCtie;\u62C8\u0600DHUVbdhmptuv\u1885\u1896\u18AA\u18BB\u18D7\u18DB\u18EC\u18FF\u1905\u190A\u1910\u1921\u0200LRlr\u188E\u1890\u1892\u1894;\u6557;\u6554;\u6556;\u6553\u0280;DUdu\u18A1\u18A2\u18A4\u18A6\u18A8\u6550;\u6566;\u6569;\u6564;\u6567\u0200LRlr\u18B3\u18B5\u18B7\u18B9;\u655D;\u655A;\u655C;\u6559\u0380;HLRhlr\u18CA\u18CB\u18CD\u18CF\u18D1\u18D3\u18D5\u6551;\u656C;\u6563;\u6560;\u656B;\u6562;\u655Fox;\u69C9\u0200LRlr\u18E4\u18E6\u18E8\u18EA;\u6555;\u6552;\u6510;\u650C\u0280;DUdu\u06BD\u18F7\u18F9\u18FB\u18FD;\u6565;\u6568;\u652C;\u6534inus;\u629Flus;\u629Eimes;\u62A0\u0200LRlr\u1919\u191B\u191D\u191F;\u655B;\u6558;\u6518;\u6514\u0380;HLRhlr\u1930\u1931\u1933\u1935\u1937\u1939\u193B\u6502;\u656A;\u6561;\u655E;\u653C;\u6524;\u651C\u0100ev\u0123\u1942bar\u803B\xA6\u40A6\u0200ceio\u1951\u1956\u195A\u1960r;\uC000\u{1D4B7}mi;\u604Fm\u0100;e\u171A\u171Cl\u0180;bh\u1968\u1969\u196B\u405C;\u69C5sub;\u67C8\u016C\u1974\u197El\u0100;e\u1979\u197A\u6022t\xBB\u197Ap\u0180;Ee\u012F\u1985\u1987;\u6AAE\u0100;q\u06DC\u06DB\u0CE1\u19A7\0\u19E8\u1A11\u1A15\u1A32\0\u1A37\u1A50\0\0\u1AB4\0\0\u1AC1\0\0\u1B21\u1B2E\u1B4D\u1B52\0\u1BFD\0\u1C0C\u0180cpr\u19AD\u19B2\u19DDute;\u4107\u0300;abcds\u19BF\u19C0\u19C4\u19CA\u19D5\u19D9\u6229nd;\u6A44rcup;\u6A49\u0100au\u19CF\u19D2p;\u6A4Bp;\u6A47ot;\u6A40;\uC000\u2229\uFE00\u0100eo\u19E2\u19E5t;\u6041\xEE\u0693\u0200aeiu\u19F0\u19FB\u1A01\u1A05\u01F0\u19F5\0\u19F8s;\u6A4Don;\u410Ddil\u803B\xE7\u40E7rc;\u4109ps\u0100;s\u1A0C\u1A0D\u6A4Cm;\u6A50ot;\u410B\u0180dmn\u1A1B\u1A20\u1A26il\u80BB\xB8\u01ADptyv;\u69B2t\u8100\xA2;e\u1A2D\u1A2E\u40A2r\xE4\u01B2r;\uC000\u{1D520}\u0180cei\u1A3D\u1A40\u1A4Dy;\u4447ck\u0100;m\u1A47\u1A48\u6713ark\xBB\u1A48;\u43C7r\u0380;Ecefms\u1A5F\u1A60\u1A62\u1A6B\u1AA4\u1AAA\u1AAE\u65CB;\u69C3\u0180;el\u1A69\u1A6A\u1A6D\u42C6q;\u6257e\u0261\u1A74\0\0\u1A88rrow\u0100lr\u1A7C\u1A81eft;\u61BAight;\u61BB\u0280RSacd\u1A92\u1A94\u1A96\u1A9A\u1A9F\xBB\u0F47;\u64C8st;\u629Birc;\u629Aash;\u629Dnint;\u6A10id;\u6AEFcir;\u69C2ubs\u0100;u\u1ABB\u1ABC\u6663it\xBB\u1ABC\u02EC\u1AC7\u1AD4\u1AFA\0\u1B0Aon\u0100;e\u1ACD\u1ACE\u403A\u0100;q\xC7\xC6\u026D\u1AD9\0\0\u1AE2a\u0100;t\u1ADE\u1ADF\u402C;\u4040\u0180;fl\u1AE8\u1AE9\u1AEB\u6201\xEE\u1160e\u0100mx\u1AF1\u1AF6ent\xBB\u1AE9e\xF3\u024D\u01E7\u1AFE\0\u1B07\u0100;d\u12BB\u1B02ot;\u6A6Dn\xF4\u0246\u0180fry\u1B10\u1B14\u1B17;\uC000\u{1D554}o\xE4\u0254\u8100\xA9;s\u0155\u1B1Dr;\u6117\u0100ao\u1B25\u1B29rr;\u61B5ss;\u6717\u0100cu\u1B32\u1B37r;\uC000\u{1D4B8}\u0100bp\u1B3C\u1B44\u0100;e\u1B41\u1B42\u6ACF;\u6AD1\u0100;e\u1B49\u1B4A\u6AD0;\u6AD2dot;\u62EF\u0380delprvw\u1B60\u1B6C\u1B77\u1B82\u1BAC\u1BD4\u1BF9arr\u0100lr\u1B68\u1B6A;\u6938;\u6935\u0270\u1B72\0\0\u1B75r;\u62DEc;\u62DFarr\u0100;p\u1B7F\u1B80\u61B6;\u693D\u0300;bcdos\u1B8F\u1B90\u1B96\u1BA1\u1BA5\u1BA8\u622Arcap;\u6A48\u0100au\u1B9B\u1B9Ep;\u6A46p;\u6A4Aot;\u628Dr;\u6A45;\uC000\u222A\uFE00\u0200alrv\u1BB5\u1BBF\u1BDE\u1BE3rr\u0100;m\u1BBC\u1BBD\u61B7;\u693Cy\u0180evw\u1BC7\u1BD4\u1BD8q\u0270\u1BCE\0\0\u1BD2re\xE3\u1B73u\xE3\u1B75ee;\u62CEedge;\u62CFen\u803B\xA4\u40A4earrow\u0100lr\u1BEE\u1BF3eft\xBB\u1B80ight\xBB\u1BBDe\xE4\u1BDD\u0100ci\u1C01\u1C07onin\xF4\u01F7nt;\u6231lcty;\u632D\u0980AHabcdefhijlorstuwz\u1C38\u1C3B\u1C3F\u1C5D\u1C69\u1C75\u1C8A\u1C9E\u1CAC\u1CB7\u1CFB\u1CFF\u1D0D\u1D7B\u1D91\u1DAB\u1DBB\u1DC6\u1DCDr\xF2\u0381ar;\u6965\u0200glrs\u1C48\u1C4D\u1C52\u1C54ger;\u6020eth;\u6138\xF2\u1133h\u0100;v\u1C5A\u1C5B\u6010\xBB\u090A\u016B\u1C61\u1C67arow;\u690Fa\xE3\u0315\u0100ay\u1C6E\u1C73ron;\u410F;\u4434\u0180;ao\u0332\u1C7C\u1C84\u0100gr\u02BF\u1C81r;\u61CAtseq;\u6A77\u0180glm\u1C91\u1C94\u1C98\u803B\xB0\u40B0ta;\u43B4ptyv;\u69B1\u0100ir\u1CA3\u1CA8sht;\u697F;\uC000\u{1D521}ar\u0100lr\u1CB3\u1CB5\xBB\u08DC\xBB\u101E\u0280aegsv\u1CC2\u0378\u1CD6\u1CDC\u1CE0m\u0180;os\u0326\u1CCA\u1CD4nd\u0100;s\u0326\u1CD1uit;\u6666amma;\u43DDin;\u62F2\u0180;io\u1CE7\u1CE8\u1CF8\u40F7de\u8100\xF7;o\u1CE7\u1CF0ntimes;\u62C7n\xF8\u1CF7cy;\u4452c\u026F\u1D06\0\0\u1D0Arn;\u631Eop;\u630D\u0280lptuw\u1D18\u1D1D\u1D22\u1D49\u1D55lar;\u4024f;\uC000\u{1D555}\u0280;emps\u030B\u1D2D\u1D37\u1D3D\u1D42q\u0100;d\u0352\u1D33ot;\u6251inus;\u6238lus;\u6214quare;\u62A1blebarwedg\xE5\xFAn\u0180adh\u112E\u1D5D\u1D67ownarrow\xF3\u1C83arpoon\u0100lr\u1D72\u1D76ef\xF4\u1CB4igh\xF4\u1CB6\u0162\u1D7F\u1D85karo\xF7\u0F42\u026F\u1D8A\0\0\u1D8Ern;\u631Fop;\u630C\u0180cot\u1D98\u1DA3\u1DA6\u0100ry\u1D9D\u1DA1;\uC000\u{1D4B9};\u4455l;\u69F6rok;\u4111\u0100dr\u1DB0\u1DB4ot;\u62F1i\u0100;f\u1DBA\u1816\u65BF\u0100ah\u1DC0\u1DC3r\xF2\u0429a\xF2\u0FA6angle;\u69A6\u0100ci\u1DD2\u1DD5y;\u445Fgrarr;\u67FF\u0900Dacdefglmnopqrstux\u1E01\u1E09\u1E19\u1E38\u0578\u1E3C\u1E49\u1E61\u1E7E\u1EA5\u1EAF\u1EBD\u1EE1\u1F2A\u1F37\u1F44\u1F4E\u1F5A\u0100Do\u1E06\u1D34o\xF4\u1C89\u0100cs\u1E0E\u1E14ute\u803B\xE9\u40E9ter;\u6A6E\u0200aioy\u1E22\u1E27\u1E31\u1E36ron;\u411Br\u0100;c\u1E2D\u1E2E\u6256\u803B\xEA\u40EAlon;\u6255;\u444Dot;\u4117\u0100Dr\u1E41\u1E45ot;\u6252;\uC000\u{1D522}\u0180;rs\u1E50\u1E51\u1E57\u6A9Aave\u803B\xE8\u40E8\u0100;d\u1E5C\u1E5D\u6A96ot;\u6A98\u0200;ils\u1E6A\u1E6B\u1E72\u1E74\u6A99nters;\u63E7;\u6113\u0100;d\u1E79\u1E7A\u6A95ot;\u6A97\u0180aps\u1E85\u1E89\u1E97cr;\u4113ty\u0180;sv\u1E92\u1E93\u1E95\u6205et\xBB\u1E93p\u01001;\u1E9D\u1EA4\u0133\u1EA1\u1EA3;\u6004;\u6005\u6003\u0100gs\u1EAA\u1EAC;\u414Bp;\u6002\u0100gp\u1EB4\u1EB8on;\u4119f;\uC000\u{1D556}\u0180als\u1EC4\u1ECE\u1ED2r\u0100;s\u1ECA\u1ECB\u62D5l;\u69E3us;\u6A71i\u0180;lv\u1EDA\u1EDB\u1EDF\u43B5on\xBB\u1EDB;\u43F5\u0200csuv\u1EEA\u1EF3\u1F0B\u1F23\u0100io\u1EEF\u1E31rc\xBB\u1E2E\u0269\u1EF9\0\0\u1EFB\xED\u0548ant\u0100gl\u1F02\u1F06tr\xBB\u1E5Dess\xBB\u1E7A\u0180aei\u1F12\u1F16\u1F1Als;\u403Dst;\u625Fv\u0100;D\u0235\u1F20D;\u6A78parsl;\u69E5\u0100Da\u1F2F\u1F33ot;\u6253rr;\u6971\u0180cdi\u1F3E\u1F41\u1EF8r;\u612Fo\xF4\u0352\u0100ah\u1F49\u1F4B;\u43B7\u803B\xF0\u40F0\u0100mr\u1F53\u1F57l\u803B\xEB\u40EBo;\u60AC\u0180cip\u1F61\u1F64\u1F67l;\u4021s\xF4\u056E\u0100eo\u1F6C\u1F74ctatio\xEE\u0559nential\xE5\u0579\u09E1\u1F92\0\u1F9E\0\u1FA1\u1FA7\0\0\u1FC6\u1FCC\0\u1FD3\0\u1FE6\u1FEA\u2000\0\u2008\u205Allingdotse\xF1\u1E44y;\u4444male;\u6640\u0180ilr\u1FAD\u1FB3\u1FC1lig;\u8000\uFB03\u0269\u1FB9\0\0\u1FBDg;\u8000\uFB00ig;\u8000\uFB04;\uC000\u{1D523}lig;\u8000\uFB01lig;\uC000fj\u0180alt\u1FD9\u1FDC\u1FE1t;\u666Dig;\u8000\uFB02ns;\u65B1of;\u4192\u01F0\u1FEE\0\u1FF3f;\uC000\u{1D557}\u0100ak\u05BF\u1FF7\u0100;v\u1FFC\u1FFD\u62D4;\u6AD9artint;\u6A0D\u0100ao\u200C\u2055\u0100cs\u2011\u2052\u03B1\u201A\u2030\u2038\u2045\u2048\0\u2050\u03B2\u2022\u2025\u2027\u202A\u202C\0\u202E\u803B\xBD\u40BD;\u6153\u803B\xBC\u40BC;\u6155;\u6159;\u615B\u01B3\u2034\0\u2036;\u6154;\u6156\u02B4\u203E\u2041\0\0\u2043\u803B\xBE\u40BE;\u6157;\u615C5;\u6158\u01B6\u204C\0\u204E;\u615A;\u615D8;\u615El;\u6044wn;\u6322cr;\uC000\u{1D4BB}\u0880Eabcdefgijlnorstv\u2082\u2089\u209F\u20A5\u20B0\u20B4\u20F0\u20F5\u20FA\u20FF\u2103\u2112\u2138\u0317\u213E\u2152\u219E\u0100;l\u064D\u2087;\u6A8C\u0180cmp\u2090\u2095\u209Dute;\u41F5ma\u0100;d\u209C\u1CDA\u43B3;\u6A86reve;\u411F\u0100iy\u20AA\u20AErc;\u411D;\u4433ot;\u4121\u0200;lqs\u063E\u0642\u20BD\u20C9\u0180;qs\u063E\u064C\u20C4lan\xF4\u0665\u0200;cdl\u0665\u20D2\u20D5\u20E5c;\u6AA9ot\u0100;o\u20DC\u20DD\u6A80\u0100;l\u20E2\u20E3\u6A82;\u6A84\u0100;e\u20EA\u20ED\uC000\u22DB\uFE00s;\u6A94r;\uC000\u{1D524}\u0100;g\u0673\u061Bmel;\u6137cy;\u4453\u0200;Eaj\u065A\u210C\u210E\u2110;\u6A92;\u6AA5;\u6AA4\u0200Eaes\u211B\u211D\u2129\u2134;\u6269p\u0100;p\u2123\u2124\u6A8Arox\xBB\u2124\u0100;q\u212E\u212F\u6A88\u0100;q\u212E\u211Bim;\u62E7pf;\uC000\u{1D558}\u0100ci\u2143\u2146r;\u610Am\u0180;el\u066B\u214E\u2150;\u6A8E;\u6A90\u8300>;cdlqr\u05EE\u2160\u216A\u216E\u2173\u2179\u0100ci\u2165\u2167;\u6AA7r;\u6A7Aot;\u62D7Par;\u6995uest;\u6A7C\u0280adels\u2184\u216A\u2190\u0656\u219B\u01F0\u2189\0\u218Epro\xF8\u209Er;\u6978q\u0100lq\u063F\u2196les\xF3\u2088i\xED\u066B\u0100en\u21A3\u21ADrtneqq;\uC000\u2269\uFE00\xC5\u21AA\u0500Aabcefkosy\u21C4\u21C7\u21F1\u21F5\u21FA\u2218\u221D\u222F\u2268\u227Dr\xF2\u03A0\u0200ilmr\u21D0\u21D4\u21D7\u21DBrs\xF0\u1484f\xBB\u2024il\xF4\u06A9\u0100dr\u21E0\u21E4cy;\u444A\u0180;cw\u08F4\u21EB\u21EFir;\u6948;\u61ADar;\u610Firc;\u4125\u0180alr\u2201\u220E\u2213rts\u0100;u\u2209\u220A\u6665it\xBB\u220Alip;\u6026con;\u62B9r;\uC000\u{1D525}s\u0100ew\u2223\u2229arow;\u6925arow;\u6926\u0280amopr\u223A\u223E\u2243\u225E\u2263rr;\u61FFtht;\u623Bk\u0100lr\u2249\u2253eftarrow;\u61A9ightarrow;\u61AAf;\uC000\u{1D559}bar;\u6015\u0180clt\u226F\u2274\u2278r;\uC000\u{1D4BD}as\xE8\u21F4rok;\u4127\u0100bp\u2282\u2287ull;\u6043hen\xBB\u1C5B\u0AE1\u22A3\0\u22AA\0\u22B8\u22C5\u22CE\0\u22D5\u22F3\0\0\u22F8\u2322\u2367\u2362\u237F\0\u2386\u23AA\u23B4cute\u803B\xED\u40ED\u0180;iy\u0771\u22B0\u22B5rc\u803B\xEE\u40EE;\u4438\u0100cx\u22BC\u22BFy;\u4435cl\u803B\xA1\u40A1\u0100fr\u039F\u22C9;\uC000\u{1D526}rave\u803B\xEC\u40EC\u0200;ino\u073E\u22DD\u22E9\u22EE\u0100in\u22E2\u22E6nt;\u6A0Ct;\u622Dfin;\u69DCta;\u6129lig;\u4133\u0180aop\u22FE\u231A\u231D\u0180cgt\u2305\u2308\u2317r;\u412B\u0180elp\u071F\u230F\u2313in\xE5\u078Ear\xF4\u0720h;\u4131f;\u62B7ed;\u41B5\u0280;cfot\u04F4\u232C\u2331\u233D\u2341are;\u6105in\u0100;t\u2338\u2339\u621Eie;\u69DDdo\xF4\u2319\u0280;celp\u0757\u234C\u2350\u235B\u2361al;\u62BA\u0100gr\u2355\u2359er\xF3\u1563\xE3\u234Darhk;\u6A17rod;\u6A3C\u0200cgpt\u236F\u2372\u2376\u237By;\u4451on;\u412Ff;\uC000\u{1D55A}a;\u43B9uest\u803B\xBF\u40BF\u0100ci\u238A\u238Fr;\uC000\u{1D4BE}n\u0280;Edsv\u04F4\u239B\u239D\u23A1\u04F3;\u62F9ot;\u62F5\u0100;v\u23A6\u23A7\u62F4;\u62F3\u0100;i\u0777\u23AElde;\u4129\u01EB\u23B8\0\u23BCcy;\u4456l\u803B\xEF\u40EF\u0300cfmosu\u23CC\u23D7\u23DC\u23E1\u23E7\u23F5\u0100iy\u23D1\u23D5rc;\u4135;\u4439r;\uC000\u{1D527}ath;\u4237pf;\uC000\u{1D55B}\u01E3\u23EC\0\u23F1r;\uC000\u{1D4BF}rcy;\u4458kcy;\u4454\u0400acfghjos\u240B\u2416\u2422\u2427\u242D\u2431\u2435\u243Bppa\u0100;v\u2413\u2414\u43BA;\u43F0\u0100ey\u241B\u2420dil;\u4137;\u443Ar;\uC000\u{1D528}reen;\u4138cy;\u4445cy;\u445Cpf;\uC000\u{1D55C}cr;\uC000\u{1D4C0}\u0B80ABEHabcdefghjlmnoprstuv\u2470\u2481\u2486\u248D\u2491\u250E\u253D\u255A\u2580\u264E\u265E\u2665\u2679\u267D\u269A\u26B2\u26D8\u275D\u2768\u278B\u27C0\u2801\u2812\u0180art\u2477\u247A\u247Cr\xF2\u09C6\xF2\u0395ail;\u691Barr;\u690E\u0100;g\u0994\u248B;\u6A8Bar;\u6962\u0963\u24A5\0\u24AA\0\u24B1\0\0\0\0\0\u24B5\u24BA\0\u24C6\u24C8\u24CD\0\u24F9ute;\u413Amptyv;\u69B4ra\xEE\u084Cbda;\u43BBg\u0180;dl\u088E\u24C1\u24C3;\u6991\xE5\u088E;\u6A85uo\u803B\xAB\u40ABr\u0400;bfhlpst\u0899\u24DE\u24E6\u24E9\u24EB\u24EE\u24F1\u24F5\u0100;f\u089D\u24E3s;\u691Fs;\u691D\xEB\u2252p;\u61ABl;\u6939im;\u6973l;\u61A2\u0180;ae\u24FF\u2500\u2504\u6AABil;\u6919\u0100;s\u2509\u250A\u6AAD;\uC000\u2AAD\uFE00\u0180abr\u2515\u2519\u251Drr;\u690Crk;\u6772\u0100ak\u2522\u252Cc\u0100ek\u2528\u252A;\u407B;\u405B\u0100es\u2531\u2533;\u698Bl\u0100du\u2539\u253B;\u698F;\u698D\u0200aeuy\u2546\u254B\u2556\u2558ron;\u413E\u0100di\u2550\u2554il;\u413C\xEC\u08B0\xE2\u2529;\u443B\u0200cqrs\u2563\u2566\u256D\u257Da;\u6936uo\u0100;r\u0E19\u1746\u0100du\u2572\u2577har;\u6967shar;\u694Bh;\u61B2\u0280;fgqs\u258B\u258C\u0989\u25F3\u25FF\u6264t\u0280ahlrt\u2598\u25A4\u25B7\u25C2\u25E8rrow\u0100;t\u0899\u25A1a\xE9\u24F6arpoon\u0100du\u25AF\u25B4own\xBB\u045Ap\xBB\u0966eftarrows;\u61C7ight\u0180ahs\u25CD\u25D6\u25DErrow\u0100;s\u08F4\u08A7arpoon\xF3\u0F98quigarro\xF7\u21F0hreetimes;\u62CB\u0180;qs\u258B\u0993\u25FAlan\xF4\u09AC\u0280;cdgs\u09AC\u260A\u260D\u261D\u2628c;\u6AA8ot\u0100;o\u2614\u2615\u6A7F\u0100;r\u261A\u261B\u6A81;\u6A83\u0100;e\u2622\u2625\uC000\u22DA\uFE00s;\u6A93\u0280adegs\u2633\u2639\u263D\u2649\u264Bppro\xF8\u24C6ot;\u62D6q\u0100gq\u2643\u2645\xF4\u0989gt\xF2\u248C\xF4\u099Bi\xED\u09B2\u0180ilr\u2655\u08E1\u265Asht;\u697C;\uC000\u{1D529}\u0100;E\u099C\u2663;\u6A91\u0161\u2669\u2676r\u0100du\u25B2\u266E\u0100;l\u0965\u2673;\u696Alk;\u6584cy;\u4459\u0280;acht\u0A48\u2688\u268B\u2691\u2696r\xF2\u25C1orne\xF2\u1D08ard;\u696Bri;\u65FA\u0100io\u269F\u26A4dot;\u4140ust\u0100;a\u26AC\u26AD\u63B0che\xBB\u26AD\u0200Eaes\u26BB\u26BD\u26C9\u26D4;\u6268p\u0100;p\u26C3\u26C4\u6A89rox\xBB\u26C4\u0100;q\u26CE\u26CF\u6A87\u0100;q\u26CE\u26BBim;\u62E6\u0400abnoptwz\u26E9\u26F4\u26F7\u271A\u272F\u2741\u2747\u2750\u0100nr\u26EE\u26F1g;\u67ECr;\u61FDr\xEB\u08C1g\u0180lmr\u26FF\u270D\u2714eft\u0100ar\u09E6\u2707ight\xE1\u09F2apsto;\u67FCight\xE1\u09FDparrow\u0100lr\u2725\u2729ef\xF4\u24EDight;\u61AC\u0180afl\u2736\u2739\u273Dr;\u6985;\uC000\u{1D55D}us;\u6A2Dimes;\u6A34\u0161\u274B\u274Fst;\u6217\xE1\u134E\u0180;ef\u2757\u2758\u1800\u65CAnge\xBB\u2758ar\u0100;l\u2764\u2765\u4028t;\u6993\u0280achmt\u2773\u2776\u277C\u2785\u2787r\xF2\u08A8orne\xF2\u1D8Car\u0100;d\u0F98\u2783;\u696D;\u600Eri;\u62BF\u0300achiqt\u2798\u279D\u0A40\u27A2\u27AE\u27BBquo;\u6039r;\uC000\u{1D4C1}m\u0180;eg\u09B2\u27AA\u27AC;\u6A8D;\u6A8F\u0100bu\u252A\u27B3o\u0100;r\u0E1F\u27B9;\u601Arok;\u4142\u8400<;cdhilqr\u082B\u27D2\u2639\u27DC\u27E0\u27E5\u27EA\u27F0\u0100ci\u27D7\u27D9;\u6AA6r;\u6A79re\xE5\u25F2mes;\u62C9arr;\u6976uest;\u6A7B\u0100Pi\u27F5\u27F9ar;\u6996\u0180;ef\u2800\u092D\u181B\u65C3r\u0100du\u2807\u280Dshar;\u694Ahar;\u6966\u0100en\u2817\u2821rtneqq;\uC000\u2268\uFE00\xC5\u281E\u0700Dacdefhilnopsu\u2840\u2845\u2882\u288E\u2893\u28A0\u28A5\u28A8\u28DA\u28E2\u28E4\u0A83\u28F3\u2902Dot;\u623A\u0200clpr\u284E\u2852\u2863\u287Dr\u803B\xAF\u40AF\u0100et\u2857\u2859;\u6642\u0100;e\u285E\u285F\u6720se\xBB\u285F\u0100;s\u103B\u2868to\u0200;dlu\u103B\u2873\u2877\u287Bow\xEE\u048Cef\xF4\u090F\xF0\u13D1ker;\u65AE\u0100oy\u2887\u288Cmma;\u6A29;\u443Cash;\u6014asuredangle\xBB\u1626r;\uC000\u{1D52A}o;\u6127\u0180cdn\u28AF\u28B4\u28C9ro\u803B\xB5\u40B5\u0200;acd\u1464\u28BD\u28C0\u28C4s\xF4\u16A7ir;\u6AF0ot\u80BB\xB7\u01B5us\u0180;bd\u28D2\u1903\u28D3\u6212\u0100;u\u1D3C\u28D8;\u6A2A\u0163\u28DE\u28E1p;\u6ADB\xF2\u2212\xF0\u0A81\u0100dp\u28E9\u28EEels;\u62A7f;\uC000\u{1D55E}\u0100ct\u28F8\u28FDr;\uC000\u{1D4C2}pos\xBB\u159D\u0180;lm\u2909\u290A\u290D\u43BCtimap;\u62B8\u0C00GLRVabcdefghijlmoprstuvw\u2942\u2953\u297E\u2989\u2998\u29DA\u29E9\u2A15\u2A1A\u2A58\u2A5D\u2A83\u2A95\u2AA4\u2AA8\u2B04\u2B07\u2B44\u2B7F\u2BAE\u2C34\u2C67\u2C7C\u2CE9\u0100gt\u2947\u294B;\uC000\u22D9\u0338\u0100;v\u2950\u0BCF\uC000\u226B\u20D2\u0180elt\u295A\u2972\u2976ft\u0100ar\u2961\u2967rrow;\u61CDightarrow;\u61CE;\uC000\u22D8\u0338\u0100;v\u297B\u0C47\uC000\u226A\u20D2ightarrow;\u61CF\u0100Dd\u298E\u2993ash;\u62AFash;\u62AE\u0280bcnpt\u29A3\u29A7\u29AC\u29B1\u29CCla\xBB\u02DEute;\u4144g;\uC000\u2220\u20D2\u0280;Eiop\u0D84\u29BC\u29C0\u29C5\u29C8;\uC000\u2A70\u0338d;\uC000\u224B\u0338s;\u4149ro\xF8\u0D84ur\u0100;a\u29D3\u29D4\u666El\u0100;s\u29D3\u0B38\u01F3\u29DF\0\u29E3p\u80BB\xA0\u0B37mp\u0100;e\u0BF9\u0C00\u0280aeouy\u29F4\u29FE\u2A03\u2A10\u2A13\u01F0\u29F9\0\u29FB;\u6A43on;\u4148dil;\u4146ng\u0100;d\u0D7E\u2A0Aot;\uC000\u2A6D\u0338p;\u6A42;\u443Dash;\u6013\u0380;Aadqsx\u0B92\u2A29\u2A2D\u2A3B\u2A41\u2A45\u2A50rr;\u61D7r\u0100hr\u2A33\u2A36k;\u6924\u0100;o\u13F2\u13F0ot;\uC000\u2250\u0338ui\xF6\u0B63\u0100ei\u2A4A\u2A4Ear;\u6928\xED\u0B98ist\u0100;s\u0BA0\u0B9Fr;\uC000\u{1D52B}\u0200Eest\u0BC5\u2A66\u2A79\u2A7C\u0180;qs\u0BBC\u2A6D\u0BE1\u0180;qs\u0BBC\u0BC5\u2A74lan\xF4\u0BE2i\xED\u0BEA\u0100;r\u0BB6\u2A81\xBB\u0BB7\u0180Aap\u2A8A\u2A8D\u2A91r\xF2\u2971rr;\u61AEar;\u6AF2\u0180;sv\u0F8D\u2A9C\u0F8C\u0100;d\u2AA1\u2AA2\u62FC;\u62FAcy;\u445A\u0380AEadest\u2AB7\u2ABA\u2ABE\u2AC2\u2AC5\u2AF6\u2AF9r\xF2\u2966;\uC000\u2266\u0338rr;\u619Ar;\u6025\u0200;fqs\u0C3B\u2ACE\u2AE3\u2AEFt\u0100ar\u2AD4\u2AD9rro\xF7\u2AC1ightarro\xF7\u2A90\u0180;qs\u0C3B\u2ABA\u2AEAlan\xF4\u0C55\u0100;s\u0C55\u2AF4\xBB\u0C36i\xED\u0C5D\u0100;r\u0C35\u2AFEi\u0100;e\u0C1A\u0C25i\xE4\u0D90\u0100pt\u2B0C\u2B11f;\uC000\u{1D55F}\u8180\xAC;in\u2B19\u2B1A\u2B36\u40ACn\u0200;Edv\u0B89\u2B24\u2B28\u2B2E;\uC000\u22F9\u0338ot;\uC000\u22F5\u0338\u01E1\u0B89\u2B33\u2B35;\u62F7;\u62F6i\u0100;v\u0CB8\u2B3C\u01E1\u0CB8\u2B41\u2B43;\u62FE;\u62FD\u0180aor\u2B4B\u2B63\u2B69r\u0200;ast\u0B7B\u2B55\u2B5A\u2B5Flle\xEC\u0B7Bl;\uC000\u2AFD\u20E5;\uC000\u2202\u0338lint;\u6A14\u0180;ce\u0C92\u2B70\u2B73u\xE5\u0CA5\u0100;c\u0C98\u2B78\u0100;e\u0C92\u2B7D\xF1\u0C98\u0200Aait\u2B88\u2B8B\u2B9D\u2BA7r\xF2\u2988rr\u0180;cw\u2B94\u2B95\u2B99\u619B;\uC000\u2933\u0338;\uC000\u219D\u0338ghtarrow\xBB\u2B95ri\u0100;e\u0CCB\u0CD6\u0380chimpqu\u2BBD\u2BCD\u2BD9\u2B04\u0B78\u2BE4\u2BEF\u0200;cer\u0D32\u2BC6\u0D37\u2BC9u\xE5\u0D45;\uC000\u{1D4C3}ort\u026D\u2B05\0\0\u2BD6ar\xE1\u2B56m\u0100;e\u0D6E\u2BDF\u0100;q\u0D74\u0D73su\u0100bp\u2BEB\u2BED\xE5\u0CF8\xE5\u0D0B\u0180bcp\u2BF6\u2C11\u2C19\u0200;Ees\u2BFF\u2C00\u0D22\u2C04\u6284;\uC000\u2AC5\u0338et\u0100;e\u0D1B\u2C0Bq\u0100;q\u0D23\u2C00c\u0100;e\u0D32\u2C17\xF1\u0D38\u0200;Ees\u2C22\u2C23\u0D5F\u2C27\u6285;\uC000\u2AC6\u0338et\u0100;e\u0D58\u2C2Eq\u0100;q\u0D60\u2C23\u0200gilr\u2C3D\u2C3F\u2C45\u2C47\xEC\u0BD7lde\u803B\xF1\u40F1\xE7\u0C43iangle\u0100lr\u2C52\u2C5Ceft\u0100;e\u0C1A\u2C5A\xF1\u0C26ight\u0100;e\u0CCB\u2C65\xF1\u0CD7\u0100;m\u2C6C\u2C6D\u43BD\u0180;es\u2C74\u2C75\u2C79\u4023ro;\u6116p;\u6007\u0480DHadgilrs\u2C8F\u2C94\u2C99\u2C9E\u2CA3\u2CB0\u2CB6\u2CD3\u2CE3ash;\u62ADarr;\u6904p;\uC000\u224D\u20D2ash;\u62AC\u0100et\u2CA8\u2CAC;\uC000\u2265\u20D2;\uC000>\u20D2nfin;\u69DE\u0180Aet\u2CBD\u2CC1\u2CC5rr;\u6902;\uC000\u2264\u20D2\u0100;r\u2CCA\u2CCD\uC000<\u20D2ie;\uC000\u22B4\u20D2\u0100At\u2CD8\u2CDCrr;\u6903rie;\uC000\u22B5\u20D2im;\uC000\u223C\u20D2\u0180Aan\u2CF0\u2CF4\u2D02rr;\u61D6r\u0100hr\u2CFA\u2CFDk;\u6923\u0100;o\u13E7\u13E5ear;\u6927\u1253\u1A95\0\0\0\0\0\0\0\0\0\0\0\0\0\u2D2D\0\u2D38\u2D48\u2D60\u2D65\u2D72\u2D84\u1B07\0\0\u2D8D\u2DAB\0\u2DC8\u2DCE\0\u2DDC\u2E19\u2E2B\u2E3E\u2E43\u0100cs\u2D31\u1A97ute\u803B\xF3\u40F3\u0100iy\u2D3C\u2D45r\u0100;c\u1A9E\u2D42\u803B\xF4\u40F4;\u443E\u0280abios\u1AA0\u2D52\u2D57\u01C8\u2D5Alac;\u4151v;\u6A38old;\u69BClig;\u4153\u0100cr\u2D69\u2D6Dir;\u69BF;\uC000\u{1D52C}\u036F\u2D79\0\0\u2D7C\0\u2D82n;\u42DBave\u803B\xF2\u40F2;\u69C1\u0100bm\u2D88\u0DF4ar;\u69B5\u0200acit\u2D95\u2D98\u2DA5\u2DA8r\xF2\u1A80\u0100ir\u2D9D\u2DA0r;\u69BEoss;\u69BBn\xE5\u0E52;\u69C0\u0180aei\u2DB1\u2DB5\u2DB9cr;\u414Dga;\u43C9\u0180cdn\u2DC0\u2DC5\u01CDron;\u43BF;\u69B6pf;\uC000\u{1D560}\u0180ael\u2DD4\u2DD7\u01D2r;\u69B7rp;\u69B9\u0380;adiosv\u2DEA\u2DEB\u2DEE\u2E08\u2E0D\u2E10\u2E16\u6228r\xF2\u1A86\u0200;efm\u2DF7\u2DF8\u2E02\u2E05\u6A5Dr\u0100;o\u2DFE\u2DFF\u6134f\xBB\u2DFF\u803B\xAA\u40AA\u803B\xBA\u40BAgof;\u62B6r;\u6A56lope;\u6A57;\u6A5B\u0180clo\u2E1F\u2E21\u2E27\xF2\u2E01ash\u803B\xF8\u40F8l;\u6298i\u016C\u2E2F\u2E34de\u803B\xF5\u40F5es\u0100;a\u01DB\u2E3As;\u6A36ml\u803B\xF6\u40F6bar;\u633D\u0AE1\u2E5E\0\u2E7D\0\u2E80\u2E9D\0\u2EA2\u2EB9\0\0\u2ECB\u0E9C\0\u2F13\0\0\u2F2B\u2FBC\0\u2FC8r\u0200;ast\u0403\u2E67\u2E72\u0E85\u8100\xB6;l\u2E6D\u2E6E\u40B6le\xEC\u0403\u0269\u2E78\0\0\u2E7Bm;\u6AF3;\u6AFDy;\u443Fr\u0280cimpt\u2E8B\u2E8F\u2E93\u1865\u2E97nt;\u4025od;\u402Eil;\u6030enk;\u6031r;\uC000\u{1D52D}\u0180imo\u2EA8\u2EB0\u2EB4\u0100;v\u2EAD\u2EAE\u43C6;\u43D5ma\xF4\u0A76ne;\u660E\u0180;tv\u2EBF\u2EC0\u2EC8\u43C0chfork\xBB\u1FFD;\u43D6\u0100au\u2ECF\u2EDFn\u0100ck\u2ED5\u2EDDk\u0100;h\u21F4\u2EDB;\u610E\xF6\u21F4s\u0480;abcdemst\u2EF3\u2EF4\u1908\u2EF9\u2EFD\u2F04\u2F06\u2F0A\u2F0E\u402Bcir;\u6A23ir;\u6A22\u0100ou\u1D40\u2F02;\u6A25;\u6A72n\u80BB\xB1\u0E9Dim;\u6A26wo;\u6A27\u0180ipu\u2F19\u2F20\u2F25ntint;\u6A15f;\uC000\u{1D561}nd\u803B\xA3\u40A3\u0500;Eaceinosu\u0EC8\u2F3F\u2F41\u2F44\u2F47\u2F81\u2F89\u2F92\u2F7E\u2FB6;\u6AB3p;\u6AB7u\xE5\u0ED9\u0100;c\u0ECE\u2F4C\u0300;acens\u0EC8\u2F59\u2F5F\u2F66\u2F68\u2F7Eppro\xF8\u2F43urlye\xF1\u0ED9\xF1\u0ECE\u0180aes\u2F6F\u2F76\u2F7Approx;\u6AB9qq;\u6AB5im;\u62E8i\xED\u0EDFme\u0100;s\u2F88\u0EAE\u6032\u0180Eas\u2F78\u2F90\u2F7A\xF0\u2F75\u0180dfp\u0EEC\u2F99\u2FAF\u0180als\u2FA0\u2FA5\u2FAAlar;\u632Eine;\u6312urf;\u6313\u0100;t\u0EFB\u2FB4\xEF\u0EFBrel;\u62B0\u0100ci\u2FC0\u2FC5r;\uC000\u{1D4C5};\u43C8ncsp;\u6008\u0300fiopsu\u2FDA\u22E2\u2FDF\u2FE5\u2FEB\u2FF1r;\uC000\u{1D52E}pf;\uC000\u{1D562}rime;\u6057cr;\uC000\u{1D4C6}\u0180aeo\u2FF8\u3009\u3013t\u0100ei\u2FFE\u3005rnion\xF3\u06B0nt;\u6A16st\u0100;e\u3010\u3011\u403F\xF1\u1F19\xF4\u0F14\u0A80ABHabcdefhilmnoprstux\u3040\u3051\u3055\u3059\u30E0\u310E\u312B\u3147\u3162\u3172\u318E\u3206\u3215\u3224\u3229\u3258\u326E\u3272\u3290\u32B0\u32B7\u0180art\u3047\u304A\u304Cr\xF2\u10B3\xF2\u03DDail;\u691Car\xF2\u1C65ar;\u6964\u0380cdenqrt\u3068\u3075\u3078\u307F\u308F\u3094\u30CC\u0100eu\u306D\u3071;\uC000\u223D\u0331te;\u4155i\xE3\u116Emptyv;\u69B3g\u0200;del\u0FD1\u3089\u308B\u308D;\u6992;\u69A5\xE5\u0FD1uo\u803B\xBB\u40BBr\u0580;abcfhlpstw\u0FDC\u30AC\u30AF\u30B7\u30B9\u30BC\u30BE\u30C0\u30C3\u30C7\u30CAp;\u6975\u0100;f\u0FE0\u30B4s;\u6920;\u6933s;\u691E\xEB\u225D\xF0\u272El;\u6945im;\u6974l;\u61A3;\u619D\u0100ai\u30D1\u30D5il;\u691Ao\u0100;n\u30DB\u30DC\u6236al\xF3\u0F1E\u0180abr\u30E7\u30EA\u30EEr\xF2\u17E5rk;\u6773\u0100ak\u30F3\u30FDc\u0100ek\u30F9\u30FB;\u407D;\u405D\u0100es\u3102\u3104;\u698Cl\u0100du\u310A\u310C;\u698E;\u6990\u0200aeuy\u3117\u311C\u3127\u3129ron;\u4159\u0100di\u3121\u3125il;\u4157\xEC\u0FF2\xE2\u30FA;\u4440\u0200clqs\u3134\u3137\u313D\u3144a;\u6937dhar;\u6969uo\u0100;r\u020E\u020Dh;\u61B3\u0180acg\u314E\u315F\u0F44l\u0200;ips\u0F78\u3158\u315B\u109Cn\xE5\u10BBar\xF4\u0FA9t;\u65AD\u0180ilr\u3169\u1023\u316Esht;\u697D;\uC000\u{1D52F}\u0100ao\u3177\u3186r\u0100du\u317D\u317F\xBB\u047B\u0100;l\u1091\u3184;\u696C\u0100;v\u318B\u318C\u43C1;\u43F1\u0180gns\u3195\u31F9\u31FCht\u0300ahlrst\u31A4\u31B0\u31C2\u31D8\u31E4\u31EErrow\u0100;t\u0FDC\u31ADa\xE9\u30C8arpoon\u0100du\u31BB\u31BFow\xEE\u317Ep\xBB\u1092eft\u0100ah\u31CA\u31D0rrow\xF3\u0FEAarpoon\xF3\u0551ightarrows;\u61C9quigarro\xF7\u30CBhreetimes;\u62CCg;\u42DAingdotse\xF1\u1F32\u0180ahm\u320D\u3210\u3213r\xF2\u0FEAa\xF2\u0551;\u600Foust\u0100;a\u321E\u321F\u63B1che\xBB\u321Fmid;\u6AEE\u0200abpt\u3232\u323D\u3240\u3252\u0100nr\u3237\u323Ag;\u67EDr;\u61FEr\xEB\u1003\u0180afl\u3247\u324A\u324Er;\u6986;\uC000\u{1D563}us;\u6A2Eimes;\u6A35\u0100ap\u325D\u3267r\u0100;g\u3263\u3264\u4029t;\u6994olint;\u6A12ar\xF2\u31E3\u0200achq\u327B\u3280\u10BC\u3285quo;\u603Ar;\uC000\u{1D4C7}\u0100bu\u30FB\u328Ao\u0100;r\u0214\u0213\u0180hir\u3297\u329B\u32A0re\xE5\u31F8mes;\u62CAi\u0200;efl\u32AA\u1059\u1821\u32AB\u65B9tri;\u69CEluhar;\u6968;\u611E\u0D61\u32D5\u32DB\u32DF\u332C\u3338\u3371\0\u337A\u33A4\0\0\u33EC\u33F0\0\u3428\u3448\u345A\u34AD\u34B1\u34CA\u34F1\0\u3616\0\0\u3633cute;\u415Bqu\xEF\u27BA\u0500;Eaceinpsy\u11ED\u32F3\u32F5\u32FF\u3302\u330B\u330F\u331F\u3326\u3329;\u6AB4\u01F0\u32FA\0\u32FC;\u6AB8on;\u4161u\xE5\u11FE\u0100;d\u11F3\u3307il;\u415Frc;\u415D\u0180Eas\u3316\u3318\u331B;\u6AB6p;\u6ABAim;\u62E9olint;\u6A13i\xED\u1204;\u4441ot\u0180;be\u3334\u1D47\u3335\u62C5;\u6A66\u0380Aacmstx\u3346\u334A\u3357\u335B\u335E\u3363\u336Drr;\u61D8r\u0100hr\u3350\u3352\xEB\u2228\u0100;o\u0A36\u0A34t\u803B\xA7\u40A7i;\u403Bwar;\u6929m\u0100in\u3369\xF0nu\xF3\xF1t;\u6736r\u0100;o\u3376\u2055\uC000\u{1D530}\u0200acoy\u3382\u3386\u3391\u33A0rp;\u666F\u0100hy\u338B\u338Fcy;\u4449;\u4448rt\u026D\u3399\0\0\u339Ci\xE4\u1464ara\xEC\u2E6F\u803B\xAD\u40AD\u0100gm\u33A8\u33B4ma\u0180;fv\u33B1\u33B2\u33B2\u43C3;\u43C2\u0400;deglnpr\u12AB\u33C5\u33C9\u33CE\u33D6\u33DE\u33E1\u33E6ot;\u6A6A\u0100;q\u12B1\u12B0\u0100;E\u33D3\u33D4\u6A9E;\u6AA0\u0100;E\u33DB\u33DC\u6A9D;\u6A9Fe;\u6246lus;\u6A24arr;\u6972ar\xF2\u113D\u0200aeit\u33F8\u3408\u340F\u3417\u0100ls\u33FD\u3404lsetm\xE9\u336Ahp;\u6A33parsl;\u69E4\u0100dl\u1463\u3414e;\u6323\u0100;e\u341C\u341D\u6AAA\u0100;s\u3422\u3423\u6AAC;\uC000\u2AAC\uFE00\u0180flp\u342E\u3433\u3442tcy;\u444C\u0100;b\u3438\u3439\u402F\u0100;a\u343E\u343F\u69C4r;\u633Ff;\uC000\u{1D564}a\u0100dr\u344D\u0402es\u0100;u\u3454\u3455\u6660it\xBB\u3455\u0180csu\u3460\u3479\u349F\u0100au\u3465\u346Fp\u0100;s\u1188\u346B;\uC000\u2293\uFE00p\u0100;s\u11B4\u3475;\uC000\u2294\uFE00u\u0100bp\u347F\u348F\u0180;es\u1197\u119C\u3486et\u0100;e\u1197\u348D\xF1\u119D\u0180;es\u11A8\u11AD\u3496et\u0100;e\u11A8\u349D\xF1\u11AE\u0180;af\u117B\u34A6\u05B0r\u0165\u34AB\u05B1\xBB\u117Car\xF2\u1148\u0200cemt\u34B9\u34BE\u34C2\u34C5r;\uC000\u{1D4C8}tm\xEE\xF1i\xEC\u3415ar\xE6\u11BE\u0100ar\u34CE\u34D5r\u0100;f\u34D4\u17BF\u6606\u0100an\u34DA\u34EDight\u0100ep\u34E3\u34EApsilo\xEE\u1EE0h\xE9\u2EAFs\xBB\u2852\u0280bcmnp\u34FB\u355E\u1209\u358B\u358E\u0480;Edemnprs\u350E\u350F\u3511\u3515\u351E\u3523\u352C\u3531\u3536\u6282;\u6AC5ot;\u6ABD\u0100;d\u11DA\u351Aot;\u6AC3ult;\u6AC1\u0100Ee\u3528\u352A;\u6ACB;\u628Alus;\u6ABFarr;\u6979\u0180eiu\u353D\u3552\u3555t\u0180;en\u350E\u3545\u354Bq\u0100;q\u11DA\u350Feq\u0100;q\u352B\u3528m;\u6AC7\u0100bp\u355A\u355C;\u6AD5;\u6AD3c\u0300;acens\u11ED\u356C\u3572\u3579\u357B\u3326ppro\xF8\u32FAurlye\xF1\u11FE\xF1\u11F3\u0180aes\u3582\u3588\u331Bppro\xF8\u331Aq\xF1\u3317g;\u666A\u0680123;Edehlmnps\u35A9\u35AC\u35AF\u121C\u35B2\u35B4\u35C0\u35C9\u35D5\u35DA\u35DF\u35E8\u35ED\u803B\xB9\u40B9\u803B\xB2\u40B2\u803B\xB3\u40B3;\u6AC6\u0100os\u35B9\u35BCt;\u6ABEub;\u6AD8\u0100;d\u1222\u35C5ot;\u6AC4s\u0100ou\u35CF\u35D2l;\u67C9b;\u6AD7arr;\u697Bult;\u6AC2\u0100Ee\u35E4\u35E6;\u6ACC;\u628Blus;\u6AC0\u0180eiu\u35F4\u3609\u360Ct\u0180;en\u121C\u35FC\u3602q\u0100;q\u1222\u35B2eq\u0100;q\u35E7\u35E4m;\u6AC8\u0100bp\u3611\u3613;\u6AD4;\u6AD6\u0180Aan\u361C\u3620\u362Drr;\u61D9r\u0100hr\u3626\u3628\xEB\u222E\u0100;o\u0A2B\u0A29war;\u692Alig\u803B\xDF\u40DF\u0BE1\u3651\u365D\u3660\u12CE\u3673\u3679\0\u367E\u36C2\0\0\0\0\0\u36DB\u3703\0\u3709\u376C\0\0\0\u3787\u0272\u3656\0\0\u365Bget;\u6316;\u43C4r\xEB\u0E5F\u0180aey\u3666\u366B\u3670ron;\u4165dil;\u4163;\u4442lrec;\u6315r;\uC000\u{1D531}\u0200eiko\u3686\u369D\u36B5\u36BC\u01F2\u368B\0\u3691e\u01004f\u1284\u1281a\u0180;sv\u3698\u3699\u369B\u43B8ym;\u43D1\u0100cn\u36A2\u36B2k\u0100as\u36A8\u36AEppro\xF8\u12C1im\xBB\u12ACs\xF0\u129E\u0100as\u36BA\u36AE\xF0\u12C1rn\u803B\xFE\u40FE\u01EC\u031F\u36C6\u22E7es\u8180\xD7;bd\u36CF\u36D0\u36D8\u40D7\u0100;a\u190F\u36D5r;\u6A31;\u6A30\u0180eps\u36E1\u36E3\u3700\xE1\u2A4D\u0200;bcf\u0486\u36EC\u36F0\u36F4ot;\u6336ir;\u6AF1\u0100;o\u36F9\u36FC\uC000\u{1D565}rk;\u6ADA\xE1\u3362rime;\u6034\u0180aip\u370F\u3712\u3764d\xE5\u1248\u0380adempst\u3721\u374D\u3740\u3751\u3757\u375C\u375Fngle\u0280;dlqr\u3730\u3731\u3736\u3740\u3742\u65B5own\xBB\u1DBBeft\u0100;e\u2800\u373E\xF1\u092E;\u625Cight\u0100;e\u32AA\u374B\xF1\u105Aot;\u65ECinus;\u6A3Alus;\u6A39b;\u69CDime;\u6A3Bezium;\u63E2\u0180cht\u3772\u377D\u3781\u0100ry\u3777\u377B;\uC000\u{1D4C9};\u4446cy;\u445Brok;\u4167\u0100io\u378B\u378Ex\xF4\u1777head\u0100lr\u3797\u37A0eftarro\xF7\u084Fightarrow\xBB\u0F5D\u0900AHabcdfghlmoprstuw\u37D0\u37D3\u37D7\u37E4\u37F0\u37FC\u380E\u381C\u3823\u3834\u3851\u385D\u386B\u38A9\u38CC\u38D2\u38EA\u38F6r\xF2\u03EDar;\u6963\u0100cr\u37DC\u37E2ute\u803B\xFA\u40FA\xF2\u1150r\u01E3\u37EA\0\u37EDy;\u445Eve;\u416D\u0100iy\u37F5\u37FArc\u803B\xFB\u40FB;\u4443\u0180abh\u3803\u3806\u380Br\xF2\u13ADlac;\u4171a\xF2\u13C3\u0100ir\u3813\u3818sht;\u697E;\uC000\u{1D532}rave\u803B\xF9\u40F9\u0161\u3827\u3831r\u0100lr\u382C\u382E\xBB\u0957\xBB\u1083lk;\u6580\u0100ct\u3839\u384D\u026F\u383F\0\0\u384Arn\u0100;e\u3845\u3846\u631Cr\xBB\u3846op;\u630Fri;\u65F8\u0100al\u3856\u385Acr;\u416B\u80BB\xA8\u0349\u0100gp\u3862\u3866on;\u4173f;\uC000\u{1D566}\u0300adhlsu\u114B\u3878\u387D\u1372\u3891\u38A0own\xE1\u13B3arpoon\u0100lr\u3888\u388Cef\xF4\u382Digh\xF4\u382Fi\u0180;hl\u3899\u389A\u389C\u43C5\xBB\u13FAon\xBB\u389Aparrows;\u61C8\u0180cit\u38B0\u38C4\u38C8\u026F\u38B6\0\0\u38C1rn\u0100;e\u38BC\u38BD\u631Dr\xBB\u38BDop;\u630Eng;\u416Fri;\u65F9cr;\uC000\u{1D4CA}\u0180dir\u38D9\u38DD\u38E2ot;\u62F0lde;\u4169i\u0100;f\u3730\u38E8\xBB\u1813\u0100am\u38EF\u38F2r\xF2\u38A8l\u803B\xFC\u40FCangle;\u69A7\u0780ABDacdeflnoprsz\u391C\u391F\u3929\u392D\u39B5\u39B8\u39BD\u39DF\u39E4\u39E8\u39F3\u39F9\u39FD\u3A01\u3A20r\xF2\u03F7ar\u0100;v\u3926\u3927\u6AE8;\u6AE9as\xE8\u03E1\u0100nr\u3932\u3937grt;\u699C\u0380eknprst\u34E3\u3946\u394B\u3952\u395D\u3964\u3996app\xE1\u2415othin\xE7\u1E96\u0180hir\u34EB\u2EC8\u3959op\xF4\u2FB5\u0100;h\u13B7\u3962\xEF\u318D\u0100iu\u3969\u396Dgm\xE1\u33B3\u0100bp\u3972\u3984setneq\u0100;q\u397D\u3980\uC000\u228A\uFE00;\uC000\u2ACB\uFE00setneq\u0100;q\u398F\u3992\uC000\u228B\uFE00;\uC000\u2ACC\uFE00\u0100hr\u399B\u399Fet\xE1\u369Ciangle\u0100lr\u39AA\u39AFeft\xBB\u0925ight\xBB\u1051y;\u4432ash\xBB\u1036\u0180elr\u39C4\u39D2\u39D7\u0180;be\u2DEA\u39CB\u39CFar;\u62BBq;\u625Alip;\u62EE\u0100bt\u39DC\u1468a\xF2\u1469r;\uC000\u{1D533}tr\xE9\u39AEsu\u0100bp\u39EF\u39F1\xBB\u0D1C\xBB\u0D59pf;\uC000\u{1D567}ro\xF0\u0EFBtr\xE9\u39B4\u0100cu\u3A06\u3A0Br;\uC000\u{1D4CB}\u0100bp\u3A10\u3A18n\u0100Ee\u3980\u3A16\xBB\u397En\u0100Ee\u3992\u3A1E\xBB\u3990igzag;\u699A\u0380cefoprs\u3A36\u3A3B\u3A56\u3A5B\u3A54\u3A61\u3A6Airc;\u4175\u0100di\u3A40\u3A51\u0100bg\u3A45\u3A49ar;\u6A5Fe\u0100;q\u15FA\u3A4F;\u6259erp;\u6118r;\uC000\u{1D534}pf;\uC000\u{1D568}\u0100;e\u1479\u3A66at\xE8\u1479cr;\uC000\u{1D4CC}\u0AE3\u178E\u3A87\0\u3A8B\0\u3A90\u3A9B\0\0\u3A9D\u3AA8\u3AAB\u3AAF\0\0\u3AC3\u3ACE\0\u3AD8\u17DC\u17DFtr\xE9\u17D1r;\uC000\u{1D535}\u0100Aa\u3A94\u3A97r\xF2\u03C3r\xF2\u09F6;\u43BE\u0100Aa\u3AA1\u3AA4r\xF2\u03B8r\xF2\u09EBa\xF0\u2713is;\u62FB\u0180dpt\u17A4\u3AB5\u3ABE\u0100fl\u3ABA\u17A9;\uC000\u{1D569}im\xE5\u17B2\u0100Aa\u3AC7\u3ACAr\xF2\u03CEr\xF2\u0A01\u0100cq\u3AD2\u17B8r;\uC000\u{1D4CD}\u0100pt\u17D6\u3ADCr\xE9\u17D4\u0400acefiosu\u3AF0\u3AFD\u3B08\u3B0C\u3B11\u3B15\u3B1B\u3B21c\u0100uy\u3AF6\u3AFBte\u803B\xFD\u40FD;\u444F\u0100iy\u3B02\u3B06rc;\u4177;\u444Bn\u803B\xA5\u40A5r;\uC000\u{1D536}cy;\u4457pf;\uC000\u{1D56A}cr;\uC000\u{1D4CE}\u0100cm\u3B26\u3B29y;\u444El\u803B\xFF\u40FF\u0500acdefhiosw\u3B42\u3B48\u3B54\u3B58\u3B64\u3B69\u3B6D\u3B74\u3B7A\u3B80cute;\u417A\u0100ay\u3B4D\u3B52ron;\u417E;\u4437ot;\u417C\u0100et\u3B5D\u3B61tr\xE6\u155Fa;\u43B6r;\uC000\u{1D537}cy;\u4436grarr;\u61DDpf;\uC000\u{1D56B}cr;\uC000\u{1D4CF}\u0100jn\u3B85\u3B87;\u600Dj;\u600C'.split("").map(u=>u.charCodeAt(0)));var Su=new Uint16Array("\u0200aglq \x1B\u026D\0\0p;\u4026os;\u4027t;\u403Et;\u403Cuot;\u4022".split("").map(u=>u.charCodeAt(0)));var zu,He=new Map([[0,65533],[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]),cu=(zu=String.fromCodePoint)!==null&&zu!==void 0?zu:function(u){let e="";return u>65535&&(u-=65536,e+=String.fromCharCode(u>>>10&1023|55296),u=56320|u&1023),e+=String.fromCharCode(u),e};function Wu(u){var e;return u>=55296&&u<=57343||u>1114111?65533:(e=He.get(u))!==null&&e!==void 0?e:u}var A;(function(u){u[u.NUM=35]="NUM",u[u.SEMI=59]="SEMI",u[u.EQUALS=61]="EQUALS",u[u.ZERO=48]="ZERO",u[u.NINE=57]="NINE",u[u.LOWER_A=97]="LOWER_A",u[u.LOWER_F=102]="LOWER_F",u[u.LOWER_X=120]="LOWER_X",u[u.LOWER_Z=122]="LOWER_Z",u[u.UPPER_A=65]="UPPER_A",u[u.UPPER_F=70]="UPPER_F",u[u.UPPER_Z=90]="UPPER_Z"})(A||(A={}));var Fe=32,O;(function(u){u[u.VALUE_LENGTH=49152]="VALUE_LENGTH",u[u.BRANCH_LENGTH=16256]="BRANCH_LENGTH",u[u.JUMP_TABLE=127]="JUMP_TABLE"})(O||(O={}));function Qu(u){return u>=A.ZERO&&u<=A.NINE}function Ge(u){return u>=A.UPPER_A&&u<=A.UPPER_F||u>=A.LOWER_A&&u<=A.LOWER_F}function je(u){return u>=A.UPPER_A&&u<=A.UPPER_Z||u>=A.LOWER_A&&u<=A.LOWER_Z||Qu(u)}function ze(u){return u===A.EQUALS||je(u)}var E;(function(u){u[u.EntityStart=0]="EntityStart",u[u.NumericStart=1]="NumericStart",u[u.NumericDecimal=2]="NumericDecimal",u[u.NumericHex=3]="NumericHex",u[u.NamedEntity=4]="NamedEntity"})(E||(E={}));var R;(function(u){u[u.Legacy=0]="Legacy",u[u.Strict=1]="Strict",u[u.Attribute=2]="Attribute"})(R||(R={}));var qu=class{constructor(e,t,a){this.decodeTree=e,this.emitCodePoint=t,this.errors=a,this.state=E.EntityStart,this.consumed=1,this.result=0,this.treeIndex=0,this.excess=1,this.decodeMode=R.Strict}startEntity(e){this.decodeMode=e,this.state=E.EntityStart,this.result=0,this.treeIndex=0,this.excess=1,this.consumed=1}write(e,t){switch(this.state){case E.EntityStart:return e.charCodeAt(t)===A.NUM?(this.state=E.NumericStart,this.consumed+=1,this.stateNumericStart(e,t+1)):(this.state=E.NamedEntity,this.stateNamedEntity(e,t));case E.NumericStart:return this.stateNumericStart(e,t);case E.NumericDecimal:return this.stateNumericDecimal(e,t);case E.NumericHex:return this.stateNumericHex(e,t);case E.NamedEntity:return this.stateNamedEntity(e,t)}}stateNumericStart(e,t){return t>=e.length?-1:(e.charCodeAt(t)|Fe)===A.LOWER_X?(this.state=E.NumericHex,this.consumed+=1,this.stateNumericHex(e,t+1)):(this.state=E.NumericDecimal,this.stateNumericDecimal(e,t))}addToNumericResult(e,t,a,r){if(t!==a){let c=a-t;this.result=this.result*Math.pow(r,c)+Number.parseInt(e.substr(t,c),r),this.consumed+=c}}stateNumericHex(e,t){let a=t;for(;t>14;for(;t>14,c!==0){if(i===A.SEMI)return this.emitNamedEntityData(this.treeIndex,c,this.consumed+this.excess);this.decodeMode!==R.Strict&&(this.result=this.treeIndex,this.consumed+=this.excess,this.excess=0)}}return-1}emitNotTerminatedNamedEntity(){var e;let{result:t,decodeTree:a}=this,r=(a[t]&O.VALUE_LENGTH)>>14;return this.emitNamedEntityData(t,r,this.consumed),(e=this.errors)===null||e===void 0||e.missingSemicolonAfterCharacterReference(),this.consumed}emitNamedEntityData(e,t,a){let{decodeTree:r}=this;return this.emitCodePoint(t===1?r[e]&~O.VALUE_LENGTH:r[e+1],a),t===3&&this.emitCodePoint(r[e+2],a),a}end(){var e;switch(this.state){case E.NamedEntity:return this.result!==0&&(this.decodeMode!==R.Attribute||this.result===this.treeIndex)?this.emitNotTerminatedNamedEntity():0;case E.NumericDecimal:return this.emitNumericEntity(0,2);case E.NumericHex:return this.emitNumericEntity(0,3);case E.NumericStart:return(e=this.errors)===null||e===void 0||e.absenceOfDigitsInNumericCharacterReference(this.consumed),0;case E.EntityStart:return 0}}};function We(u,e,t,a){let r=(e&O.BRANCH_LENGTH)>>7,c=e&O.JUMP_TABLE;if(r===0)return c!==0&&a===c?t:-1;if(c){let s=a-c;return s<0||s>=r?-1:u[t+s]-1}let i=t,n=i+r-1;for(;i<=n;){let s=i+n>>>1,d=u[s];if(da)n=s-1;else return u[s+r]}return-1}var l;(function(u){u[u.Tab=9]="Tab",u[u.NewLine=10]="NewLine",u[u.FormFeed=12]="FormFeed",u[u.CarriageReturn=13]="CarriageReturn",u[u.Space=32]="Space",u[u.ExclamationMark=33]="ExclamationMark",u[u.Number=35]="Number",u[u.Amp=38]="Amp",u[u.SingleQuote=39]="SingleQuote",u[u.DoubleQuote=34]="DoubleQuote",u[u.Dash=45]="Dash",u[u.Slash=47]="Slash",u[u.Zero=48]="Zero",u[u.Nine=57]="Nine",u[u.Semi=59]="Semi",u[u.Lt=60]="Lt",u[u.Eq=61]="Eq",u[u.Gt=62]="Gt",u[u.Questionmark=63]="Questionmark",u[u.UpperA=65]="UpperA",u[u.LowerA=97]="LowerA",u[u.UpperF=70]="UpperF",u[u.LowerF=102]="LowerF",u[u.UpperZ=90]="UpperZ",u[u.LowerZ=122]="LowerZ",u[u.LowerX=120]="LowerX",u[u.OpeningSquareBracket=91]="OpeningSquareBracket"})(l||(l={}));var f;(function(u){u[u.Text=1]="Text",u[u.BeforeTagName=2]="BeforeTagName",u[u.InTagName=3]="InTagName",u[u.InSelfClosingTag=4]="InSelfClosingTag",u[u.BeforeClosingTagName=5]="BeforeClosingTagName",u[u.InClosingTagName=6]="InClosingTagName",u[u.AfterClosingTagName=7]="AfterClosingTagName",u[u.BeforeAttributeName=8]="BeforeAttributeName",u[u.InAttributeName=9]="InAttributeName",u[u.AfterAttributeName=10]="AfterAttributeName",u[u.BeforeAttributeValue=11]="BeforeAttributeValue",u[u.InAttributeValueDq=12]="InAttributeValueDq",u[u.InAttributeValueSq=13]="InAttributeValueSq",u[u.InAttributeValueNq=14]="InAttributeValueNq",u[u.BeforeDeclaration=15]="BeforeDeclaration",u[u.InDeclaration=16]="InDeclaration",u[u.InProcessingInstruction=17]="InProcessingInstruction",u[u.BeforeComment=18]="BeforeComment",u[u.CDATASequence=19]="CDATASequence",u[u.InSpecialComment=20]="InSpecialComment",u[u.InCommentLike=21]="InCommentLike",u[u.BeforeSpecialS=22]="BeforeSpecialS",u[u.BeforeSpecialT=23]="BeforeSpecialT",u[u.SpecialStartSequence=24]="SpecialStartSequence",u[u.InSpecialTag=25]="InSpecialTag",u[u.InEntity=26]="InEntity"})(f||(f={}));function B(u){return u===l.Space||u===l.NewLine||u===l.Tab||u===l.FormFeed||u===l.CarriageReturn}function Nu(u){return u===l.Slash||u===l.Gt||B(u)}function Qe(u){return u>=l.LowerA&&u<=l.LowerZ||u>=l.UpperA&&u<=l.UpperZ}var D;(function(u){u[u.NoValue=0]="NoValue",u[u.Unquoted=1]="Unquoted",u[u.Single=2]="Single",u[u.Double=3]="Double"})(D||(D={}));var w={Cdata:new Uint8Array([67,68,65,84,65,91]),CdataEnd:new Uint8Array([93,93,62]),CommentEnd:new Uint8Array([45,45,62]),ScriptEnd:new Uint8Array([60,47,115,99,114,105,112,116]),StyleEnd:new Uint8Array([60,47,115,116,121,108,101]),TitleEnd:new Uint8Array([60,47,116,105,116,108,101]),TextareaEnd:new Uint8Array([60,47,116,101,120,116,97,114,101,97]),XmpEnd:new Uint8Array([60,47,120,109,112])},z=class{constructor({xmlMode:e=!1,decodeEntities:t=!0},a){this.cbs=a,this.state=f.Text,this.buffer="",this.sectionStart=0,this.index=0,this.entityStart=0,this.baseState=f.Text,this.isSpecial=!1,this.running=!0,this.offset=0,this.currentSequence=void 0,this.sequenceIndex=0,this.xmlMode=e,this.decodeEntities=t,this.entityDecoder=new qu(e?Su:Tu,(r,c)=>this.emitCodePoint(r,c))}reset(){this.state=f.Text,this.buffer="",this.sectionStart=0,this.index=0,this.baseState=f.Text,this.currentSequence=void 0,this.running=!0,this.offset=0}write(e){this.offset+=this.buffer.length,this.buffer=e,this.parse()}end(){this.running&&this.finish()}pause(){this.running=!1}resume(){this.running=!0,this.indexthis.sectionStart&&this.cbs.ontext(this.sectionStart,this.index),this.state=f.BeforeTagName,this.sectionStart=this.index):this.decodeEntities&&e===l.Amp&&this.startEntity()}stateSpecialStartSequence(e){let t=this.sequenceIndex===this.currentSequence.length;if(!(t?Nu(e):(e|32)===this.currentSequence[this.sequenceIndex]))this.isSpecial=!1;else if(!t){this.sequenceIndex++;return}this.sequenceIndex=0,this.state=f.InTagName,this.stateInTagName(e)}stateInSpecialTag(e){if(this.sequenceIndex===this.currentSequence.length){if(e===l.Gt||B(e)){let t=this.index-this.currentSequence.length;if(this.sectionStart=0?(this.state=this.baseState,e===0&&(this.index=this.entityStart)):this.index=this.offset+this.buffer.length-1}cleanup(){this.running&&this.sectionStart!==this.index&&(this.state===f.Text||this.state===f.InSpecialTag&&this.sequenceIndex===0?(this.cbs.ontext(this.sectionStart,this.index),this.sectionStart=this.index):(this.state===f.InAttributeValueDq||this.state===f.InAttributeValueSq||this.state===f.InAttributeValueNq)&&(this.cbs.onattribdata(this.sectionStart,this.index),this.sectionStart=this.index))}shouldContinue(){return this.index=e||(this.state===f.InCommentLike?this.currentSequence===w.CdataEnd?this.cbs.oncdata(this.sectionStart,e,0):this.cbs.oncomment(this.sectionStart,e,0):this.state===f.InTagName||this.state===f.BeforeAttributeName||this.state===f.BeforeAttributeValue||this.state===f.AfterAttributeName||this.state===f.InAttributeName||this.state===f.InAttributeValueSq||this.state===f.InAttributeValueDq||this.state===f.InAttributeValueNq||this.state===f.InClosingTagName||this.cbs.ontext(this.sectionStart,e))}emitCodePoint(e,t){this.baseState!==f.Text&&this.baseState!==f.InSpecialTag?(this.sectionStart0&&i.has(this.stack[0]);){let n=this.stack.shift();(a=(t=this.cbs).onclosetag)===null||a===void 0||a.call(t,n,!0)}this.isVoidElement(e)||(this.stack.unshift(e),this.htmlMode&&(O0.has(e)?this.foreignContext.unshift(!0):H0.has(e)&&this.foreignContext.unshift(!1))),(c=(r=this.cbs).onopentagname)===null||c===void 0||c.call(r,e),this.cbs.onopentag&&(this.attribs={})}endOpenTag(e){var t,a;this.startIndex=this.openTagStart,this.attribs&&((a=(t=this.cbs).onopentag)===null||a===void 0||a.call(t,this.tagname,this.attribs,e),this.attribs=null),this.cbs.onclosetag&&this.isVoidElement(this.tagname)&&this.cbs.onclosetag(this.tagname,!0),this.tagname=""}onopentagend(e){this.endIndex=e,this.endOpenTag(!1),this.startIndex=e+1}onclosetag(e,t){var a,r,c,i,n,s,d,o;this.endIndex=t;let x=this.getSlice(e,t);if(this.lowerCaseTagNames&&(x=x.toLowerCase()),this.htmlMode&&(O0.has(x)||H0.has(x))&&this.foreignContext.shift(),this.isVoidElement(x))this.htmlMode&&x==="br"&&((i=(c=this.cbs).onopentagname)===null||i===void 0||i.call(c,"br"),(s=(n=this.cbs).onopentag)===null||s===void 0||s.call(n,"br",{},!0),(o=(d=this.cbs).onclosetag)===null||o===void 0||o.call(d,"br",!1));else{let m=this.stack.indexOf(x);if(m!==-1)for(let h=0;h<=m;h++){let g=this.stack.shift();(r=(a=this.cbs).onclosetag)===null||r===void 0||r.call(a,g,h!==m)}else this.htmlMode&&x==="p"&&(this.emitOpenTag("p"),this.closeCurrentTag(!0))}this.startIndex=t+1}onselfclosingtag(e){this.endIndex=e,this.recognizeSelfClosing||this.foreignContext[0]?(this.closeCurrentTag(!1),this.startIndex=e+1):this.onopentagend(e)}closeCurrentTag(e){var t,a;let r=this.tagname;this.endOpenTag(e),this.stack[0]===r&&((a=(t=this.cbs).onclosetag)===null||a===void 0||a.call(t,r,!e),this.stack.shift())}onattribname(e,t){this.startIndex=e;let a=this.getSlice(e,t);this.attribname=this.lowerCaseAttributeNames?a.toLowerCase():a}onattribdata(e,t){this.attribvalue+=this.getSlice(e,t)}onattribentity(e){this.attribvalue+=cu(e)}onattribend(e,t){var a,r;this.endIndex=t,(r=(a=this.cbs).onattribute)===null||r===void 0||r.call(a,this.attribname,this.attribvalue,e===D.Double?'"':e===D.Single?"'":e===D.NoValue?void 0:null),this.attribs&&!Object.prototype.hasOwnProperty.call(this.attribs,this.attribname)&&(this.attribs[this.attribname]=this.attribvalue),this.attribvalue=""}getInstructionName(e){let t=e.search(Xe),a=t<0?e:e.substr(0,t);return this.lowerCaseTagNames&&(a=a.toLowerCase()),a}ondeclaration(e,t){this.endIndex=t;let a=this.getSlice(e,t);if(this.cbs.onprocessinginstruction){let r=this.getInstructionName(a);this.cbs.onprocessinginstruction(`!${r}`,`!${a}`)}this.startIndex=t+1}onprocessinginstruction(e,t){this.endIndex=t;let a=this.getSlice(e,t);if(this.cbs.onprocessinginstruction){let r=this.getInstructionName(a);this.cbs.onprocessinginstruction(`?${r}`,`?${a}`)}this.startIndex=t+1}oncomment(e,t,a){var r,c,i,n;this.endIndex=t,(c=(r=this.cbs).oncomment)===null||c===void 0||c.call(r,this.getSlice(e,t-a)),(n=(i=this.cbs).oncommentend)===null||n===void 0||n.call(i),this.startIndex=t+1}oncdata(e,t,a){var r,c,i,n,s,d,o,x,m,h;this.endIndex=t;let g=this.getSlice(e,t-a);!this.htmlMode||this.options.recognizeCDATA?((c=(r=this.cbs).oncdatastart)===null||c===void 0||c.call(r),(n=(i=this.cbs).ontext)===null||n===void 0||n.call(i,g),(d=(s=this.cbs).oncdataend)===null||d===void 0||d.call(s)):((x=(o=this.cbs).oncomment)===null||x===void 0||x.call(o,`[CDATA[${g}]]`),(h=(m=this.cbs).oncommentend)===null||h===void 0||h.call(m)),this.startIndex=t+1}onend(){var e,t;if(this.cbs.onclosetag){this.endIndex=this.startIndex;for(let a=0;a=this.buffers[0].length;)this.shiftBuffer();let a=this.buffers[0].slice(e-this.bufferOffset,t-this.bufferOffset);for(;t-this.bufferOffset>this.buffers[0].length;)this.shiftBuffer(),a+=this.buffers[0].slice(0,t-this.bufferOffset);return a}shiftBuffer(){this.bufferOffset+=this.buffers[0].length,this.writeIndex--,this.buffers.shift()}write(e){var t,a;if(this.ended){(a=(t=this.cbs).onerror)===null||a===void 0||a.call(t,new Error(".write() after done!"));return}this.buffers.push(e),this.tokenizer.running&&(this.tokenizer.write(e),this.writeIndex++)}end(e){var t,a;if(this.ended){(a=(t=this.cbs).onerror)===null||a===void 0||a.call(t,new Error(".end() after done!"));return}e&&this.write(e),this.ended=!0,this.tokenizer.end()}pause(){this.tokenizer.pause()}resume(){for(this.tokenizer.resume();this.tokenizer.running&&this.writeIndext0,Comment:()=>Yu,Directive:()=>Ju,Doctype:()=>a0,ElementType:()=>p,Root:()=>$u,Script:()=>Ku,Style:()=>u0,Tag:()=>e0,Text:()=>Xu,isTag:()=>Zu});var p;(function(u){u.Root="root",u.Text="text",u.Directive="directive",u.Comment="comment",u.Script="script",u.Style="style",u.Tag="tag",u.CDATA="cdata",u.Doctype="doctype"})(p||(p={}));function Zu(u){return u.type===p.Tag||u.type===p.Script||u.type===p.Style}var $u=p.Root,Xu=p.Text,Ju=p.Directive,Yu=p.Comment,Ku=p.Script,u0=p.Style,e0=p.Tag,t0=p.CDATA,a0=p.Doctype;var Lu=class{constructor(){this.parent=null,this.prev=null,this.next=null,this.startIndex=null,this.endIndex=null}get parentNode(){return this.parent}set parentNode(e){this.parent=e}get previousSibling(){return this.prev}set previousSibling(e){this.prev=e}get nextSibling(){return this.next}set nextSibling(e){this.next=e}cloneNode(e=!1){return F0(this,e)}},iu=class extends Lu{constructor(e){super(),this.data=e}get nodeValue(){return this.data}set nodeValue(e){this.data=e}},J=class extends iu{constructor(){super(...arguments),this.type=p.Text}get nodeType(){return 3}},nu=class extends iu{constructor(){super(...arguments),this.type=p.Comment}get nodeType(){return 8}},su=class extends iu{constructor(e,t){super(t),this.name=e,this.type=p.Directive}get nodeType(){return 1}},du=class extends Lu{constructor(e){super(),this.children=e}get firstChild(){var e;return(e=this.children[0])!==null&&e!==void 0?e:null}get lastChild(){return this.children.length>0?this.children[this.children.length-1]:null}get childNodes(){return this.children}set childNodes(e){this.children=e}},fu=class extends du{constructor(){super(...arguments),this.type=p.CDATA}get nodeType(){return 4}},Y=class extends du{constructor(){super(...arguments),this.type=p.Root}get nodeType(){return 9}},ou=class extends du{constructor(e,t,a=[],r=e==="script"?p.Script:e==="style"?p.Style:p.Tag){super(a),this.name=e,this.attribs=t,this.type=r}get nodeType(){return 1}get tagName(){return this.name}set tagName(e){this.name=e}get attributes(){return Object.keys(this.attribs).map(e=>{var t,a;return{name:e,value:this.attribs[e],namespace:(t=this["x-attribsNamespace"])===null||t===void 0?void 0:t[e],prefix:(a=this["x-attribsPrefix"])===null||a===void 0?void 0:a[e]}})}};function T(u){return Zu(u)}function K(u){return u.type===p.CDATA}function M(u){return u.type===p.Text}function bu(u){return u.type===p.Comment}function Je(u){return u.type===p.Directive}function c0(u){return u.type===p.Root}function N(u){return Object.prototype.hasOwnProperty.call(u,"children")}function F0(u,e=!1){let t;if(M(u))t=new J(u.data);else if(bu(u))t=new nu(u.data);else if(T(u)){let a=e?r0(u.children):[],r=new ou(u.name,{...u.attribs},a);a.forEach(c=>c.parent=r),u.namespace!=null&&(r.namespace=u.namespace),u["x-attribsNamespace"]&&(r["x-attribsNamespace"]={...u["x-attribsNamespace"]}),u["x-attribsPrefix"]&&(r["x-attribsPrefix"]={...u["x-attribsPrefix"]}),t=r}else if(K(u)){let a=e?r0(u.children):[],r=new fu(a);a.forEach(c=>c.parent=r),t=r}else if(c0(u)){let a=e?r0(u.children):[],r=new Y(a);a.forEach(c=>c.parent=r),u["x-mode"]&&(r["x-mode"]=u["x-mode"]),t=r}else if(Je(u)){let a=new su(u.name,u.data);u["x-name"]!=null&&(a["x-name"]=u["x-name"],a["x-publicId"]=u["x-publicId"],a["x-systemId"]=u["x-systemId"]),t=a}else throw new Error(`Not implemented yet: ${u.type}`);return t.startIndex=u.startIndex,t.endIndex=u.endIndex,u.sourceCodeLocation!=null&&(t.sourceCodeLocation=u.sourceCodeLocation),t}function r0(u){let e=u.map(t=>F0(t,!0));for(let t=1;tI,append:()=>qt,appendChild:()=>St,compareDocumentPosition:()=>fe,existsOne:()=>se,filter:()=>uu,find:()=>ne,findAll:()=>Dt,findOne:()=>Mu,findOneChild:()=>Lt,getAttributeValue:()=>yt,getChildren:()=>ce,getElementById:()=>_t,getElements:()=>Rt,getElementsByClassName:()=>Mt,getElementsByTagName:()=>W,getElementsByTagType:()=>Bt,getFeed:()=>xu,getInnerHTML:()=>mt,getName:()=>wt,getOuterHTML:()=>re,getParent:()=>ie,getSiblings:()=>gt,getText:()=>_u,hasAttrib:()=>vt,hasChildren:()=>N,innerText:()=>p0,isCDATA:()=>K,isComment:()=>bu,isDocument:()=>c0,isTag:()=>T,isText:()=>M,nextElementSibling:()=>Et,prepend:()=>kt,prependChild:()=>Nt,prevElementSibling:()=>At,removeElement:()=>pu,removeSubsets:()=>Pt,replaceElement:()=>Tt,testElement:()=>Ct,textContent:()=>hu,uniqueSort:()=>Vt});var j0=new Uint16Array('\u1D41<\xD5\u0131\u028A\u049D\u057B\u05D0\u0675\u06DE\u07A2\u07D6\u080F\u0A4A\u0A91\u0DA1\u0E6D\u0F09\u0F26\u10CA\u1228\u12E1\u1415\u149D\u14C3\u14DF\u1525\0\0\0\0\0\0\u156B\u16CD\u198D\u1C12\u1DDD\u1F7E\u2060\u21B0\u228D\u23C0\u23FB\u2442\u2824\u2912\u2D08\u2E48\u2FCE\u3016\u32BA\u3639\u37AC\u38FE\u3A28\u3A71\u3AE0\u3B2E\u0800EMabcfglmnoprstu\\bfms\x7F\x84\x8B\x90\x95\x98\xA6\xB3\xB9\xC8\xCFlig\u803B\xC6\u40C6P\u803B&\u4026cute\u803B\xC1\u40C1reve;\u4102\u0100iyx}rc\u803B\xC2\u40C2;\u4410r;\uC000\u{1D504}rave\u803B\xC0\u40C0pha;\u4391acr;\u4100d;\u6A53\u0100gp\x9D\xA1on;\u4104f;\uC000\u{1D538}plyFunction;\u6061ing\u803B\xC5\u40C5\u0100cs\xBE\xC3r;\uC000\u{1D49C}ign;\u6254ilde\u803B\xC3\u40C3ml\u803B\xC4\u40C4\u0400aceforsu\xE5\xFB\xFE\u0117\u011C\u0122\u0127\u012A\u0100cr\xEA\xF2kslash;\u6216\u0176\xF6\xF8;\u6AE7ed;\u6306y;\u4411\u0180crt\u0105\u010B\u0114ause;\u6235noullis;\u612Ca;\u4392r;\uC000\u{1D505}pf;\uC000\u{1D539}eve;\u42D8c\xF2\u0113mpeq;\u624E\u0700HOacdefhilorsu\u014D\u0151\u0156\u0180\u019E\u01A2\u01B5\u01B7\u01BA\u01DC\u0215\u0273\u0278\u027Ecy;\u4427PY\u803B\xA9\u40A9\u0180cpy\u015D\u0162\u017Aute;\u4106\u0100;i\u0167\u0168\u62D2talDifferentialD;\u6145leys;\u612D\u0200aeio\u0189\u018E\u0194\u0198ron;\u410Cdil\u803B\xC7\u40C7rc;\u4108nint;\u6230ot;\u410A\u0100dn\u01A7\u01ADilla;\u40B8terDot;\u40B7\xF2\u017Fi;\u43A7rcle\u0200DMPT\u01C7\u01CB\u01D1\u01D6ot;\u6299inus;\u6296lus;\u6295imes;\u6297o\u0100cs\u01E2\u01F8kwiseContourIntegral;\u6232eCurly\u0100DQ\u0203\u020FoubleQuote;\u601Duote;\u6019\u0200lnpu\u021E\u0228\u0247\u0255on\u0100;e\u0225\u0226\u6237;\u6A74\u0180git\u022F\u0236\u023Aruent;\u6261nt;\u622FourIntegral;\u622E\u0100fr\u024C\u024E;\u6102oduct;\u6210nterClockwiseContourIntegral;\u6233oss;\u6A2Fcr;\uC000\u{1D49E}p\u0100;C\u0284\u0285\u62D3ap;\u624D\u0580DJSZacefios\u02A0\u02AC\u02B0\u02B4\u02B8\u02CB\u02D7\u02E1\u02E6\u0333\u048D\u0100;o\u0179\u02A5trahd;\u6911cy;\u4402cy;\u4405cy;\u440F\u0180grs\u02BF\u02C4\u02C7ger;\u6021r;\u61A1hv;\u6AE4\u0100ay\u02D0\u02D5ron;\u410E;\u4414l\u0100;t\u02DD\u02DE\u6207a;\u4394r;\uC000\u{1D507}\u0100af\u02EB\u0327\u0100cm\u02F0\u0322ritical\u0200ADGT\u0300\u0306\u0316\u031Ccute;\u40B4o\u0174\u030B\u030D;\u42D9bleAcute;\u42DDrave;\u4060ilde;\u42DCond;\u62C4ferentialD;\u6146\u0470\u033D\0\0\0\u0342\u0354\0\u0405f;\uC000\u{1D53B}\u0180;DE\u0348\u0349\u034D\u40A8ot;\u60DCqual;\u6250ble\u0300CDLRUV\u0363\u0372\u0382\u03CF\u03E2\u03F8ontourIntegra\xEC\u0239o\u0274\u0379\0\0\u037B\xBB\u0349nArrow;\u61D3\u0100eo\u0387\u03A4ft\u0180ART\u0390\u0396\u03A1rrow;\u61D0ightArrow;\u61D4e\xE5\u02CAng\u0100LR\u03AB\u03C4eft\u0100AR\u03B3\u03B9rrow;\u67F8ightArrow;\u67FAightArrow;\u67F9ight\u0100AT\u03D8\u03DErrow;\u61D2ee;\u62A8p\u0241\u03E9\0\0\u03EFrrow;\u61D1ownArrow;\u61D5erticalBar;\u6225n\u0300ABLRTa\u0412\u042A\u0430\u045E\u047F\u037Crrow\u0180;BU\u041D\u041E\u0422\u6193ar;\u6913pArrow;\u61F5reve;\u4311eft\u02D2\u043A\0\u0446\0\u0450ightVector;\u6950eeVector;\u695Eector\u0100;B\u0459\u045A\u61BDar;\u6956ight\u01D4\u0467\0\u0471eeVector;\u695Fector\u0100;B\u047A\u047B\u61C1ar;\u6957ee\u0100;A\u0486\u0487\u62A4rrow;\u61A7\u0100ct\u0492\u0497r;\uC000\u{1D49F}rok;\u4110\u0800NTacdfglmopqstux\u04BD\u04C0\u04C4\u04CB\u04DE\u04E2\u04E7\u04EE\u04F5\u0521\u052F\u0536\u0552\u055D\u0560\u0565G;\u414AH\u803B\xD0\u40D0cute\u803B\xC9\u40C9\u0180aiy\u04D2\u04D7\u04DCron;\u411Arc\u803B\xCA\u40CA;\u442Dot;\u4116r;\uC000\u{1D508}rave\u803B\xC8\u40C8ement;\u6208\u0100ap\u04FA\u04FEcr;\u4112ty\u0253\u0506\0\0\u0512mallSquare;\u65FBerySmallSquare;\u65AB\u0100gp\u0526\u052Aon;\u4118f;\uC000\u{1D53C}silon;\u4395u\u0100ai\u053C\u0549l\u0100;T\u0542\u0543\u6A75ilde;\u6242librium;\u61CC\u0100ci\u0557\u055Ar;\u6130m;\u6A73a;\u4397ml\u803B\xCB\u40CB\u0100ip\u056A\u056Fsts;\u6203onentialE;\u6147\u0280cfios\u0585\u0588\u058D\u05B2\u05CCy;\u4424r;\uC000\u{1D509}lled\u0253\u0597\0\0\u05A3mallSquare;\u65FCerySmallSquare;\u65AA\u0370\u05BA\0\u05BF\0\0\u05C4f;\uC000\u{1D53D}All;\u6200riertrf;\u6131c\xF2\u05CB\u0600JTabcdfgorst\u05E8\u05EC\u05EF\u05FA\u0600\u0612\u0616\u061B\u061D\u0623\u066C\u0672cy;\u4403\u803B>\u403Emma\u0100;d\u05F7\u05F8\u4393;\u43DCreve;\u411E\u0180eiy\u0607\u060C\u0610dil;\u4122rc;\u411C;\u4413ot;\u4120r;\uC000\u{1D50A};\u62D9pf;\uC000\u{1D53E}eater\u0300EFGLST\u0635\u0644\u064E\u0656\u065B\u0666qual\u0100;L\u063E\u063F\u6265ess;\u62DBullEqual;\u6267reater;\u6AA2ess;\u6277lantEqual;\u6A7Eilde;\u6273cr;\uC000\u{1D4A2};\u626B\u0400Aacfiosu\u0685\u068B\u0696\u069B\u069E\u06AA\u06BE\u06CARDcy;\u442A\u0100ct\u0690\u0694ek;\u42C7;\u405Eirc;\u4124r;\u610ClbertSpace;\u610B\u01F0\u06AF\0\u06B2f;\u610DizontalLine;\u6500\u0100ct\u06C3\u06C5\xF2\u06A9rok;\u4126mp\u0144\u06D0\u06D8ownHum\xF0\u012Fqual;\u624F\u0700EJOacdfgmnostu\u06FA\u06FE\u0703\u0707\u070E\u071A\u071E\u0721\u0728\u0744\u0778\u078B\u078F\u0795cy;\u4415lig;\u4132cy;\u4401cute\u803B\xCD\u40CD\u0100iy\u0713\u0718rc\u803B\xCE\u40CE;\u4418ot;\u4130r;\u6111rave\u803B\xCC\u40CC\u0180;ap\u0720\u072F\u073F\u0100cg\u0734\u0737r;\u412AinaryI;\u6148lie\xF3\u03DD\u01F4\u0749\0\u0762\u0100;e\u074D\u074E\u622C\u0100gr\u0753\u0758ral;\u622Bsection;\u62C2isible\u0100CT\u076C\u0772omma;\u6063imes;\u6062\u0180gpt\u077F\u0783\u0788on;\u412Ef;\uC000\u{1D540}a;\u4399cr;\u6110ilde;\u4128\u01EB\u079A\0\u079Ecy;\u4406l\u803B\xCF\u40CF\u0280cfosu\u07AC\u07B7\u07BC\u07C2\u07D0\u0100iy\u07B1\u07B5rc;\u4134;\u4419r;\uC000\u{1D50D}pf;\uC000\u{1D541}\u01E3\u07C7\0\u07CCr;\uC000\u{1D4A5}rcy;\u4408kcy;\u4404\u0380HJacfos\u07E4\u07E8\u07EC\u07F1\u07FD\u0802\u0808cy;\u4425cy;\u440Cppa;\u439A\u0100ey\u07F6\u07FBdil;\u4136;\u441Ar;\uC000\u{1D50E}pf;\uC000\u{1D542}cr;\uC000\u{1D4A6}\u0580JTaceflmost\u0825\u0829\u082C\u0850\u0863\u09B3\u09B8\u09C7\u09CD\u0A37\u0A47cy;\u4409\u803B<\u403C\u0280cmnpr\u0837\u083C\u0841\u0844\u084Dute;\u4139bda;\u439Bg;\u67EAlacetrf;\u6112r;\u619E\u0180aey\u0857\u085C\u0861ron;\u413Ddil;\u413B;\u441B\u0100fs\u0868\u0970t\u0500ACDFRTUVar\u087E\u08A9\u08B1\u08E0\u08E6\u08FC\u092F\u095B\u0390\u096A\u0100nr\u0883\u088FgleBracket;\u67E8row\u0180;BR\u0899\u089A\u089E\u6190ar;\u61E4ightArrow;\u61C6eiling;\u6308o\u01F5\u08B7\0\u08C3bleBracket;\u67E6n\u01D4\u08C8\0\u08D2eeVector;\u6961ector\u0100;B\u08DB\u08DC\u61C3ar;\u6959loor;\u630Aight\u0100AV\u08EF\u08F5rrow;\u6194ector;\u694E\u0100er\u0901\u0917e\u0180;AV\u0909\u090A\u0910\u62A3rrow;\u61A4ector;\u695Aiangle\u0180;BE\u0924\u0925\u0929\u62B2ar;\u69CFqual;\u62B4p\u0180DTV\u0937\u0942\u094CownVector;\u6951eeVector;\u6960ector\u0100;B\u0956\u0957\u61BFar;\u6958ector\u0100;B\u0965\u0966\u61BCar;\u6952ight\xE1\u039Cs\u0300EFGLST\u097E\u098B\u0995\u099D\u09A2\u09ADqualGreater;\u62DAullEqual;\u6266reater;\u6276ess;\u6AA1lantEqual;\u6A7Dilde;\u6272r;\uC000\u{1D50F}\u0100;e\u09BD\u09BE\u62D8ftarrow;\u61DAidot;\u413F\u0180npw\u09D4\u0A16\u0A1Bg\u0200LRlr\u09DE\u09F7\u0A02\u0A10eft\u0100AR\u09E6\u09ECrrow;\u67F5ightArrow;\u67F7ightArrow;\u67F6eft\u0100ar\u03B3\u0A0Aight\xE1\u03BFight\xE1\u03CAf;\uC000\u{1D543}er\u0100LR\u0A22\u0A2CeftArrow;\u6199ightArrow;\u6198\u0180cht\u0A3E\u0A40\u0A42\xF2\u084C;\u61B0rok;\u4141;\u626A\u0400acefiosu\u0A5A\u0A5D\u0A60\u0A77\u0A7C\u0A85\u0A8B\u0A8Ep;\u6905y;\u441C\u0100dl\u0A65\u0A6FiumSpace;\u605Flintrf;\u6133r;\uC000\u{1D510}nusPlus;\u6213pf;\uC000\u{1D544}c\xF2\u0A76;\u439C\u0480Jacefostu\u0AA3\u0AA7\u0AAD\u0AC0\u0B14\u0B19\u0D91\u0D97\u0D9Ecy;\u440Acute;\u4143\u0180aey\u0AB4\u0AB9\u0ABEron;\u4147dil;\u4145;\u441D\u0180gsw\u0AC7\u0AF0\u0B0Eative\u0180MTV\u0AD3\u0ADF\u0AE8ediumSpace;\u600Bhi\u0100cn\u0AE6\u0AD8\xEB\u0AD9eryThi\xEE\u0AD9ted\u0100GL\u0AF8\u0B06reaterGreate\xF2\u0673essLes\xF3\u0A48Line;\u400Ar;\uC000\u{1D511}\u0200Bnpt\u0B22\u0B28\u0B37\u0B3Areak;\u6060BreakingSpace;\u40A0f;\u6115\u0680;CDEGHLNPRSTV\u0B55\u0B56\u0B6A\u0B7C\u0BA1\u0BEB\u0C04\u0C5E\u0C84\u0CA6\u0CD8\u0D61\u0D85\u6AEC\u0100ou\u0B5B\u0B64ngruent;\u6262pCap;\u626DoubleVerticalBar;\u6226\u0180lqx\u0B83\u0B8A\u0B9Bement;\u6209ual\u0100;T\u0B92\u0B93\u6260ilde;\uC000\u2242\u0338ists;\u6204reater\u0380;EFGLST\u0BB6\u0BB7\u0BBD\u0BC9\u0BD3\u0BD8\u0BE5\u626Fqual;\u6271ullEqual;\uC000\u2267\u0338reater;\uC000\u226B\u0338ess;\u6279lantEqual;\uC000\u2A7E\u0338ilde;\u6275ump\u0144\u0BF2\u0BFDownHump;\uC000\u224E\u0338qual;\uC000\u224F\u0338e\u0100fs\u0C0A\u0C27tTriangle\u0180;BE\u0C1A\u0C1B\u0C21\u62EAar;\uC000\u29CF\u0338qual;\u62ECs\u0300;EGLST\u0C35\u0C36\u0C3C\u0C44\u0C4B\u0C58\u626Equal;\u6270reater;\u6278ess;\uC000\u226A\u0338lantEqual;\uC000\u2A7D\u0338ilde;\u6274ested\u0100GL\u0C68\u0C79reaterGreater;\uC000\u2AA2\u0338essLess;\uC000\u2AA1\u0338recedes\u0180;ES\u0C92\u0C93\u0C9B\u6280qual;\uC000\u2AAF\u0338lantEqual;\u62E0\u0100ei\u0CAB\u0CB9verseElement;\u620CghtTriangle\u0180;BE\u0CCB\u0CCC\u0CD2\u62EBar;\uC000\u29D0\u0338qual;\u62ED\u0100qu\u0CDD\u0D0CuareSu\u0100bp\u0CE8\u0CF9set\u0100;E\u0CF0\u0CF3\uC000\u228F\u0338qual;\u62E2erset\u0100;E\u0D03\u0D06\uC000\u2290\u0338qual;\u62E3\u0180bcp\u0D13\u0D24\u0D4Eset\u0100;E\u0D1B\u0D1E\uC000\u2282\u20D2qual;\u6288ceeds\u0200;EST\u0D32\u0D33\u0D3B\u0D46\u6281qual;\uC000\u2AB0\u0338lantEqual;\u62E1ilde;\uC000\u227F\u0338erset\u0100;E\u0D58\u0D5B\uC000\u2283\u20D2qual;\u6289ilde\u0200;EFT\u0D6E\u0D6F\u0D75\u0D7F\u6241qual;\u6244ullEqual;\u6247ilde;\u6249erticalBar;\u6224cr;\uC000\u{1D4A9}ilde\u803B\xD1\u40D1;\u439D\u0700Eacdfgmoprstuv\u0DBD\u0DC2\u0DC9\u0DD5\u0DDB\u0DE0\u0DE7\u0DFC\u0E02\u0E20\u0E22\u0E32\u0E3F\u0E44lig;\u4152cute\u803B\xD3\u40D3\u0100iy\u0DCE\u0DD3rc\u803B\xD4\u40D4;\u441Eblac;\u4150r;\uC000\u{1D512}rave\u803B\xD2\u40D2\u0180aei\u0DEE\u0DF2\u0DF6cr;\u414Cga;\u43A9cron;\u439Fpf;\uC000\u{1D546}enCurly\u0100DQ\u0E0E\u0E1AoubleQuote;\u601Cuote;\u6018;\u6A54\u0100cl\u0E27\u0E2Cr;\uC000\u{1D4AA}ash\u803B\xD8\u40D8i\u016C\u0E37\u0E3Cde\u803B\xD5\u40D5es;\u6A37ml\u803B\xD6\u40D6er\u0100BP\u0E4B\u0E60\u0100ar\u0E50\u0E53r;\u603Eac\u0100ek\u0E5A\u0E5C;\u63DEet;\u63B4arenthesis;\u63DC\u0480acfhilors\u0E7F\u0E87\u0E8A\u0E8F\u0E92\u0E94\u0E9D\u0EB0\u0EFCrtialD;\u6202y;\u441Fr;\uC000\u{1D513}i;\u43A6;\u43A0usMinus;\u40B1\u0100ip\u0EA2\u0EADncareplan\xE5\u069Df;\u6119\u0200;eio\u0EB9\u0EBA\u0EE0\u0EE4\u6ABBcedes\u0200;EST\u0EC8\u0EC9\u0ECF\u0EDA\u627Aqual;\u6AAFlantEqual;\u627Cilde;\u627Eme;\u6033\u0100dp\u0EE9\u0EEEuct;\u620Fortion\u0100;a\u0225\u0EF9l;\u621D\u0100ci\u0F01\u0F06r;\uC000\u{1D4AB};\u43A8\u0200Ufos\u0F11\u0F16\u0F1B\u0F1FOT\u803B"\u4022r;\uC000\u{1D514}pf;\u611Acr;\uC000\u{1D4AC}\u0600BEacefhiorsu\u0F3E\u0F43\u0F47\u0F60\u0F73\u0FA7\u0FAA\u0FAD\u1096\u10A9\u10B4\u10BEarr;\u6910G\u803B\xAE\u40AE\u0180cnr\u0F4E\u0F53\u0F56ute;\u4154g;\u67EBr\u0100;t\u0F5C\u0F5D\u61A0l;\u6916\u0180aey\u0F67\u0F6C\u0F71ron;\u4158dil;\u4156;\u4420\u0100;v\u0F78\u0F79\u611Cerse\u0100EU\u0F82\u0F99\u0100lq\u0F87\u0F8Eement;\u620Builibrium;\u61CBpEquilibrium;\u696Fr\xBB\u0F79o;\u43A1ght\u0400ACDFTUVa\u0FC1\u0FEB\u0FF3\u1022\u1028\u105B\u1087\u03D8\u0100nr\u0FC6\u0FD2gleBracket;\u67E9row\u0180;BL\u0FDC\u0FDD\u0FE1\u6192ar;\u61E5eftArrow;\u61C4eiling;\u6309o\u01F5\u0FF9\0\u1005bleBracket;\u67E7n\u01D4\u100A\0\u1014eeVector;\u695Dector\u0100;B\u101D\u101E\u61C2ar;\u6955loor;\u630B\u0100er\u102D\u1043e\u0180;AV\u1035\u1036\u103C\u62A2rrow;\u61A6ector;\u695Biangle\u0180;BE\u1050\u1051\u1055\u62B3ar;\u69D0qual;\u62B5p\u0180DTV\u1063\u106E\u1078ownVector;\u694FeeVector;\u695Cector\u0100;B\u1082\u1083\u61BEar;\u6954ector\u0100;B\u1091\u1092\u61C0ar;\u6953\u0100pu\u109B\u109Ef;\u611DndImplies;\u6970ightarrow;\u61DB\u0100ch\u10B9\u10BCr;\u611B;\u61B1leDelayed;\u69F4\u0680HOacfhimoqstu\u10E4\u10F1\u10F7\u10FD\u1119\u111E\u1151\u1156\u1161\u1167\u11B5\u11BB\u11BF\u0100Cc\u10E9\u10EEHcy;\u4429y;\u4428FTcy;\u442Ccute;\u415A\u0280;aeiy\u1108\u1109\u110E\u1113\u1117\u6ABCron;\u4160dil;\u415Erc;\u415C;\u4421r;\uC000\u{1D516}ort\u0200DLRU\u112A\u1134\u113E\u1149ownArrow\xBB\u041EeftArrow\xBB\u089AightArrow\xBB\u0FDDpArrow;\u6191gma;\u43A3allCircle;\u6218pf;\uC000\u{1D54A}\u0272\u116D\0\0\u1170t;\u621Aare\u0200;ISU\u117B\u117C\u1189\u11AF\u65A1ntersection;\u6293u\u0100bp\u118F\u119Eset\u0100;E\u1197\u1198\u628Fqual;\u6291erset\u0100;E\u11A8\u11A9\u6290qual;\u6292nion;\u6294cr;\uC000\u{1D4AE}ar;\u62C6\u0200bcmp\u11C8\u11DB\u1209\u120B\u0100;s\u11CD\u11CE\u62D0et\u0100;E\u11CD\u11D5qual;\u6286\u0100ch\u11E0\u1205eeds\u0200;EST\u11ED\u11EE\u11F4\u11FF\u627Bqual;\u6AB0lantEqual;\u627Dilde;\u627FTh\xE1\u0F8C;\u6211\u0180;es\u1212\u1213\u1223\u62D1rset\u0100;E\u121C\u121D\u6283qual;\u6287et\xBB\u1213\u0580HRSacfhiors\u123E\u1244\u1249\u1255\u125E\u1271\u1276\u129F\u12C2\u12C8\u12D1ORN\u803B\xDE\u40DEADE;\u6122\u0100Hc\u124E\u1252cy;\u440By;\u4426\u0100bu\u125A\u125C;\u4009;\u43A4\u0180aey\u1265\u126A\u126Fron;\u4164dil;\u4162;\u4422r;\uC000\u{1D517}\u0100ei\u127B\u1289\u01F2\u1280\0\u1287efore;\u6234a;\u4398\u0100cn\u128E\u1298kSpace;\uC000\u205F\u200ASpace;\u6009lde\u0200;EFT\u12AB\u12AC\u12B2\u12BC\u623Cqual;\u6243ullEqual;\u6245ilde;\u6248pf;\uC000\u{1D54B}ipleDot;\u60DB\u0100ct\u12D6\u12DBr;\uC000\u{1D4AF}rok;\u4166\u0AE1\u12F7\u130E\u131A\u1326\0\u132C\u1331\0\0\0\0\0\u1338\u133D\u1377\u1385\0\u13FF\u1404\u140A\u1410\u0100cr\u12FB\u1301ute\u803B\xDA\u40DAr\u0100;o\u1307\u1308\u619Fcir;\u6949r\u01E3\u1313\0\u1316y;\u440Eve;\u416C\u0100iy\u131E\u1323rc\u803B\xDB\u40DB;\u4423blac;\u4170r;\uC000\u{1D518}rave\u803B\xD9\u40D9acr;\u416A\u0100di\u1341\u1369er\u0100BP\u1348\u135D\u0100ar\u134D\u1350r;\u405Fac\u0100ek\u1357\u1359;\u63DFet;\u63B5arenthesis;\u63DDon\u0100;P\u1370\u1371\u62C3lus;\u628E\u0100gp\u137B\u137Fon;\u4172f;\uC000\u{1D54C}\u0400ADETadps\u1395\u13AE\u13B8\u13C4\u03E8\u13D2\u13D7\u13F3rrow\u0180;BD\u1150\u13A0\u13A4ar;\u6912ownArrow;\u61C5ownArrow;\u6195quilibrium;\u696Eee\u0100;A\u13CB\u13CC\u62A5rrow;\u61A5own\xE1\u03F3er\u0100LR\u13DE\u13E8eftArrow;\u6196ightArrow;\u6197i\u0100;l\u13F9\u13FA\u43D2on;\u43A5ing;\u416Ecr;\uC000\u{1D4B0}ilde;\u4168ml\u803B\xDC\u40DC\u0480Dbcdefosv\u1427\u142C\u1430\u1433\u143E\u1485\u148A\u1490\u1496ash;\u62ABar;\u6AEBy;\u4412ash\u0100;l\u143B\u143C\u62A9;\u6AE6\u0100er\u1443\u1445;\u62C1\u0180bty\u144C\u1450\u147Aar;\u6016\u0100;i\u144F\u1455cal\u0200BLST\u1461\u1465\u146A\u1474ar;\u6223ine;\u407Ceparator;\u6758ilde;\u6240ThinSpace;\u600Ar;\uC000\u{1D519}pf;\uC000\u{1D54D}cr;\uC000\u{1D4B1}dash;\u62AA\u0280cefos\u14A7\u14AC\u14B1\u14B6\u14BCirc;\u4174dge;\u62C0r;\uC000\u{1D51A}pf;\uC000\u{1D54E}cr;\uC000\u{1D4B2}\u0200fios\u14CB\u14D0\u14D2\u14D8r;\uC000\u{1D51B};\u439Epf;\uC000\u{1D54F}cr;\uC000\u{1D4B3}\u0480AIUacfosu\u14F1\u14F5\u14F9\u14FD\u1504\u150F\u1514\u151A\u1520cy;\u442Fcy;\u4407cy;\u442Ecute\u803B\xDD\u40DD\u0100iy\u1509\u150Drc;\u4176;\u442Br;\uC000\u{1D51C}pf;\uC000\u{1D550}cr;\uC000\u{1D4B4}ml;\u4178\u0400Hacdefos\u1535\u1539\u153F\u154B\u154F\u155D\u1560\u1564cy;\u4416cute;\u4179\u0100ay\u1544\u1549ron;\u417D;\u4417ot;\u417B\u01F2\u1554\0\u155BoWidt\xE8\u0AD9a;\u4396r;\u6128pf;\u6124cr;\uC000\u{1D4B5}\u0BE1\u1583\u158A\u1590\0\u15B0\u15B6\u15BF\0\0\0\0\u15C6\u15DB\u15EB\u165F\u166D\0\u1695\u169B\u16B2\u16B9\0\u16BEcute\u803B\xE1\u40E1reve;\u4103\u0300;Ediuy\u159C\u159D\u15A1\u15A3\u15A8\u15AD\u623E;\uC000\u223E\u0333;\u623Frc\u803B\xE2\u40E2te\u80BB\xB4\u0306;\u4430lig\u803B\xE6\u40E6\u0100;r\xB2\u15BA;\uC000\u{1D51E}rave\u803B\xE0\u40E0\u0100ep\u15CA\u15D6\u0100fp\u15CF\u15D4sym;\u6135\xE8\u15D3ha;\u43B1\u0100ap\u15DFc\u0100cl\u15E4\u15E7r;\u4101g;\u6A3F\u0264\u15F0\0\0\u160A\u0280;adsv\u15FA\u15FB\u15FF\u1601\u1607\u6227nd;\u6A55;\u6A5Clope;\u6A58;\u6A5A\u0380;elmrsz\u1618\u1619\u161B\u161E\u163F\u164F\u1659\u6220;\u69A4e\xBB\u1619sd\u0100;a\u1625\u1626\u6221\u0461\u1630\u1632\u1634\u1636\u1638\u163A\u163C\u163E;\u69A8;\u69A9;\u69AA;\u69AB;\u69AC;\u69AD;\u69AE;\u69AFt\u0100;v\u1645\u1646\u621Fb\u0100;d\u164C\u164D\u62BE;\u699D\u0100pt\u1654\u1657h;\u6222\xBB\xB9arr;\u637C\u0100gp\u1663\u1667on;\u4105f;\uC000\u{1D552}\u0380;Eaeiop\u12C1\u167B\u167D\u1682\u1684\u1687\u168A;\u6A70cir;\u6A6F;\u624Ad;\u624Bs;\u4027rox\u0100;e\u12C1\u1692\xF1\u1683ing\u803B\xE5\u40E5\u0180cty\u16A1\u16A6\u16A8r;\uC000\u{1D4B6};\u402Amp\u0100;e\u12C1\u16AF\xF1\u0288ilde\u803B\xE3\u40E3ml\u803B\xE4\u40E4\u0100ci\u16C2\u16C8onin\xF4\u0272nt;\u6A11\u0800Nabcdefiklnoprsu\u16ED\u16F1\u1730\u173C\u1743\u1748\u1778\u177D\u17E0\u17E6\u1839\u1850\u170D\u193D\u1948\u1970ot;\u6AED\u0100cr\u16F6\u171Ek\u0200ceps\u1700\u1705\u170D\u1713ong;\u624Cpsilon;\u43F6rime;\u6035im\u0100;e\u171A\u171B\u623Dq;\u62CD\u0176\u1722\u1726ee;\u62BDed\u0100;g\u172C\u172D\u6305e\xBB\u172Drk\u0100;t\u135C\u1737brk;\u63B6\u0100oy\u1701\u1741;\u4431quo;\u601E\u0280cmprt\u1753\u175B\u1761\u1764\u1768aus\u0100;e\u010A\u0109ptyv;\u69B0s\xE9\u170Cno\xF5\u0113\u0180ahw\u176F\u1771\u1773;\u43B2;\u6136een;\u626Cr;\uC000\u{1D51F}g\u0380costuvw\u178D\u179D\u17B3\u17C1\u17D5\u17DB\u17DE\u0180aiu\u1794\u1796\u179A\xF0\u0760rc;\u65EFp\xBB\u1371\u0180dpt\u17A4\u17A8\u17ADot;\u6A00lus;\u6A01imes;\u6A02\u0271\u17B9\0\0\u17BEcup;\u6A06ar;\u6605riangle\u0100du\u17CD\u17D2own;\u65BDp;\u65B3plus;\u6A04e\xE5\u1444\xE5\u14ADarow;\u690D\u0180ako\u17ED\u1826\u1835\u0100cn\u17F2\u1823k\u0180lst\u17FA\u05AB\u1802ozenge;\u69EBriangle\u0200;dlr\u1812\u1813\u1818\u181D\u65B4own;\u65BEeft;\u65C2ight;\u65B8k;\u6423\u01B1\u182B\0\u1833\u01B2\u182F\0\u1831;\u6592;\u65914;\u6593ck;\u6588\u0100eo\u183E\u184D\u0100;q\u1843\u1846\uC000=\u20E5uiv;\uC000\u2261\u20E5t;\u6310\u0200ptwx\u1859\u185E\u1867\u186Cf;\uC000\u{1D553}\u0100;t\u13CB\u1863om\xBB\u13CCtie;\u62C8\u0600DHUVbdhmptuv\u1885\u1896\u18AA\u18BB\u18D7\u18DB\u18EC\u18FF\u1905\u190A\u1910\u1921\u0200LRlr\u188E\u1890\u1892\u1894;\u6557;\u6554;\u6556;\u6553\u0280;DUdu\u18A1\u18A2\u18A4\u18A6\u18A8\u6550;\u6566;\u6569;\u6564;\u6567\u0200LRlr\u18B3\u18B5\u18B7\u18B9;\u655D;\u655A;\u655C;\u6559\u0380;HLRhlr\u18CA\u18CB\u18CD\u18CF\u18D1\u18D3\u18D5\u6551;\u656C;\u6563;\u6560;\u656B;\u6562;\u655Fox;\u69C9\u0200LRlr\u18E4\u18E6\u18E8\u18EA;\u6555;\u6552;\u6510;\u650C\u0280;DUdu\u06BD\u18F7\u18F9\u18FB\u18FD;\u6565;\u6568;\u652C;\u6534inus;\u629Flus;\u629Eimes;\u62A0\u0200LRlr\u1919\u191B\u191D\u191F;\u655B;\u6558;\u6518;\u6514\u0380;HLRhlr\u1930\u1931\u1933\u1935\u1937\u1939\u193B\u6502;\u656A;\u6561;\u655E;\u653C;\u6524;\u651C\u0100ev\u0123\u1942bar\u803B\xA6\u40A6\u0200ceio\u1951\u1956\u195A\u1960r;\uC000\u{1D4B7}mi;\u604Fm\u0100;e\u171A\u171Cl\u0180;bh\u1968\u1969\u196B\u405C;\u69C5sub;\u67C8\u016C\u1974\u197El\u0100;e\u1979\u197A\u6022t\xBB\u197Ap\u0180;Ee\u012F\u1985\u1987;\u6AAE\u0100;q\u06DC\u06DB\u0CE1\u19A7\0\u19E8\u1A11\u1A15\u1A32\0\u1A37\u1A50\0\0\u1AB4\0\0\u1AC1\0\0\u1B21\u1B2E\u1B4D\u1B52\0\u1BFD\0\u1C0C\u0180cpr\u19AD\u19B2\u19DDute;\u4107\u0300;abcds\u19BF\u19C0\u19C4\u19CA\u19D5\u19D9\u6229nd;\u6A44rcup;\u6A49\u0100au\u19CF\u19D2p;\u6A4Bp;\u6A47ot;\u6A40;\uC000\u2229\uFE00\u0100eo\u19E2\u19E5t;\u6041\xEE\u0693\u0200aeiu\u19F0\u19FB\u1A01\u1A05\u01F0\u19F5\0\u19F8s;\u6A4Don;\u410Ddil\u803B\xE7\u40E7rc;\u4109ps\u0100;s\u1A0C\u1A0D\u6A4Cm;\u6A50ot;\u410B\u0180dmn\u1A1B\u1A20\u1A26il\u80BB\xB8\u01ADptyv;\u69B2t\u8100\xA2;e\u1A2D\u1A2E\u40A2r\xE4\u01B2r;\uC000\u{1D520}\u0180cei\u1A3D\u1A40\u1A4Dy;\u4447ck\u0100;m\u1A47\u1A48\u6713ark\xBB\u1A48;\u43C7r\u0380;Ecefms\u1A5F\u1A60\u1A62\u1A6B\u1AA4\u1AAA\u1AAE\u65CB;\u69C3\u0180;el\u1A69\u1A6A\u1A6D\u42C6q;\u6257e\u0261\u1A74\0\0\u1A88rrow\u0100lr\u1A7C\u1A81eft;\u61BAight;\u61BB\u0280RSacd\u1A92\u1A94\u1A96\u1A9A\u1A9F\xBB\u0F47;\u64C8st;\u629Birc;\u629Aash;\u629Dnint;\u6A10id;\u6AEFcir;\u69C2ubs\u0100;u\u1ABB\u1ABC\u6663it\xBB\u1ABC\u02EC\u1AC7\u1AD4\u1AFA\0\u1B0Aon\u0100;e\u1ACD\u1ACE\u403A\u0100;q\xC7\xC6\u026D\u1AD9\0\0\u1AE2a\u0100;t\u1ADE\u1ADF\u402C;\u4040\u0180;fl\u1AE8\u1AE9\u1AEB\u6201\xEE\u1160e\u0100mx\u1AF1\u1AF6ent\xBB\u1AE9e\xF3\u024D\u01E7\u1AFE\0\u1B07\u0100;d\u12BB\u1B02ot;\u6A6Dn\xF4\u0246\u0180fry\u1B10\u1B14\u1B17;\uC000\u{1D554}o\xE4\u0254\u8100\xA9;s\u0155\u1B1Dr;\u6117\u0100ao\u1B25\u1B29rr;\u61B5ss;\u6717\u0100cu\u1B32\u1B37r;\uC000\u{1D4B8}\u0100bp\u1B3C\u1B44\u0100;e\u1B41\u1B42\u6ACF;\u6AD1\u0100;e\u1B49\u1B4A\u6AD0;\u6AD2dot;\u62EF\u0380delprvw\u1B60\u1B6C\u1B77\u1B82\u1BAC\u1BD4\u1BF9arr\u0100lr\u1B68\u1B6A;\u6938;\u6935\u0270\u1B72\0\0\u1B75r;\u62DEc;\u62DFarr\u0100;p\u1B7F\u1B80\u61B6;\u693D\u0300;bcdos\u1B8F\u1B90\u1B96\u1BA1\u1BA5\u1BA8\u622Arcap;\u6A48\u0100au\u1B9B\u1B9Ep;\u6A46p;\u6A4Aot;\u628Dr;\u6A45;\uC000\u222A\uFE00\u0200alrv\u1BB5\u1BBF\u1BDE\u1BE3rr\u0100;m\u1BBC\u1BBD\u61B7;\u693Cy\u0180evw\u1BC7\u1BD4\u1BD8q\u0270\u1BCE\0\0\u1BD2re\xE3\u1B73u\xE3\u1B75ee;\u62CEedge;\u62CFen\u803B\xA4\u40A4earrow\u0100lr\u1BEE\u1BF3eft\xBB\u1B80ight\xBB\u1BBDe\xE4\u1BDD\u0100ci\u1C01\u1C07onin\xF4\u01F7nt;\u6231lcty;\u632D\u0980AHabcdefhijlorstuwz\u1C38\u1C3B\u1C3F\u1C5D\u1C69\u1C75\u1C8A\u1C9E\u1CAC\u1CB7\u1CFB\u1CFF\u1D0D\u1D7B\u1D91\u1DAB\u1DBB\u1DC6\u1DCDr\xF2\u0381ar;\u6965\u0200glrs\u1C48\u1C4D\u1C52\u1C54ger;\u6020eth;\u6138\xF2\u1133h\u0100;v\u1C5A\u1C5B\u6010\xBB\u090A\u016B\u1C61\u1C67arow;\u690Fa\xE3\u0315\u0100ay\u1C6E\u1C73ron;\u410F;\u4434\u0180;ao\u0332\u1C7C\u1C84\u0100gr\u02BF\u1C81r;\u61CAtseq;\u6A77\u0180glm\u1C91\u1C94\u1C98\u803B\xB0\u40B0ta;\u43B4ptyv;\u69B1\u0100ir\u1CA3\u1CA8sht;\u697F;\uC000\u{1D521}ar\u0100lr\u1CB3\u1CB5\xBB\u08DC\xBB\u101E\u0280aegsv\u1CC2\u0378\u1CD6\u1CDC\u1CE0m\u0180;os\u0326\u1CCA\u1CD4nd\u0100;s\u0326\u1CD1uit;\u6666amma;\u43DDin;\u62F2\u0180;io\u1CE7\u1CE8\u1CF8\u40F7de\u8100\xF7;o\u1CE7\u1CF0ntimes;\u62C7n\xF8\u1CF7cy;\u4452c\u026F\u1D06\0\0\u1D0Arn;\u631Eop;\u630D\u0280lptuw\u1D18\u1D1D\u1D22\u1D49\u1D55lar;\u4024f;\uC000\u{1D555}\u0280;emps\u030B\u1D2D\u1D37\u1D3D\u1D42q\u0100;d\u0352\u1D33ot;\u6251inus;\u6238lus;\u6214quare;\u62A1blebarwedg\xE5\xFAn\u0180adh\u112E\u1D5D\u1D67ownarrow\xF3\u1C83arpoon\u0100lr\u1D72\u1D76ef\xF4\u1CB4igh\xF4\u1CB6\u0162\u1D7F\u1D85karo\xF7\u0F42\u026F\u1D8A\0\0\u1D8Ern;\u631Fop;\u630C\u0180cot\u1D98\u1DA3\u1DA6\u0100ry\u1D9D\u1DA1;\uC000\u{1D4B9};\u4455l;\u69F6rok;\u4111\u0100dr\u1DB0\u1DB4ot;\u62F1i\u0100;f\u1DBA\u1816\u65BF\u0100ah\u1DC0\u1DC3r\xF2\u0429a\xF2\u0FA6angle;\u69A6\u0100ci\u1DD2\u1DD5y;\u445Fgrarr;\u67FF\u0900Dacdefglmnopqrstux\u1E01\u1E09\u1E19\u1E38\u0578\u1E3C\u1E49\u1E61\u1E7E\u1EA5\u1EAF\u1EBD\u1EE1\u1F2A\u1F37\u1F44\u1F4E\u1F5A\u0100Do\u1E06\u1D34o\xF4\u1C89\u0100cs\u1E0E\u1E14ute\u803B\xE9\u40E9ter;\u6A6E\u0200aioy\u1E22\u1E27\u1E31\u1E36ron;\u411Br\u0100;c\u1E2D\u1E2E\u6256\u803B\xEA\u40EAlon;\u6255;\u444Dot;\u4117\u0100Dr\u1E41\u1E45ot;\u6252;\uC000\u{1D522}\u0180;rs\u1E50\u1E51\u1E57\u6A9Aave\u803B\xE8\u40E8\u0100;d\u1E5C\u1E5D\u6A96ot;\u6A98\u0200;ils\u1E6A\u1E6B\u1E72\u1E74\u6A99nters;\u63E7;\u6113\u0100;d\u1E79\u1E7A\u6A95ot;\u6A97\u0180aps\u1E85\u1E89\u1E97cr;\u4113ty\u0180;sv\u1E92\u1E93\u1E95\u6205et\xBB\u1E93p\u01001;\u1E9D\u1EA4\u0133\u1EA1\u1EA3;\u6004;\u6005\u6003\u0100gs\u1EAA\u1EAC;\u414Bp;\u6002\u0100gp\u1EB4\u1EB8on;\u4119f;\uC000\u{1D556}\u0180als\u1EC4\u1ECE\u1ED2r\u0100;s\u1ECA\u1ECB\u62D5l;\u69E3us;\u6A71i\u0180;lv\u1EDA\u1EDB\u1EDF\u43B5on\xBB\u1EDB;\u43F5\u0200csuv\u1EEA\u1EF3\u1F0B\u1F23\u0100io\u1EEF\u1E31rc\xBB\u1E2E\u0269\u1EF9\0\0\u1EFB\xED\u0548ant\u0100gl\u1F02\u1F06tr\xBB\u1E5Dess\xBB\u1E7A\u0180aei\u1F12\u1F16\u1F1Als;\u403Dst;\u625Fv\u0100;D\u0235\u1F20D;\u6A78parsl;\u69E5\u0100Da\u1F2F\u1F33ot;\u6253rr;\u6971\u0180cdi\u1F3E\u1F41\u1EF8r;\u612Fo\xF4\u0352\u0100ah\u1F49\u1F4B;\u43B7\u803B\xF0\u40F0\u0100mr\u1F53\u1F57l\u803B\xEB\u40EBo;\u60AC\u0180cip\u1F61\u1F64\u1F67l;\u4021s\xF4\u056E\u0100eo\u1F6C\u1F74ctatio\xEE\u0559nential\xE5\u0579\u09E1\u1F92\0\u1F9E\0\u1FA1\u1FA7\0\0\u1FC6\u1FCC\0\u1FD3\0\u1FE6\u1FEA\u2000\0\u2008\u205Allingdotse\xF1\u1E44y;\u4444male;\u6640\u0180ilr\u1FAD\u1FB3\u1FC1lig;\u8000\uFB03\u0269\u1FB9\0\0\u1FBDg;\u8000\uFB00ig;\u8000\uFB04;\uC000\u{1D523}lig;\u8000\uFB01lig;\uC000fj\u0180alt\u1FD9\u1FDC\u1FE1t;\u666Dig;\u8000\uFB02ns;\u65B1of;\u4192\u01F0\u1FEE\0\u1FF3f;\uC000\u{1D557}\u0100ak\u05BF\u1FF7\u0100;v\u1FFC\u1FFD\u62D4;\u6AD9artint;\u6A0D\u0100ao\u200C\u2055\u0100cs\u2011\u2052\u03B1\u201A\u2030\u2038\u2045\u2048\0\u2050\u03B2\u2022\u2025\u2027\u202A\u202C\0\u202E\u803B\xBD\u40BD;\u6153\u803B\xBC\u40BC;\u6155;\u6159;\u615B\u01B3\u2034\0\u2036;\u6154;\u6156\u02B4\u203E\u2041\0\0\u2043\u803B\xBE\u40BE;\u6157;\u615C5;\u6158\u01B6\u204C\0\u204E;\u615A;\u615D8;\u615El;\u6044wn;\u6322cr;\uC000\u{1D4BB}\u0880Eabcdefgijlnorstv\u2082\u2089\u209F\u20A5\u20B0\u20B4\u20F0\u20F5\u20FA\u20FF\u2103\u2112\u2138\u0317\u213E\u2152\u219E\u0100;l\u064D\u2087;\u6A8C\u0180cmp\u2090\u2095\u209Dute;\u41F5ma\u0100;d\u209C\u1CDA\u43B3;\u6A86reve;\u411F\u0100iy\u20AA\u20AErc;\u411D;\u4433ot;\u4121\u0200;lqs\u063E\u0642\u20BD\u20C9\u0180;qs\u063E\u064C\u20C4lan\xF4\u0665\u0200;cdl\u0665\u20D2\u20D5\u20E5c;\u6AA9ot\u0100;o\u20DC\u20DD\u6A80\u0100;l\u20E2\u20E3\u6A82;\u6A84\u0100;e\u20EA\u20ED\uC000\u22DB\uFE00s;\u6A94r;\uC000\u{1D524}\u0100;g\u0673\u061Bmel;\u6137cy;\u4453\u0200;Eaj\u065A\u210C\u210E\u2110;\u6A92;\u6AA5;\u6AA4\u0200Eaes\u211B\u211D\u2129\u2134;\u6269p\u0100;p\u2123\u2124\u6A8Arox\xBB\u2124\u0100;q\u212E\u212F\u6A88\u0100;q\u212E\u211Bim;\u62E7pf;\uC000\u{1D558}\u0100ci\u2143\u2146r;\u610Am\u0180;el\u066B\u214E\u2150;\u6A8E;\u6A90\u8300>;cdlqr\u05EE\u2160\u216A\u216E\u2173\u2179\u0100ci\u2165\u2167;\u6AA7r;\u6A7Aot;\u62D7Par;\u6995uest;\u6A7C\u0280adels\u2184\u216A\u2190\u0656\u219B\u01F0\u2189\0\u218Epro\xF8\u209Er;\u6978q\u0100lq\u063F\u2196les\xF3\u2088i\xED\u066B\u0100en\u21A3\u21ADrtneqq;\uC000\u2269\uFE00\xC5\u21AA\u0500Aabcefkosy\u21C4\u21C7\u21F1\u21F5\u21FA\u2218\u221D\u222F\u2268\u227Dr\xF2\u03A0\u0200ilmr\u21D0\u21D4\u21D7\u21DBrs\xF0\u1484f\xBB\u2024il\xF4\u06A9\u0100dr\u21E0\u21E4cy;\u444A\u0180;cw\u08F4\u21EB\u21EFir;\u6948;\u61ADar;\u610Firc;\u4125\u0180alr\u2201\u220E\u2213rts\u0100;u\u2209\u220A\u6665it\xBB\u220Alip;\u6026con;\u62B9r;\uC000\u{1D525}s\u0100ew\u2223\u2229arow;\u6925arow;\u6926\u0280amopr\u223A\u223E\u2243\u225E\u2263rr;\u61FFtht;\u623Bk\u0100lr\u2249\u2253eftarrow;\u61A9ightarrow;\u61AAf;\uC000\u{1D559}bar;\u6015\u0180clt\u226F\u2274\u2278r;\uC000\u{1D4BD}as\xE8\u21F4rok;\u4127\u0100bp\u2282\u2287ull;\u6043hen\xBB\u1C5B\u0AE1\u22A3\0\u22AA\0\u22B8\u22C5\u22CE\0\u22D5\u22F3\0\0\u22F8\u2322\u2367\u2362\u237F\0\u2386\u23AA\u23B4cute\u803B\xED\u40ED\u0180;iy\u0771\u22B0\u22B5rc\u803B\xEE\u40EE;\u4438\u0100cx\u22BC\u22BFy;\u4435cl\u803B\xA1\u40A1\u0100fr\u039F\u22C9;\uC000\u{1D526}rave\u803B\xEC\u40EC\u0200;ino\u073E\u22DD\u22E9\u22EE\u0100in\u22E2\u22E6nt;\u6A0Ct;\u622Dfin;\u69DCta;\u6129lig;\u4133\u0180aop\u22FE\u231A\u231D\u0180cgt\u2305\u2308\u2317r;\u412B\u0180elp\u071F\u230F\u2313in\xE5\u078Ear\xF4\u0720h;\u4131f;\u62B7ed;\u41B5\u0280;cfot\u04F4\u232C\u2331\u233D\u2341are;\u6105in\u0100;t\u2338\u2339\u621Eie;\u69DDdo\xF4\u2319\u0280;celp\u0757\u234C\u2350\u235B\u2361al;\u62BA\u0100gr\u2355\u2359er\xF3\u1563\xE3\u234Darhk;\u6A17rod;\u6A3C\u0200cgpt\u236F\u2372\u2376\u237By;\u4451on;\u412Ff;\uC000\u{1D55A}a;\u43B9uest\u803B\xBF\u40BF\u0100ci\u238A\u238Fr;\uC000\u{1D4BE}n\u0280;Edsv\u04F4\u239B\u239D\u23A1\u04F3;\u62F9ot;\u62F5\u0100;v\u23A6\u23A7\u62F4;\u62F3\u0100;i\u0777\u23AElde;\u4129\u01EB\u23B8\0\u23BCcy;\u4456l\u803B\xEF\u40EF\u0300cfmosu\u23CC\u23D7\u23DC\u23E1\u23E7\u23F5\u0100iy\u23D1\u23D5rc;\u4135;\u4439r;\uC000\u{1D527}ath;\u4237pf;\uC000\u{1D55B}\u01E3\u23EC\0\u23F1r;\uC000\u{1D4BF}rcy;\u4458kcy;\u4454\u0400acfghjos\u240B\u2416\u2422\u2427\u242D\u2431\u2435\u243Bppa\u0100;v\u2413\u2414\u43BA;\u43F0\u0100ey\u241B\u2420dil;\u4137;\u443Ar;\uC000\u{1D528}reen;\u4138cy;\u4445cy;\u445Cpf;\uC000\u{1D55C}cr;\uC000\u{1D4C0}\u0B80ABEHabcdefghjlmnoprstuv\u2470\u2481\u2486\u248D\u2491\u250E\u253D\u255A\u2580\u264E\u265E\u2665\u2679\u267D\u269A\u26B2\u26D8\u275D\u2768\u278B\u27C0\u2801\u2812\u0180art\u2477\u247A\u247Cr\xF2\u09C6\xF2\u0395ail;\u691Barr;\u690E\u0100;g\u0994\u248B;\u6A8Bar;\u6962\u0963\u24A5\0\u24AA\0\u24B1\0\0\0\0\0\u24B5\u24BA\0\u24C6\u24C8\u24CD\0\u24F9ute;\u413Amptyv;\u69B4ra\xEE\u084Cbda;\u43BBg\u0180;dl\u088E\u24C1\u24C3;\u6991\xE5\u088E;\u6A85uo\u803B\xAB\u40ABr\u0400;bfhlpst\u0899\u24DE\u24E6\u24E9\u24EB\u24EE\u24F1\u24F5\u0100;f\u089D\u24E3s;\u691Fs;\u691D\xEB\u2252p;\u61ABl;\u6939im;\u6973l;\u61A2\u0180;ae\u24FF\u2500\u2504\u6AABil;\u6919\u0100;s\u2509\u250A\u6AAD;\uC000\u2AAD\uFE00\u0180abr\u2515\u2519\u251Drr;\u690Crk;\u6772\u0100ak\u2522\u252Cc\u0100ek\u2528\u252A;\u407B;\u405B\u0100es\u2531\u2533;\u698Bl\u0100du\u2539\u253B;\u698F;\u698D\u0200aeuy\u2546\u254B\u2556\u2558ron;\u413E\u0100di\u2550\u2554il;\u413C\xEC\u08B0\xE2\u2529;\u443B\u0200cqrs\u2563\u2566\u256D\u257Da;\u6936uo\u0100;r\u0E19\u1746\u0100du\u2572\u2577har;\u6967shar;\u694Bh;\u61B2\u0280;fgqs\u258B\u258C\u0989\u25F3\u25FF\u6264t\u0280ahlrt\u2598\u25A4\u25B7\u25C2\u25E8rrow\u0100;t\u0899\u25A1a\xE9\u24F6arpoon\u0100du\u25AF\u25B4own\xBB\u045Ap\xBB\u0966eftarrows;\u61C7ight\u0180ahs\u25CD\u25D6\u25DErrow\u0100;s\u08F4\u08A7arpoon\xF3\u0F98quigarro\xF7\u21F0hreetimes;\u62CB\u0180;qs\u258B\u0993\u25FAlan\xF4\u09AC\u0280;cdgs\u09AC\u260A\u260D\u261D\u2628c;\u6AA8ot\u0100;o\u2614\u2615\u6A7F\u0100;r\u261A\u261B\u6A81;\u6A83\u0100;e\u2622\u2625\uC000\u22DA\uFE00s;\u6A93\u0280adegs\u2633\u2639\u263D\u2649\u264Bppro\xF8\u24C6ot;\u62D6q\u0100gq\u2643\u2645\xF4\u0989gt\xF2\u248C\xF4\u099Bi\xED\u09B2\u0180ilr\u2655\u08E1\u265Asht;\u697C;\uC000\u{1D529}\u0100;E\u099C\u2663;\u6A91\u0161\u2669\u2676r\u0100du\u25B2\u266E\u0100;l\u0965\u2673;\u696Alk;\u6584cy;\u4459\u0280;acht\u0A48\u2688\u268B\u2691\u2696r\xF2\u25C1orne\xF2\u1D08ard;\u696Bri;\u65FA\u0100io\u269F\u26A4dot;\u4140ust\u0100;a\u26AC\u26AD\u63B0che\xBB\u26AD\u0200Eaes\u26BB\u26BD\u26C9\u26D4;\u6268p\u0100;p\u26C3\u26C4\u6A89rox\xBB\u26C4\u0100;q\u26CE\u26CF\u6A87\u0100;q\u26CE\u26BBim;\u62E6\u0400abnoptwz\u26E9\u26F4\u26F7\u271A\u272F\u2741\u2747\u2750\u0100nr\u26EE\u26F1g;\u67ECr;\u61FDr\xEB\u08C1g\u0180lmr\u26FF\u270D\u2714eft\u0100ar\u09E6\u2707ight\xE1\u09F2apsto;\u67FCight\xE1\u09FDparrow\u0100lr\u2725\u2729ef\xF4\u24EDight;\u61AC\u0180afl\u2736\u2739\u273Dr;\u6985;\uC000\u{1D55D}us;\u6A2Dimes;\u6A34\u0161\u274B\u274Fst;\u6217\xE1\u134E\u0180;ef\u2757\u2758\u1800\u65CAnge\xBB\u2758ar\u0100;l\u2764\u2765\u4028t;\u6993\u0280achmt\u2773\u2776\u277C\u2785\u2787r\xF2\u08A8orne\xF2\u1D8Car\u0100;d\u0F98\u2783;\u696D;\u600Eri;\u62BF\u0300achiqt\u2798\u279D\u0A40\u27A2\u27AE\u27BBquo;\u6039r;\uC000\u{1D4C1}m\u0180;eg\u09B2\u27AA\u27AC;\u6A8D;\u6A8F\u0100bu\u252A\u27B3o\u0100;r\u0E1F\u27B9;\u601Arok;\u4142\u8400<;cdhilqr\u082B\u27D2\u2639\u27DC\u27E0\u27E5\u27EA\u27F0\u0100ci\u27D7\u27D9;\u6AA6r;\u6A79re\xE5\u25F2mes;\u62C9arr;\u6976uest;\u6A7B\u0100Pi\u27F5\u27F9ar;\u6996\u0180;ef\u2800\u092D\u181B\u65C3r\u0100du\u2807\u280Dshar;\u694Ahar;\u6966\u0100en\u2817\u2821rtneqq;\uC000\u2268\uFE00\xC5\u281E\u0700Dacdefhilnopsu\u2840\u2845\u2882\u288E\u2893\u28A0\u28A5\u28A8\u28DA\u28E2\u28E4\u0A83\u28F3\u2902Dot;\u623A\u0200clpr\u284E\u2852\u2863\u287Dr\u803B\xAF\u40AF\u0100et\u2857\u2859;\u6642\u0100;e\u285E\u285F\u6720se\xBB\u285F\u0100;s\u103B\u2868to\u0200;dlu\u103B\u2873\u2877\u287Bow\xEE\u048Cef\xF4\u090F\xF0\u13D1ker;\u65AE\u0100oy\u2887\u288Cmma;\u6A29;\u443Cash;\u6014asuredangle\xBB\u1626r;\uC000\u{1D52A}o;\u6127\u0180cdn\u28AF\u28B4\u28C9ro\u803B\xB5\u40B5\u0200;acd\u1464\u28BD\u28C0\u28C4s\xF4\u16A7ir;\u6AF0ot\u80BB\xB7\u01B5us\u0180;bd\u28D2\u1903\u28D3\u6212\u0100;u\u1D3C\u28D8;\u6A2A\u0163\u28DE\u28E1p;\u6ADB\xF2\u2212\xF0\u0A81\u0100dp\u28E9\u28EEels;\u62A7f;\uC000\u{1D55E}\u0100ct\u28F8\u28FDr;\uC000\u{1D4C2}pos\xBB\u159D\u0180;lm\u2909\u290A\u290D\u43BCtimap;\u62B8\u0C00GLRVabcdefghijlmoprstuvw\u2942\u2953\u297E\u2989\u2998\u29DA\u29E9\u2A15\u2A1A\u2A58\u2A5D\u2A83\u2A95\u2AA4\u2AA8\u2B04\u2B07\u2B44\u2B7F\u2BAE\u2C34\u2C67\u2C7C\u2CE9\u0100gt\u2947\u294B;\uC000\u22D9\u0338\u0100;v\u2950\u0BCF\uC000\u226B\u20D2\u0180elt\u295A\u2972\u2976ft\u0100ar\u2961\u2967rrow;\u61CDightarrow;\u61CE;\uC000\u22D8\u0338\u0100;v\u297B\u0C47\uC000\u226A\u20D2ightarrow;\u61CF\u0100Dd\u298E\u2993ash;\u62AFash;\u62AE\u0280bcnpt\u29A3\u29A7\u29AC\u29B1\u29CCla\xBB\u02DEute;\u4144g;\uC000\u2220\u20D2\u0280;Eiop\u0D84\u29BC\u29C0\u29C5\u29C8;\uC000\u2A70\u0338d;\uC000\u224B\u0338s;\u4149ro\xF8\u0D84ur\u0100;a\u29D3\u29D4\u666El\u0100;s\u29D3\u0B38\u01F3\u29DF\0\u29E3p\u80BB\xA0\u0B37mp\u0100;e\u0BF9\u0C00\u0280aeouy\u29F4\u29FE\u2A03\u2A10\u2A13\u01F0\u29F9\0\u29FB;\u6A43on;\u4148dil;\u4146ng\u0100;d\u0D7E\u2A0Aot;\uC000\u2A6D\u0338p;\u6A42;\u443Dash;\u6013\u0380;Aadqsx\u0B92\u2A29\u2A2D\u2A3B\u2A41\u2A45\u2A50rr;\u61D7r\u0100hr\u2A33\u2A36k;\u6924\u0100;o\u13F2\u13F0ot;\uC000\u2250\u0338ui\xF6\u0B63\u0100ei\u2A4A\u2A4Ear;\u6928\xED\u0B98ist\u0100;s\u0BA0\u0B9Fr;\uC000\u{1D52B}\u0200Eest\u0BC5\u2A66\u2A79\u2A7C\u0180;qs\u0BBC\u2A6D\u0BE1\u0180;qs\u0BBC\u0BC5\u2A74lan\xF4\u0BE2i\xED\u0BEA\u0100;r\u0BB6\u2A81\xBB\u0BB7\u0180Aap\u2A8A\u2A8D\u2A91r\xF2\u2971rr;\u61AEar;\u6AF2\u0180;sv\u0F8D\u2A9C\u0F8C\u0100;d\u2AA1\u2AA2\u62FC;\u62FAcy;\u445A\u0380AEadest\u2AB7\u2ABA\u2ABE\u2AC2\u2AC5\u2AF6\u2AF9r\xF2\u2966;\uC000\u2266\u0338rr;\u619Ar;\u6025\u0200;fqs\u0C3B\u2ACE\u2AE3\u2AEFt\u0100ar\u2AD4\u2AD9rro\xF7\u2AC1ightarro\xF7\u2A90\u0180;qs\u0C3B\u2ABA\u2AEAlan\xF4\u0C55\u0100;s\u0C55\u2AF4\xBB\u0C36i\xED\u0C5D\u0100;r\u0C35\u2AFEi\u0100;e\u0C1A\u0C25i\xE4\u0D90\u0100pt\u2B0C\u2B11f;\uC000\u{1D55F}\u8180\xAC;in\u2B19\u2B1A\u2B36\u40ACn\u0200;Edv\u0B89\u2B24\u2B28\u2B2E;\uC000\u22F9\u0338ot;\uC000\u22F5\u0338\u01E1\u0B89\u2B33\u2B35;\u62F7;\u62F6i\u0100;v\u0CB8\u2B3C\u01E1\u0CB8\u2B41\u2B43;\u62FE;\u62FD\u0180aor\u2B4B\u2B63\u2B69r\u0200;ast\u0B7B\u2B55\u2B5A\u2B5Flle\xEC\u0B7Bl;\uC000\u2AFD\u20E5;\uC000\u2202\u0338lint;\u6A14\u0180;ce\u0C92\u2B70\u2B73u\xE5\u0CA5\u0100;c\u0C98\u2B78\u0100;e\u0C92\u2B7D\xF1\u0C98\u0200Aait\u2B88\u2B8B\u2B9D\u2BA7r\xF2\u2988rr\u0180;cw\u2B94\u2B95\u2B99\u619B;\uC000\u2933\u0338;\uC000\u219D\u0338ghtarrow\xBB\u2B95ri\u0100;e\u0CCB\u0CD6\u0380chimpqu\u2BBD\u2BCD\u2BD9\u2B04\u0B78\u2BE4\u2BEF\u0200;cer\u0D32\u2BC6\u0D37\u2BC9u\xE5\u0D45;\uC000\u{1D4C3}ort\u026D\u2B05\0\0\u2BD6ar\xE1\u2B56m\u0100;e\u0D6E\u2BDF\u0100;q\u0D74\u0D73su\u0100bp\u2BEB\u2BED\xE5\u0CF8\xE5\u0D0B\u0180bcp\u2BF6\u2C11\u2C19\u0200;Ees\u2BFF\u2C00\u0D22\u2C04\u6284;\uC000\u2AC5\u0338et\u0100;e\u0D1B\u2C0Bq\u0100;q\u0D23\u2C00c\u0100;e\u0D32\u2C17\xF1\u0D38\u0200;Ees\u2C22\u2C23\u0D5F\u2C27\u6285;\uC000\u2AC6\u0338et\u0100;e\u0D58\u2C2Eq\u0100;q\u0D60\u2C23\u0200gilr\u2C3D\u2C3F\u2C45\u2C47\xEC\u0BD7lde\u803B\xF1\u40F1\xE7\u0C43iangle\u0100lr\u2C52\u2C5Ceft\u0100;e\u0C1A\u2C5A\xF1\u0C26ight\u0100;e\u0CCB\u2C65\xF1\u0CD7\u0100;m\u2C6C\u2C6D\u43BD\u0180;es\u2C74\u2C75\u2C79\u4023ro;\u6116p;\u6007\u0480DHadgilrs\u2C8F\u2C94\u2C99\u2C9E\u2CA3\u2CB0\u2CB6\u2CD3\u2CE3ash;\u62ADarr;\u6904p;\uC000\u224D\u20D2ash;\u62AC\u0100et\u2CA8\u2CAC;\uC000\u2265\u20D2;\uC000>\u20D2nfin;\u69DE\u0180Aet\u2CBD\u2CC1\u2CC5rr;\u6902;\uC000\u2264\u20D2\u0100;r\u2CCA\u2CCD\uC000<\u20D2ie;\uC000\u22B4\u20D2\u0100At\u2CD8\u2CDCrr;\u6903rie;\uC000\u22B5\u20D2im;\uC000\u223C\u20D2\u0180Aan\u2CF0\u2CF4\u2D02rr;\u61D6r\u0100hr\u2CFA\u2CFDk;\u6923\u0100;o\u13E7\u13E5ear;\u6927\u1253\u1A95\0\0\0\0\0\0\0\0\0\0\0\0\0\u2D2D\0\u2D38\u2D48\u2D60\u2D65\u2D72\u2D84\u1B07\0\0\u2D8D\u2DAB\0\u2DC8\u2DCE\0\u2DDC\u2E19\u2E2B\u2E3E\u2E43\u0100cs\u2D31\u1A97ute\u803B\xF3\u40F3\u0100iy\u2D3C\u2D45r\u0100;c\u1A9E\u2D42\u803B\xF4\u40F4;\u443E\u0280abios\u1AA0\u2D52\u2D57\u01C8\u2D5Alac;\u4151v;\u6A38old;\u69BClig;\u4153\u0100cr\u2D69\u2D6Dir;\u69BF;\uC000\u{1D52C}\u036F\u2D79\0\0\u2D7C\0\u2D82n;\u42DBave\u803B\xF2\u40F2;\u69C1\u0100bm\u2D88\u0DF4ar;\u69B5\u0200acit\u2D95\u2D98\u2DA5\u2DA8r\xF2\u1A80\u0100ir\u2D9D\u2DA0r;\u69BEoss;\u69BBn\xE5\u0E52;\u69C0\u0180aei\u2DB1\u2DB5\u2DB9cr;\u414Dga;\u43C9\u0180cdn\u2DC0\u2DC5\u01CDron;\u43BF;\u69B6pf;\uC000\u{1D560}\u0180ael\u2DD4\u2DD7\u01D2r;\u69B7rp;\u69B9\u0380;adiosv\u2DEA\u2DEB\u2DEE\u2E08\u2E0D\u2E10\u2E16\u6228r\xF2\u1A86\u0200;efm\u2DF7\u2DF8\u2E02\u2E05\u6A5Dr\u0100;o\u2DFE\u2DFF\u6134f\xBB\u2DFF\u803B\xAA\u40AA\u803B\xBA\u40BAgof;\u62B6r;\u6A56lope;\u6A57;\u6A5B\u0180clo\u2E1F\u2E21\u2E27\xF2\u2E01ash\u803B\xF8\u40F8l;\u6298i\u016C\u2E2F\u2E34de\u803B\xF5\u40F5es\u0100;a\u01DB\u2E3As;\u6A36ml\u803B\xF6\u40F6bar;\u633D\u0AE1\u2E5E\0\u2E7D\0\u2E80\u2E9D\0\u2EA2\u2EB9\0\0\u2ECB\u0E9C\0\u2F13\0\0\u2F2B\u2FBC\0\u2FC8r\u0200;ast\u0403\u2E67\u2E72\u0E85\u8100\xB6;l\u2E6D\u2E6E\u40B6le\xEC\u0403\u0269\u2E78\0\0\u2E7Bm;\u6AF3;\u6AFDy;\u443Fr\u0280cimpt\u2E8B\u2E8F\u2E93\u1865\u2E97nt;\u4025od;\u402Eil;\u6030enk;\u6031r;\uC000\u{1D52D}\u0180imo\u2EA8\u2EB0\u2EB4\u0100;v\u2EAD\u2EAE\u43C6;\u43D5ma\xF4\u0A76ne;\u660E\u0180;tv\u2EBF\u2EC0\u2EC8\u43C0chfork\xBB\u1FFD;\u43D6\u0100au\u2ECF\u2EDFn\u0100ck\u2ED5\u2EDDk\u0100;h\u21F4\u2EDB;\u610E\xF6\u21F4s\u0480;abcdemst\u2EF3\u2EF4\u1908\u2EF9\u2EFD\u2F04\u2F06\u2F0A\u2F0E\u402Bcir;\u6A23ir;\u6A22\u0100ou\u1D40\u2F02;\u6A25;\u6A72n\u80BB\xB1\u0E9Dim;\u6A26wo;\u6A27\u0180ipu\u2F19\u2F20\u2F25ntint;\u6A15f;\uC000\u{1D561}nd\u803B\xA3\u40A3\u0500;Eaceinosu\u0EC8\u2F3F\u2F41\u2F44\u2F47\u2F81\u2F89\u2F92\u2F7E\u2FB6;\u6AB3p;\u6AB7u\xE5\u0ED9\u0100;c\u0ECE\u2F4C\u0300;acens\u0EC8\u2F59\u2F5F\u2F66\u2F68\u2F7Eppro\xF8\u2F43urlye\xF1\u0ED9\xF1\u0ECE\u0180aes\u2F6F\u2F76\u2F7Approx;\u6AB9qq;\u6AB5im;\u62E8i\xED\u0EDFme\u0100;s\u2F88\u0EAE\u6032\u0180Eas\u2F78\u2F90\u2F7A\xF0\u2F75\u0180dfp\u0EEC\u2F99\u2FAF\u0180als\u2FA0\u2FA5\u2FAAlar;\u632Eine;\u6312urf;\u6313\u0100;t\u0EFB\u2FB4\xEF\u0EFBrel;\u62B0\u0100ci\u2FC0\u2FC5r;\uC000\u{1D4C5};\u43C8ncsp;\u6008\u0300fiopsu\u2FDA\u22E2\u2FDF\u2FE5\u2FEB\u2FF1r;\uC000\u{1D52E}pf;\uC000\u{1D562}rime;\u6057cr;\uC000\u{1D4C6}\u0180aeo\u2FF8\u3009\u3013t\u0100ei\u2FFE\u3005rnion\xF3\u06B0nt;\u6A16st\u0100;e\u3010\u3011\u403F\xF1\u1F19\xF4\u0F14\u0A80ABHabcdefhilmnoprstux\u3040\u3051\u3055\u3059\u30E0\u310E\u312B\u3147\u3162\u3172\u318E\u3206\u3215\u3224\u3229\u3258\u326E\u3272\u3290\u32B0\u32B7\u0180art\u3047\u304A\u304Cr\xF2\u10B3\xF2\u03DDail;\u691Car\xF2\u1C65ar;\u6964\u0380cdenqrt\u3068\u3075\u3078\u307F\u308F\u3094\u30CC\u0100eu\u306D\u3071;\uC000\u223D\u0331te;\u4155i\xE3\u116Emptyv;\u69B3g\u0200;del\u0FD1\u3089\u308B\u308D;\u6992;\u69A5\xE5\u0FD1uo\u803B\xBB\u40BBr\u0580;abcfhlpstw\u0FDC\u30AC\u30AF\u30B7\u30B9\u30BC\u30BE\u30C0\u30C3\u30C7\u30CAp;\u6975\u0100;f\u0FE0\u30B4s;\u6920;\u6933s;\u691E\xEB\u225D\xF0\u272El;\u6945im;\u6974l;\u61A3;\u619D\u0100ai\u30D1\u30D5il;\u691Ao\u0100;n\u30DB\u30DC\u6236al\xF3\u0F1E\u0180abr\u30E7\u30EA\u30EEr\xF2\u17E5rk;\u6773\u0100ak\u30F3\u30FDc\u0100ek\u30F9\u30FB;\u407D;\u405D\u0100es\u3102\u3104;\u698Cl\u0100du\u310A\u310C;\u698E;\u6990\u0200aeuy\u3117\u311C\u3127\u3129ron;\u4159\u0100di\u3121\u3125il;\u4157\xEC\u0FF2\xE2\u30FA;\u4440\u0200clqs\u3134\u3137\u313D\u3144a;\u6937dhar;\u6969uo\u0100;r\u020E\u020Dh;\u61B3\u0180acg\u314E\u315F\u0F44l\u0200;ips\u0F78\u3158\u315B\u109Cn\xE5\u10BBar\xF4\u0FA9t;\u65AD\u0180ilr\u3169\u1023\u316Esht;\u697D;\uC000\u{1D52F}\u0100ao\u3177\u3186r\u0100du\u317D\u317F\xBB\u047B\u0100;l\u1091\u3184;\u696C\u0100;v\u318B\u318C\u43C1;\u43F1\u0180gns\u3195\u31F9\u31FCht\u0300ahlrst\u31A4\u31B0\u31C2\u31D8\u31E4\u31EErrow\u0100;t\u0FDC\u31ADa\xE9\u30C8arpoon\u0100du\u31BB\u31BFow\xEE\u317Ep\xBB\u1092eft\u0100ah\u31CA\u31D0rrow\xF3\u0FEAarpoon\xF3\u0551ightarrows;\u61C9quigarro\xF7\u30CBhreetimes;\u62CCg;\u42DAingdotse\xF1\u1F32\u0180ahm\u320D\u3210\u3213r\xF2\u0FEAa\xF2\u0551;\u600Foust\u0100;a\u321E\u321F\u63B1che\xBB\u321Fmid;\u6AEE\u0200abpt\u3232\u323D\u3240\u3252\u0100nr\u3237\u323Ag;\u67EDr;\u61FEr\xEB\u1003\u0180afl\u3247\u324A\u324Er;\u6986;\uC000\u{1D563}us;\u6A2Eimes;\u6A35\u0100ap\u325D\u3267r\u0100;g\u3263\u3264\u4029t;\u6994olint;\u6A12ar\xF2\u31E3\u0200achq\u327B\u3280\u10BC\u3285quo;\u603Ar;\uC000\u{1D4C7}\u0100bu\u30FB\u328Ao\u0100;r\u0214\u0213\u0180hir\u3297\u329B\u32A0re\xE5\u31F8mes;\u62CAi\u0200;efl\u32AA\u1059\u1821\u32AB\u65B9tri;\u69CEluhar;\u6968;\u611E\u0D61\u32D5\u32DB\u32DF\u332C\u3338\u3371\0\u337A\u33A4\0\0\u33EC\u33F0\0\u3428\u3448\u345A\u34AD\u34B1\u34CA\u34F1\0\u3616\0\0\u3633cute;\u415Bqu\xEF\u27BA\u0500;Eaceinpsy\u11ED\u32F3\u32F5\u32FF\u3302\u330B\u330F\u331F\u3326\u3329;\u6AB4\u01F0\u32FA\0\u32FC;\u6AB8on;\u4161u\xE5\u11FE\u0100;d\u11F3\u3307il;\u415Frc;\u415D\u0180Eas\u3316\u3318\u331B;\u6AB6p;\u6ABAim;\u62E9olint;\u6A13i\xED\u1204;\u4441ot\u0180;be\u3334\u1D47\u3335\u62C5;\u6A66\u0380Aacmstx\u3346\u334A\u3357\u335B\u335E\u3363\u336Drr;\u61D8r\u0100hr\u3350\u3352\xEB\u2228\u0100;o\u0A36\u0A34t\u803B\xA7\u40A7i;\u403Bwar;\u6929m\u0100in\u3369\xF0nu\xF3\xF1t;\u6736r\u0100;o\u3376\u2055\uC000\u{1D530}\u0200acoy\u3382\u3386\u3391\u33A0rp;\u666F\u0100hy\u338B\u338Fcy;\u4449;\u4448rt\u026D\u3399\0\0\u339Ci\xE4\u1464ara\xEC\u2E6F\u803B\xAD\u40AD\u0100gm\u33A8\u33B4ma\u0180;fv\u33B1\u33B2\u33B2\u43C3;\u43C2\u0400;deglnpr\u12AB\u33C5\u33C9\u33CE\u33D6\u33DE\u33E1\u33E6ot;\u6A6A\u0100;q\u12B1\u12B0\u0100;E\u33D3\u33D4\u6A9E;\u6AA0\u0100;E\u33DB\u33DC\u6A9D;\u6A9Fe;\u6246lus;\u6A24arr;\u6972ar\xF2\u113D\u0200aeit\u33F8\u3408\u340F\u3417\u0100ls\u33FD\u3404lsetm\xE9\u336Ahp;\u6A33parsl;\u69E4\u0100dl\u1463\u3414e;\u6323\u0100;e\u341C\u341D\u6AAA\u0100;s\u3422\u3423\u6AAC;\uC000\u2AAC\uFE00\u0180flp\u342E\u3433\u3442tcy;\u444C\u0100;b\u3438\u3439\u402F\u0100;a\u343E\u343F\u69C4r;\u633Ff;\uC000\u{1D564}a\u0100dr\u344D\u0402es\u0100;u\u3454\u3455\u6660it\xBB\u3455\u0180csu\u3460\u3479\u349F\u0100au\u3465\u346Fp\u0100;s\u1188\u346B;\uC000\u2293\uFE00p\u0100;s\u11B4\u3475;\uC000\u2294\uFE00u\u0100bp\u347F\u348F\u0180;es\u1197\u119C\u3486et\u0100;e\u1197\u348D\xF1\u119D\u0180;es\u11A8\u11AD\u3496et\u0100;e\u11A8\u349D\xF1\u11AE\u0180;af\u117B\u34A6\u05B0r\u0165\u34AB\u05B1\xBB\u117Car\xF2\u1148\u0200cemt\u34B9\u34BE\u34C2\u34C5r;\uC000\u{1D4C8}tm\xEE\xF1i\xEC\u3415ar\xE6\u11BE\u0100ar\u34CE\u34D5r\u0100;f\u34D4\u17BF\u6606\u0100an\u34DA\u34EDight\u0100ep\u34E3\u34EApsilo\xEE\u1EE0h\xE9\u2EAFs\xBB\u2852\u0280bcmnp\u34FB\u355E\u1209\u358B\u358E\u0480;Edemnprs\u350E\u350F\u3511\u3515\u351E\u3523\u352C\u3531\u3536\u6282;\u6AC5ot;\u6ABD\u0100;d\u11DA\u351Aot;\u6AC3ult;\u6AC1\u0100Ee\u3528\u352A;\u6ACB;\u628Alus;\u6ABFarr;\u6979\u0180eiu\u353D\u3552\u3555t\u0180;en\u350E\u3545\u354Bq\u0100;q\u11DA\u350Feq\u0100;q\u352B\u3528m;\u6AC7\u0100bp\u355A\u355C;\u6AD5;\u6AD3c\u0300;acens\u11ED\u356C\u3572\u3579\u357B\u3326ppro\xF8\u32FAurlye\xF1\u11FE\xF1\u11F3\u0180aes\u3582\u3588\u331Bppro\xF8\u331Aq\xF1\u3317g;\u666A\u0680123;Edehlmnps\u35A9\u35AC\u35AF\u121C\u35B2\u35B4\u35C0\u35C9\u35D5\u35DA\u35DF\u35E8\u35ED\u803B\xB9\u40B9\u803B\xB2\u40B2\u803B\xB3\u40B3;\u6AC6\u0100os\u35B9\u35BCt;\u6ABEub;\u6AD8\u0100;d\u1222\u35C5ot;\u6AC4s\u0100ou\u35CF\u35D2l;\u67C9b;\u6AD7arr;\u697Bult;\u6AC2\u0100Ee\u35E4\u35E6;\u6ACC;\u628Blus;\u6AC0\u0180eiu\u35F4\u3609\u360Ct\u0180;en\u121C\u35FC\u3602q\u0100;q\u1222\u35B2eq\u0100;q\u35E7\u35E4m;\u6AC8\u0100bp\u3611\u3613;\u6AD4;\u6AD6\u0180Aan\u361C\u3620\u362Drr;\u61D9r\u0100hr\u3626\u3628\xEB\u222E\u0100;o\u0A2B\u0A29war;\u692Alig\u803B\xDF\u40DF\u0BE1\u3651\u365D\u3660\u12CE\u3673\u3679\0\u367E\u36C2\0\0\0\0\0\u36DB\u3703\0\u3709\u376C\0\0\0\u3787\u0272\u3656\0\0\u365Bget;\u6316;\u43C4r\xEB\u0E5F\u0180aey\u3666\u366B\u3670ron;\u4165dil;\u4163;\u4442lrec;\u6315r;\uC000\u{1D531}\u0200eiko\u3686\u369D\u36B5\u36BC\u01F2\u368B\0\u3691e\u01004f\u1284\u1281a\u0180;sv\u3698\u3699\u369B\u43B8ym;\u43D1\u0100cn\u36A2\u36B2k\u0100as\u36A8\u36AEppro\xF8\u12C1im\xBB\u12ACs\xF0\u129E\u0100as\u36BA\u36AE\xF0\u12C1rn\u803B\xFE\u40FE\u01EC\u031F\u36C6\u22E7es\u8180\xD7;bd\u36CF\u36D0\u36D8\u40D7\u0100;a\u190F\u36D5r;\u6A31;\u6A30\u0180eps\u36E1\u36E3\u3700\xE1\u2A4D\u0200;bcf\u0486\u36EC\u36F0\u36F4ot;\u6336ir;\u6AF1\u0100;o\u36F9\u36FC\uC000\u{1D565}rk;\u6ADA\xE1\u3362rime;\u6034\u0180aip\u370F\u3712\u3764d\xE5\u1248\u0380adempst\u3721\u374D\u3740\u3751\u3757\u375C\u375Fngle\u0280;dlqr\u3730\u3731\u3736\u3740\u3742\u65B5own\xBB\u1DBBeft\u0100;e\u2800\u373E\xF1\u092E;\u625Cight\u0100;e\u32AA\u374B\xF1\u105Aot;\u65ECinus;\u6A3Alus;\u6A39b;\u69CDime;\u6A3Bezium;\u63E2\u0180cht\u3772\u377D\u3781\u0100ry\u3777\u377B;\uC000\u{1D4C9};\u4446cy;\u445Brok;\u4167\u0100io\u378B\u378Ex\xF4\u1777head\u0100lr\u3797\u37A0eftarro\xF7\u084Fightarrow\xBB\u0F5D\u0900AHabcdfghlmoprstuw\u37D0\u37D3\u37D7\u37E4\u37F0\u37FC\u380E\u381C\u3823\u3834\u3851\u385D\u386B\u38A9\u38CC\u38D2\u38EA\u38F6r\xF2\u03EDar;\u6963\u0100cr\u37DC\u37E2ute\u803B\xFA\u40FA\xF2\u1150r\u01E3\u37EA\0\u37EDy;\u445Eve;\u416D\u0100iy\u37F5\u37FArc\u803B\xFB\u40FB;\u4443\u0180abh\u3803\u3806\u380Br\xF2\u13ADlac;\u4171a\xF2\u13C3\u0100ir\u3813\u3818sht;\u697E;\uC000\u{1D532}rave\u803B\xF9\u40F9\u0161\u3827\u3831r\u0100lr\u382C\u382E\xBB\u0957\xBB\u1083lk;\u6580\u0100ct\u3839\u384D\u026F\u383F\0\0\u384Arn\u0100;e\u3845\u3846\u631Cr\xBB\u3846op;\u630Fri;\u65F8\u0100al\u3856\u385Acr;\u416B\u80BB\xA8\u0349\u0100gp\u3862\u3866on;\u4173f;\uC000\u{1D566}\u0300adhlsu\u114B\u3878\u387D\u1372\u3891\u38A0own\xE1\u13B3arpoon\u0100lr\u3888\u388Cef\xF4\u382Digh\xF4\u382Fi\u0180;hl\u3899\u389A\u389C\u43C5\xBB\u13FAon\xBB\u389Aparrows;\u61C8\u0180cit\u38B0\u38C4\u38C8\u026F\u38B6\0\0\u38C1rn\u0100;e\u38BC\u38BD\u631Dr\xBB\u38BDop;\u630Eng;\u416Fri;\u65F9cr;\uC000\u{1D4CA}\u0180dir\u38D9\u38DD\u38E2ot;\u62F0lde;\u4169i\u0100;f\u3730\u38E8\xBB\u1813\u0100am\u38EF\u38F2r\xF2\u38A8l\u803B\xFC\u40FCangle;\u69A7\u0780ABDacdeflnoprsz\u391C\u391F\u3929\u392D\u39B5\u39B8\u39BD\u39DF\u39E4\u39E8\u39F3\u39F9\u39FD\u3A01\u3A20r\xF2\u03F7ar\u0100;v\u3926\u3927\u6AE8;\u6AE9as\xE8\u03E1\u0100nr\u3932\u3937grt;\u699C\u0380eknprst\u34E3\u3946\u394B\u3952\u395D\u3964\u3996app\xE1\u2415othin\xE7\u1E96\u0180hir\u34EB\u2EC8\u3959op\xF4\u2FB5\u0100;h\u13B7\u3962\xEF\u318D\u0100iu\u3969\u396Dgm\xE1\u33B3\u0100bp\u3972\u3984setneq\u0100;q\u397D\u3980\uC000\u228A\uFE00;\uC000\u2ACB\uFE00setneq\u0100;q\u398F\u3992\uC000\u228B\uFE00;\uC000\u2ACC\uFE00\u0100hr\u399B\u399Fet\xE1\u369Ciangle\u0100lr\u39AA\u39AFeft\xBB\u0925ight\xBB\u1051y;\u4432ash\xBB\u1036\u0180elr\u39C4\u39D2\u39D7\u0180;be\u2DEA\u39CB\u39CFar;\u62BBq;\u625Alip;\u62EE\u0100bt\u39DC\u1468a\xF2\u1469r;\uC000\u{1D533}tr\xE9\u39AEsu\u0100bp\u39EF\u39F1\xBB\u0D1C\xBB\u0D59pf;\uC000\u{1D567}ro\xF0\u0EFBtr\xE9\u39B4\u0100cu\u3A06\u3A0Br;\uC000\u{1D4CB}\u0100bp\u3A10\u3A18n\u0100Ee\u3980\u3A16\xBB\u397En\u0100Ee\u3992\u3A1E\xBB\u3990igzag;\u699A\u0380cefoprs\u3A36\u3A3B\u3A56\u3A5B\u3A54\u3A61\u3A6Airc;\u4175\u0100di\u3A40\u3A51\u0100bg\u3A45\u3A49ar;\u6A5Fe\u0100;q\u15FA\u3A4F;\u6259erp;\u6118r;\uC000\u{1D534}pf;\uC000\u{1D568}\u0100;e\u1479\u3A66at\xE8\u1479cr;\uC000\u{1D4CC}\u0AE3\u178E\u3A87\0\u3A8B\0\u3A90\u3A9B\0\0\u3A9D\u3AA8\u3AAB\u3AAF\0\0\u3AC3\u3ACE\0\u3AD8\u17DC\u17DFtr\xE9\u17D1r;\uC000\u{1D535}\u0100Aa\u3A94\u3A97r\xF2\u03C3r\xF2\u09F6;\u43BE\u0100Aa\u3AA1\u3AA4r\xF2\u03B8r\xF2\u09EBa\xF0\u2713is;\u62FB\u0180dpt\u17A4\u3AB5\u3ABE\u0100fl\u3ABA\u17A9;\uC000\u{1D569}im\xE5\u17B2\u0100Aa\u3AC7\u3ACAr\xF2\u03CEr\xF2\u0A01\u0100cq\u3AD2\u17B8r;\uC000\u{1D4CD}\u0100pt\u17D6\u3ADCr\xE9\u17D4\u0400acefiosu\u3AF0\u3AFD\u3B08\u3B0C\u3B11\u3B15\u3B1B\u3B21c\u0100uy\u3AF6\u3AFBte\u803B\xFD\u40FD;\u444F\u0100iy\u3B02\u3B06rc;\u4177;\u444Bn\u803B\xA5\u40A5r;\uC000\u{1D536}cy;\u4457pf;\uC000\u{1D56A}cr;\uC000\u{1D4CE}\u0100cm\u3B26\u3B29y;\u444El\u803B\xFF\u40FF\u0500acdefhiosw\u3B42\u3B48\u3B54\u3B58\u3B64\u3B69\u3B6D\u3B74\u3B7A\u3B80cute;\u417A\u0100ay\u3B4D\u3B52ron;\u417E;\u4437ot;\u417C\u0100et\u3B5D\u3B61tr\xE6\u155Fa;\u43B6r;\uC000\u{1D537}cy;\u4436grarr;\u61DDpf;\uC000\u{1D56B}cr;\uC000\u{1D4CF}\u0100jn\u3B85\u3B87;\u600Dj;\u600C'.split("").map(u=>u.charCodeAt(0)));var z0=new Uint16Array("\u0200aglq \x1B\u026D\0\0p;\u4026os;\u4027t;\u403Et;\u403Cuot;\u4022".split("").map(u=>u.charCodeAt(0)));var i0,Ye=new Map([[0,65533],[128,8364],[130,8218],[131,402],[132,8222],[133,8230],[134,8224],[135,8225],[136,710],[137,8240],[138,352],[139,8249],[140,338],[142,381],[145,8216],[146,8217],[147,8220],[148,8221],[149,8226],[150,8211],[151,8212],[152,732],[153,8482],[154,353],[155,8250],[156,339],[158,382],[159,376]]),n0=(i0=String.fromCodePoint)!==null&&i0!==void 0?i0:function(u){let e="";return u>65535&&(u-=65536,e+=String.fromCharCode(u>>>10&1023|55296),u=56320|u&1023),e+=String.fromCharCode(u),e};function s0(u){var e;return u>=55296&&u<=57343||u>1114111?65533:(e=Ye.get(u))!==null&&e!==void 0?e:u}var q;(function(u){u[u.NUM=35]="NUM",u[u.SEMI=59]="SEMI",u[u.EQUALS=61]="EQUALS",u[u.ZERO=48]="ZERO",u[u.NINE=57]="NINE",u[u.LOWER_A=97]="LOWER_A",u[u.LOWER_F=102]="LOWER_F",u[u.LOWER_X=120]="LOWER_X",u[u.LOWER_Z=122]="LOWER_Z",u[u.UPPER_A=65]="UPPER_A",u[u.UPPER_F=70]="UPPER_F",u[u.UPPER_Z=90]="UPPER_Z"})(q||(q={}));var Ke=32,F;(function(u){u[u.VALUE_LENGTH=49152]="VALUE_LENGTH",u[u.BRANCH_LENGTH=16256]="BRANCH_LENGTH",u[u.JUMP_TABLE=127]="JUMP_TABLE"})(F||(F={}));function d0(u){return u>=q.ZERO&&u<=q.NINE}function ut(u){return u>=q.UPPER_A&&u<=q.UPPER_F||u>=q.LOWER_A&&u<=q.LOWER_F}function et(u){return u>=q.UPPER_A&&u<=q.UPPER_Z||u>=q.LOWER_A&&u<=q.LOWER_Z||d0(u)}function tt(u){return u===q.EQUALS||et(u)}var S;(function(u){u[u.EntityStart=0]="EntityStart",u[u.NumericStart=1]="NumericStart",u[u.NumericDecimal=2]="NumericDecimal",u[u.NumericHex=3]="NumericHex",u[u.NamedEntity=4]="NamedEntity"})(S||(S={}));var V;(function(u){u[u.Legacy=0]="Legacy",u[u.Strict=1]="Strict",u[u.Attribute=2]="Attribute"})(V||(V={}));var Du=class{constructor(e,t,a){this.decodeTree=e,this.emitCodePoint=t,this.errors=a,this.state=S.EntityStart,this.consumed=1,this.result=0,this.treeIndex=0,this.excess=1,this.decodeMode=V.Strict}startEntity(e){this.decodeMode=e,this.state=S.EntityStart,this.result=0,this.treeIndex=0,this.excess=1,this.consumed=1}write(e,t){switch(this.state){case S.EntityStart:return e.charCodeAt(t)===q.NUM?(this.state=S.NumericStart,this.consumed+=1,this.stateNumericStart(e,t+1)):(this.state=S.NamedEntity,this.stateNamedEntity(e,t));case S.NumericStart:return this.stateNumericStart(e,t);case S.NumericDecimal:return this.stateNumericDecimal(e,t);case S.NumericHex:return this.stateNumericHex(e,t);case S.NamedEntity:return this.stateNamedEntity(e,t)}}stateNumericStart(e,t){return t>=e.length?-1:(e.charCodeAt(t)|Ke)===q.LOWER_X?(this.state=S.NumericHex,this.consumed+=1,this.stateNumericHex(e,t+1)):(this.state=S.NumericDecimal,this.stateNumericDecimal(e,t))}addToNumericResult(e,t,a,r){if(t!==a){let c=a-t;this.result=this.result*Math.pow(r,c)+parseInt(e.substr(t,c),r),this.consumed+=c}}stateNumericHex(e,t){let a=t;for(;t>14;for(;t>14,c!==0){if(i===q.SEMI)return this.emitNamedEntityData(this.treeIndex,c,this.consumed+this.excess);this.decodeMode!==V.Strict&&(this.result=this.treeIndex,this.consumed+=this.excess,this.excess=0)}}return-1}emitNotTerminatedNamedEntity(){var e;let{result:t,decodeTree:a}=this,r=(a[t]&F.VALUE_LENGTH)>>14;return this.emitNamedEntityData(t,r,this.consumed),(e=this.errors)===null||e===void 0||e.missingSemicolonAfterCharacterReference(),this.consumed}emitNamedEntityData(e,t,a){let{decodeTree:r}=this;return this.emitCodePoint(t===1?r[e]&~F.VALUE_LENGTH:r[e+1],a),t===3&&this.emitCodePoint(r[e+2],a),a}end(){var e;switch(this.state){case S.NamedEntity:return this.result!==0&&(this.decodeMode!==V.Attribute||this.result===this.treeIndex)?this.emitNotTerminatedNamedEntity():0;case S.NumericDecimal:return this.emitNumericEntity(0,2);case S.NumericHex:return this.emitNumericEntity(0,3);case S.NumericStart:return(e=this.errors)===null||e===void 0||e.absenceOfDigitsInNumericCharacterReference(this.consumed),0;case S.EntityStart:return 0}}};function W0(u){let e="",t=new Du(u,a=>e+=n0(a));return function(r,c){let i=0,n=0;for(;(n=r.indexOf("&",n))>=0;){e+=r.slice(i,n),t.startEntity(c);let d=t.write(r,n+1);if(d<0){i=n+t.end();break}i=n+d,n=d===0?i+1:i}let s=e+r.slice(i);return e="",s}}function at(u,e,t,a){let r=(e&F.BRANCH_LENGTH)>>7,c=e&F.JUMP_TABLE;if(r===0)return c!==0&&a===c?t:-1;if(c){let s=a-c;return s<0||s>=r?-1:u[t+s]-1}let i=t,n=i+r-1;for(;i<=n;){let s=i+n>>>1,d=u[s];if(da)n=s-1;else return u[s+r]}return-1}var $1=W0(j0),X1=W0(z0);function Iu(u){for(let e=1;e$\x80-\uFFFF]/g,Q0=new Map([[34,"""],[38,"&"],[39,"'"],[60,"<"],[62,">"]]),Z0=String.prototype.codePointAt!=null?(u,e)=>u.codePointAt(e):(u,e)=>(u.charCodeAt(e)&64512)===55296?(u.charCodeAt(e)-55296)*1024+u.charCodeAt(e+1)-56320+65536:u.charCodeAt(e);function lu(u){let e="",t=0,a;for(;(a=f0.exec(u))!==null;){let r=a.index,c=u.charCodeAt(r),i=Q0.get(c);i!==void 0?(e+=u.substring(t,r)+i,t=r+1):(e+=`${u.substring(t,r)}&#x${Z0(u,r).toString(16)};`,t=f0.lastIndex+=+((c&64512)===55296))}return e+u.substr(t)}function o0(u,e){return function(a){let r,c=0,i="";for(;r=u.exec(a);)c!==r.index&&(i+=a.substring(c,r.index)),i+=e.get(r[0].charCodeAt(0)),c=r.index+1;return i+a.substring(c)}}var $0=o0(/[&<>'"]/g,Q0),Cu=o0(/["&\u00A0]/g,new Map([[34,"""],[38,"&"],[160," "]])),Ru=o0(/[&<>\u00A0]/g,new Map([[38,"&"],[60,"<"],[62,">"],[160," "]]));var X0;(function(u){u[u.XML=0]="XML",u[u.HTML=1]="HTML"})(X0||(X0={}));var J0;(function(u){u[u.UTF8=0]="UTF8",u[u.ASCII=1]="ASCII",u[u.Extensive=2]="Extensive",u[u.Attribute=3]="Attribute",u[u.Text=4]="Text"})(J0||(J0={}));var ue=new Map(["altGlyph","altGlyphDef","altGlyphItem","animateColor","animateMotion","animateTransform","clipPath","feBlend","feColorMatrix","feComponentTransfer","feComposite","feConvolveMatrix","feDiffuseLighting","feDisplacementMap","feDistantLight","feDropShadow","feFlood","feFuncA","feFuncB","feFuncG","feFuncR","feGaussianBlur","feImage","feMerge","feMergeNode","feMorphology","feOffset","fePointLight","feSpecularLighting","feSpotLight","feTile","feTurbulence","foreignObject","glyphRef","linearGradient","radialGradient","textPath"].map(u=>[u.toLowerCase(),u])),ee=new Map(["definitionURL","attributeName","attributeType","baseFrequency","baseProfile","calcMode","clipPathUnits","diffuseConstant","edgeMode","filterUnits","glyphRef","gradientTransform","gradientUnits","kernelMatrix","kernelUnitLength","keyPoints","keySplines","keyTimes","lengthAdjust","limitingConeAngle","markerHeight","markerUnits","markerWidth","maskContentUnits","maskUnits","numOctaves","pathLength","patternContentUnits","patternTransform","patternUnits","pointsAtX","pointsAtY","pointsAtZ","preserveAlpha","preserveAspectRatio","primitiveUnits","refX","refY","repeatCount","repeatDur","requiredExtensions","requiredFeatures","specularConstant","specularExponent","spreadMethod","startOffset","stdDeviation","stitchTiles","surfaceScale","systemLanguage","tableValues","targetX","targetY","textLength","viewBox","viewTarget","xChannelSelector","yChannelSelector","zoomAndPan"].map(u=>[u.toLowerCase(),u]));var it=new Set(["style","script","xmp","iframe","noembed","noframes","plaintext","noscript"]);function nt(u){return u.replace(/"/g,""")}function st(u,e){var t;if(!u)return;let a=((t=e.encodeEntities)!==null&&t!==void 0?t:e.decodeEntities)===!1?nt:e.xmlMode||e.encodeEntities!=="utf8"?lu:Cu;return Object.keys(u).map(r=>{var c,i;let n=(c=u[r])!==null&&c!==void 0?c:"";return e.xmlMode==="foreign"&&(r=(i=ee.get(r))!==null&&i!==void 0?i:r),!e.emptyAttrs&&!e.xmlMode&&n===""?r:`${r}="${a(n)}"`}).join(" ")}var te=new Set(["area","base","basefont","br","col","command","embed","frame","hr","img","input","isindex","keygen","link","meta","param","source","track","wbr"]);function h0(u,e={}){let t="length"in u?u:[u],a="";for(let r=0;r0&&(a+=h0(u.children,e)),(e.xmlMode||!te.has(u.name))&&(a+=``)),a}function lt(u){return`<${u.data}>`}function ht(u,e){var t;let a=u.data||"";return((t=e.encodeEntities)!==null&&t!==void 0?t:e.decodeEntities)!==!1&&!(!e.xmlMode&&u.parent&&it.has(u.parent.name))&&(a=e.xmlMode||e.encodeEntities!=="utf8"?lu(a):Ru(a)),a}function pt(u){return``}function xt(u){return``}function re(u,e){return ae(u,e)}function mt(u,e){return N(u)?u.children.map(t=>re(t,e)).join(""):""}function _u(u){return Array.isArray(u)?u.map(_u).join(""):T(u)?u.name==="br"?` +`:_u(u.children):K(u)?_u(u.children):M(u)?u.data:""}function hu(u){return Array.isArray(u)?u.map(hu).join(""):N(u)&&!bu(u)?hu(u.children):M(u)?u.data:""}function p0(u){return Array.isArray(u)?u.map(p0).join(""):N(u)&&(u.type===p.Tag||K(u))?p0(u.children):M(u)?u.data:""}function ce(u){return N(u)?u.children:[]}function ie(u){return u.parent||null}function gt(u){let e=ie(u);if(e!=null)return ce(e);let t=[u],{prev:a,next:r}=u;for(;a!=null;)t.unshift(a),{prev:a}=a;for(;r!=null;)t.push(r),{next:r}=r;return t}function yt(u,e){var t;return(t=u.attribs)===null||t===void 0?void 0:t[e]}function vt(u,e){return u.attribs!=null&&Object.prototype.hasOwnProperty.call(u.attribs,e)&&u.attribs[e]!=null}function wt(u){return u.name}function Et(u){let{next:e}=u;for(;e!==null&&!T(e);)({next:e}=e);return e}function At(u){let{prev:e}=u;for(;e!==null&&!T(e);)({prev:e}=e);return e}function pu(u){if(u.prev&&(u.prev.next=u.next),u.next&&(u.next.prev=u.prev),u.parent){let e=u.parent.children,t=e.lastIndexOf(u);t>=0&&e.splice(t,1)}u.next=null,u.prev=null,u.parent=null}function Tt(u,e){let t=e.prev=u.prev;t&&(t.next=e);let a=e.next=u.next;a&&(a.prev=e);let r=e.parent=u.parent;if(r){let c=r.children;c[c.lastIndexOf(u)]=e,u.parent=null}}function St(u,e){if(pu(e),e.next=null,e.parent=u,u.children.push(e)>1){let t=u.children[u.children.length-2];t.next=e,e.prev=t}else e.prev=null}function qt(u,e){pu(e);let{parent:t}=u,a=u.next;if(e.next=a,e.prev=u,u.next=e,e.parent=t,a){if(a.prev=e,t){let r=t.children;r.splice(r.lastIndexOf(a),0,e)}}else t&&t.children.push(e)}function Nt(u,e){if(pu(e),e.parent=u,e.prev=null,u.children.unshift(e)!==1){let t=u.children[1];t.prev=e,e.next=t}else e.next=null}function kt(u,e){pu(e);let{parent:t}=u;if(t){let a=t.children;a.splice(a.indexOf(u),0,e)}u.prev&&(u.prev.next=e),e.parent=t,e.prev=u.prev,e.next=u,u.prev=e}function uu(u,e,t=!0,a=1/0){return ne(u,Array.isArray(e)?e:[e],t,a)}function ne(u,e,t,a){let r=[],c=[Array.isArray(e)?e:[e]],i=[0];for(;;){if(i[0]>=c[0].length){if(i.length===1)return r;c.shift(),i.shift();continue}let n=c[0][i[0]++];if(u(n)&&(r.push(n),--a<=0))return r;t&&N(n)&&n.children.length>0&&(i.unshift(0),c.unshift(n.children))}}function Lt(u,e){return e.find(u)}function Mu(u,e,t=!0){let a=Array.isArray(e)?e:[e];for(let r=0;r0){let i=Mu(u,c.children,!0);if(i)return i}}return null}function se(u,e){return(Array.isArray(e)?e:[e]).some(t=>T(t)&&u(t)||N(t)&&se(u,t.children))}function Dt(u,e){let t=[],a=[Array.isArray(e)?e:[e]],r=[0];for(;;){if(r[0]>=a[0].length){if(a.length===1)return t;a.shift(),r.shift();continue}let c=a[0][r[0]++];T(c)&&u(c)&&t.push(c),N(c)&&c.children.length>0&&(r.unshift(0),a.unshift(c.children))}}var Bu={tag_name(u){return typeof u=="function"?e=>T(e)&&u(e.name):u==="*"?T:e=>T(e)&&e.name===u},tag_type(u){return typeof u=="function"?e=>u(e.type):e=>e.type===u},tag_contains(u){return typeof u=="function"?e=>M(e)&&u(e.data):e=>M(e)&&e.data===u}};function x0(u,e){return typeof e=="function"?t=>T(t)&&e(t.attribs[u]):t=>T(t)&&t.attribs[u]===e}function It(u,e){return t=>u(t)||e(t)}function de(u){let e=Object.keys(u).map(t=>{let a=u[t];return Object.prototype.hasOwnProperty.call(Bu,t)?Bu[t](a):x0(t,a)});return e.length===0?null:e.reduce(It)}function Ct(u,e){let t=de(u);return t?t(e):!0}function Rt(u,e,t,a=1/0){let r=de(u);return r?uu(r,e,t,a):[]}function _t(u,e,t=!0){return Array.isArray(e)||(e=[e]),Mu(x0("id",u),e,t)}function W(u,e,t=!0,a=1/0){return uu(Bu.tag_name(u),e,t,a)}function Mt(u,e,t=!0,a=1/0){return uu(x0("class",u),e,t,a)}function Bt(u,e,t=!0,a=1/0){return uu(Bu.tag_type(u),e,t,a)}function Pt(u){let e=u.length;for(;--e>=0;){let t=u[e];if(e>0&&u.lastIndexOf(t,e-1)>=0){u.splice(e,1);continue}for(let a=t.parent;a;a=a.parent)if(u.includes(a)){u.splice(e,1);break}}return u}var I;(function(u){u[u.DISCONNECTED=1]="DISCONNECTED",u[u.PRECEDING=2]="PRECEDING",u[u.FOLLOWING=4]="FOLLOWING",u[u.CONTAINS=8]="CONTAINS",u[u.CONTAINED_BY=16]="CONTAINED_BY"})(I||(I={}));function fe(u,e){let t=[],a=[];if(u===e)return 0;let r=N(u)?u:u.parent;for(;r;)t.unshift(r),r=r.parent;for(r=N(e)?e:e.parent;r;)a.unshift(r),r=r.parent;let c=Math.min(t.length,a.length),i=0;for(;is.indexOf(o)?n===e?I.FOLLOWING|I.CONTAINED_BY:I.FOLLOWING:n===u?I.PRECEDING|I.CONTAINS:I.PRECEDING}function Vt(u){return u=u.filter((e,t,a)=>!a.includes(e,t+1)),u.sort((e,t)=>{let a=fe(e,t);return a&I.PRECEDING?-1:a&I.FOLLOWING?1:0}),u}function xu(u){let e=Pu(Gt,u);return e?e.name==="feed"?Ut(e):Ot(e):null}function Ut(u){var e;let t=u.children,a={type:"atom",items:W("entry",t).map(i=>{var n;let{children:s}=i,d={media:oe(s)};k(d,"id","id",s),k(d,"title","title",s);let o=(n=Pu("link",s))===null||n===void 0?void 0:n.attribs.href;o&&(d.link=o);let x=G("summary",s)||G("content",s);x&&(d.description=x);let m=G("updated",s);return m&&(d.pubDate=new Date(m)),d})};k(a,"id","id",t),k(a,"title","title",t);let r=(e=Pu("link",t))===null||e===void 0?void 0:e.attribs.href;r&&(a.link=r),k(a,"description","subtitle",t);let c=G("updated",t);return c&&(a.updated=new Date(c)),k(a,"author","email",t,!0),a}function Ot(u){var e,t;let a=(t=(e=Pu("channel",u.children))===null||e===void 0?void 0:e.children)!==null&&t!==void 0?t:[],r={type:u.name.substr(0,3),id:"",items:W("item",u.children).map(i=>{let{children:n}=i,s={media:oe(n)};k(s,"id","guid",n),k(s,"title","title",n),k(s,"link","link",n),k(s,"description","description",n);let d=G("pubDate",n)||G("dc:date",n);return d&&(s.pubDate=new Date(d)),s})};k(r,"title","title",a),k(r,"link","link",a),k(r,"description","description",a);let c=G("lastBuildDate",a);return c&&(r.updated=new Date(c)),k(r,"author","managingEditor",a,!0),r}var Ht=["url","type","lang"],Ft=["fileSize","bitrate","framerate","samplingrate","channels","duration","height","width"];function oe(u){return W("media:content",u).map(e=>{let{attribs:t}=e,a={medium:t.medium,isDefault:!!t.isDefault};for(let r of Ht)t[r]&&(a[r]=t[r]);for(let r of Ft)t[r]&&(a[r]=parseInt(t[r],10));return t.expression&&(a.expression=t.expression),a})}function Pu(u,e){return W(u,e,!0,1)[0]}function G(u,e,t=!1){return hu(W(u,e,t,1)).trim()}function k(u,e,t,a,r=!1){let c=G(t,a,r);c&&(u[e]=c)}function Gt(u){return u==="rss"||u==="feed"||u==="rdf:RDF"}function be(u,e){let t=new P(void 0,e);return new H(t,e).end(u),t.root}function le(u,e){return be(u,e).children}function jt(u,e,t){let a=new P(r=>u(r,a.root),e,t);return new H(a,e)}function zt(u,e,t){let a=new P(u,e,t);return new H(a,e)}var Wt={xmlMode:!0};function Qt(u,e=Wt){return xu(le(u,e))}var R0={};ru(R0,{_compileToken:()=>l1,_compileUnsafe:()=>b1,aliases:()=>Uu,compile:()=>o1,default:()=>m1,filters:()=>au,is:()=>x1,prepareContext:()=>Ie,pseudos:()=>wu,selectAll:()=>Ce,selectOne:()=>p1});var D0=$(Q(),1);var b;(function(u){u.Attribute="attribute",u.Pseudo="pseudo",u.PseudoElement="pseudo-element",u.Tag="tag",u.Universal="universal",u.Adjacent="adjacent",u.Child="child",u.Descendant="descendant",u.Parent="parent",u.Sibling="sibling",u.ColumnCombinator="column-combinator"})(b||(b={}));var v;(function(u){u.Any="any",u.Element="element",u.End="end",u.Equals="equals",u.Exists="exists",u.Hyphen="hyphen",u.Not="not",u.Start="start"})(v||(v={}));var pe=/^[^\\#]?(?:\\(?:[\da-f]{1,6}\s?|.)|[\w\-\u00b0-\uFFFF])+/,Zt=/\\([\da-f]{1,6}\s?|(\s)|.)/gi,$t=new Map([[126,v.Element],[94,v.Start],[36,v.End],[42,v.Any],[33,v.Not],[124,v.Hyphen]]),Xt=new Set(["has","not","matches","is","where","host","host-context"]);function me(u){switch(u.type){case b.Adjacent:case b.Child:case b.Descendant:case b.Parent:case b.Sibling:case b.ColumnCombinator:return!0;default:return!1}}var Jt=new Set(["contains","icontains"]);function Yt(u,e,t){let a=parseInt(e,16)-65536;return a!==a||t?e:a<0?String.fromCharCode(a+65536):String.fromCharCode(a>>10|55296,a&1023|56320)}function mu(u){return u.replace(Zt,Yt)}function g0(u){return u===39||u===34}function xe(u){return u===32||u===9||u===10||u===12||u===13}function gu(u){let e=[],t=ge(e,`${u}`,0);if(t0&&t0&&me(a[a.length-1]))throw new Error("Did not expect successive traversals.")}function d(m){if(a.length>0&&a[a.length-1].type===b.Descendant){a[a.length-1].type=m;return}s(),a.push({type:m})}function o(m,h){a.push({type:b.Attribute,name:m,action:h,value:r(1),namespace:null,ignoreCase:"quirks"})}function x(){if(a.length&&a[a.length-1].type===b.Descendant&&a.pop(),a.length===0)throw new Error("Empty sub-selector");u.push(a)}if(c(0),e.length===t)return t;u:for(;t=0&&a>=1)):u.type===b.Pseudo&&(u.data?u.name==="has"||u.name==="contains"?a=0:Array.isArray(u.data)?(a=Math.min(...u.data.map(r=>Math.min(...r.map(ve)))),a<0&&(a=0)):a=2:a=3),a}var vu=$(Q(),1),u1=/[-[\]{}()*+?.,\\^$|#\s]/g;function we(u){return u.replace(u1,"\\$&")}var e1=new Set(["accept","accept-charset","align","alink","axis","bgcolor","charset","checked","clear","codetype","color","compact","declare","defer","dir","direction","disabled","enctype","face","frame","hreflang","http-equiv","lang","language","link","media","method","multiple","nohref","noresize","noshade","nowrap","readonly","rel","rev","rules","scope","scrolling","selected","shape","target","text","type","valign","valuetype","vlink"]);function Z(u,e){return typeof u.ignoreCase=="boolean"?u.ignoreCase:u.ignoreCase==="quirks"?!!e.quirksMode:!e.xmlMode&&e1.has(u.name)}var Ee={equals(u,e,t){let{adapter:a}=t,{name:r}=e,{value:c}=e;return Z(e,t)?(c=c.toLowerCase(),i=>{let n=a.getAttributeValue(i,r);return n!=null&&n.length===c.length&&n.toLowerCase()===c&&u(i)}):i=>a.getAttributeValue(i,r)===c&&u(i)},hyphen(u,e,t){let{adapter:a}=t,{name:r}=e,{value:c}=e,i=c.length;return Z(e,t)?(c=c.toLowerCase(),function(s){let d=a.getAttributeValue(s,r);return d!=null&&(d.length===i||d.charAt(i)==="-")&&d.substr(0,i).toLowerCase()===c&&u(s)}):function(s){let d=a.getAttributeValue(s,r);return d!=null&&(d.length===i||d.charAt(i)==="-")&&d.substr(0,i)===c&&u(s)}},element(u,e,t){let{adapter:a}=t,{name:r,value:c}=e;if(/\s/.test(c))return vu.default.falseFunc;let i=new RegExp(`(?:^|\\s)${we(c)}(?:$|\\s)`,Z(e,t)?"i":"");return function(s){let d=a.getAttributeValue(s,r);return d!=null&&d.length>=c.length&&i.test(d)&&u(s)}},exists(u,{name:e},{adapter:t}){return a=>t.hasAttrib(a,e)&&u(a)},start(u,e,t){let{adapter:a}=t,{name:r}=e,{value:c}=e,i=c.length;return i===0?vu.default.falseFunc:Z(e,t)?(c=c.toLowerCase(),n=>{let s=a.getAttributeValue(n,r);return s!=null&&s.length>=i&&s.substr(0,i).toLowerCase()===c&&u(n)}):n=>{var s;return!!(!((s=a.getAttributeValue(n,r))===null||s===void 0)&&s.startsWith(c))&&u(n)}},end(u,e,t){let{adapter:a}=t,{name:r}=e,{value:c}=e,i=-c.length;return i===0?vu.default.falseFunc:Z(e,t)?(c=c.toLowerCase(),n=>{var s;return((s=a.getAttributeValue(n,r))===null||s===void 0?void 0:s.substr(i).toLowerCase())===c&&u(n)}):n=>{var s;return!!(!((s=a.getAttributeValue(n,r))===null||s===void 0)&&s.endsWith(c))&&u(n)}},any(u,e,t){let{adapter:a}=t,{name:r,value:c}=e;if(c==="")return vu.default.falseFunc;if(Z(e,t)){let i=new RegExp(we(c),"i");return function(s){let d=a.getAttributeValue(s,r);return d!=null&&d.length>=c.length&&i.test(d)&&u(s)}}return i=>{var n;return!!(!((n=a.getAttributeValue(i,r))===null||n===void 0)&&n.includes(c))&&u(i)}},not(u,e,t){let{adapter:a}=t,{name:r}=e,{value:c}=e;return c===""?i=>!!a.getAttributeValue(i,r)&&u(i):Z(e,t)?(c=c.toLowerCase(),i=>{let n=a.getAttributeValue(i,r);return(n==null||n.length!==c.length||n.toLowerCase()!==c)&&u(i)}):i=>a.getAttributeValue(i,r)!==c&&u(i)}};var t1=new Set([9,10,12,13,32]),Ae=48,a1=57;function Te(u){if(u=u.trim().toLowerCase(),u==="even")return[2,0];if(u==="odd")return[2,1];let e=0,t=0,a=c(),r=i();if(e=Ae&&u.charCodeAt(e)<=a1;)d=d*10+(u.charCodeAt(e)-Ae),e++;return e===s?null:d}function n(){for(;ec<=t;if(e===0)return c=>c===t;if(e===1)return t<0?v0.default.trueFunc:c=>c>=t;let a=Math.abs(e),r=(t%a+a)%a;return e>1?c=>c>=t&&c%a===r:c=>c<=t&&c%a===r}function tu(u){return Se(Te(u))}var L=$(Q(),1);function Vu(u,e){return t=>{let a=e.getParent(t);return a!=null&&e.isTag(a)&&u(t)}}var au={contains(u,e,{adapter:t}){return function(r){return u(r)&&t.getText(r).includes(e)}},icontains(u,e,{adapter:t}){let a=e.toLowerCase();return function(c){return u(c)&&t.getText(c).toLowerCase().includes(a)}},"nth-child"(u,e,{adapter:t,equals:a}){let r=tu(e);return r===L.default.falseFunc?L.default.falseFunc:r===L.default.trueFunc?Vu(u,t):function(i){let n=t.getSiblings(i),s=0;for(let d=0;d=0&&!a(i,n[d]);d--)t.isTag(n[d])&&s++;return r(s)&&u(i)}},"nth-of-type"(u,e,{adapter:t,equals:a}){let r=tu(e);return r===L.default.falseFunc?L.default.falseFunc:r===L.default.trueFunc?Vu(u,t):function(i){let n=t.getSiblings(i),s=0;for(let d=0;d=0;d--){let o=n[d];if(a(i,o))break;t.isTag(o)&&t.getName(o)===t.getName(i)&&s++}return r(s)&&u(i)}},root(u,e,{adapter:t}){return a=>{let r=t.getParent(a);return(r==null||!t.isTag(r))&&u(a)}},scope(u,e,t,a){let{equals:r}=t;return!a||a.length===0?au.root(u,e,t):a.length===1?c=>r(a[0],c)&&u(c):c=>a.includes(c)&&u(c)},hover:w0("isHovered"),visited:w0("isVisited"),active:w0("isActive")};function w0(u){return function(t,a,{adapter:r}){let c=r[u];return typeof c!="function"?L.default.falseFunc:function(n){return c(n)&&t(n)}}}var wu={empty(u,{adapter:e}){return!e.getChildren(u).some(t=>e.isTag(t)||e.getText(t)!=="")},"first-child"(u,{adapter:e,equals:t}){if(e.prevElementSibling)return e.prevElementSibling(u)==null;let a=e.getSiblings(u).find(r=>e.isTag(r));return a!=null&&t(u,a)},"last-child"(u,{adapter:e,equals:t}){let a=e.getSiblings(u);for(let r=a.length-1;r>=0;r--){if(t(u,a[r]))return!0;if(e.isTag(a[r]))break}return!1},"first-of-type"(u,{adapter:e,equals:t}){let a=e.getSiblings(u),r=e.getName(u);for(let c=0;c=0;c--){let i=a[c];if(t(u,i))return!0;if(e.isTag(i)&&e.getName(i)===r)break}return!1},"only-of-type"(u,{adapter:e,equals:t}){let a=e.getName(u);return e.getSiblings(u).every(r=>t(u,r)||!e.isTag(r)||e.getName(r)!==a)},"only-child"(u,{adapter:e,equals:t}){return e.getSiblings(u).every(a=>t(u,a)||!e.isTag(a))}};function E0(u,e,t,a){if(t===null){if(u.length>a)throw new Error(`Pseudo-class :${e} requires an argument`)}else if(u.length===a)throw new Error(`Pseudo-class :${e} doesn't have any arguments`)}var Uu={"any-link":":is(a, area, link)[href]",link:":any-link:not(:visited)",disabled:`:is( + :is(button, input, select, textarea, optgroup, option)[disabled], + optgroup[disabled] > option, + fieldset[disabled]:not(fieldset[disabled] legend:first-of-type *) + )`,enabled:":not(:disabled)",checked:":is(:is(input[type=radio], input[type=checkbox])[checked], option:selected)",required:":is(input, select, textarea)[required]",optional:":is(input, select, textarea):not([required])",selected:"option:is([selected], select:not([multiple]):not(:has(> option[selected])) > :first-of-type)",checkbox:"[type=checkbox]",file:"[type=file]",password:"[type=password]",radio:"[type=radio]",reset:"[type=reset]",image:"[type=image]",submit:"[type=submit]",parent:":not(:empty)",header:":is(h1, h2, h3, h4, h5, h6)",button:":is(button, input[type=button])",input:":is(input, textarea, select, button)",text:"input:is(:not([type!='']), [type=text])"};var C=$(Q(),1);var S0={};function q0(u,e){return u===C.default.falseFunc?C.default.falseFunc:t=>e.isTag(t)&&u(t)}function N0(u,e){let t=e.getSiblings(u);if(t.length<=1)return[];let a=t.indexOf(u);return a<0||a===t.length-1?[]:t.slice(a+1).filter(e.isTag)}function T0(u){return{xmlMode:!!u.xmlMode,lowerCaseAttributeNames:!!u.lowerCaseAttributeNames,lowerCaseTags:!!u.lowerCaseTags,quirksMode:!!u.quirksMode,cacheResults:!!u.cacheResults,pseudos:u.pseudos,adapter:u.adapter,equals:u.equals}}var A0=(u,e,t,a,r)=>{let c=r(e,T0(t),a);return c===C.default.trueFunc?u:c===C.default.falseFunc?C.default.falseFunc:i=>c(i)&&u(i)},Ou={is:A0,matches:A0,where:A0,not(u,e,t,a,r){let c=r(e,T0(t),a);return c===C.default.falseFunc?u:c===C.default.trueFunc?C.default.falseFunc:i=>!c(i)&&u(i)},has(u,e,t,a,r){let{adapter:c}=t,i=T0(t);i.relativeSelector=!0;let n=e.some(o=>o.some(yu))?[S0]:void 0,s=r(e,i,n);if(s===C.default.falseFunc)return C.default.falseFunc;let d=q0(s,c);if(n&&s!==C.default.trueFunc){let{shouldTestNextSiblings:o=!1}=s;return x=>{if(!u(x))return!1;n[0]=x;let m=c.getChildren(x),h=o?[...m,...N0(x,c)]:m;return c.existsOne(d,h)}}return o=>u(o)&&c.existsOne(d,c.getChildren(o))}};function qe(u,e,t,a,r){var c;let{name:i,data:n}=e;if(Array.isArray(n)){if(!(i in Ou))throw new Error(`Unknown pseudo-class :${i}(${n})`);return Ou[i](u,n,t,a,r)}let s=(c=t.pseudos)===null||c===void 0?void 0:c[i],d=typeof s=="string"?s:Uu[i];if(typeof d=="string"){if(n!=null)throw new Error(`Pseudo ${i} doesn't have any arguments`);let o=gu(d);return Ou.is(u,o,t,a,r)}if(typeof s=="function")return E0(s,i,n,1),o=>s(o,n)&&u(o);if(i in au)return au[i](u,n,t,a);if(i in wu){let o=wu[i];return E0(o,i,n,2),x=>o(x,t,n)&&u(x)}throw new Error(`Unknown pseudo-class :${i}`)}function k0(u,e){let t=e.getParent(u);return t&&e.isTag(t)?t:null}function Ne(u,e,t,a,r){let{adapter:c,equals:i}=t;switch(e.type){case b.PseudoElement:throw new Error("Pseudo-elements are not supported by css-select");case b.ColumnCombinator:throw new Error("Column combinators are not yet supported by css-select");case b.Attribute:{if(e.namespace!=null)throw new Error("Namespaced attributes are not yet supported by css-select");return(!t.xmlMode||t.lowerCaseAttributeNames)&&(e.name=e.name.toLowerCase()),Ee[e.action](u,e,t)}case b.Pseudo:return qe(u,e,t,a,r);case b.Tag:{if(e.namespace!=null)throw new Error("Namespaced tag names are not yet supported by css-select");let{name:n}=e;return(!t.xmlMode||t.lowerCaseTags)&&(n=n.toLowerCase()),function(d){return c.getName(d)===n&&u(d)}}case b.Descendant:{if(t.cacheResults===!1||typeof WeakSet>"u")return function(d){let o=d;for(;o=k0(o,c);)if(u(o))return!0;return!1};let n=new WeakSet;return function(d){let o=d;for(;o=k0(o,c);)if(!n.has(o)){if(c.isTag(o)&&u(o))return!0;n.add(o)}return!1}}case"_flexibleDescendant":return function(s){let d=s;do if(u(d))return!0;while(d=k0(d,c));return!1};case b.Parent:return function(s){return c.getChildren(s).some(d=>c.isTag(d)&&u(d))};case b.Child:return function(s){let d=c.getParent(s);return d!=null&&c.isTag(d)&&u(d)};case b.Sibling:return function(s){let d=c.getSiblings(s);for(let o=0;oe.some(ke)))}var r1={type:b.Descendant},c1={type:"_flexibleDescendant"},i1={type:b.Pseudo,name:"scope",data:null};function n1(u,{adapter:e},t){let a=!!t?.every(r=>{let c=e.isTag(r)&&e.getParent(r);return r===S0||c&&e.isTag(c)});for(let r of u){if(!(r.length>0&&yu(r[0])&&r[0].type!==b.Descendant))if(a&&!r.some(ke))r.unshift(r1);else continue;r.unshift(i1)}}function Fu(u,e,t){var a;u.forEach(y0),t=(a=e.context)!==null&&a!==void 0?a:t;let r=Array.isArray(t),c=t&&(Array.isArray(t)?t:[t]);if(e.relativeSelector!==!1)n1(u,e,c);else if(u.some(s=>s.length>0&&yu(s[0])))throw new Error("Relative selectors are not allowed when the `relativeSelector` option is disabled");let i=!1,n=u.map(s=>{if(s.length>=2){let[d,o]=s;d.type!==b.Pseudo||d.name!=="scope"||(r&&o.type===b.Descendant?s[1]=c1:(o.type===b.Adjacent||o.type===b.Sibling)&&(i=!0))}return s1(s,e,c)}).reduce(d1,U.default.falseFunc);return n.shouldTestNextSiblings=i,n}function s1(u,e,t){var a;return u.reduce((r,c)=>r===U.default.falseFunc?U.default.falseFunc:Ne(r,c,e,t,Fu),(a=e.rootFunc)!==null&&a!==void 0?a:U.default.trueFunc)}function d1(u,e){return e===U.default.falseFunc||u===U.default.trueFunc?u:u===U.default.falseFunc||e===U.default.trueFunc?e:function(a){return u(a)||e(a)}}var Le=(u,e)=>u===e,f1={adapter:eu,equals:Le};function I0(u){var e,t,a,r;let c=u??f1;return(e=c.adapter)!==null&&e!==void 0||(c.adapter=eu),(t=c.equals)!==null&&t!==void 0||(c.equals=(r=(a=c.adapter)===null||a===void 0?void 0:a.equals)!==null&&r!==void 0?r:Le),c}function C0(u){return function(t,a,r){let c=I0(a);return u(t,c,r)}}var o1=C0(L0),b1=C0(Hu),l1=C0(Fu);function De(u){return function(t,a,r){let c=I0(r);typeof t!="function"&&(t=Hu(t,c,a));let i=Ie(a,c.adapter,t.shouldTestNextSiblings);return u(t,i,c)}}function Ie(u,e,t=!1){return t&&(u=h1(u,e)),Array.isArray(u)?e.removeSubsets(u):e.getChildren(u)}function h1(u,e){let t=Array.isArray(u)?u.slice(0):[u],a=t.length;for(let r=0;ru===D0.default.falseFunc||!e||e.length===0?[]:t.adapter.findAll(u,e)),p1=De((u,e,t)=>u===D0.default.falseFunc||!e||e.length===0?null:t.adapter.findOne(u,e));function x1(u,e,t){let a=I0(t);return(typeof e=="function"?e:L0(e,a))(u)}var m1=Ce;return Oe(g1);})(); diff --git a/Kanzen/KanzenEngine/Utils/ErrorDefinitions.swift b/Kanzen/KanzenEngine/Utils/ErrorDefinitions.swift new file mode 100644 index 00000000..55b18039 --- /dev/null +++ b/Kanzen/KanzenEngine/Utils/ErrorDefinitions.swift @@ -0,0 +1,68 @@ +// +// ErrorDefinitions.swift +// Kanzen +// +// Created by Dawud Osman on 12/05/2025. +// +import Foundation +enum ScriptExecutionError: Error,CustomStringConvertible { + case jsRuntimeError(String) // JavaScript runtime error with a message + case invalidReturnValue // The script returned an invalid value + case scriptLoadError(String) // Failed to load a script (e.g., file not found) + // Custom description for printing + var description: String { + switch self { + case .jsRuntimeError(let message): + return "JavaScript Runtime Error: \(message)" + case .invalidReturnValue: + return "Invalid Return Value from script" + case .scriptLoadError(let message): + return "Script Load Error: \(message)" + } + } +} +// Module Creation ERRORS +enum ModuleCreationError: Error,CustomStringConvertible { + case invalidScriptUrl(String) + case moduleAlreadyExists(String) + case invalidModuleName(String) + var description: String { + switch self { + case .moduleAlreadyExists(let message): + return "Module Already Exists: \(message)" + case .invalidModuleName(let message): + return "Invalid Module Name: \(message)" + case .invalidScriptUrl(let message): + return "Invalid Script URL: \(message)" + } + } +} +// LOADING MODULE ERRORS +enum ModuleLoadingError: Error,CustomStringConvertible { + case moduleNotFound(String) + case moduleDecodeError(String) + case missingScriptPath(String) + case scriptDownloadError(String) + case invalidScriptFormat(String) + + var description: String { + switch self + { + case.moduleNotFound(let message): + return "Module Not Found: \(message)" + case.moduleDecodeError(let message): + return "Module Decode Error: \(message)" + case.missingScriptPath(let message): + return "Missing Script Path: \(message)" + case.scriptDownloadError(let message): + return "Script Download Error: \(message)" + case.invalidScriptFormat(let message): + return "Invalid Script Format: \(message)" + } + + + + } +} + + diff --git a/Kanzen/KanzenEngine/Utils/Extensions/JavaScriptCore.swift b/Kanzen/KanzenEngine/Utils/Extensions/JavaScriptCore.swift new file mode 100644 index 00000000..017a234f --- /dev/null +++ b/Kanzen/KanzenEngine/Utils/Extensions/JavaScriptCore.swift @@ -0,0 +1,168 @@ +// +// JavaScriptCore.swift +// Kanzen +// +// Created by Dawud Osman on 13/05/2025. +// +import JavaScriptCore +import Foundation +extension JSContext +{ + func setupTimeOut() + { + // 2. Define `setTimeout` in Swift + let setTimeout: @convention(block) (JSValue, Double) -> Void = { callback, delay in + let delayTime = DispatchTime.now() + delay / 1000.0 // Convert ms to seconds + DispatchQueue.main.asyncAfter(deadline: delayTime) { + callback.call(withArguments: []) + } + } + // 3. Inject `setTimeout` into JSContext + self.setObject(setTimeout, forKeyedSubscript: "setTimeout" as (NSCopying & NSObjectProtocol)) + } + func setupBundle() + { + guard let jsPath = Bundle.main.path(forResource: "bundle", ofType: "js") + else{ + Logger.shared.log("bundle not found",type: "Error") + return + } + do { + let jsCode = try String(contentsOfFile: jsPath, encoding: .utf8) + self.evaluateScript(jsCode) + Logger.shared.log("bundle loaded successfully") + } catch { + Logger.shared.log("Error loading bundle.js: \(error)") + } + + } + func setUpConsole() + { + let consoleObject = JSValue(newObjectIn: self) + let consoleLogFunction: @convention(block) (String) -> Void = { + message in + Logger.shared.log(message,type: "Debug") + } + let consolePrintFunction: @convention(block) (JSValue) -> Void = { + message in + print(message) + } + + consoleObject?.setObject(consoleLogFunction, forKeyedSubscript: "log" as NSString) + consoleObject?.setObject(consolePrintFunction, forKeyedSubscript: "print" as NSString) + self.setObject(consoleObject, forKeyedSubscript: "console" as NSString) + } + func setUpFetch() + { + let fetch: @convention(block) (JSValue,JSValue) -> JSValue = { + jsUrl, jsOptions in + print("fetch Called") + guard let urlStr = jsUrl.toString(), let url = URL(string: urlStr) else + { + return JSValue(newErrorFromMessage: "Invalid URL", in: self) + } + print("url is \(urlStr)") + guard let promiseConstructor = self.objectForKeyedSubscript("Promise") else + { + fatalError("Promise constructor not found in JSContext") + } + let executor: @convention(block) (@escaping (JSValue) -> Void, @escaping (JSValue) -> Void) -> Void = { resolve, reject in + var request = URLRequest(url: url) + request.httpMethod = "GET" + if let options = jsOptions.toDictionary() as? [String: Any] + { + if let method = options["method"] as? String + { + request.httpMethod = method.uppercased() + } + if let headers = options["headers"] as? [String: String] + { + for (key,value) in headers + { + request.addValue(value, forHTTPHeaderField: key) + } + } + if let body = options["body"] as? String + { + let bodyData = body.data(using: .utf8) + request.httpBody = bodyData + } + } + + + let task = URLSession.shared.dataTask(with: request) { data, response, error in + + if let error = error + { + return reject(JSValue(newErrorFromMessage: error.localizedDescription, in: self)) + } + guard let httpResponse = response as? HTTPURLResponse + else + { + reject(JSValue(newErrorFromMessage: "No Response", in: self )) + return + } + let textFunc: @convention(block) () -> String = { + if let data = data + { + return String(data: data, encoding: .utf8) ?? "" + } + return "" + } + let jsonFunc: @convention(block) () -> JSValue = { + if let data = data { + do{ + let json = try JSONSerialization.jsonObject(with: data, options: []) + return JSValue(object: json, in: self) + } + catch + { + Logger.shared.log("JSON serialization failed",type:"Error") + } + } + return JSValue(newErrorFromMessage: "No Data", in: self) + + } + guard let textJs = JSValue(object: textFunc, in: self), + let jsonJs = JSValue(object: jsonFunc, in: self) + else + { + return reject(JSValue(newErrorFromMessage: "Failed to create JSValue", in: self)) + } + let responseObject: [String: Any] = [ + "status": httpResponse.statusCode, + "headers": httpResponse.allHeaderFields, + "text": textJs, + "json": jsonJs, + "data": data?.base64EncodedString() ?? "" + ] + + resolve(JSValue(object: responseObject, in: self)) + + } + task.resume() + + } + + + let promise = JSValue(newPromiseIn: self, fromExecutor: { resolve, reject in + executor( + { value in resolve?.call(withArguments: [value]) }, + { error in reject?.call(withArguments: [error]) } + ) + }) + + return promise ?? JSValue(newErrorFromMessage: "Promise not supported", in: self) + + } + + self.setObject(fetch, forKeyedSubscript: "fetch" as NSString) + } + func setUpJSEnvirontment() + { + setUpFetch() + setUpConsole() + setupBundle() + setupTimeOut() + } +} diff --git a/Kanzen/KanzenModule/Controllers/ModuleManager.swift b/Kanzen/KanzenModule/Controllers/ModuleManager.swift new file mode 100644 index 00000000..c3358aa7 --- /dev/null +++ b/Kanzen/KanzenModule/Controllers/ModuleManager.swift @@ -0,0 +1,179 @@ +// +// ModuleManager.swift +// Kanzen +// +// Created by Dawud Osman on 13/05/2025. +// +import Foundation +class ModuleManager: ObservableObject { + static let shared = ModuleManager() + @Published var modules: [ModuleDataContainer] = [] + private let fileManager = FileManager.default + private let modulesFileName: String = "modules.json" + private init() + { + createModuleFile() + // loadModules + loadModules() + // validate Modules + for module in modules { + validateModule(module){isValid in + if !isValid { + Logger.shared.log("Module \(module.moduleData.sourceName) is not valid", type: "Error") + } + } + } + print("Modules Called") + } + func saveModules() + { + DispatchQueue.main.async { + let url = ModuleManager.shared.getModulesFilePath() + guard let data = try? JSONEncoder().encode(self.modules) else {return} + try? data.write(to: url) + print("modules saved") + } + } + func addModules(_ moduleUrL:String, metaData: ModuleData) async throws -> Void + { + // check if module exists already + if modules.contains(where: {$0.moduleurl == moduleUrL}) + { + throw ModuleCreationError.moduleAlreadyExists("module already exists") + } + // validate and extra ModuleData (metaData) + + + let jsContent = try await validateJSfile(metaData.scriptURL) + let fileName = "\(UUID().uuidString).js" + let localUrl = getDocumentsDirectory().appendingPathComponent(fileName) + try jsContent.write(to: localUrl, atomically: true, encoding: .utf8) + let module = ModuleDataContainer( moduleData: metaData, localPath: fileName, moduleurl: moduleUrL) + DispatchQueue.main.async { + ModuleManager.shared.modules.append(module) + ModuleManager.shared.saveModules() + } + + } + func deleteModule(_ module: ModuleDataContainer) + { + let fileUrl = getDocumentsDirectory().appendingPathComponent(modulesFileName) + try? fileManager.removeItem(at: fileUrl) + ModuleManager.shared.modules.removeAll(where: {$0.id == module.id}) + ModuleManager.shared.saveModules() + + } + func getModulesFilePath() -> URL + { + getDocumentsDirectory().appendingPathComponent(modulesFileName) + } + func getModuleScript(module: ModuleDataContainer) throws -> String{ + let localUrl = getDocumentsDirectory().appendingPathComponent(module.localPath) + return try String(contentsOf: localUrl, encoding: .utf8) + } + func createModuleFile() + { + let fileUrl = getDocumentsDirectory().appendingPathComponent(modulesFileName) + if(!fileManager.fileExists(atPath: fileUrl.path)) + { + do { + try "[]".write(to:fileUrl,atomically: true,encoding: .utf8) + Logger.shared.log("Created new modules file",type: "Info") + } + catch { + Logger.shared.log("Failed to create modules file: \(error.localizedDescription)", type: "Error") + } + } + } + func getDocumentsDirectory() -> URL { + let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) + return paths[0] + } + func loadModules() + { + let fileUrl = getDocumentsDirectory().appendingPathComponent(modulesFileName) + do + { + let data = try Data(contentsOf: fileUrl) + let decodedModules = try JSONDecoder().decode([ModuleDataContainer].self, from: data) + modules = decodedModules + + } + catch + { + modules = [] + Logger.shared.log(ModuleLoadingError.moduleDecodeError(error.localizedDescription).localizedDescription,type: "Error") + + } + + } + func validateJSfile(_ url: String) async throws -> String + { + + + guard let scriptUrl = URL(string: url) else { + throw ModuleLoadingError.invalidScriptFormat("Invalid Script Url") + + } + + let (scriptData,_) = try await URLSession.shared.data(from: scriptUrl) + guard let jsContent = String(data:scriptData, encoding: .utf8) else + { + throw ModuleLoadingError.invalidScriptFormat("Invalid Script Format") + } + + return jsContent + + + } + func validateModuleUrl(_ urlString: String) async throws -> ModuleData + { + do{ + guard let url = URL(string: urlString) else + { + throw ModuleCreationError.invalidScriptUrl("invalid Script URL") + } + let (rawData,_) = try await URLSession.shared.data(from: url) + let metaData = try JSONDecoder().decode(ModuleData.self, from: rawData) + return metaData + } + catch{ + throw error + + } + } + func validateModule(_ module: ModuleDataContainer, completion: @escaping (Bool) -> Void) + { Task + { + do { + + let fileUrl = getDocumentsDirectory().appendingPathComponent(module.localPath) + + let validFilePath = fileManager.fileExists(atPath: fileUrl.path) + + if(!validFilePath) + { + Logger.shared.log("downloading js file for: \(module.moduleData.sourceName)") + let validJsContent = try await validateJSfile(module.moduleData.scriptURL) + try validJsContent.write(to:fileUrl,atomically: true, encoding: .utf8 ) + } + completion(true) + + + } + catch { + Logger.shared.log("Module Validation Error: (\(module.moduleData.sourceName)) \(error.localizedDescription)",type: "Error") + completion(false) + + } + + } + } + func getModule(_ moduleId: UUID) -> ModuleDataContainer? + { + return ModuleManager.shared.modules.first { $0.id == moduleId } + } + + + +} diff --git a/Kanzen/KanzenModule/Models/Module.swift b/Kanzen/KanzenModule/Models/Module.swift new file mode 100644 index 00000000..55119418 --- /dev/null +++ b/Kanzen/KanzenModule/Models/Module.swift @@ -0,0 +1,45 @@ +// +// Module.swift +// Kanzen +// +// Created by Dawud Osman on 13/05/2025. +// +import Foundation +struct ModuleData: Codable, Equatable +{ + + + let sourceName: String + let author: Author + let iconURL: String + let version: String + let language: String + let scriptURL: String + + struct Author: Codable, Equatable + { + let name: String + let iconURL: String + } +} +struct ModuleDataContainer: Codable, Identifiable,Hashable +{ + let id: UUID + let moduleData: ModuleData + let localPath: String + let moduleurl: String + var isActive: Bool + init(id:UUID = UUID(), moduleData: ModuleData, localPath: String, moduleurl: String, isActive: Bool = false) { + self.id = id + self.moduleData = moduleData + self.localPath = localPath + self.moduleurl = moduleurl + self.isActive = isActive + } + func hash(into hasher: inout Hasher) { + hasher.combine(id) + } + static func == (lhs: ModuleDataContainer, rhs: ModuleDataContainer) -> Bool { + return lhs.id == rhs.id + } +} diff --git a/Kanzen/Models/Settings.swift b/Kanzen/Models/Settings.swift new file mode 100644 index 00000000..15b20857 --- /dev/null +++ b/Kanzen/Models/Settings.swift @@ -0,0 +1,65 @@ +// +// Settings.swift +// Luna +// +// Created by Dawud Osman on 17/11/2025. +// +import SwiftUI +// helper Class & Enums +enum Appearance: String, CaseIterable, Identifiable { + case system, light, dark + + var id: String { self.rawValue } +} +class Settings: ObservableObject { + @Published var accentColor: Color { + didSet { + saveAccentColor(accentColor) + } + } + @Published var selectedAppearance: Appearance { + didSet { + UserDefaults.standard.set(selectedAppearance.rawValue, forKey: "selectedAppearance") + updateAppearance() + } + } + + init() { + if let colorData = UserDefaults.standard.data(forKey: "accentColor"), + let uiColor = try? NSKeyedUnarchiver.unarchivedObject(ofClass: UIColor.self, from: colorData) { + self.accentColor = Color(uiColor) + } else { + self.accentColor = .accentColor + } + if let appearanceRawValue = UserDefaults.standard.string(forKey: "selectedAppearance"), + let appearance = Appearance(rawValue: appearanceRawValue) { + self.selectedAppearance = appearance + } else { + self.selectedAppearance = .system + } + updateAppearance() + } + + private func saveAccentColor(_ color: Color) { + + let uiColor = UIColor(color) + do { + let colorData = try NSKeyedArchiver.archivedData(withRootObject: uiColor, requiringSecureCoding: false) + UserDefaults.standard.set(colorData, forKey: "accentColor") + } catch { + Logger.shared.log("Failed to save accent color: \(error.localizedDescription)") + } + } + + func updateAppearance() { + guard let windowScene = UIApplication.shared.connectedScenes.first as? UIWindowScene else { return } + switch selectedAppearance { + case .system: + windowScene.windows.first?.overrideUserInterfaceStyle = .unspecified + case .light: + windowScene.windows.first?.overrideUserInterfaceStyle = .light + case .dark: + windowScene.windows.first?.overrideUserInterfaceStyle = .dark + } + } +} diff --git a/Kanzen/Models/favouriteManager.swift b/Kanzen/Models/favouriteManager.swift new file mode 100644 index 00000000..f7f7c19b --- /dev/null +++ b/Kanzen/Models/favouriteManager.swift @@ -0,0 +1,93 @@ +// +// favouriteManager.swift +// Luna +// +// Created by Dawud Osman on 17/11/2025. +// + +// +// favouriteManager.swift +// Kanzen +// +// Created by Dawud Osman on 18/10/2025. +// +import SwiftUI +import CoreData +class FavouriteManager: ObservableObject { + static let shared = FavouriteManager() + let container: NSPersistentContainer + init() { + container = NSPersistentContainer(name: "ContentModel") + container.loadPersistentStores{ description, error in + if let error = error { + fatalError("Unable to load Core Data store: \(error)") + } + else + { + print("Favourite Manager init success") + } + } + } + func addFavourite(module: ModuleDataContainer?, content: Manga) { + print("add favourite called") + let newFavourite = createFavouriteEntity(module: module, content: content) + } + func removeFavourite(moduleId: UUID, contentId: String) + { + + let context = container.viewContext + + let fetchRequest: NSFetchRequest = MangaData.fetchRequest() as! NSFetchRequest + fetchRequest.predicate = NSPredicate(format: "sourceId == %@ AND mangaId == %@", moduleId as CVarArg, contentId) + do { + let contentsToDelete = try context.fetch(fetchRequest) + for contentToDelete in contentsToDelete { + context.delete(contentToDelete) + } + try context.save() + } + catch { + print("error deleting favourites: \(error) ") + } + + print("remove favourite Called") + } + func removeAllModuleFavourites(module: ModuleDataContainer){} + func createFavouriteEntity(module: ModuleDataContainer?, content: Manga) + { + print("create favourite called") + if let module = module + { + let context = container.viewContext + let newContent = MangaData(context: context) + newContent.title = content.title + newContent.imageURL = content.imageURL + newContent.mangaId = content.mangaId + newContent.sourceId = module.id + do { + try context.save() + print("✅ Favourite saved successfully!") + } catch { + print("❌ Failed to save favourite: \(error)") + } + + return + } + } + func isFavourite(moduleId: UUID, contentId: String) -> Bool + { + let context = container.viewContext + + let fetchRequest: NSFetchRequest = MangaData.fetchRequest() as! NSFetchRequest + fetchRequest.predicate = NSPredicate(format: "sourceId == %@ AND mangaId == %@", moduleId as CVarArg, contentId) + do { + let count = try context.count(for: fetchRequest) + return count > 0 + } + catch{ + print("Error finding favourite: \(error)") + return false + } + + } +} diff --git a/Kanzen/Models/mangaData.swift b/Kanzen/Models/mangaData.swift new file mode 100644 index 00000000..1fe653bd --- /dev/null +++ b/Kanzen/Models/mangaData.swift @@ -0,0 +1,34 @@ +// +// mangaData.swift +// Luna +// +// Created by Dawud Osman on 17/11/2025. +// +// +// mangaData.swift +// Kanzen +// +// Created by Dawud Osman on 12/10/2025. +// +import SwiftUI +import CoreData + +struct Manga: Identifiable { + let id: UUID = UUID() + let title: String + let imageURL: String + let mangaId: String + var parentModule: ModuleDataContainer? + +} + + +// official mangaData +class MangaData: NSManagedObject { + @NSManaged var sourceId : UUID + @NSManaged var mangaId : String + @NSManaged var title: String? + @NSManaged var imageURL: String? + @NSManaged var synopsis: String? + +} diff --git a/Kanzen/Models/pageData.swift b/Kanzen/Models/pageData.swift new file mode 100644 index 00000000..0fb78803 --- /dev/null +++ b/Kanzen/Models/pageData.swift @@ -0,0 +1,111 @@ +// +// pageData.swift +// Luna +// +// Created by Dawud Osman on 17/11/2025. +// +// +// pageData.swift +// Kanzen +// +// Created by Dawud Osman on 15/07/2025. +// +import SwiftUI +import Foundation +import Kingfisher +enum ChapterPosition +{ + case prev + case curr + case next +} + +struct PageData: Identifiable, Equatable { + let id: UUID = UUID() + let content: String + init (content:String) + { + + self.content = content + } + + var body: chapterView { + chapterView(page: self, index: "0") + } + static func == (lhs: PageData, rhs: PageData) -> Bool { + lhs.id == rhs.id + } + + +} +struct Chapters: Identifiable +{ + let id: UUID = UUID() + let language: String + var chapters: [Chapter] +} +struct Chapter: Identifiable +{ + let id: UUID = UUID() + let chapterNumber: String + let idx: Int + let chapterData: [ ChapterData]? +} +struct ChapterData: Identifiable +{ + let id: UUID = UUID() + var scanlationGroup: String = "" + var title: String = "" + let params: Any? + init?(dict: [String:Any]) + { + print("dicts are") + print(dict) + guard let scanlationGroup = dict["scanlation_group"] as? String, let params = dict["id"] else { return nil } + + self.scanlationGroup = scanlationGroup + self.params = params + + } +} + + + +struct chapterView: View { + let page: PageData + let index: String + + + + var body: some View { + + if page.content == "CHAPTER_END" + { + Text("Chapter \(index) End") + .frame(maxWidth: .infinity) + .clipped() + + + + } + else{ + if let url = URL(string: page.content) + { + + KFImage(url) + .placeholder{ + CircularLoader(progress: 0) + } + .resizable() + .scaledToFit() + .frame(width: UIScreen.main.bounds.width) + .background(Color.black) + + + } + } + + + + } +} diff --git a/Kanzen/Models/readerManager.swift b/Kanzen/Models/readerManager.swift new file mode 100644 index 00000000..2c523334 --- /dev/null +++ b/Kanzen/Models/readerManager.swift @@ -0,0 +1,447 @@ +// +// readerManager.swift +// Luna +// +// Created by Dawud Osman on 17/11/2025. +// + +import SwiftUI +import Kingfisher + + + +class readerManager: ObservableObject { + @Published var chapters: [Chapter]? + @Published var selectedChapter: Chapter? + @Published var index: Int + @Published var currChapter: [PageData] + @Published var prevChapter: [PageData] + @Published var nextChapter: [PageData] + @AppStorage("readingMode") var readingModeRaw: Int = 0 + var pagePrefetcher: ImagePrefetcher? + var readingMode: ReadingMode { + ReadingMode(rawValue: readingModeRaw) ?? .LTR + } + var changeIndex: Bool = false + + var kanzen : KanzenEngine + // Cached controllers - only recreated when data changes + var currControllers: [UIViewController]? + var prevControllers: [UIViewController]? +var nextControllers: [UIViewController]? + + // task + private var currTask: Task? + + // Task storage for loadPages operations + private var loadPagesTasks: [ChapterPosition: Task] = [:] + + + + var currRange: ClosedRange { + //print("currChapter type: \(type(of: currChapter))") + //print("currChapter value: \(currChapter)") + + guard currChapter is [PageData] else { + print("ERROR: currChapter is not [PageData]!") + return 0...0 + } + if currChapter.count > 0 { + return 0...CGFloat(currChapter.count - 1) + } else { + return 0...CGFloat(0) + } + } + + init(index: Int = 0, currChapter: [PageData] = [], prevChapter: [PageData] = [], nextChapter: [PageData] = [], shiftChapterLeft: @escaping () -> Void = {}, shiftChapterRight: @escaping () -> Void = {}, fetchPrev: @escaping () -> Void = {}, fetchNext: @escaping () -> Void = {}, kanzen: KanzenEngine,chapters: [Chapter]?, selectedChapter: Chapter?) { + self.index = index + self.currChapter = currChapter + self.prevChapter = prevChapter + self.nextChapter = nextChapter + self.kanzen = kanzen + self.chapters = chapters + self.selectedChapter = selectedChapter + } + func initChapters(){ + // resetState + resetState() + + } + func resetState() + { + cancelAllLoadPagesTasks() + prevChapter = [] + currChapter = [] + nextChapter = [] + currControllers = nil + prevControllers = nil + nextControllers = nil + if let selectedChapter = selectedChapter, let chapters = chapters + { + if let currSources = selectedChapter.chapterData, currSources.count > 0 + { + let currParams = currSources[0].params + loadPages(params: currParams, position: .curr) + } + let idx = selectedChapter.idx + // fetch Prev Images + + if idx > 0 + { + let prevChapter = chapters[idx - 1] + if let prevSources = prevChapter.chapterData, prevSources.count > 0 + { + let prevParams = prevSources[0].params + loadPages(params: prevParams, position: .prev) + + } + + } + if idx < chapters.count - 1 + { + let nextChapters = chapters[idx + 1] + if let nextSources = nextChapters.chapterData, nextSources.count > 0 + { + let nextParams = nextSources[0].params + loadPages(params: nextParams, position: .next) + } + } + + + } + } + // Cancel all loadPages tasks + private func cancelAllLoadPagesTasks() { + for (_, task) in loadPagesTasks { + task.cancel() + } + loadPagesTasks.removeAll() + } + + // Cancel specific loadPages task + private func cancelLoadPagesTask(for position: ChapterPosition) { + + loadPagesTasks[position]?.cancel() + loadPagesTasks.removeValue(forKey: position) + } + + // Setter Functions + func setIndex(_ index: Int) { + self.index = index + } + + func setCurrChapter(_ currChapter: [PageData]) { + self.currChapter = currChapter + generateCurrControllers() + } + + func setPrevChapter(_ prevChapter: [PageData]) { + self.prevChapter = prevChapter + generatePrevControllers() + } + + func setNextChapter(_ nextChapter: [PageData]) { + self.nextChapter = nextChapter + generateNextControllers() + } + func generateCurrControllers() + { + currControllers = currChapter.map { UIHostingController(rootView: $0.body) } + if let selectedChapter = selectedChapter{ + let transistionView: any View = chapterView(page: PageData(content: "CHAPTER_END"), index: selectedChapter.chapterNumber) + currControllers = currChapter.map { UIHostingController(rootView: $0.body) } + [UIHostingController(rootView: AnyView( transistionView))] + } + + } + func generatePrevControllers() + { + + prevControllers = prevChapter.map { UIHostingController(rootView: $0.body) } + if let selectedChapter = selectedChapter, let chapters = chapters, selectedChapter.idx > 0 { + let transistionView: any View = chapterView(page: PageData(content: "CHAPTER_END"), index: chapters[selectedChapter.idx-1].chapterNumber) + prevControllers = prevChapter.map { UIHostingController(rootView: $0.body) } + [UIHostingController(rootView: AnyView( transistionView))] + + } + } + func generateNextControllers() + { + nextControllers = nextChapter.map { UIHostingController(rootView: $0.body) } + if let selectedChapter = selectedChapter, let chapters = chapters, selectedChapter.idx < chapters.count - 1 { + let transistionView: any View = chapterView(page: PageData(content: "CHAPTER_END"), index: chapters[selectedChapter.idx + 1].chapterNumber) + nextControllers = nextChapter.map { UIHostingController(rootView: $0.body) } + [UIHostingController(rootView: AnyView( transistionView))] + + } + } + func shiftLeft() { + // Cancel next chapter loading since it's no longer needed + cancelLoadPagesTask(for: .next) + if let currChapter = selectedChapter, let idx = currChapter.idx as? Int, let chapters = chapters, currChapter.idx == 0 + { + print("End of chapters reached - no more chapters to load") + return + } + + //shift Controllers + nextControllers = currControllers + currControllers = prevControllers + prevControllers = nil + + // Shift chapters (this will trigger didSet and invalidate controllers) + nextChapter = currChapter + currChapter = prevChapter + prevChapter = [] + + // Now shift the controllers to maintain references + // What was "current" becomes "next" + // What was "previous" becomes "current" + // "Previous" becomes empty + shiftChapterLeft() + index = currChapter.count - 1 + print("Shifted left - controllers moved") + } + + func shiftRight() { + // Cancel prev chapter loading since it's no longer needed + cancelLoadPagesTask(for: .prev) + if let currChapter = selectedChapter, let idx = currChapter.idx as? Int, let chapters = chapters, currChapter.idx == chapters.count - 1 + { + print("End of chapters reached - no more chapters to load") + return + } + + prevControllers = currControllers + currControllers = nextControllers + nextControllers = nil + // Shift chapters (this will trigger didSet and invalidate controllers) + prevChapter = currChapter + currChapter = nextChapter + nextChapter = [] + + + // What was "current" becomes "previous" + // What was "next" becomes "current" + // "Next" becomes empty + + index = 0 + shiftChapterRight() + + print("Shifted right - controllers moved") + } + + func getIndex() -> Int { + return index + } + + // Optional: Force refresh all controllers (useful for debugging) + func refreshAllControllers() { + print("Force refreshing all controllers") + + } + func findControllers(currView: UIViewController) -> Bool { + if let currControllers = currControllers, currControllers.contains(currView) { + return true + } + if let prevControllers = prevControllers, prevControllers.contains(currView) { + return true + } + if let nextControllers = nextControllers, nextControllers.contains(currView) { + return true + } + return false + } + func fetchTask(bool: Bool, completion: @escaping (() -> Void ) = {}) + { + currTask?.cancel() + if bool { + // Also cancel the actual loading task for next + cancelLoadPagesTask(for: .next) + currTask = Task + { + fetchNext(completion: completion) + } + + + } + else + { // Also cancel the actual loading task for prev + cancelLoadPagesTask(for: .prev) + currTask = Task + { + fetchPrev(completion: completion) + } + + + } + } + + + func loadPages(params: Any,position: ChapterPosition, completion: @escaping () -> Void = {}){ + print("params are") + print(params) + // Cancel any existing task for this position + cancelLoadPagesTask(for: position) + + // Create new task and store it + loadPagesTasks[position] = Task { @MainActor in + do { + // Check for cancellation before starting + try Task.checkCancellation() + + print("Loading chapter \(position)...") + + // Convert callback to async/await + let result = await withCheckedContinuation { continuation in + self.kanzen.getChapterImages(params: params) { result in + continuation.resume(returning: result) + } + } + + // Check for cancellation after network call + try Task.checkCancellation() + + + + if let result = result { + var pages = result.map{PageData(content: $0)} + pages = pages + print("pages is ") + print("\(pages)") + // Check for cancellation before updating UI + try Task.checkCancellation() + + // Update UI on main thread (already on MainActor) + switch position + { + case .prev: + print("prev is called") + self.setPrevChapter(pages) + + case .next: + print("next is called") + self.setNextChapter(pages) + + + case .curr: + print("curr is called") + self.setCurrChapter(pages) + + + } + print("successfully set \(position) chapter") + completion() + } + + // Remove completed task from storage + self.loadPagesTasks.removeValue(forKey: position) + + } catch { + if error is CancellationError { + print("Loading chapter \(position) was cancelled") + } else { + print("Error loading chapter \(position): \(error)") + } + // Remove failed/cancelled task from storage + self.loadPagesTasks.removeValue(forKey: position) + } + } + } + // shiftCurrChapter + func shiftChapterLeft() + { + if let currChapter = selectedChapter, let chapters = chapters + { + let idx = currChapter.idx + if idx > 0 + { + selectedChapter = chapters[idx - 1] + print("shift chapter Left successfull") + } + } + } + func shiftChapterRight() + { + if let currChapter = selectedChapter, let chapters = chapters + { + let idx = currChapter.idx + if idx < chapters.count - 1 + { + selectedChapter = chapters[idx + 1] + print("shift Chapter Right successfull") + } + } + } + func fetchPrev(completion: @escaping () -> Void = {}) + { + print("fetchPrev called") + if let selectedChapter = selectedChapter, let chapters = chapters { + let idx = selectedChapter.idx + if idx > 0 { + let prevChapter = chapters[idx - 1] + if let prevSources = prevChapter.chapterData, prevSources.count > 0 + { + let prevParams = prevSources[0].params + loadPages(params: prevParams, position: .prev,completion: completion) + + } + + } + } + } + func fetchNext(completion: @escaping () -> Void = {}) + { if let selectedChapter = selectedChapter, let chapters = chapters { + let idx = selectedChapter.idx + if idx < chapters.count - 1 { + let nextChapters = chapters[idx + 1] + if let nextSources = nextChapters.chapterData, nextSources.count > 0 + { + let nextParams = nextSources[0].params + loadPages(params: nextParams, position: .next,completion: completion) + } + + } + } + + } + + func preloadAdjacentPages() + { + pagePrefetcher?.stop() + var pagesURLs: [URL] = [] + + if index < currChapter.count - 1 { + let pageUrl = URL(string: currChapter[index+1].content) + if let pageUrl = pageUrl { + pagesURLs.append(pageUrl) + } + } + if index < currChapter.count - 2 { + let pageUrl = URL(string: currChapter[index+2].content) + if let pageUrl = pageUrl { + pagesURLs.append(pageUrl) + } + } + else if nextChapter.count > 0 { + let pageUrl = URL(string: nextChapter.first!.content) + if let pageUrl = pageUrl { + pagesURLs.append(pageUrl) + } + } + + if index > 0 { + let pageUrl = URL(string: currChapter[index - 1 ].content) + if let pageUrl = pageUrl { + pagesURLs.append(pageUrl) + } + } + else if prevChapter.count > 0 { + let pageUrl = URL(string: prevChapter.last!.content) + if let pageUrl = pageUrl { + pagesURLs.append(pageUrl) + } + } + + pagePrefetcher = ImagePrefetcher(urls: pagesURLs) + pagePrefetcher?.start() + + } +} diff --git a/Kanzen/Models/readingMode.swift b/Kanzen/Models/readingMode.swift new file mode 100644 index 00000000..7dc5ea2e --- /dev/null +++ b/Kanzen/Models/readingMode.swift @@ -0,0 +1,35 @@ +// +// readingMode.swift +// Luna +// +// Created by Dawud Osman on 17/11/2025. +// +// +// readingMode.swift +// Kanzen +// +// Created by Dawud Osman on 04/10/2025. +// +enum ReadingMode: Int,CaseIterable,Identifiable { + case LTR = 0 + case RTL = 1 + case WEBTOON = 2 + case VERTICAL = 3 + + var id: Int{ rawValue} + var title: String { + switch self { + case .LTR: return "Left to Right" + case .RTL: return "Right to Left" + case .WEBTOON: return "Webtoon" + case .VERTICAL: return "Vertical" + } + } +} + +enum pageViewMode: Int,CaseIterable { + case LTR = 0 + case RTL = 1 + case Vertical = 2 + +} diff --git a/Kanzen/Views/Browse/browseView.swift b/Kanzen/Views/Browse/browseView.swift new file mode 100644 index 00000000..a0898ade --- /dev/null +++ b/Kanzen/Views/Browse/browseView.swift @@ -0,0 +1,10 @@ +import SwiftUI +struct BrowseView: View { + @EnvironmentObject var moduleManager: ModuleManager + let kanzen: KanzenEngine = KanzenEngine() + var body: some View { + NavigationView(){ + KanzenModuleView() + }.environmentObject(kanzen) + } +} diff --git a/Kanzen/Views/Browse/kanzenModuleView.swift b/Kanzen/Views/Browse/kanzenModuleView.swift new file mode 100644 index 00000000..48df3cf4 --- /dev/null +++ b/Kanzen/Views/Browse/kanzenModuleView.swift @@ -0,0 +1,397 @@ +// +// ModuleView.swift +// Kanzen +// +// Created by Dawud Osman on 15/05/2025. +// +import SwiftUI +import Kingfisher +struct KanzenModuleView: View { + @AppStorage("selectedModuleId") private var selectedModuleId: String? + @EnvironmentObject var moduleManager : ModuleManager + @State var copySelectedModule: String? = nil + private var fallbackCircle: some View { + Circle() + .fill(Color.black) + .frame(width: 60, height: 60) + } + func metaDataInfo(title: String, value: String ) -> some View { + VStack(alignment: .leading, spacing: 4) { + Text(title) + .font(.subheadline) + .foregroundColor(.secondary) + Text(value) + .font(.body) + .lineLimit(2) + } + + + } + func deleteItems(at offsets: IndexSet) { + for index in offsets + { + moduleManager.deleteModule(moduleManager.modules[index]) + } + } + var body: some View { + + ZStack{ + Color(UIColor.systemGroupedBackground) + .ignoresSafeArea() + Form + { + if(moduleManager.modules.isEmpty) + { + VStack(spacing:10){ + Image(systemName: "plus.app").font(.largeTitle).foregroundColor(.secondary) + Text("No Modules Found").font(.headline) + Text("Tap the \"+\" button to add a module").font(.caption).foregroundColor(.secondary) + }.padding().frame(maxWidth:.infinity) + } + else + { Section{ + ForEach(moduleManager.modules){item in + let selectedModule = copySelectedModule == item.moduleurl + var row = ZStack{ + + HStack + { + + circularImage(from: item.moduleData.iconURL, size: 50) + .padding(.trailing,10) + HStack{Divider()} + + VStack(alignment: .leading) + { + HStack(alignment: .bottom, spacing: 4){ + Text(item.moduleData.sourceName) + .font(.headline) + .foregroundColor(.primary) + Text("v\(item.moduleData.version)") + .font(.subheadline) + .foregroundColor(.secondary) + } + + Text("Author: \(item.moduleData.author.name)") + .font(.subheadline) + .foregroundColor(.secondary) + Text("Language: \(item.moduleData.language)") + .font(.subheadline) + .foregroundColor(.secondary) + }.padding(.horizontal,20) + Spacer() + + + } + .padding(.leading,10) + .padding(.trailing,10) + .animation(.spring(response: 0.3, dampingFraction: 0.4), value: selectedModule) + .scaleEffect(selectedModule ? 1.02 : 1.0) + } + let destination = KanzenSearchView(module: item) + NavigationLink(destination: destination){ + row + .allowsHitTesting(true) + .buttonStyle(.plain) + } + .allowsHitTesting(true) + .buttonStyle(.plain) + .simultaneousGesture( + LongPressGesture(minimumDuration: 0.3) + .onEnded { _ in + UIPasteboard.general.string = item.moduleurl + withAnimation(.spring) { + copySelectedModule = item.moduleurl + } + UIImpactFeedbackGenerator(style: .light).impactOccurred() + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + withAnimation(.snappy) { + copySelectedModule = nil + } + } + } + ) + .padding(.leading,5) + .padding(.trailing,5) + .padding(.top,10) + .padding(.bottom,10) + .listRowInsets(EdgeInsets()) + .compositingGroup() // create a drawing group + + .frame(maxWidth: .infinity,alignment: .center) + .shadow(color: .black.opacity(selectedModule ? 0.4 : 0.2), radius: selectedModule ? 10 : 4) + + .clipShape(RoundedRectangle(cornerRadius: 12)) + + .overlay( + RoundedRectangle(cornerRadius: 12) + .stroke(Color.accentColor.opacity(selectedModule ? 1 : 0), lineWidth: selectedModule ? 5 : 0) + ) + + + + } + + + .onDelete(perform: deleteItems) + } + + + + + + } + + } + + // Remove default list styling + //.scrollContentBackground(.hidden) // hides default form background + .background(Color(.systemGroupedBackground)) + .navigationTitle("Modules") + .navigationBarTitleDisplayMode(.inline) + .frame(maxWidth: .infinity,alignment: .center) + .padding(.top,10) + .overlay + { + if copySelectedModule != nil + { + Text("Copied to Clipboard") + + .foregroundColor(.accentColor) + + .padding() + .background(Color(.systemBackground).cornerRadius(20)) + .padding(.bottom) + .shadow(radius: 3) + .transition(.move(edge: .top)) + .frame(maxHeight: .infinity, alignment: .top) + } + } + .toolbar{ + ToolbarItem(placement: .navigationBarTrailing) + { + Button(action: { + addModuleAlert() + }){Image(systemName: "plus").resizable().frame(width: 20, height: 20)} + } + } + } + + + } + func circularImage(from urlString: String, size: CGFloat) -> some View { + Group { + if let url = URL(string: urlString) { + KFImage(url) + .placeholder { + ProgressView() + } + .cancelOnDisappear(true) + .resizable() + .aspectRatio(contentMode: .fill) + .background { + // Optional: placeholder background or error state + } + } else { + Circle().fill(Color.black) + } + } + + + .frame(width: size, height: size) + .clipShape(Circle()) + .shadow(radius: 5) + + } + func addModule(fetchedModule: ModuleData, url: String, dismiss: @escaping () -> Void) + { + Task{ + do{ + try await moduleManager.addModules(url, metaData: fetchedModule) + } + catch { + Logger.shared.log((error.localizedDescription),type: "Error") + } + dismiss() + } + } + func popupContent(fetchedModule: ModuleData?,url: String,width: CGFloat,height: CGFloat, dismiss: @escaping () -> Void) -> some View { + + ZStack{ + if let moduleData = fetchedModule + { + VStack(spacing:25){ + VStack(spacing:15){ + circularImage(from: moduleData.iconURL, size: 120) + Text(moduleData.sourceName) + .font(.system(size: 28, weight: .bold)) + .multilineTextAlignment(.center) + } + Divider() + HStack(spacing:10){ + circularImage(from: moduleData.author.iconURL, size: 60) + VStack(alignment: .leading,spacing:4) + { + Text(moduleData.author.name).font(.headline) + Text("Author").font(.subheadline).foregroundColor(.secondary) + } + HStack(){Divider().frame(maxHeight: 125)} + .padding(.horizontal) + VStack(alignment: .leading, spacing: 10){ + metaDataInfo(title: "Version", value: moduleData.version) + metaDataInfo(title: "Language" , value: moduleData.language) + metaDataInfo(title: "Script URL", value: moduleData.scriptURL) + }.padding(.horizontal) + Spacer() + } + .padding(.horizontal) + .frame(maxHeight: 150,alignment: .center) + + Divider() + Spacer() + VStack() + { + Button(action : { + addModule(fetchedModule: moduleData,url: url,dismiss: dismiss) + } ) + { + HStack{ + Image(systemName: "plus.circle.fill") + Text("add module") + } + } + .tint(.accentColor) + .font(.headline) + .foregroundColor(.white) + .frame(maxWidth: .infinity) + .padding() + .background( + RoundedRectangle(cornerRadius: 15) + .fill(Color.accentColor) + ) + .padding(.horizontal) + Button(action : { + dismiss() + }) + { + Text("Cancel") + .foregroundColor((Color.accentColor)) + .padding(.top, 10) + } + }.padding(.bottom, 20) + }.padding(.top).frame(maxWidth: .infinity,alignment: .top) + + } + + }.padding(.top).frame(maxWidth: .infinity,alignment: .top) + .clipped() + } + + func fetchModule(url: String) -> Void + { + let screenBounds = UIScreen.main.bounds + let width = screenBounds.width + let height = screenBounds.height + validFetchedModule(url){metaData in + DispatchQueue.main.async { + if let metaData = metaData + { + + var hostingController: UIHostingController? = nil + + let content = popupContent( + fetchedModule: metaData,url: url, + width: width, + height: height, + dismiss: { + hostingController?.dismiss(animated: true) + } + ) + + hostingController = UIHostingController(rootView: AnyView(content)) + + + if let topVC = getTopViewController(), let hc = hostingController { + topVC.present(hc, animated: true) + } + + + } + else{ + let alert = UIAlertController(title: "Failed to Add Module", + message: "The provided Module URL is invalid", + preferredStyle: .alert) + alert.addAction(UIAlertAction(title: "OK", style: .default)) + // Present it from the top view controller + if let topVC = getTopViewController() { + topVC.present(alert, animated: true) + } + } + + } + + } + + } + // + func validFetchedModule(_ urlString: String, completion: @escaping (ModuleData?) -> Void) { + Task { + do{ + let metaData = try await moduleManager.validateModuleUrl(urlString) + + + completion(metaData) + } + catch { + Logger.shared.log(error.localizedDescription,type: "Error") + completion(nil) + } + } + } + func addModuleAlert() + { + let alert = UIAlertController(title: "Add Module", message: "Enter Module Name", preferredStyle: .alert) + + alert.addTextField { textField in + textField.placeholder = "https://real.url/module.json" + } + alert.addAction(UIAlertAction(title: "Add", style: .default, handler: {_ in + if let url = alert.textFields?.first?.text, !url.isEmpty { + displayFetchedContent(url: url) + } + })) + alert.addAction(UIAlertAction(title: "Cancel", style: .cancel, handler: nil)) + // Present alert directly from this top View Controler + if let topVC = getTopViewController() { + topVC.present(alert, animated: true, completion: nil) + } + } + //display content from fetchedUrl + func displayFetchedContent(url: String) + { + self.fetchModule(url:url) + + } + + // returns visible viewController to display Alert + func getTopViewController(base: UIViewController? = UIApplication.shared.connectedScenes + .compactMap { $0 as? UIWindowScene } + .first?.windows + .first(where: { $0.isKeyWindow })?.rootViewController) -> UIViewController? { + + if let nav = base as? UINavigationController { + return getTopViewController(base: nav.visibleViewController) + } + + if let tab = base as? UITabBarController, let selected = tab.selectedViewController { + return getTopViewController(base: selected) + } + + if let presented = base?.presentedViewController { + return getTopViewController(base: presented) + } + + return base + } + +} diff --git a/Kanzen/Views/Library/Library.swift b/Kanzen/Views/Library/Library.swift new file mode 100644 index 00000000..e7b4b9cb --- /dev/null +++ b/Kanzen/Views/Library/Library.swift @@ -0,0 +1,68 @@ +// +// LibraryView.swift +// Kanzen +// +// Created by Dawud Osman on 22/05/2025. +// +import SwiftUI +import CoreData +struct KanzenLibraryView: View { + @EnvironmentObject var favouriteManager: FavouriteManager + @EnvironmentObject var moduleManager : ModuleManager + @FetchRequest( + entity: MangaData.entity(), sortDescriptors: [NSSortDescriptor(keyPath: \MangaData.title, ascending: true)] + ) var favouriteRequest : FetchedResults + @State var cellWidth: CGFloat = 150 + private var columnCount: Int { + let screenWidth = UIScreen.main.bounds.width + return Int(screenWidth/(cellWidth+10)) + } + var favouriteList : [MangaData]{ + if !favouriteRequest.isEmpty{ + return Array(favouriteRequest) + } + else{ + return [] + } + } + var body: some View { + NavigationView + { + ZStack{ + if !favouriteList.isEmpty{ + ScrollView{ + LazyVGrid(columns: Array(repeating: GridItem(.fixed(cellWidth),spacing: 10), count: columnCount), spacing: 10) { + ForEach(favouriteList, id: \.self) + { + item in + let currItem : MangaData = item + let currModuleId : UUID = currItem.sourceId + let currModule : ModuleDataContainer? = ModuleManager.shared.getModule(currModuleId) + NavigationLink(destination: favouriteViewWrapper(favouriteContent: currItem,currModule: currModule) ) {contentCell(title: (item.title ?? ""), urlString: (item.imageURL ?? ""), width: cellWidth)} + .contextMenu{ + Button("Remove from Favourites"){ + removeFavourite(item: currItem) + + } + } + } + } + } + } + else{ + Text("Empty Favourites :( Add some to your favourites") + } + } + .navigationTitle("Favourites") + .navigationBarTitleDisplayMode(.inline) + } + + + + + + } + func removeFavourite(item: MangaData){ + FavouriteManager.shared.removeFavourite(moduleId: item.sourceId,contentId: item.mangaId) + } +} diff --git a/Kanzen/Views/MainMenu.swift b/Kanzen/Views/MainMenu.swift new file mode 100644 index 00000000..2f1f97c9 --- /dev/null +++ b/Kanzen/Views/MainMenu.swift @@ -0,0 +1,24 @@ +// +// MainMenu.swift +// Luna +// +// Created by Dawud Osman on 17/11/2025. +// +import SwiftUI +struct KanzenMenu: View { + let kanzen = KanzenEngine(); + var body: some View { + TabView{ + KanzenLibraryView() + .tabItem { + Label("Library", systemImage: "books.vertical") + } + BrowseView().tabItem { + Label("Browse",systemImage: "list.bullet") + } + KanzenSettingsView() + .tabItem{ Label("Settings", systemImage: "gear")} + + } + } +} diff --git a/Kanzen/Views/Reader/Paged/pagedViewController.swift b/Kanzen/Views/Reader/Paged/pagedViewController.swift new file mode 100644 index 00000000..b031cf50 --- /dev/null +++ b/Kanzen/Views/Reader/Paged/pagedViewController.swift @@ -0,0 +1,208 @@ +import SwiftUI +import UIKit + +struct pageReader: UIViewControllerRepresentable { + @ObservedObject var reader_manager: readerManager + var pageViewConfig: pageViewMode + + func makeCoordinator() -> Coordinator { + return Coordinator(reader_manager: reader_manager,pageViewConfig: pageViewConfig) + } + + func makeUIViewController(context: Context) -> UIPageViewController { + print("makeUI called") + let navigationOrientation: UIPageViewController.NavigationOrientation + switch pageViewConfig { + case .LTR: + navigationOrientation = .horizontal + case .RTL: + navigationOrientation = .horizontal + case .Vertical: + navigationOrientation = .vertical + } + let controller = UIPageViewController( + transitionStyle: .scroll, + navigationOrientation: navigationOrientation + ) + controller.dataSource = context.coordinator + controller.delegate = context.coordinator + // Force a slight delay to ensure controllers are ready + print("index is \(reader_manager.getIndex())") + DispatchQueue.main.async { + if self.reader_manager.currControllers == nil{ + self.reader_manager.generateCurrControllers() + } + if self.reader_manager.nextControllers == nil{ + self.reader_manager.generateNextControllers() + } + if self.reader_manager.prevControllers == nil{ + self.reader_manager.generatePrevControllers() + } + let index = self.reader_manager.getIndex() + if let currControllers = self.reader_manager.currControllers, + !currControllers.isEmpty, + index >= 0, + index < currControllers.count { + controller.setViewControllers([currControllers[index]], direction: .forward, animated: false) + print("Set initial controller at index \(index)") + } + else{ + print("failed to set initial controller") + print("is currController empty? \( self.reader_manager.currControllers?.isEmpty)") + print("index < currController count ? \(index < (self.reader_manager.currControllers?.count ?? 0))") + } + } + + + + return controller + } + + func updateUIViewController(_ controller: UIPageViewController, context: Context) { + print("updateCalled") + + let index = reader_manager.getIndex() + + if let currControllers = reader_manager.currControllers, index >= 0 && index < currControllers.count { + if (reader_manager.currChapter != context.coordinator.currChapter) || (reader_manager.changeIndex == true) { + print("update conditions met") + context.coordinator.currChapter = reader_manager.currChapter + context.coordinator.currControllers = currControllers + context.coordinator.currIdx = index + controller.setViewControllers([currControllers[index]], direction: .forward, animated: false) + if reader_manager.changeIndex == true { + reader_manager.changeIndex = false + } + // prefetch Images + reader_manager.preloadAdjacentPages() + + } + + } + + } + + class Coordinator: NSObject, UIPageViewControllerDataSource, UIPageViewControllerDelegate { + + func navigateForward(index: Int) -> UIViewController? + { + if index < currControllers.count - 1 { + return currControllers[index + 1] + } + if let nextControllers = reader_manager.nextControllers,!nextControllers.isEmpty { + return nextControllers.first + } + print("next Empty") + return nil + } + func navigateBackward(index: Int) -> UIViewController?{ + if index > 0 { + return currControllers[index - 1] + } + if let prevControllers = reader_manager.prevControllers, !prevControllers.isEmpty + { + return prevControllers.last + } + print("prevEmpty") + + return nil + } + func pageViewController(_ pageViewController: UIPageViewController, viewControllerBefore viewController: UIViewController) -> UIViewController? { + guard let index = currControllers.firstIndex(of: viewController) else { + print("find before Controlelr instead") + print(reader_manager.findControllers(currView: viewController)) + return nil + } + switch pageViewConfig { + case .LTR: + return navigateBackward(index: index) + case .RTL: + return navigateForward(index: index) + case .Vertical: + return navigateBackward(index: index) + } + } + + func pageViewController(_ pageViewController: UIPageViewController, viewControllerAfter viewController: UIViewController) -> UIViewController? { + guard let index = currControllers.firstIndex(of: viewController) else { + print("find after Controlelr instead") + print(reader_manager.findControllers(currView: viewController)) + return nil + } + switch pageViewConfig { + case .LTR: + return navigateForward(index: index) + case .RTL: + return navigateBackward(index: index) + case .Vertical: + return navigateForward(index: index) + } + } + func pageViewController(_ pageViewController: UIPageViewController, willTransitionTo pendingViewControllers: [UIViewController]) { + transitioning = true + print("Will transition - blocking updates") + } + func pageViewController(_ pageViewController: UIPageViewController, didFinishAnimating finished: Bool, previousViewControllers: [UIViewController], transitionCompleted completed: Bool) { + if !completed || !finished { + + } + transitioning = false + print("transition & animation finished") + guard let vC = pageViewController.viewControllers?.first else { + return + } + guard let index = currControllers.firstIndex(of: vC) else { + + if let nextControllers = reader_manager.nextControllers,nextControllers.contains(vC){ + print("found in next") + + reader_manager.shiftRight() + currChapter = reader_manager.currChapter + currControllers = reader_manager.currControllers ?? [] + reader_manager.fetchTask(bool: true) + + return + } + else if let prevControllers = reader_manager.prevControllers, prevControllers.contains(vC){ + print("found in prev") + DispatchQueue.main.async{} + reader_manager.shiftLeft() + currChapter = reader_manager.currChapter + currControllers = reader_manager.currControllers ?? [] + reader_manager.fetchTask(bool: false) + + return + } + print("find lost Controlelr instead") + print(reader_manager.findControllers(currView: vC)) + + return + } + print("found in curr ") + print("index is \(index)") + currIdx = index + reader_manager.setIndex(index) + print("reader Manager Index is \(reader_manager.getIndex())") + print("coOrdinator Index is \(currIdx)") + + // prefetch Images + reader_manager.preloadAdjacentPages() + + } + @ObservedObject var reader_manager: readerManager + var currChapter: [PageData] + var currControllers: [UIViewController] + var currIdx: Int = 0 + var transitioning: Bool = false + var pageViewConfig: pageViewMode + init(reader_manager: readerManager, pageViewConfig: pageViewMode) { + self.reader_manager = reader_manager + self.currChapter = reader_manager.currChapter + self.currControllers = reader_manager.currControllers ?? [] + self.currIdx = reader_manager.getIndex() + self.pageViewConfig = pageViewConfig + } + } + +} + diff --git a/Kanzen/Views/Reader/WebToon/webToonViewController.swift b/Kanzen/Views/Reader/WebToon/webToonViewController.swift new file mode 100644 index 00000000..ebebbe6b --- /dev/null +++ b/Kanzen/Views/Reader/WebToon/webToonViewController.swift @@ -0,0 +1,597 @@ +// +// WebtoonView.swift +// Kanzen +// +// Created by Dawud Osman on 01/09/2025. +// +import SwiftUI +import Kingfisher + +struct WebtoonView: UIViewRepresentable { + @ObservedObject var reader_manager: readerManager + + func makeCoordinator() -> Coordinator { + Coordinator(reader_manager: reader_manager) + } + + func makeUIView(context: Context) -> UICollectionView { + let layout = UICollectionViewFlowLayout() + layout.scrollDirection = .vertical + layout.minimumInteritemSpacing = 0 + layout.minimumLineSpacing = 0 + + let collectionView = UICollectionView(frame: .zero, collectionViewLayout: layout) + collectionView.backgroundColor = .black + collectionView.dataSource = context.coordinator + collectionView.delegate = context.coordinator + collectionView.register(ChapterCollectionViewCell.self, forCellWithReuseIdentifier: ChapterCollectionViewCell.reuseIdentifier) + + return collectionView + } + + func updateUIView(_ uiView: UICollectionView, context: Context) { + //print("updateUIVIEW called") + if context.coordinator.currChapter != reader_manager.currChapter { + print("diff CurrChapter") + context.coordinator.reader_manager = reader_manager + context.coordinator.currChapter = reader_manager.currChapter + context.coordinator.chapters = [reader_manager.currChapter] + context.coordinator.imageSizes = [[:]] // Clear cached sizes + uiView.reloadData() + uiView.collectionViewLayout.invalidateLayout() + uiView.layoutIfNeeded() + context.coordinator.reader_manager = reader_manager + context.coordinator.currChapter = reader_manager.currChapter + context.coordinator.chapters = [reader_manager.currChapter] + } + + if reader_manager.changeIndex, let sectionIdx = context.coordinator.chapters.firstIndex(of: reader_manager.currChapter){ + print("Change index called && currChapter in chapters") + let pathItem = IndexPath(item: reader_manager.index, section: sectionIdx ) + uiView.scrollToItem(at: pathItem, at: UICollectionView.ScrollPosition.centeredVertically, animated: false) + if reader_manager.changeIndex == true { + reader_manager.changeIndex = false + } + } + + + + } + + class Coordinator: NSObject, UICollectionViewDataSource, UICollectionViewDelegate, UICollectionViewDelegateFlowLayout { + + // Cache image sizes to avoid recalculating + var imageSizes: [[Int: CGSize]] = [] + var loadingPrevious = false + var loadingNext = false + + init(reader_manager: readerManager) { + self.reader_manager = reader_manager + self.chapters.append(reader_manager.currChapter) + self.currChapter = reader_manager.currChapter + imageSizes.append([:]) + } + + func getCurrentpagePath(collectionView: UICollectionView, position: ScreenPosition = .mid) -> IndexPath? { + let value : CGFloat = switch position + { + case .mid: collectionView.bounds.height / 2 + case .bottom: collectionView.bounds.height + case .top: 0 + } + + let currentPoint = CGPoint(x: collectionView.contentOffset.x,y: collectionView.contentOffset.y + value ) + return collectionView.indexPathForItem(at: currentPoint) + } + func scrollViewDidScroll(_ scrollView: UIScrollView) { + if let collectionView = scrollView as? UICollectionView { + //print("=== DEBUG INFO ===") + //print("Chapters Count: \(chapters.count)") + + + guard + let chapterIdx = chapters.firstIndex(of: currChapter) + else { + print("Curr Chapter not found") + print("Curr Chapter cnt: \(currChapter.count)") + return + } + let midPath = getCurrentpagePath(collectionView: collectionView,position: .mid) + let midIdx = midPath?.section ?? 0 + //print("midPath Idx: \(String(describing: midIdx))") + //print("currChapter Idx: \(String(describing: chapterIdx)) ") + reader_manager.setIndex(midPath?.item ?? 0) + if midIdx == chapterIdx { + return + } + if chapterIdx > 0 && midIdx < chapterIdx { + print("shift Left") + + self.reader_manager.shiftLeft() + loadingPrevious = false + + // sync currChapter and reader_manager.currChapter + // More robust sync + if midIdx >= 0 && midIdx < chapters.count { + self.reader_manager.currChapter = chapters[midIdx] + self.currChapter = self.reader_manager.currChapter + } + + } + else if chapterIdx < chapters.count - 1 && midIdx > chapterIdx + { + print("shift Right") + self.reader_manager.shiftRight() + loadingNext = false + // sync currChapter and reader_manager.currChapter + // More robust sync + if midIdx >= 0 && midIdx < chapters.count { + self.reader_manager.currChapter = chapters[midIdx] + self.currChapter = self.reader_manager.currChapter + } + + } + print("==================") + + } + } + + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + if let collectionView = scrollView as? UICollectionView { + let visibleIndexPaths = collectionView.indexPathsForVisibleItems + if !loadingPrevious { + if visibleIndexPaths.contains(IndexPath(item:0, section:0)) + { + print("First cell is VISIBLE adding prev chapters") + + if reader_manager.prevChapter.count == 0 { + loadingNext = true + self.reader_manager.fetchTask(bool: false){ + print("completion handler called") + + self.prependChapter(collectionView: collectionView) + + } + } + else { + print("nextChap is not empty") + prependChapter(collectionView: collectionView) + } + + + + } + } + + let bottomPath = getCurrentpagePath(collectionView: collectionView,position: .bottom) + if !loadingNext { + + if bottomPath == nil || bottomPath?.section == chapters.count - 1 && bottomPath?.item == chapters[chapters.count - 1].count - 1 { + print("bottom path (section, idx) is (\(bottomPath?.section),\(bottomPath?.item)") + if reader_manager.nextChapter.count == 0 { + loadingNext = true + self.reader_manager.fetchTask(bool: true){ + print("completion handler called") + + self.appendChapter(collectionView: collectionView) + + } + } + else { + print("nextChap is not empty") + appendChapter(collectionView: collectionView) + } + } + } + + } + print("SCROLLING AS STOPPED") + } + //get height + func getHeightForSection(_ section: Int, collectionView: UICollectionView) -> CGFloat { + var totalHeight: CGFloat = 0 + let layout = collectionView.collectionViewLayout as! UICollectionViewFlowLayout + for item in 0.. 0 + { + print("prevChap > 0 && loadingNext == false") + loadingPrevious = true + + // 🔧 FIX: Disable animations to prevent jumping + CATransaction.begin() + CATransaction.setDisableActions(true) + UIView.setAnimationsEnabled(false) + + // Store current offset and content size + let oldOffset = collectionView.contentOffset + let oldContentSize = collectionView.collectionViewLayout.collectionViewContentSize + + + chapters.insert(reader_manager.prevChapter, at: 0) + imageSizes.insert([:], at: 0) + + collectionView.performBatchUpdates({ + collectionView.insertSections(IndexSet(integer: 0)) + }, completion: { _ in + // Calculate new offset + let newContentSize = collectionView.collectionViewLayout.collectionViewContentSize + let heightDiff = newContentSize.height - oldContentSize.height + let newOffset = CGPoint(x: oldOffset.x, y: oldOffset.y + heightDiff) + + // Set new offset without animation + collectionView.setContentOffset(newOffset, animated: false) + + // 🔧 FIX: Re-enable animations and reset loading flag + if self.chapters.count > 3 { + // 1. First update your data source + let lastSectionIndex = self.chapters.count - 1 + self.chapters.removeLast() + self.imageSizes.removeLast() // Also remove corresponding cached data + + // 2. Then update the UI + collectionView.performBatchUpdates({ + collectionView.deleteSections(IndexSet(integer: lastSectionIndex)) + }, completion: { completed in + if completed { + print("First section removed successfully") + + } + self.loadingPrevious = false + UIView.setAnimationsEnabled(true) + CATransaction.commit() + + }) + } + else { + self.loadingPrevious = false + UIView.setAnimationsEnabled(true) + CATransaction.commit() + } + + }) + + } + } + // append Chapter + func appendChapter(collectionView: UICollectionView){ + print("append Called") + if reader_manager.nextChapter.count > 0 + { + print("nextChap > 0 && loadingNext == false") + // append next Chapter + loadingNext = true + // 🔧 FIX: Disable animations to prevent jumping + CATransaction.begin() + CATransaction.setDisableActions(true) + UIView.setAnimationsEnabled(false) + + // Store current offset and content size + let oldOffset = collectionView.contentOffset + let oldContentSize = collectionView.collectionViewLayout.collectionViewContentSize + let removedSectionHeight = getHeightForSection(0, collectionView: collectionView) + if chapters.count >= 3 { + // Sliding window: replace first chapter with new one + chapters.removeFirst() + chapters.append(reader_manager.nextChapter) + imageSizes.removeFirst() + imageSizes.append([:]) + + collectionView.performBatchUpdates({ + collectionView.deleteSections(IndexSet(integer: 0)) + collectionView.insertSections(IndexSet(integer: 2)) + }, completion: { _ in + // 🔧 FIX: Adjust offset by the difference in content size + + let adjustedOffset = CGPoint(x: oldOffset.x, y: max(0, oldOffset.y - removedSectionHeight)) + + collectionView.setContentOffset(adjustedOffset, animated: false) + + + UIView.setAnimationsEnabled(true) + CATransaction.commit() + self.loadingNext = false + }) + } else { + // Simple append: just add new chapter + chapters.append(reader_manager.nextChapter) + imageSizes.append([:]) + + collectionView.performBatchUpdates({ + collectionView.insertSections(IndexSet(integer: self.chapters.count - 1)) + }, completion: { _ in + UIView.setAnimationsEnabled(true) + CATransaction.commit() + self.loadingNext = false + }) + } + + print("sucessfully added") + } + + + + } + + + func numberOfSections(in collectionView: UICollectionView) -> Int { + chapters.count + } + + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + chapters[section].count + } + + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + // Don't reveal image here - let it happen after resize + // print("cell section \(indexPath.section) - item \(indexPath.item) displayed ; number of sections \(chapters.count)") + } + + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ChapterCollectionViewCell.reuseIdentifier, for: indexPath) as? ChapterCollectionViewCell else { + fatalError("Could not dequeue cell") + } + + print("cellForItemAt section \(indexPath.section) - item \(indexPath.item)") + print("chapters count \(chapters.count)") + if chapters.count >= 3 { + print("last chapter count \(chapters[2].count)") + } + let rootView = chapters[indexPath.section][indexPath.item].body + cell.set(rootView: rootView, coordinator: self, indexPath: indexPath) + + return cell + } + + func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { + let width = collectionView.bounds.width + + // If we have the cached size, use it + if let cachedSize = imageSizes[indexPath.section][indexPath.item] { + let aspectRatio = cachedSize.height / cachedSize.width + return CGSize(width: width, height: width * aspectRatio) + } + + // Default size while image loads + return CGSize(width: width, height: 400) + } + + func updateImageSize(for indexPath: IndexPath, size: CGSize, collectionView: UICollectionView, isCached: Bool) { + print("cell section \(indexPath.section) - item \(indexPath.item) updated ; number of sections \(chapters.count)") + imageSizes[indexPath.section][indexPath.item] = size + + // 🔧 FIX: Only update layout if this is a new image that needs resizing + if !isCached { + DispatchQueue.main.async { + UIView.performWithoutAnimation { + collectionView.performBatchUpdates({ + collectionView.reloadItems(at: [indexPath]) + }) { completed in + collectionView.layoutIfNeeded() + if completed, let cell = collectionView.cellForItem(at: indexPath) as? ChapterCollectionViewCell, cell.indexPath == indexPath { + cell.revealImage() + } + } + } + } + } else { + // 🔧 FIX: For cached images, just reveal immediately without layout updates + if let cell = collectionView.cellForItem(at: indexPath) as? ChapterCollectionViewCell, cell.indexPath == indexPath { + cell.revealImage() + } + } + } + + var reader_manager: readerManager + var chapters: [[PageData]] = [] + var currChapter: [PageData] + } +} + +// Updated cell that calculates size dynamically +class ChapterCollectionViewCell: UICollectionViewCell { + static let reuseIdentifier = "ChapterCell" + private let imageView = UIImageView() + private var hostingController : UIHostingController! + private var coordinator: WebtoonView.Coordinator? + var indexPath: IndexPath? + private let hostingContainer = UIView() + + + // Add a unique identifier for each cell configuration + private var currentLoadingTask: UUID? + + override init(frame: CGRect) { + super.init(frame: frame) + setupImageView() + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + // 🔧 FIX: Modified prepareForReuse to prevent flicker + override func prepareForReuse() { + super.prepareForReuse() + + // Cancel any ongoing image loading + imageView.kf.cancelDownloadTask() + + // Reset state properly + currentLoadingTask = nil + + // 🔧 FIX: DON'T reset image or visibility state here - let set() method handle it + // This prevents the flicker when cells are reused during batch updates + + // Clear references + coordinator = nil + indexPath = nil + } + + private func setupImageView() { + print("🏗️ setupImageView called") + + // Only setup once - check if already setup + if !hostingContainer.subviews.isEmpty { + return + } + + // progress bar + // 1️⃣ Add container to contentView + hostingContainer.translatesAutoresizingMaskIntoConstraints = false + contentView.addSubview(hostingContainer) + NSLayoutConstraint.activate([ + hostingContainer.topAnchor.constraint(equalTo: contentView.topAnchor), + hostingContainer.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + hostingContainer.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + hostingContainer.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) + ]) + + // 2️⃣ Add hostingController inside container + hostingController = UIHostingController(rootView: CircularLoader(progress: 0)) + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + hostingController.view.backgroundColor = .clear + hostingController.view.clipsToBounds = false + hostingController.view.isOpaque = false + hostingContainer.addSubview(hostingController.view) + NSLayoutConstraint.activate([ + hostingController.view.topAnchor.constraint(equalTo: hostingContainer.topAnchor), + hostingController.view.bottomAnchor.constraint(equalTo: hostingContainer.bottomAnchor), + hostingController.view.leadingAnchor.constraint(equalTo: hostingContainer.leadingAnchor), + hostingController.view.trailingAnchor.constraint(equalTo: hostingContainer.trailingAnchor) + ]) + + // image view + imageView.contentMode = .scaleAspectFill + imageView.clipsToBounds = true + imageView.translatesAutoresizingMaskIntoConstraints = false + imageView.isHidden = true + contentView.addSubview(imageView) + + NSLayoutConstraint.activate([ + imageView.topAnchor.constraint(equalTo: contentView.topAnchor), + imageView.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + imageView.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + imageView.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) + ]) + } + + // 🔧 FIX: Smart loading state management + func set(rootView: chapterView, coordinator: WebtoonView.Coordinator, indexPath: IndexPath) { + self.coordinator = coordinator + self.indexPath = indexPath + + // Create a unique task identifier for this configuration + let taskId = UUID() + self.currentLoadingTask = taskId + + // 🔧 FIX: Check if image is already cached + let url = URL(string: rootView.page.content) + let isCached = url != nil && ImageCache.default.isCached(forKey: url!.absoluteString) + + if isCached { + // 🔧 FIX: Image is cached, show it immediately without loading view + imageView.isHidden = false + hostingController.view.isHidden = true + } else { + // 🔧 FIX: Image needs to load, show loading view + imageView.isHidden = true + hostingController.view.isHidden = false + } + + guard let url = URL(string: rootView.page.content) else { return } + + // 🔧 FIX: Set the image with options to prevent flicker + imageView.kf.setImage( + with: url, + options: [ + .transition(.none), // Disable fade transition to prevent flicker + .cacheOriginalImage + ] + ) { [weak self] result in + guard let self = self, + let coordinator = self.coordinator, + let indexPath = self.indexPath, + self.currentLoadingTask == taskId else { + // Cell was reused or task was cancelled + return + } + + switch result { + case .success(let value): + let imageSize = value.image.size + + // If size is not cached, update it and trigger resize + if coordinator.imageSizes[indexPath.section][indexPath.item] == nil { + if let collectionView = self.findCollectionView() { + coordinator.updateImageSize(for: indexPath, size: imageSize, collectionView: collectionView, isCached: false) + } + } else { + // 🔧 FIX: Size is cached, update cache and reveal immediately + coordinator.imageSizes[indexPath.section][indexPath.item] = imageSize + self.revealImage() + } + + case .failure(let error): + print("Image loading failed: \(error)") + + // Handle the special case where image loaded but task was cancelled + if case .imageSettingError(let reason) = error, + case .notCurrentSourceTask(let result) = reason, + let retrieveResult = result.result { + // Image actually loaded successfully, we can still use the size info + let imageSize = retrieveResult.image.size + if let collectionView = self.findCollectionView() { + coordinator.updateImageSize(for: indexPath, size: imageSize, collectionView: collectionView, isCached: false) + } + } else { + // True failure - keep loading view visible + DispatchQueue.main.async { + //self.imageView.isHidden = true + //self.hostingController.view.isHidden = false + } + } + } + } + } + + func revealImage() { + // Double check that this cell hasn't been reused + guard currentLoadingTask != nil else { return } + + DispatchQueue.main.async { [weak self] in + guard let self = self else { return } + self.imageView.isHidden = false + self.hostingController.view.isHidden = true + } + } + + private func findCollectionView() -> UICollectionView? { + var view = self.superview + while view != nil { + if let collectionView = view as? UICollectionView { + return collectionView + } + view = view?.superview + } + return nil + } +} + + +//enum ScreenPosition +enum ScreenPosition { + case mid + case top + case bottom +} + diff --git a/Kanzen/Views/Reader/readerManagerView.swift b/Kanzen/Views/Reader/readerManagerView.swift new file mode 100644 index 00000000..44a28b54 --- /dev/null +++ b/Kanzen/Views/Reader/readerManagerView.swift @@ -0,0 +1,194 @@ + +// +// readerManagerView.swift +// Kanzen +// +// Created by Dawud Osman on 13/06/2025. +// +import SwiftUI +import Kingfisher + + +struct readerManagerView:View { + @State var chapters: [Chapter]? + @State var selectedChapter: Chapter? + @ObservedObject var kanzen : KanzenEngine + @EnvironmentObject var settings : Settings + @Environment(\.dismiss) var dismiss + @State private var showFullScreen = true + @State private var showChapterlist: Bool = false + @State private var showReadingModePicker = false + @Environment(\.colorScheme) var colorScheme + @State var someValue: CGFloat = 0 + @State var RTL: Bool = true + + @State private var sliderRange: ClosedRange = 0...0 + @State private var debounceWorkItem: DispatchWorkItem? + // new Implementation + + @StateObject var reader_manager: readerManager + init (chapters: [Chapter]?,selectedChapter: Chapter?,kanzen: KanzenEngine) + { + print("CHAPTER IS") + print(selectedChapter) + self.kanzen = kanzen + _reader_manager = StateObject(wrappedValue: readerManager(kanzen:kanzen,chapters: chapters,selectedChapter: selectedChapter)) + _chapters = State(initialValue: chapters) + _selectedChapter = State(initialValue: selectedChapter) + } + + var body: some View { + ZStack { + // Custom Back Button + + //pageReader(reader_manager: reader_manager) + + //ScrollView{LazyVStack{ForEach(reader_manager.currChapter) { chapter in chapter.body}}} + if(reader_manager.currChapter.count > 0) + { + readerContent() + } + else{ + CircularLoader(progress: 0) + } + readerOverlay() + } + + .sheet(isPresented: $showChapterlist) + { + ChapterList(readerManager: reader_manager) + } + .sheet(isPresented: $showReadingModePicker){ + readerManagerSettings(readerManager: reader_manager) + //.presentationDetents([.fraction(0.3)]) // 👈 make it short (30% screen height) + //.presentationCornerRadius(24) // 👈 curved top corners + //.presentationBackground(.regularMaterial) // 👈 blurred material background + + } + .onChange(of: reader_manager.index) { newIndex in + let clamped = min(CGFloat(newIndex), reader_manager.currRange.upperBound) + if someValue != clamped { // avoid redundant triggers + someValue = clamped + } + } + .onChange(of: someValue) { newValue in + print("someValue changed") + guard Int(newValue) != reader_manager.index else { return } + debounceWorkItem?.cancel() + let workItem = DispatchWorkItem { + reader_manager.setIndex(Int(newValue)) + reader_manager.changeIndex.toggle() + + } + debounceWorkItem = workItem + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5, execute: workItem) + } + + + .navigationBarBackButtonHidden(true) + .task { + reader_manager.initChapters() + } + } + + @ViewBuilder + func readerContent() -> some View { + switch(reader_manager.readingMode){ + case .LTR: pageReader(reader_manager: reader_manager, pageViewConfig: .LTR) .id("LTR") .onTapGesture { + showFullScreen.toggle() + } + case .WEBTOON: WebtoonView(reader_manager: reader_manager).id("WEBTOON") .onTapGesture { + showFullScreen.toggle() + } + case .RTL: pageReader(reader_manager: reader_manager,pageViewConfig: .RTL) .id("RTL") .onTapGesture { + showFullScreen.toggle() + } + case .VERTICAL: pageReader(reader_manager: reader_manager,pageViewConfig: .Vertical) .id("VERTICAL") .onTapGesture { + showFullScreen.toggle() + } + + } + + } + + @ViewBuilder + func readerOverlay() -> some View { + if showFullScreen + { + + VStack{ + HStack{ + + HStack{ + Image(systemName: "multiply.circle.fill").onTapGesture { + dismiss() + } + .font(.title) + .foregroundColor(settings.accentColor ) + + } + + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.leading,10) + Spacer() + VStack{ + Text("Chapter") + Text("\(reader_manager.selectedChapter?.chapterNumber ?? "No Title")") + + } + .frame(maxWidth: .infinity) + Spacer() + HStack{ + Image(systemName: "line.3.horizontal.decrease.circle.fill") + .font(.title) + .foregroundColor(settings.accentColor ) + .onTapGesture { + showReadingModePicker = true + + } + Image(systemName: "list.bullet.circle.fill") + .font(.title) + .foregroundColor(settings.accentColor ) + .onTapGesture { + showChapterlist = true + } + } + .frame(maxWidth: .infinity, alignment: .trailing) + .padding(.trailing,10) + } + .frame(height: 50) + .frame(maxWidth: .infinity) + .background( colorScheme == .dark ? Color.black.opacity(0.5) : Color.black.opacity(0.1)) + Spacer() + VStack + { + HStack{ + + customSlider(value: $someValue,RTL: $RTL,range: reader_manager.currRange) + + .padding(.leading, 10) + .padding(.trailing,10) + + + + + } + .frame(height: 50) + Text("\(min(Int(someValue),Int(reader_manager.currRange.upperBound)))/\(Int(reader_manager.currRange.upperBound))") + + + } + .background( colorScheme == .dark ? Color.black.opacity(0.5) : Color.black.opacity(0.1)) + + + } + + + + } + } + + + + +} diff --git a/Kanzen/Views/Reader/utils/chapterList.swift b/Kanzen/Views/Reader/utils/chapterList.swift new file mode 100644 index 00000000..67d656ea --- /dev/null +++ b/Kanzen/Views/Reader/utils/chapterList.swift @@ -0,0 +1,86 @@ +// +// chapterList.swift +// Kanzen +// +// Created by Dawud Osman on 09/10/2025. +// +import SwiftUI +struct ChapterList: View { + @ObservedObject var readerManager: readerManager + @EnvironmentObject var settings : Settings + @State var reverseChapterlist: Bool = false + + var body: some View { + ScrollView { + VStack{ + if let chapters = readerManager.chapters { + var displayedChapters: Array.Element> { + if reverseChapterlist + { + Array(chapters.enumerated().reversed()) + } + else + {Array(chapters.enumerated())} + + + } + HStack{ + Text("\(chapters.count) Chapters") + .font(.headline) + .bold() + .foregroundColor(settings.accentColor) + Spacer() + Image(systemName: "line.3.horizontal.decrease") + + .renderingMode(.template) + .foregroundColor(settings.accentColor) + .padding(.leading,20) + .font(.title2) + + + .contentShape(Rectangle()) + .onTapGesture { + reverseChapterlist.toggle() + } + + } + Divider() + ForEach(displayedChapters, id:\.offset) { index, item in + if let chapterData = item.chapterData { + Button + { + DispatchQueue.main.async { + readerManager.selectedChapter = item + readerManager.resetState() + } + }label: { + HStack{ + + + if chapterData.count > 0 { + + Text("\(item.chapterNumber )").font(.subheadline) + .foregroundColor(settings.accentColor) + Text(" \u{00B7} \(chapterData[0].scanlationGroup)") + .foregroundColor(settings.accentColor) + .font(.footnote) + //.foregroundStyle(.secondary) + + + } + else{ + Text("\(item.chapterNumber)").font(.subheadline) + .foregroundColor(settings.accentColor) + } + + + }.frame(maxWidth: .infinity, alignment: .leading) + } + } + Divider() + } + } + } + }.padding(10) + } +} + diff --git a/Kanzen/Views/Reader/utils/readerSettingsView.swift b/Kanzen/Views/Reader/utils/readerSettingsView.swift new file mode 100644 index 00000000..52bec4dc --- /dev/null +++ b/Kanzen/Views/Reader/utils/readerSettingsView.swift @@ -0,0 +1,23 @@ +// +// readerSettings.swift +// Kanzen +// +// Created by Dawud Osman on 05/10/2025. +// +import SwiftUI +struct readerManagerSettings: View { + @ObservedObject var readerManager: readerManager + var body: some View + { + Form{ + Section{ + Picker("Reading Mode",selection: readerManager.$readingModeRaw){ + ForEach(ReadingMode.allCases) { mode in + Text(mode.title).tag(mode.rawValue) + } + } + } + } + } + +} diff --git a/Kanzen/Views/Search/search.swift b/Kanzen/Views/Search/search.swift new file mode 100644 index 00000000..3676c0cb --- /dev/null +++ b/Kanzen/Views/Search/search.swift @@ -0,0 +1,156 @@ +// +// SearchView.swift +// Kanzen +// +// Created by Dawud Osman on 22/05/2025. +// +import SwiftUI +import Foundation +import Kingfisher +struct KanzenSearchView: View { + + @State var module: ModuleDataContainer? + @State var searchText: String = "" + @State var cellWidth: CGFloat = 150 + @State var searchPage: Int = 0 + @State var endOfPage: Bool = false + private var columnCount: Int { + let screenWidth = UIScreen.main.bounds.width + return Int(screenWidth/(cellWidth+10)) + } + @EnvironmentObject var kanzen: KanzenEngine + @EnvironmentObject var moduleManager: ModuleManager + @State var searchArray : [Manga] = [] + + private func performSearch(append:Bool = false){ + if endOfPage{ + print("end of page") + return + } + kanzen.searchInput(searchText,page: searchPage){ + + result in + if let result = result{ + if result.isEmpty{ + endOfPage = true + return + } + let item = result.compactMap{ dict -> Manga? in + guard + + let title = dict["title"] as? String, + let imageURL = dict["imageURL"] as? String, + let mangaId = dict["id"] as? String + else { print(dict) ; print("error formating search Output") ;return nil } + return Manga(title: title, imageURL: imageURL, mangaId: mangaId,parentModule: module) + + } + + + + + DispatchQueue.main.async { + if append { + searchArray.append(contentsOf: item) + } else { + searchArray = item + } + searchPage += 1 + } + + } + + + + } + } + private func generateCells() -> some View { + + return ScrollView{ + if searchArray.count > 0 + { + + LazyVGrid(columns: Array(repeating: GridItem(.fixed(cellWidth),spacing: 5), count: columnCount), spacing: 5) { + ForEach(searchArray) { item in + + NavigationLink(destination: {contentView(parentModule: module,title: item.title,imageURL: item.imageURL,params: item.mangaId)}){contentCell(title:item.title,urlString: item.imageURL,width: cellWidth)} + + } + Color.clear.frame(height:1) + .onAppear{ + if(!endOfPage) + { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { + performSearch(append: true) + print("End of List") + } + } + + + } + } + + } + } + + } + var body: some View { + VStack { + SearchBar(text: $searchText,onSearchButtonClicked: { + searchPage = 0 + endOfPage = false + performSearch() + } + ).padding(.leading,20) + .padding(.trailing,20) + generateCells() + + }.frame(maxHeight: .infinity, alignment: .top) + .onAppear{ + do { + if let module = module { + let content = try moduleManager.getModuleScript(module: module) + try kanzen.loadScript(content) + } + } + catch{ + Logger.shared.log(error.localizedDescription,type: "Error") + } + } + } +} +struct SearchBar: View { + @State private var debounceTimer: Timer? + @Binding var text: String + var onSearchButtonClicked: () -> Void + + var body: some View { + HStack { + TextField("Search...", text: $text, onCommit: onSearchButtonClicked) + + .padding(7) + .padding(.horizontal, 25) + .background(Color(.systemGray6)) + .cornerRadius(8) + .contentShape(Rectangle()) + .overlay( + HStack { + Image(systemName: "magnifyingglass") + .foregroundColor(.secondary) + .frame(minWidth: 0, maxWidth: .infinity, alignment: .leading) + .padding(.leading, 8) + + if !text.isEmpty { + Button(action: { + self.text = "" + }) { + Image(systemName: "multiply.circle.fill") + .foregroundColor(.secondary) + .padding(.trailing, 8) + } + } + } + ) + } + } +} diff --git a/Kanzen/Views/Settings/kanzenSettings.swift b/Kanzen/Views/Settings/kanzenSettings.swift new file mode 100644 index 00000000..9ae2f925 --- /dev/null +++ b/Kanzen/Views/Settings/kanzenSettings.swift @@ -0,0 +1,47 @@ +// +// kanzenSettings.swift +// Luna +// +// Created by Dawud Osman on 17/11/2025. +// +// +// SettingsView.swift +// Kanzen +// +// Created by Dawud Osman on 16/05/2025. +// +import SwiftUI +struct KanzenSettingsView : View +{ + @AppStorage("showKanzen") private var showKanzen: Bool = false + var body: some View + { + NavigationView { + Form { + Section(header: Text("General")) { + NavigationLink(destination: KanzenGeneralSettingsView()){Text("Preferences")} + + + } + Section(header: Text("Activity")) { + NavigationLink(destination: LoggerView()) { + Text("Logs") + + } + + } + Section(header: Text("Others")){ + Text("Switch to Sora") + .onTapGesture { + showKanzen = false + } + } + + Section(footer: Text("Running Kanzen v0.1 - Churly" )){} + }.navigationTitle("Settings") + + + + } + } +} diff --git a/Kanzen/Views/Settings/subSettings/kanzenGeneralSettings.swift b/Kanzen/Views/Settings/subSettings/kanzenGeneralSettings.swift new file mode 100644 index 00000000..c5d37e41 --- /dev/null +++ b/Kanzen/Views/Settings/subSettings/kanzenGeneralSettings.swift @@ -0,0 +1,36 @@ +// +// GeneralView.swift +// Kanzen +// +// Created by Dawud Osman on 22/05/2025. +// +import SwiftUI +struct KanzenGeneralSettingsView: View { + @EnvironmentObject var settings : Settings + var body: some View { + Form { + Section(header: Text("Interface")) { + ColorPicker("Accent Color", selection: $settings.accentColor) + + HStack { + + Text("Appearance") + Spacer() + Picker("Appearance", selection: $settings.selectedAppearance) { + Text("System").tag(Appearance.system) + Text("Light").tag(Appearance.light) + Text("Dark").tag(Appearance.dark) + } + .pickerStyle(SegmentedPickerStyle()) + .frame(maxWidth: 300) + + + } + + + } + } + .navigationTitle(Text("Preferences")) + } + +} diff --git a/Kanzen/Views/Util/circularLoading.swift b/Kanzen/Views/Util/circularLoading.swift new file mode 100644 index 00000000..71979a64 --- /dev/null +++ b/Kanzen/Views/Util/circularLoading.swift @@ -0,0 +1,36 @@ +// +// circularLoading.swift +// Kanzen +// +// Created by Dawud Osman on 24/10/2025. +// + +// circular progress bar +import SwiftUI +struct CircularLoader: View { + var progress: Double + @State private var rotation: Double = 0 + var body: some View { + ZStack { + // Add a background for debugging + //Rectangle() + //.fill(Color.green.opacity(0.3)) + VStack{ + Text("LOADING...") + // Custom rotating spinner + Circle() + .trim(from: 0, to: 0.8) + .stroke(Color.white, lineWidth: 4) + .frame(width: 50, height: 50) + .rotationEffect(.degrees(rotation)) + .onAppear { + withAnimation(Animation.linear(duration: 1.0).repeatForever(autoreverses: false)) { + rotation = 360 + } + } + } + + } + } +} + diff --git a/Kanzen/Views/Util/contentCell.swift b/Kanzen/Views/Util/contentCell.swift new file mode 100644 index 00000000..06e65b94 --- /dev/null +++ b/Kanzen/Views/Util/contentCell.swift @@ -0,0 +1,86 @@ +// +// contentCell.swift +// Kanzen +// +// Created by Dawud Osman on 26/05/2025. +// +import SwiftUI +import Foundation +import Kingfisher +struct contentCell: View { + @State var title: String + @State var urlString: String + @State var width: CGFloat + + + init(title: String, urlString: String, width: CGFloat) { + self.title = title + self.urlString = urlString + self.width = width + + } + var body: some View { + ZStack(alignment: .bottomLeading){ + if let url = URL(string: urlString) { + + KFImage(url) + // Don't set processor here to ensure cached original image is used + .onSuccess { result in + switch result.cacheType { + case .none: + print("Image loaded from network.") + case .memory: + print("Image loaded from memory cache.") + case .disk: + print("Image loaded from disk cache.") + @unknown default: + print("Unknown cache type.") + } + } + .onFailure { error in + print("Image loading failed: \(error)") + } + .placeholder { + ProgressView() + } + .fade(duration: 0.25) + .setProcessor(DownsamplingImageProcessor(size: CGSize(width: width, height: width * 1.5))) + .resizable() + .scaleFactor(UIScreen.main.scale) + .interpolation(.low) + .aspectRatio(0.72, contentMode: .fill) + .onAppear{ + print("Image appeared \(urlString)") + } + + // SwiftUI resizes smoothly + .frame(width: width, height: width * 1.5) + .clipped() + + + + + } else { + Rectangle().fill(Color.black).clipped().frame(width: width,height: width * 1.5) + } + // Gradient fade at the bottom + LinearGradient( + gradient: Gradient(colors: [Color.clear, Color.black.opacity(0.8)]), + startPoint: .top, + endPoint: .bottom + ) + .frame(height: 80) + .frame(maxWidth: .infinity, alignment: .bottom) + .clipped() + Text(title).lineLimit(1).foregroundColor(.white) +.cornerRadius(5).padding([.leading, .bottom], 5) + + } + .frame(maxWidth: 150) + .frame(height: 150 * 1.5) + .cornerRadius(5) + + + + } +} diff --git a/Kanzen/Views/Util/contentView.swift b/Kanzen/Views/Util/contentView.swift new file mode 100644 index 00000000..067581b6 --- /dev/null +++ b/Kanzen/Views/Util/contentView.swift @@ -0,0 +1,347 @@ +// +// contentView.swift +// Kanzen +// +// Created by Dawud Osman on 27/05/2025. +// +import SwiftUI +import Foundation +import Kingfisher +import Flow +struct contentView: View { + @State var parentModule: ModuleDataContainer? + @State var title: String + @State var imageURL: String + @State var params: String + @State var expandedDescription : Bool = false + @State private var contentData: [String:Any]? + @State private var contentChapters: [Chapters]? + @EnvironmentObject var kanzen: KanzenEngine + @EnvironmentObject var settings: Settings + @EnvironmentObject var favouriteManager : FavouriteManager + @State private var width: CGFloat = 150 + @State private var langaugeIdx: Int = 0 + @State private var showChaptersMenu: Bool = false + @State private var selectedChapterData: Chapter? = nil + @State private var selectedChapterIdx: Int? + @State var reverseChapterlist: Bool = false + @State var toggleFavourite: Bool = false + @State var loadingState : Bool = true + + + var body: some View { + renderedContent().onAppear{ + getContentData() + } + } + + + func renderedContent() -> some View { + ScrollView{ + VStack(alignment: .leading){ + HStack(){ + KFImage(URL(string: imageURL)!) + .resizable() + .placeholder{ + ProgressView() + } + .scaledToFill() + .frame(width: width, height: width * 1.5) + .clipped() + .aspectRatio(contentMode: .fill) + .frame(maxWidth: width) + .frame(height: width * 1.5) + .cornerRadius(5) + + VStack(alignment: .leading){ + Text(title) + .font(.title) + + if let contentData = contentData { + if let authorArtist = contentData["authorArtist"] as? [String] + { + HStack{ + ForEach(Array(authorArtist.enumerated()),id: \.offset) + { + idx,item in + Text(item).font(.caption) + .padding(.leading,3) + .padding(.trailing,3) + .background(Color.accentColor) + + .cornerRadius(3) + } + } + + + + } + if let tags = contentData["tags"] as? [String] + { + HStack{ + ForEach(Array(tags.enumerated()),id: \.offset) + { + idx,item in + Text(item).font(.caption) + .padding(.leading,3) + .padding(.trailing,3) + .background(Color.accentColor) + + .cornerRadius(3) + } + } + + + + } + } + Divider() + HStack{ + if !toggleFavourite { + Image(systemName: "bookmark") + .foregroundColor(settings.accentColor) + .onTapGesture { + favouriteManager.addFavourite(module: parentModule ?? nil, content: Manga(title: title, imageURL: imageURL, mangaId: params)) + toggleFavourite.toggle() + } + } + else{ + Image(systemName: "bookmark.fill") + .foregroundColor(settings.accentColor) + .onTapGesture { + if let module = parentModule{ + favouriteManager.removeFavourite(moduleId: module.id, contentId: params) + } + toggleFavourite.toggle() + } + } + }.frame(alignment: .leading) + } + + .frame(maxHeight: .infinity, alignment: .top) + + } + .frame(maxWidth: .infinity,alignment: .leading) + + Divider() + if let contentData = contentData { + + if let description = contentData["description"] as? String{ + + Text(description) + .font(.footnote) + .lineLimit(expandedDescription ? nil : 3) + .onTapGesture { + withAnimation{ + expandedDescription.toggle() + } + + } + + + } + } + Divider() + if loadingState { + ProgressView() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center) + } + else{ + chaptersView() + .padding(.trailing,5) + .padding(.leading,5) + } + + + } + .padding(.trailing,5) + .padding(.leading,5) + .frame(maxHeight: .infinity, alignment: .top) + } + .fullScreenCover(item: $selectedChapterData){ chapter in + if let contentChapters = self.contentChapters{ + readerManagerView(chapters: contentChapters[langaugeIdx].chapters,selectedChapter: chapter,kanzen: kanzen) + } + + } + .onAppear{ + toggleFavourite = checkIfFavorited() + } + .navigationTitle(title) + } + + + func checkIfFavorited() -> Bool { + if let module = parentModule + { + return FavouriteManager.shared.isFavourite(moduleId: module.id, contentId: params) + } + else{ + return false + } + } + + + + func getContentData() { + DispatchQueue.main.async{ + kanzen.getContentData(params: self.params) + { + result in + + self.contentData = result + print("contentData is ") + print(result) + } + kanzen.getChapters(params: self.params){ + result in + if let result = result{ + var temp: [Chapters] = [] + for (key, value) in result + { + var tempChapters: [Chapter] = [] + if let chapters = value as? [Any?] + { + for (idx,chapter) in chapters.enumerated() { + print("chapter is ") + print(idx) + print(chapter) + if let chapter = chapter as? [Any?], let chapterName = chapter[0] as? String, let rawData = chapter[1] as? [[String: Any?]], let chapterData = rawData.compactMap({ChapterData(dict: $0 as [String : Any])}) as? [ChapterData] { + let tempChapter: Chapter = Chapter(chapterNumber: chapterName, idx:idx,chapterData: chapterData) + tempChapters.append(tempChapter) + + } + } + } + if tempChapters.count > 0 { + temp.append(Chapters(language: key, chapters: tempChapters)) + } + } + self.contentChapters = temp + print("contentChapters is") + print(contentChapters) + } + + loadingState = false + } + + } + } + @ViewBuilder + func chaptersMenu() -> some View { + if let contentChapters = self.contentChapters, contentChapters.count > 0 { + + } + } + + @ViewBuilder + func chaptersView() -> some View { + if var chaptersData = self.contentChapters, chaptersData.count > 0 { + var selectedLanguage = chaptersData[langaugeIdx] + var displayedChapters: Array.Element> { + if reverseChapterlist + { + Array(selectedLanguage.chapters.enumerated().reversed()) + } + else + {Array(selectedLanguage.chapters.enumerated())} + + + } + + VStack { + HStack { + Text("\(selectedLanguage.chapters.count) Chapters") + .font(.headline) + .bold() + .foregroundColor(Color.accentColor) + Spacer() + Image(systemName: "line.3.horizontal.decrease") + + .renderingMode(.template) + .foregroundColor(.accentColor) + .padding(.leading,20) + .font(.title2) + + + .contentShape(Rectangle()) + .contextMenu{ + if let contentChapters = contentChapters as? [Chapters], contentChapters.count > 0 { + Menu{ + ForEach(Array(contentChapters.enumerated()),id: \.offset) + { + index, item in + Button(item.language){langaugeIdx = index} + } + + } label: {Text("Language")} + } + + } + .onTapGesture { + print("chapterList reversed") + reverseChapterlist.toggle() + } + .onLongPressGesture { + withAnimation(){ + if((contentChapters ?? []).count > 0) + { + showChaptersMenu.toggle() + } + + } + } + + } + .frame(maxWidth: .infinity, alignment: .leading) + Divider() + VStack{ + ForEach(displayedChapters, id:\.offset ) + { + index,item in + + if let chapterData = item.chapterData + { + Button{ + + selectedChapterIdx = index + DispatchQueue.main.async { + selectedChapterData = item + } + print("idx is \(selectedChapterIdx)") + + }label:{ + HStack{ + + + if chapterData.count > 0 { + + Text("\(item.chapterNumber )").font(.subheadline) + .foregroundColor(Color.accentColor) + Text(" \u{00B7} \(chapterData[0].scanlationGroup)") + .font(.footnote) + .foregroundColor(.secondary) + + + } + else{ + Text("\(item.chapterNumber)").font(.subheadline) + .foregroundColor(Color.accentColor) + } + + + }.frame(maxWidth: .infinity, alignment: .leading) + } + + } + + + + Divider() + } + } + } + } else { + Text("No chapters found") + } + } +} diff --git a/Kanzen/Views/Util/customSlider.swift b/Kanzen/Views/Util/customSlider.swift new file mode 100644 index 00000000..74986048 --- /dev/null +++ b/Kanzen/Views/Util/customSlider.swift @@ -0,0 +1,67 @@ +// +// customSlider.swift +// Kanzen +// +// Created by Dawud Osman on 21/06/2025. +// +import SwiftUI +import Foundation + +extension CGFloat { + func map(from: ClosedRange, to: ClosedRange) -> CGFloat { + let result = ((self - from.lowerBound) / (from.upperBound - from.lowerBound)) * (to.upperBound - to.lowerBound) + to.lowerBound + return result + } +} +struct customSlider: View{ + @Binding var value: CGFloat + @Binding var RTL: Bool + @State var lastOffset: CGFloat = 0 + @EnvironmentObject var settings : Settings + var range: ClosedRange + + var body: some View { + GeometryReader { geometry in + VStack{ + Spacer() + ZStack { + HStack(spacing: 0) { + Rectangle() + .frame(width: self.$value.wrappedValue.map(from: self.range, to: 0...(geometry.size.width )), height: 5) + .foregroundColor(RTL ? .gray :settings.accentColor) + + Rectangle() + .frame(height: 5) + .foregroundColor(RTL ? settings.accentColor : .gray) + } + HStack { + Circle() + .frame(width: 11, height: 11) + .foregroundColor(.white) + .offset(x: self.$value.wrappedValue.map(from: range, to: 0...(geometry.size.width - 0 - 11))) + .gesture( + DragGesture(minimumDistance: 0) + .onChanged { value in + + if abs(value.translation.width) < 0.1 { + self.lastOffset = self.$value.wrappedValue.map(from: range, to: 0...(geometry.size.width - 0 - 11)) + } + + let sliderPos = max(0, min(self.lastOffset + value.translation.width, geometry.size.width - 0 - 11)) + + let sliderVal = sliderPos.map(from: 0...(geometry.size.width - 11), to: range) + self.value = sliderVal + + } + ) + Spacer() + } + } + .padding(.bottom,10) + } + + } + + } +} + diff --git a/Kanzen/Views/Util/favouriteViewWrapper.swift b/Kanzen/Views/Util/favouriteViewWrapper.swift new file mode 100644 index 00000000..0da7385f --- /dev/null +++ b/Kanzen/Views/Util/favouriteViewWrapper.swift @@ -0,0 +1,49 @@ +// +// favouriteViewWrapper.swift +// Kanzen +// +// Created by Dawud Osman on 22/10/2025. +// +import SwiftUI +struct favouriteViewWrapper: View { + var favouriteContent: MangaData + var currModule: ModuleDataContainer? + @State var moduleLoaded: Bool + @ObservedObject var kanzen : KanzenEngine + init(favouriteContent: MangaData, currModule: ModuleDataContainer?) { + self.favouriteContent = favouriteContent + self.moduleLoaded = false + self.kanzen = KanzenEngine() + self.currModule = currModule + + } + var body: some View { + + if moduleLoaded { + contentView(parentModule: currModule,title: favouriteContent.title ?? "", imageURL: favouriteContent.imageURL ?? "", params: favouriteContent.mangaId).environmentObject(kanzen) + } + else{ + Text("MODULE NOT LOADED") + .task{ + print("module is ") + print(currModule) + if let module = currModule { + print("this called") + do { + let content = try ModuleManager.shared.getModuleScript(module: module) + try kanzen.loadScript(content) + self.moduleLoaded = true + } + catch{ + print("Error loading module") + } + } + else{ + print("no module assigned in init") + } + } + } + + } +} + diff --git a/Kanzen/mangaModel.xcdatamodeld/mangaModel.xcdatamodel/contents b/Kanzen/mangaModel.xcdatamodeld/mangaModel.xcdatamodel/contents new file mode 100644 index 00000000..d6089e52 --- /dev/null +++ b/Kanzen/mangaModel.xcdatamodeld/mangaModel.xcdatamodel/contents @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Luna.xcodeproj/project.pbxproj b/Luna.xcodeproj/project.pbxproj index 3cc2e068..f11340b1 100644 --- a/Luna.xcodeproj/project.pbxproj +++ b/Luna.xcodeproj/project.pbxproj @@ -3,7 +3,7 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ @@ -78,6 +78,8 @@ 13F5B5922E73090000565E75 /* HomeSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13F5B5912E73090000565E75 /* HomeSection.swift */; }; 13F5B5952E730CB200565E75 /* TMDBContentFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13F5B5942E730CB200565E75 /* TMDBContentFilter.swift */; }; 13F5B5972E730CDB00565E75 /* TMDBFiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13F5B5962E730CDB00565E75 /* TMDBFiltersView.swift */; }; + 3802B83A2ECB7E6D00CF44B4 /* Flow in Frameworks */ = {isa = PBXBuildFile; productRef = 3802B8392ECB7E6D00CF44B4 /* Flow */; }; + 38F669352ECC3A5700005865 /* ContentModel.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 38F669332ECC3A5700005865 /* ContentModel.xcdatamodeld */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -164,8 +166,13 @@ 13F5B5912E73090000565E75 /* HomeSection.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HomeSection.swift; sourceTree = ""; }; 13F5B5942E730CB200565E75 /* TMDBContentFilter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TMDBContentFilter.swift; sourceTree = ""; }; 13F5B5962E730CDB00565E75 /* TMDBFiltersView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TMDBFiltersView.swift; sourceTree = ""; }; + 38F669342ECC3A5700005865 /* ContentModel.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ContentModel.xcdatamodel; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 3802B7632ECB4C6B00CF44B4 /* Kanzen */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = Kanzen; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 13F072582E4B2D3300EF90EB /* Frameworks */ = { isa = PBXFrameworksBuildPhase; @@ -173,6 +180,7 @@ files = ( 131BC8562EC36FD900E19F3E /* Kingfisher in Frameworks */, 12E7AF5E2EB7FDB7003B7664 /* FakeWebKit in Frameworks */, + 3802B83A2ECB7E6D00CF44B4 /* Flow in Frameworks */, 131BC8532EC36FD500E19F3E /* SoraCore in Frameworks */, 131BC8502EC36FCF00E19F3E /* FakeWebKit in Frameworks */, 131BC8592EC3728500E19F3E /* MPVKit-GPL in Frameworks */, @@ -223,6 +231,8 @@ 13F072522E4B2D3300EF90EB = { isa = PBXGroup; children = ( + 38F669332ECC3A5700005865 /* ContentModel.xcdatamodeld */, + 3802B7632ECB4C6B00CF44B4 /* Kanzen */, 129CC9062EBA1D4600D2D84B /* Build.xcconfig */, 129CC9082EBA1F8400D2D84B /* Build.local.xcconfig */, 13F0725D2E4B2D3300EF90EB /* Luna */, @@ -475,6 +485,9 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 3802B7632ECB4C6B00CF44B4 /* Kanzen */, + ); name = Luna; packageProductDependencies = ( 12E7AF5D2EB7FDB7003B7664 /* FakeWebKit */, @@ -482,6 +495,7 @@ 131BC8522EC36FD500E19F3E /* SoraCore */, 131BC8552EC36FD900E19F3E /* Kingfisher */, 131BC8582EC3728500E19F3E /* MPVKit-GPL */, + 3802B8392ECB7E6D00CF44B4 /* Flow */, ); productName = Sora; productReference = 13F0725B2E4B2D3300EF90EB /* Luna.app */; @@ -516,6 +530,7 @@ 131BC8512EC36FD500E19F3E /* XCRemoteSwiftPackageReference "SoraCore" */, 131BC8542EC36FD900E19F3E /* XCRemoteSwiftPackageReference "Kingfisher" */, 131BC8572EC3728500E19F3E /* XCRemoteSwiftPackageReference "MPVKit" */, + 3802B8382ECB7E6D00CF44B4 /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */, ); productRefGroup = 13F0725C2E4B2D3300EF90EB /* Products */; projectDirPath = ""; @@ -627,6 +642,7 @@ 13F187B12E6F38CE00747F7E /* CollectionDetailView.swift in Sources */, 13830AF42E55F5FF00B3CDE4 /* AccentColorManager.swift in Sources */, 13F072A12E4B2D9200EF90EB /* JSController.swift in Sources */, + 38F669352ECC3A5700005865 /* ContentModel.xcdatamodeld in Sources */, 13F0729F2E4B2D9200EF90EB /* ServiceManager.swift in Sources */, 13F187AC2E6F38CE00747F7E /* LibraryManager.swift in Sources */, ); @@ -767,7 +783,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = "${DEVELOPMENT_TEAM}"; + DEVELOPMENT_TEAM = C5TY8LKCH7; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = NO; ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; @@ -784,7 +800,7 @@ ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Luna/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "${APP_NAME}"; + INFOPLIST_KEY_CFBundleDisplayName = Luna; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; @@ -802,6 +818,7 @@ MACOSX_DEPLOYMENT_TARGET = 15.6; MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_IDENTIFIER}"; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = dawud.luna; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; @@ -823,7 +840,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = "${DEVELOPMENT_TEAM}"; + DEVELOPMENT_TEAM = C5TY8LKCH7; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = NO; ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; @@ -840,7 +857,7 @@ ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Luna/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "${APP_NAME}"; + INFOPLIST_KEY_CFBundleDisplayName = Luna; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; @@ -858,6 +875,7 @@ MACOSX_DEPLOYMENT_TARGET = 15.6; MARKETING_VERSION = 1.0.0; PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_IDENTIFIER}"; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = dawud.luna; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; @@ -925,6 +943,14 @@ minimumVersion = "0.40.0-xcode"; }; }; + 3802B8382ECB7E6D00CF44B4 /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/tevelee/SwiftUI-Flow"; + requirement = { + kind = exactVersion; + version = 1.1.0; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -952,7 +978,25 @@ package = 131BC8572EC3728500E19F3E /* XCRemoteSwiftPackageReference "MPVKit" */; productName = "MPVKit-GPL"; }; + 3802B8392ECB7E6D00CF44B4 /* Flow */ = { + isa = XCSwiftPackageProductDependency; + package = 3802B8382ECB7E6D00CF44B4 /* XCRemoteSwiftPackageReference "SwiftUI-Flow" */; + productName = Flow; + }; /* End XCSwiftPackageProductDependency section */ + +/* Begin XCVersionGroup section */ + 38F669332ECC3A5700005865 /* ContentModel.xcdatamodeld */ = { + isa = XCVersionGroup; + children = ( + 38F669342ECC3A5700005865 /* ContentModel.xcdatamodel */, + ); + currentVersion = 38F669342ECC3A5700005865 /* ContentModel.xcdatamodel */; + path = ContentModel.xcdatamodeld; + sourceTree = ""; + versionGroupType = wrapper.xcdatamodel; + }; +/* End XCVersionGroup section */ }; rootObject = 13F072532E4B2D3300EF90EB /* Project object */; } diff --git a/Luna.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Luna.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index bdf69bd2..706502e4 100644 --- a/Luna.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Luna.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "f2f79d2ed668cf7e7dbbea0f8aedc3ac5df1bf95516cc74d3ca65a65757d0c69", "pins" : [ { "identity" : "fakewebkit", @@ -35,7 +36,16 @@ "branch" : "main", "revision" : "50ddc160fd606e9708adefc1d9fc7003b574446b" } + }, + { + "identity" : "swiftui-flow", + "kind" : "remoteSourceControl", + "location" : "https://github.com/tevelee/SwiftUI-Flow", + "state" : { + "revision" : "76c03c9fa9fa0e5470cb6d8f2da17466b2efd63d", + "version" : "1.1.0" + } } ], - "version" : 2 + "version" : 3 } diff --git a/Luna.xcodeproj/xcshareddata/xcschemes/Luna.xcscheme b/Luna.xcodeproj/xcshareddata/xcschemes/Luna.xcscheme index b8dafea2..b2fdb6e9 100644 --- a/Luna.xcodeproj/xcshareddata/xcschemes/Luna.xcscheme +++ b/Luna.xcodeproj/xcshareddata/xcschemes/Luna.xcscheme @@ -1,7 +1,7 @@ + version = "1.7"> + allowLocationSimulation = "YES" + disablePerformanceAntipatternChecker = "YES"> Date: Mon, 8 Dec 2025 16:11:39 +0900 Subject: [PATCH 2/4] implement webtoon transition page --- Kanzen/Models/pageData.swift | 9 ++ Kanzen/Models/readerManager.swift | 16 +++ .../WebToon/webToonViewController.swift | 117 ++++++++++++++---- 3 files changed, 117 insertions(+), 25 deletions(-) diff --git a/Kanzen/Models/pageData.swift b/Kanzen/Models/pageData.swift index 0fb78803..e002a835 100644 --- a/Kanzen/Models/pageData.swift +++ b/Kanzen/Models/pageData.swift @@ -109,3 +109,12 @@ struct chapterView: View { } } + +struct TransitionPage: View { + var index: String + var body: some View { + Text("Chapter \(index) End") + .frame(maxWidth: .infinity) + .clipped() + } +} diff --git a/Kanzen/Models/readerManager.swift b/Kanzen/Models/readerManager.swift index 2c523334..f7bd805c 100644 --- a/Kanzen/Models/readerManager.swift +++ b/Kanzen/Models/readerManager.swift @@ -444,4 +444,20 @@ var nextControllers: [UIViewController]? pagePrefetcher?.start() } + func getNextChapterIdx() -> String{ + if let idx = selectedChapter?.idx, let currChapters = chapters, idx + 1 < currChapters.count { + return currChapters[idx+1].chapterNumber + + } + return "0" + + } + func getPrevChapterIdx() -> String + { + if let idx = selectedChapter?.idx, let currChapters = chapters, idx - 1 >= 0 { + return currChapters[idx - 1].chapterNumber + + } + return "0" + } } diff --git a/Kanzen/Views/Reader/WebToon/webToonViewController.swift b/Kanzen/Views/Reader/WebToon/webToonViewController.swift index ebebbe6b..3741f90f 100644 --- a/Kanzen/Views/Reader/WebToon/webToonViewController.swift +++ b/Kanzen/Views/Reader/WebToon/webToonViewController.swift @@ -36,6 +36,7 @@ struct WebtoonView: UIViewRepresentable { context.coordinator.reader_manager = reader_manager context.coordinator.currChapter = reader_manager.currChapter context.coordinator.chapters = [reader_manager.currChapter] + context.coordinator.transitionPages = [reader_manager.selectedChapter?.chapterNumber ?? "0"] context.coordinator.imageSizes = [[:]] // Clear cached sizes uiView.reloadData() uiView.collectionViewLayout.invalidateLayout() @@ -47,14 +48,14 @@ struct WebtoonView: UIViewRepresentable { if reader_manager.changeIndex, let sectionIdx = context.coordinator.chapters.firstIndex(of: reader_manager.currChapter){ print("Change index called && currChapter in chapters") - let pathItem = IndexPath(item: reader_manager.index, section: sectionIdx ) + let pathItem = IndexPath(item: reader_manager.index, section: sectionIdx * 2) uiView.scrollToItem(at: pathItem, at: UICollectionView.ScrollPosition.centeredVertically, animated: false) if reader_manager.changeIndex == true { reader_manager.changeIndex = false + } } - } @@ -64,11 +65,16 @@ struct WebtoonView: UIViewRepresentable { var imageSizes: [[Int: CGSize]] = [] var loadingPrevious = false var loadingNext = false + var reader_manager: readerManager + var chapters: [[PageData]] = [] + var currChapter: [PageData] + var transitionPages: [String] = [] init(reader_manager: readerManager) { self.reader_manager = reader_manager self.chapters.append(reader_manager.currChapter) self.currChapter = reader_manager.currChapter + self.transitionPages.append(reader_manager.selectedChapter?.chapterNumber ?? "0") imageSizes.append([:]) } @@ -97,7 +103,7 @@ struct WebtoonView: UIViewRepresentable { return } let midPath = getCurrentpagePath(collectionView: collectionView,position: .mid) - let midIdx = midPath?.section ?? 0 + let midIdx = (midPath?.section ?? 0)/2 //print("midPath Idx: \(String(describing: midIdx))") //print("currChapter Idx: \(String(describing: chapterIdx)) ") reader_manager.setIndex(midPath?.item ?? 0) @@ -196,6 +202,10 @@ struct WebtoonView: UIViewRepresentable { let size = self.collectionView(collectionView, layout: layout, sizeForItemAt: indexPath) totalHeight += size.height } + // Add transition page height (section at chapterIndex * 2 + 1) + let transitionIndexPath = IndexPath(item: 0, section: section * 2 + 1) + let transitionSize = self.collectionView(collectionView, layout: layout, sizeForItemAt: transitionIndexPath) + totalHeight += transitionSize.height return totalHeight } // prepend Chapter @@ -219,9 +229,10 @@ struct WebtoonView: UIViewRepresentable { chapters.insert(reader_manager.prevChapter, at: 0) imageSizes.insert([:], at: 0) + transitionPages.insert(reader_manager.getPrevChapterIdx(), at: 0) collectionView.performBatchUpdates({ - collectionView.insertSections(IndexSet(integer: 0)) + collectionView.insertSections(IndexSet(integersIn: 0..<2)) }, completion: { _ in // Calculate new offset let newContentSize = collectionView.collectionViewLayout.collectionViewContentSize @@ -234,13 +245,15 @@ struct WebtoonView: UIViewRepresentable { // 🔧 FIX: Re-enable animations and reset loading flag if self.chapters.count > 3 { // 1. First update your data source - let lastSectionIndex = self.chapters.count - 1 + //let lastSectionIndex = self.chapters.count - 1 + let lastSectionStart = (self.chapters.count - 1) * 2 self.chapters.removeLast() - self.imageSizes.removeLast() // Also remove corresponding cached data + self.imageSizes.removeLast() + self.transitionPages.removeLast()// Also remove corresponding cached data // 2. Then update the UI collectionView.performBatchUpdates({ - collectionView.deleteSections(IndexSet(integer: lastSectionIndex)) + collectionView.deleteSections(IndexSet(integersIn: lastSectionStart.. Int { - chapters.count + (chapters.count * 2 ) } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { - chapters[section].count + if section % 2 == 0 + { + let chapterIndex = section / 2 + return chapters[chapterIndex].count + } + else{ + return 1 + } } func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { @@ -340,23 +365,34 @@ struct WebtoonView: UIViewRepresentable { guard let cell = collectionView.dequeueReusableCell(withReuseIdentifier: ChapterCollectionViewCell.reuseIdentifier, for: indexPath) as? ChapterCollectionViewCell else { fatalError("Could not dequeue cell") } - + let chapterIndex = indexPath.section / 2 print("cellForItemAt section \(indexPath.section) - item \(indexPath.item)") print("chapters count \(chapters.count)") if chapters.count >= 3 { print("last chapter count \(chapters[2].count)") } - let rootView = chapters[indexPath.section][indexPath.item].body - cell.set(rootView: rootView, coordinator: self, indexPath: indexPath) + + let rootView = chapters[chapterIndex][indexPath.item].body + if(indexPath.section % 2 == 0){ + cell.set(rootView: rootView, coordinator: self, indexPath: indexPath) + } + else{ + print("Chapters Count is //// \(chapters.count)") + print("transition pages count is //// \(transitionPages.count)") + cell.setTransitionPage(chapterNumber: transitionPages[chapterIndex], indexPath: indexPath) + } return cell } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize { let width = collectionView.bounds.width - + if indexPath.section % 2 == 1{ + return CGSize(width: width, height: 400) + } // If we have the cached size, use it - if let cachedSize = imageSizes[indexPath.section][indexPath.item] { + let chapterIndex = indexPath.section / 2 + if let cachedSize = imageSizes[chapterIndex][indexPath.item] { let aspectRatio = cachedSize.height / cachedSize.width return CGSize(width: width, height: width * aspectRatio) } @@ -366,8 +402,13 @@ struct WebtoonView: UIViewRepresentable { } func updateImageSize(for indexPath: IndexPath, size: CGSize, collectionView: UICollectionView, isCached: Bool) { + print("cell section \(indexPath.section) - item \(indexPath.item) updated ; number of sections \(chapters.count)") - imageSizes[indexPath.section][indexPath.item] = size + if indexPath.section % 2 == 1 { + return + } + let chapterIndex = indexPath.section / 2 + imageSizes[chapterIndex][indexPath.item] = size // 🔧 FIX: Only update layout if this is a new image that needs resizing if !isCached { @@ -391,9 +432,7 @@ struct WebtoonView: UIViewRepresentable { } } - var reader_manager: readerManager - var chapters: [[PageData]] = [] - var currChapter: [PageData] + } } @@ -402,6 +441,7 @@ class ChapterCollectionViewCell: UICollectionViewCell { static let reuseIdentifier = "ChapterCell" private let imageView = UIImageView() private var hostingController : UIHostingController! + private var transitionHostingController: UIHostingController? private var coordinator: WebtoonView.Coordinator? var indexPath: IndexPath? private let hostingContainer = UIView() @@ -435,6 +475,8 @@ class ChapterCollectionViewCell: UICollectionViewCell { // Clear references coordinator = nil indexPath = nil + transitionHostingController?.view.removeFromSuperview() + transitionHostingController = nil } private func setupImageView() { @@ -485,6 +527,29 @@ class ChapterCollectionViewCell: UICollectionViewCell { ]) } + // set Transition page + func setTransitionPage(chapterNumber: String, indexPath: IndexPath) + { + self.currentLoadingTask = nil + imageView.isHidden = true + hostingController.view.isHidden = true + transitionHostingController?.view.removeFromSuperview() + + + + let transitionView: AnyView = AnyView(TransitionPage(index:chapterNumber)) + transitionHostingController = UIHostingController(rootView: transitionView) + transitionHostingController!.view.translatesAutoresizingMaskIntoConstraints = false + transitionHostingController!.view.backgroundColor = .black + contentView.addSubview(transitionHostingController!.view) + + NSLayoutConstraint.activate([ + transitionHostingController!.view.topAnchor.constraint(equalTo: contentView.topAnchor), + transitionHostingController!.view.bottomAnchor.constraint(equalTo: contentView.bottomAnchor), + transitionHostingController!.view.leadingAnchor.constraint(equalTo: contentView.leadingAnchor), + transitionHostingController!.view.trailingAnchor.constraint(equalTo: contentView.trailingAnchor) + ]) + } // 🔧 FIX: Smart loading state management func set(rootView: chapterView, coordinator: WebtoonView.Coordinator, indexPath: IndexPath) { self.coordinator = coordinator @@ -507,7 +572,8 @@ class ChapterCollectionViewCell: UICollectionViewCell { imageView.isHidden = true hostingController.view.isHidden = false } - + transitionHostingController?.view.removeFromSuperview() + transitionHostingController = nil guard let url = URL(string: rootView.page.content) else { return } // 🔧 FIX: Set the image with options to prevent flicker @@ -531,13 +597,14 @@ class ChapterCollectionViewCell: UICollectionViewCell { let imageSize = value.image.size // If size is not cached, update it and trigger resize - if coordinator.imageSizes[indexPath.section][indexPath.item] == nil { + let chatperIndex = indexPath.section / 2 + if coordinator.imageSizes[chatperIndex][indexPath.item] == nil { if let collectionView = self.findCollectionView() { coordinator.updateImageSize(for: indexPath, size: imageSize, collectionView: collectionView, isCached: false) } } else { // 🔧 FIX: Size is cached, update cache and reveal immediately - coordinator.imageSizes[indexPath.section][indexPath.item] = imageSize + coordinator.imageSizes[chatperIndex][indexPath.item] = imageSize self.revealImage() } From a73f3840f711a2879ca2c6dbe11ad2ba8359ca73 Mon Sep 17 00:00:00 2001 From: DawudOsman Date: Tue, 9 Dec 2025 17:32:13 +0900 Subject: [PATCH 3/4] implement chapter switching whilst using webtoon reader --- .../Reader/WebToon/webToonViewController.swift | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/Kanzen/Views/Reader/WebToon/webToonViewController.swift b/Kanzen/Views/Reader/WebToon/webToonViewController.swift index 3741f90f..4f215618 100644 --- a/Kanzen/Views/Reader/WebToon/webToonViewController.swift +++ b/Kanzen/Views/Reader/WebToon/webToonViewController.swift @@ -31,8 +31,11 @@ struct WebtoonView: UIViewRepresentable { func updateUIView(_ uiView: UICollectionView, context: Context) { //print("updateUIVIEW called") - if context.coordinator.currChapter != reader_manager.currChapter { + if context.coordinator.currChapter != reader_manager.currChapter, reader_manager.currChapter.count > 0 { print("diff CurrChapter") + print("Curr Updated Chapter is ") + print(reader_manager.currChapter) + context.coordinator.reader_manager = reader_manager context.coordinator.currChapter = reader_manager.currChapter context.coordinator.chapters = [reader_manager.currChapter] @@ -44,6 +47,9 @@ struct WebtoonView: UIViewRepresentable { context.coordinator.reader_manager = reader_manager context.coordinator.currChapter = reader_manager.currChapter context.coordinator.chapters = [reader_manager.currChapter] + context.coordinator.loadingNext = false + context.coordinator.loadingPrevious = false + } if reader_manager.changeIndex, let sectionIdx = context.coordinator.chapters.firstIndex(of: reader_manager.currChapter){ @@ -143,6 +149,8 @@ struct WebtoonView: UIViewRepresentable { } func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + print("LoadingNext is \(loadingNext)") + print("LoadingPrev is \(loadingPrevious)") if let collectionView = scrollView as? UICollectionView { let visibleIndexPaths = collectionView.indexPathsForVisibleItems if !loadingPrevious { @@ -151,7 +159,7 @@ struct WebtoonView: UIViewRepresentable { print("First cell is VISIBLE adding prev chapters") if reader_manager.prevChapter.count == 0 { - loadingNext = true + loadingPrevious = true self.reader_manager.fetchTask(bool: false){ print("completion handler called") @@ -371,7 +379,7 @@ struct WebtoonView: UIViewRepresentable { if chapters.count >= 3 { print("last chapter count \(chapters[2].count)") } - + print("currChapter count is \(chapters[chapterIndex].count)") let rootView = chapters[chapterIndex][indexPath.item].body if(indexPath.section % 2 == 0){ cell.set(rootView: rootView, coordinator: self, indexPath: indexPath) From 6147543a297146aa6866bdbddb965403ad168e48 Mon Sep 17 00:00:00 2001 From: DawudOsman Date: Sun, 11 Jan 2026 11:55:37 +0900 Subject: [PATCH 4/4] Refine Content View UI --- Kanzen/Views/Util/contentView.swift | 28 ++++++++++++------- Luna.xcodeproj/project.pbxproj | 27 +++++++++++++++--- .../xcshareddata/swiftpm/Package.resolved | 12 +++++++- Luna/Info.plist | 4 +-- Luna/Luna.entitlements | 15 +--------- 5 files changed, 55 insertions(+), 31 deletions(-) diff --git a/Kanzen/Views/Util/contentView.swift b/Kanzen/Views/Util/contentView.swift index fbd1f330..9691c87e 100644 --- a/Kanzen/Views/Util/contentView.swift +++ b/Kanzen/Views/Util/contentView.swift @@ -8,7 +8,7 @@ import SwiftUI import Foundation import Kingfisher - +import WrappingHStack struct contentView: View { @State var parentModule: ModuleDataContainer? @State var title: String @@ -61,15 +61,19 @@ struct contentView: View { if let authorArtist = contentData["authorArtist"] as? [String] { HStack{ - ForEach(Array(authorArtist.enumerated()),id: \.offset) + WrappingHStack(authorArtist,id: \.self) { - idx,item in + item in Text(item).font(.caption) + + .padding(.leading,3) .padding(.trailing,3) .background(Color.accentColor) - .cornerRadius(3) + .padding(.bottom,3) + + } } @@ -79,15 +83,19 @@ struct contentView: View { if let tags = contentData["tags"] as? [String] { HStack{ - ForEach(Array(tags.enumerated()),id: \.offset) + WrappingHStack(tags,id: \.self) { - idx,item in + item in Text(item).font(.caption) - .padding(.leading,3) - .padding(.trailing,3) - .background(Color.accentColor) - .cornerRadius(3) + .padding(.leading,3) + .padding(.trailing,3) + .background(Color.accentColor) + .cornerRadius(3) + .padding(.bottom,3) + + + } } diff --git a/Luna.xcodeproj/project.pbxproj b/Luna.xcodeproj/project.pbxproj index f26c1eab..dde06aeb 100644 --- a/Luna.xcodeproj/project.pbxproj +++ b/Luna.xcodeproj/project.pbxproj @@ -115,6 +115,7 @@ 13F5B5922E73090000565E75 /* HomeSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13F5B5912E73090000565E75 /* HomeSection.swift */; }; 13F5B5952E730CB200565E75 /* TMDBContentFilter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13F5B5942E730CB200565E75 /* TMDBContentFilter.swift */; }; 13F5B5972E730CDB00565E75 /* TMDBFiltersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 13F5B5962E730CDB00565E75 /* TMDBFiltersView.swift */; }; + 384E7AE12F13319C00FAA291 /* WrappingHStack in Frameworks */ = {isa = PBXBuildFile; productRef = 384E7AE02F13319C00FAA291 /* WrappingHStack */; }; /* End PBXBuildFile section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -249,6 +250,7 @@ 12E7AF5E2EB7FDB7003B7664 /* FakeWebKit in Frameworks */, 13A46D032EF5945700423BC6 /* SoraCore in Frameworks */, 13A46CFD2EF5943400423BC6 /* FakeWebKit in Frameworks */, + 384E7AE12F13319C00FAA291 /* WrappingHStack in Frameworks */, 13A46D062EF595DD00423BC6 /* MPVKit-GPL in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -769,6 +771,7 @@ 13A46CFF2EF5944C00423BC6 /* Kingfisher */, 13A46D022EF5945700423BC6 /* SoraCore */, 13A46D052EF595DD00423BC6 /* MPVKit-GPL */, + 384E7AE02F13319C00FAA291 /* WrappingHStack */, ); productName = Sora; productReference = 13F0725B2E4B2D3300EF90EB /* Luna.app */; @@ -803,6 +806,7 @@ 13A46CFE2EF5944C00423BC6 /* XCRemoteSwiftPackageReference "Kingfisher" */, 13A46D012EF5945700423BC6 /* XCRemoteSwiftPackageReference "SoraCore" */, 13A46D042EF595DD00423BC6 /* XCRemoteSwiftPackageReference "MPVKit" */, + 384E7ADF2F13319C00FAA291 /* XCRemoteSwiftPackageReference "WrappingHStack" */, ); productRefGroup = 13F0725C2E4B2D3300EF90EB /* Products */; projectDirPath = ""; @@ -1092,7 +1096,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = "${DEVELOPMENT_TEAM}"; + DEVELOPMENT_TEAM = C5TY8LKCH7; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = NO; ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; @@ -1109,7 +1113,7 @@ ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Luna/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "${APP_NAME}"; + INFOPLIST_KEY_CFBundleDisplayName = Luna; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; @@ -1128,6 +1132,7 @@ MARKETING_VERSION = 1.0.0; OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_IDENTIFIER}"; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = dawudosman.sora; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; @@ -1149,7 +1154,7 @@ CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEAD_CODE_STRIPPING = YES; - DEVELOPMENT_TEAM = "${DEVELOPMENT_TEAM}"; + DEVELOPMENT_TEAM = C5TY8LKCH7; ENABLE_APP_SANDBOX = YES; ENABLE_HARDENED_RUNTIME = NO; ENABLE_INCOMING_NETWORK_CONNECTIONS = NO; @@ -1166,7 +1171,7 @@ ENABLE_USER_SELECTED_FILES = readonly; GENERATE_INFOPLIST_FILE = YES; INFOPLIST_FILE = Luna/Info.plist; - INFOPLIST_KEY_CFBundleDisplayName = "${APP_NAME}"; + INFOPLIST_KEY_CFBundleDisplayName = Luna; INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.entertainment"; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphoneos*]" = YES; "INFOPLIST_KEY_UIApplicationSceneManifest_Generation[sdk=iphonesimulator*]" = YES; @@ -1185,6 +1190,7 @@ MARKETING_VERSION = 1.0.0; OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = "${BUNDLE_IDENTIFIER}"; + "PRODUCT_BUNDLE_IDENTIFIER[sdk=iphoneos*]" = dawudosman.sora; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; SUPPORTED_PLATFORMS = "appletvos appletvsimulator iphoneos iphonesimulator"; @@ -1252,6 +1258,14 @@ version = "0.40.0-xcode"; }; }; + 384E7ADF2F13319C00FAA291 /* XCRemoteSwiftPackageReference "WrappingHStack" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/dkk/WrappingHStack"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 2.2.11; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -1279,6 +1293,11 @@ package = 13A46D042EF595DD00423BC6 /* XCRemoteSwiftPackageReference "MPVKit" */; productName = "MPVKit-GPL"; }; + 384E7AE02F13319C00FAA291 /* WrappingHStack */ = { + isa = XCSwiftPackageProductDependency; + package = 384E7ADF2F13319C00FAA291 /* XCRemoteSwiftPackageReference "WrappingHStack" */; + productName = WrappingHStack; + }; /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ diff --git a/Luna.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/Luna.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index a90d04f9..4ce2e73e 100644 --- a/Luna.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/Luna.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,4 +1,5 @@ { + "originHash" : "4af77fd9d0b22d3ede401304eb96d7bd60df69ca70c6a21d497092e39009d052", "pins" : [ { "identity" : "fakewebkit", @@ -35,7 +36,16 @@ "branch" : "main", "revision" : "50ddc160fd606e9708adefc1d9fc7003b574446b" } + }, + { + "identity" : "wrappinghstack", + "kind" : "remoteSourceControl", + "location" : "https://github.com/dkk/WrappingHStack", + "state" : { + "revision" : "425d9488ba55f58f0b34498c64c054c77fc2a44b", + "version" : "2.2.11" + } } ], - "version" : 2 + "version" : 3 } diff --git a/Luna/Info.plist b/Luna/Info.plist index 732c7e53..fa2e952d 100644 --- a/Luna/Info.plist +++ b/Luna/Info.plist @@ -23,7 +23,7 @@ fetch processing - iCloudContainerID - $(ICLOUD_CONTAINER_ID) + iCloudContainerID + $(ICLOUD_CONTAINER_ID) diff --git a/Luna/Luna.entitlements b/Luna/Luna.entitlements index 21e2de8a..0c67376e 100644 --- a/Luna/Luna.entitlements +++ b/Luna/Luna.entitlements @@ -1,18 +1,5 @@ - - aps-environment - development - com.apple.developer.aps-environment - development - com.apple.developer.icloud-container-identifiers - - iCloud.me.cranci.sora - - com.apple.developer.icloud-services - - CloudKit - - +